{GPL, other}: Relicense to MIT license.
[collectd.git] / src / utils_tail.c
index 58e027e..9d05fe1 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 collectd.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,74 @@ 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. */
+  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)
+  {
+    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,
@@ -155,6 +220,8 @@ int cu_tail_read (cu_tail_t *obj, char *buf, int buflen, tailfunc_t *callback,
 
        while (42)
        {
+               size_t len;
+
                status = cu_tail_readline (obj, buf, buflen);
                if (status != 0)
                {
@@ -164,9 +231,17 @@ 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;
 
+               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)
                {