utils_vl_lookup: Fixed a race when creating user objects.
[collectd.git] / src / utils_tail.c
index 5e5aa03..0b31262 100644 (file)
@@ -1,6 +1,7 @@
 /**
  * collectd - src/utils_tail.c
- * Copyright (C) 2008  C-Ware, Inc.
+ * 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
  *
  * 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
  *   the end of a file.
  **/
 
-
+#include "collectd.h"
+#include "common.h"
 #include "utils_tail.h"
-#include <stdio.h>
-#include <sys/stat.h>
-#include <string.h>
-#include <malloc.h>
 
 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;
@@ -53,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);
 
@@ -70,60 +143,113 @@ int cu_tail_destroy (cu_tail_t *obj)
 
 int cu_tail_readline (cu_tail_t *obj, char *buf, int buflen)
 {
-       struct stat stat_now;
-       FILE *new_fd;
-       int len;
-
-       if( buflen < 1 )
-               return -1;
-       
-       *buf = '\0';
-
-       if (stat (obj->file, &stat_now) != 0)
-               return 0;
-
-       if (stat_now.st_dev != obj->stat.st_dev ||
-               stat_now.st_ino != obj->stat.st_ino)
-       {
-               new_fd = fopen (obj->file, "r");
-               if (new_fd == NULL)
-                       return -1;
-               
-               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)
-       {
-               rewind (obj->fd);
-       }
-
-       memcpy (&obj->stat, &stat_now, sizeof (struct stat));   
-       
-       if (fgets (buf, buflen, obj->fd) == NULL && feof (obj->fd) == 0)
-               return -1;
-
-       len = strlen (buf);
-       if (len > 0 && *(buf + len - 1) != '\n' && feof (obj->fd))
-       {
-               fseek (obj->fd, -len, SEEK_CUR);
-               *buf = '\0';
-       }
-
-       return 0;
+  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 *func, void *data)
+int cu_tail_read (cu_tail_t *obj, char *buf, int buflen, tailfunc_t *callback,
+               void *data)
 {
-       int ret;
+       int status;
 
-       while ((ret = cu_tail_readline (obj, buf, buflen)) == 0)
-               if (*buf == '\0' || (ret = func (data, buf, buflen)))
+       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 ret;
+       return status;
 } /* int cu_tail_read */
-