--- /dev/null
+/**
+ * collectd - src/utils_tail.c
+ * Copyright (C) 2007-2008 C-Ware, Inc.
+ * Copyright (C) 2008 Florian Forster
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ *
+ * Author:
+ * Luke Heberling <lukeh at c-ware.com>
+ * Florian Forster <octo at collectd.org>
+ *
+ * Description:
+ * Encapsulates useful code for plugins which must watch for appends to
+ * the end of a file.
+ **/
+
+#include "collectd.h"
+
+#include "utils/common/common.h"
+#include "utils/tail/tail.h"
+
+struct cu_tail_s {
+ char *file;
+ FILE *fh;
+ struct stat stat;
+};
+
+static int cu_tail_reopen(cu_tail_t *obj) {
+ int seek_end = 0;
+ struct stat stat_buf = {0};
+
+ int status = stat(obj->file, &stat_buf);
+ if (status != 0) {
+ P_ERROR("utils_tail: stat (%s) failed: %s", obj->file, STRERRNO);
+ return -1;
+ }
+
+ /* The file is already open.. */
+ if ((obj->fh != NULL) && (stat_buf.st_ino == obj->stat.st_ino)) {
+ /* Seek to the beginning if file was truncated */
+ if (stat_buf.st_size < obj->stat.st_size) {
+ P_INFO("utils_tail: File `%s' was truncated.", obj->file);
+ status = fseek(obj->fh, 0, SEEK_SET);
+ if (status != 0) {
+ P_ERROR("utils_tail: fseek (%s) failed: %s", obj->file, STRERRNO);
+ fclose(obj->fh);
+ obj->fh = NULL;
+ return -1;
+ }
+ }
+ memcpy(&obj->stat, &stat_buf, sizeof(struct stat));
+ return 1;
+ }
+
+ /* Seek to the end if we re-open the same file again or the file opened
+ * is the first at all or the first after an error */
+ if ((obj->stat.st_ino == 0) || (obj->stat.st_ino == stat_buf.st_ino))
+ seek_end = 1;
+
+ FILE *fh = fopen(obj->file, "r");
+ if (fh == NULL) {
+ P_ERROR("utils_tail: fopen (%s) failed: %s", obj->file, STRERRNO);
+ return -1;
+ }
+
+ if (seek_end != 0) {
+ status = fseek(fh, 0, SEEK_END);
+ if (status != 0) {
+ P_ERROR("utils_tail: fseek (%s) failed: %s", obj->file, STRERRNO);
+ fclose(fh);
+ return -1;
+ }
+ }
+
+ if (obj->fh != NULL)
+ fclose(obj->fh);
+ obj->fh = fh;
+ memcpy(&obj->stat, &stat_buf, sizeof(struct stat));
+
+ return 0;
+} /* int cu_tail_reopen */
+
+cu_tail_t *cu_tail_create(const char *file) {
+ cu_tail_t *obj;
+
+ obj = calloc(1, sizeof(*obj));
+ if (obj == NULL)
+ return NULL;
+
+ obj->file = strdup(file);
+ if (obj->file == NULL) {
+ free(obj);
+ return NULL;
+ }
+
+ obj->fh = NULL;
+
+ return obj;
+} /* cu_tail_t *cu_tail_create */
+
+int cu_tail_destroy(cu_tail_t *obj) {
+ if (obj->fh != NULL)
+ fclose(obj->fh);
+ free(obj->file);
+ free(obj);
+
+ return 0;
+} /* int cu_tail_destroy */
+
+int cu_tail_readline(cu_tail_t *obj, char *buf, int buflen) {
+ int status;
+
+ if (buflen < 1) {
+ ERROR("utils_tail: cu_tail_readline: buflen too small: %i bytes.", buflen);
+ return -1;
+ }
+
+ if (obj->fh == NULL) {
+ status = cu_tail_reopen(obj);
+ if (status < 0)
+ return status;
+ }
+ assert(obj->fh != NULL);
+
+ /* Try to read from the filehandle. If that succeeds, everything appears to
+ * be fine and we can return. */
+ clearerr(obj->fh);
+ if (fgets(buf, buflen, obj->fh) != NULL) {
+ buf[buflen - 1] = '\0';
+ return 0;
+ }
+
+ /* Check if we encountered an error */
+ if (ferror(obj->fh) != 0) {
+ /* Jupp, error. Force `cu_tail_reopen' to reopen the file.. */
+ fclose(obj->fh);
+ obj->fh = NULL;
+ }
+ /* else: eof -> check if the file was moved away and reopen the new file if
+ * so.. */
+
+ status = cu_tail_reopen(obj);
+ /* error -> return with error */
+ if (status < 0)
+ return status;
+ /* file end reached and file not reopened -> nothing more to read */
+ else if (status > 0) {
+ buf[0] = 0;
+ return 0;
+ }
+
+ /* If we get here: file was re-opened and there may be more to read.. Let's
+ * try again. */
+ if (fgets(buf, buflen, obj->fh) != NULL) {
+ buf[buflen - 1] = '\0';
+ return 0;
+ }
+
+ if (ferror(obj->fh) != 0) {
+ WARNING("utils_tail: fgets (%s) returned an error: %s", obj->file,
+ STRERRNO);
+ fclose(obj->fh);
+ obj->fh = NULL;
+ return -1;
+ }
+
+ /* EOf, well, apparently the new file is empty.. */
+ buf[0] = 0;
+ return 0;
+} /* int cu_tail_readline */
+
+int cu_tail_read(cu_tail_t *obj, char *buf, int buflen, tailfunc_t *callback,
+ void *data) {
+ int status;
+
+ while (42) {
+ size_t len;
+
+ status = cu_tail_readline(obj, buf, buflen);
+ if (status != 0) {
+ ERROR("utils_tail: cu_tail_read: cu_tail_readline "
+ "failed.");
+ break;
+ }
+
+ /* check for EOF */
+ if (buf[0] == 0)
+ break;
+
+ len = strlen(buf);
+ while (len > 0) {
+ if (buf[len - 1] != '\n')
+ break;
+ buf[len - 1] = '\0';
+ len--;
+ }
+
+ status = callback(data, buf, buflen);
+ if (status != 0) {
+ ERROR("utils_tail: cu_tail_read: callback returned "
+ "status %i.",
+ status);
+ break;
+ }
+ }
+
+ return status;
+} /* int cu_tail_read */