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