/** * 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 * Florian Forster * * 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, bool force_rewind) { 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; } /* Unless flag for rewinding to the start is set, 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 = !force_rewind; 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, bool force_rewind) { 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, force_rewind); 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, force_rewind); /* 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, bool force_rewind) { int status; while (42) { size_t len; status = cu_tail_readline(obj, buf, buflen, force_rewind); 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 */