src/utils_tail.c: Call `stat' after reading till the end of file.
authorFlorian Forster <octo@noris.net>
Mon, 31 Mar 2008 13:00:35 +0000 (15:00 +0200)
committerFlorian Forster <octo@noris.net>
Mon, 31 Mar 2008 13:00:35 +0000 (15:00 +0200)
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

index 58e027e..eaf8f73 100644 (file)
@@ -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 <lukeh at c-ware.com>
+ *   Florian Forster <octo at verplant.org>
  *
  * Description:
  *   Encapsulates useful code for plugins which must watch for appends to
 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);