/*
 * Copyright (C) 2015 Red Hat, Inc.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library;  If not, see
 * <http://www.gnu.org/licenses/>.
 *
 * Author: Daniel P. Berrange <berrange@redhat.com>
 */

#include <config.h>
#include <sys/stat.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>

#include "virrotatingfile.h"
#include "virlog.h"
#include "testutils.h"

#define VIR_FROM_THIS VIR_FROM_NONE

VIR_LOG_INIT("tests.rotatingfiletest");

#define FILENAME "virrotatingfiledata.txt"
#define FILENAME0 "virrotatingfiledata.txt.0"
#define FILENAME1 "virrotatingfiledata.txt.1"

#define FILEBYTE 0xde
#define FILEBYTE0 0xad
#define FILEBYTE1 0xbe

static int testRotatingFileWriterAssertOneFileSize(const char *filename,
                                                   off_t size)
{
    struct stat sb;

    if (stat(filename, &sb) < 0) {
        if (size == (off_t)-1) {
            return 0;
        } else {
            fprintf(stderr, "File %s does not exist\n", filename);
            return -1;
        }
    } else {
        if (size == (off_t)-1) {
            fprintf(stderr, "File %s should not exist\n", filename);
            return -1;
        } else if (sb.st_size != size) {
            fprintf(stderr, "File %s should be %llu bytes not %llu\n",
                    filename, (unsigned long long)size,
                    (unsigned long long)sb.st_size);
            return -1;
        } else {
            return 0;
        }
    }
}

static int testRotatingFileWriterAssertFileSizes(off_t baseSize,
                                                 off_t backup0Size,
                                                 off_t backup1Size)
{
    if (testRotatingFileWriterAssertOneFileSize(FILENAME, baseSize) < 0 ||
        testRotatingFileWriterAssertOneFileSize(FILENAME0, backup0Size) < 0 ||
        testRotatingFileWriterAssertOneFileSize(FILENAME1, backup1Size) < 0)
        return -1;
    return 0;
}


static int testRotatingFileReaderAssertBufferContent(const char *buf,
                                                     size_t buflen,
                                                     size_t nregions,
                                                     size_t *sizes)
{
    size_t i, j;
    char bytes[] = { FILEBYTE, FILEBYTE0, FILEBYTE1 };
    size_t total = 0;

    if (nregions > ARRAY_CARDINALITY(bytes)) {
        fprintf(stderr, "Too many regions %zu\n", nregions);
        return -1;
    }

    for (i = 0; i < nregions; i++)
        total += sizes[i];

    if (total != buflen) {
        fprintf(stderr, "Expected %zu bytes in file not %zu\n",
                total, buflen);
        return -1;
    }

    for (i = 0; i < nregions; i++) {
        char want = bytes[nregions - (i + 1)];
        for (j = 0; j < sizes[i]; j++) {
            if (*buf != want) {
                fprintf(stderr,
                        "Expected '0x%x' but got '0x%x' at region %zu byte %zu\n",
                        want & 0xff, *buf & 0xff, i, j);
                return -1;
            }
            buf++;
        }
    }

    return 0;
}


static int testRotatingFileInitOne(const char *filename,
                                   off_t size,
                                   char pattern)
{
    if (size == (off_t)-1) {
        VIR_DEBUG("Deleting %s", filename);
        unlink(filename);
    } else {
        VIR_DEBUG("Creating %s size %zu", filename, (size_t)size);
        char buf[1024];
        int fd = open(filename, O_WRONLY|O_CREAT|O_TRUNC, 0700);
        if (fd < 0) {
            fprintf(stderr, "Cannot create %s\n", filename);
            return -1;
        }
        memset(buf, pattern, sizeof(buf));
        while (size) {
            size_t towrite = size;
            if (towrite > sizeof(buf))
                towrite = sizeof(buf);

            if (safewrite(fd, buf, towrite) != towrite) {
                fprintf(stderr, "Cannot write to %s\n", filename);
                VIR_FORCE_CLOSE(fd);
                return -1;
            }
            size -= towrite;
        }
        VIR_FORCE_CLOSE(fd);
    }
    return 0;
}

