Replace all uses of strerror() by ovs_strerror(), for thread safety.
[sliver-openvswitch.git] / lib / vlog.c
index d4d277a..f87f48f 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008, 2009, 2010, 2011, 2012 Nicira, Inc.
+ * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013 Nicira, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -19,6 +19,7 @@
 #include <assert.h>
 #include <ctype.h>
 #include <errno.h>
+#include <fcntl.h>
 #include <stdarg.h>
 #include <stdlib.h>
 #include <string.h>
 #include <syslog.h>
 #include <time.h>
 #include <unistd.h>
+#include "coverage.h"
 #include "dirs.h"
 #include "dynamic-string.h"
+#include "ofpbuf.h"
 #include "sat-math.h"
 #include "svec.h"
 #include "timeval.h"
 #include "unixctl.h"
 #include "util.h"
+#include "worker.h"
 
 VLOG_DEFINE_THIS_MODULE(vlog);
 
+COVERAGE_DEFINE(vlog_recursive);
+
+/* ovs_assert() logs the assertion message, so using ovs_assert() in this
+ * source file could cause recursion. */
+#undef ovs_assert
+#define ovs_assert use_assert_instead_of_ovs_assert_in_this_module
+
 /* Name for each logging level. */
-static const char *level_names[VLL_N_LEVELS] = {
+static const char *const level_names[VLL_N_LEVELS] = {
 #define VLOG_LEVEL(NAME, SYSLOG_LEVEL) #NAME,
     VLOG_LEVELS
 #undef VLOG_LEVEL
 };
 
 /* Syslog value for each logging level. */
-static int syslog_levels[VLL_N_LEVELS] = {
+static const int syslog_levels[VLL_N_LEVELS] = {
 #define VLOG_LEVEL(NAME, SYSLOG_LEVEL) SYSLOG_LEVEL,
     VLOG_LEVELS
 #undef VLOG_LEVEL
@@ -84,7 +95,7 @@ static struct facility facilities[VLF_N_FACILITIES] = {
 
 /* VLF_FILE configuration. */
 static char *log_file_name;
-static FILE *log_file;
+static int log_fd = -1;
 
 /* vlog initialized? */
 static bool vlog_inited;
@@ -93,11 +104,13 @@ static void format_log_message(const struct vlog_module *, enum vlog_level,
                                enum vlog_facility, unsigned int msg_num,
                                const char *message, va_list, struct ds *)
     PRINTF_FORMAT(5, 0);
+static void vlog_write_file(struct ds *);
+static void vlog_update_async_log_fd(void);
 
 /* Searches the 'n_names' in 'names'.  Returns the index of a match for
  * 'target', or 'n_names' if no name matches. */
 static size_t
-search_name_array(const char *target, const char **names, size_t n_names)
+search_name_array(const char *target, const char *const *names, size_t n_names)
 {
     size_t i;
 
@@ -186,7 +199,7 @@ update_min_level(struct vlog_module *module)
 
     module->min_level = VLL_OFF;
     for (facility = 0; facility < VLF_N_FACILITIES; facility++) {
-        if (log_file || facility != VLF_FILE) {
+        if (log_fd >= 0 || facility != VLF_FILE) {
             enum vlog_level level = module->levels[facility];
             if (level > module->min_level) {
                 module->min_level = level;
@@ -279,10 +292,10 @@ vlog_set_log_file(const char *file_name)
     int error;
 
     /* Close old log file. */
-    if (log_file) {
+    if (log_fd >= 0) {
         VLOG_INFO("closing log file");
-        fclose(log_file);
-        log_file = NULL;
+        close(log_fd);
+        log_fd = -1;
     }
 
     /* Update log file name and free old name.  The ordering is important
@@ -296,15 +309,18 @@ vlog_set_log_file(const char *file_name)
 
     /* Open new log file and update min_levels[] to reflect whether we actually
      * have a log_file. */
-    log_file = fopen(log_file_name, "a");
+    log_fd = open(log_file_name, O_WRONLY | O_CREAT | O_APPEND, 0666);
+    if (log_fd >= 0) {
+        vlog_update_async_log_fd();
+    }
     for (mp = vlog_modules; mp < &vlog_modules[n_vlog_modules]; mp++) {
         update_min_level(*mp);
     }
 
     /* Log success or failure. */
-    if (!log_file) {
+    if (log_fd < 0) {
         VLOG_WARN("failed to open %s for logging: %s",
-                  log_file_name, strerror(errno));
+                  log_file_name, ovs_strerror(errno));
         error = errno;
     } else {
         VLOG_INFO("opened log file %s", log_file_name);
@@ -330,8 +346,8 @@ vlog_reopen_log_file(void)
     /* Skip re-opening if it would be a no-op because the old and new files are
      * the same.  (This avoids writing "closing log file" followed immediately
      * by "opened log file".) */
-    if (log_file
-        && !fstat(fileno(log_file), &old)
+    if (log_fd >= 0
+        && !fstat(log_fd, &old)
         && !stat(log_file_name, &new)
         && old.st_dev == new.st_dev
         && old.st_ino == new.st_ino) {
@@ -415,6 +431,16 @@ exit:
     return msg;
 }
 
+/* Set debugging levels.  Abort with an error message if 's' is invalid. */
+void
+vlog_set_levels_from_string_assert(const char *s)
+{
+    char *error = vlog_set_levels_from_string(s);
+    if (error) {
+        ovs_fatal(0, "%s", error);
+    }
+}
+
 /* If 'arg' is null, configure maximum verbosity.  Otherwise, sets
  * configuration according to 'arg' (see vlog_set_levels_from_string()). */
 void
@@ -463,7 +489,7 @@ vlog_unixctl_reopen(struct unixctl_conn *conn, int argc OVS_UNUSED,
     if (log_file_name) {
         int error = vlog_reopen_log_file();
         if (error) {
-            unixctl_command_reply_error(conn, strerror(errno));
+            unixctl_command_reply_error(conn, ovs_strerror(errno));
         } else {
             unixctl_command_reply(conn, NULL);
         }
@@ -472,6 +498,55 @@ vlog_unixctl_reopen(struct unixctl_conn *conn, int argc OVS_UNUSED,
     }
 }
 
+static void
+set_all_rate_limits(bool enable)
+{
+    struct vlog_module **mp;
+
+    for (mp = vlog_modules; mp < &vlog_modules[n_vlog_modules]; mp++) {
+        (*mp)->honor_rate_limits = enable;
+    }
+}
+
+static void
+set_rate_limits(struct unixctl_conn *conn, int argc,
+                const char *argv[], bool enable)
+{
+    if (argc > 1) {
+        int i;
+
+        for (i = 1; i < argc; i++) {
+            if (!strcasecmp(argv[i], "ANY")) {
+                set_all_rate_limits(enable);
+            } else {
+                struct vlog_module *module = vlog_module_from_name(argv[i]);
+                if (!module) {
+                    unixctl_command_reply_error(conn, "unknown module");
+                    return;
+                }
+                module->honor_rate_limits = enable;
+            }
+        }
+    } else {
+        set_all_rate_limits(enable);
+    }
+    unixctl_command_reply(conn, NULL);
+}
+
+static void
+vlog_enable_rate_limit(struct unixctl_conn *conn, int argc,
+                       const char *argv[], void *aux OVS_UNUSED)
+{
+    set_rate_limits(conn, argc, argv, true);
+}
+
+static void
+vlog_disable_rate_limit(struct unixctl_conn *conn, int argc,
+                       const char *argv[], void *aux OVS_UNUSED)
+{
+    set_rate_limits(conn, argc, argv, false);
+}
+
 /* Initializes the logging subsystem and registers its unixctl server
  * commands. */
 void
@@ -495,18 +570,19 @@ vlog_init(void)
 
     now = time_wall();
     if (now < 0) {
-        struct tm tm;
-        char s[128];
-
-        gmtime_r(&now, &tm);
-        strftime(s, sizeof s, "%a, %d %b %Y %H:%M:%S", &tm);
+        char *s = xastrftime("%a, %d %b %Y %H:%M:%S", now, true);
         VLOG_ERR("current time is negative: %s (%ld)", s, (long int) now);
+        free(s);
     }
 
     unixctl_command_register(
         "vlog/set", "{spec | PATTERN:facility:pattern}",
         1, INT_MAX, vlog_unixctl_set, NULL);
     unixctl_command_register("vlog/list", "", 0, 0, vlog_unixctl_list, NULL);
+    unixctl_command_register("vlog/enable-rate-limit", "[module]...",
+                             0, INT_MAX, vlog_enable_rate_limit, NULL);
+    unixctl_command_register("vlog/disable-rate-limit", "[module]...",
+                             0, INT_MAX, vlog_disable_rate_limit, NULL);
     unixctl_command_register("vlog/reopen", "", 0, 0,
                              vlog_unixctl_reopen, NULL);
 }
@@ -535,12 +611,20 @@ vlog_get_levels(void)
     ds_put_format(&s, "                 -------    ------    ------\n");
 
     for (mp = vlog_modules; mp < &vlog_modules[n_vlog_modules]; mp++) {
-        line = xasprintf("%-16s  %4s       %4s       %4s\n",
-           vlog_get_module_name(*mp),
-           vlog_get_level_name(vlog_get_level(*mp, VLF_CONSOLE)),
-           vlog_get_level_name(vlog_get_level(*mp, VLF_SYSLOG)),
-           vlog_get_level_name(vlog_get_level(*mp, VLF_FILE)));
-        svec_add_nocopy(&lines, line);
+        struct ds line;
+
+        ds_init(&line);
+        ds_put_format(&line, "%-16s  %4s       %4s       %4s",
+                      vlog_get_module_name(*mp),
+                      vlog_get_level_name(vlog_get_level(*mp, VLF_CONSOLE)),
+                      vlog_get_level_name(vlog_get_level(*mp, VLF_SYSLOG)),
+                      vlog_get_level_name(vlog_get_level(*mp, VLF_FILE)));
+        if (!(*mp)->honor_rate_limits) {
+            ds_put_cstr(&line, "    (rate limiting disabled)");
+        }
+        ds_put_char(&line, '\n');
+
+        svec_add_nocopy(&lines, ds_steal_cstr(&line));
     }
 
     svec_sort(&lines);
@@ -622,11 +706,11 @@ format_log_message(const struct vlog_module *module, enum vlog_level level,
             break;
         case 'd':
             p = fetch_braces(p, "%Y-%m-%d %H:%M:%S", tmp, sizeof tmp);
-            ds_put_strftime(s, tmp, false);
+            ds_put_strftime(s, tmp, time_wall(), false);
             break;
         case 'D':
             p = fetch_braces(p, "%Y-%m-%d %H:%M:%S", tmp, sizeof tmp);
-            ds_put_strftime(s, tmp, true);
+            ds_put_strftime(s, tmp, time_wall(), true);
             break;
         case 'm':
             /* Format user-supplied log message and trim trailing new-lines. */
@@ -689,7 +773,7 @@ vlog_valist(const struct vlog_module *module, enum vlog_level level,
 {
     bool log_to_console = module->levels[VLF_CONSOLE] >= level;
     bool log_to_syslog = module->levels[VLF_SYSLOG] >= level;
-    bool log_to_file = module->levels[VLF_FILE] >= level && log_file;
+    bool log_to_file = module->levels[VLF_FILE] >= level && log_fd >= 0;
     if (log_to_console || log_to_syslog || log_to_file) {
         int save_errno = errno;
         static unsigned int msg_num;
@@ -725,8 +809,7 @@ vlog_valist(const struct vlog_module *module, enum vlog_level level,
             format_log_message(module, level, VLF_FILE, msg_num,
                                message, args, &s);
             ds_put_char(&s, '\n');
-            fputs(ds_cstr(&s), log_file);
-            fflush(log_file);
+            vlog_write_file(&s);
         }
 
         ds_destroy(&s);
@@ -755,7 +838,7 @@ void
 vlog_fatal_valist(const struct vlog_module *module_,
                   const char *message, va_list args)
 {
-    struct vlog_module *module = (struct vlog_module *) module_;
+    struct vlog_module *module = CONST_CAST(struct vlog_module *, module_);
 
     /* Don't log this message to the console to avoid redundancy with the
      * message written by the later ovs_fatal_valist(). */
@@ -819,6 +902,10 @@ bool
 vlog_should_drop(const struct vlog_module *module, enum vlog_level level,
                  struct vlog_rate_limit *rl)
 {
+    if (!module->honor_rate_limits) {
+        return false;
+    }
+
     if (!vlog_is_enabled(module, level)) {
         return true;
     }
@@ -871,3 +958,59 @@ vlog_usage(void)
            "                          (default: %s/%s.log)\n",
            ovs_logdir(), program_name);
 }
+\f
+static bool vlog_async_inited = false;
+
+static worker_request_func vlog_async_write_request_cb;
+
+static void
+vlog_write_file(struct ds *s)
+{
+    if (worker_is_running()) {
+        static bool in_worker_request = false;
+        if (!in_worker_request) {
+            in_worker_request = true;
+
+            worker_request(s->string, s->length,
+                           &log_fd, vlog_async_inited ? 0 : 1,
+                           vlog_async_write_request_cb, NULL, NULL);
+            vlog_async_inited = true;
+
+            in_worker_request = false;
+            return;
+        } else {
+            /* We've been entered recursively.  This can happen if
+             * worker_request(), or a function that it calls, tries to log
+             * something.  We can't call worker_request() recursively, so fall
+             * back to writing the log file directly. */
+            COVERAGE_INC(vlog_recursive);
+        }
+    }
+    ignore(write(log_fd, s->string, s->length));
+}
+
+static void
+vlog_update_async_log_fd(void)
+{
+    if (worker_is_running()) {
+        worker_request(NULL, 0, &log_fd, 1, vlog_async_write_request_cb,
+                       NULL, NULL);
+        vlog_async_inited = true;
+    }
+}
+
+static void
+vlog_async_write_request_cb(struct ofpbuf *request,
+                            const int *fd, size_t n_fds)
+{
+    if (n_fds > 0) {
+        if (log_fd >= 0) {
+            close(log_fd);
+        }
+        log_fd = *fd;
+    }
+
+    if (request->size > 0) {
+        ignore(write(log_fd, request->data, request->size));
+    }
+}