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