| From: John Ogness <john.ogness@linutronix.de> |
| Date: Tue, 12 Feb 2019 15:30:00 +0100 |
| Subject: [PATCH 22/25] printk: implement /dev/kmsg |
| |
| Since printk messages are now logged to a new ring buffer, update |
| the /dev/kmsg functions to pull the messages from there. |
| |
| Signed-off-by: John Ogness <john.ogness@linutronix.de> |
| Signed-off-by: Sebastian Andrzej Siewior <bigeasy@linutronix.de> |
| --- |
| fs/proc/kmsg.c | 4 - |
| include/linux/printk.h | 1 |
| kernel/printk/printk.c | 162 +++++++++++++++++++++++++++++++++---------------- |
| 3 files changed, 113 insertions(+), 54 deletions(-) |
| |
| --- a/fs/proc/kmsg.c |
| +++ b/fs/proc/kmsg.c |
| @@ -18,8 +18,6 @@ |
| #include <linux/uaccess.h> |
| #include <asm/io.h> |
| |
| -extern wait_queue_head_t log_wait; |
| - |
| static int kmsg_open(struct inode * inode, struct file * file) |
| { |
| return do_syslog(SYSLOG_ACTION_OPEN, NULL, 0, SYSLOG_FROM_PROC); |
| @@ -42,7 +40,7 @@ static ssize_t kmsg_read(struct file *fi |
| |
| static __poll_t kmsg_poll(struct file *file, poll_table *wait) |
| { |
| - poll_wait(file, &log_wait, wait); |
| + poll_wait(file, printk_wait_queue(), wait); |
| if (do_syslog(SYSLOG_ACTION_SIZE_UNREAD, NULL, 0, SYSLOG_FROM_PROC)) |
| return EPOLLIN | EPOLLRDNORM; |
| return 0; |
| --- a/include/linux/printk.h |
| +++ b/include/linux/printk.h |
| @@ -191,6 +191,7 @@ void __init setup_log_buf(int early); |
| void dump_stack_print_info(const char *log_lvl); |
| void show_regs_print_info(const char *log_lvl); |
| extern asmlinkage void dump_stack(void) __cold; |
| +struct wait_queue_head *printk_wait_queue(void); |
| #else |
| static inline __printf(1, 0) |
| int vprintk(const char *s, va_list args) |
| --- a/kernel/printk/printk.c |
| +++ b/kernel/printk/printk.c |
| @@ -637,10 +637,11 @@ static ssize_t msg_print_ext_body(char * |
| /* /dev/kmsg - userspace message inject/listen interface */ |
| struct devkmsg_user { |
| u64 seq; |
| - u32 idx; |
| + struct prb_iterator iter; |
| struct ratelimit_state rs; |
| struct mutex lock; |
| char buf[CONSOLE_EXT_LOG_MAX]; |
| + char msgbuf[PRINTK_RECORD_MAX]; |
| }; |
| |
| static __printf(3, 4) __cold |
| @@ -723,9 +724,11 @@ static ssize_t devkmsg_read(struct file |
| size_t count, loff_t *ppos) |
| { |
| struct devkmsg_user *user = file->private_data; |
| + struct prb_iterator backup_iter; |
| struct printk_log *msg; |
| - size_t len; |
| ssize_t ret; |
| + size_t len; |
| + u64 seq; |
| |
| if (!user) |
| return -EBADF; |
| @@ -734,52 +737,67 @@ static ssize_t devkmsg_read(struct file |
| if (ret) |
| return ret; |
| |
| - logbuf_lock_irq(); |
| - while (user->seq == log_next_seq) { |
| - if (file->f_flags & O_NONBLOCK) { |
| - ret = -EAGAIN; |
| - logbuf_unlock_irq(); |
| - goto out; |
| - } |
| + /* make a backup copy in case there is a problem */ |
| + prb_iter_copy(&backup_iter, &user->iter); |
| |
| - logbuf_unlock_irq(); |
| - ret = wait_event_interruptible(log_wait, |
| - user->seq != log_next_seq); |
| - if (ret) |
| - goto out; |
| - logbuf_lock_irq(); |
| + if (file->f_flags & O_NONBLOCK) { |
| + ret = prb_iter_next(&user->iter, &user->msgbuf[0], |
| + sizeof(user->msgbuf), &seq); |
| + } else { |
| + ret = prb_iter_wait_next(&user->iter, &user->msgbuf[0], |
| + sizeof(user->msgbuf), &seq); |
| } |
| - |
| - if (user->seq < log_first_seq) { |
| - /* our last seen message is gone, return error and reset */ |
| - user->idx = log_first_idx; |
| - user->seq = log_first_seq; |
| + if (ret == 0) { |
| + /* end of list */ |
| + ret = -EAGAIN; |
| + goto out; |
| + } else if (ret == -EINVAL) { |
| + /* iterator invalid, return error and reset */ |
| ret = -EPIPE; |
| - logbuf_unlock_irq(); |
| + prb_iter_init(&user->iter, &printk_rb, &user->seq); |
| + goto out; |
| + } else if (ret < 0) { |
| + /* interrupted by signal */ |
| goto out; |
| } |
| |
| - msg = log_from_idx(user->idx); |
| + if (user->seq == 0) { |
| + user->seq = seq; |
| + } else { |
| + user->seq++; |
| + if (user->seq < seq) { |
| + ret = -EPIPE; |
| + goto restore_out; |
| + } |
| + } |
| + |
| + msg = (struct printk_log *)&user->msgbuf[0]; |
| len = msg_print_ext_header(user->buf, sizeof(user->buf), |
| msg, user->seq); |
| len += msg_print_ext_body(user->buf + len, sizeof(user->buf) - len, |
| log_dict(msg), msg->dict_len, |
| log_text(msg), msg->text_len); |
| |
| - user->idx = log_next(user->idx); |
| - user->seq++; |
| - logbuf_unlock_irq(); |
| - |
| if (len > count) { |
| ret = -EINVAL; |
| - goto out; |
| + goto restore_out; |
| } |
| |
| if (copy_to_user(buf, user->buf, len)) { |
| ret = -EFAULT; |
| - goto out; |
| + goto restore_out; |
| } |
| + |
| ret = len; |
| + goto out; |
| +restore_out: |
| + /* |
| + * There was an error, but this message should not be |
| + * lost because of it. Restore the backup and setup |
| + * seq so that it will work with the next read. |
| + */ |
| + prb_iter_copy(&user->iter, &backup_iter); |
| + user->seq = seq - 1; |
| out: |
| mutex_unlock(&user->lock); |
| return ret; |
| @@ -788,19 +806,21 @@ static ssize_t devkmsg_read(struct file |
| static loff_t devkmsg_llseek(struct file *file, loff_t offset, int whence) |
| { |
| struct devkmsg_user *user = file->private_data; |
| - loff_t ret = 0; |
| + loff_t ret; |
| |
| if (!user) |
| return -EBADF; |
| if (offset) |
| return -ESPIPE; |
| |
| - logbuf_lock_irq(); |
| + ret = mutex_lock_interruptible(&user->lock); |
| + if (ret) |
| + return ret; |
| + |
| switch (whence) { |
| case SEEK_SET: |
| /* the first record */ |
| - user->idx = log_first_idx; |
| - user->seq = log_first_seq; |
| + prb_iter_init(&user->iter, &printk_rb, &user->seq); |
| break; |
| case SEEK_DATA: |
| /* |
| @@ -808,40 +828,83 @@ static loff_t devkmsg_llseek(struct file |
| * like issued by 'dmesg -c'. Reading /dev/kmsg itself |
| * changes no global state, and does not clear anything. |
| */ |
| - user->idx = clear_idx; |
| - user->seq = clear_seq; |
| + for (;;) { |
| + prb_iter_init(&user->iter, &printk_rb, NULL); |
| + ret = prb_iter_seek(&user->iter, clear_seq); |
| + if (ret > 0) { |
| + /* seeked to clear seq */ |
| + user->seq = clear_seq; |
| + break; |
| + } else if (ret == 0) { |
| + /* |
| + * The end of the list was hit without |
| + * ever seeing the clear seq. Just |
| + * seek to the beginning of the list. |
| + */ |
| + prb_iter_init(&user->iter, &printk_rb, |
| + &user->seq); |
| + break; |
| + } |
| + /* iterator invalid, start over */ |
| + } |
| + ret = 0; |
| break; |
| case SEEK_END: |
| /* after the last record */ |
| - user->idx = log_next_idx; |
| - user->seq = log_next_seq; |
| + for (;;) { |
| + ret = prb_iter_next(&user->iter, NULL, 0, &user->seq); |
| + if (ret == 0) |
| + break; |
| + else if (ret > 0) |
| + continue; |
| + /* iterator invalid, start over */ |
| + prb_iter_init(&user->iter, &printk_rb, &user->seq); |
| + } |
| + ret = 0; |
| break; |
| default: |
| ret = -EINVAL; |
| } |
| - logbuf_unlock_irq(); |
| + |
| + mutex_unlock(&user->lock); |
| return ret; |
| } |
| |
| +struct wait_queue_head *printk_wait_queue(void) |
| +{ |
| + /* FIXME: using prb internals! */ |
| + return printk_rb.wq; |
| +} |
| + |
| static __poll_t devkmsg_poll(struct file *file, poll_table *wait) |
| { |
| struct devkmsg_user *user = file->private_data; |
| + struct prb_iterator iter; |
| __poll_t ret = 0; |
| + int rbret; |
| + u64 seq; |
| |
| if (!user) |
| return EPOLLERR|EPOLLNVAL; |
| |
| - poll_wait(file, &log_wait, wait); |
| + poll_wait(file, printk_wait_queue(), wait); |
| |
| - logbuf_lock_irq(); |
| - if (user->seq < log_next_seq) { |
| - /* return error when data has vanished underneath us */ |
| - if (user->seq < log_first_seq) |
| - ret = EPOLLIN|EPOLLRDNORM|EPOLLERR|EPOLLPRI; |
| - else |
| - ret = EPOLLIN|EPOLLRDNORM; |
| - } |
| - logbuf_unlock_irq(); |
| + mutex_lock(&user->lock); |
| + |
| + /* use copy so no actual iteration takes place */ |
| + prb_iter_copy(&iter, &user->iter); |
| + |
| + rbret = prb_iter_next(&iter, &user->msgbuf[0], |
| + sizeof(user->msgbuf), &seq); |
| + if (rbret == 0) |
| + goto out; |
| + |
| + ret = EPOLLIN|EPOLLRDNORM; |
| + |
| + if (rbret < 0 || (seq - user->seq) != 1) |
| + ret |= EPOLLERR|EPOLLPRI; |
| +out: |
| + mutex_unlock(&user->lock); |
| |
| return ret; |
| } |
| @@ -871,10 +934,7 @@ static int devkmsg_open(struct inode *in |
| |
| mutex_init(&user->lock); |
| |
| - logbuf_lock_irq(); |
| - user->idx = log_first_idx; |
| - user->seq = log_first_seq; |
| - logbuf_unlock_irq(); |
| + prb_iter_init(&user->iter, &printk_rb, &user->seq); |
| |
| file->private_data = user; |
| return 0; |