Merge branch 'collectd-4.3' into collectd-4.4
[collectd.git] / src / utils_tail.c
1 /**
2  * collectd - src/utils_tail.c
3  * Copyright (C) 2007-2008  C-Ware, Inc.
4  * Copyright (C) 2008  Florian Forster
5  *
6  * This program is free software; you can redistribute it and/or modify it
7  * under the terms of the GNU General Public License as published by the
8  * Free Software Foundation; only version 2 of the License is applicable.
9  *
10  * This program is distributed in the hope that it will be useful, but
11  * WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License along
16  * with this program; if not, write to the Free Software Foundation, Inc.,
17  * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
18  *
19  * Author:
20  *   Luke Heberling <lukeh at c-ware.com>
21  *   Florian Forster <octo at verplant.org>
22  *
23  * Description:
24  *   Encapsulates useful code for plugins which must watch for appends to
25  *   the end of a file.
26  **/
27
28 #include "collectd.h"
29 #include "common.h"
30 #include "utils_tail.h"
31
32 struct cu_tail_s
33 {
34         char  *file;
35         FILE  *fh;
36         struct stat stat;
37 };
38
39 static int cu_tail_reopen (cu_tail_t *obj)
40 {
41   int seek_end = 0;
42   FILE *fh;
43   struct stat stat_buf;
44   int status;
45
46   memset (&stat_buf, 0, sizeof (stat_buf));
47   status = stat (obj->file, &stat_buf);
48   if (status != 0)
49   {
50     char errbuf[1024];
51     ERROR ("utils_tail: stat (%s) failed: %s", obj->file,
52         sstrerror (errno, errbuf, sizeof (errbuf)));
53     return (-1);
54   }
55
56   /* The file is already open.. */
57   if ((obj->fh != NULL) && (stat_buf.st_ino == obj->stat.st_ino))
58   {
59     /* Seek to the beginning if file was truncated */
60     if (stat_buf.st_size < obj->stat.st_size)
61     {
62       INFO ("utils_tail: File `%s' was truncated.", obj->file);
63       status = fseek (obj->fh, 0, SEEK_SET);
64       if (status != 0)
65       {
66         char errbuf[1024];
67         ERROR ("utils_tail: fseek (%s) failed: %s", obj->file,
68             sstrerror (errno, errbuf, sizeof (errbuf)));
69         fclose (obj->fh);
70         obj->fh = NULL;
71         return (-1);
72       }
73     }
74     memcpy (&obj->stat, &stat_buf, sizeof (struct stat));
75     return (1);
76   }
77
78   /* Seek to the end if we re-open the same file again or the file opened
79    * is the first at all or the first after an error */
80   if ((obj->stat.st_ino == 0) || (obj->stat.st_ino == stat_buf.st_ino))
81     seek_end = 1;
82
83   fh = fopen (obj->file, "r");
84   if (fh == NULL)
85   {
86     char errbuf[1024];
87     ERROR ("utils_tail: fopen (%s) failed: %s", obj->file,
88         sstrerror (errno, errbuf, sizeof (errbuf)));
89     return (-1);
90   }
91
92   if (seek_end != 0)
93   {
94     status = fseek (fh, 0, SEEK_END);
95     if (status != 0)
96     {
97       char errbuf[1024];
98       ERROR ("utils_tail: fseek (%s) failed: %s", obj->file,
99           sstrerror (errno, errbuf, sizeof (errbuf)));
100       fclose (fh);
101       return (-1);
102     }
103   }
104
105   if (obj->fh != NULL)
106     fclose (obj->fh);
107   obj->fh = fh;
108   memcpy (&obj->stat, &stat_buf, sizeof (struct stat));
109
110   return (0);
111 } /* int cu_tail_reopen */
112
113 cu_tail_t *cu_tail_create (const char *file)
114 {
115         cu_tail_t *obj;
116
117         obj = (cu_tail_t *) malloc (sizeof (cu_tail_t));
118         if (obj == NULL)
119                 return (NULL);
120         memset (obj, '\0', sizeof (cu_tail_t));
121
122         obj->file = strdup (file);
123         if (obj->file == NULL)
124         {
125                 free (obj);
126                 return (NULL);
127         }
128
129         obj->fh = NULL;
130
131         return (obj);
132 } /* cu_tail_t *cu_tail_create */
133
134 int cu_tail_destroy (cu_tail_t *obj)
135 {
136         if (obj->fh != NULL)
137                 fclose (obj->fh);
138         free (obj->file);
139         free (obj);
140
141         return (0);
142 } /* int cu_tail_destroy */
143
144 int cu_tail_readline (cu_tail_t *obj, char *buf, int buflen)
145 {
146   int status;
147
148   if (buflen < 1)
149   {
150     ERROR ("utils_tail: cu_tail_readline: buflen too small: %i bytes.",
151         buflen);
152     return (-1);
153   }
154
155   if (obj->fh == NULL)
156   {
157     status = cu_tail_reopen (obj);
158     if (status < 0)
159       return (status);
160   }
161   assert (obj->fh != NULL);
162
163   /* Try to read from the filehandle. If that succeeds, everything appears to
164    * be fine and we can return. */
165   if (fgets (buf, buflen, obj->fh) != NULL)
166   {
167     buf[buflen - 1] = 0;
168     return (0);
169   }
170
171   /* Check if we encountered an error */
172   if (ferror (obj->fh) != 0)
173   {
174     /* Jupp, error. Force `cu_tail_reopen' to reopen the file.. */
175     fclose (obj->fh);
176     obj->fh = NULL;
177   }
178   /* else: eof -> check if the file was moved away and reopen the new file if
179    * so.. */
180
181   status = cu_tail_reopen (obj);
182   /* error -> return with error */
183   if (status < 0)
184     return (status);
185   /* file end reached and file not reopened -> nothing more to read */
186   else if (status > 0)
187   {
188     buf[0] = 0;
189     return (0);
190   }
191
192   /* If we get here: file was re-opened and there may be more to read.. Let's
193    * try again. */
194   if (fgets (buf, buflen, obj->fh) != NULL)
195   {
196     buf[buflen - 1] = 0;
197     return (0);
198   }
199
200   if (ferror (obj->fh) != 0)
201   {
202     char errbuf[1024];
203     WARNING ("utils_tail: fgets (%s) returned an error: %s", obj->file,
204         sstrerror (errno, errbuf, sizeof (errbuf)));
205     fclose (obj->fh);
206     obj->fh = NULL;
207     return (-1);
208   }
209
210   /* EOf, well, apparently the new file is empty.. */
211   buf[0] = 0;
212   return (0);
213 } /* int cu_tail_readline */
214
215 int cu_tail_read (cu_tail_t *obj, char *buf, int buflen, tailfunc_t *callback,
216                 void *data)
217 {
218         int status;
219
220         while (42)
221         {
222                 status = cu_tail_readline (obj, buf, buflen);
223                 if (status != 0)
224                 {
225                         ERROR ("utils_tail: cu_tail_read: cu_tail_readline "
226                                         "failed.");
227                         break;
228                 }
229
230                 /* check for EOF */
231                 if (buf[0] == 0)
232                         break;
233
234                 status = callback (data, buf, buflen);
235                 if (status != 0)
236                 {
237                         ERROR ("utils_tail: cu_tail_read: callback returned "
238                                         "status %i.", status);
239                         break;
240                 }
241         }
242
243         return status;
244 } /* int cu_tail_read */