/*
 * (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
 *
 * 2017/12/15	nobuo.iwata@fujixerox.co.jp
 */

#include "fxlsm_list_internal.h"
#include "fxlsm_array.h"
#include "fxlsm_namespace.h"
#include "fxlsm_prof.h"
#include "fxlsm_log.h"
#ifdef FXLSM_LOADABLE
#include <linux/module.h>
#endif /* FXLSM_LOADABLE */
#ifndef FXLSM_LIST_USER
#include "fxlsm_ops.h"
#include <linux/slab.h>
#include <linux/fs.h>
#include <linux/ctype.h>
#include <linux/version.h>
#include <asm/uaccess.h>
#endif /* !FXLSM_LIST_USER */

static unsigned long flags;

#define FXLSM_SECONDARY		0
#define FXLSM_CAPTURE_LOAD	1

static inline int is_secondary_enabled(void)
{
	return test_bit(FXLSM_SECONDARY, &flags);
}

int fxlsm_get_secondary(void)
{
	return is_secondary_enabled();
}

int fxlsm_set_secondary(int stat)
{
	if (stat)
		return test_and_set_bit(FXLSM_SECONDARY, &flags);
	return test_and_clear_bit(FXLSM_SECONDARY, &flags);
}

static inline int is_capture_load_enabled(void)
{
	return test_bit(FXLSM_CAPTURE_LOAD, &flags);
}

int fxlsm_set_capture_load(int stat)
{
	if (stat)
		return test_and_set_bit(FXLSM_CAPTURE_LOAD, &flags);
	return test_and_clear_bit(FXLSM_CAPTURE_LOAD, &flags);
}

#define LINEBUFLEN      512
#define LINESZ_INIT     1024
#define LINESZ_ADD      1024

struct line_st {
	char *line;
	unsigned int nr;
	char buf[LINEBUFLEN];
	int buf_i, buf_n;
	int line_n, line_s;
};

static void getline_init(struct line_st *st)
{
	st->line = NULL;
	st->nr = 0;
	st->buf_i = st->buf_n = 0;
}

static void getline_exit(struct line_st *st)
{
	if (st->line)
		kfree(st->line);
}

#ifndef FXLSM_LIST_USER
static inline ssize_t kread(struct file *fp, void *buf, size_t count)
{
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,14,0)
	return kernel_read(fp, buf, count, &fp->f_pos);
#else
	mm_segment_t fs;
	ssize_t ret;

	fs = get_fs();
	set_fs(get_ds());
	ret = vfs_read(fp, (char __user *)buf, count, &fp->f_pos);
	set_fs(fs);
	return ret;
#endif
}

#if defined(FXLSM_USE_DUMP) || defined(FXLSM_USE_CAP_WRITE)
static int kwrite(struct file *fp, void *buf, size_t count)
{
	ssize_t ret;

#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,14,0)
	ret = kernel_write(fp, buf, count, &fp->f_pos);
#else
	mm_segment_t fs;

	fs = get_fs();
	set_fs(get_ds());
	ret = vfs_write(fp, (__force const char __user *)buf,
			count, &fp->f_pos);
	set_fs(fs);
#endif
	if (ret != count) {
		ERR("kwrite %zd %zd", count, ret);
		return -1;
	}
	return 0;
}
#endif /* FXLSM_USE_DUMP || FXLSM_USE_CAP_WRITE */
#endif /* !FXLSM_LIST_USER */

static int getline_read(struct line_st *st, struct file *fp)
{
	int cont = 1;
	int len;
	char *p;

	if (!st->line) {
		st->line_s = LINESZ_INIT;
		st->line = (char *)kmalloc(st->line_s, GFP_KERNEL);
		if (!st->line) {
			ERR("alloc %d", st->line_s);
			return -ENOMEM;
		}
	}
	st->line_n = 0;

	while (cont) {
		if (st->buf_n <= st->buf_i) {
			st->buf_n = kread(fp, st->buf, LINEBUFLEN);
			if (st->buf_n <= 0) {
				if (st->line_n > 0)
					goto out;
				return st->buf_n;
			}
			st->buf_i = 0;
		}
		p = (char *)memchr(st->buf + st->buf_i, '\n',
				 st->buf_n - st->buf_i);
		if (p) {
			len = p - (st->buf + st->buf_i) + 1;
			cont = 0;
		} else {
			len = st->buf_n - st->buf_i;
		}
		 if (len + 1 > st->line_s - st->line_n) {
			st->line = (char *)krealloc(st->line,
				st->line_s + LINESZ_ADD, GFP_KERNEL);
			if (IS_ERR(st->line)) {
				ERR("realloc %d+%d", st->line_s, LINESZ_ADD);
				return PTR_ERR(st->line);
			}
			st->line_s += LINESZ_ADD;
		}
		memcpy(st->line + st->line_n, st->buf + st->buf_i, len);
		st->buf_i += len;
		st->line_n += len;
	}
out:
	*(st->line + st->line_n) = 0;
	++(st->nr);
	return st->line_n;
}

static inline int __isspace(const char *p)
{
	return isspace(*p) || (*p == 0);
}

static inline int __isend(const char *p)
{
	return (*p == 0) || (*p == '#');
}

struct word {
	const char *start;
	int len;
};

static int get_words(const char *line, struct word words[], int max)
{
	const char *p;
	int in = 0, count = 0;

	for (p = line; count < max; p++) {
		if (in) {
			if (__isspace(p)) {
				words[count].len = p - words[count].start;
				count++;
				if (__isend(p))
					break;
				else
					in = 0;
			}
		} else {
			if (!__isspace(p)) {
				words[count].start = p;
				in = 1;
			} else if (__isend(p)) {
				break;
			}
		}
	}
	return count;
}

static inline size_t entry_size(int len)
{
	return sizeof(struct fxlsm_entry) + len + 1;
}

static struct fxlsm_entry *alloc_entry(
		struct fxlsm_list *list, const char *s, int len, int add_slash)
{
	struct fxlsm_entry *e;
	int slen = add_slash ? 1 : 0;
	size_t sz = entry_size(len + slen);

