x86: Do not free zero sized per cpu areas
[safe/jmp/linux-2.6] / kernel / printk.c
index b5ac4d9..75077ad 100644 (file)
@@ -34,6 +34,8 @@
 #include <linux/syscalls.h>
 #include <linux/kexec.h>
 #include <linux/ratelimit.h>
+#include <linux/kmsg_dump.h>
+#include <linux/syslog.h>
 
 #include <asm/uaccess.h>
 
@@ -68,8 +70,6 @@ int console_printk[4] = {
        DEFAULT_CONSOLE_LOGLEVEL,       /* default_console_loglevel */
 };
 
-static int saved_console_loglevel = -1;
-
 /*
  * Low level drivers may need that to know if they can schedule in
  * their unblank() callback or not. So let's export it.
@@ -144,6 +144,7 @@ static char __log_buf[__LOG_BUF_LEN];
 static char *log_buf = __log_buf;
 static int log_buf_len = __LOG_BUF_LEN;
 static unsigned logged_chars; /* Number of chars produced since last read+clear operation */
+static int saved_console_loglevel = -1;
 
 #ifdef CONFIG_KEXEC
 /*
@@ -257,38 +258,23 @@ static inline void boot_delay_msec(void)
 }
 #endif
 
-/*
- * Commands to do_syslog:
- *
- *     0 -- Close the log.  Currently a NOP.
- *     1 -- Open the log. Currently a NOP.
- *     2 -- Read from the log.
- *     3 -- Read all messages remaining in the ring buffer.
- *     4 -- Read and clear all messages remaining in the ring buffer
- *     5 -- Clear ring buffer.
- *     6 -- Disable printk's to console
- *     7 -- Enable printk's to console
- *     8 -- Set level of messages printed to console
- *     9 -- Return number of unread characters in the log buffer
- *     10 -- Return size of the log buffer
- */
-int do_syslog(int type, char __user *buf, int len)
+int do_syslog(int type, char __user *buf, int len, bool from_file)
 {
        unsigned i, j, limit, count;
        int do_clear = 0;
        char c;
        int error = 0;
 
-       error = security_syslog(type);
+       error = security_syslog(type, from_file);
        if (error)
                return error;
 
        switch (type) {
-       case 0:         /* Close log */
+       case SYSLOG_ACTION_CLOSE:       /* Close log */
                break;
-       case 1:         /* Open log */
+       case SYSLOG_ACTION_OPEN:        /* Open log */
                break;
-       case 2:         /* Read from log */
+       case SYSLOG_ACTION_READ:        /* Read from log */
                error = -EINVAL;
                if (!buf || len < 0)
                        goto out;
@@ -319,10 +305,12 @@ int do_syslog(int type, char __user *buf, int len)
                if (!error)
                        error = i;
                break;
-       case 4:         /* Read/clear last kernel messages */
+       /* Read/clear last kernel messages */
+       case SYSLOG_ACTION_READ_CLEAR:
                do_clear = 1;
                /* FALL THRU */
-       case 3:         /* Read last kernel messages */
+       /* Read last kernel messages */
+       case SYSLOG_ACTION_READ_ALL:
                error = -EINVAL;
                if (!buf || len < 0)
                        goto out;
@@ -375,21 +363,25 @@ int do_syslog(int type, char __user *buf, int len)
                        }
                }
                break;
-       case 5:         /* Clear ring buffer */
+       /* Clear ring buffer */
+       case SYSLOG_ACTION_CLEAR:
                logged_chars = 0;
                break;
-       case 6:         /* Disable logging to console */
+       /* Disable logging to console */
+       case SYSLOG_ACTION_CONSOLE_OFF:
                if (saved_console_loglevel == -1)
                        saved_console_loglevel = console_loglevel;
                console_loglevel = minimum_console_loglevel;
                break;
-       case 7:         /* Enable logging to console */
+       /* Enable logging to console */
+       case SYSLOG_ACTION_CONSOLE_ON:
                if (saved_console_loglevel != -1) {
                        console_loglevel = saved_console_loglevel;
                        saved_console_loglevel = -1;
                }
                break;
-       case 8:         /* Set level of messages printed to console */
+       /* Set level of messages printed to console */
+       case SYSLOG_ACTION_CONSOLE_LEVEL:
                error = -EINVAL;
                if (len < 1 || len > 8)
                        goto out;
@@ -400,10 +392,12 @@ int do_syslog(int type, char __user *buf, int len)
                saved_console_loglevel = -1;
                error = 0;
                break;
-       case 9:         /* Number of chars in the log buffer */
+       /* Number of chars in the log buffer */
+       case SYSLOG_ACTION_SIZE_UNREAD:
                error = log_end - log_start;
                break;
