/*
 * (C) Copyright Fuji Xerox Co., Ltd. All rights are reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 * 2018/06/12	nobuo.iwata@fujixerox.co.jp
 */

#include "fxlsm_sysfs.h"
#include "fxlsm_ops.h"
#include "fxlsm_net.h"
#include "fxlsm_list.h"
#include "fxlsm_event.h"
#include "fxlsm_prof.h"
#include "fxlsm_list.h"
#include "fxlsm_log.h"
#include <linux/kobject.h>
#include <linux/sysfs.h>
#include <linux/slab.h>

static inline int bool_val(const char *buf)
{
	return (simple_strtol(buf, NULL, 10) != 0);
}

static inline int int_val(const char *buf)
{
	return simple_strtol(buf, NULL, 10);
}

static ssize_t enabled_show(struct kobject *kobj,
		struct kobj_attribute *attr, char *buf)
{
	return sprintf(buf, "%s\n", fxlsm_get_enabled_string());
}

static ssize_t enabled_store(struct kobject *kobj,
		struct kobj_attribute *attr, const char *buf, size_t count)
{
	int prev = fxlsm_get_enabled();
	int val = fxlsm_set_enabled(buf);
	
	fxlsm_notify_enabled(prev, val);
	return count;
}

static struct kobj_attribute enabled_attr =
	__ATTR(enabled, 0660, enabled_show, enabled_store);

static ssize_t through_show(struct kobject *kobj,
		struct kobj_attribute *attr, char *buf)
{
	return sprintf(buf, "%s\n", fxlsm_get_through_string());
}

static ssize_t through_store(struct kobject *kobj,
		struct kobj_attribute *attr, const char *buf, size_t count)
{
	int prev = fxlsm_get_through();
	int val = fxlsm_set_through(buf);
	
	fxlsm_notify_through(prev, val);
	return count;
}

static struct kobj_attribute through_attr =
	__ATTR(through, 0660, through_show, through_store);

static ssize_t capture_show(struct kobject *kobj,
		struct kobj_attribute *attr, char *buf)
{
	return sprintf(buf, "%s\n", fxlsm_get_capture_string());
}

static ssize_t capture_store(struct kobject *kobj,
		struct kobj_attribute *attr, const char *buf, size_t count)
{
	fxlsm_set_capture(buf);
	return count;
}

static struct kobj_attribute capture_attr =
	__ATTR(capture, 0660, capture_show, capture_store);

#ifdef FXLSM_USE_CAP_WRITE
static ssize_t capture_write_store(struct kobject *kobj,
		struct kobj_attribute *attr, const char *buf, size_t count)
{
	int val = bool_val(buf);
	int ret;

	if (!val)
		return -EINVAL;
	ret = fxlsm_capture_write();
	if (ret)
		return ret;
	return count;
}

static struct kobj_attribute capture_write_attr =
	__ATTR(capture_write, 0220, NULL, capture_write_store);
#endif /* FXLSM_USE_CAP_WRITE */

static ssize_t secondary_show(struct kobject *kobj,
		struct kobj_attribute *attr, char *buf)
{
	return sprintf(buf, "%d\n", fxlsm_get_secondary());
}

static ssize_t secondary_store(struct kobject *kobj,
		struct kobj_attribute *attr, const char *buf, size_t count)
{
	int val = simple_strtol(buf, NULL, 10);

	if (val > 0)
		fxlsm_set_secondary(1);
	else
		fxlsm_set_secondary(0);
	if (val < 0)
		fxlsm_free_secondary();
	return count;
}

static struct kobj_attribute secondary_attr =
	__ATTR(secondary, 0660, secondary_show, secondary_store);

static ssize_t comms_show(struct kobject *kobj,
		struct kobj_attribute *attr, char *buf)
{
	return sprintf(buf, "%d\n", fxlsm_get_comms());
}

static ssize_t comms_store(struct kobject *kobj,
		struct kobj_attribute *attr, const char *buf, size_t count)
{
	fxlsm_set_comms(bool_val(buf));
	return count;
}

static struct kobj_attribute comms_attr =
	__ATTR(comms, 0660, comms_show, comms_store);

