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