	e = (struct fxlsm_entry *)kmalloc(sz, GFP_KERNEL);
	if (!e) {
		ERR("malloc %zu", sz);
		return (struct fxlsm_entry *)ERR_PTR(-ENOMEM);
	}
	FXLSM_PROF_MEM_ADD(sz, list->prof_mem_id);
	memset(e, 0, sizeof(struct fxlsm_entry));
	memcpy(e->string, s, len);
	if (slen)
		*(e->string + len) = '/';
	*(e->string + len + slen) = 0;
	e->len = len + slen;
	return e;
}

void fxlsm_entry_free(struct fxlsm_list *list, struct fxlsm_entry *e)
{
	FXLSM_PROF_MEM_SUB(entry_size(e->len), list->prof_mem_id);
	kfree(e);
}

char fxlsm_mode2c(unsigned char mode)
{
	if (mode == FXLSM_MODE_BLACK)
		return '-';
	return '+';
}

static inline char mode_char(struct fxlsm_entry *e)
{
	return fxlsm_mode2c(fxlsm_get_mode(e));
}

static void get_mode(unsigned char *mode, unsigned char flag,
		const char *name, unsigned int nr)
{
	if (!mode)
		return;

	if (*mode) {
		if (name)
			WRN("duplicate mode: orig=%c ignored=%c %s %u",
				fxlsm_mode2c(*mode), fxlsm_mode2c(flag),
				name, nr);
		return;
	}
	*mode = flag;
}

static char acc2c(unsigned char acc)
{
	if (acc == FXLSM_ACC_CREATE)
		return 'c';
	if (acc == FXLSM_ACC_DELETE)
		return 'd';
	if (acc == FXLSM_ACC_READ)
		return 'r';
	if (acc == FXLSM_ACC_WRITE)
		return 'w';
	if (acc == FXLSM_ACC_MODIFY)
		return 'm';
	if (acc == FXLSM_ACC_EXEC)
		return 'x';
	return '?';
}

static void get_acc(unsigned char *acc, unsigned char flag,
		const char *name, unsigned int nr)
{
	if (!acc)
		return;

	if (*acc & flag) {
		if (name)
			WRN("duplicate acc %c: %s %u", acc2c(flag), name, nr);
		return;
	} 
	*acc |= flag;
}

void fxlsm_get_acc_mode(unsigned char *acc, unsigned char *mode,
		const char *s, int len, const char *name, unsigned int nr)
{
	const char *end = s + len - 1;
	const char *p;

	for (p = s; p <= end; p++) {
		switch(*p) {
		case '+':
			get_mode(mode, FXLSM_MODE_WHITE, name, nr);
			break;
		case '-':
			get_mode(mode, FXLSM_MODE_BLACK, name, nr);
			break;
		case 'C':
		case 'c':
			get_acc(acc, FXLSM_ACC_CREATE, name, nr);
			break;
		case 'D':
		case 'd':
			get_acc(acc, FXLSM_ACC_DELETE, name, nr);
			break;
		case 'R':
		case 'r':
			get_acc(acc, FXLSM_ACC_READ, name, nr);
			break;
		case 'W':
		case 'w':
			get_acc(acc, FXLSM_ACC_WRITE, name, nr);
			break;
		case 'M':
		case 'm':
			get_acc(acc, FXLSM_ACC_MODIFY, name, nr);
			break;
		case 'X':
		case 'x':
			get_acc(acc, FXLSM_ACC_EXEC, name, nr);
			break;
		case '*':
			get_acc(acc, FXLSM_ACC_CREATE, name, nr);
			get_acc(acc, FXLSM_ACC_DELETE, name, nr);
			get_acc(acc, FXLSM_ACC_READ, name, nr);
			get_acc(acc, FXLSM_ACC_WRITE, name, nr);
			get_acc(acc, FXLSM_ACC_MODIFY, name, nr);
			get_acc(acc, FXLSM_ACC_EXEC, name, nr);
			break;
		case '.':
			break;
		default:
			if (name)
				WRN("unknown acc char %c: %s %u", *p, name, nr);
		}
	}
	if (!acc)
		WRN("no valid mode: %s %u", name, nr);
}

static const char *type2s(unsigned int type)
{
	switch (type) {
	case FXLSM_TYPE_PATH:
		return "PATH";
	case FXLSM_TYPE_DPATH:
		return "DPATH";
	case FXLSM_TYPE_DIR:
		return "DIR";
	case FXLSM_TYPE_FILE:
		return "FILE";
	case FXLSM_TYPE_SFILE:
		return "SFILE";
	case FXLSM_TYPE_NS:
		return "NS";
	}
	return "?";
}

static inline const char *type_s(struct fxlsm_entry *e)
{
	return type2s(fxlsm_get_type(e));
}
static int get_type(
		unsigned char *type, const char *s, int len,
		const char *name, unsigned int nr, unsigned char only)
{
	const char *end = s + len - 1;
	unsigned char __type;

	if (*s == '/') {
		if (*end == '/') {
			if (len >= 2 && *(end - 1) == '/')
				__type = FXLSM_TYPE_DPATH;
			else
				__type = FXLSM_TYPE_DIR;
		} else {
			__type = FXLSM_TYPE_PATH;
		}
	} else {
		__type = FXLSM_TYPE_FILE;
	}
	if (only && __type != only) {
		if (name)
			WRN("unexpected type %s against %s: %s %u",
				type2s(__type), type2s(only), name, nr);
		return -1;
	}
	*type = __type;
	return 0;
}

static inline int is_root_dir(const char *s, int len, unsigned char type)
{
	if (*s == '/' && len == 1 && type == FXLSM_TYPE_DIR)
		return 1;
	return 0;
}

#define FXLSM_SFILE_WORD "sfile"
#define	FXLSM_SFILE_WORD_LEN 5

static inline int is_sfile_specified(const char *s, int len)
{
	if (len == FXLSM_SFILE_WORD_LEN &&
		memcmp(s, FXLSM_SFILE_WORD, len) == 0)
		return 1;
	return 0;
}