static ssize_t lds_show(struct kobject *kobj,
		struct kobj_attribute *attr, char *buf)
{
	return sprintf(buf, "%d\n", fxlsm_get_lds());
}

static ssize_t lds_store(struct kobject *kobj,
		struct kobj_attribute *attr, const char *buf, size_t count)
{
	fxlsm_set_lds(bool_val(buf));
	return count;
}

static struct kobj_attribute lds_attr =
	__ATTR(lds, 0660, lds_show, lds_store);

static ssize_t creatx_show(struct kobject *kobj,
		struct kobj_attribute *attr, char *buf)
{
	return sprintf(buf, "%d\n", fxlsm_get_creatx());
}

static ssize_t creatx_store(struct kobject *kobj,
		struct kobj_attribute *attr, const char *buf, size_t count)
{
	fxlsm_set_creatx(int_val(buf));
	return count;
}

static struct kobj_attribute creatx_attr =
	__ATTR(creatx, 0660, creatx_show, creatx_store);

static ssize_t chmodx_show(struct kobject *kobj,
		struct kobj_attribute *attr, char *buf)
{
	return sprintf(buf, "%d\n", fxlsm_get_chmodx());
}

static ssize_t chmodx_store(struct kobject *kobj,
		struct kobj_attribute *attr, const char *buf, size_t count)
{
	fxlsm_set_chmodx(int_val(buf));
	return count;
}

static struct kobj_attribute chmodx_attr =
	__ATTR(chmodx, 0660, chmodx_show, chmodx_store);

static ssize_t net_show(struct kobject *kobj,
		struct kobj_attribute *attr, char *buf)
{
	return sprintf(buf, "%d\n", fxlsm_get_net());
}

static ssize_t net_store(struct kobject *kobj,
		struct kobj_attribute *attr, const char *buf, size_t count)
{
	fxlsm_set_net(bool_val(buf));
	return count;
}

static struct kobj_attribute net_attr =
	__ATTR(net, 0660, net_show, net_store);

static ssize_t max_capture_show(struct kobject *kobj,
		struct kobj_attribute *attr, char *buf)
{
	return sprintf(buf, "%d\n", fxlsm_get_max_capture());
}

static ssize_t max_capture_store(struct kobject *kobj,
		struct kobj_attribute *attr, const char *buf, size_t count)
{
	fxlsm_set_max_capture(simple_strtol(buf, NULL, 10));
	return count;
}

static struct kobj_attribute max_capture_attr =
	__ATTR(max_capture, 0660, max_capture_show, max_capture_store);

static ssize_t max_events_show(struct kobject *kobj,
		struct kobj_attribute *attr, char *buf)
{
	return sprintf(buf, "%d\n", fxlsm_get_max_events());
}

static ssize_t max_events_store(struct kobject *kobj,
		struct kobj_attribute *attr, const char *buf, size_t count)
{
	fxlsm_set_max_events(simple_strtol(buf, NULL, 10));
	return count;
}

static struct kobj_attribute max_events_attr =
	__ATTR(max_events, 0660, max_events_show, max_events_store);

static ssize_t lost_events_show(struct kobject *kobj,
		struct kobj_attribute *attr, char *buf)
{
	return sprintf(buf, "%d\n", fxlsm_get_lost_events());
}

static struct kobj_attribute lost_events_attr =
	__ATTR(lost_events, 0440, lost_events_show, NULL);

#ifdef FXLSM_USE_PROF_TIME
#define LIST_SHOW_TIME(buf, off, hit, mis, __hit, __mis) \
	list_show_time(buf, off, hit, mis, __hit, __mis)
#define LIST_SHOW_TIME_TOTAL(buf, off, __hit, __mis) \
	list_show_time_total(buf, off, __hit, __mis)
static void list_show_time(
		char *buf, ssize_t *off, int id_hit, int id_mis,
		struct fxlsm_prof_time *__hit, struct fxlsm_prof_time *__mis)
{
	struct fxlsm_prof_time all, hit, mis;
	int rate;

