634 lines
16 KiB
C
634 lines
16 KiB
C
|
/*
|
||
|
* 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);
|