Merge pull request #1829 from rubenk/clang-format
[collectd.git] / src / daemon / 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 #include "common.h"
35 #include "utils_tail.h"
36
37 struct cu_tail_s
38 {
39         char  *file;
40         FILE  *fh;
41         struct stat stat;
42 };
43
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;
50
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   }
59
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   }
81
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;
86
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   }
95
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   }
108
109   if (obj->fh != NULL)
110     fclose (obj->fh);
111   obj->fh = fh;
112   memcpy (&obj->stat, &stat_buf, sizeof (struct stat));
113
114   return (0);
115 } /* int cu_tail_reopen */
116
117 cu_tail_t *cu_tail_create (const char *file)
118 {
119         cu_tail_t *obj;
120
121         obj = calloc (1, sizeof (*obj));
122         if (obj == NULL)
123                 return (NULL);
124
125         obj->file = strdup (file);
126         if (obj->file == NULL)
127         {
128                 free (obj);
129                 return (NULL);
130         }
131
132         obj->fh = NULL;
133
134         return (obj);
135 } /* cu_tail_t *cu_tail_create */
136
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);
143
144         return (0);
145 } /* int cu_tail_destroy */
146
147 int cu_tail_readline (cu_tail_t *obj, char *buf, int buflen)
148 {
149   int status;
150
151   if (buflen < 1)
152   {
153     ERROR ("utils_tail: cu_tail_readline: buflen too small: %i bytes.",
154         buflen);
155     return (-1);
156   }
157
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);
165
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   }
174
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.. */
184
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   }
195
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   }
203
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   }
213
214   /* EOf, well, apparently the new file is empty.. */
215   buf[0] = 0;
216   return (0);
217 } /* int cu_tail_readline */
218
219 int cu_tail_read (cu_tail_t *obj, char *buf, int buflen, tailfunc_t *callback,
220                 void *data)
221 {
222         int status;
223
224         while (42)
225         {
226                 size_t len;
227
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                 }
235
236                 /* check for EOF */
237                 if (buf[0] == 0)
238                         break;
239
240                 len = strlen (buf);
241                 while (len > 0) {
242                         if (buf[len - 1] != '\n')
243                                 break;
244                         buf[len - 1] = '\0';
245                         len--;
246                 }
247
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         }
256
257         return status;
258 } /* int cu_tail_read */