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 **/
28 #include "collectd.h"
29 #include "common.h"
30 #include "utils_tail.h"
32 struct cu_tail_s
33 {
34 char *file;
35 FILE *fh;
36 struct stat stat;
37 };
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;
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 }
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 }
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;
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 }
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 }
105 if (obj->fh != NULL)
106 fclose (obj->fh);
107 obj->fh = fh;
108 memcpy (&obj->stat, &stat_buf, sizeof (struct stat));
110 return (0);
111 } /* int cu_tail_reopen */
113 cu_tail_t *cu_tail_create (const char *file)
114 {
115 cu_tail_t *obj;
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));
122 obj->file = strdup (file);
123 if (obj->file == NULL)
124 {
125 free (obj);
126 return (NULL);
127 }
129 obj->fh = NULL;
131 return (obj);
132 } /* cu_tail_t *cu_tail_create */
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);
141 return (0);
142 } /* int cu_tail_destroy */
144 int cu_tail_readline (cu_tail_t *obj, char *buf, int buflen)
145 {
146 int status;
148 if (buflen < 1)
149 {
150 ERROR ("utils_tail: cu_tail_readline: buflen too small: %i bytes.",
151 buflen);
152 return (-1);
153 }
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);
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 }
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.. */
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 }
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 }
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 }
211 /* EOf, well, apparently the new file is empty.. */
212 buf[0] = 0;
213 return (0);
214 } /* int cu_tail_readline */
216 int cu_tail_read (cu_tail_t *obj, char *buf, int buflen, tailfunc_t *callback,
217 void *data)
218 {
219 int status;
221 while (42)
222 {
223 status = cu_tail_readline (obj, buf, buflen);
224 if (status != 0)
225 {
226 ERROR ("utils_tail: cu_tail_read: cu_tail_readline "
227 "failed.");
228 break;
229 }
231 /* check for EOF */
232 if (buf[0] == 0)
233 break;
235 status = callback (data, buf, buflen);
236 if (status != 0)
237 {
238 ERROR ("utils_tail: cu_tail_read: callback returned "
239 "status %i.", status);
240 break;
241 }
242 }
244 return status;
245 } /* int cu_tail_read */