blob: 5de79d89f616aa857514ad5457de76fb04c4f83c [file] [log] [blame]
/*
* linux/fs/proc/fslog.c
*
*/
#include <linux/types.h>
#include <linux/errno.h>
#include <linux/time.h>
#include <linux/kernel.h>
#include <linux/security.h>
#include <linux/syscalls.h>
#include <linux/proc_fs.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/interrupt.h>
#include <asm/uaccess.h>
#include <linux/fs.h>
#include <linux/seq_file.h>
#include <linux/vmalloc.h>
#include <linux/mm.h>
#include <linux/sched/clock.h>
/* For compatibility */
#include <linux/fslog.h>
#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);