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;
49 int status;
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 }
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 }
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;
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 }
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 }
110 if (obj->fh != NULL)
111 fclose (obj->fh);
112 obj->fh = fh;
113 memcpy (&obj->stat, &stat_buf, sizeof (struct stat));
115 return (0);
116 } /* int cu_tail_reopen */
118 cu_tail_t *cu_tail_create (const char *file)
119 {
120 cu_tail_t *obj;
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));
127 obj->file = strdup (file);
128 if (obj->file == NULL)
129 {
130 free (obj);
131 return (NULL);
132 }
134 obj->fh = NULL;
136 return (obj);
137 } /* cu_tail_t *cu_tail_create */
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);
146 return (0);
147 } /* int cu_tail_destroy */
149 int cu_tail_readline (cu_tail_t *obj, char *buf, int buflen)
150 {
151 int status;
153 if (buflen < 1)
154 {
155 ERROR ("utils_tail: cu_tail_readline: buflen too small: %i bytes.",
156 buflen);
157 return (-1);
158 }
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);
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 }
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.. */
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 }
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 }
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 }
216 /* EOf, well, apparently the new file is empty.. */
217 buf[0] = 0;
218 return (0);
219 } /* int cu_tail_readline */
221 int cu_tail_read (cu_tail_t *obj, char *buf, int buflen, tailfunc_t *callback,
222 void *data)
223 {
224 int status;
226 while (42)
227 {
228 size_t len;
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 }
238 /* check for EOF */
239 if (buf[0] == 0)
240 break;
242 len = strlen (buf);
243 while (len > 0) {
244 if (buf[len - 1] != '\n')
245 break;
246 buf[len - 1] = '\0';
247 len--;
248 }
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 }
259 return status;
260 } /* int cu_tail_read */