diff --git a/po/POTFILES.in b/po/POTFILES.in index 0cc5b99ada..401ac6f4e3 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -214,6 +214,7 @@ src/util/virpolkit.c src/util/virportallocator.c src/util/virprocess.c src/util/virrandom.c +src/util/virrotatingfile.c src/util/virsexpr.c src/util/virscsi.c src/util/virsocketaddr.c diff --git a/src/Makefile.am b/src/Makefile.am index 99b4993a2d..ee082ec2bb 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -150,6 +150,7 @@ UTIL_SOURCES = \ util/virprobe.h \ util/virprocess.c util/virprocess.h \ util/virrandom.h util/virrandom.c \ + util/virrotatingfile.h util/virrotatingfile.c \ util/virscsi.c util/virscsi.h \ util/virseclabel.c util/virseclabel.h \ util/virsexpr.c util/virsexpr.h \ diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index 7e60d87539..b112023f1b 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -2047,6 +2047,19 @@ virRandomGenerateWWN; virRandomInt; +# util/virrotatingfile.h +virRotatingFileReaderConsume; +virRotatingFileReaderFree; +virRotatingFileReaderNew; +virRotatingFileReaderSeek; +virRotatingFileWriterAppend; +virRotatingFileWriterFree; +virRotatingFileWriterGetINode; +virRotatingFileWriterGetOffset; +virRotatingFileWriterGetPath; +virRotatingFileWriterNew; + + # util/virscsi.h virSCSIDeviceFileIterate; virSCSIDeviceFree; diff --git a/src/util/virrotatingfile.c b/src/util/virrotatingfile.c new file mode 100644 index 0000000000..1260710301 --- /dev/null +++ b/src/util/virrotatingfile.c @@ -0,0 +1,639 @@ +/* + * virrotatingfile.c: file I/O with size rotation + * + * 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 + * . + * + */ + +#include + +#include +#include +#include + +#include "virrotatingfile.h" +#include "viralloc.h" +#include "virerror.h" +#include "virstring.h" +#include "virfile.h" +#include "virlog.h" + +VIR_LOG_INIT("util.rotatingfile"); + +#define VIR_FROM_THIS VIR_FROM_NONE + +#define VIR_MAX_MAX_BACKUP 32 + +typedef struct virRotatingFileWriterEntry virRotatingFileWriterEntry; +typedef virRotatingFileWriterEntry *virRotatingFileWriterEntryPtr; + +typedef struct virRotatingFileReaderEntry virRotatingFileReaderEntry; +typedef virRotatingFileReaderEntry *virRotatingFileReaderEntryPtr; + +struct virRotatingFileWriterEntry { + int fd; + off_t inode; + off_t pos; + off_t len; +}; + +struct virRotatingFileWriter { + char *basepath; + virRotatingFileWriterEntryPtr entry; + size_t maxbackup; + mode_t mode; + size_t maxlen; +}; + + +struct virRotatingFileReaderEntry { + char *path; + int fd; + off_t inode; +}; + +struct virRotatingFileReader { + virRotatingFileReaderEntryPtr *entries; + size_t nentries; + size_t current; +}; + + +static void +virRotatingFileWriterEntryFree(virRotatingFileWriterEntryPtr entry) +{ + if (!entry) + return; + + VIR_FORCE_CLOSE(entry->fd); + VIR_FREE(entry); +} + + +static void +virRotatingFileReaderEntryFree(virRotatingFileReaderEntryPtr entry) +{ + if (!entry) + return; + + VIR_FREE(entry->path); + VIR_FORCE_CLOSE(entry->fd); + VIR_FREE(entry); +} + + +static virRotatingFileWriterEntryPtr +virRotatingFileWriterEntryNew(const char *path, + mode_t mode) +{ + virRotatingFileWriterEntryPtr entry; + struct stat sb; + + VIR_DEBUG("Opening %s mode=%02o", path, mode); + + if (VIR_ALLOC(entry) < 0) + return NULL; + + if ((entry->fd = open(path, O_CREAT|O_APPEND|O_WRONLY, mode)) < 0) { + virReportSystemError(errno, + _("Unable to open file: %s"), path); + goto error; + } + + entry->pos = lseek(entry->fd, 0, SEEK_END); + if (entry->pos == (off_t)-1) { + virReportSystemError(errno, + _("Unable to determine current file offset: %s"), + path); + goto error; + } + + if (fstat(entry->fd, &sb) < 0) { + virReportSystemError(errno, + _("Unable to determine current file inode: %s"), + path); + goto error; + } + + entry->len = sb.st_size; + entry->inode = sb.st_ino; + + return entry; + + error: + virRotatingFileWriterEntryFree(entry); + return NULL; +} + + +static virRotatingFileReaderEntryPtr +virRotatingFileReaderEntryNew(const char *path) +{ + virRotatingFileReaderEntryPtr entry; + struct stat sb; + + VIR_DEBUG("Opening %s", path); + + if (VIR_ALLOC(entry) < 0) + return NULL; + + if ((entry->fd = open(path, O_RDONLY)) < 0) { + if (errno != ENOENT) { + virReportSystemError(errno, + _("Unable to open file: %s"), path); + goto error; + } + } + + if (entry->fd != -1) { + if (fstat(entry->fd, &sb) < 0) { + virReportSystemError(errno, + _("Unable to determine current file inode: %s"), + path); + goto error; + } + + entry->inode = sb.st_ino; + } + + if (VIR_STRDUP(entry->path, path) < 0) + goto error; + + return entry; + + error: + virRotatingFileReaderEntryFree(entry); + return NULL; +} + + +static int +virRotatingFileWriterDelete(virRotatingFileWriterPtr file) +{ + size_t i; + + if (unlink(file->basepath) < 0 && + errno != ENOENT) { + virReportSystemError(errno, + _("Unable to delete file %s"), + file->basepath); + return -1; + } + + for (i = 0; i < file->maxbackup; i++) { + char *oldpath; + if (virAsprintf(&oldpath, "%s.%zu", file->basepath, i) < 0) + return -1; + + if (unlink(oldpath) < 0 && + errno != ENOENT) { + virReportSystemError(errno, + _("Unable to delete file %s"), + oldpath); + VIR_FREE(oldpath); + return -1; + } + VIR_FREE(oldpath); + } + + return 0; +} + + +/** + * virRotatingFileWriterNew + * @path: the base path for files + * @maxlen: the maximum number of bytes to write before rollover + * @maxbackup: number of backup files to keep when rolling over + * @truncate: whether to truncate the current files when opening + * @mode: the file mode to use for creating new files + * + * Create a new object for writing data to a file with + * automatic rollover. If @maxbackup is zero, no backup + * files will be created. The primary file will just get + * truncated and reopened. + * + * The files will never exceed @maxlen bytes in size, + * but may be rolled over before they reach this size + * in order to avoid splitting lines + */ +virRotatingFileWriterPtr +virRotatingFileWriterNew(const char *path, + off_t maxlen, + size_t maxbackup, + bool truncate, + mode_t mode) +{ + virRotatingFileWriterPtr file; + + if (VIR_ALLOC(file) < 0) + goto error; + + if (VIR_STRDUP(file->basepath, path) < 0) + goto error; + + if (maxbackup > VIR_MAX_MAX_BACKUP) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("Max backup %zu must be less than or equal to %d"), + maxbackup, VIR_MAX_MAX_BACKUP); + goto error; + } + + file->mode = mode; + file->maxbackup = maxbackup; + file->maxlen = maxlen; + + if (truncate && + virRotatingFileWriterDelete(file) < 0) + goto error; + + if (!(file->entry = virRotatingFileWriterEntryNew(file->basepath, + mode))) + goto error; + + return file; + + error: + virRotatingFileWriterFree(file); + return NULL; +} + + +/** + * virRotatingFileReaderNew: + * @path: the base path for files + * @maxbackup: number of backup files to read history from + * + * Create a new object for reading from a set of rolling files. + * I/O will start from the oldest file and proceed through + * files until the end of the newest one. + * + * If @maxbackup is zero the only the newest file will be read. + */ +virRotatingFileReaderPtr +virRotatingFileReaderNew(const char *path, + size_t maxbackup) +{ + virRotatingFileReaderPtr file; + size_t i; + + if (VIR_ALLOC(file) < 0) + goto error; + + if (maxbackup > VIR_MAX_MAX_BACKUP) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("Max backup %zu must be less than or equal to %d"), + maxbackup, VIR_MAX_MAX_BACKUP); + goto error; + } + + file->nentries = maxbackup + 1; + if (VIR_ALLOC_N(file->entries, file->nentries) < 0) + goto error; + + if (!(file->entries[file->nentries - 1] = virRotatingFileReaderEntryNew(path))) + goto error; + + for (i = 0; i < maxbackup; i++) { + char *tmppath; + if (virAsprintf(&tmppath, "%s.%zu", path, i) < 0) + goto error; + + file->entries[file->nentries - (i + 2)] = virRotatingFileReaderEntryNew(tmppath); + VIR_FREE(tmppath); + if (!file->entries[file->nentries - (i + 2)]) + goto error; + } + + return file; + + error: + virRotatingFileReaderFree(file); + return NULL; +} + + +/** + * virRotatingFileWriterGetPath: + * @file: the file context + * + * Return the primary file path + */ +const char * +virRotatingFileWriterGetPath(virRotatingFileWriterPtr file) +{ + return file->basepath; +} + + +/** + * virRotatingFileWriterGetINode: + * @file: the file context + * + * Return the inode of the file currently being written to + */ +ino_t +virRotatingFileWriterGetINode(virRotatingFileWriterPtr file) +{ + return file->entry->inode; +} + + +/** + * virRotatingFileWriterGetOffset: + * @file: the file context + * + * Return the offset at which data is currently being written + */ +off_t +virRotatingFileWriterGetOffset(virRotatingFileWriterPtr file) +{ + return file->entry->pos; +} + + +static int +virRotatingFileWriterRollover(virRotatingFileWriterPtr file) +{ + size_t i; + char *nextpath = NULL; + char *thispath = NULL; + int ret = -1; + + VIR_DEBUG("Rollover %s", file->basepath); + if (file->maxbackup == 0) { + if (unlink(file->basepath) < 0 && + errno != ENOENT) { + virReportSystemError(errno, + _("Unable to remove %s"), + file->basepath); + goto cleanup; + } + } else { + if (virAsprintf(&nextpath, "%s.%zu", file->basepath, file->maxbackup - 1) < 0) + return -1; + + for (i = file->maxbackup; i > 0; i--) { + if (i == 1) { + if (VIR_STRDUP(thispath, file->basepath) < 0) + goto cleanup; + } else { + if (virAsprintf(&thispath, "%s.%zu", file->basepath, i - 2) < 0) + goto cleanup; + } + VIR_DEBUG("Rollover %s -> %s", thispath, nextpath); + + if (rename(thispath, nextpath) < 0 && + errno != ENOENT) { + virReportSystemError(errno, + _("Unable to rename %s to %s"), + thispath, nextpath); + goto cleanup; + } + + VIR_FREE(nextpath); + nextpath = thispath; + thispath = NULL; + } + } + + VIR_DEBUG("Rollover done %s", file->basepath); + + ret = 0; + cleanup: + VIR_FREE(nextpath); + VIR_FREE(thispath); + return ret; +} + + +/** + * virRotatingFileWriterAppend: + * @file: the file context + * @buf: the data buffer + * @len: the number of bytes in @buf + * + * Append the data in @buf to the file, performing rollover + * of the files if their size would exceed the limit + * + * Returns the number of bytes written, or -1 on error + */ +ssize_t +virRotatingFileWriterAppend(virRotatingFileWriterPtr file, + const char *buf, + size_t len) +{ + ssize_t ret = 0; + size_t i; + while (len) { + size_t towrite = len; + bool forceRollover = false; + + if ((file->entry->pos + towrite) > file->maxlen) { + towrite = file->maxlen - file->entry->pos; + + /* + * If there's a newline in the last 80 chars + * we're about to write, then break at that + * point to avoid splitting lines across + * separate files + */ + for (i = 0; i < towrite && i < 80; i++) { + if (buf[towrite - i - 1] == '\n') { + towrite -= i; + forceRollover = true; + break; + } + } + } + + if (towrite) { + if (safewrite(file->entry->fd, buf, towrite) != towrite) { + virReportSystemError(errno, + _("Unable to write to file %s"), + file->basepath); + return -1; + } + + len -= towrite; + buf += towrite; + ret += towrite; + file->entry->pos += towrite; + file->entry->len += towrite; + } + + if ((file->entry->pos == file->maxlen && len) || + forceRollover) { + virRotatingFileWriterEntryPtr tmp = file->entry; + VIR_DEBUG("Hit max size %zu on %s (force=%d)\n", + file->maxlen, file->basepath, forceRollover); + + if (virRotatingFileWriterRollover(file) < 0) + return -1; + + if (!(file->entry = virRotatingFileWriterEntryNew(file->basepath, + file->mode))) + return -1; + + virRotatingFileWriterEntryFree(tmp); + } + } + + return ret; +} + + +/** + * virRotatingFileReaderSeek + * @file: the file context + * @inode: the inode of the file to seek to + * @offset: the offset within the file to seek to + * + * Seek to @offset in the file identified by @inode. + * If no file with a inode matching @inode currently + * exists, then seeks to the start of the oldest + * file, on the basis that the requested file has + * probably been rotated out of existance + */ +int +virRotatingFileReaderSeek(virRotatingFileReaderPtr file, + ino_t inode, + off_t offset) +{ + size_t i; + off_t ret; + + for (i = 0; i < file->nentries; i++) { + virRotatingFileReaderEntryPtr entry = file->entries[i]; + if (entry->inode != inode || + entry->fd == -1) + continue; + + ret = lseek(entry->fd, offset, SEEK_SET); + if (ret == (off_t)-1) { + virReportSystemError(errno, + _("Unable to seek to inode %llu offset %llu"), + (unsigned long long)inode, (unsigned long long)offset); + return -1; + } + + file->current = i; + return 0; + } + + file->current = 0; + ret = lseek(file->entries[0]->fd, offset, SEEK_SET); + if (ret == (off_t)-1) { + virReportSystemError(errno, + _("Unable to seek to inode %llu offset %llu"), + (unsigned long long)inode, (unsigned long long)offset); + return -1; + } + return 0; +} + + +/** + * virRotatingFileReaderConsume: + * @file: the file context + * @buf: the buffer to fill with data + * @len: the size of @buf + * + * Reads data from the file starting at the current offset. + * The returned data may be pulled from multiple files. + * + * Returns: the number of bytes read or -1 on error + */ +ssize_t +virRotatingFileReaderConsume(virRotatingFileReaderPtr file, + char *buf, + size_t len) +{ + ssize_t ret = 0; + + VIR_DEBUG("Consume %p %zu\n", buf, len); + while (len) { + virRotatingFileReaderEntryPtr entry; + ssize_t got; + + if (file->current >= file->nentries) + break; + + entry = file->entries[file->current]; + if (entry->fd == -1) { + file->current++; + continue; + } + + got = saferead(entry->fd, buf + ret, len); + if (got < 0) { + virReportSystemError(errno, + _("Unable to read from file %s"), + entry->path); + return -1; + } + + if (got == 0) { + file->current++; + continue; + } + + ret += got; + len -= got; + } + + return ret; +} + + +/** + * virRotatingFileWriterFree: + * @file: the file context + * + * Close the current file and release all resources + */ +void +virRotatingFileWriterFree(virRotatingFileWriterPtr file) +{ + if (!file) + return; + + virRotatingFileWriterEntryFree(file->entry); + VIR_FREE(file->basepath); + VIR_FREE(file); +} + + +/** + * virRotatingFileReaderFree: + * @file: the file context + * + * Close the files and release all resources + */ +void +virRotatingFileReaderFree(virRotatingFileReaderPtr file) +{ + size_t i; + + if (!file) + return; + + for (i = 0; i < file->nentries; i++) + virRotatingFileReaderEntryFree(file->entries[i]); + VIR_FREE(file->entries); + VIR_FREE(file); +} diff --git a/src/util/virrotatingfile.h b/src/util/virrotatingfile.h new file mode 100644 index 0000000000..30cc8a5408 --- /dev/null +++ b/src/util/virrotatingfile.h @@ -0,0 +1,62 @@ +/* + * virrotatingfile.h: reading/writing of auto-rotating files + * + * 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 + * . + * + */ + +#ifndef __VIR_ROTATING_FILE_H__ +# define __VIR_ROTATING_FILE_H__ + +# include "internal.h" + +typedef struct virRotatingFileWriter virRotatingFileWriter; +typedef virRotatingFileWriter *virRotatingFileWriterPtr; + +typedef struct virRotatingFileReader virRotatingFileReader; +typedef virRotatingFileReader *virRotatingFileReaderPtr; + +virRotatingFileWriterPtr virRotatingFileWriterNew(const char *path, + off_t maxlen, + size_t maxbackup, + bool truncate, + mode_t mode); + +virRotatingFileReaderPtr virRotatingFileReaderNew(const char *path, + size_t maxbackup); + +const char *virRotatingFileWriterGetPath(virRotatingFileWriterPtr file); + +ino_t virRotatingFileWriterGetINode(virRotatingFileWriterPtr file); +off_t virRotatingFileWriterGetOffset(virRotatingFileWriterPtr file); + +ssize_t virRotatingFileWriterAppend(virRotatingFileWriterPtr file, + const char *buf, + size_t len); + +int virRotatingFileReaderSeek(virRotatingFileReaderPtr file, + ino_t inode, + off_t offset); + +ssize_t virRotatingFileReaderConsume(virRotatingFileReaderPtr file, + char *buf, + size_t len); + +void virRotatingFileWriterFree(virRotatingFileWriterPtr file); +void virRotatingFileReaderFree(virRotatingFileReaderPtr file); + +#endif /* __VIR_ROTATING_FILE_H__ */ diff --git a/tests/Makefile.am b/tests/Makefile.am index c564038f74..b449286f1f 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -172,6 +172,7 @@ test_programs = virshtest sockettest \ virkeycodetest \ virlockspacetest \ virlogtest \ + virrotatingfiletest \ virstringtest \ virportallocatortest \ sysinfotest \ @@ -1100,6 +1101,11 @@ virpolkittest_SOURCES = \ virpolkittest_CFLAGS = $(AM_CFLAGS) $(DBUS_CFLAGS) virpolkittest_LDADD = $(LDADDS) $(DBUS_LIBS) +virrotatingfiletest_SOURCES = \ + virrotatingfiletest.c testutils.h testutils.c +virrotatingfiletest_CFLAGS = $(AM_CFLAGS) $(DBUS_CFLAGS) +virrotatingfiletest_LDADD = $(LDADDS) $(DBUS_LIBS) + virsystemdtest_SOURCES = \ virsystemdtest.c testutils.h testutils.c virsystemdtest_CFLAGS = $(AM_CFLAGS) $(DBUS_CFLAGS) diff --git a/tests/virrotatingfiletest.c b/tests/virrotatingfiletest.c new file mode 100644 index 0000000000..ed55e6384e --- /dev/null +++ b/tests/virrotatingfiletest.c @@ -0,0 +1,698 @@ +/* + * 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 + * . + * + * Author: Daniel P. Berrange + */ + +#include +#include +#include +#include +#include + +#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 %zu bytes not %zu\n", + filename, size, 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 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 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)