text: /*
   *  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>
  
  /* 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);
  