syslogd: add option to suppress logging of messages lower than level N (-n N)
[platform/upstream/busybox.git] / sysklogd / syslogd.c
1 /* vi: set sw=4 ts=4: */
2 /*
3  * Mini syslogd implementation for busybox
4  *
5  * Copyright (C) 1999-2004 by Erik Andersen <andersen@codepoet.org>
6  *
7  * Copyright (C) 2000 by Karl M. Hegbloom <karlheg@debian.org>
8  *
9  * "circular buffer" Copyright (C) 2001 by Gennady Feldman <gfeldman@gena01.com>
10  *
11  * Maintainer: Gennady Feldman <gfeldman@gena01.com> as of Mar 12, 2001
12  *
13  * Licensed under the GPL v2 or later, see the file LICENSE in this tarball.
14  */
15
16 #include "busybox.h"
17 #include <paths.h>
18 #include <stdbool.h>
19 #include <sys/un.h>
20
21 /* SYSLOG_NAMES defined to pull some extra junk from syslog.h */
22 #define SYSLOG_NAMES
23 #include <sys/syslog.h>
24 #include <sys/uio.h>
25
26 /* Path for the file where all log messages are written */
27 #define __LOG_FILE "/var/log/messages"
28
29 /* Path to the unix socket */
30 static char lfile[MAXPATHLEN];
31
32 static const char *logFilePath = __LOG_FILE;
33
34 #ifdef CONFIG_FEATURE_ROTATE_LOGFILE
35 /* max size of message file before being rotated */
36 static int logFileSize = 200 * 1024;
37
38 /* number of rotated message files */
39 static int logFileRotate = 1;
40 #endif
41
42 /* interval between marks in seconds */
43 static int MarkInterval = 20 * 60;
44
45 /* level of messages to be locally logged */
46 static int logLevel = 8;
47
48 /* localhost's name */
49 static char LocalHostName[64];
50
51 #ifdef CONFIG_FEATURE_REMOTE_LOG
52 #include <netinet/in.h>
53 /* udp socket for logging to remote host */
54 static int remotefd = -1;
55 static struct sockaddr_in remoteaddr;
56
57 /* where do we log? */
58 static char *RemoteHost;
59
60 /* what port to log to? */
61 static int RemotePort = 514;
62
63 #endif
64
65 /* options */
66 static unsigned opts;
67 #define SYSLOG_OPT_small     (1)
68 #define SYSLOG_OPT_remotelog (2)
69 #define SYSLOG_OPT_locallog  (4)
70 #define SYSLOG_OPT_circularlog (8)
71
72 #define MAXLINE         1024    /* maximum line length */
73
74
75 /* circular buffer variables/structures */
76 #ifdef CONFIG_FEATURE_IPC_SYSLOG
77
78 #if CONFIG_FEATURE_IPC_SYSLOG_BUFFER_SIZE < 4
79 #error Sorry, you must set the syslogd buffer size to at least 4KB.
80 #error Please check CONFIG_FEATURE_IPC_SYSLOG_BUFFER_SIZE
81 #endif
82
83 #include <sys/ipc.h>
84 #include <sys/sem.h>
85 #include <sys/shm.h>
86
87 /* our shared key */
88 static const long KEY_ID = 0x414e4547;  /*"GENA" */
89
90 // Semaphore operation structures
91 static struct shbuf_ds {
92         int size;                       // size of data written
93         int head;                       // start of message list
94         int tail;                       // end of message list
95         char data[1];           // data/messages
96 } *buf = NULL;                  // shared memory pointer
97
98 static struct sembuf SMwup[1] = { {1, -1, IPC_NOWAIT} };        // set SMwup
99 static struct sembuf SMwdn[3] = { {0, 0}, {1, 0}, {1, +1} };    // set SMwdn
100
101 static int shmid = -1;  // ipc shared memory id
102 static int s_semid = -1;        // ipc semaphore id
103 static int shm_size = ((CONFIG_FEATURE_IPC_SYSLOG_BUFFER_SIZE)*1024);   // default shm size
104
105 static void ipcsyslog_cleanup(void)
106 {
107         printf("Exiting Syslogd!\n");
108         if (shmid != -1) {
109                 shmdt(buf);
110         }
111
112         if (shmid != -1) {
113                 shmctl(shmid, IPC_RMID, NULL);
114         }
115         if (s_semid != -1) {
116                 semctl(s_semid, 0, IPC_RMID, 0);
117         }
118 }
119
120 static void ipcsyslog_init(void)
121 {
122         if (buf == NULL) {
123                 if ((shmid = shmget(KEY_ID, shm_size, IPC_CREAT | 1023)) == -1) {
124                         bb_perror_msg_and_die("shmget");
125                 }
126
127                 if ((buf = shmat(shmid, NULL, 0)) == NULL) {
128                         bb_perror_msg_and_die("shmat");
129                 }
130
131                 buf->size = shm_size - sizeof(*buf);
132                 buf->head = buf->tail = 0;
133
134                 // we'll trust the OS to set initial semval to 0 (let's hope)
135                 if ((s_semid = semget(KEY_ID, 2, IPC_CREAT | IPC_EXCL | 1023)) == -1) {
136                         if (errno == EEXIST) {
137                                 if ((s_semid = semget(KEY_ID, 2, 0)) == -1) {
138                                         bb_perror_msg_and_die("semget");
139                                 }
140                         } else {
141                                 bb_perror_msg_and_die("semget");
142                         }
143                 }
144         } else {
145                 printf("Buffer already allocated just grab the semaphore?");
146         }
147 }
148
149 /* write message to buffer */
150 static void circ_message(const char *msg)
151 {
152         int l = strlen(msg) + 1;        /* count the whole message w/ '\0' included */
153         const char * const fail_msg = "Can't find the terminator token%s?\n";
154
155         if (semop(s_semid, SMwdn, 3) == -1) {
156                 bb_perror_msg_and_die("SMwdn");
157         }
158
159         /*
160          * Circular Buffer Algorithm:
161          * --------------------------
162          *
163          * Start-off w/ empty buffer of specific size SHM_SIZ
164          * Start filling it up w/ messages. I use '\0' as separator to break up messages.
165          * This is also very handy since we can do printf on message.
166          *
167          * Once the buffer is full we need to get rid of the first message in buffer and
168          * insert the new message. (Note: if the message being added is >1 message then
169          * we will need to "remove" >1 old message from the buffer). The way this is done
170          * is the following:
171          *      When we reach the end of the buffer we set a mark and start from the beginning.
172          *      Now what about the beginning and end of the buffer? Well we have the "head"
173          *      index/pointer which is the starting point for the messages and we have "tail"
174          *      index/pointer which is the ending point for the messages. When we "display" the
175          *      messages we start from the beginning and continue until we reach "tail". If we
176          *      reach end of buffer, then we just start from the beginning (offset 0). "head" and
177          *      "tail" are actually offsets from the beginning of the buffer.
178          *
179          * Note: This algorithm uses Linux IPC mechanism w/ shared memory and semaphores to provide
180          *       a threadsafe way of handling shared memory operations.
181          */
182         if ((buf->tail + l) < buf->size) {
183                 /* before we append the message we need to check the HEAD so that we won't
184                    overwrite any of the message that we still need and adjust HEAD to point
185                    to the next message! */
186                 if (buf->tail < buf->head) {
187                         if ((buf->tail + l) >= buf->head) {
188                                 /* we need to move the HEAD to point to the next message
189                                  * Theoretically we have enough room to add the whole message to the
190                                  * buffer, because of the first outer IF statement, so we don't have
191                                  * to worry about overflows here!
192                                  */
193                                 int k = buf->tail + l - buf->head;      /* we need to know how many bytes
194                                                                                                            we are overwriting to make
195                                                                                                            enough room */
196                                 char *c =
197                                         memchr(buf->data + buf->head + k, '\0',
198                                                    buf->size - (buf->head + k));
199                                 if (c != NULL) {        /* do a sanity check just in case! */
200                                         buf->head = c - buf->data + 1;  /* we need to convert pointer to
201                                                                                                            offset + skip the '\0' since
202                                                                                                            we need to point to the beginning
203                                                                                                            of the next message */
204                                         /* Note: HEAD is only used to "retrieve" messages, it's not used
205                                            when writing messages into our buffer */
206                                 } else {        /* show an error message to know we messed up? */
207                                         printf(fail_msg,"");
208                                         buf->head = 0;
209                                 }
210                         }
211                 }
212
213                 /* in other cases no overflows have been done yet, so we don't care! */
214                 /* we should be ok to append the message now */
215                 strncpy(buf->data + buf->tail, msg, l); /* append our message */
216                 buf->tail += l; /* count full message w/ '\0' terminating char */
217         } else {
218                 /* we need to break up the message and "circle" it around */
219                 char *c;
220                 int k = buf->tail + l - buf->size;      /* count # of bytes we don't fit */
221
222                 /* We need to move HEAD! This is always the case since we are going
223                  * to "circle" the message.
224                  */
225                 c = memchr(buf->data + k, '\0', buf->size - k);
226
227                 if (c != NULL) {        /* if we don't have '\0'??? weird!!! */
228                         /* move head pointer */
229                         buf->head = c - buf->data + 1;
230
231                         /* now write the first part of the message */
232                         strncpy(buf->data + buf->tail, msg, l - k - 1);
233
234                         /* ALWAYS terminate end of buffer w/ '\0' */
235                         buf->data[buf->size - 1] = '\0';
236
237                         /* now write out the rest of the string to the beginning of the buffer */
238                         strcpy(buf->data, &msg[l - k - 1]);
239
240                         /* we need to place the TAIL at the end of the message */
241                         buf->tail = k + 1;
242                 } else {
243                         printf(fail_msg, " from the beginning");
244                         buf->head = buf->tail = 0;      /* reset buffer, since it's probably corrupted */
245                 }
246
247         }
248         if (semop(s_semid, SMwup, 1) == -1) {
249                 bb_perror_msg_and_die("SMwup");
250         }
251
252 }
253 #else
254 void ipcsyslog_cleanup(void);
255 void ipcsyslog_init(void);
256 void circ_message(const char *msg);
257 #endif                                                  /* CONFIG_FEATURE_IPC_SYSLOG */
258
259 /* Note: There is also a function called "message()" in init.c */
260 /* Print a message to the log file. */
261 static void message(char *fmt, ...) __attribute__ ((format(printf, 1, 2)));
262 static void message(char *fmt, ...)
263 {
264         int fd = -1;
265         struct flock fl;
266         va_list arguments;
267
268         fl.l_whence = SEEK_SET;
269         fl.l_start = 0;
270         fl.l_len = 1;
271
272 #ifdef CONFIG_FEATURE_IPC_SYSLOG
273         if ((opts & SYSLOG_OPT_circularlog) && (buf != NULL)) {
274                 char b[1024];
275
276                 va_start(arguments, fmt);
277                 vsnprintf(b, sizeof(b) - 1, fmt, arguments);
278                 va_end(arguments);
279                 circ_message(b);
280
281         } else
282 #endif
283         fd = device_open(logFilePath, O_WRONLY | O_CREAT
284                                         | O_NOCTTY | O_APPEND | O_NONBLOCK);
285         if (fd >= 0) {
286                 fl.l_type = F_WRLCK;
287                 fcntl(fd, F_SETLKW, &fl);
288
289 #ifdef CONFIG_FEATURE_ROTATE_LOGFILE
290                 if (ENABLE_FEATURE_ROTATE_LOGFILE && logFileSize > 0 ) {
291                         struct stat statf;
292                         int r = fstat(fd, &statf);
293                         if( !r && (statf.st_mode & S_IFREG)
294                                 && (lseek(fd,0,SEEK_END) > logFileSize) ) {
295                                 if(logFileRotate > 0) {
296                                         int i;
297                                         char oldFile[(strlen(logFilePath)+4)];
298                                         char newFile[(strlen(logFilePath)+4)];
299                                         for(i=logFileRotate-1;i>0;i--) {
300                                                 sprintf(oldFile, "%s.%d", logFilePath, i-1);
301                                                 sprintf(newFile, "%s.%d", logFilePath, i);
302                                                 rename(oldFile, newFile);
303                                         }
304                                         sprintf(newFile, "%s.%d", logFilePath, 0);
305                                         fl.l_type = F_UNLCK;
306                                         fcntl (fd, F_SETLKW, &fl);
307                                         close(fd);
308                                         rename(logFilePath, newFile);
309                                         fd = device_open (logFilePath,
310                                                    O_WRONLY | O_CREAT | O_NOCTTY | O_APPEND |
311                                                    O_NONBLOCK);
312                                         fl.l_type = F_WRLCK;
313                                         fcntl (fd, F_SETLKW, &fl);
314                                 } else {
315                                         ftruncate( fd, 0 );
316                                 }
317                         }
318                 }
319 #endif
320                 va_start(arguments, fmt);
321                 vdprintf(fd, fmt, arguments);
322                 va_end(arguments);
323                 fl.l_type = F_UNLCK;
324                 fcntl(fd, F_SETLKW, &fl);
325                 close(fd);
326         } else {
327                 /* Always send console messages to /dev/console so people will see them. */
328                 fd = device_open(_PATH_CONSOLE, O_WRONLY | O_NOCTTY | O_NONBLOCK);
329                 if (fd >= 0) {
330                         va_start(arguments, fmt);
331                         vdprintf(fd, fmt, arguments);
332                         va_end(arguments);
333                         close(fd);
334                 } else {
335                         fprintf(stderr, "Bummer, can't print: ");
336                         va_start(arguments, fmt);
337                         vfprintf(stderr, fmt, arguments);
338                         fflush(stderr);
339                         va_end(arguments);
340                 }
341         }
342 }
343
344 #ifdef CONFIG_FEATURE_REMOTE_LOG
345 static void init_RemoteLog(void)
346 {
347         memset(&remoteaddr, 0, sizeof(remoteaddr));
348         remotefd = xsocket(AF_INET, SOCK_DGRAM, 0);
349         remoteaddr.sin_family = AF_INET;
350         remoteaddr.sin_addr = *(struct in_addr *) *(xgethostbyname(RemoteHost))->h_addr_list;
351         remoteaddr.sin_port = htons(RemotePort);
352 }
353 #else
354 void init_RemoteLog(void);
355 #endif
356
357 static void logMessage(int pri, char *msg)
358 {
359         time_t now;
360         char *timestamp;
361         char res[20];
362         CODE *c_pri, *c_fac;
363
364         if (pri != 0) {
365                 for (c_fac = facilitynames;
366                          c_fac->c_name && !(c_fac->c_val == LOG_FAC(pri) << 3); c_fac++);
367                 for (c_pri = prioritynames;
368                          c_pri->c_name && !(c_pri->c_val == LOG_PRI(pri)); c_pri++);
369                 if (c_fac->c_name == NULL || c_pri->c_name == NULL) {
370                         snprintf(res, sizeof(res), "<%d>", pri);
371                 } else {
372                         snprintf(res, sizeof(res), "%s.%s", c_fac->c_name, c_pri->c_name);
373                 }
374         }
375
376         if (strlen(msg) < 16 || msg[3] != ' ' || msg[6] != ' ' ||
377                 msg[9] != ':' || msg[12] != ':' || msg[15] != ' ') {
378                 time(&now);
379                 timestamp = ctime(&now) + 4;
380                 timestamp[15] = '\0';
381         } else {
382                 timestamp = msg;
383                 timestamp[15] = '\0';
384                 msg += 16;
385         }
386
387         /* todo: supress duplicates */
388
389 #ifdef CONFIG_FEATURE_REMOTE_LOG
390         if (opts & SYSLOG_OPT_remotelog) {
391                 char line[MAXLINE + 1];
392                 /* trying connect the socket */
393                 if (-1 == remotefd) {
394                         init_RemoteLog();
395                 }
396
397                 /* if we have a valid socket, send the message */
398                 if (-1 != remotefd) {
399                         now = 1;
400                         snprintf(line, sizeof(line), "<%d>%s", pri, msg);
401
402 retry:
403                         /* send message to remote logger */
404                         if(( -1 == sendto(remotefd, line, strlen(line), 0,
405                                                         (struct sockaddr *) &remoteaddr,
406                                                         sizeof(remoteaddr))) && (errno == EINTR)) {
407                                 /* sleep now seconds and retry (with now * 2) */
408                                 sleep(now);
409                                 now *= 2;
410                                 goto retry;
411                         }
412                 }
413         }
414
415         if (opts & SYSLOG_OPT_locallog)
416 #endif
417         {
418                 /* now spew out the message to wherever it is supposed to go */
419                 if (pri == 0 || LOG_PRI(pri) < logLevel) {
420                         if (opts & SYSLOG_OPT_small)
421                                 message("%s %s\n", timestamp, msg);
422                         else
423                                 message("%s %s %s %s\n", timestamp, LocalHostName, res, msg);
424                 }
425         }
426 }
427
428 static void quit_signal(int sig)
429 {
430         logMessage(LOG_SYSLOG | LOG_INFO, "System log daemon exiting.");
431         unlink(lfile);
432         if (ENABLE_FEATURE_IPC_SYSLOG)
433                 ipcsyslog_cleanup();
434
435         exit(TRUE);
436 }
437
438 static void domark(int sig)
439 {
440         if (MarkInterval > 0) {
441                 logMessage(LOG_SYSLOG | LOG_INFO, "-- MARK --");
442                 alarm(MarkInterval);
443         }
444 }
445
446 /* This must be a #define, since when CONFIG_DEBUG and BUFFERS_GO_IN_BSS are
447  * enabled, we otherwise get a "storage size isn't constant error. */
448 static int serveConnection(char *tmpbuf, int n_read)
449 {
450         char *p = tmpbuf;
451
452         while (p < tmpbuf + n_read) {
453
454                 int pri = (LOG_USER | LOG_NOTICE);
455                 int num_lt = 0;
456                 char line[MAXLINE + 1];
457                 unsigned char c;
458                 char *q = line;
459
460                 while ((c = *p) && q < &line[sizeof(line) - 1]) {
461                         if (c == '<' && num_lt == 0) {
462                                 /* Parse the magic priority number. */
463                                 num_lt++;
464                                 pri = 0;
465                                 while (isdigit(*(++p))) {
466                                         pri = 10 * pri + (*p - '0');
467                                 }
468                                 if (pri & ~(LOG_FACMASK | LOG_PRIMASK)) {
469                                         pri = (LOG_USER | LOG_NOTICE);
470                                 }
471                         } else if (c == '\n') {
472                                 *q++ = ' ';
473                         } else if (iscntrl(c) && (c < 0177)) {
474                                 *q++ = '^';
475                                 *q++ = c ^ 0100;
476                         } else {
477                                 *q++ = c;
478                         }
479                         p++;
480                 }
481                 *q = '\0';
482                 p++;
483                 /* Now log it */
484                 logMessage(pri, line);
485         }
486         return n_read;
487 }
488
489 static void doSyslogd(void) ATTRIBUTE_NORETURN;
490 static void doSyslogd(void)
491 {
492         struct sockaddr_un sunx;
493         socklen_t addrLength;
494
495         int sock_fd;
496         fd_set fds;
497
498         /* Set up signal handlers. */
499         signal(SIGINT, quit_signal);
500         signal(SIGTERM, quit_signal);
501         signal(SIGQUIT, quit_signal);
502         signal(SIGHUP, SIG_IGN);
503         signal(SIGCHLD, SIG_IGN);
504 #ifdef SIGCLD
505         signal(SIGCLD, SIG_IGN);
506 #endif
507         signal(SIGALRM, domark);
508         alarm(MarkInterval);
509
510         /* Create the syslog file so realpath() can work. */
511         if (realpath(_PATH_LOG, lfile) != NULL) {
512                 unlink(lfile);
513         }
514
515         memset(&sunx, 0, sizeof(sunx));
516         sunx.sun_family = AF_UNIX;
517         strncpy(sunx.sun_path, lfile, sizeof(sunx.sun_path));
518         sock_fd = xsocket(AF_UNIX, SOCK_DGRAM, 0);
519         addrLength = sizeof(sunx.sun_family) + strlen(sunx.sun_path);
520         if (bind(sock_fd, (struct sockaddr *) &sunx, addrLength) < 0) {
521                 bb_perror_msg_and_die("Could not connect to socket " _PATH_LOG);
522         }
523
524         if (chmod(lfile, 0666) < 0) {
525                 bb_perror_msg_and_die("Could not set permission on " _PATH_LOG);
526         }
527         if (ENABLE_FEATURE_IPC_SYSLOG && opts & SYSLOG_OPT_circularlog) {
528                 ipcsyslog_init();
529         }
530
531         if (ENABLE_FEATURE_REMOTE_LOG && opts & SYSLOG_OPT_remotelog) {
532                 init_RemoteLog();
533         }
534
535         logMessage(LOG_SYSLOG | LOG_INFO, "syslogd started: " "BusyBox v" BB_VER );
536
537         for (;;) {
538
539                 FD_ZERO(&fds);
540                 FD_SET(sock_fd, &fds);
541
542                 if (select(sock_fd + 1, &fds, NULL, NULL, NULL) < 0) {
543                         if (errno == EINTR) {
544                                 /* alarm may have happened. */
545                                 continue;
546                         }
547                         bb_perror_msg_and_die("select error");
548                 }
549
550                 if (FD_ISSET(sock_fd, &fds)) {
551                         int i;
552 #if MAXLINE > BUFSIZ
553 # define TMP_BUF_SZ BUFSIZ
554 #else
555 # define TMP_BUF_SZ MAXLINE
556 #endif
557 #define tmpbuf bb_common_bufsiz1
558
559                         if ((i = recv(sock_fd, tmpbuf, TMP_BUF_SZ, 0)) > 0) {
560                                 tmpbuf[i] = '\0';
561                                 serveConnection(tmpbuf, i);
562                         } else {
563                                 bb_perror_msg_and_die("UNIX socket error");
564                         }
565                 }                               /* FD_ISSET() */
566         }                                       /* for main loop */
567 }
568
569 int syslogd_main(int argc, char **argv)
570 {
571         int opt;
572
573         int doFork = TRUE;
574
575         char *p;
576
577         /* do normal option parsing */
578         while ((opt = getopt(argc, argv, "m:nO:s:Sb:R:LC::")) > 0) {
579                 switch (opt) {
580                 case 'm':
581                         MarkInterval = atoi(optarg) * 60;
582                         break;
583                 case 'n':
584                         doFork = FALSE;
585                         break;
586                 case 'O':
587                         logFilePath = optarg;
588                         break;
589                 case 'l':
590                         logLevel = atoi(optarg);
591                         /* Valid levels are between 1 and 8 */
592                         if (logLevel < 1 || logLevel > 8) {
593                                 bb_show_usage();
594                         }
595                         break;
596 #ifdef CONFIG_FEATURE_ROTATE_LOGFILE
597                 case 's':
598                         logFileSize = atoi(optarg) * 1024;
599                         break;
600                 case 'b':
601                         logFileRotate = atoi(optarg);
602                         if( logFileRotate > 99 ) logFileRotate = 99;
603                         break;
604 #endif
605 #ifdef CONFIG_FEATURE_REMOTE_LOG
606                 case 'R':
607                         RemoteHost = xstrdup(optarg);
608                         if ((p = strchr(RemoteHost, ':'))) {
609                                 RemotePort = atoi(p + 1);
610                                 *p = '\0';
611                         }
612                         opts |= SYSLOG_OPT_remotelog;
613                         break;
614                 case 'L':
615                         opts |= SYSLOG_OPT_locallog;
616                         break;
617 #endif
618 #ifdef CONFIG_FEATURE_IPC_SYSLOG
619                 case 'C':
620                         if (optarg) {
621                                 int buf_size = atoi(optarg);
622                                 if (buf_size >= 4) {
623                                         shm_size = buf_size * 1024;
624                                 }
625                         }
626                         opts |= SYSLOG_OPT_circularlog;
627                         break;
628 #endif
629                 case 'S':
630                         opts |= SYSLOG_OPT_small;
631                         break;
632                 default:
633                         bb_show_usage();
634                 }
635         }
636
637         /* If they have not specified remote logging, then log locally */
638         if (ENABLE_FEATURE_REMOTE_LOG && !(opts & SYSLOG_OPT_remotelog))
639                 opts |= SYSLOG_OPT_locallog;
640
641
642         /* Store away localhost's name before the fork */
643         gethostname(LocalHostName, sizeof(LocalHostName));
644         if ((p = strchr(LocalHostName, '.'))) {
645                 *p = '\0';
646         }
647
648         umask(0);
649
650         if (doFork == TRUE) {
651 #ifdef BB_NOMMU
652                 vfork_daemon_rexec(0, 1, argc, argv, "-n");
653 #else
654                 xdaemon(0, 1);
655 #endif
656         }
657         doSyslogd();
658
659         return EXIT_SUCCESS;
660 }