01bdfcb20f6e5f9cb9a4a33680aefb1dbb894650
[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-2008 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 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 (void *) 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[64];
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, "%63s %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         if (stat(buf, &st) == 0) {
91                 if (S_ISFIFO(st.st_mode)) {
92                         fd = open(buf, O_RDONLY | O_NONBLOCK);
93
94                         if (fd == -1) {
95                                 ERR("%s logfile does not exist, or you do "
96                                     "not have correct permissions", me);
97                                 return 1;
98                         }
99
100                         obj->data.tail.fd = fd;
101                 } else {
102 NO_FIFO:
103                         fp = fopen(buf, "r");
104                 }
105         }
106
107         if (fp || obj->data.tail.fd != -1) {
108                 obj->data.tail.logfile = malloc(text_buffer_size);
109                 strcpy(obj->data.tail.logfile, buf);
110                 obj->data.tail.wantedlines = n1;
111                 obj->data.tail.interval = update_interval * 2;
112
113                 if (obj->data.tail.fd == -1) {
114                         fclose(fp);
115                 }
116         } else {
117                 // fclose(fp);
118                 ERR("%s logfile does not exist, or you do not have "
119                                 "correct permissions", me);
120                 return 1;
121         }
122         /* XXX: the following implies update_interval >= 1 ?! */
123         if (numargs == 3 && (n2 < 1 || n2 < update_interval)) {
124                 ERR("%s interval must be greater than "
125                     "0 and "PACKAGE_NAME"'s interval, ignoring", me);
126         } else if (numargs == 3) {
127                         obj->data.tail.interval = n2;
128         }
129         /* asumming all else worked */
130         obj->data.tail.buffer = malloc(text_buffer_size * 20);
131         return 0;
132 }
133
134 /* Allows reading from a FIFO (i.e., /dev/xconsole).
135  * The file descriptor is set to non-blocking which makes this possible.
136  *
137  * FIXME: Since lseek cannot seek a file descriptor long lines will break. */
138 static void tail_pipe(struct text_object *obj, char *dst, size_t dst_size)
139 {
140 #define TAIL_PIPE_BUFSIZE       4096
141         int lines = 0;
142         int line_len = 0;
143         int last_line = 0;
144         int fd = obj->data.tail.fd;
145
146         while (1) {
147                 char buf[TAIL_PIPE_BUFSIZE];
148                 ssize_t len = read(fd, buf, sizeof(buf));
149                 int i;
150
151                 if (len == -1) {
152                         if (errno != EAGAIN) {
153                                 strcpy(obj->data.tail.buffer, "Logfile Read Error");
154                                 snprintf(dst, dst_size, "Logfile Read Error");
155                         }
156
157                         break;
158                 } else if (len == 0) {
159                         strcpy(obj->data.tail.buffer, "Logfile Empty");
160                         snprintf(dst, dst_size, "Logfile Empty");
161                         break;
162                 }
163
164                 for (line_len = 0, i = 0; i < len; i++) {
165                         int pos = 0;
166                         char *p;
167
168                         if (buf[i] == '\n') {
169                                 lines++;
170
171                                 if (obj->data.tail.readlines > 0) {
172                                         int n;
173                                         int olines = 0;
174                                         int first_line = 0;
175
176                                         for (n = 0; obj->data.tail.buffer[n]; n++) {
177                                                 if (obj->data.tail.buffer[n] == '\n') {
178                                                         if (!first_line) {
179                                                                 first_line = n + 1;
180                                                         }
181
182                                                         if (++olines < obj->data.tail.wantedlines) {
183                                                                 pos = n + 1;
184                                                                 continue;
185                                                         }
186
187                                                         n++;
188                                                         p = obj->data.tail.buffer + first_line;
189                                                         pos = n - first_line;
190                                                         memmove(obj->data.tail.buffer,
191                                                                         obj->data.tail.buffer + first_line, strlen(p));
192                                                         obj->data.tail.buffer[pos] = 0;
193                                                         break;
194                                                 }
195                                         }
196                                 }
197
198                                 p = buf + last_line;
199                                 line_len++;
200                                 memcpy(&(obj->data.tail.buffer[pos]), p, line_len);
201                                 obj->data.tail.buffer[pos + line_len] = 0;
202                                 last_line = i + 1;
203                                 line_len = 0;
204                                 obj->data.tail.readlines = lines;
205                                 continue;
206                         }
207
208                         line_len++;
209                 }
210         }
211
212         snprintf(dst, dst_size, "%s", obj->data.tail.buffer);
213 }
214
215 static long rev_fcharfind(FILE *fp, char val, unsigned int step)
216 {
217 #define BUFSZ 0x1000
218         long ret = -1;
219         unsigned int count = 0;
220         static char buf[BUFSZ];
221         long orig_pos = ftell(fp);
222         long buf_pos = -1;
223         long file_pos = orig_pos;
224         long buf_size = BUFSZ;
225         char *cur_found;
226
227         while (count < step) {
228                 if (buf_pos <= 0) {
229                         if (file_pos > BUFSZ) {
230                                 fseek(fp, file_pos - BUFSZ, SEEK_SET);
231                         } else {
232                                 buf_size = file_pos;
233                                 fseek(fp, 0, SEEK_SET);
234                         }
235                         file_pos = ftell(fp);
236                         buf_pos = fread(buf, 1, buf_size, fp);
237                 }
238                 cur_found = memrchr(buf, val, (size_t) buf_pos);
239                 if (cur_found != NULL) {
240                         buf_pos = cur_found - buf;
241                         count++;
242                 } else {
243                         buf_pos = -1;
244                         if (file_pos == 0) {
245                                 break;
246                         }
247                 }
248         }
249         fseek(fp, orig_pos, SEEK_SET);
250         if (count == step) {
251                 ret = file_pos + buf_pos;
252         }
253         return ret;
254 }
255
256 int print_tail_object(struct text_object *obj, char *p, size_t p_max_size)
257 {
258         FILE *fp;
259         long nl = 0, bsize;
260         int iter;
261
262         if (current_update_time - obj->data.tail.last_update < obj->data.tail.interval) {
263                 snprintf(p, p_max_size, "%s", obj->data.tail.buffer);
264                 return 0;
265         }
266
267         obj->data.tail.last_update = current_update_time;
268
269         if (obj->data.tail.fd != -1) {
270                 tail_pipe(obj, p, p_max_size);
271                 return 0;
272         }
273
274         fp = fopen(obj->data.tail.logfile, "rt");
275         if (fp == NULL) {
276                 /* Send one message, but do not consistently spam
277                  * on missing logfiles. */
278                 if (obj->data.tail.readlines != 0) {
279                         ERR("tail logfile failed to open");
280                         strcpy(obj->data.tail.buffer, "Logfile Missing");
281                 }
282                 obj->data.tail.readlines = 0;
283                 snprintf(p, p_max_size, "Logfile Missing");
284         } else {
285                 obj->data.tail.readlines = 0;
286                 /* -1 instead of 0 to avoid counting a trailing
287                  * newline */
288                 fseek(fp, -1, SEEK_END);
289                 bsize = ftell(fp) + 1;
290                 for (iter = obj->data.tail.wantedlines; iter > 0;
291                                 iter--) {
292                         nl = rev_fcharfind(fp, '\n', iter);
293                         if (nl >= 0) {
294                                 break;
295                         }
296                 }
297                 obj->data.tail.readlines = iter;
298                 if (obj->data.tail.readlines
299                                 < obj->data.tail.wantedlines) {
300                         fseek(fp, 0, SEEK_SET);
301                 } else {
302                         fseek(fp, nl + 1, SEEK_SET);
303                         bsize -= ftell(fp);
304                 }
305                 /* Make sure bsize is at least 1 byte smaller than the
306                  * buffer max size. */
307                 if (bsize > (long) ((text_buffer_size * 20) - 1)) {
308                         fseek(fp, bsize - text_buffer_size * 20 - 1,
309                                         SEEK_CUR);
310                         bsize = text_buffer_size * 20 - 1;
311                 }
312                 bsize = fread(obj->data.tail.buffer, 1, bsize, fp);
313                 fclose(fp);
314                 if (bsize > 0) {
315                         /* Clean up trailing newline, make sure the
316                          * buffer is null terminated. */
317                         if (obj->data.tail.buffer[bsize - 1] == '\n') {
318                                 obj->data.tail.buffer[bsize - 1] = '\0';
319                         } else {
320                                 obj->data.tail.buffer[bsize] = '\0';
321                         }
322                         snprintf(p, p_max_size, "%s",
323                                         obj->data.tail.buffer);
324                 } else {
325                         strcpy(obj->data.tail.buffer, "Logfile Empty");
326                         snprintf(p, p_max_size, "Logfile Empty");
327                 }       /* bsize > 0 */
328         }               /* fp == NULL */
329         return 0;
330 }
331
332 long fwd_fcharfind(FILE *fp, char val, unsigned int step)
333 {
334 #define BUFSZ 0x1000
335        long ret = -1;
336        unsigned int count = 0;
337        static char buf[BUFSZ];
338        long orig_pos = ftell(fp);
339        long buf_pos = -1;
340        long buf_size = BUFSZ;
341        char *cur_found = NULL;
342
343        while (count < step) {
344                if (cur_found == NULL) {
345                        buf_size = fread(buf, 1, buf_size, fp);
346                        buf_pos = 0;
347                }
348                cur_found = memchr(buf + buf_pos, val, buf_size - buf_pos);
349                if (cur_found != NULL) {
350                        buf_pos = cur_found - buf + 1;
351                        count++;
352                } else {
353                        if (feof(fp)) {
354                                break;
355                        }
356                }
357        }
358        if (count == step) {
359                ret = ftell(fp) - buf_size + buf_pos - 1;
360        }
361        fseek(fp, orig_pos, SEEK_SET);
362        return ret;
363 }
364
365 int print_head_object(struct text_object *obj, char *p, size_t p_max_size)
366 {
367         FILE *fp;
368         long nl = 0;
369         int iter;
370
371         if (current_update_time - obj->data.tail.last_update < obj->data.tail.interval) {
372                 snprintf(p, p_max_size, "%s", obj->data.tail.buffer);
373                 return 0;
374         }
375
376         obj->data.tail.last_update = current_update_time;
377
378         fp = fopen(obj->data.tail.logfile, "rt");
379         if (fp == NULL) {
380                 /* Send one message, but do not consistently spam
381                  * on missing logfiles. */
382                 if (obj->data.tail.readlines != 0) {
383                         ERR("head logfile failed to open");
384                         strcpy(obj->data.tail.buffer, "Logfile Missing");
385                 }
386                 obj->data.tail.readlines = 0;
387                 snprintf(p, p_max_size, "Logfile Missing");
388         } else {
389                 obj->data.tail.readlines = 0;
390                 for (iter = obj->data.tail.wantedlines; iter > 0;
391                                 iter--) {
392                         nl = fwd_fcharfind(fp, '\n', iter);
393                         if (nl >= 0) {
394                                 break;
395                         }
396                 }
397                 obj->data.tail.readlines = iter;
398                 /* Make sure nl is at least 1 byte smaller than the
399                  * buffer max size. */
400                 if (nl > (long) ((text_buffer_size * 20) - 1)) {
401                         nl = text_buffer_size * 20 - 1;
402                 }
403                 nl = fread(obj->data.tail.buffer, 1, nl, fp);
404                 fclose(fp);
405                 if (nl > 0) {
406                         /* Clean up trailing newline, make sure the buffer
407                          * is null terminated. */
408                         if (obj->data.tail.buffer[nl - 1] == '\n') {
409                                 obj->data.tail.buffer[nl - 1] = '\0';
410                         } else {
411                                 obj->data.tail.buffer[nl] = '\0';
412                         }
413                         snprintf(p, p_max_size, "%s",
414                                         obj->data.tail.buffer);
415                 } else {
416                         strcpy(obj->data.tail.buffer, "Logfile Empty");
417                         snprintf(p, p_max_size, "Logfile Empty");
418                 }       /* nl > 0 */
419         }               /* if fp == null */
420         return 0;
421 }