static int testRotatingFileInitFiles(off_t baseSize,
                                     off_t backup0Size,
                                     off_t backup1Size)
{
    if (testRotatingFileInitOne(FILENAME, baseSize, FILEBYTE) < 0 ||
        testRotatingFileInitOne(FILENAME0, backup0Size, FILEBYTE0) < 0 ||
        testRotatingFileInitOne(FILENAME1, backup1Size, FILEBYTE1) < 0) {
        return -1;
    }
    return 0;
}

static int testRotatingFileWriterNew(const void *data ATTRIBUTE_UNUSED)
{
    virRotatingFileWriterPtr file;
    int ret = -1;
    char buf[512];

    if (testRotatingFileInitFiles((off_t)-1,
                                  (off_t)-1,
                                  (off_t)-1) < 0)
        return -1;

    file = virRotatingFileWriterNew(FILENAME,
                                    1024,
                                    2,
                                    false,
                                    0700);
    if (!file)
        goto cleanup;

    if (testRotatingFileWriterAssertFileSizes(0,
                                              (off_t)-1,
                                              (off_t)-1) < 0)
        goto cleanup;

    memset(buf, 0x5e, sizeof(buf));

    virRotatingFileWriterAppend(file, buf, sizeof(buf));

    if (testRotatingFileWriterAssertFileSizes(sizeof(buf),
                                              (off_t)-1,
                                              (off_t)-1) < 0)
        goto cleanup;

    ret = 0;
 cleanup:
    virRotatingFileWriterFree(file);
    unlink(FILENAME);
    unlink(FILENAME0);
    unlink(FILENAME1);
    return ret;
}


static int testRotatingFileWriterAppend(const void *data ATTRIBUTE_UNUSED)
{
    virRotatingFileWriterPtr file;
    int ret = -1;
    char buf[512];

    if (testRotatingFileInitFiles(512,
                                  (off_t)-1,
                                  (off_t)-1) < 0)
        return -1;

    file = virRotatingFileWriterNew(FILENAME,
                                    1024,
                                    2,
                                    false,
                                    0700);
    if (!file)
        goto cleanup;

    if (testRotatingFileWriterAssertFileSizes(512,
                                              (off_t)-1,
                                              (off_t)-1) < 0)
        goto cleanup;

    memset(buf, 0x5e, sizeof(buf));

    virRotatingFileWriterAppend(file, buf, sizeof(buf));

    if (testRotatingFileWriterAssertFileSizes(1024,
                                              (off_t)-1,
                                              (off_t)-1) < 0)
        goto cleanup;

    ret = 0;
 cleanup:
    virRotatingFileWriterFree(file);
    unlink(FILENAME);
    unlink(FILENAME0);
    unlink(FILENAME1);
    return ret;
}


static int testRotatingFileWriterTruncate(const void *data ATTRIBUTE_UNUSED)
{
    virRotatingFileWriterPtr file;
    int ret = -1;
    char buf[512];

    if (testRotatingFileInitFiles(512,
                                  (off_t)-1,
                                  (off_t)-1) < 0)
        return -1;

    file = virRotatingFileWriterNew(FILENAME,
                                    1024,
                                    2,
                                    true,
                                    0700);
    if (!file)
        goto cleanup;

    if (testRotatingFileWriterAssertFileSizes(0,
                                              (off_t)-1,
                                              (off_t)-1) < 0)
        goto cleanup;

    memset(buf, 0x5e, sizeof(buf));

    virRotatingFileWriterAppend(file, buf, sizeof(buf));

    if (testRotatingFileWriterAssertFileSizes(512,
                                              (off_t)-1,
                                              (off_t)-1) < 0)
        goto cleanup;

    ret = 0;
 cleanup:
    virRotatingFileWriterFree(file);
    unlink(FILENAME);
    unlink(FILENAME0);
    unlink(FILENAME1);
    return ret;
}


