syslogd: optional support for /etc/syslog.conf
authorSergey Naumov <sknaumov@gmail.com>
Sun, 10 Apr 2011 05:34:27 +0000 (07:34 +0200)
committerDenys Vlasenko <vda.linux@googlemail.com>
Sun, 10 Apr 2011 05:34:27 +0000 (07:34 +0200)
function                                             old     new   delta
syslogd_main                                        1241    1870    +629
timestamp_and_log                                    301     540    +239
find_by_name                                           -      37     +37
find_by_val                                            -      22     +22
init_data                                             64      68      +4
log_locally                                          603     413    -190
------------------------------------------------------------------------------
(add/remove: 2/0 grow/shrink: 3/1 up/down: 931/-190)          Total: 741 bytes

Signed-off-by: Sergey Naumov <sknaumov@gmail.com>
Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
sysklogd/Config.src
sysklogd/syslogd.c

index d62dc5f5c497d21b932f858530b7bbf2a0f6e554..b3e13d7c06f2d59ba93aedbd7c7333ea2c8588f8 100644 (file)
@@ -52,6 +52,13 @@ config FEATURE_SYSLOGD_DUP
          Option -D instructs syslogd to drop consecutive messages
          which are totally the same.
 
+config FEATURE_SYSLOGD_CFG
+       bool "Support syslog.conf"
+       default y
+       depends on SYSLOGD
+       help
+         Supports restricted syslogd config.
+
 config FEATURE_SYSLOGD_READ_BUFFER_SIZE
        int "Read buffer size in bytes"
        default 256
index 24cab3b6c68c66954d556f9dc9d5c613dc18326a..f179dc5ac1ae1ebfd60aa7c211963fe35738628c 100644 (file)
@@ -66,10 +66,26 @@ typedef struct {
 } remoteHost_t;
 #endif
 
+typedef struct logFile_t {
+       const char *path;
+       int fd;
+#if ENABLE_FEATURE_ROTATE_LOGFILE
+       unsigned size;
+       uint8_t isRegular;
+#endif
+} logFile_t;
+
+#if ENABLE_FEATURE_SYSLOGD_CFG
+typedef struct logRule_t {
+       uint8_t enabled_facility_priomap[LOG_NFACILITIES];
+       struct logFile_t *file;
+       struct logRule_t *next;
+} logRule_t;
+#endif
+
 /* Allows us to have smaller initializer. Ugly. */
 #define GLOBALS \
-       const char *logFilePath;                \
-       int logFD;                              \
+       logFile_t logFile;                      \
        /* interval between marks in seconds */ \
        /*int markInterval;*/                   \
        /* level of messages to be logged */    \