static int __add_entry(struct fxlsm_list *list, struct line_st *st)
{
#define MIN_WORDS 2
#define MAX_WORDS 3
	struct word words[MAX_WORDS];
	int num_words;
	unsigned char type = 0;
	unsigned char acc = 0;
	unsigned char mode = 0;
	unsigned char ns = 0;
	struct fxlsm_entry *e;
	const char *name = list->name;

	num_words = get_words(st->line, words, MAX_WORDS);
	if (num_words < MIN_WORDS ||
		(num_words > 0 && *(words[0].start) == '#')) {
		DBG("ignored line: %s %u", name, st->nr);
		return 0;
	}
	if (get_type(&type, words[1].start, words[1].len, name, st->nr,
		list->only))
		return -EINVAL;
	if (is_root_dir(words[1].start, words[1].len, type)) {
#ifdef FXLSM_ACCEPT_ROOT_DIR_ENTRY
		WRN("found root dir entry: %s %u", name, st->nr);
#else
		WRN("ignored root dir entry: %s %u", name, st->nr);
		return 0;
#endif /* !FXLSM_ACCEPT_ROOT_DIR_ENTRY */
	}
	fxlsm_get_acc_mode(&acc, &mode, words[0].start, words[0].len,
		name, st->nr);
	if (num_words > 2) {
		if (is_sfile_specified(words[2].start, words[2].len))
			type = FXLSM_TYPE_SFILE;
		else if (fxlsm_parse_namespace(&ns,
				words[2].start, words[2].len))
			type = FXLSM_TYPE_NS;
	}
	e = alloc_entry(list, words[1].start, words[1].len, 0);
	if (IS_ERR(e))
		return PTR_ERR(e);
	fxlsm_set_mode_type(e, mode, type);
	e->acc = acc;
	e->ns = ns;
	e->nr = st->nr;
	list_add_tail(&e->node, &list->list);
	++(list->num);
	return 0;
}

static void list_free(struct fxlsm_list *list)
{
	struct fxlsm_entry *pos, *n;

	write_lock(&list->lock);
	if (!list->num)
		goto out;
	list_for_each_entry_safe(pos, n, &list->list, node) {
		list_del(&pos->node);
		fxlsm_entry_free(list, pos);
	}
	list->num = 0;
	fxlsm_arrays_free((struct fxlsm_arrays *)list->arrays);
out:
	write_unlock(&list->lock);
}

struct fxlsm_arrays fxlsm_arrays_nseq;
struct fxlsm_arrays fxlsm_arrays_nseq2;
struct fxlsm_arrays fxlsm_arrays_comm;
struct fxlsm_arrays fxlsm_arrays_comm2;

static struct fxlsm_list lists[FXLSM_LIST_NUM] =
{
	{ FXLSM_LIST_DEF("seq", SEQ, FILES) },
	{ FXLSM_LIST_DEF("seq2", SEQ2, FILES),
		.type = FXLSM_LIST_TYPE_SECONDARY },
	{ FXLSM_LIST_DEF("nseq", NSEQ, FILES),
		.arrays = &fxlsm_arrays_nseq },
	{ FXLSM_LIST_DEF("nseq2", NSEQ2, FILES),
		.type = FXLSM_LIST_TYPE_SECONDARY,
		.arrays = &fxlsm_arrays_nseq2 },
	{ FXLSM_LIST_DEF("capf", CAPF, FILES),
		.type = FXLSM_LIST_TYPE_CAPTURE },
	{ FXLSM_LIST_DEF("comm", COMM, COMMS),
		.only = FXLSM_TYPE_FILE,
		.arrays= &fxlsm_arrays_comm },
	{ FXLSM_LIST_DEF("comm2", COMM2, COMMS),
		.type = FXLSM_LIST_TYPE_SECONDARY,
		.only = FXLSM_TYPE_FILE,
		.arrays= &fxlsm_arrays_comm2 },
	{ FXLSM_LIST_DEF("capc", CAPC, COMMS),
		.type = FXLSM_LIST_TYPE_CAPTURE,
		.only = FXLSM_TYPE_FILE },
	{ FXLSM_LIST_DEF("ld", LD, LDS),
		.only = FXLSM_TYPE_PATH },
	{ FXLSM_LIST_DEF("ld2", LD2, LDS),
		.type = FXLSM_LIST_TYPE_SECONDARY,
		.only = FXLSM_TYPE_PATH }
};

static inline struct fxlsm_list *id2list(int id)
{
	return (id < 0 || id >= FXLSM_LIST_NUM) ? NULL : lists + id;
}

#define FXLSM_LIST_PATH_SEQ	"/etc/fxlsm/seq.list"
#define FXLSM_LIST_PATH_SEQ2	"/etc/fxlsm/seq2.list"
#define FXLSM_LIST_PATH_NSEQ	"/etc/fxlsm/nseq.list"
#define FXLSM_LIST_PATH_NSEQ2	"/etc/fxlsm/nseq2.list"
#define FXLSM_LIST_PATH_CAPF	"/etc/fxlsm/capf.list"
#define FXLSM_LIST_PATH_COMM	"/etc/fxlsm/comm.list"
#define FXLSM_LIST_PATH_COMM2	"/etc/fxlsm/comm2.list"
#define FXLSM_LIST_PATH_CAPC	"/etc/fxlsm/capc.list"
#define FXLSM_LIST_PATH_LD	"/etc/fxlsm/ld.list"
#define FXLSM_LIST_PATH_LD2	"/etc/fxlsm/ld2.list"

static struct fxlsm_list_paths list_paths =
{
	.paths = {
		FXLSM_LIST_PATH_SEQ,
		FXLSM_LIST_PATH_SEQ2,
		FXLSM_LIST_PATH_NSEQ,
		FXLSM_LIST_PATH_NSEQ2,
		FXLSM_LIST_PATH_CAPF,
		FXLSM_LIST_PATH_COMM,
		FXLSM_LIST_PATH_COMM2,
		FXLSM_LIST_PATH_CAPC,
		FXLSM_LIST_PATH_LD,
		FXLSM_LIST_PATH_LD2
	},
};