static int testRotatingFileWriterRolloverNone(const void *data ATTRIBUTE_UNUSED)
{
    virRotatingFileWriterPtr file;
    int ret = -1;
    char buf[512];

    if (testRotatingFileInitFiles((off_t)-1,
                                  (off_t)-1,
                                  (off_t)-1) < 0)
        return -1;

    file = virRotatingFileWriterNew(FILENAME,
                                    200,
                                    0,
                                    false,
                                    0700);
    if (!file)
        goto cleanup;

    if (testRotatingFileWriterAssertFileSizes(0,
                                              (off_t)-1,
                                              (off_t)-1) < 0)
        goto cleanup;

    memset(buf, 0x5e, sizeof(buf));

    virRotatingFileWriterAppend(file, buf, sizeof(buf));

    if (testRotatingFileWriterAssertFileSizes(112,
                                              (off_t)-1,
                                              (off_t)-1) < 0)
        goto cleanup;

    ret = 0;
 cleanup:
    virRotatingFileWriterFree(file);
    unlink(FILENAME);
    unlink(FILENAME0);
    unlink(FILENAME1);
    return ret;
}


static int testRotatingFileWriterRolloverOne(const void *data ATTRIBUTE_UNUSED)
{
    virRotatingFileWriterPtr file;
    int ret = -1;
    char buf[512];

    if (testRotatingFileInitFiles((off_t)-1,
                                  (off_t)-1,
                                  (off_t)-1) < 0)
        return -1;

    file = virRotatingFileWriterNew(FILENAME,
                                    1024,
                                    2,
                                    false,
                                    0700);
    if (!file)
        goto cleanup;

    if (testRotatingFileWriterAssertFileSizes(0,
                                              (off_t)-1,
                                              (off_t)-1) < 0)
        goto cleanup;

    memset(buf, 0x5e, sizeof(buf));

    virRotatingFileWriterAppend(file, buf, sizeof(buf));
    virRotatingFileWriterAppend(file, buf, sizeof(buf));
    virRotatingFileWriterAppend(file, buf, sizeof(buf));

    if (testRotatingFileWriterAssertFileSizes(512,
                                              1024,
                                              (off_t)-1) < 0)
        goto cleanup;

    ret = 0;
 cleanup:
    virRotatingFileWriterFree(file);
    unlink(FILENAME);
    unlink(FILENAME0);
    unlink(FILENAME1);
    return ret;
}


static int testRotatingFileWriterRolloverAppend(const void *data ATTRIBUTE_UNUSED)
{
    virRotatingFileWriterPtr file;
    int ret = -1;
    char buf[512];

    if (testRotatingFileInitFiles((off_t)768,
                                  (off_t)-1,
                                  (off_t)-1) < 0)
        return -1;

    file = virRotatingFileWriterNew(FILENAME,
                                    1024,
                                    2,
                                    false,
                                    0700);
    if (!file)
        goto cleanup;

    if (testRotatingFileWriterAssertFileSizes(768,
                                              (off_t)-1,
                                              (off_t)-1) < 0)
        goto cleanup;

    memset(buf, 0x5e, sizeof(buf));

    virRotatingFileWriterAppend(file, buf, sizeof(buf));

    if (testRotatingFileWriterAssertFileSizes(256,
                                              1024,
                                              (off_t)-1) < 0)
        goto cleanup;

    ret = 0;
 cleanup:
    virRotatingFileWriterFree(file);
    unlink(FILENAME);
    unlink(FILENAME0);
    unlink(FILENAME1);
    return ret;
}


static int testRotatingFileWriterRolloverMany(const void *data ATTRIBUTE_UNUSED)
{
    virRotatingFileWriterPtr file;
    int ret = -1;
    char buf[512];

    if (testRotatingFileInitFiles((off_t)-1,
                                  (off_t)-1,
                                  (off_t)-1) < 0)
        return -1;

    file = virRotatingFileWriterNew(FILENAME,
                                    1024,
                                    2,
                                    false,
                                    0700);
    if (!file)
        goto cleanup;

    if (testRotatingFileWriterAssertFileSizes(0,
                                              (off_t)-1,
                                              (off_t)-1) < 0)
        goto cleanup;

    memset(buf, 0x5e, sizeof(buf));

    virRotatingFileWriterAppend(file, buf, sizeof(buf));
    virRotatingFileWriterAppend(file, buf, sizeof(buf));
    virRotatingFileWriterAppend(file, buf, sizeof(buf));
    virRotatingFileWriterAppend(file, buf, sizeof(buf));
    virRotatingFileWriterAppend(file, buf, sizeof(buf));
    virRotatingFileWriterAppend(file, buf, sizeof(buf));
    virRotatingFileWriterAppend(file, buf, sizeof(buf));

    if (testRotatingFileWriterAssertFileSizes(512,
                                              1024,
                                              1024) < 0)
        goto cleanup;

    ret = 0;
 cleanup:
    virRotatingFileWriterFree(file);
    unlink(FILENAME);
    unlink(FILENAME0);
    unlink(FILENAME1);
    return ret;
}


