{GPL, other}: Relicense to MIT license.
[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 collectd.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   clearerr (obj->fh);
166   if (fgets (buf, buflen, obj->fh) != NULL)
167   {
168     buf[buflen - 1] = 0;
169     return (0);
170   }
171
172   /* Check if we encountered an error */
173   if (ferror (obj->fh) != 0)
174   {
175     /* Jupp, error. Force `cu_tail_reopen' to reopen the file.. */
176     fclose (obj->fh);
177     obj->fh = NULL;
178   }
179   /* else: eof -> check if the file was moved away and reopen the new file if
180    * so.. */
181
182   status = cu_tail_reopen (obj);
183   /* error -> return with error */
184   if (status < 0)
185     return (status);
186   /* file end reached and file not reopened -> nothing more to read */
187   else if (status > 0)
188   {
189     buf[0] = 0;
190     return (0);
191   }
192
193   /* If we get here: file was re-opened and there may be more to read.. Let's
194    * try again. */
195   if (fgets (buf, buflen, obj->fh) != NULL)
196   {
197     buf[buflen - 1] = 0;
198     return (0);
199   }
200
201   if (ferror (obj->fh) != 0)
202   {
203     char errbuf[1024];
204     WARNING ("utils_tail: fgets (%s) returned an error: %s", obj->file,
205         sstrerror (errno, errbuf, sizeof (errbuf)));
206     fclose (obj->fh);
207     obj->fh = NULL;
208     return (-1);
209   }
210
211   /* EOf, well, apparently the new file is empty.. */
212   buf[0] = 0;
213   return (0);
214 } /* int cu_tail_readline */
215
216 int cu_tail_read (cu_tail_t *obj, char *buf, int buflen, tailfunc_t *callback,
217                 void *data)
218 {
219         int status;
220
221         while (42)
222         {
223                 size_t len;
224
225                 status = cu_tail_readline (obj, buf, buflen);
226                 if (status != 0)
227                 {
228                         ERROR ("utils_tail: cu_tail_read: cu_tail_readline "
229                                         "failed.");
230                         break;
231                 }
232
233                 /* check for EOF */
234                 if (buf[0] == 0)
235                         break;
236
237                 len = strlen (buf);
238                 while (len > 0) {
239                         if (buf[len - 1] != '\n')
240                                 break;
241                         buf[len - 1] = '\0';
242                         len--;
243                 }
244
245                 status = callback (data, buf, buflen);
246                 if (status != 0)
247                 {
248                         ERROR ("utils_tail: cu_tail_read: callback returned "
249                                         "status %i.", status);
250                         break;
251                 }
252         }
253
254         return status;
255 } /* int cu_tail_read */