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 **/
33 #include "collectd.h"
34 #include "common.h"
35 #include "utils_tail.h"
37 struct cu_tail_s
38 {
39 char *file;
40 FILE *fh;
41 struct stat stat;
42 };
44 static int cu_tail_reopen (cu_tail_t *obj)
45 {
46 int seek_end = 0;
47 FILE *fh;
48 struct stat stat_buf = { 0 };
49 int status;
51 status = stat (obj->file, &stat_buf);
52 if (status != 0)
53 {
54 char errbuf[1024];
55 ERROR ("utils_tail: stat (%s) failed: %s", obj->file,
56 sstrerror (errno, errbuf, sizeof (errbuf)));
57 return (-1);
58 }
60 /* The file is already open.. */
61 if ((obj->fh != NULL) && (stat_buf.st_ino == obj->stat.st_ino))
62 {
63 /* Seek to the beginning if file was truncated */
64 if (stat_buf.st_size < obj->stat.st_size)
65 {
66 INFO ("utils_tail: File `%s' was truncated.", obj->file);
67 status = fseek (obj->fh, 0, SEEK_SET);
68 if (status != 0)
69 {
70 char errbuf[1024];
71 ERROR ("utils_tail: fseek (%s) failed: %s", obj->file,
72 sstrerror (errno, errbuf, sizeof (errbuf)));
73 fclose (obj->fh);
74 obj->fh = NULL;
75 return (-1);
76 }
77 }
78 memcpy (&obj->stat, &stat_buf, sizeof (struct stat));
79 return (1);
80 }
82 /* Seek to the end if we re-open the same file again or the file opened
83 * is the first at all or the first after an error */
84 if ((obj->stat.st_ino == 0) || (obj->stat.st_ino == stat_buf.st_ino))
85 seek_end = 1;
87 fh = fopen (obj->file, "r");
88 if (fh == NULL)
89 {
90 char errbuf[1024];
91 ERROR ("utils_tail: fopen (%s) failed: %s", obj->file,
92 sstrerror (errno, errbuf, sizeof (errbuf)));
93 return (-1);
94 }
96 if (seek_end != 0)
97 {
98 status = fseek (fh, 0, SEEK_END);
99 if (status != 0)
100 {
101 char errbuf[1024];
102 ERROR ("utils_tail: fseek (%s) failed: %s", obj->file,
103 sstrerror (errno, errbuf, sizeof (errbuf)));
104 fclose (fh);
105 return (-1);
106 }
107 }
109 if (obj->fh != NULL)
110 fclose (obj->fh);
111 obj->fh = fh;
112 memcpy (&obj->stat, &stat_buf, sizeof (struct stat));
114 return (0);
115 } /* int cu_tail_reopen */
117 cu_tail_t *cu_tail_create (const char *file)
118 {
119 cu_tail_t *obj;
121 obj = calloc (1, sizeof (*obj));
122 if (obj == NULL)
123 return (NULL);
125 obj->file = strdup (file);
126 if (obj->file == NULL)
127 {
128 free (obj);
129 return (NULL);
130 }
132 obj->fh = NULL;
134 return (obj);
135 } /* cu_tail_t *cu_tail_create */
137 int cu_tail_destroy (cu_tail_t *obj)
138 {
139 if (obj->fh != NULL)
140 fclose (obj->fh);
141 free (obj->file);
142 free (obj);
144 return (0);
145 } /* int cu_tail_destroy */
147 int cu_tail_readline (cu_tail_t *obj, char *buf, int buflen)
148 {
149 int status;
151 if (buflen < 1)
152 {
153 ERROR ("utils_tail: cu_tail_readline: buflen too small: %i bytes.",
154 buflen);
155 return (-1);
156 }
158 if (obj->fh == NULL)
159 {
160 status = cu_tail_reopen (obj);
161 if (status < 0)
162 return (status);
163 }
164 assert (obj->fh != NULL);
166 /* Try to read from the filehandle. If that succeeds, everything appears to
167 * be fine and we can return. */
168 clearerr (obj->fh);
169 if (fgets (buf, buflen, obj->fh) != NULL)
170 {
171 buf[buflen - 1] = 0;
172 return (0);
173 }
175 /* Check if we encountered an error */
176 if (ferror (obj->fh) != 0)
177 {
178 /* Jupp, error. Force `cu_tail_reopen' to reopen the file.. */
179 fclose (obj->fh);
180 obj->fh = NULL;
181 }
182 /* else: eof -> check if the file was moved away and reopen the new file if
183 * so.. */
185 status = cu_tail_reopen (obj);
186 /* error -> return with error */
187 if (status < 0)
188 return (status);
189 /* file end reached and file not reopened -> nothing more to read */
190 else if (status > 0)
191 {
192 buf[0] = 0;
193 return (0);
194 }
196 /* If we get here: file was re-opened and there may be more to read.. Let's
197 * try again. */
198 if (fgets (buf, buflen, obj->fh) != NULL)
199 {
200 buf[buflen - 1] = 0;
201 return (0);
202 }
204 if (ferror (obj->fh) != 0)
205 {
206 char errbuf[1024];
207 WARNING ("utils_tail: fgets (%s) returned an error: %s", obj->file,
208 sstrerror (errno, errbuf, sizeof (errbuf)));
209 fclose (obj->fh);
210 obj->fh = NULL;
211 return (-1);
212 }
214 /* EOf, well, apparently the new file is empty.. */
215 buf[0] = 0;
216 return (0);
217 } /* int cu_tail_readline */
219 int cu_tail_read (cu_tail_t *obj, char *buf, int buflen, tailfunc_t *callback,
220 void *data)
221 {
222 int status;
224 while (42)
225 {
226 size_t len;
228 status = cu_tail_readline (obj, buf, buflen);
229 if (status != 0)
230 {
231 ERROR ("utils_tail: cu_tail_read: cu_tail_readline "
232 "failed.");
233 break;
234 }
236 /* check for EOF */
237 if (buf[0] == 0)
238 break;
240 len = strlen (buf);
241 while (len > 0) {
242 if (buf[len - 1] != '\n')
243 break;
244 buf[len - 1] = '\0';
245 len--;
246 }
248 status = callback (data, buf, buflen);
249 if (status != 0)
250 {
251 ERROR ("utils_tail: cu_tail_read: callback returned "
252 "status %i.", status);
253 break;
254 }
255 }
257 return status;
258 } /* int cu_tail_read */