static int testRotatingFileWriterRolloverLineBreak(const void *data ATTRIBUTE_UNUSED)
{
    virRotatingFileWriterPtr file;
    int ret = -1;
    const char *buf = "The quick brown fox jumps over the lazy dog\n"
        "The wizard quickly jinxed the gnomes before they vaporized\n";

    if (testRotatingFileInitFiles(100,
                                  (off_t)-1,
                                  (off_t)-1) < 0)
        return -1;

    file = virRotatingFileWriterNew(FILENAME,
                                    160,
                                    2,
                                    false,
                                    0700);
    if (!file)
        goto cleanup;

    if (testRotatingFileWriterAssertFileSizes(100,
                                              (off_t)-1,
                                              (off_t)-1) < 0)
        goto cleanup;

    virRotatingFileWriterAppend(file, buf, strlen(buf));

    if (testRotatingFileWriterAssertFileSizes(59,
                                              144,
                                              (off_t)-1) < 0)
        goto cleanup;

    ret = 0;
 cleanup:
    virRotatingFileWriterFree(file);
    unlink(FILENAME);
    unlink(FILENAME0);
    unlink(FILENAME1);
    return ret;
}


static int testRotatingFileWriterLargeFile(const void *data ATTRIBUTE_UNUSED)
{
    virRotatingFileWriterPtr file;
    int ret = -1;
    const char *buf = "The quick brown fox jumps over the lazy dog\n"
        "The wizard quickly jinxed the gnomes before they vaporized\n";

    if (testRotatingFileInitFiles(200,
                                  (off_t)-1,
                                  (off_t)-1) < 0)
        return -1;

    file = virRotatingFileWriterNew(FILENAME,
                                    160,
                                    2,
                                    false,
                                    0700);
    if (!file)
        goto cleanup;

    if (testRotatingFileWriterAssertFileSizes(200,
                                              (off_t)-1,
                                              (off_t)-1) < 0)
        goto cleanup;

    virRotatingFileWriterAppend(file, buf, strlen(buf));

    if (testRotatingFileWriterAssertFileSizes(103,
                                              200,
                                              (off_t)-1) < 0)
        goto cleanup;

    ret = 0;
 cleanup:
    virRotatingFileWriterFree(file);
    unlink(FILENAME);
    unlink(FILENAME0);
    unlink(FILENAME1);
    return ret;
}


static int testRotatingFileReaderOne(const void *data ATTRIBUTE_UNUSED)
{
    virRotatingFileReaderPtr file;
    int ret = -1;
    char buf[512];
    ssize_t got;
    size_t regions[] = { 256 };

    if (testRotatingFileInitFiles(256, (off_t)-1, (off_t)-1) < 0)
        return -1;

    file = virRotatingFileReaderNew(FILENAME, 2);
    if (!file)
        goto cleanup;

    if ((got = virRotatingFileReaderConsume(file, buf, sizeof(buf))) < 0)
        goto cleanup;

    if (testRotatingFileReaderAssertBufferContent(buf, got,
                                                  ARRAY_CARDINALITY(regions),
                                                  regions) < 0)
        goto cleanup;

    ret = 0;
 cleanup:
    virRotatingFileReaderFree(file);
    unlink(FILENAME);
    unlink(FILENAME0);
    unlink(FILENAME1);
    return ret;
}

static int testRotatingFileReaderAll(const void *data ATTRIBUTE_UNUSED)
{
    virRotatingFileReaderPtr file;
    int ret = -1;
    char buf[768];
    ssize_t got;
    size_t regions[] = { 256, 256, 256 };

    if (testRotatingFileInitFiles(256, 256, 256) < 0)
        return -1;

    file = virRotatingFileReaderNew(FILENAME, 2);
    if (!file)
        goto cleanup;

    if ((got = virRotatingFileReaderConsume(file, buf, sizeof(buf))) < 0)
        goto cleanup;

    if (testRotatingFileReaderAssertBufferContent(buf, got,
                                                  ARRAY_CARDINALITY(regions),
                                                  regions) < 0)
        goto cleanup;

    ret = 0;
 cleanup:
    virRotatingFileReaderFree(file);
    unlink(FILENAME);
    unlink(FILENAME0);
    unlink(FILENAME1);
    return ret;
}

