Merge branch 'collectd-5.7'
[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   FILE *fh;
47   struct stat stat_buf = {0};
48   int status;
49
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   }
57
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   }
76
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;
81
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   }
89
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   }
100
101   if (obj->fh != NULL)
102     fclose(obj->fh);
103   obj->fh = fh;
104   memcpy(&obj->stat, &stat_buf, sizeof(struct stat));
105
106   return 0;
107 } /* int cu_tail_reopen */
108
109 cu_tail_t *cu_tail_create(const char *file) {
110   cu_tail_t *obj;
111
112   obj = calloc(1, sizeof(*obj));
113   if (obj == NULL)
114     return NULL;
115
116   obj->file = strdup(file);
117   if (obj->file == NULL) {
118     free(obj);
119     return NULL;
120   }
121
122   obj->fh = NULL;
123
124   return obj;
125 } /* cu_tail_t *cu_tail_create */
126
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);
132
133   return 0;
134 } /* int cu_tail_destroy */
135
136 int cu_tail_readline(cu_tail_t *obj, char *buf, int buflen) {
137   int status;
138
139   if (buflen < 1) {
140     ERROR("utils_tail: cu_tail_readline: buflen too small: %i bytes.", buflen);
141     return -1;
142   }
143
144   if (obj->fh == NULL) {
145     status = cu_tail_reopen(obj);
146     if (status < 0)
147       return status;
148   }
149   assert(obj->fh != NULL);
150
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   }
158
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.. */
167
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   }
177
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   }
184
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   }
193
194   /* EOf, well, apparently the new file is empty.. */
195   buf[0] = 0;
196   return 0;
197 } /* int cu_tail_readline */
198
199 int cu_tail_read(cu_tail_t *obj, char *buf, int buflen, tailfunc_t *callback,
200                  void *data) {
201   int status;
202
203   while (42) {
204     size_t len;
205
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     }
212
213     /* check for EOF */
214     if (buf[0] == 0)
215       break;
216
217     len = strlen(buf);
218     while (len > 0) {
219       if (buf[len - 1] != '\n')
220         break;
221       buf[len - 1] = '\0';
222       len--;
223     }
224
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   }
233
234   return status;
235 } /* int cu_tail_read */