-       case 10:        /* Size of the log buffer */
+       /* Size of the log buffer */
+       case SYSLOG_ACTION_SIZE_BUFFER:
                error = log_buf_len;
                break;
        default:
@@ -416,7 +410,7 @@ out:
 
 SYSCALL_DEFINE3(syslog, int, type, char __user *, buf, int, len)
 {
-       return do_syslog(type, buf, len);
+       return do_syslog(type, buf, len, SYSLOG_FROM_CALL);
 }
 
 /*
@@ -1405,4 +1399,123 @@ bool printk_timed_ratelimit(unsigned long *caller_jiffies,
        return false;
 }
 EXPORT_SYMBOL(printk_timed_ratelimit);
+
+static DEFINE_SPINLOCK(dump_list_lock);
+static LIST_HEAD(dump_list);
+
+/**
+ * kmsg_dump_register - register a kernel log dumper.
+ * @dumper: pointer to the kmsg_dumper structure
+ *
+ * Adds a kernel log dumper to the system. The dump callback in the
+ * structure will be called when the kernel oopses or panics and must be
+ * set. Returns zero on success and %-EINVAL or %-EBUSY otherwise.
+ */
+int kmsg_dump_register(struct kmsg_dumper *dumper)
+{
+       unsigned long flags;
+       int err = -EBUSY;
+
+       /* The dump callback needs to be set */
+       if (!dumper->dump)
+               return -EINVAL;
+
+       spin_lock_irqsave(&dump_list_lock, flags);
+       /* Don't allow registering multiple times */
+       if (!dumper->registered) {
+               dumper->registered = 1;
+               list_add_tail(&dumper->list, &dump_list);
+               err = 0;
+       }
+       spin_unlock_irqrestore(&dump_list_lock, flags);
+
+       return err;
+}
+EXPORT_SYMBOL_GPL(kmsg_dump_register);
+
+/**
+ * kmsg_dump_unregister - unregister a kmsg dumper.
+ * @dumper: pointer to the kmsg_dumper structure
+ *
+ * Removes a dump device from the system. Returns zero on success and
+ * %-EINVAL otherwise.
+ */
+int kmsg_dump_unregister(struct kmsg_dumper *dumper)
+{
+       unsigned long flags;
+       int err = -EINVAL;
+
+       spin_lock_irqsave(&dump_list_lock, flags);
+       if (dumper->registered) {
+               dumper->registered = 0;
+               list_del(&dumper->list);
+               err = 0;
+       }
+       spin_unlock_irqrestore(&dump_list_lock, flags);
+
+       return err;
+}
+EXPORT_SYMBOL_GPL(kmsg_dump_unregister);
+
+static const char const *kmsg_reasons[] = {
+       [KMSG_DUMP_OOPS]        = "oops",
+       [KMSG_DUMP_PANIC]       = "panic",
+       [KMSG_DUMP_KEXEC]       = "kexec",
+};
+
+static const char *kmsg_to_str(enum kmsg_dump_reason reason)
+{
+       if (reason >= ARRAY_SIZE(kmsg_reasons) || reason < 0)
+               return "unknown";
+
+       return kmsg_reasons[reason];
+}
+
+/**
+ * kmsg_dump - dump kernel log to kernel message dumpers.
+ * @reason: the reason (oops, panic etc) for dumping
+ *
+ * Iterate through each of the dump devices and call the oops/panic
+ * callbacks with the log buffer.
+ */
+void kmsg_dump(enum kmsg_dump_reason reason)
+{
+       unsigned long end;
+       unsigned chars;
+       struct kmsg_dumper *dumper;
+       const char *s1, *s2;
+       unsigned long l1, l2;
+       unsigned long flags;
+
+       /* Theoretically, the log could move on after we do this, but
+          there's not a lot we can do about that. The new messages
+          will overwrite the start of what we dump. */
+       spin_lock_irqsave(&logbuf_lock, flags);
+       end = log_end & LOG_BUF_MASK;
+       chars = logged_chars;
+       spin_unlock_irqrestore(&logbuf_lock, flags);
+
+       if (logged_chars > end) {
+               s1 = log_buf + log_buf_len - logged_chars + end;
+               l1 = logged_chars - end;
+
+               s2 = log_buf;
+               l2 = end;
+       } else {
+               s1 = "";
+               l1 = 0;
+
+               s2 = log_buf + end - logged_chars;
+               l2 = logged_chars;
+       }
+
+       if (!spin_trylock_irqsave(&dump_list_lock, flags)) {
+               printk(KERN_ERR "dump_kmsg: dump list lock is held during %s, skipping dump\n",
+                               kmsg_to_str(reason));
+               return;
+       }
+       list_for_each_entry(dumper, &dump_list, list)
+               dumper->dump(dumper, reason, s1, l1, s2, l2);
+       spin_unlock_irqrestore(&dump_list_lock, flags);
+}
 #endif