#ifdef FXLSM_LOADABLE
static char *fxlsm_list_path_seq = FXLSM_LIST_PATH_SEQ;
static char *fxlsm_list_path_seq2 = FXLSM_LIST_PATH_SEQ2;
static char *fxlsm_list_path_nseq = FXLSM_LIST_PATH_NSEQ;
static char *fxlsm_list_path_nseq2 = FXLSM_LIST_PATH_NSEQ2;
static char *fxlsm_list_path_capf = FXLSM_LIST_PATH_CAPF;
static char *fxlsm_list_path_comm = FXLSM_LIST_PATH_COMM;
static char *fxlsm_list_path_comm2 = FXLSM_LIST_PATH_COMM2;
static char *fxlsm_list_path_capc = FXLSM_LIST_PATH_CAPC;
static char *fxlsm_list_path_ld = FXLSM_LIST_PATH_LD;
static char *fxlsm_list_path_ld2 = FXLSM_LIST_PATH_LD2;
module_param(fxlsm_list_path_seq, charp, S_IRUGO);
MODULE_PARM_DESC(fxlsm_list_path_seq, "fxlsm_list_path_seq");
module_param(fxlsm_list_path_seq2, charp, S_IRUGO);
MODULE_PARM_DESC(fxlsm_list_path_seq2, "fxlsm_list_path_seq2");
module_param(fxlsm_list_path_nseq, charp, S_IRUGO);
MODULE_PARM_DESC(fxlsm_list_path_nseq, "fxlsm_list_path_nseq");
module_param(fxlsm_list_path_nseq2, charp, S_IRUGO);
MODULE_PARM_DESC(fxlsm_list_path_nseq2, "fxlsm_list_path_nseq2");
module_param(fxlsm_list_path_capf, charp, S_IRUGO);
MODULE_PARM_DESC(fxlsm_list_path_capf, "fxlsm_list_path_capf");
module_param(fxlsm_list_path_comm, charp, S_IRUGO);
MODULE_PARM_DESC(fxlsm_list_path_comm, "fxlsm_list_path_comm");
module_param(fxlsm_list_path_comm2, charp, S_IRUGO);
MODULE_PARM_DESC(fxlsm_list_path_comm2, "fxlsm_list_path_comm2");
module_param(fxlsm_list_path_capc, charp, S_IRUGO);
MODULE_PARM_DESC(fxlsm_list_path_capc, "fxlsm_list_path_capc");
module_param(fxlsm_list_path_ld, charp, S_IRUGO);
MODULE_PARM_DESC(fxlsm_list_path_ld, "fxlsm_list_path_ld");
module_param(fxlsm_list_path_ld2, charp, S_IRUGO);
MODULE_PARM_DESC(fxlsm_list_path_ld2, "fxlsm_list_path_ld2");

static void list_init_load(void)
{
	list_paths.paths[0] = fxlsm_list_path_seq;
	list_paths.paths[1] = fxlsm_list_path_seq2;
	list_paths.paths[2] = fxlsm_list_path_nseq;
	list_paths.paths[3] = fxlsm_list_path_nseq2;
	list_paths.paths[4] = fxlsm_list_path_capf;
	list_paths.paths[5] = fxlsm_list_path_comm;
	list_paths.paths[6] = fxlsm_list_path_comm2;
	list_paths.paths[7] = fxlsm_list_path_capc;
	list_paths.paths[8] = fxlsm_list_path_ld;
	list_paths.paths[9] = fxlsm_list_path_ld2;
	return;
}
#endif /* FXLSM_LOADABLE */

static const char *id2path(int id)
{
	return (id < 0 || id >= FXLSM_LIST_NUM) ?  NULL : list_paths.paths[id];
}

static int list_load(int id)
{
	struct fxlsm_list *list = id2list(id);
	const char *path = id2path(id);
	struct file *fp;
	struct line_st *st;
	ssize_t bytes;
	int ret;

	if (!list || !path)
		return 0;

	if ((list->type & FXLSM_LIST_TYPE_CAPTURE) &&
		!is_capture_load_enabled())
		return 0;
	DBG("loading list %s", list->name);
	fp = filp_open(path, O_RDONLY | O_LARGEFILE, 0);
	if (IS_ERR(fp)) {
		ret = PTR_ERR(fp);
		ERR("open %s %d", path, ret);
		return 0;
	}
	st = (struct line_st *)kmalloc(sizeof(struct line_st), GFP_KERNEL);
	if (!st) {
		ERR("alloc %ld", sizeof(struct line_st));
		ret = -ENOMEM;
		goto err_close;
	}
	getline_init(st);
	write_lock(&list->lock);
	while ((bytes = getline_read(st, fp)) > 0) {
		ret = __add_entry(list, st);
		if (ret < 0)
			goto err_rest;
	}
	write_unlock(&list->lock);
	getline_exit(st);
	filp_close(fp, 0);
	kfree(st);
	if (list->arrays)
		fxlsm_arrays_setup(list);
	return 0;
err_rest:
	write_unlock(&list->lock);
	getline_exit(st);
	list_free(list);
	kfree(st);
err_close:
	filp_close(fp, 0);
	return ret;
}

static atomic_t capture = ATOMIC_INIT(0);

int fxlsm_will_capture(unsigned char acc)
{
	return ((atomic_read(&capture) & acc) != 0);
}

static atomic_t max_capture = ATOMIC_INIT(0);

int fxlsm_get_max_capture(void)
{
	return atomic_read(&max_capture);
}

void fxlsm_set_max_capture(int max)
{
	atomic_set(&max_capture, max);
}

static void may_capture(struct fxlsm_list_group *group,
		unsigned char acc, unsigned char __acc,
		struct fxlsm_find_params *par, int *ret)
{
	struct fxlsm_list *list = id2list(group->capture);
	struct fxlsm_string s;
	int __max_capture;
	int valid_access = 0;
	unsigned char type = 0;
	int add_slash = 0;
	struct fxlsm_entry *e;