	fxlsm_prof_time_read(id_hit, &hit);
	fxlsm_prof_time_read(id_mis, &mis);
	fxlsm_prof_time_marge(&all, &hit, &mis, &rate);
	*off += sprintf(buf + *off,
		" a:%llu/%lu=%llu h:%llu/%lu=%llu m:%llu/%lu=%llu %d\n",
		all.time, all.count, all.average,
		hit.time, hit.count, hit.average,
		mis.time, mis.count, mis.average,
		rate);
	if (__hit) {
		__hit->time += hit.time;
		if (id_hit == FXLSM_PROF_TIME_ID_FILES_HIT)
			__hit->count = hit.count;
	}
	if (__mis) {
		__mis->time += mis.time;
		if (id_hit == FXLSM_PROF_TIME_ID_FILES_MIS)
			__mis->count = mis.count;
	}
}

static void list_show_time_total(
		char *buf, ssize_t *off,
		struct fxlsm_prof_time *__hit, struct fxlsm_prof_time *__mis)
{
	struct fxlsm_prof_time all;
	int rate;

	fxlsm_prof_time_average(__hit);
	fxlsm_prof_time_average(__mis);
	fxlsm_prof_time_marge(&all, __hit, __mis, &rate);
	*off += sprintf(buf + *off,
			" a:%llu/%lu=%llu h:-/-=- m:-/-=- - %llu\n",
		all.time, all.count, all.average, fxlsm_prof_time_running());
}
#else /* FXLSM_USE_PROF_TIME */
#define LIST_SHOW_TIME(buf, off, hit, mis, __hit, __mis) \
	list_show_time(buf, off)
#define LIST_SHOW_TIME_TOTAL(buf, off, __hit, __mis) \
	list_show_time(buf, off)
static void list_show_time(char *buf, ssize_t *off)
{
	*off += sprintf(buf + *off, "\n");
}
#endif /* !FXLSM_USE_PROF_TIME */

static ssize_t lists_show(struct kobject *kobj,
		struct kobj_attribute *attr, char *buf)
{
	int id;
	struct fxlsm_list_info list;
	struct fxlsm_list_group_info group;
	ssize_t off = 0;
	int id_max = 0;
	unsigned int total_entries = 0;
	FXLSM_PROF_TIME_DEF(hit);
	FXLSM_PROF_TIME_DEF(mis);

	for (id = 0;; id++) {
		if (fxlsm_list_get_info(id, &list))
			break;
		off += sprintf(buf + off,
			"%d: %s %u %d %c",
			id, list.name, list.num_entries,
			list.state, list.search);
		LIST_SHOW_TIME(buf, &off,
			list.prof_time_hit, list.prof_time_mis, NULL, NULL);
		id_max = id;
	}
	off += sprintf(buf + off, "\n");
	for (id = 0;; id++) {
		if (fxlsm_list_get_group_info(id, &group))
			break;
		off += sprintf(buf + off,
			"%d-%d: %s %u %d x",
			group.list_id_from, group.list_id_to, group.name,
			group.num_entries, group.state);
		LIST_SHOW_TIME(buf, &off,
			group.prof_time_hit, group.prof_time_mis, &hit, &mis);
		total_entries += group.num_entries;
	}
	off += sprintf(buf + off, "\n");
	off += sprintf(buf + off, "0-%d: total %u 1 x", id_max, total_entries);
	LIST_SHOW_TIME_TOTAL(buf, &off, &hit, &mis);
	return off;
}

static struct kobj_attribute lists_attr =
	__ATTR(lists, 0440, lists_show, NULL);

#ifdef FXLSM_USE_PROF_TIME
static ssize_t print_runtime_rate(
		char *buf, unsigned long long num, unsigned long long den)
{
	unsigned long tt = num;
	int i, quot = 0;
	char *zeros;
	ssize_t off;

	if (!den)
		return sprintf(buf, "0");
	tt = num;
	for (i = 0;; i++) {
		quot = (tt + (den - 1)) / den;
		if (quot > 9)
			break;
		tt *= 10;
	}
	if (i == 0)
		return sprintf(buf, "%d", quot);
	if (i == 1)
		return sprintf(buf, "%d.%d", quot / 10, quot % 10); 
	if (i == 2)
		return sprintf(buf, "0.%d", quot); 
	i += 2;
	zeros = (char *)kmalloc(i + 1, GFP_KERNEL);
	if (!zeros)
		return 0;
	memset(zeros, '0', i);
	*(zeros + i) = 0;
	off = sprintf(buf, "0.%s%d", zeros, quot);
	kfree(zeros);
	return off;
}

