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