	if (unlikely(!list))
		return;
	if (!*ret)	/* already valid */
		return;
	if (__acc)	/* alredy matched some entry */
		return;
	if (!(atomic_read(&capture) & acc)) /* not enabled nor match capture */
		return;
	if (test_bit(FXLSM_LIST_BIT_IN_CAPTURE, &list->flags)) {
		WRN("recursive capture");
		return;
	}
	__max_capture = fxlsm_get_max_capture();
	if (__max_capture >= 0 && list->num >= fxlsm_get_max_capture())
		return;
	switch (group->id) {
	case FXLSM_LIST_GROUP_FILES:
		if (par->ns) {
			if (!fxlsm_ns_is_cur_inum(par->ns, par->inum))
				return;
			type = FXLSM_TYPE_NS;
			s.s = par->comm;
			s.len = strlen(par->comm);
			valid_access = 1;
		} else if (fxlsm_is_valid_path(&par->path)) {
			s.s = par->path.s;
			s.len = par->path.len;
			valid_access = 1;
		}
		break;
	case FXLSM_LIST_GROUP_COMMS:
		if (fxlsm_is_valid_string(&par->file)) {
			s.s = par->file.s;
			s.len = par->file.len;
			valid_access = 1;
		}
		break;
	}
	if (!valid_access) {
		/*
		 * it may be a SFILE, not captured.
		 */ 
		WRN("ignored void access to capture");
		return;
	}
	if (!type && get_type(&type, s.s, s.len, list->name, 0, list->only))
		return;
	if (type == FXLSM_TYPE_DIR) {
		type = FXLSM_TYPE_DPATH;
		add_slash = 1;
	}
	e = alloc_entry(list, s.s, s.len, add_slash);
	if (!e)
		return;

	set_bit(FXLSM_LIST_BIT_IN_CAPTURE, &list->flags);
	write_lock(&list->lock);
	fxlsm_set_mode_type(e, FXLSM_MODE_WHITE, type);
	e->acc = acc;
	e->ns = par->ns;
	e->nr = ++(list->num); /* must be inside write_lock */
#ifdef FXLSM_USE_PROF_ENTRY
	e->count = 1;
#endif /* FXLSM_USE_PROF_ENTRY */
	list_add_tail(&e->node, &list->list);
	write_unlock(&list->lock);
	clear_bit(FXLSM_LIST_BIT_IN_CAPTURE, &list->flags);
	DEBUG_ENTRY_PRINT("captured: ", e);
	*ret = 0;
}

static inline int is_capture_enabled(void)
{
	return (atomic_read(&capture) != 0);
}

void fxlsm_free_secondary(void)
{
	int id;
	struct fxlsm_list *list;

	if (is_secondary_enabled())
		return;
        for (id = 0; id < FXLSM_LIST_NUM; id++) {
		list = id2list(id);
		if (list->type & FXLSM_LIST_TYPE_SECONDARY)
			list_free(list);
	}
}

static int get_display_state(struct fxlsm_list *list)
{
	if (list->group == FXLSM_LIST_GROUP_COMMS && !fxlsm_get_comms())
		return 0;
	if (list->group == FXLSM_LIST_GROUP_LDS && !fxlsm_get_lds())
		return 0;
	if (list->type & FXLSM_LIST_TYPE_SECONDARY && !is_secondary_enabled())
		return 0;
	return 1;
}

static inline int is_bsearch(struct fxlsm_list *list)
{
	return (list->arrays != NULL &&
		test_bit(FXLSM_LIST_BIT_ARRAYS_VALID, &list->flags));
}

int fxlsm_list_get_info(int id, struct fxlsm_list_info *info)
{
	struct fxlsm_list *list = id2list(id);

	if (unlikely(!list))
		return -1;
	info->name = list->name;
	info->state = get_display_state(list);
	info->num_entries = list->num;
	info->search = is_bsearch(list) ? 'b' : 's';
#ifdef FXLSM_USE_PROF_TIME
	info->prof_time_hit = list->prof_time_hit;
	info->prof_time_mis = list->prof_time_mis;
#endif /* FXLSM_USE_PROF_TIME */
	return 0;
}

void fxlsm_acc2s(unsigned char acc, char *buf)
{
	*(buf + FXLSM_ACC_SHIFT_CREATE) = (acc & FXLSM_ACC_CREATE) ? 'c' : '.';
	*(buf + FXLSM_ACC_SHIFT_DELETE) = (acc & FXLSM_ACC_DELETE) ? 'd' : '.';
	*(buf + FXLSM_ACC_SHIFT_READ) = (acc & FXLSM_ACC_READ) ? 'r' : '.';
	*(buf + FXLSM_ACC_SHIFT_WRITE) = (acc & FXLSM_ACC_WRITE) ? 'w' : '.';
	*(buf + FXLSM_ACC_SHIFT_MODIFY) = (acc & FXLSM_ACC_MODIFY) ? 'm' : '.';
	*(buf + FXLSM_ACC_SHIFT_EXEC) = (acc & FXLSM_ACC_EXEC) ? 'x' : '.';
	*(buf + FXLSM_NUM_ACCS) = 0;
}

static char capture_string[FXLSM_NUM_ACCS + 1];

const char *fxlsm_get_capture_string(void)
{
	return capture_string;
}

void fxlsm_set_capture(const char *s)
{
	unsigned char acc = 0;

	if (s)
		fxlsm_get_acc_mode(&acc, NULL, s, strlen(s), NULL, 0);
	fxlsm_acc2s(acc, capture_string);
	atomic_set(&capture, acc);
}

#define FXLSM_THIRD_LEN 80

int e2third(struct fxlsm_entry *e, char *buf, int len)
{
	switch (fxlsm_get_type(e)) {
	case FXLSM_TYPE_SFILE:
		strncpy(buf, FXLSM_SFILE_WORD, len);
		return FXLSM_SFILE_WORD_LEN;
	case FXLSM_TYPE_NS:
		return fxlsm_ns_string(e, buf, len);
	} 
	*buf = 0;
	return 0;
}