static ssize_t hooks_show(struct kobject *kobj,
		struct kobj_attribute *attr, char *buf)
{
	struct fxlsm_prof_time_id *id;
	struct fxlsm_prof_time time;
	FXLSM_PROF_TIME_DEF(total);
	unsigned long long runtime = fxlsm_prof_time_running();
	ssize_t off = 0;

	FXLSM_PROF_TIME_ID_FOR_EACH(fxlsm_ops_prof_times, id) {
		fxlsm_prof_time_read(id->id, &time);
		off += sprintf(buf + off, "%s: %llu/%lu=%llu\n",
			id->name, time.time, time.count, time.average);
		total.time += time.time;
		total.count += time.count;
	}
	fxlsm_prof_time_average(&total);
	off += sprintf(buf + off, "total: %llu/%lu=%llu\n\n",
		total.time, total.count, total.average);

	FXLSM_PROF_TIME_ID_FOR_EACH(fxlsm_ops_prof_funcs, id) {
		fxlsm_prof_time_read(id->id, &time);
		off += sprintf(buf + off, "%s: %llu/%lu=%llu\n",
			id->name, time.time, time.count, time.average);
	}
	off += sprintf(buf + off, "\n");
	FXLSM_PROF_TIME_ID_FOR_EACH(fxlsm_ops_prof_sums, id) {
		fxlsm_prof_time_read(id->id, &time);
		off += sprintf(buf + off, "%s: %llu/%lu=%llu\n",
			id->name, time.time, time.count, time.average);
	}
	off += sprintf(buf + off, "\nruntime: %llu/%llu=",
		total.time, runtime);
	off += print_runtime_rate(buf + off, total.time, runtime);
	off += sprintf(buf + off, "\n");
	return off;
}

static struct kobj_attribute hooks_attr =
	__ATTR(hooks, 0440, hooks_show, NULL);
#endif /* FXLSM_USE_PROF_TIME */

#ifdef FXLSM_USE_PROF_TIME
static ssize_t time_reset_store(struct kobject *kobj,
		struct kobj_attribute *attr, const char *buf, size_t count)
{
	int val = bool_val(buf);

	if (!val)
		return -EINVAL;
	fxlsm_prof_time_reset();
	return count;
}

static struct kobj_attribute time_reset_attr =
	__ATTR(time_reset, 0220, NULL, time_reset_store);
#endif /* FXLSM_USE_PROF_TIME */

#ifdef FXLSM_USE_PROF_MEM
static ssize_t mems_show(struct kobject *kobj,
		struct kobj_attribute *attr, char *buf)
{
	int id;
	struct fxlsm_prof_mem prof;
	ssize_t off = 0;

	for (id = 0; id < FXLSM_PROF_MEM_NUM; id++) {
		FXLSM_PROF_MEM_READ(&prof, id);
		off += sprintf(buf + off, "%s: %lu %lu\n", 
			FXLSM_PROF_MEM_NAME(id), prof.bytes, prof.blocks);
	}
	FXLSM_PROF_MEM_TOTAL(&prof);
	off += sprintf(buf + off, "total: %lu %lu\n", prof.bytes, prof.blocks);
	return off;
}

static struct kobj_attribute mems_attr =
	__ATTR(mems, 0440, mems_show, NULL);
#endif /* FXLSM_USE_PROF_MEM */

#ifdef FXLSM_USE_DUMP
static rwlock_t __dump_lock;
static char *__dump_dir;

static void dump_dir_set(const char *s, int len)
{
	int __len = len;
	const char *p;
	char *dir;

	for (p = s + len - 1; p > s ; p--) {
		if (*p != 0 && *p != '\n' && *p != '/')
			break;
	}
	__len = p - s + 1;
	dir = (char *)kmalloc(__len + 1, GFP_KERNEL);

	if (!dir) {
		ERR("kmalloc %d", __len + 1);
		return;
	}
	memcpy(dir, s, __len);
	*(dir + __len) = 0;
	write_lock(&__dump_lock);
	if (__dump_dir)
		kfree(__dump_dir);
	__dump_dir = dir;
	write_unlock(&__dump_lock);
}

