#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
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
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
.first_idx = 0,
.next_seq = 0,
.next_idx = 0,
+ .clear_seq = 0,
+ .clear_idx = 0,
.mode = 0,
.minor = 0,
};
char cont = '-';
size_t len;
ssize_t ret;
+ const int prime = (log_b == &log_buf);
p = user->buf;
e = user->buf + sizeof(user->buf);
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);
ret = -ENXIO;
if (kref_put(&log_b->refcount, log_buf_release))
ret = -ENXIO;
- rcu_read_lock();
}
if (ret)
goto out;
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) {
continue;
}
- if (c < ' ' || c >= 127 || c == '\\') {
+ if (prime && (c < ' ' || c >= 127 || c == '\\')) {
p += scnprintf(p, e - p, "\\x%02x", c);
continue;
}
ssize_t ret = -ENXIO;
int minor = iminor(file->f_inode);
struct log_buffer *log_b;
+ int found = 0;
if (!user)
return -EBADF;
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;
}
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 */
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)
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;
}
}
.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)
{
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;
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);
}
/* 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);
}
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);
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);
*/
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;
}