@@ -79,8 +95,6 @@ IF_FEATURE_ROTATE_LOGFILE( \
        unsigned logFileSize;                   \
        /* number of rotated message files */   \
        unsigned logFileRotate;                 \
-       unsigned curFileSize;                   \
-       smallint isRegular;                     \
 ) \
 IF_FEATURE_IPC_SYSLOG( \
        int shmid; /* ipc shared memory id */   \
@@ -88,6 +102,9 @@ IF_FEATURE_IPC_SYSLOG( \
        int shm_size;                           \
        struct sembuf SMwup[1];                 \
        struct sembuf SMwdn[3];                 \
+) \
+IF_FEATURE_SYSLOGD_CFG( \
+       logRule_t *log_rules; \
 )
 
 struct init_globals {
@@ -119,8 +136,10 @@ struct globals {
 };
 
 static const struct init_globals init_data = {
-       .logFilePath = "/var/log/messages",
-       .logFD = -1,
+       .logFile = {
+               .path = "/var/log/messages",
+               .fd = -1,
+       },
 #ifdef SYSLOGD_MARK
        .markInterval = 20 * 60,
 #endif
@@ -132,7 +151,7 @@ static const struct init_globals init_data = {
 #if ENABLE_FEATURE_IPC_SYSLOG
        .shmid = -1,
        .s_semid = -1,
-       .shm_size = ((CONFIG_FEATURE_IPC_SYSLOG_BUFFER_SIZE)*1024), // default shm size
+       .shm_size = ((CONFIG_FEATURE_IPC_SYSLOG_BUFFER_SIZE)*1024), /* default shm size */
        .SMwup = { {1, -1, IPC_NOWAIT} },
        .SMwdn = { {0, 0}, {1, 0}, {1, +1} },
 #endif
@@ -157,6 +176,7 @@ enum {
        IF_FEATURE_REMOTE_LOG(    OPTBIT_locallog   ,)  // -L
        IF_FEATURE_IPC_SYSLOG(    OPTBIT_circularlog,)  // -C
        IF_FEATURE_SYSLOGD_DUP(   OPTBIT_dup        ,)  // -D
+       IF_FEATURE_SYSLOGD_CFG(   OPTBIT_cfg        ,)  // -f
 
        OPT_mark        = 1 << OPTBIT_mark    ,
        OPT_nofork      = 1 << OPTBIT_nofork  ,
@@ -169,6 +189,7 @@ enum {
        OPT_locallog    = IF_FEATURE_REMOTE_LOG(    (1 << OPTBIT_locallog   )) + 0,
        OPT_circularlog = IF_FEATURE_IPC_SYSLOG(    (1 << OPTBIT_circularlog)) + 0,
        OPT_dup         = IF_FEATURE_SYSLOGD_DUP(   (1 << OPTBIT_dup        )) + 0,
+       OPT_cfg         = IF_FEATURE_SYSLOGD_CFG(   (1 << OPTBIT_cfg        )) + 0,
 };
 #define OPTION_STR "m:nO:l:S" \
        IF_FEATURE_ROTATE_LOGFILE("s:" ) \
@@ -176,18 +197,194 @@ enum {
        IF_FEATURE_REMOTE_LOG(    "R:" ) \
        IF_FEATURE_REMOTE_LOG(    "L"  ) \
        IF_FEATURE_IPC_SYSLOG(    "C::") \
-       IF_FEATURE_SYSLOGD_DUP(   "D"  )
+       IF_FEATURE_SYSLOGD_DUP(   "D"  ) \
+       IF_FEATURE_SYSLOGD_CFG(   "f:"  )
 #define OPTION_DECL *opt_m, *opt_l \
        IF_FEATURE_ROTATE_LOGFILE(,*opt_s) \
        IF_FEATURE_ROTATE_LOGFILE(,*opt_b) \
-       IF_FEATURE_IPC_SYSLOG(    ,*opt_C = NULL)
-#define OPTION_PARAM &opt_m, &G.logFilePath, &opt_l \
+       IF_FEATURE_IPC_SYSLOG(    ,*opt_C = NULL) \
+       IF_FEATURE_SYSLOGD_CFG(   ,*opt_f = NULL)
+#define OPTION_PARAM &opt_m, &(G.logFile.path), &opt_l \
        IF_FEATURE_ROTATE_LOGFILE(,&opt_s) \
        IF_FEATURE_ROTATE_LOGFILE(,&opt_b) \
        IF_FEATURE_REMOTE_LOG(    ,&remoteAddrList) \
-       IF_FEATURE_IPC_SYSLOG(    ,&opt_C)
+       IF_FEATURE_IPC_SYSLOG(    ,&opt_C) \
+       IF_FEATURE_SYSLOGD_CFG(   ,&opt_f)
 
 
+#if ENABLE_FEATURE_SYSLOGD_CFG
+static const CODE* find_by_name(char *name, const CODE* c_set)
+{
+       for (; c_set->c_name; c_set++) {
+               if (strcmp(name, c_set->c_name) == 0)
+                       return c_set;
+       }
+       return NULL;
+}
+#endif
+static const CODE* find_by_val(int val, const CODE* c_set)
+{
+       for (; c_set->c_name; c_set++) {
+               if (c_set->c_val == val)
+                       return c_set;
+       }
+       return NULL;
+}
+
+#if ENABLE_FEATURE_SYSLOGD_CFG
+static void parse_syslogdcfg(const char *file)
+{
+       char *t;
+       logRule_t **pp_rule;
+       /* tok[0] set of selectors */
+       /* tok[1] file name */
+       /* tok[2] has to be NULL */
+       char *tok[3];
+       parser_t *parser;
+
+       parser = config_open2(file ? file : "/etc/syslog.conf",
+                               file ? xfopen_for_read : fopen_or_warn_stdin);
+       if (!parser)
+               /* didn't find default /etc/syslog.conf */
+               /* proceed as if we built busybox without config support */
+               return;
+
+       /* use ptr to ptr to avoid checking whether head was initialized */
+       pp_rule = &G.log_rules;
+       /* iterate through lines of config, skipping comments */
+       while (config_read(parser, tok, 3, 2, "# \t", PARSE_NORMAL | PARSE_MIN_DIE)) {
+               char *cur_selector;
+               logRule_t *cur_rule;
+
+               /* unexpected trailing token? */
+               if (tok[2]) {
+                       t = tok[2];
+                       goto cfgerr;
+               }
+
+               cur_rule = *pp_rule = xzalloc(sizeof(*cur_rule));
+
+               cur_selector = tok[0];
+               /* iterate through selectors: "kern.info;kern.!err;..." */
+               do {
+                       const CODE *code;
+                       char *next_selector;
+                       uint8_t negated_prio; /* "kern.!err" */
+                       uint8_t single_prio;  /* "kern.=err" */
+                       uint32_t facmap; /* bitmap of enabled facilities */
+                       uint8_t primap;  /* bitmap of enabled priorities */
+                       unsigned i;
+
+                       next_selector = strchr(cur_selector, ';');
+                       if (next_selector)
+                               *next_selector++ = '\0';
+
+                       t = strchr(cur_selector, '.');
+                       if (!t) {
+                               t = cur_selector;
+                               goto cfgerr;
+                       }
+                       *t++ = '\0'; /* separate facility from priority */
+
+                       negated_prio = 0;
+                       single_prio = 0;
+                       if (*t == '!') {
+                               negated_prio = 1;
+                               ++t;
+                       }
+                       if (*t == '=') {
+                               single_prio = 1;
+                               ++t;
+                       }
+
+                       /* parse priority */
+                       if (*t == '*')
+                               primap = 0xff; /* all 8 log levels enabled */
+                       else {
+                               uint8_t priority;
+                               code = find_by_name(t, prioritynames);
+                               if (!code)
+                                       goto cfgerr;
+                               primap = 0;
+                               priority = code->c_val;
+                               if (priority == INTERNAL_NOPRI) {
+                                       /* ensure we take "enabled_facility_priomap[fac] &= 0" branch below */
+                                       negated_prio = 1;
+                               } else {
+                                       priority = 1 << priority;
+                                       do {
+                                               primap |= priority;
+                                               if (single_prio)
+                                                       break;
+                                               priority >>= 1;
+                                       } while (priority);
+                                       if (negated_prio)
+                                               primap = ~primap;
+                               }
+                       }
+
+                       /* parse facility */
+                       if (*cur_selector == '*')
+                               facmap = (1<<LOG_NFACILITIES) - 1;
+                       else {
+                               char *next_facility;
+                               facmap = 0;
+                               t = cur_selector;
+                               /* iterate through facilities: "kern,daemon.<priospec>" */
+                               do {
+                                       next_facility = strchr(t, ',');
+                                       if (next_facility)
+                                               *next_facility++ = '\0';
+                                       code = find_by_name(t, facilitynames);
+                                       if (!code)
+                                               goto cfgerr;
+                                       /* "mark" is not a real facility, skip it */
+                                       if (code->c_val != INTERNAL_MARK)
+                                               facmap |= 1<<(LOG_FAC(code->c_val));
+                                       t = next_facility;
+                               } while (t);
+                       }
+
+                       /* merge result with previous selectors */
+                       for (i = 0; i < LOG_NFACILITIES; ++i) {
+                               if (!(facmap & (1<<i)))
+                                       continue;
+                               if (negated_prio)
+                                       cur_rule->enabled_facility_priomap[i] &= primap;
+                               else
+                                       cur_rule->enabled_facility_priomap[i] |= primap;
+                       }
+
+                       cur_selector = next_selector;
+               } while (cur_selector);
+
+               /* check whether current file name was mentioned in previous rules.
+                * temporarily use cur_rule as iterator, but *pp_rule still points to
+                * currently processing rule entry.
+                * NOTE: *pp_rule points to the current (and last in the list) rule.
+                */
+               for (cur_rule = G.log_rules; cur_rule != *pp_rule; cur_rule = cur_rule->next) {
+                       if (strcmp(cur_rule->file->path, tok[1]) == 0) {
+                               /* found - reuse the same file structure */
+                               (*pp_rule)->file = cur_rule->file;
+                               cur_rule = *pp_rule;
+                               goto found;
+                       }
+               }
+               cur_rule->file = xzalloc(sizeof(*cur_rule->file));
+               cur_rule->file->fd = -1;
+               cur_rule->file->path = xstrdup(tok[1]);
+ found:
+               pp_rule = &cur_rule->next;
+       }
+       config_close(parser);
+       return;
+
+ cfgerr:
+       bb_error_msg_and_die("bad line %d: wrong token '%s'", parser->lineno, t);
+}
+#endif
+
 /* circular buffer variables/structures */
 #if ENABLE_FEATURE_IPC_SYSLOG
 
@@ -231,7 +428,7 @@ static void ipcsyslog_init(void)
        G.shbuf->size = G.shm_size - offsetof(struct shbuf_ds, data) - 1;
        /*G.shbuf->tail = 0;*/
 
-       // we'll trust the OS to set initial semval to 0 (let's hope)
+       /* we'll trust the OS to set initial semval to 0 (let's hope) */
        G.s_semid = semget(KEY_ID, 2, IPC_CREAT | IPC_EXCL | 1023);
        if (G.s_semid == -1) {
                if (errno == EEXIST) {
@@ -244,9 +441,10 @@ static void ipcsyslog_init(void)
 }
 
 /* Write message to shared mem buffer */
-static void log_to_shmem(const char *msg, int len)
+static void log_to_shmem(const char *msg)
 {
        int old_tail, new_tail;
+       int len;
 
        if (semop(G.s_semid, G.SMwdn, 3) == -1) {
                bb_perror_msg_and_die("SMwdn");
@@ -258,7 +456,7 @@ static void log_to_shmem(const char *msg, int len)
         * tail's max value is (shbuf->size - 1)
         * Last byte of buffer is never used and remains NUL.
         */
-       len++; /* length with NUL included */
+       len = strlen(msg) + 1; /* length with NUL included */
  again:
        old_tail = G.shbuf->tail;
        new_tail = old_tail + len;
@@ -288,22 +486,15 @@ void ipcsyslog_init(void);
 void log_to_shmem(const char *msg);
 #endif /* FEATURE_IPC_SYSLOG */
 
-
 /* Print a message to the log file. */
-static void log_locally(time_t now, char *msg)
+static void log_locally(time_t now, char *msg, logFile_t *log_file)
 {
 #ifdef SYSLOGD_WRLOCK
        struct flock fl;
 #endif
        int len = strlen(msg);
 
-#if ENABLE_FEATURE_IPC_SYSLOG
-       if ((option_mask32 & OPT_circularlog) && G.shbuf) {
-               log_to_shmem(msg, len);
-               return;
-       }
-#endif
-       if (G.logFD >= 0) {
+       if (log_file->fd >= 0) {
                /* Reopen log file every second. This allows admin
                 * to delete the file and not worry about restarting us.
                 * This costs almost nothing since it happens
@@ -313,15 +504,15 @@ static void log_locally(time_t now, char *msg)
                        now = time(NULL);
                if (G.last_log_time != now) {
                        G.last_log_time = now;
-                       close(G.logFD);
+                       close(log_file->fd);
                        goto reopen;
                }
        } else {
  reopen:
-               G.logFD = open(G.logFilePath, O_WRONLY | O_CREAT
+               log_file->fd = open(log_file->path, O_WRONLY | O_CREAT
                                        | O_NOCTTY | O_APPEND | O_NONBLOCK,
                                        0666);
-               if (G.logFD < 0) {
+               if (log_file->fd < 0) {
                        /* cannot open logfile? - print to /dev/console then */
                        int fd = device_open(DEV_CONSOLE, O_WRONLY | O_NOCTTY | O_NONBLOCK);
                        if (fd < 0)
@@ -334,9 +525,9 @@ static void log_locally(time_t now, char *msg)
 #if ENABLE_FEATURE_ROTATE_LOGFILE
                {
                        struct stat statf;
-                       G.isRegular = (fstat(G.logFD, &statf) == 0 && S_ISREG(statf.st_mode));
+                       log_file->isRegular = (fstat(log_file->fd, &statf) == 0 && S_ISREG(statf.st_mode));
                        /* bug (mostly harmless): can wrap around if file > 4gb */
-                       G.curFileSize = statf.st_size;
+                       log_file->size = statf.st_size;
                }
 #endif
        }
@@ -346,41 +537,41 @@ static void log_locally(time_t now, char *msg)
        fl.l_start = 0;
        fl.l_len = 1;
        fl.l_type = F_WRLCK;
-       fcntl(G.logFD, F_SETLKW, &fl);
+       fcntl(log_file->fd, F_SETLKW, &fl);
 #endif
 
 #if ENABLE_FEATURE_ROTATE_LOGFILE
-       if (G.logFileSize && G.isRegular && G.curFileSize > G.logFileSize) {
+       if (G.logFileSize && log_file->isRegular && log_file->size > G.logFileSize) {
                if (G.logFileRotate) { /* always 0..99 */
-                       int i = strlen(G.logFilePath) + 3 + 1;
+                       int i = strlen(log_file->path) + 3 + 1;
                        char oldFile[i];
                        char newFile[i];
                        i = G.logFileRotate - 1;
                        /* rename: f.8 -> f.9; f.7 -> f.8; ... */
                        while (1) {
-                               sprintf(newFile, "%s.%d", G.logFilePath, i);
+                               sprintf(newFile, "%s.%d", log_file->path, i);
                                if (i == 0) break;
-                               sprintf(oldFile, "%s.%d", G.logFilePath, --i);
+                               sprintf(oldFile, "%s.%d", log_file->path, --i);
                                /* ignore errors - file might be missing */
                                rename(oldFile, newFile);
                        }
                        /* newFile == "f.0" now */
-                       rename(G.logFilePath, newFile);
+                       rename(log_file->path, newFile);
 #ifdef SYSLOGD_WRLOCK
                        fl.l_type = F_UNLCK;
-                       fcntl(G.logFD, F_SETLKW, &fl);
+                       fcntl(log_file->fd, F_SETLKW, &fl);
 #endif
-                       close(G.logFD);
+                       close(log_file->fd);
                        goto reopen;
                }
-               ftruncate(G.logFD, 0);
+               ftruncate(log_file->fd, 0);
        }
-       G.curFileSize +=
+       log_file->size +=
 #endif
-                       full_write(G.logFD, msg, len);
+                       full_write(log_file->fd, msg, len);
 #ifdef SYSLOGD_WRLOCK
        fl.l_type = F_UNLCK;
-       fcntl(G.logFD, F_SETLKW, &fl);
+       fcntl(log_file->fd, F_SETLKW, &fl);
 #endif
 }
 
@@ -388,29 +579,15 @@ static void parse_fac_prio_20(int pri, char *res20)
 {
        const CODE *c_pri, *c_fac;
 
-       if (pri != 0) {
-               c_fac = facilitynames;
-               while (c_fac->c_name) {
-                       if (c_fac->c_val != (LOG_FAC(pri) << 3)) {
-                               c_fac++;
-                               continue;
-                       }
-                       /* facility is found, look for prio */
-                       c_pri = prioritynames;
-                       while (c_pri->c_name) {
-                               if (c_pri->c_val != LOG_PRI(pri)) {
-                                       c_pri++;
-                                       continue;
-                               }
-                               snprintf(res20, 20, "%s.%s",
-                                               c_fac->c_name, c_pri->c_name);
-                               return;
-                       }
-                       /* prio not found, bail out */
-                       break;
+       c_fac = find_by_val(LOG_FAC(pri) << 3, facilitynames);
+       if (c_fac) {
+               c_pri = find_by_val(LOG_PRI(pri), prioritynames);
+               if (c_pri) {
+                       snprintf(res20, 20, "%s.%s", c_fac->c_name, c_pri->c_name);
+                       return;
                }
-               snprintf(res20, 20, "<%d>", pri);
        }
+       snprintf(res20, 20, "<%d>", pri);
 }
 
 /* len parameter is used only for "is there a timestamp?" check.
@@ -444,7 +621,32 @@ static void timestamp_and_log(int pri, char *msg, int len)
        }
 
        /* Log message locally (to file or shared mem) */
-       log_locally(now, G.printbuf);
+#if ENABLE_FEATURE_SYSLOGD_CFG
+       {
+               bool match = 0;
+               logRule_t *rule;
+               uint8_t facility = LOG_FAC(pri);
+               uint8_t prio_bit = 1 << LOG_PRI(pri);
+
+               for (rule = G.log_rules; rule; rule = rule->next) {
+                       if (rule->enabled_facility_priomap[facility] & prio_bit) {
+                               log_locally(now, G.printbuf, rule->file);
+                               match = 1;
+                       }
+               }
+               if (match)
+                       return;
+       }
+#endif
+       if (LOG_PRI(pri) < G.logLevel) {
+#if ENABLE_FEATURE_IPC_SYSLOG
+               if ((option_mask32 & OPT_circularlog) && G.shbuf) {
+                       log_to_shmem(msg);
+                       return;
+               }
+#endif
+               log_locally(now, G.printbuf, &G.logFile);
+       }
 }
 
 static void timestamp_and_log_internal(const char *msg)
@@ -489,8 +691,7 @@ static void split_escape_and_log(char *tmpbuf, int len)
                *q = '\0';
 
                /* Now log it */
-               if (LOG_PRI(pri) < G.logLevel)
-                       timestamp_and_log(pri, G.parsebuf, q - G.parsebuf);
+               timestamp_and_log(pri, G.parsebuf, q - G.parsebuf);
        }
 }
 
@@ -719,10 +920,12 @@ int syslogd_main(int argc UNUSED_PARAM, char **argv)
        if (opt_C) // -Cn
                G.shm_size = xatoul_range(opt_C, 4, INT_MAX/1024) * 1024;
 #endif
-
        /* If they have not specified remote logging, then log locally */
        if (ENABLE_FEATURE_REMOTE_LOG && !(opts & OPT_remotelog)) // -R
                option_mask32 |= OPT_locallog;
+#if ENABLE_FEATURE_SYSLOGD_CFG
+       parse_syslogdcfg(opt_f);
+#endif
 
        /* Store away localhost's name before the fork */
        G.hostname = safe_gethostname();