static const char *default_dump_dir = "/root";

static inline void dump_dir_init(void)
{
	rwlock_init(&__dump_lock);
	dump_dir_set(default_dump_dir, strlen(default_dump_dir));
}

static void dump_dir_free(void)
{
	write_lock(&__dump_lock);
	if (__dump_dir)
		kfree(__dump_dir);
	__dump_dir = NULL;
	write_unlock(&__dump_lock);
}

static char *dump_dir_copy(void)
{
	int sz;
	char *dir = NULL;

	read_lock(&__dump_lock);
	if (__dump_dir) {
		sz = strlen(__dump_dir) + 1;
		dir = (char *)kmalloc(sz, GFP_KERNEL);
		if (dir)
			strcpy(dir, __dump_dir);
	}
	read_unlock(&__dump_lock);
	return dir;
}

static ssize_t dump_dir_show(struct kobject *kobj,
		struct kobj_attribute *attr, char *buf)
{
	ssize_t off = 0;

	read_lock(&__dump_lock);
	if (__dump_dir)
		off = sprintf(buf, "%s\n", __dump_dir);
	read_unlock(&__dump_lock);
	return off;
}

static ssize_t dump_dir_store(struct kobject *kobj,
		struct kobj_attribute *attr, const char *buf, size_t count)
{
	dump_dir_set(buf, count);
	return count;
}

static struct kobj_attribute dump_dir_attr =
	__ATTR(dump_dir, 0660, dump_dir_show, dump_dir_store);

static ssize_t dump_store(struct kobject *kobj,
		struct kobj_attribute *attr, const char *buf, size_t count)
{
	int id = simple_strtol(buf, NULL, 10);
	char *dir = dump_dir_copy();
	int ret;

	if (!dir)
		return -ENOMEM;
	ret = fxlsm_list_dump(id, dir);
	kfree(dir);
	if (ret)
		return ret;
	return count;
}

static struct kobj_attribute dump_attr =
	__ATTR(dump, 0220, NULL, dump_store);

#define DUMP_DIR_INIT() dump_dir_init()
#define DUMP_DIR_FREE() dump_dir_free()
#else /* FXLSM_USE_DUMP */
#define DUMP_DIR_INIT() do {} while (0)
#define DUMP_DIR_FREE() do {} while (0)
#endif /* !FXLSM_USE_DUMP */

static struct attribute *attrs[] = {
	&enabled_attr.attr,
	&through_attr.attr,
	&capture_attr.attr,
#ifdef FXLSM_USE_CAP_WRITE
	&capture_write_attr.attr,
#endif /* FXLSM_USE_CAP_WRITE */
	&secondary_attr.attr,
	&comms_attr.attr,
	&lds_attr.attr,
	&creatx_attr.attr,
	&chmodx_attr.attr,
	&net_attr.attr,
	&max_capture_attr.attr,
	&max_events_attr.attr,
	&lost_events_attr.attr,
	&lists_attr.attr,
#ifdef FXLSM_USE_PROF_TIME
	&hooks_attr.attr,
	&time_reset_attr.attr,
#endif /* FXLSM_USE_PROF_TIME */
#ifdef FXLSM_USE_PROF_MEM
	&mems_attr.attr,
#endif /* FXLSM_USE_PROF_MEM */
#ifdef FXLSM_USE_DUMP
	&dump_dir_attr.attr,
	&dump_attr.attr,
#endif /* FXLSM_USE_DUMP */
	NULL,   /* need to NULL terminate the list of attributes */
};

static struct attribute_group attr_group = { .attrs = attrs, };

static struct kobject *kobj;

int fxlsm_sysfs_init(void)
{
	int ret;

	DUMP_DIR_INIT();

	kobj = kobject_create_and_add("fxlsm", kernel_kobj);
	if (IS_ERR(kobj)) {
		ret = PTR_ERR(kobj);
		goto err_out;
	}
	ret = sysfs_create_group(kobj, &attr_group);
	if (ret)
		goto err_put;

	return 0;
err_put:
	kobject_put(kobj);
err_out:
	DUMP_DIR_FREE();
	return ret;	
}

void fxlsm_sysfs_exit(void)
{
	DUMP_DIR_FREE();
	kobject_put(kobj);
}