void fxlsm_entry_print(const char *label, struct fxlsm_entry *e)
{
	char accs[FXLSM_NUM_ACCS + 1];
	char third[FXLSM_THIRD_LEN];

	fxlsm_acc2s(e->acc, accs);
	e2third(e, third, FXLSM_THIRD_LEN);
	PR("%s%c%s %s %s %s",
		label, mode_char(e), accs, type_s(e), e->string, third);
}

#ifdef FXLSM_USE_LIST_PRINT
static void list_print(struct fxlsm_list *list)
{
	struct fxlsm_entry *pos;

	PR("=== list %s (%u) ===", list->name, list->num);
	read_lock(&list->lock);
	list_for_each_entry(pos, &list->list, node) {
		fxlsm_entry_print("", pos);
	}
	if (list->arrays)
		fxlsm_arrays_print((struct fxlsm_arrays *)list->arrays);
	read_unlock(&list->lock);
}

static void list_print_all(void)
{
	int id;

        for (id = 0; id < FXLSM_LIST_NUM; id++)
		list_print(id2list(id));
	PR("=== end ===");
}
#define LIST_PRINT_ALL() list_print_all()
#else /* FXLSM_USE_LIST_PRINT */
#define LIST_PRINT_ALL() do {} while (0)
#endif /* !FXLSM_USE_LIST_PRINT */

#if defined(FXLSM_USE_DUMP) || defined(FXLSM_USE_CAP_WRITE)
static void __list_dump(struct fxlsm_list *list, const char *path)
{
	struct file *fp;
	struct fxlsm_entry *pos;
#define PRE_LEN (FXLSM_NUM_ACCS + 2)
	char pre[PRE_LEN];
	int third_len;
	char third[FXLSM_THIRD_LEN + 1];
	int post_len;
#define POST_LEN (3 + 6 + 20 + 2)
	char post[POST_LEN];
	int ret;

	if (!list->num)
		return;

	/*
	 * open before read_lock!
	 * it may invoke capture which needs write_lock.
	 */ 
	fp = filp_open(path,
		O_WRONLY | O_CREAT | O_TRUNC | O_LARGEFILE |
		O_NOFOLLOW | O_DSYNC, 0660);
	if (IS_ERR(fp)) {
		ret = PTR_ERR(fp);
		ERR("open %s %d", path, ret);
		return;
	}
	read_lock(&list->lock);
	list_for_each_entry(pos, &list->list, node) {
		pre[0] = mode_char(pos);
		fxlsm_acc2s(pos->acc, pre + 1);
		pre[FXLSM_NUM_ACCS + 1] = '\t';
		third_len = e2third(pos, third + 1, FXLSM_THIRD_LEN);
		if (third_len > 0) {
			*third = ' ';
			third_len += 1;
		}
#ifdef FXLSM_USE_PROF_ENTRY
		post_len = snprintf(post, POST_LEN, " # %s %u\n",
			type_s(pos), pos->count);
#else
		post_len = snprintf(post, POST_LEN, " # %s\n",
			type_s(pos));
#endif /* FXLSM_USE_PROF_ENTRY */
		if (kwrite(fp, pre, PRE_LEN) ||
			kwrite(fp, pos->string, pos->len))
			break;
		if (third_len > 0 && kwrite(fp, third, third_len))
			break;
		if (kwrite(fp, post, post_len))
			break;
	}
	read_unlock(&list->lock);
	filp_close(fp, 0);
}

struct fxlsm_list_dump_info {
	struct fxlsm_list *list;
	const char *path;
	int free_path;
};

unsigned long in_list_dump;
#define FXLSM_MAX_LIST_DUMP 3
struct fxlsm_list_dump_info list_dump_infos[FXLSM_MAX_LIST_DUMP];
static int list_dump_num;

static void list_dump(struct work_struct *unused)
{
	struct fxlsm_list_dump_info *info;
	int i;

	for (i = 0; i < list_dump_num; i++) {
		info = list_dump_infos + i;
		__list_dump(info->list, info->path);
		if (info->free_path)
			kfree(info->path);
	};
	clear_bit(0, &in_list_dump);
}

static DECLARE_WORK(list_dump_work, list_dump);
#endif /* FXLSM_USE_DUMP || FXLSM_USE_CAP_WRITE */

#ifdef FXLSM_USE_DUMP
int fxlsm_list_dump(int id, const char *dir)
{
	struct fxlsm_list_dump_info *info = list_dump_infos;
	struct fxlsm_list *list = id2list(id);
	char *path;
	int sz;

	if (unlikely(!list)) {
		WRN("unknown list %d", id);
		return -EINVAL;
	}
	if (test_and_set_bit(0, &in_list_dump)) {
		WRN("list_dump request in list_dump");
		return -EBUSY;
	}
	sz = strlen(dir) + strlen(list->name) + 7;
	path = kmalloc(sz, GFP_KERNEL);
	if (!path) {
		ERR("kmalloc %d", sz);
		clear_bit(0, &in_list_dump);
		return -ENOMEM;
	}
	snprintf(path, sz, "%s/%s.list", dir, list->name);
	info->list = list;
	info->path = path;
	info->free_path = 1;
	list_dump_num = 1;
	schedule_work(&list_dump_work);
	return 0;
}
#endif /* FXLSM_USE_DUMP */

#ifdef FXLSM_USE_CAP_WRITE
int fxlsm_capture_write(void)
{
	int id;
	struct fxlsm_list_dump_info *info = list_dump_infos;
	struct fxlsm_list *list;
	const char *path;

#ifdef FXLSM_PROHIBIT_CAP_WRITE_IN_CAP
	if (fxlsm_capture) {
		WRN("capture should be stopped to write");
		return -EBUSY;
	}
#endif /* FXLSM_PROHIBIT_CAP_WRITE_IN_CAP */
	if (test_and_set_bit(0, &in_list_dump)) {
		WRN("list_dump request in list_dump");
		return -EBUSY;
	}
	list_dump_num = 0;
	for (id = 0; id < FXLSM_LIST_NUM; id++) {
		if (list_dump_num >= FXLSM_MAX_LIST_DUMP) {
			WRN("num caps exceeds max list_dump");
			break;
		}
		list = id2list(id);
		path = id2path(id);
		if (!list || !path ||
			!(list->type & FXLSM_LIST_TYPE_CAPTURE))
			continue;
		info->list = list;
		info->path = path;
		info->free_path = 0;
		++info;
		++list_dump_num;
	}
	schedule_work(&list_dump_work);
	return 0;
}
#endif /* FXLSM_USE_CAP_WRITE */

