/* * linux/fs/proc/fslog.c * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* For compatibility */ #include #define FSLOG_ACTION_CLOSE 0 #define FSLOG_ACTION_OPEN 1 #define FSLOG_ACTION_READ 2 #define FSLOG_ACTION_READ_ALL 3 #define FSLOG_ACTION_WRITE 4 #define FSLOG_ACTION_SIZE_BUFFER 5 #define S_PREFIX_MAX 32 #define FSLOG_BUF_LINE_MAX (1024 - S_PREFIX_MAX) #define FSLOG_BUF_ALIGN __alignof__(struct fslog_metadata) #ifdef CONFIG_PROC_FSLOG_LOWMEM #define FSLOG_BUFLEN_STLOG (32 * 1024) #define FSLOG_BUFLEN_DLOG_EFS (16 * 1024) #define FSLOG_BUFLEN_DLOG_RMDIR (16 * 1024) #define FSLOG_BUFLEN_DLOG_ETC (64 * 1024) #define FSLOG_BUFLEN_DLOG_MM (128 * 1024) #else /* device has DRAM of high capacity */ #define FSLOG_BUFLEN_STLOG (32 * 1024) #define FSLOG_BUFLEN_DLOG_EFS (16 * 1024) #define FSLOG_BUFLEN_DLOG_RMDIR (16 * 1024) #define FSLOG_BUFLEN_DLOG_ETC (192 * 1024) #define FSLOG_BUFLEN_DLOG_MM (256 * 1024) #endif #define FSLOG_FILE_MODE_VERSION (S_IRUSR | S_IRGRP | S_IROTH) #define FSLOG_FILE_MODE_DIR \ (S_IRUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH) #define FSLOG_FILE_MODE_STLOG \ (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH) #define FSLOG_FILE_MODE_DLOG (S_IRUSR | S_IRGRP) struct fslog_sequence { u64 fslog_seq; u32 fslog_idx; u64 fslog_first_seq; u32 fslog_first_idx; u64 fslog_next_seq; u32 fslog_next_idx; u64 fslog_clear_seq; u32 fslog_clear_idx; u64 fslog_end_seq; }; struct fslog_metadata { u64 ts_nsec; u16 len; u16 text_len; pid_t pid; pid_t tgid; char comm[TASK_COMM_LEN]; char tgid_comm[TASK_COMM_LEN]; }; struct fslog_data { struct fslog_sequence fslog_seq; struct fslog_metadata fslog_meta; spinlock_t fslog_spinlock; wait_queue_head_t fslog_wait_queue; u32 fslog_buf_len; char *fslog_cbuf; }; static struct proc_dir_entry *fslog_dir; static int do_fslog(int type, struct fslog_data *fl_data, char __user *buf, int count); static int do_fslog_write(struct fslog_data *fl_data, const char __user *buf, int len); static int vfslog(struct fslog_data *fl_data, const char *fmt, va_list args); #define DEFINE_FSLOG_VARIABLE(_name, size) \ static char fslog_cbuf_##_name[size]; \ \ static struct fslog_data fslog_data_##_name = { \ .fslog_seq = { \ .fslog_seq = 0, \ .fslog_idx = 0, \ .fslog_first_seq = 0, \ .fslog_first_idx = 0, \ .fslog_next_seq = 0, \ .fslog_next_idx = 0, \ .fslog_clear_seq = 0, \ .fslog_clear_idx = 0, \ .fslog_end_seq = -1 \ }, \ .fslog_meta = { \ .ts_nsec = 0, \ .len = 0, \ .text_len = 0, \ .pid = 0, \ .tgid = 0, \ .comm = {0, }, \ .tgid_comm = {0, } \ }, \ .fslog_spinlock = __SPIN_LOCK_INITIALIZER(fslog_data_##_name.fslog_spinlock), \ .fslog_wait_queue = __WAIT_QUEUE_HEAD_INITIALIZER(fslog_data_##_name.fslog_wait_queue), \ .fslog_buf_len = size, \ .fslog_cbuf = fslog_cbuf_##_name, \ } static int fslog_release(struct inode *inode, struct file *file) { struct fslog_data *fl_data = (struct fslog_data *)(file->private_data); return do_fslog(FSLOG_ACTION_CLOSE, fl_data, NULL, 0); } static ssize_t fslog_read(struct file *file, char __user *buf, size_t count, loff_t *ppos) { struct fslog_data *fl_data = (struct fslog_data *)(file->private_data); return do_fslog(FSLOG_ACTION_READ_ALL, fl_data, buf, count); } static ssize_t fslog_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos) { struct fslog_data *fl_data = (struct fslog_data *)(file->private_data); return do_fslog(FSLOG_ACTION_WRITE, fl_data, (char __user *)buf, count); } loff_t fslog_llseek(struct file *file, loff_t offset, int whence) { struct fslog_data *fl_data = (struct fslog_data *)(file->private_data); return (loff_t)do_fslog(FSLOG_ACTION_SIZE_BUFFER, fl_data, 0, 0); } #define DEFINE_FSLOG_OPERATION(_name) \ static int fslog_open_##_name(struct inode *inode, struct file *file) \ { \ file->f_flags |= O_NONBLOCK; \ file->private_data = &fslog_data_##_name; \ return do_fslog(FSLOG_ACTION_OPEN, &fslog_data_##_name, NULL, 0); \ } \ \ static const struct file_operations fslog_##_name##_operations = { \ .read = fslog_read, \ .write = fslog_write, \ .open = fslog_open_##_name, \ .release = fslog_release, \ .llseek = fslog_llseek, \ }; DEFINE_FSLOG_VARIABLE(dlog_mm, FSLOG_BUFLEN_DLOG_MM); DEFINE_FSLOG_VARIABLE(dlog_efs, FSLOG_BUFLEN_DLOG_EFS); DEFINE_FSLOG_VARIABLE(dlog_etc, FSLOG_BUFLEN_DLOG_ETC); DEFINE_FSLOG_VARIABLE(dlog_rmdir, FSLOG_BUFLEN_DLOG_RMDIR); DEFINE_FSLOG_VARIABLE(stlog, FSLOG_BUFLEN_STLOG); DEFINE_FSLOG_OPERATION(dlog_mm); DEFINE_FSLOG_OPERATION(dlog_efs); DEFINE_FSLOG_OPERATION(dlog_etc); DEFINE_FSLOG_OPERATION(dlog_rmdir); DEFINE_FSLOG_OPERATION(stlog); static const char FSLOG_VERSION_STR[] = "1.1.0\n"; static int fslog_version_show(struct seq_file *m, void *v) { seq_printf(m, "%s", FSLOG_VERSION_STR); return 0; } static int fslog_version_open(struct inode *inode, struct file *file) { return single_open(file, fslog_version_show, NULL); } static const struct file_operations fslog_ver_operations = { .open = fslog_version_open, .read = seq_read, .llseek = seq_lseek, .release = single_release, }; #define DEFINE_FSLOG_CREATE(_name, log_name, file_mode) \ static void fslog_create_##_name(struct proc_dir_entry *fslog_dir) \ { \ proc_create(#log_name, file_mode, \ fslog_dir, &fslog_##_name##_operations); \ } DEFINE_FSLOG_CREATE(dlog_mm, dlog_mm, FSLOG_FILE_MODE_DLOG); DEFINE_FSLOG_CREATE(dlog_efs, dlog_efs, FSLOG_FILE_MODE_DLOG); DEFINE_FSLOG_CREATE(dlog_etc, dlog_etc, FSLOG_FILE_MODE_DLOG); DEFINE_FSLOG_CREATE(dlog_rmdir, dlog_rmdir, FSLOG_FILE_MODE_DLOG); DEFINE_FSLOG_CREATE(stlog, stlog, FSLOG_FILE_MODE_STLOG); static int __init fslog_init(void) { fslog_dir = proc_mkdir_mode("fslog", FSLOG_FILE_MODE_DIR, NULL); proc_create("version", FSLOG_FILE_MODE_VERSION, fslog_dir, &fslog_ver_operations); fslog_create_dlog_mm(fslog_dir); fslog_create_dlog_efs(fslog_dir); fslog_create_dlog_etc(fslog_dir); fslog_create_dlog_rmdir(fslog_dir); fslog_create_stlog(fslog_dir); /* To ensure sub-compatibility in versions below O OS */ proc_symlink("stlog", NULL, "/proc/fslog/stlog"); proc_symlink("stlog_version", NULL, "/proc/fslog/version"); return 0; } module_init(fslog_init); /* human readable text of the record */ static char *fslog_text(const struct fslog_metadata *msg) { return (char *)msg + sizeof(struct fslog_metadata); } static struct fslog_metadata *fslog_buf_from_idx(char *fslog_cbuf, u32 idx) { struct fslog_metadata *msg = (struct fslog_metadata *)(fslog_cbuf + idx); if (!msg->len) return (struct fslog_metadata *)fslog_cbuf; return msg; } static u32 fslog_next(char *fslog_cbuf, u32 idx) { struct fslog_metadata *msg = (struct fslog_metadata *)(fslog_cbuf + idx); if (!msg->len) { msg = (struct fslog_metadata *)fslog_cbuf; return msg->len; } return idx + msg->len; } static inline void fslog_find_task_by_vpid(struct fslog_metadata *msg, struct task_struct *owner) { struct task_struct *p; rcu_read_lock(); p = find_task_by_vpid(owner->tgid); if (p) { msg->tgid = owner->tgid; memcpy(msg->tgid_comm, p->comm, TASK_COMM_LEN); rcu_read_unlock(); return; } rcu_read_unlock(); msg->tgid = 0; msg->tgid_comm[0] = 0; } static void fslog_buf_store(const char *text, u16 text_len, struct fslog_data *fl_data, struct task_struct *owner) { struct fslog_metadata *msg; struct fslog_sequence *fslog_seq = &(fl_data->fslog_seq); wait_queue_head_t *fslog_wait_queue = &(fl_data->fslog_wait_queue); char *fslog_cbuf = fl_data->fslog_cbuf; u32 size, pad_len, fslog_buf_len = fl_data->fslog_buf_len; /* number of '\0' padding bytes to next message */ size = sizeof(struct fslog_metadata) + text_len; pad_len = (-size) & (FSLOG_BUF_ALIGN - 1); size += pad_len; while (fslog_seq->fslog_first_seq < fslog_seq->fslog_next_seq) { u32 free; if (fslog_seq->fslog_next_idx > fslog_seq->fslog_first_idx) free = max(fslog_buf_len - fslog_seq->fslog_next_idx, fslog_seq->fslog_first_idx); else free = fslog_seq->fslog_first_idx - fslog_seq->fslog_next_idx; if (free > size + sizeof(struct fslog_metadata)) break; /* drop old messages until we have enough space */ fslog_seq->fslog_first_idx = fslog_next(fslog_cbuf, fslog_seq->fslog_first_idx); fslog_seq->fslog_first_seq++; } if (fslog_seq->fslog_next_idx + size + sizeof(struct fslog_metadata) >= fslog_buf_len) { memset(fslog_cbuf + fslog_seq->fslog_next_idx, 0, sizeof(struct fslog_metadata)); fslog_seq->fslog_next_idx = 0; } /* fill message */ msg = (struct fslog_metadata *)(fslog_cbuf + fslog_seq->fslog_next_idx); memcpy(fslog_text(msg), text, text_len); msg->text_len = text_len; msg->pid = owner->pid; memcpy(msg->comm, owner->comm, TASK_COMM_LEN); fslog_find_task_by_vpid(msg, owner); msg->ts_nsec = local_clock(); msg->len = sizeof(struct fslog_metadata) + text_len + pad_len; /* insert message */ fslog_seq->fslog_next_idx += msg->len; fslog_seq->fslog_next_seq++; wake_up_interruptible(fslog_wait_queue); } static size_t fslog_print_time(u64 ts, char *buf) { unsigned long rem_nsec; rem_nsec = do_div(ts, 1000000000); if (!buf) return snprintf(NULL, 0, "[%5lu.000000] ", (unsigned long)ts); return sprintf(buf, "[%5lu.%06lu] ", (unsigned long)ts, rem_nsec / 1000); } static size_t fslog_print_pid(const struct fslog_metadata *msg, char *buf) { if (!buf) { if (msg->pid == msg->tgid) return snprintf(NULL, 0, "[%15s, %5d] ", msg->comm, msg->pid); return snprintf(NULL, 0, "[%s(%d)|%s(%d)] ", msg->comm, msg->pid, msg->tgid_comm, msg->tgid); } if (msg->pid == msg->tgid) return sprintf(buf, "[%15s, %5d] ", msg->comm, msg->pid); return sprintf(buf, "[%s(%d)|%s(%d)] ", msg->comm, msg->pid, msg->tgid_comm, msg->tgid); } static size_t fslog_print_prefix(const struct fslog_metadata *msg, char *buf) { size_t len = 0; len += fslog_print_time(msg->ts_nsec, buf ? buf + len : NULL); len += fslog_print_pid(msg, buf ? buf + len : NULL); return len; } static size_t fslog_print_text(const struct fslog_metadata *msg, char *buf, size_t size) { const char *text = fslog_text(msg); size_t text_size = msg->text_len; bool prefix = true; bool newline = true; size_t len = 0; do { const char *next = memchr(text, '\n', text_size); size_t text_len; if (next) { text_len = next - text; next++; text_size -= next - text; } else { text_len = text_size; } if (buf) { if (fslog_print_prefix(msg, NULL) + text_len + 1 >= size - len) break; if (prefix) len += fslog_print_prefix(msg, buf + len); memcpy(buf + len, text, text_len); len += text_len; if (next || newline) buf[len++] = '\n'; } else { /* buffer size only calculation */ if (prefix) len += fslog_print_prefix(msg, NULL); len += text_len; if (next || newline) len++; } prefix = true; text = next; } while (text); return len; } static int fslog_print_all(struct fslog_data *fl_data, char __user *buf, int size) { char *text; int len = 0; struct fslog_sequence *fslog_seq = &(fl_data->fslog_seq); spinlock_t *fslog_spinlock = &(fl_data->fslog_spinlock); char *fslog_cbuf = fl_data->fslog_cbuf; u64 seq = fslog_seq->fslog_next_seq; u32 idx = fslog_seq->fslog_next_idx; text = kmalloc(FSLOG_BUF_LINE_MAX + S_PREFIX_MAX, GFP_KERNEL); if (!text) return -ENOMEM; spin_lock_irq(fslog_spinlock); if (fslog_seq->fslog_end_seq == -1) fslog_seq->fslog_end_seq = fslog_seq->fslog_next_seq; if (buf) { if (fslog_seq->fslog_clear_seq < fslog_seq->fslog_first_seq) { /* messages are gone, move to first available one */ fslog_seq->fslog_clear_seq = fslog_seq->fslog_first_seq; fslog_seq->fslog_clear_idx = fslog_seq->fslog_first_idx; } seq = fslog_seq->fslog_clear_seq; idx = fslog_seq->fslog_clear_idx; while (seq < fslog_seq->fslog_end_seq) { struct fslog_metadata *msg = fslog_buf_from_idx(fslog_cbuf, idx); int textlen; textlen = fslog_print_text(msg, text, FSLOG_BUF_LINE_MAX + S_PREFIX_MAX); if (textlen < 0) { len = textlen; break; } else if(len + textlen > size) { break; } idx = fslog_next(fslog_cbuf, idx); seq++; spin_unlock_irq(fslog_spinlock); if (copy_to_user(buf + len, text, textlen)) len = -EFAULT; else len += textlen; spin_lock_irq(fslog_spinlock); if (seq < fslog_seq->fslog_first_seq) { /* messages are gone, move to next one */ seq = fslog_seq->fslog_first_seq; idx = fslog_seq->fslog_first_idx; } } } fslog_seq->fslog_clear_seq = seq; fslog_seq->fslog_clear_idx = idx; spin_unlock_irq(fslog_spinlock); kfree(text); return len; } static int do_fslog(int type, struct fslog_data *fl_data, char __user *buf, int len) { int error = 0; u32 fslog_buf_len = fl_data->fslog_buf_len; struct fslog_sequence *fslog_seq = &(fl_data->fslog_seq); switch (type) { case FSLOG_ACTION_CLOSE: /* Close log */ break; case FSLOG_ACTION_OPEN: /* Open log */ break; case FSLOG_ACTION_READ_ALL: /* cat /proc/fslog */ /* dumpstate */ error = -EINVAL; if (!buf || len < 0) goto out; error = 0; if (!len) goto out; if (!access_ok(VERIFY_WRITE, buf, len)) { error = -EFAULT; goto out; } error = fslog_print_all(fl_data, buf, len); if (error == 0) { fslog_seq->fslog_clear_seq = fslog_seq->fslog_first_seq; fslog_seq->fslog_clear_idx = fslog_seq->fslog_first_idx; fslog_seq->fslog_end_seq = -1; } break; /* Size of the log buffer */ case FSLOG_ACTION_WRITE: error = do_fslog_write(fl_data, (const char __user *)buf, len); break; case FSLOG_ACTION_SIZE_BUFFER: error = fslog_buf_len; break; default: error = -EINVAL; break; } out: return error; } static int fslog(struct fslog_data *fl_data, const char *fmt, ...) { va_list args; int r; va_start(args, fmt); r = vfslog(fl_data, fmt, args); va_end(args); return r; } static int do_fslog_write(struct fslog_data *fl_data, const char __user *buf, int len) { int error = 0; char *kern_buf = 0; char *line = 0; if (!buf || len < 0) goto out; if (!len) goto out; if (len > FSLOG_BUF_LINE_MAX) return -EINVAL; kern_buf = kmalloc(len+1, GFP_KERNEL); if (kern_buf == NULL) return -ENOMEM; line = kern_buf; if (copy_from_user(line, buf, len)) { error = -EFAULT; goto out; } line[len] = '\0'; error = fslog(fl_data, "%s", line); if ((line[len-1] == '\n') && (error == (len-1))) error++; out: kfree(kern_buf); return error; } static int vfslog(struct fslog_data *fl_data, const char *fmt, va_list args) { static char textbuf[FSLOG_BUF_LINE_MAX]; char *text = textbuf; size_t text_len; unsigned long flags; int printed_len = 0; bool stored = false; spinlock_t *fslog_spinlock = &(fl_data->fslog_spinlock); local_irq_save(flags); spin_lock(fslog_spinlock); text_len = vscnprintf(text, sizeof(textbuf), fmt, args); /* mark and strip a trailing newline */ if (text_len && text[text_len-1] == '\n') text_len--; if (!stored) fslog_buf_store(text, text_len, fl_data, current); printed_len += text_len; spin_unlock(fslog_spinlock); local_irq_restore(flags); return printed_len; } /* * fslog - print a deleted entry message * @fmt: format string */ #define DEFINE_FSLOG_FUNC(_name)\ int fslog_##_name(const char *fmt, ...)\ {\ va_list args;\ int r;\ \ va_start(args, fmt);\ r = vfslog(&fslog_data_##_name, fmt, args); \ \ va_end(args);\ \ return r;\ } DEFINE_FSLOG_FUNC(dlog_mm); DEFINE_FSLOG_FUNC(dlog_efs); DEFINE_FSLOG_FUNC(dlog_etc); DEFINE_FSLOG_FUNC(dlog_rmdir); DEFINE_FSLOG_FUNC(stlog);