static int testRotatingFileReaderPartial(const void *data ATTRIBUTE_UNUSED)
{
    virRotatingFileReaderPtr file;
    int ret = -1;
    char buf[600];
    ssize_t got;
    size_t regions[] = { 256, 256, 88 };

    if (testRotatingFileInitFiles(256, 256, 256) < 0)
        return -1;

    file = virRotatingFileReaderNew(FILENAME, 2);
    if (!file)
        goto cleanup;

    if ((got = virRotatingFileReaderConsume(file, buf, sizeof(buf))) < 0)
        goto cleanup;

    if (testRotatingFileReaderAssertBufferContent(buf, got,
                                                  ARRAY_CARDINALITY(regions),
                                                  regions) < 0)
        goto cleanup;

    ret = 0;
 cleanup:
    virRotatingFileReaderFree(file);
    unlink(FILENAME);
    unlink(FILENAME0);
    unlink(FILENAME1);
    return ret;
}

static int testRotatingFileReaderSeek(const void *data ATTRIBUTE_UNUSED)
{
    virRotatingFileReaderPtr file;
    int ret = -1;
    char buf[600];
    ssize_t got;
    size_t regions[] = { 156, 256 };
    struct stat sb;

    if (testRotatingFileInitFiles(256, 256, 256) < 0)
        return -1;

    file = virRotatingFileReaderNew(FILENAME, 2);
    if (!file)
        goto cleanup;

    if (stat(FILENAME0, &sb) < 0) {
        virReportSystemError(errno, "Cannot stat %s", FILENAME0);
        goto cleanup;
    }

    if (virRotatingFileReaderSeek(file, sb.st_ino, 100) < 0)
        goto cleanup;

    if ((got = virRotatingFileReaderConsume(file, buf, sizeof(buf))) < 0)
        goto cleanup;

    if (testRotatingFileReaderAssertBufferContent(buf, got,
                                                  ARRAY_CARDINALITY(regions),
                                                  regions) < 0)
        goto cleanup;

    ret = 0;
 cleanup:
    virRotatingFileReaderFree(file);
    unlink(FILENAME);
    unlink(FILENAME0);
    unlink(FILENAME1);
    return ret;
}

static int
mymain(void)
{
    int ret = 0;

    if (virtTestRun("Rotating file write new", testRotatingFileWriterNew, NULL) < 0)
        ret = -1;

    if (virtTestRun("Rotating file write append", testRotatingFileWriterAppend, NULL) < 0)
        ret = -1;

    if (virtTestRun("Rotating file write truncate", testRotatingFileWriterTruncate, NULL) < 0)
        ret = -1;

    if (virtTestRun("Rotating file write rollover no backup", testRotatingFileWriterRolloverNone, NULL) < 0)
        ret = -1;

    if (virtTestRun("Rotating file write rollover one", testRotatingFileWriterRolloverOne, NULL) < 0)
        ret = -1;

    if (virtTestRun("Rotating file write rollover append", testRotatingFileWriterRolloverAppend, NULL) < 0)
        ret = -1;

    if (virtTestRun("Rotating file write rollover many", testRotatingFileWriterRolloverMany, NULL) < 0)
        ret = -1;

    if (virtTestRun("Rotating file write rollover line break", testRotatingFileWriterRolloverLineBreak, NULL) < 0)
        ret = -1;

    if (virtTestRun("Rotating file write to file larger then maxlen", testRotatingFileWriterLargeFile, NULL) < 0)
        ret = -1;

    if (virtTestRun("Rotating file read one", testRotatingFileReaderOne, NULL) < 0)
        ret = -1;

    if (virtTestRun("Rotating file read all", testRotatingFileReaderAll, NULL) < 0)
        ret = -1;

    if (virtTestRun("Rotating file read partial", testRotatingFileReaderPartial, NULL) < 0)
        ret = -1;

    if (virtTestRun("Rotating file read seek", testRotatingFileReaderSeek, NULL) < 0)
        ret = -1;

    return ret == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
}

VIRT_TEST_MAIN(mymain)
