packaging: install license for rpm package instead of license package
[profile/mobile/platform/kernel/linux-3.10-sc7730.git] / kernel / printk_kmsg.c
index 5b2afd9..00515b8 100644 (file)
 #define CREATE_TRACE_POINTS
 #include <trace/events/printk.h>
 
+#ifdef CONFIG_PRINTK
+#include <uapi/linux/kmsg_ioctl.h>
+#endif
+
 #ifdef CONFIG_DEBUG_LL
 extern void printascii(char *);
 #endif
@@ -250,6 +254,10 @@ struct log_buffer {
        u64 next_seq;
 #ifdef CONFIG_PRINTK
        u32 next_idx;           /* index of the next record to store */
+/* sequence number of the next record to read after last 'clear' command */
+       u64 clear_seq;
+/* index of the next record to read after last 'clear' command */
+       u32 clear_idx;
        int mode;               /* mode of device */
        int minor;              /* minor representing buffer device */
 #endif
@@ -267,10 +275,6 @@ static u64 console_seq;
 static u32 console_idx;
 static enum log_flags console_prev;
 
-/* the next printk record to read after the last 'clear' command */
-static u64 clear_seq;
-static u32 clear_idx;
-
 #ifdef CONFIG_PRINTK_PROCESS
 #define PREFIX_MAX             48
 #else
@@ -299,6 +303,8 @@ static struct log_buffer log_buf = {
        .first_idx      = 0,
        .next_seq       = 0,
        .next_idx       = 0,
+       .clear_seq      = 0,
+       .clear_idx      = 0,
        .mode           = 0,
        .minor          = 0,
 };
@@ -989,6 +995,7 @@ static ssize_t kmsg_read(struct log_buffer *log_b, struct file *file,
        char cont = '-';
        size_t len;
        ssize_t ret;
+       const int prime = (log_b == &log_buf);
 
        p = user->buf;
        e = user->buf + sizeof(user->buf);
@@ -1010,7 +1017,6 @@ static ssize_t kmsg_read(struct log_buffer *log_b, struct file *file,
                        ret = wait_event_interruptible(log_b->wait,
                                                user->seq != log_b->next_seq);
                } else {
-                       rcu_read_unlock();
                        kref_get(&log_b->refcount);
                        ret = wait_event_interruptible(log_b->wait,
                                                user->seq != log_b->next_seq);
@@ -1018,7 +1024,6 @@ static ssize_t kmsg_read(struct log_buffer *log_b, struct file *file,
                                ret = -ENXIO;
                        if (kref_put(&log_b->refcount, log_buf_release))
                                ret = -ENXIO;
-                       rcu_read_lock();
                }
                if (ret)
                        goto out;
@@ -1057,15 +1062,23 @@ static ssize_t kmsg_read(struct log_buffer *log_b, struct file *file,
                       user->seq, ts_usec, cont);
        user->prev = msg->flags;
 
-       /* escape non-printable characters */
        for (i = 0; i < msg->text_len; i++) {
                unsigned char c = log_text(msg)[i];
 
-               if (c < ' ' || c >= 127 || c == '\\')
+               if (prime && (c < ' ' || c >= 127 || c == '\\'))
                        p += scnprintf(p, e - p, "\\x%02x", c);
                else
                        append_char(&p, e, c);
        }
+
+       /*
+        * The \0 is delimits the text part, while the newline is for formatting
+        * when catting the device directly. We cannot use \n for delimiting due
+        * to security: else one could forge dictionary tags through the message
+        * such as "text\n _PID=123"
+        */
+       if (!prime)
+               append_char(&p, e, '\0');
        append_char(&p, e, '\n');
 
        if (msg->dict_len) {
@@ -1085,7 +1098,7 @@ static ssize_t kmsg_read(struct log_buffer *log_b, struct file *file,
                                continue;
                        }
 
-                       if (c < ' ' || c >= 127 || c == '\\') {
+                       if (prime && (c < ' ' || c >= 127 || c == '\\')) {
                                p += scnprintf(p, e - p, "\\x%02x", c);
                                continue;
                        }
@@ -1123,6 +1136,7 @@ static ssize_t devkmsg_read(struct file *file, char __user *buf,
        ssize_t ret = -ENXIO;
        int minor = iminor(file->f_inode);
        struct log_buffer *log_b;
+       int found = 0;
 
        if (!user)
                return -EBADF;
@@ -1133,11 +1147,17 @@ static ssize_t devkmsg_read(struct file *file, char __user *buf,
        rcu_read_lock();
        list_for_each_entry_rcu(log_b, &log_buf.list, list) {
                if (log_b->minor == minor) {
-                       ret = kmsg_read(log_b, file, buf, count, ppos);
+                       found = 1;
+                       kref_get(&log_b->refcount);
                        break;
                }
        }
        rcu_read_unlock();
+
+       if(found){
+               ret = kmsg_read(log_b, file, buf, count, ppos);
+               kref_put(&log_b->refcount, log_buf_release);
+       }
        return ret;
 }
 
@@ -1155,18 +1175,14 @@ static loff_t kmsg_llseek(struct log_buffer *log_b, struct file *file,
                user->seq = log_b->first_seq;
                break;
        case SEEK_DATA:
-               /* no clear index for kmsg_sys buffers */
-               if (log_b != &log_buf) {
-                       ret = -EINVAL;
-                       break;
-               }
                /*
                 * The first record after the last SYSLOG_ACTION_CLEAR,
-                * like issued by 'dmesg -c'. Reading /dev/kmsg itself
-                * changes no global state, and does not clear anything.
+                * like issued by 'dmesg -c' or KMSG_CMD_CLEAR ioctl
+                * command. Reading /dev/kmsg itself changes no global
+                * state, and does not clear anything.
                 */
-               user->idx = clear_idx;
-               user->seq = clear_seq;
+               user->idx = log_b->clear_idx;
+               user->seq = log_b->clear_seq;
                break;
        case SEEK_END:
                /* after the last record */
@@ -1281,6 +1297,7 @@ static int devkmsg_open(struct inode *inode, struct file *file)
        int ret = -ENXIO;
        int minor = iminor(file->f_inode);
        struct log_buffer *log_b;
+       int found = 0;
 
        /* write-only does not need any file context */
        if ((file->f_flags & O_ACCMODE) == O_WRONLY)
@@ -1298,7 +1315,63 @@ static int devkmsg_open(struct inode *inode, struct file *file)
        rcu_read_lock();
        list_for_each_entry_rcu(log_b, &log_buf.list, list) {
                if (log_b->minor == minor) {
-                       ret = kmsg_open(log_b, file);
+                       found = 1;
+                       kref_get(&log_b->refcount);
+                       break;
+               }
+       }
+       rcu_read_unlock();
+
+       if(found){
+               ret = kmsg_open(log_b, file);
+               kref_put(&log_b->refcount, log_buf_release);
+       }
+       return ret;
+}
+
+static long kmsg_ioctl(struct log_buffer *log_b, unsigned int cmd,
+                      unsigned long arg)
+{
+       void __user *argp = (void __user *)arg;
+       static const u32 read_size_max = CONSOLE_EXT_LOG_MAX;
+
+       switch (cmd) {
+       case KMSG_CMD_GET_BUF_SIZE:
+               if (copy_to_user(argp, &log_b->len, sizeof(u32)))
+                       return -EFAULT;
+               break;
+       case KMSG_CMD_GET_READ_SIZE_MAX:
+               if (copy_to_user(argp, &read_size_max, sizeof(u32)))
+                       return -EFAULT;
+               break;
+       case KMSG_CMD_CLEAR:
+               if (!capable(CAP_SYSLOG))
+                       return -EPERM;
+               raw_spin_lock_irq(&log_b->lock);
+               log_b->clear_seq = log_b->next_seq;
+               log_b->clear_idx = log_b->next_idx;
+               raw_spin_unlock_irq(&log_b->lock);
+               break;
+       default:
+               return -ENOTTY;
+       }
+       return 0;
+}
+
+static long devkmsg_ioctl(struct file *file, unsigned int cmd,
+                         unsigned long arg)
+{
+       long ret = -ENXIO;
+       int minor = iminor(file->f_inode);
+       struct log_buffer *log_b;
+
+       if (minor == log_buf.minor)
+               return kmsg_ioctl(&log_buf, cmd, arg);
+
+       rcu_read_lock();
+       list_for_each_entry_rcu(log_b, &log_buf.list, list) {
+               if (log_b->minor == minor) {
+                       ret = kmsg_ioctl(log_b, cmd, arg);
                        break;
                }
        }
@@ -1324,9 +1397,125 @@ const struct file_operations kmsg_fops = {
        .aio_write = devkmsg_writev,
        .llseek = devkmsg_llseek,
        .poll = devkmsg_poll,
+       .unlocked_ioctl = devkmsg_ioctl,
+       .compat_ioctl = devkmsg_ioctl,
        .release = devkmsg_release,
 };
 
+#define MAX_MINOR_LEN  20
+
+static int kmsg_open_ext(struct inode *inode, struct file *file)
+{
+       return kmsg_fops.open(inode, file);
+}
+
+static ssize_t kmsg_writev_ext(struct kiocb *iocb, const struct iovec *iov, unsigned long count, loff_t pos)
+{
+       return kmsg_fops.aio_write(iocb, iov, count, pos);
+}
+
+static ssize_t kmsg_read_ext(struct file *file, char __user *buf,
+                            size_t count, loff_t *ppos)
+{
+       return kmsg_fops.read(file, buf, count, ppos);
+}
+
+static loff_t kmsg_llseek_ext(struct file *file, loff_t offset, int whence)
+{
+       return kmsg_fops.llseek(file, offset, whence);
+}
+
+static unsigned int kmsg_poll_ext(struct file *file,
+                                 struct poll_table_struct *wait)
+{
+       return kmsg_fops.poll(file, wait);
+}
+
+static long kmsg_ioctl_buffers(struct file *file, unsigned int cmd,
+                              unsigned long arg)
+{
+       void __user *argp = (void __user *)arg;
+       size_t size;
+       umode_t mode;
+       char name[4 + MAX_MINOR_LEN + 1];
+       struct device *dev;
+       int minor;
+
+       if (iminor(file->f_inode) != log_buf.minor)
+               return -ENOTTY;
+
+       switch (cmd) {
+       case KMSG_CMD_BUFFER_ADD:
+               if (copy_from_user(&size, argp, sizeof(size)))
+                       return -EFAULT;
+               argp += sizeof(size);
+               if (copy_from_user(&mode, argp, sizeof(mode)))
+                       return -EFAULT;
+               argp += sizeof(mode);
+               minor = kmsg_sys_buffer_add(size, mode);
+               if (minor < 0)
+                       return minor;
+               sprintf(name, "kmsg%d", minor);
+               dev = device_create(mem_class, NULL, MKDEV(MEM_MAJOR, minor),
+                                   NULL, name);
+               if (IS_ERR(dev)) {
+                       kmsg_sys_buffer_del(minor);
+                       return PTR_ERR(dev);
+               }
+               if (copy_to_user(argp, &minor, sizeof(minor))) {
+                       device_destroy(mem_class, MKDEV(MEM_MAJOR, minor));
+                       kmsg_sys_buffer_del(minor);
+                       return -EFAULT;
+               }
+               return 0;
+       case KMSG_CMD_BUFFER_DEL:
+               if (copy_from_user(&minor, argp, sizeof(minor)))
+                       return -EFAULT;
+               if (minor <= log_buf.minor)
+                       return -EINVAL;
+               device_destroy(mem_class, MKDEV(MEM_MAJOR, minor));
+               kmsg_sys_buffer_del(minor);
+               return 0;
+       }
+       return -ENOTTY;
+}
+
+static long kmsg_unlocked_ioctl_ext(struct file *file, unsigned int cmd,
+                                   unsigned long arg)
+{
+       long ret = kmsg_ioctl_buffers(file, cmd, arg);
+
+       if (ret == -ENOTTY)
+               return kmsg_fops.unlocked_ioctl(file, cmd, arg);
+       return ret;
+}
+
+static long kmsg_compat_ioctl_ext(struct file *file, unsigned int cmd,
+                                 unsigned long arg)
+{
+       long ret = kmsg_ioctl_buffers(file, cmd, arg);
+
+       if (ret == -ENOTTY)
+               return kmsg_fops.compat_ioctl(file, cmd, arg);
+       return ret;
+}
+
+static int kmsg_release_ext(struct inode *inode, struct file *file)
+{
+       return kmsg_fops.release(inode, file);
+}
+
+const struct file_operations kmsg_fops_ext = {
+       .open           = kmsg_open_ext,
+       .read           = kmsg_read_ext,
+       .aio_write      = kmsg_writev_ext,
+       .llseek         = kmsg_llseek_ext,
+       .poll           = kmsg_poll_ext,
+       .unlocked_ioctl = kmsg_unlocked_ioctl_ext,
+       .compat_ioctl   = kmsg_compat_ioctl_ext,
+       .release        = kmsg_release_ext,
+};
+
 /* Should be used for device registration */
 struct device *init_kmsg(int minor, umode_t mode)
 {
@@ -1343,6 +1532,13 @@ int kmsg_memory_open(struct inode *inode, struct file *filp)
        return kmsg_fops.open(inode, filp);
 }
 
+int kmsg_memory_open_ext(struct inode *inode, struct file *filp)
+{
+       filp->f_op = &kmsg_fops_ext;
+
+       return kmsg_fops_ext.open(inode, filp);
+}
+
 int kmsg_mode(int minor, umode_t *mode)
 {
        int ret = -ENXIO;
@@ -1782,18 +1978,18 @@ static int syslog_print_all(char __user *buf, int size, bool clear)
                u32 idx;
                enum log_flags prev;
 
-               if (clear_seq < log_buf.first_seq) {
+               if (log_buf.clear_seq < log_buf.first_seq) {
                        /* messages are gone, move to first available one */
-                       clear_seq = log_buf.first_seq;
-                       clear_idx = log_buf.first_idx;
+                       log_buf.clear_seq = log_buf.first_seq;
+                       log_buf.clear_idx = log_buf.first_idx;
                }
 
                /*
                 * Find first record that fits, including all following records,
                 * into the user-provided buffer for this dump.
                 */
-               seq = clear_seq;
-               idx = clear_idx;
+               seq = log_buf.clear_seq;
+               idx = log_buf.clear_idx;
                prev = 0;
                while (seq < log_buf.next_seq) {
                        struct log *msg = log_from_idx(&log_buf, idx);
@@ -1805,8 +2001,8 @@ static int syslog_print_all(char __user *buf, int size, bool clear)
                }
 
                /* move first record forward until length fits into the buffer */
-               seq = clear_seq;
-               idx = clear_idx;
+               seq = log_buf.clear_seq;
+               idx = log_buf.clear_idx;
                prev = 0;
                while (len > size && seq < log_buf.next_seq) {
                        struct log *msg = log_from_idx(&log_buf, idx);
@@ -1853,8 +2049,8 @@ static int syslog_print_all(char __user *buf, int size, bool clear)
        }
 
        if (clear) {
-               clear_seq = log_buf.next_seq;
-               clear_idx = log_buf.next_idx;
+               log_buf.clear_seq = log_buf.next_seq;
+               log_buf.clear_idx = log_buf.next_idx;
        }
        raw_spin_unlock_irq(&log_buf.lock);
 
@@ -3193,8 +3389,8 @@ void kmsg_dump(enum kmsg_dump_reason reason)
                dumper->active = true;
 
                raw_spin_lock_irqsave(&log_buf.lock, flags);
-               dumper->cur_seq = clear_seq;
-               dumper->cur_idx = clear_idx;
+               dumper->cur_seq = log_buf.clear_seq;
+               dumper->cur_idx = log_buf.clear_idx;
                dumper->next_seq = log_buf.next_seq;
                dumper->next_idx = log_buf.next_idx;
                raw_spin_unlock_irqrestore(&log_buf.lock, flags);
@@ -3401,8 +3597,8 @@ EXPORT_SYMBOL_GPL(kmsg_dump_get_buffer);
  */
 void kmsg_dump_rewind_nolock(struct kmsg_dumper *dumper)
 {
-       dumper->cur_seq = clear_seq;
-       dumper->cur_idx = clear_idx;
+       dumper->cur_seq = log_buf.clear_seq;
+       dumper->cur_idx = log_buf.clear_idx;
        dumper->next_seq = log_buf.next_seq;
        dumper->next_idx = log_buf.next_idx;
 }