Merge branch 'collectd-4.3'
[collectd.git] / src / utils_tail.c
1 /**
2  * collectd - src/utils_tail.c
3  * Copyright (C) 2007-2008  C-Ware, Inc.
4  *
5  * This program is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License as published by the
7  * Free Software Foundation; only version 2 of the License is applicable.
8  *
9  * This program is distributed in the hope that it will be useful, but
10  * WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License along
15  * with this program; if not, write to the Free Software Foundation, Inc.,
16  * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
17  *
18  * Author:
19  *   Luke Heberling <lukeh at c-ware.com>
20  *
21  * Description:
22  *   Encapsulates useful code for plugins which must watch for appends to
23  *   the end of a file.
24  **/
25
26 #include "collectd.h"
27 #include "common.h"
28 #include "utils_tail.h"
29
30 struct cu_tail_s
31 {
32         char  *file;
33         FILE  *fd;
34         struct stat stat;
35 };
36
37 cu_tail_t *cu_tail_create (const char *file)
38 {
39         cu_tail_t *obj;
40
41         obj = (cu_tail_t *) malloc (sizeof (cu_tail_t));
42         if (obj == NULL)
43                 return (NULL);
44         memset (obj, '\0', sizeof (cu_tail_t));
45
46         obj->file = strdup (file);
47         if (obj->file == NULL)
48         {
49                 free (obj);
50                 return (NULL);
51         }
52
53         obj->fd = NULL;
54
55         return (obj);
56 } /* cu_tail_t *cu_tail_create */
57
58 int cu_tail_destroy (cu_tail_t *obj)
59 {
60         if (obj->fd != NULL)
61                 fclose (obj->fd);
62         free (obj->file);
63         free (obj);
64
65         return (0);
66 } /* int cu_tail_destroy */
67
68 int cu_tail_readline (cu_tail_t *obj, char *buf, int buflen)
69 {
70         struct stat stat_now;
71         int status;
72
73         if (buflen < 1)
74         {
75                 ERROR ("utils_tail: cu_tail_readline: buflen too small: "
76                                 "%i bytes.", buflen);
77                 return (-1);
78         }
79         
80         if (stat (obj->file, &stat_now) != 0)
81         {
82                 char errbuf[1024];
83                 ERROR ("cu_tail_readline: stat (%s) failed: %s",
84                                 obj->file,
85                                 sstrerror (errno, errbuf, sizeof (errbuf)));
86                 return (-1);
87         }
88
89         if ((stat_now.st_dev != obj->stat.st_dev) ||
90                 (stat_now.st_ino != obj->stat.st_ino))
91         {
92                 /*
93                  * If the file was replaced open the new file and close the
94                  * old filehandle
95                  */
96                 FILE *new_fd;
97
98                 DEBUG ("utils_tail: cu_tail_readline: (Re)Opening %s..",
99                                 obj->file);
100
101                 new_fd = fopen (obj->file, "r");
102                 if (new_fd == NULL)
103                 {
104                         char errbuf[1024];
105                         ERROR ("utils_tail: cu_tail_readline: open (%s) failed: %s",
106                                         obj->file,
107                                         sstrerror (errno, errbuf,
108                                                 sizeof (errbuf)));
109                         return (-1);
110                 }
111                 
112                 /* If there was no previous file, seek to the end. We don't
113                  * want to read in the entire file, usually. */
114                 if (obj->stat.st_ino == 0)
115                         fseek (new_fd, 0, SEEK_END);
116
117                 if (obj->fd != NULL)
118                         fclose (obj->fd);
119                 obj->fd = new_fd;
120
121         }
122         else if (stat_now.st_size < obj->stat.st_size)
123         {
124                 /*
125                  * Else, if the file was not replaces, but the file was
126                  * truncated, seek to the beginning of the file.
127                  */
128                 assert (obj->fd != NULL);
129                 rewind (obj->fd);
130         }
131
132         status = 0;
133         if (fgets (buf, buflen, obj->fd) == NULL)
134         {
135                 if (feof (obj->fd) != 0)
136                         buf[0] = '\0';
137                 else /* an error occurred */
138                 {
139                         ERROR ("utils_tail: cu_tail_readline: fgets returned "
140                                         "an error.");
141                         status = -1;
142                 }
143         }
144
145         if (status == 0)
146                 memcpy (&obj->stat, &stat_now, sizeof (struct stat));   
147         
148         return (status);
149 } /* int cu_tail_readline */
150
151 int cu_tail_read (cu_tail_t *obj, char *buf, int buflen, tailfunc_t *callback,
152                 void *data)
153 {
154         int status;
155
156         while (42)
157         {
158                 status = cu_tail_readline (obj, buf, buflen);
159                 if (status != 0)
160                 {
161                         ERROR ("utils_tail: cu_tail_read: cu_tail_readline "
162                                         "failed.");
163                         break;
164                 }
165
166                 /* check for EOF */
167                 if (buf[0] == '\0')
168                         break;
169
170                 status = callback (data, buf, buflen);
171                 if (status != 0)
172                 {
173                         ERROR ("utils_tail: cu_tail_read: callback returned "
174                                         "status %i.", status);
175                         break;
176                 }
177         }
178
179         return status;
180 } /* int cu_tail_read */