Merge pull request #3339 from jkohen/patch-1
[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  * Permission is hereby granted, free of charge, to any person obtaining a
7  * copy of this software and associated documentation files (the "Software"),
8  * to deal in the Software without restriction, including without limitation
9  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
10  * and/or sell copies of the Software, and to permit persons to whom the
11  * Software is furnished to do so, subject to the following conditions:
12  *
13  * The above copyright notice and this permission notice shall be included in
14  * all copies or substantial portions of the Software.
15  *
16  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
22  * DEALINGS IN THE SOFTWARE.
23  *
24  * Author:
25  *   Luke Heberling <lukeh at c-ware.com>
26  *   Florian Forster <octo at collectd.org>
27  *
28  * Description:
29  *   Encapsulates useful code for plugins which must watch for appends to
30  *   the end of a file.
31  **/
32
33 #include "collectd.h"
34 #include "common.h"
35 #include "utils_tail.h"
36
37 struct cu_tail_s
38 {
39         char  *file;
40         FILE  *fh;
41         struct stat stat;
42 };
43
44 static int cu_tail_reopen (cu_tail_t *obj)
45 {
46   int seek_end = 0;
47   FILE *fh;
48   struct stat stat_buf;
49   int status;
50
51   memset (&stat_buf, 0, sizeof (stat_buf));
52   status = stat (obj->file, &stat_buf);
53   if (status != 0)
54   {
55     char errbuf[1024];
56     ERROR ("utils_tail: stat (%s) failed: %s", obj->file,
57         sstrerror (errno, errbuf, sizeof (errbuf)));
58     return (-1);
59   }
60
61   /* The file is already open.. */
62   if ((obj->fh != NULL) && (stat_buf.st_ino == obj->stat.st_ino))
63   {
64     /* Seek to the beginning if file was truncated */
65     if (stat_buf.st_size < obj->stat.st_size)
66     {
67       INFO ("utils_tail: File `%s' was truncated.", obj->file);
68       status = fseek (obj->fh, 0, SEEK_SET);
69       if (status != 0)
70       {
71         char errbuf[1024];
72         ERROR ("utils_tail: fseek (%s) failed: %s", obj->file,
73             sstrerror (errno, errbuf, sizeof (errbuf)));
74         fclose (obj->fh);
75         obj->fh = NULL;
76         return (-1);
77       }
78     }
79     memcpy (&obj->stat, &stat_buf, sizeof (struct stat));
80     return (1);
81   }
82
83   /* Seek to the end if we re-open the same file again or the file opened
84    * is the first at all or the first after an error */
85   if ((obj->stat.st_ino == 0) || (obj->stat.st_ino == stat_buf.st_ino))
86     seek_end = 1;
87
88   fh = fopen (obj->file, "r");
89   if (fh == NULL)
90   {
91     char errbuf[1024];
92     ERROR ("utils_tail: fopen (%s) failed: %s", obj->file,
93         sstrerror (errno, errbuf, sizeof (errbuf)));
94     return (-1);
95   }
96
97   if (seek_end != 0)
98   {
99     status = fseek (fh, 0, SEEK_END);
100     if (status != 0)
101     {
102       char errbuf[1024];
103       ERROR ("utils_tail: fseek (%s) failed: %s", obj->file,
104           sstrerror (errno, errbuf, sizeof (errbuf)));
105       fclose (fh);
106       return (-1);
107     }
108   }
109
110   if (obj->fh != NULL)
111     fclose (obj->fh);
112   obj->fh = fh;
113   memcpy (&obj->stat, &stat_buf, sizeof (struct stat));
114
115   return (0);
116 } /* int cu_tail_reopen */
117
118 cu_tail_t *cu_tail_create (const char *file)
119 {
120         cu_tail_t *obj;
121
122         obj = (cu_tail_t *) malloc (sizeof (cu_tail_t));
123         if (obj == NULL)
124                 return (NULL);
125         memset (obj, '\0', sizeof (cu_tail_t));
126
127         obj->file = strdup (file);
128         if (obj->file == NULL)
129         {
130                 free (obj);
131                 return (NULL);
132         }
133
134         obj->fh = NULL;
135
136         return (obj);
137 } /* cu_tail_t *cu_tail_create */
138
139 int cu_tail_destroy (cu_tail_t *obj)
140 {
141         if (obj->fh != NULL)
142                 fclose (obj->fh);
143         free (obj->file);
144         free (obj);
145
146         return (0);
147 } /* int cu_tail_destroy */
148
149 int cu_tail_readline (cu_tail_t *obj, char *buf, int buflen)
150 {
151   int status;
152
153   if (buflen < 1)
154   {
155     ERROR ("utils_tail: cu_tail_readline: buflen too small: %i bytes.",
156         buflen);
157     return (-1);
158   }
159
160   if (obj->fh == NULL)
161   {
162     status = cu_tail_reopen (obj);
163     if (status < 0)
164       return (status);
165   }
166   assert (obj->fh != NULL);
167
168   /* Try to read from the filehandle. If that succeeds, everything appears to
169    * be fine and we can return. */
170   clearerr (obj->fh);
171   if (fgets (buf, buflen, obj->fh) != NULL)
172   {
173     buf[buflen - 1] = 0;
174     return (0);
175   }
176
177   /* Check if we encountered an error */
178   if (ferror (obj->fh) != 0)
179   {
180     /* Jupp, error. Force `cu_tail_reopen' to reopen the file.. */
181     fclose (obj->fh);
182     obj->fh = NULL;
183   }
184   /* else: eof -> check if the file was moved away and reopen the new file if
185    * so.. */
186
187   status = cu_tail_reopen (obj);
188   /* error -> return with error */
189   if (status < 0)
190     return (status);
191   /* file end reached and file not reopened -> nothing more to read */
192   else if (status > 0)
193   {
194     buf[0] = 0;
195     return (0);
196   }
197
198   /* If we get here: file was re-opened and there may be more to read.. Let's
199    * try again. */
200   if (fgets (buf, buflen, obj->fh) != NULL)
201   {
202     buf[buflen - 1] = 0;
203     return (0);
204   }
205
206   if (ferror (obj->fh) != 0)
207   {
208     char errbuf[1024];
209     WARNING ("utils_tail: fgets (%s) returned an error: %s", obj->file,
210         sstrerror (errno, errbuf, sizeof (errbuf)));
211     fclose (obj->fh);
212     obj->fh = NULL;
213     return (-1);
214   }
215
216   /* EOf, well, apparently the new file is empty.. */
217   buf[0] = 0;
218   return (0);
219 } /* int cu_tail_readline */
220
221 int cu_tail_read (cu_tail_t *obj, char *buf, int buflen, tailfunc_t *callback,
222                 void *data)
223 {
224         int status;
225
226         while (42)
227         {
228                 size_t len;
229
230                 status = cu_tail_readline (obj, buf, buflen);
231                 if (status != 0)
232                 {
233                         ERROR ("utils_tail: cu_tail_read: cu_tail_readline "
234                                         "failed.");
235                         break;
236                 }
237
238                 /* check for EOF */
239                 if (buf[0] == 0)
240                         break;
241
242                 len = strlen (buf);
243                 while (len > 0) {
244                         if (buf[len - 1] != '\n')
245                                 break;
246                         buf[len - 1] = '\0';
247                         len--;
248                 }
249
250                 status = callback (data, buf, buflen);
251                 if (status != 0)
252                 {
253                         ERROR ("utils_tail: cu_tail_read: callback returned "
254                                         "status %i.", status);
255                         break;
256                 }
257         }
258
259         return status;
260 } /* int cu_tail_read */