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