void fxlsm_list_exit(void)
{
	int id;

        for (id = 0; id < FXLSM_LIST_NUM; id++)
		list_free(id2list(id));
}

#ifdef FXLSM_LIST_USER
void fxlsm_list_set_path(struct fxlsm_list_paths *paths, int id,
		const char *path)
{
	if (id < FXLSM_LIST_NUM)
		paths->paths[id] = path;
}

void fxlsm_list_update_paths(struct fxlsm_list_paths *paths, int nullify)
{
	int i;
	const char *path;

	for (i = 0; i < FXLSM_LIST_NUM; i++) {
		path = paths->paths[i];
		if (nullify || path)
			list_paths.paths[i] = path;
	}
}
#endif /* FXLSM_LIST_USER */

int fxlsm_list_init(void)
{
	int i, ret; 

	for (i = 0; i < FXLSM_LIST_NUM; i++) {
		INIT_LIST_HEAD(&lists[i].list);
		rwlock_init(&lists[i].lock);
	}
#ifdef FXLSM_LOADABLE
	list_init_load();
#endif
	for (i = 0; i < FXLSM_LIST_NUM; i++) {
		ret = list_load(i);
		if (ret)
			goto err_out;
	}
	LIST_PRINT_ALL();
	return 0;
err_out:
	fxlsm_list_exit();
	return ret;
}

static const char *__basename(const char *name, int *lenp)
{
	const char *p, *start = name, *tail;
	int len = *lenp;

	p = (tail = name + len);
	do {
		if (*p == '/') {
			start = p + 1;
			len = (tail - p) - 1;
			break;
		}
	} while (--p >= name);
	*lenp = len;
	return start;
}

static int is_match_path(struct fxlsm_entry *e, struct fxlsm_string *path)
{
	if (fxlsm_is_valid_path(path) &&
		e->len == path->len &&
		memcmp(e->string, path->s, e->len) == 0)
		return 1;
	return 0;
}

static int is_match_dpath(struct fxlsm_entry *e, struct fxlsm_string *path)
{
	int len = e->len - 1;

	if (fxlsm_is_valid_path(path) &&
		len == path->len &&
		memcmp(e->string, path->s, len) == 0)
		return 1;
	return 0;
}

static int is_match_dir(struct fxlsm_entry *e, struct fxlsm_string *path)
{
	if (fxlsm_is_valid_path(path) &&
		e->len <= path->len &&
		memcmp(e->string, path->s, e->len) == 0)
		return 1;
	return 0;
}

static int is_match_file(struct fxlsm_entry *e, struct fxlsm_string *file)
{
	if (fxlsm_is_valid_string(file) &&
		e->len == file->len &&
		memcmp(e->string, file->s, e->len) == 0)
		return 1;
	return 0;
}

static int is_match_sfile(struct fxlsm_entry *e, struct fxlsm_string *path)
{
	if (fxlsm_is_valid_string(path) &&
		e->len == path->len &&
		memcmp(e->string, path->s, e->len) == 0)
		return 1;
	return 0;
}

static int is_match(struct fxlsm_entry *e, const char *name,
		struct fxlsm_find_params *par)
{
	switch (fxlsm_get_type(e)) {
	case FXLSM_TYPE_PATH:
		return is_match_path(e, &par->path);
	case FXLSM_TYPE_DPATH:
		return is_match_dpath(e, &par->path);
	case FXLSM_TYPE_DIR:
		return is_match_dir(e, &par->path);
	case FXLSM_TYPE_FILE:
		return is_match_file(e, &par->file);
	case FXLSM_TYPE_SFILE:
		return is_match_sfile(e, &par->path);
	case FXLSM_TYPE_NS:
		break;
	default:
		ERR("invalid entry to match: %s %u", name, e->nr);
	}
	return 0;
}

struct fxlsm_list_ids {
	int id;
#ifdef FXLSM_USE_PROF_TIME
	int hit;
	int mis;
#endif /* FXLSM_USE_PROF_TIME */
};

static struct fxlsm_entry *list_find(
		int id, unsigned char acc, unsigned char *__acc,
		struct fxlsm_find_params *par,
		int *ret, struct fxlsm_list **__list)
{
	struct fxlsm_list *list = id2list(id);
	struct fxlsm_entry *pos;
	FXLSM_PROF_TIME(time);

	if (unlikely(!list))
		return NULL;
	if (!list->num)
		return NULL;

	if ((list->type & FXLSM_LIST_TYPE_SECONDARY) &&
		!is_secondary_enabled())
		return NULL;

	FXLSM_PROF_TIME_START(time);
	read_lock(&list->lock);
	if (is_bsearch(list)) {
		pos = fxlsm_arrays_find(
			(struct fxlsm_arrays *)list->arrays, acc, __acc, par);
		goto out;
	}
	if (par->ns) {
		pos = fxlsm_ns_list_find(list, acc, __acc, par);
		goto out;
	}
	list_for_each_entry(pos, &list->list, node) {
		if ((pos->acc & acc) &&
			is_match(pos, list->name, par)) {
			*__acc = pos->acc & acc;
			goto out;
		}
	}
	pos = NULL;
out:
	read_unlock(&list->lock);
	FXLSM_PROF_TIME_END(time, pos,
		list->prof_time_hit, list->prof_time_mis);
	if (pos && fxlsm_is_white(pos))
		*ret = 0;
#ifdef FXLSM_USE_PROF_ENTRY
	if (pos)
		++(pos->count);
#endif /* FXLSM_USE_PROF_ENTRY */
	*__list = list;
	return pos;
}

