f3de04653e0c72bbd98eff74c3e11206249c4b05
[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         fd = device_open(logFilePath, O_WRONLY | O_CREAT
281                                         | O_NOCTTY | O_APPEND | O_NONBLOCK);
282         if (fd >= 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)];
295                                         char newFile[(strlen(logFilePath)+4)];
296                                         for(i=logFileRotate-1;i>0;i--) {
297                                                 sprintf(oldFile, "%s.%d", logFilePath, i-1);
298                                                 sprintf(newFile, "%s.%d", logFilePath, i);
299                                                 rename(oldFile, newFile);
300                                         }
301                                         sprintf(newFile, "%s.%d", logFilePath, 0);
302                                         fl.l_type = F_UNLCK;
303                                         fcntl (fd, F_SETLKW, &fl);
304                                         close(fd);
305                                         rename(logFilePath, newFile);
306                                         fd = device_open (logFilePath,
307                                                    O_WRONLY | O_CREAT | O_NOCTTY | O_APPEND |
308                                                    O_NONBLOCK);
309                                         fl.l_type = F_WRLCK;
310                                         fcntl (fd, F_SETLKW, &fl);
311                                 } else {
312                                         ftruncate( fd, 0 );
313                                 }
314                         }
315                 }
316 #endif
317                 va_start(arguments, fmt);
318                 vdprintf(fd, fmt, arguments);
319                 va_end(arguments);
320                 fl.l_type = F_UNLCK;
321                 fcntl(fd, F_SETLKW, &fl);
322                 close(fd);
323         } else {
324                 /* Always send console messages to /dev/console so people will see them. */
325                 fd = device_open(_PATH_CONSOLE, O_WRONLY | O_NOCTTY | O_NONBLOCK);
326                 if (fd >= 0) {
327                         va_start(arguments, fmt);
328                         vdprintf(fd, fmt, arguments);
329                         va_end(arguments);
330                         close(fd);
331                 } else {
332                         fprintf(stderr, "Bummer, can't print: ");
333                         va_start(arguments, fmt);
334                         vfprintf(stderr, fmt, arguments);
335                         fflush(stderr);
336                         va_end(arguments);
337                 }
338         }
339 }
340
341 #ifdef CONFIG_FEATURE_REMOTE_LOG
342 static void init_RemoteLog(void)
343 {
344         memset(&remoteaddr, 0, sizeof(remoteaddr));
345         remotefd = xsocket(AF_INET, SOCK_DGRAM, 0);
346         remoteaddr.sin_family = AF_INET;
347         remoteaddr.sin_addr = *(struct in_addr *) *(xgethostbyname(RemoteHost))->h_addr_list;
348         remoteaddr.sin_port = htons(RemotePort);
349 }
350 #else
351 void init_RemoteLog(void);
352 #endif
353
354 static void logMessage(int pri, char *msg)
355 {
356         time_t now;
357         char *timestamp;
358         char res[20];
359         CODE *c_pri, *c_fac;
360
361         if (pri != 0) {
362                 for (c_fac = facilitynames;
363                          c_fac->c_name && !(c_fac->c_val == LOG_FAC(pri) << 3); c_fac++);
364                 for (c_pri = prioritynames;
365                          c_pri->c_name && !(c_pri->c_val == LOG_PRI(pri)); c_pri++);
366                 if (c_fac->c_name == NULL || c_pri->c_name == NULL) {
367                         snprintf(res, sizeof(res), "<%d>", pri);
368                 } else {
369                         snprintf(res, sizeof(res), "%s.%s", c_fac->c_name, c_pri->c_name);
370                 }
371         }
372
373         if (strlen(msg) < 16 || msg[3] != ' ' || msg[6] != ' ' ||
374                 msg[9] != ':' || msg[12] != ':' || msg[15] != ' ') {
375                 time(&now);
376                 timestamp = ctime(&now) + 4;
377                 timestamp[15] = '\0';
378         } else {
379                 timestamp = msg;
380                 timestamp[15] = '\0';
381                 msg += 16;
382         }
383
384         /* todo: supress duplicates */
385
386 #ifdef CONFIG_FEATURE_REMOTE_LOG
387         if (opts & SYSLOG_OPT_remotelog) {
388                 char line[MAXLINE + 1];
389                 /* trying connect the socket */
390                 if (-1 == remotefd) {
391                         init_RemoteLog();
392                 }
393
394                 /* if we have a valid socket, send the message */
395                 if (-1 != remotefd) {
396                         now = 1;
397                         snprintf(line, sizeof(line), "<%d>%s", pri, msg);
398
399 retry:
400                         /* send message to remote logger */
401                         if(( -1 == sendto(remotefd, line, strlen(line), 0,
402                                                         (struct sockaddr *) &remoteaddr,
403                                                         sizeof(remoteaddr))) && (errno == EINTR)) {
404                                 /* sleep now seconds and retry (with now * 2) */
405                                 sleep(now);
406                                 now *= 2;
407                                 goto retry;
408                         }
409                 }
410         }
411
412         if (opts & SYSLOG_OPT_locallog)
413 #endif
414         {
415                 /* now spew out the message to wherever it is supposed to go */
416                 if (opts & SYSLOG_OPT_small)
417                         message("%s %s\n", timestamp, msg);
418                 else
419                         message("%s %s %s %s\n", timestamp, LocalHostName, res, msg);
420         }
421 }
422
423 static void quit_signal(int sig)
424 {
425         logMessage(LOG_SYSLOG | LOG_INFO, "System log daemon exiting.");
426         unlink(lfile);
427         if (ENABLE_FEATURE_IPC_SYSLOG)
428                 ipcsyslog_cleanup();
429
430         exit(TRUE);
431 }
432
433 static void domark(int sig)
434 {
435         if (MarkInterval > 0) {
436                 logMessage(LOG_SYSLOG | LOG_INFO, "-- MARK --");
437                 alarm(MarkInterval);
438         }
439 }
440
441 /* This must be a #define, since when CONFIG_DEBUG and BUFFERS_GO_IN_BSS are
442  * enabled, we otherwise get a "storage size isn't constant error. */
443 static int serveConnection(char *tmpbuf, int n_read)
444 {
445         char *p = tmpbuf;
446
447         while (p < tmpbuf + n_read) {
448
449                 int pri = (LOG_USER | LOG_NOTICE);
450                 int num_lt = 0;
451                 char line[MAXLINE + 1];
452                 unsigned char c;
453                 char *q = line;
454
455                 while ((c = *p) && q < &line[sizeof(line) - 1]) {
456                         if (c == '<' && num_lt == 0) {
457                                 /* Parse the magic priority number. */
458                                 num_lt++;
459                                 pri = 0;
460                                 while (isdigit(*(++p))) {
461                                         pri = 10 * pri + (*p - '0');
462                                 }
463                                 if (pri & ~(LOG_FACMASK | LOG_PRIMASK)) {
464                                         pri = (LOG_USER | LOG_NOTICE);
465                                 }
466                         } else if (c == '\n') {
467                                 *q++ = ' ';
468                         } else if (iscntrl(c) && (c < 0177)) {
469                                 *q++ = '^';
470                                 *q++ = c ^ 0100;
471                         } else {
472                                 *q++ = c;
473                         }
474                         p++;
475                 }
476                 *q = '\0';
477                 p++;
478                 /* Now log it */
479                 logMessage(pri, line);
480         }
481         return n_read;
482 }
483
484 static void doSyslogd(void) ATTRIBUTE_NORETURN;
485 static void doSyslogd(void)
486 {
487         struct sockaddr_un sunx;
488         socklen_t addrLength;
489
490         int sock_fd;
491         fd_set fds;
492
493         /* Set up signal handlers. */
494         signal(SIGINT, quit_signal);
495         signal(SIGTERM, quit_signal);
496         signal(SIGQUIT, quit_signal);
497         signal(SIGHUP, SIG_IGN);
498         signal(SIGCHLD, SIG_IGN);
499 #ifdef SIGCLD
500         signal(SIGCLD, SIG_IGN);
501 #endif
502         signal(SIGALRM, domark);
503         alarm(MarkInterval);
504
505         /* Create the syslog file so realpath() can work. */
506         if (realpath(_PATH_LOG, lfile) != NULL) {
507                 unlink(lfile);
508         }
509
510         memset(&sunx, 0, sizeof(sunx));
511         sunx.sun_family = AF_UNIX;
512         strncpy(sunx.sun_path, lfile, sizeof(sunx.sun_path));
513         sock_fd = xsocket(AF_UNIX, SOCK_DGRAM, 0);
514         addrLength = sizeof(sunx.sun_family) + strlen(sunx.sun_path);
515         if (bind(sock_fd, (struct sockaddr *) &sunx, addrLength) < 0) {
516                 bb_perror_msg_and_die("Could not connect to socket " _PATH_LOG);
517         }
518
519         if (chmod(lfile, 0666) < 0) {
520                 bb_perror_msg_and_die("Could not set permission on " _PATH_LOG);
521         }
522         if (ENABLE_FEATURE_IPC_SYSLOG && opts & SYSLOG_OPT_circularlog) {
523                 ipcsyslog_init();
524         }
525
526         if (ENABLE_FEATURE_REMOTE_LOG && opts & SYSLOG_OPT_remotelog) {
527                 init_RemoteLog();
528         }
529
530         logMessage(LOG_SYSLOG | LOG_INFO, "syslogd started: " "BusyBox v" BB_VER );
531
532         for (;;) {
533
534                 FD_ZERO(&fds);
535                 FD_SET(sock_fd, &fds);
536
537                 if (select(sock_fd + 1, &fds, NULL, NULL, NULL) < 0) {
538                         if (errno == EINTR) {
539                                 /* alarm may have happened. */
540                                 continue;
541                         }
542                         bb_perror_msg_and_die("select error");
543                 }
544
545                 if (FD_ISSET(sock_fd, &fds)) {
546                         int i;
547 #if MAXLINE > BUFSIZ
548 # define TMP_BUF_SZ BUFSIZ
549 #else
550 # define TMP_BUF_SZ MAXLINE
551 #endif
552 #define tmpbuf bb_common_bufsiz1
553
554                         if ((i = recv(sock_fd, tmpbuf, TMP_BUF_SZ, 0)) > 0) {
555                                 tmpbuf[i] = '\0';
556                                 serveConnection(tmpbuf, i);
557                         } else {
558                                 bb_perror_msg_and_die("UNIX socket error");
559                         }
560                 }                               /* FD_ISSET() */
561         }                                       /* for main loop */
562 }
563
564 int syslogd_main(int argc, char **argv)
565 {
566         int opt;
567
568         int doFork = TRUE;
569
570         char *p;
571
572         /* do normal option parsing */
573         while ((opt = getopt(argc, argv, "m:nO:s:Sb:R:LC::")) > 0) {
574                 switch (opt) {
575                 case 'm':
576                         MarkInterval = atoi(optarg) * 60;
577                         break;
578                 case 'n':
579                         doFork = FALSE;
580                         break;
581                 case 'O':
582                         logFilePath = optarg;
583                         break;
584 #ifdef CONFIG_FEATURE_ROTATE_LOGFILE
585                 case 's':
586                         logFileSize = atoi(optarg) * 1024;
587                         break;
588                 case 'b':
589                         logFileRotate = atoi(optarg);
590                         if( logFileRotate > 99 ) logFileRotate = 99;
591                         break;
592 #endif
593 #ifdef CONFIG_FEATURE_REMOTE_LOG
594                 case 'R':
595                         RemoteHost = xstrdup(optarg);
596                         if ((p = strchr(RemoteHost, ':'))) {
597                                 RemotePort = atoi(p + 1);
598                                 *p = '\0';
599                         }
600                         opts |= SYSLOG_OPT_remotelog;
601                         break;
602                 case 'L':
603                         opts |= SYSLOG_OPT_locallog;
604                         break;
605 #endif
606 #ifdef CONFIG_FEATURE_IPC_SYSLOG
607                 case 'C':
608                         if (optarg) {
609                                 int buf_size = atoi(optarg);
610                                 if (buf_size >= 4) {
611                                         shm_size = buf_size * 1024;
612                                 }
613                         }
614                         opts |= SYSLOG_OPT_circularlog;
615                         break;
616 #endif
617                 case 'S':
618                         opts |= SYSLOG_OPT_small;
619                         break;
620                 default:
621                         bb_show_usage();
622                 }
623         }
624
625         /* If they have not specified remote logging, then log locally */
626         if (ENABLE_FEATURE_REMOTE_LOG && !(opts & SYSLOG_OPT_remotelog))
627                 opts |= SYSLOG_OPT_locallog;
628
629
630         /* Store away localhost's name before the fork */
631         gethostname(LocalHostName, sizeof(LocalHostName));
632         if ((p = strchr(LocalHostName, '.'))) {
633                 *p = '\0';
634         }
635
636         umask(0);
637
638         if (doFork == TRUE) {
639 #ifdef BB_NOMMU
640                 vfork_daemon_rexec(0, 1, argc, argv, "-n");
641 #else
642                 xdaemon(0, 1);
643 #endif
644         }
645         doSyslogd();
646
647         return EXIT_SUCCESS;
648 }