From b8cf3eff3c9f88615a55b7f4d606912a4a533e71 Mon Sep 17 00:00:00 2001 From: Florian Forster Date: Mon, 31 Mar 2008 15:00:35 +0200 Subject: [PATCH] src/utils_tail.c: Call `stat' after reading till the end of file. The old code used stat(2) to determine if the file was moved and reopened the file before reading anything. This way lines, that were added to the file before it was moved, would have been missed. This commit changes that behavior so that the file is read until EOF and _then_ stat(2) is used to check if the file has been moved away. --- src/utils_tail.c | 232 ++++++++++++++++++++++++++++++----------------- 1 file changed, 148 insertions(+), 84 deletions(-) diff --git a/src/utils_tail.c b/src/utils_tail.c index 58e027e4..eaf8f738 100644 --- a/src/utils_tail.c +++ b/src/utils_tail.c @@ -1,6 +1,7 @@ /** * collectd - src/utils_tail.c * Copyright (C) 2007-2008 C-Ware, Inc. + * Copyright (C) 2008 Florian Forster * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the @@ -17,6 +18,7 @@ * * Author: * Luke Heberling + * Florian Forster * * Description: * Encapsulates useful code for plugins which must watch for appends to @@ -30,10 +32,84 @@ struct cu_tail_s { char *file; - FILE *fd; + FILE *fh; struct stat stat; }; +static int cu_tail_reopen (cu_tail_t *obj) +{ + int seek_end = 0; + FILE *fh; + struct stat stat_buf; + int status; + + memset (&stat_buf, 0, sizeof (stat_buf)); + status = stat (obj->file, &stat_buf); + if (status != 0) + { + char errbuf[1024]; + ERROR ("utils_tail: stat (%s) failed: %s", obj->file, + sstrerror (errno, errbuf, sizeof (errbuf))); + 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) + { + INFO ("utils_tail: File `%s' was truncated.", obj->file); + status = fseek (obj->fh, 0, SEEK_SET); + if (status != 0) + { + char errbuf[1024]; + ERROR ("utils_tail: fseek (%s) failed: %s", obj->file, + sstrerror (errno, errbuf, sizeof (errbuf))); + 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; + + fh = fopen (obj->file, "r"); + if (fh == NULL) + { + char errbuf[1024]; + ERROR ("utils_tail: fopen (%s) failed: %s", obj->file, + sstrerror (errno, errbuf, sizeof (errbuf))); + return (-1); + } + + if (seek_end != 0) + { + status = fseek (fh, 0, SEEK_END); + if (status != 0) + { + char errbuf[1024]; + ERROR ("utils_tail: fseek (%s) failed: %s", obj->file, + sstrerror (errno, errbuf, sizeof (errbuf))); + 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; @@ -50,15 +126,15 @@ cu_tail_t *cu_tail_create (const char *file) return (NULL); } - obj->fd = NULL; + obj->fh = NULL; return (obj); } /* cu_tail_t *cu_tail_create */ int cu_tail_destroy (cu_tail_t *obj) { - if (obj->fd != NULL) - fclose (obj->fd); + if (obj->fh != NULL) + fclose (obj->fh); free (obj->file); free (obj); @@ -67,85 +143,73 @@ int cu_tail_destroy (cu_tail_t *obj) int cu_tail_readline (cu_tail_t *obj, char *buf, int buflen) { - struct stat stat_now; - int status; - - if (buflen < 1) - { - ERROR ("utils_tail: cu_tail_readline: buflen too small: " - "%i bytes.", buflen); - return (-1); - } - - if (stat (obj->file, &stat_now) != 0) - { - char errbuf[1024]; - ERROR ("cu_tail_readline: stat (%s) failed: %s", - obj->file, - sstrerror (errno, errbuf, sizeof (errbuf))); - return (-1); - } - - if ((stat_now.st_dev != obj->stat.st_dev) || - (stat_now.st_ino != obj->stat.st_ino)) - { - /* - * If the file was replaced open the new file and close the - * old filehandle - */ - FILE *new_fd; - - DEBUG ("utils_tail: cu_tail_readline: (Re)Opening %s..", - obj->file); - - new_fd = fopen (obj->file, "r"); - if (new_fd == NULL) - { - char errbuf[1024]; - ERROR ("utils_tail: cu_tail_readline: open (%s) failed: %s", - obj->file, - sstrerror (errno, errbuf, - sizeof (errbuf))); - return (-1); - } - - /* If there was no previous file, seek to the end. We don't - * want to read in the entire file, usually. */ - if (obj->stat.st_ino == 0) - fseek (new_fd, 0, SEEK_END); - - if (obj->fd != NULL) - fclose (obj->fd); - obj->fd = new_fd; - - } - else if (stat_now.st_size < obj->stat.st_size) - { - /* - * Else, if the file was not replaces, but the file was - * truncated, seek to the beginning of the file. - */ - assert (obj->fd != NULL); - rewind (obj->fd); - } - - status = 0; - if (fgets (buf, buflen, obj->fd) == NULL) - { - if (feof (obj->fd) != 0) - buf[0] = '\0'; - else /* an error occurred */ - { - ERROR ("utils_tail: cu_tail_readline: fgets returned " - "an error."); - status = -1; - } - } - - if (status == 0) - memcpy (&obj->stat, &stat_now, sizeof (struct stat)); - - return (status); + 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. */ + 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) + { + char errbuf[1024]; + WARNING ("utils_tail: fgets (%s) returned an error: %s", obj->file, + sstrerror (errno, errbuf, sizeof (errbuf))); + 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, @@ -164,7 +228,7 @@ int cu_tail_read (cu_tail_t *obj, char *buf, int buflen, tailfunc_t *callback, } /* check for EOF */ - if (buf[0] == '\0') + if (buf[0] == 0) break; status = callback (data, buf, buflen); -- 2.30.2