static inline void set_string(struct fxlsm_string *str, const char *s, int len)
{
	str->s= s;
	str->len = (!len) ? strlen(s) : len;
}

#ifdef DEBUG
static inline const char *str2s(struct fxlsm_string *str)
{
	return (str && str->s) ? str->s : "";
}

static inline const char *invalid2s(int ret)
{
	return ret ? "invalid" : "valid";
}

static void debug_list(
		struct fxlsm_list *list, struct fxlsm_entry *e,
		unsigned char acc, int ret, struct fxlsm_find_params *par)
{
	char accs[FXLSM_NUM_ACCS + 1];
	struct fxlsm_string *path = &par->path;
	struct fxlsm_string *file = &par->file;

	if (!ret)
		return;
	fxlsm_acc2s(acc, accs);
	if (e && list)
		DBG("acc=%s path=%s file=%s is %s by %s %u %c %s",
			accs, str2s(path), str2s(file), invalid2s(ret),
			list->name, e->nr, mode_char(e), type_s(e));
	else
		DBG("acc=%s path=%s file=%s is %s",
			accs, str2s(path), str2s(file), invalid2s(ret));
}
#define	DEBUG_LIST debug_list
#else /* DEBUG */
#define	DEBUG_LIST(...) do {} while (0)
#endif /* !DEBUG */

int is_invalid(struct fxlsm_list_group *group,
		unsigned char acc, unsigned char *__acc,
		struct fxlsm_find_params *par)
{
	int *id;
	struct fxlsm_entry *e = NULL;
	struct fxlsm_list *list = NULL;
	int ret = 1;
	FXLSM_PROF_TIME(time);

	FXLSM_PROF_TIME_START(time);
	*__acc = 0;
	for (id = group->ids; !e && *id >= 0; id++) {
		e = list_find(*id, acc, __acc, par, &ret, &list);
	}
	FXLSM_PROF_TIME_END(time, e,
		group->prof_time_hit, group->prof_time_mis);
	DEBUG_LIST(list, e, acc, ret, par);
	may_capture(group, acc, *__acc, par, &ret);
	return ret;
}

static int files_lists[] = { 
	FXLSM_LIST_ID_SEQ,
	FXLSM_LIST_ID_SEQ2,
	FXLSM_LIST_ID_NSEQ,
	FXLSM_LIST_ID_NSEQ2,
	FXLSM_LIST_ID_CAPF,
	-1
};

static struct fxlsm_list_group files = {
	FXLSM_LIST_GROUP_DEF("files", files_lists, FILES, CAPF)
};

int fxlsm_is_invalid(const char *path, int plen, const char *comm,
			unsigned char acc, unsigned char *__acc)
{
	struct fxlsm_find_params par = { .comm = comm };
	const char *fn;
	int len;

	set_string(&par.path, path, plen);
	len = par.path.len;
	fn = __basename(path, &len);
	set_string(&par.file, fn, len);
	par.ns = fxlsm_is_namespace(&par.path, &par.inum);
	return is_invalid(&files, acc, __acc, &par);
}

static int comms_lists[] = { 
	FXLSM_LIST_ID_COMM,
	FXLSM_LIST_ID_COMM2,
	FXLSM_LIST_ID_CAPC,
	-1
};

static struct fxlsm_list_group comms = {
	FXLSM_LIST_GROUP_DEF("comms", comms_lists, COMMS, CAPC)
};

int fxlsm_is_invalid_comm(const char *comm,
		unsigned char acc, unsigned char *__acc)
{
	struct fxlsm_find_params par = { .ns = 0 };

	set_string(&par.file, comm, 0);
	return is_invalid(&comms, acc, __acc, &par);
}

static int lds_lists[] = { 
	FXLSM_LIST_ID_LD,
	FXLSM_LIST_ID_LD2,
	-1
};

static struct fxlsm_list_group lds = {
	FXLSM_LIST_GROUP_DEF("lds", lds_lists, LDS, VOID)
};

int fxlsm_is_invalid_ld(const char *path, int plen, unsigned char *__acc)
{
	struct fxlsm_find_params par = { .ns = 0 };
	unsigned char acc = FXLSM_ACC_EXEC;

	set_string(&par.path, path, plen);
	return is_invalid(&lds, acc, __acc, &par);
}

static int get_group_display_state(int id)
{
	if (id == FXLSM_LIST_GROUP_FILES)
		return 1;
	if (id == FXLSM_LIST_GROUP_COMMS)
		return fxlsm_get_comms();
	if (id == FXLSM_LIST_GROUP_LDS)
		return fxlsm_get_lds();
	return 0;
}

static struct fxlsm_list_group *groups[] = { &files, &comms, &lds, NULL };

static struct fxlsm_list_group *id2group(int id)
{
	struct fxlsm_list_group **p;

	for (p = groups; *p; p++) {
		if ((*p)->id == id)
			return *p;
	}
	return NULL;
}

int fxlsm_list_get_group_info(int id, struct fxlsm_list_group_info *info)
{
	struct fxlsm_list_group *group = id2group(id);
	struct fxlsm_list *list;

	if (unlikely(!group))
		return -1;
	info->name = group->name;
	info->state = get_group_display_state(id);
	info->num_entries = 0;
	info->list_id_from = -1;
	info->list_id_to = -1;
        for (id = 0; id < FXLSM_LIST_NUM; id++) {
		list = id2list(id);
		if (!list)
			continue;
		if (list->group != group->id)
			continue;
                if (info->list_id_from < 0)
                        info->list_id_from = id;
                if (id > info->list_id_to)
                        info->list_id_to = id;
		info->num_entries += list->num;
        }
#ifdef FXLSM_USE_PROF_TIME
	info->prof_time_hit = group->prof_time_hit;
	info->prof_time_mis = group->prof_time_mis;
#endif /* FXLSM_USE_PROF_TIME */
	return 0;
}
