fix for testing and debug on FreeBSD
[monky] / src / tailhead.c
1 /* Conky, a system monitor, based on torsmo
2  *
3  * Any original torsmo code is licensed under the BSD license
4  *
5  * All code written since the fork of torsmo is licensed under the GPL
6  *
7  * Please see COPYING for details
8  *
9  * Copyright (c) 2004, Hannu Saransaari and Lauri Hakkarainen
10  * Copyright (c) 2005-2009 Brenden Matthews, Philip Kovacs, et. al.
11  *      (see AUTHORS)
12  * All rights reserved.
13  *
14  * This program is free software: you can redistribute it and/or modify
15  * it under the terms of the GNU General Public License as published by
16  * the Free Software Foundation, either version 3 of the License, or
17  * (at your option) any later version.
18  *
19  * This program is distributed in the hope that it will be useful,
20  * but WITHOUT ANY WARRANTY; without even the implied warranty of
21  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
22  * GNU General Public License for more details.
23  * You should have received a copy of the GNU General Public License
24  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
25  *
26  */
27 #include "config.h"
28 #include "conky.h"
29 #include "logging.h"
30 #include "tailhead.h"
31 #include "text_object.h"
32
33 #include <errno.h>
34 #include <fcntl.h>
35 #include <unistd.h>
36 #include <sys/types.h>
37 #include <sys/stat.h>
38
39 #ifndef HAVE_MEMRCHR
40 static const void *memrchr(const void *buffer, char c, size_t n)
41 {
42         const unsigned char *p = buffer;
43
44         for (p += n; n; n--) {
45                 if (*--p == c) {
46                         return p;
47                 }
48         }
49         return NULL;
50 }
51 #endif
52
53 int init_tailhead_object(enum tailhead_type type,
54                 struct text_object *obj, const char *arg)
55 {
56         char buf[128];
57         int n1, n2;
58         struct stat st;
59         FILE *fp = NULL;
60         int fd;
61         int numargs;
62         const char *me;
63
64         /* FIXME: use #define for that */
65         me = (type == TAIL) ? "tail" : "head";
66
67         if (!arg) {
68                 ERR("%s needs arguments", me);
69                 return 1;
70         }
71
72         numargs = sscanf(arg, "%127s %i %i", buf, &n1, &n2);
73
74         if (numargs < 2 || numargs > 3) {
75                 ERR("incorrect number of arguments given to %s object", me);
76                 return 1;
77         }
78
79         if (n1 < 1 || n1 > MAX_TAIL_LINES) {
80                 ERR("invalid arg for %s, number of lines must be "
81                                 "between 1 and %i", me, MAX_TAIL_LINES);
82                 return 1;
83         }
84
85         obj->data.tail.fd = -1;
86
87         if (type == HEAD) {
88                 goto NO_FIFO;
89         }
90         to_real_path(buf, buf);
91         if (stat(buf, &st) == 0) {
92                 if (S_ISFIFO(st.st_mode)) {
93                         fd = open(buf, O_RDONLY | O_NONBLOCK);
94
95                         if (fd == -1) {
96                                 ERR("%s logfile does not exist, or you do "
97                                     "not have correct permissions", me);
98                                 return 1;
99                         }
100
101                         obj->data.tail.fd = fd;
102                 } else {
103 NO_FIFO:
104                         fp = fopen(buf, "r");
105                 }
106         }
107
108         if (fp || obj->data.tail.fd != -1) {
109                 obj->data.tail.logfile = malloc(text_buffer_size);
110                 strcpy(obj->data.tail.logfile, buf);
111                 obj->data.tail.wantedlines = n1;
112                 obj->data.tail.interval = update_interval * 2;
113
114                 if (obj->data.tail.fd == -1) {
115                         fclose(fp);
116                 }
117         } else {
118                 // fclose(fp);
119                 ERR("%s logfile does not exist, or you do not have "
120                                 "correct permissions", me);
121                 return 1;
122         }
123         /* XXX: the following implies update_interval >= 1 ?! */
124         if (numargs == 3 && (n2 < 1 || n2 < update_interval)) {
125                 ERR("%s interval must be greater than "
126                     "0 and "PACKAGE_NAME"'s interval, ignoring", me);
127         } else if (numargs == 3) {
128                         obj->data.tail.interval = n2;
129         }
130         /* asumming all else worked */
131         obj->data.tail.buffer = malloc(text_buffer_size * 20);
132         return 0;
133 }
134
135 /* Allows reading from a FIFO (i.e., /dev/xconsole).
136  * The file descriptor is set to non-blocking which makes this possible.
137  *
138  * FIXME: Since lseek cannot seek a file descriptor long lines will break. */
139 static void tail_pipe(struct text_object *obj, char *dst, size_t dst_size)
140 {
141 #define TAIL_PIPE_BUFSIZE       4096
142         int lines = 0;
143         int line_len = 0;
144         int last_line = 0;
145         int fd = obj->data.tail.fd;
146
147         while (1) {
148                 char buf[TAIL_PIPE_BUFSIZE];
149                 ssize_t len = read(fd, buf, sizeof(buf));
150                 int i;
151
152                 if (len == -1) {
153                         if (errno != EAGAIN) {
154                                 strcpy(obj->data.tail.buffer, "Logfile Read Error");
155                                 snprintf(dst, dst_size, "Logfile Read Error");
156                         }
157
158                         break;
159                 } else if (len == 0) {
160                         strcpy(obj->data.tail.buffer, "Logfile Empty");
161                         snprintf(dst, dst_size, "Logfile Empty");
162                         break;
163                 }
164
165                 for (line_len = 0, i = 0; i < len; i++) {
166                         int pos = 0;
167                         char *p;
168
169                         if (buf[i] == '\n') {
170                                 lines++;
171
172                                 if (obj->data.tail.readlines > 0) {
173                                         int n;
174                                         int olines = 0;
175                                         int first_line = 0;
176
177                                         for (n = 0; obj->data.tail.buffer[n]; n++) {
178                                                 if (obj->data.tail.buffer[n] == '\n') {
179                                                         if (!first_line) {
180                                                                 first_line = n + 1;
181                                                         }
182
183                                                         if (++olines < obj->data.tail.wantedlines) {
184                                                                 pos = n + 1;
185                                                                 continue;
186                                                         }
187
188                                                         n++;
189                                                         p = obj->data.tail.buffer + first_line;
190                                                         pos = n - first_line;
191                                                         memmove(obj->data.tail.buffer,
192                                                                         obj->data.tail.buffer + first_line, strlen(p));
193                                                         obj->data.tail.buffer[pos] = 0;
194                                                         break;
195                                                 }
196                                         }
197                                 }
198
199                                 p = buf + last_line;
200                                 line_len++;
201                                 memcpy(&(obj->data.tail.buffer[pos]), p, line_len);
202                                 obj->data.tail.buffer[pos + line_len] = 0;
203                                 last_line = i + 1;
204                                 line_len = 0;
205                                 obj->data.tail.readlines = lines;
206                                 continue;
207                         }
208
209                         line_len++;
210                 }
211         }
212
213         snprintf(dst, dst_size, "%s", obj->data.tail.buffer);
214 }
215
216 static long rev_fcharfind(FILE *fp, char val, unsigned int step)
217 {
218 #define BUFSZ 0x1000
219         long ret = -1;
220         unsigned int count = 0;
221         static char buf[BUFSZ];
222         long orig_pos = ftell(fp);
223         long buf_pos = -1;
224         long file_pos = orig_pos;
225         long buf_size = BUFSZ;
226         const char *cur_found;
227
228         while (count < step) {
229                 if (buf_pos <= 0) {
230                         if (file_pos > BUFSZ) {
231                                 fseek(fp, file_pos - BUFSZ, SEEK_SET);
232                         } else {
233                                 buf_size = file_pos;
234                                 fseek(fp, 0, SEEK_SET);
235                         }
236                         file_pos = ftell(fp);
237                         buf_pos = fread(buf, 1, buf_size, fp);
238                 }
239                 cur_found = memrchr(buf, val, (size_t) buf_pos);
240                 if (cur_found != NULL) {
241                         buf_pos = cur_found - buf;
242                         count++;
243                 } else {
244                         buf_pos = -1;
245                         if (file_pos == 0) {
246                                 break;
247                         }
248                 }
249         }
250         fseek(fp, orig_pos, SEEK_SET);
251         if (count == step) {
252                 ret = file_pos + buf_pos;
253         }
254         return ret;
255 }
256
257 int print_tail_object(struct text_object *obj, char *p, size_t p_max_size)
258 {
259         FILE *fp;
260         long nl = 0, bsize;
261         int iter;
262
263         if (current_update_time - obj->data.tail.last_update < obj->data.tail.interval) {
264                 snprintf(p, p_max_size, "%s", obj->data.tail.buffer);
265                 return 0;
266         }
267
268         obj->data.tail.last_update = current_update_time;
269
270         if (obj->data.tail.fd != -1) {
271                 tail_pipe(obj, p, p_max_size);
272                 return 0;
273         }
274
275         fp = fopen(obj->data.tail.logfile, "rt");
276         if (fp == NULL) {
277                 /* Send one message, but do not consistently spam
278                  * on missing logfiles. */
279                 if (obj->data.tail.readlines != 0) {
280                         ERR("tail logfile failed to open");
281                         strcpy(obj->data.tail.buffer, "Logfile Missing");
282                 }
283                 obj->data.tail.readlines = 0;
284                 snprintf(p, p_max_size, "Logfile Missing");
285         } else {
286                 obj->data.tail.readlines = 0;
287                 /* -1 instead of 0 to avoid counting a trailing
288                  * newline */
289                 fseek(fp, -1, SEEK_END);
290                 bsize = ftell(fp) + 1;
291                 for (iter = obj->data.tail.wantedlines; iter > 0;
292                                 iter--) {
293                         nl = rev_fcharfind(fp, '\n', iter);
294                         if (nl >= 0) {
295                                 break;
296                         }
297                 }
298                 obj->data.tail.readlines = iter;
299                 if (obj->data.tail.readlines
300                                 < obj->data.tail.wantedlines) {
301                         fseek(fp, 0, SEEK_SET);
302                 } else {
303                         fseek(fp, nl + 1, SEEK_SET);
304                         bsize -= ftell(fp);
305                 }
306                 /* Make sure bsize is at least 1 byte smaller than the
307                  * buffer max size. */
308                 if (bsize > (long) ((text_buffer_size * 20) - 1)) {
309                         fseek(fp, bsize - text_buffer_size * 20 - 1,
310                                         SEEK_CUR);
311                         bsize = text_buffer_size * 20 - 1;
312                 }
313                 bsize = fread(obj->data.tail.buffer, 1, bsize, fp);
314                 fclose(fp);
315                 if (bsize > 0) {
316                         /* Clean up trailing newline, make sure the
317                          * buffer is null terminated. */
318                         if (obj->data.tail.buffer[bsize - 1] == '\n') {
319                                 obj->data.tail.buffer[bsize - 1] = '\0';
320                         } else {
321                                 obj->data.tail.buffer[bsize] = '\0';
322                         }
323                         snprintf(p, p_max_size, "%s",
324                                         obj->data.tail.buffer);
325                 } else {
326                         strcpy(obj->data.tail.buffer, "Logfile Empty");
327                         snprintf(p, p_max_size, "Logfile Empty");
328                 }       /* bsize > 0 */
329         }               /* fp == NULL */
330         return 0;
331 }
332
333 long fwd_fcharfind(FILE *fp, char val, unsigned int step)
334 {
335 #define BUFSZ 0x1000
336        long ret = -1;
337        unsigned int count = 0;
338        static char buf[BUFSZ];
339        long orig_pos = ftell(fp);
340        long buf_pos = -1;
341        long buf_size = BUFSZ;
342        char *cur_found = NULL;
343
344        while (count < step) {
345                if (cur_found == NULL) {
346                        buf_size = fread(buf, 1, buf_size, fp);
347                        buf_pos = 0;
348                }
349                cur_found = memchr(buf + buf_pos, val, buf_size - buf_pos);
350                if (cur_found != NULL) {
351                        buf_pos = cur_found - buf + 1;
352                        count++;
353                } else {
354                        if (feof(fp)) {
355                                break;
356                        }
357                }
358        }
359        if (count == step) {
360                ret = ftell(fp) - buf_size + buf_pos - 1;
361        }
362        fseek(fp, orig_pos, SEEK_SET);
363        return ret;
364 }
365
366 int print_head_object(struct text_object *obj, char *p, size_t p_max_size)
367 {
368         FILE *fp;
369         long nl = 0;
370         int iter;
371
372         if (current_update_time - obj->data.tail.last_update < obj->data.tail.interval) {
373                 snprintf(p, p_max_size, "%s", obj->data.tail.buffer);
374                 return 0;
375         }
376
377         obj->data.tail.last_update = current_update_time;
378
379         fp = fopen(obj->data.tail.logfile, "rt");
380         if (fp == NULL) {
381                 /* Send one message, but do not consistently spam
382                  * on missing logfiles. */
383                 if (obj->data.tail.readlines != 0) {
384                         ERR("head logfile failed to open");
385                         strcpy(obj->data.tail.buffer, "Logfile Missing");
386                 }
387                 obj->data.tail.readlines = 0;
388                 snprintf(p, p_max_size, "Logfile Missing");
389         } else {
390                 obj->data.tail.readlines = 0;
391                 for (iter = obj->data.tail.wantedlines; iter > 0;
392                                 iter--) {
393                         nl = fwd_fcharfind(fp, '\n', iter);
394                         if (nl >= 0) {
395                                 break;
396                         }
397                 }
398                 obj->data.tail.readlines = iter;
399                 /* Make sure nl is at least 1 byte smaller than the
400                  * buffer max size. */
401                 if (nl > (long) ((text_buffer_size * 20) - 1)) {
402                         nl = text_buffer_size * 20 - 1;
403                 }
404                 nl = fread(obj->data.tail.buffer, 1, nl, fp);
405                 fclose(fp);
406                 if (nl > 0) {
407                         /* Clean up trailing newline, make sure the buffer
408                          * is null terminated. */
409                         if (obj->data.tail.buffer[nl - 1] == '\n') {
410                                 obj->data.tail.buffer[nl - 1] = '\0';
411                         } else {
412                                 obj->data.tail.buffer[nl] = '\0';
413                         }
414                         snprintf(p, p_max_size, "%s",
415                                         obj->data.tail.buffer);
416                 } else {
417                         strcpy(obj->data.tail.buffer, "Logfile Empty");
418                         snprintf(p, p_max_size, "Logfile Empty");
419                 }       /* nl > 0 */
420         }               /* if fp == null */
421         return 0;
422 }