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

#include "fxlsm_ops.h"
#include "fxlsm_list.h"
#include "fxlsm_event.h"
#include "fxlsm_log.h"
#include <linux/sched.h>
#include <linux/slab.h>
#include <linux/mm.h>
#include <linux/fs.h>

static atomic_t enabled = ATOMIC_INIT(0);
static atomic_t through = ATOMIC_INIT(0);
static atomic_t creatx = ATOMIC_INIT(0);
static atomic_t chmodx = ATOMIC_INIT(0);
static unsigned long flags;

#define FXLSM_COMMS	0
#define FXLSM_LDS	1

static inline int calc_enabled(unsigned char acc)
{
	return atomic_read(&enabled) & acc;
}

static inline int calc_through(unsigned char acc)
{
	return atomic_read(&through) & acc;
}

static inline int is_comms_enabled(void)
{
	return test_bit(FXLSM_COMMS, &flags);
}

static inline int is_lds_enabled(void)
{
	return test_bit(FXLSM_LDS, &flags);
}

static inline int is_crmx_enabled(int stat)
{
	return (stat > FXLSM_CRMX_DISABLED);
}

static inline int is_crmx_through(int stat)
{
	return (stat > FXLSM_CRMX_ENABLED);
}

int fxlsm_get_enabled(void)
{
	return atomic_read(&enabled);
}

int fxlsm_get_through(void)
{
	return atomic_read(&through);
}

static char enabled_string[FXLSM_NUM_ACCS + 1];

const char *fxlsm_get_enabled_string(void)
{
	return enabled_string;
}

static char through_string[FXLSM_NUM_ACCS + 1];

const char *fxlsm_get_through_string(void)
{
	return through_string;
}

int fxlsm_get_comms(void)
{
	return is_comms_enabled();
}

int fxlsm_get_lds(void)
{
	return is_lds_enabled();
}

int fxlsm_get_creatx(void)
{
	return atomic_read(&creatx);
}

int fxlsm_get_chmodx(void)
{
	return atomic_read(&chmodx);
}

int fxlsm_set_enabled(const char *s)
{
	unsigned char acc = 0;

	if (s)
		fxlsm_get_acc_mode(&acc, NULL, s, strlen(s), NULL, 0);
	fxlsm_acc2s(acc, enabled_string);
	atomic_set(&enabled, acc);
	return acc;
}

int fxlsm_set_through(const char *s)
{
	unsigned char acc = 0;

	if (s)
		fxlsm_get_acc_mode(&acc, NULL, s, strlen(s), NULL, 0);
	fxlsm_acc2s(acc, through_string);
	atomic_set(&through, acc);
	return acc;
}

int fxlsm_set_comms(int stat)
{
	if (stat)
		return test_and_set_bit(FXLSM_COMMS, &flags);
	return test_and_clear_bit(FXLSM_COMMS, &flags);
}

int fxlsm_set_lds(int stat)
{
	if (stat)
		return test_and_set_bit(FXLSM_LDS, &flags);
	return test_and_clear_bit(FXLSM_LDS, &flags);
}

void fxlsm_set_creatx(int stat)
{
	atomic_set(&creatx, stat);
}

void fxlsm_set_chmodx(int stat)
{
	atomic_set(&chmodx, stat);
}

static inline umode_t dentry2mode(struct dentry *dentry)
{
	struct inode *inode = dentry->d_inode;

	if (inode)
		return inode->i_mode;
	return 0;
}

#define MQUEUE_MAGIC	0x19800202
static inline int path_is_mq(const struct path *path)
{
	struct inode *inode = path->dentry->d_inode;
	struct super_block *sb;

	if (inode && (sb = inode->i_sb))
		return (sb->s_magic == MQUEUE_MAGIC);
	return 0;
}

static inline int path_is_reg(const struct path *path)
{
	umode_t mode = dentry2mode(path->dentry);

	return S_ISREG(mode);
}

static inline int path_is_dir(const struct path *path)
{
	umode_t mode = dentry2mode(path->dentry);

	return S_ISDIR(mode);
}

#define BUFLEN 1024

static char *__path2path(const struct path *path, int *lenp, int is_dir)
{
	char *buf, *p, *s, *tail;
	int alen;
	int slen;
	int plen;
#define DLEN 10

	buf = kmalloc(BUFLEN, GFP_KERNEL);
	if (!buf) {
		ERR("alloc %d", BUFLEN);
		s = ERR_PTR(-ENOMEM);
		goto out;
	}
	p = d_path(path, buf, BUFLEN);
	if (IS_ERR(p)) {
		ERR("d_path %d", BUFLEN);
		s = p;
		goto free_buf;
	}
	plen = ((buf + BUFLEN) - p) - 1; /* plen = strlen(p); */
	if (plen > DLEN && !memcmp(p + plen - DLEN, " (deleted)", DLEN)) {
		plen -= DLEN;
		for (; *(p + plen - 1) == ' ' && plen > 0; plen--);
	}
	if (plen <= 0) {
		ERR("plen %d", plen);
		s = ERR_PTR(-EINVAL);
		goto free_buf;
	}
	slen = (is_dir && *(p + plen - 1) != '/') ? 1 : 0;
	alen = plen + slen + 1;
	s = kmalloc(alen, GFP_KERNEL);
	if (!s) {
		ERR("alloc %d", alen);
		s = ERR_PTR(-ENOMEM);
		goto free_buf;
	}
	memcpy(s, p, plen);
	*lenp = alen - 1;
	tail = s + plen;
	*tail = '/';
	tail += slen;
	*tail = 0;
free_buf:
	kfree(buf);
out:
	return s;
}

static inline char *path2path(const struct path *path, int *lenp)
{
	return __path2path(path, lenp, path_is_dir(path));
}

static inline char *file2path(struct file *file, int *lenp)
{
	return path2path(&file->f_path, lenp);
}

static int __is_valid_ld(
		const char *comm, struct task_struct *task, unsigned long ip)
{
	struct vm_area_struct *vma;
	char *path = NULL;
	int plen;
	int valid = 0;
	unsigned char __acc;
	int ret;

	if (!task->mm || !task->mm->mmap) {
		ERR("invalid mm");
		fxlsm_notify_lds(fxlsm_e_exec, comm, "(invalid mm)", 0);
		return valid;
	}
	for (vma = task->mm->mmap; vma && !valid; vma = vma->vm_next) {
		if (!(vma->vm_flags & VM_EXEC) || !vma->vm_file)
			continue;
		if (path)
			kfree(path);
		path = file2path(vma->vm_file, &plen);
		if (!path)
			continue;
		ret = fxlsm_is_invalid_ld(path, plen, &__acc);
		if (ret && __acc) /* black */
			break;
		else if (!ret) /* white */
			valid = 1;
	}
	if (!valid) {
		/*
		 * The last path may not be meaningless but the last one tends
		 * to be an interpreter.
		 */
		if (path)
			fxlsm_notify_lds(fxlsm_e_exec, comm, path, plen);
		else
			fxlsm_notify_lds(fxlsm_e_exec, comm, "(none)", 0);
	}
	if (path)
		kfree(path);
	return valid;
}

static int is_valid_ld(const char *comm)
{
	struct task_struct *p = current;
	struct pt_regs *regs;
	unsigned long ip;

	regs = task_pt_regs(p);
	if (!regs) {
		ERR("task_pt_regs");
		return 0;
	}
	if (!user_mode(regs)) /* kernel thread */
		return 1;
	ip = instruction_pointer(regs);
	return __is_valid_ld(comm, p, ip);
}

static inline unsigned char flags2rw_acc(int flags)
{
	unsigned char acc = 0;
	int mode = flags & O_ACCMODE;

	if (mode == O_RDONLY || mode == O_RDWR)
		acc |= FXLSM_ACC_READ;
	if (mode == O_WRONLY || mode == O_RDWR)
		acc |= FXLSM_ACC_WRITE;
	return acc;
}

static const char *get_event(unsigned char acc, unsigned char __acc)
{
	unsigned char res = __acc ? __acc : acc;

	if (res & FXLSM_ACC_READ)
		return fxlsm_e_read;
	if (res & FXLSM_ACC_WRITE)
		return fxlsm_e_write;
	if (res & FXLSM_ACC_EXEC)
		return fxlsm_e_exec;
	return fxlsm_e_unknown;
}

static inline int may_through(int ret, unsigned char acc)
{
	if (calc_through(acc))
		return 0;
	return ret;
}

static inline int may_crmx_through(int ret, int stat)
{
	if (is_crmx_through(stat))
		return 0;
	return ret;
}

static inline int check_ld(const char *comm)
{
	int ret = 0;
	FXLSM_PROF_TIME(time);

	if (!is_lds_enabled())
		return 0;
	FXLSM_PROF_TIME_START(time);
	if (!is_valid_ld(comm))
		ret = -EACCES;
	FXLSM_PROF_TIME_END(time, ret,
		FXLSM_PROF_TIME_ID_LDS_ERR, FXLSM_PROF_TIME_ID_LDS_OK);
	FXLSM_PROF_TIME_END_ONE(time, FXLSM_PROF_TIME_ID_LDS);
	return may_through(ret, FXLSM_ACC_EXEC);
}

static inline int check_file(
		const char *path, int plen, const char *comm, unsigned char acc,
		const char *event)
{
	unsigned char __acc;
	const char *__event;
	int ret = 0;
	FXLSM_PROF_TIME(time);

	FXLSM_PROF_TIME_START(time);
	if (fxlsm_is_invalid(path, plen, comm, acc, &__acc)) {
		__event = (event) ? event : get_event(acc, __acc);
		fxlsm_notify_files(__event, comm, path, plen);
		ret = -EACCES;
	}
	FXLSM_PROF_TIME_END(time, ret,
		FXLSM_PROF_TIME_ID_FILES_ERR, FXLSM_PROF_TIME_ID_FILES_OK);
	return may_through(ret, acc);
}

static inline int check_comm(
		const char *path, int plen, const char *comm, unsigned char acc,
		const char *event)
{
	unsigned char __acc;
	const char *__event;
	int ret = 0;
	FXLSM_PROF_TIME(time);

	if (!is_comms_enabled())
		return 0;
	FXLSM_PROF_TIME_START(time);
	if (is_comms_enabled() &&
		fxlsm_is_invalid_comm(comm, acc, &__acc)) {
		__event = (event) ? event : get_event(acc, __acc);
		fxlsm_notify_comms(__event, comm, path, plen);
		ret = -EACCES;
	}
	FXLSM_PROF_TIME_END(time, ret,
		FXLSM_PROF_TIME_ID_COMMS_ERR, FXLSM_PROF_TIME_ID_COMMS_OK);
	return may_through(ret, acc);
}

static int check_file_and_comm(
		const char *path, int plen, const char *comm, unsigned char acc,
		const char *event)
{
	int ret;
	FXLSM_PROF_TIME(time);

	FXLSM_PROF_TIME_START(time);
	ret = check_file(path, plen, comm, acc, event);
	if (!ret)
		ret = check_comm(path, plen, comm, acc, event);
	FXLSM_PROF_TIME_END_ONE(time, FXLSM_PROF_TIME_ID_FILES_COMMS);
	return ret;
}

int fxlsm_file_open(struct file *file, const struct cred *cred)
{
	const char *comm = __comm();
	char *path;
	int plen;
	unsigned char acc, __acc;
	int ret = 0;
	FXLSM_PROF_TIME(time);

	FXLSM_PROF_TIME_START(time);
	if (path_is_mq(&file->f_path))
		goto out;
	if (current->in_execve)
		acc = FXLSM_ACC_EXEC;
	else
		acc = flags2rw_acc(file->f_flags);
	__acc = calc_enabled(acc);
	if (!__acc)
		goto out;
	if (acc == FXLSM_ACC_EXEC) {
		ret = check_ld(comm);
		if (ret)
			goto out;
	}

	path = file2path(file, &plen);
	if (IS_ERR(path)) {
		ret = PTR_ERR(path);
		ERR("file2path %d", ret);
		goto out;
	}
	if (!acc) {
		WRN("unhandled open %s", path);
		goto out_free;
	}
	if (plen == 1 && *path == '/' && !IS_ROOT(file->f_path.dentry)) {
		struct inode *inode = file->f_path.dentry->d_inode;
		WRN("overlay copy ino %ld", (inode) ? inode->i_ino : 0);
		goto out_free;
	}
	ret = check_file_and_comm(path, plen, comm, __acc, NULL);
out_free:
	kfree(path);
out:
	FXLSM_PROF_TIME_END_ONE(time, FXLSM_PROF_TIME_ID_FILE_OPEN);
#if defined(FXLSM_LSM_OLD) && defined(FXLSM_LOADABLE)
	if (!ret && fxlsm_org_file_open)
		return (*fxlsm_org_file_open)(file, cred);
#endif
	return ret;
}

#ifdef CONFIG_SECURITY_PATH
static inline int path_crmx(
		const char *event, const char *path, int plen, const char *comm,
		int stat)
{
	FXLSM_PROF_TIME(time);

	FXLSM_PROF_TIME_START(time);
	fxlsm_notify_crmx(event, comm, path, plen);
	FXLSM_PROF_TIME_END_ONE(time, FXLSM_PROF_TIME_ID_CRMX);
	return may_crmx_through(-EACCES, stat);
}

static int path_create(
		const struct path *dir, struct dentry *dentry, umode_t mode)
{
	const char *comm = __comm();
	struct path __path = { .mnt = dir->mnt, .dentry = dentry };
	char *path;
	int plen;
	int stat = fxlsm_get_creatx();
	int ret = 0;

	if (!calc_enabled(FXLSM_ACC_CREATE))
		goto out;

	path = __path2path(&__path, &plen, S_ISDIR(mode));
	if (IS_ERR(path)) {
		ret = PTR_ERR(path);
		ERR("path2path %d", ret);
		goto out;
	}

	if (is_crmx_enabled(stat) &&
		!fxlsm_will_capture(FXLSM_ACC_CREATE) &&
		S_ISREG(mode) &&
		(mode & (S_IXGRP | S_IXOTH | S_IXUSR))) {
		ret = path_crmx(fxlsm_e_creatx, path, plen, comm, stat);
		if (ret)
			goto out_free;
	}
	ret = check_file_and_comm(path, plen, comm, FXLSM_ACC_CREATE, fxlsm_e_creat);
out_free:
	kfree(path);
out:
	return ret;
}

static int path_delete(const struct path *dir, struct dentry *dentry)
{
	const char *comm = __comm();
	struct path __path = { .mnt = dir->mnt, .dentry = dentry };
	char *path;
	int plen;
	int ret = 0;

	if (!calc_enabled(FXLSM_ACC_DELETE))
		goto out;

	path = path2path(&__path, &plen);
	if (IS_ERR(path)) {
		ret = PTR_ERR(path);
		ERR("path2path %d", ret);
		goto out;
	}
	ret = check_file_and_comm(path, plen, comm, FXLSM_ACC_DELETE, fxlsm_e_delete);
	kfree(path);
out:
	return ret;
}

static int path_modify(
		const struct path *__path, const char *event, umode_t chmod)
{
	const char *comm = __comm();
	char *path;
	int plen;
	int stat = fxlsm_get_chmodx();
	int ret = 0;

	if (!calc_enabled(FXLSM_ACC_MODIFY))
		goto out;

	path = path2path(__path, &plen);
	if (IS_ERR(path)) {
		ret = PTR_ERR(path);
		ERR("path2path %d", ret);
		goto out;
	}
	if (is_crmx_enabled(stat) &&
		!fxlsm_will_capture(FXLSM_ACC_MODIFY) &&
		(chmod & (S_IXGRP | S_IXOTH | S_IXUSR))) {
		ret = path_crmx(fxlsm_e_chmodx, path, plen, comm, stat);
		if (ret)
			goto out_free;
	}
	ret = check_file_and_comm(path, plen, comm, FXLSM_ACC_MODIFY, event);
out_free:
	kfree(path);
out:
	return ret;
}

int fxlsm_path_unlink(FXLSM_PATH_CONST struct path *dir, struct dentry *dentry)
{
	int ret;
	FXLSM_PROF_TIME(time);

	FXLSM_PROF_TIME_START(time);
	ret = path_delete(dir, dentry);
	FXLSM_PROF_TIME_END_ONE(time, FXLSM_PROF_TIME_ID_PATH_UNLINK);
#if defined(FXLSM_LSM_OLD) && defined(FXLSM_LOADABLE)
	if (!ret && fxlsm_org_path_unlink)
		return (*fxlsm_org_path_unlink)(dir, dentry);
#endif
	return ret;
}

int fxlsm_path_mkdir(FXLSM_PATH_CONST struct path *dir, struct dentry *dentry,
		umode_t mode)
{
	int ret;
	FXLSM_PROF_TIME(time);

	FXLSM_PROF_TIME_START(time);
	ret = path_create(dir, dentry, mode | S_IFDIR);
	FXLSM_PROF_TIME_END_ONE(time, FXLSM_PROF_TIME_ID_PATH_MKDIR);
#if defined(FXLSM_LSM_OLD) && defined(FXLSM_LOADABLE)
	if (!ret && fxlsm_org_path_mkdir)
		return (*fxlsm_org_path_mkdir)(dir, dentry, mode);
#endif
	return ret;
}

int fxlsm_path_rmdir(FXLSM_PATH_CONST struct path *dir, struct dentry *dentry)
{
	int ret;
	FXLSM_PROF_TIME(time);

	FXLSM_PROF_TIME_START(time);
	ret = path_delete(dir, dentry);
	FXLSM_PROF_TIME_END_ONE(time, FXLSM_PROF_TIME_ID_PATH_RMDIR);
#if defined(FXLSM_LSM_OLD) && defined(FXLSM_LOADABLE)
	if (!ret && fxlsm_org_path_rmdir)
		return (*fxlsm_org_path_rmdir)(dir, dentry);
#endif
	return ret;
}

int fxlsm_path_mknod(FXLSM_PATH_CONST struct path *dir, struct dentry *dentry,
		umode_t mode, unsigned int dev)
{
	int ret;
	FXLSM_PROF_TIME(time);

	FXLSM_PROF_TIME_START(time);
	ret = path_create(dir, dentry, mode);
	FXLSM_PROF_TIME_END_ONE(time, FXLSM_PROF_TIME_ID_PATH_MKNOD);
#if defined(FXLSM_LSM_OLD) && defined(FXLSM_LOADABLE)
	if (!ret && fxlsm_org_path_mknod)
		return (*fxlsm_org_path_mknod)(dir, dentry, mode, dev);
#endif
	return ret;
}

int fxlsm_path_symlink(FXLSM_PATH_CONST struct path *dir, struct dentry *dentry,
		const char *old_name)
{
	int ret;
	FXLSM_PROF_TIME(time);

	FXLSM_PROF_TIME_START(time);
	ret = path_create(dir, dentry, 0);
	FXLSM_PROF_TIME_END_ONE(time, FXLSM_PROF_TIME_ID_PATH_SYMLINK);
#if defined(FXLSM_LSM_OLD) && defined(FXLSM_LOADABLE)
	if (!ret && fxlsm_org_path_symlink)
		return (*fxlsm_org_path_symlink)(dir, dentry, old_name);
#endif
	return ret;
}

int fxlsm_path_link(struct dentry *old_dentry,
		FXLSM_PATH_CONST struct path *new_dir,
		struct dentry *new_dentry)
{
	int ret;
	FXLSM_PROF_TIME(time);

	FXLSM_PROF_TIME_START(time);
	ret = path_create(new_dir, new_dentry, 0);
	FXLSM_PROF_TIME_END_ONE(time, FXLSM_PROF_TIME_ID_PATH_LINK);
#if defined(FXLSM_LSM_OLD) && defined(FXLSM_LOADABLE)
	if (!ret && fxlsm_org_path_link)
		return (*fxlsm_org_path_link)(old_dentry, new_dir, new_dentry);
#endif
	return ret;
}

int fxlsm_path_rename(FXLSM_PATH_CONST struct path *old_dir,
		struct dentry *old_dentry,
		FXLSM_PATH_CONST struct path *new_dir,
		struct dentry *new_dentry)
{
	umode_t mode = dentry2mode(old_dentry);
	int ret;
	FXLSM_PROF_TIME(time);

	FXLSM_PROF_TIME_START(time);
	ret = path_create(new_dir, new_dentry, mode);
	if (!ret)
		ret = path_delete(old_dir, old_dentry);
	FXLSM_PROF_TIME_END_ONE(time, FXLSM_PROF_TIME_ID_PATH_RENAME);
#if defined(FXLSM_LSM_OLD) && defined(FXLSM_LOADABLE)
	if (!ret && fxlsm_org_path_rename)
		return (*fxlsm_org_path_rename)
				(old_dir, old_dentry, new_dir, new_dentry);
#endif
	return ret;
}

int fxlsm_path_chmod(FXLSM_PATH_CONST struct path *path, umode_t mode)
{
	int ret;
	FXLSM_PROF_TIME(time);

	FXLSM_PROF_TIME_START(time);
	ret = path_modify(path, fxlsm_e_chmod, mode);
	FXLSM_PROF_TIME_END_ONE(time, FXLSM_PROF_TIME_ID_PATH_CHMOD);
#if defined(FXLSM_LSM_OLD) && defined(FXLSM_LOADABLE)
	if (!ret && fxlsm_org_path_chmod)
		return (*fxlsm_org_path_chmod)(path, mode);
#endif
	return ret;
}

int fxlsm_path_chown(FXLSM_PATH_CONST struct path *path,
		kuid_t uid, kgid_t gid)
{
	int ret;
	FXLSM_PROF_TIME(time);

	FXLSM_PROF_TIME_START(time);
	ret = path_modify(path, fxlsm_e_chown, 0);
	FXLSM_PROF_TIME_END_ONE(time, FXLSM_PROF_TIME_ID_PATH_CHOWN);
#if defined(FXLSM_LSM_OLD) && defined(FXLSM_LOADABLE)
	if (!ret && fxlsm_org_path_chown)
		return (*fxlsm_org_path_chown)(path, uid, gid);
#endif
	return ret;
}

int fxlsm_path_chroot(FXLSM_PATH_CONST struct path *path)
{
	int ret;
	FXLSM_PROF_TIME(time);

	FXLSM_PROF_TIME_START(time);
	ret = path_modify(path, fxlsm_e_chroot, 0);
	FXLSM_PROF_TIME_END_ONE(time, FXLSM_PROF_TIME_ID_PATH_CHROOT);
#if defined(FXLSM_LSM_OLD) && defined(FXLSM_LOADABLE)
	if (!ret && fxlsm_org_path_chroot)
		return (*fxlsm_org_path_chroot)(path);
#endif
	return ret;
}

int fxlsm_path_truncate(FXLSM_PATH_CONST struct path *path)
{
	int ret;
	FXLSM_PROF_TIME(time);

	FXLSM_PROF_TIME_START(time);
	ret = path_modify(path, fxlsm_e_truncate, 0);
	FXLSM_PROF_TIME_END_ONE(time, FXLSM_PROF_TIME_ID_PATH_TRUNC);
#if defined(FXLSM_LSM_OLD) && defined(FXLSM_LOADABLE)
	if (!ret && fxlsm_org_path_truncate)
		return (*fxlsm_org_path_truncate)(path);
#endif
	return ret;
}
#endif /* CONFIG_SECURITY_PATH */

#ifdef FXLSM_USE_PROF_TIME
struct fxlsm_prof_time_id fxlsm_ops_prof_times[] = {
	FXLSM_PROF_TIME_ID(FILE_OPEN, "open"),
	FXLSM_PROF_TIME_ID(PATH_UNLINK, "unlink"),
	FXLSM_PROF_TIME_ID(PATH_MKDIR, "mkdir"),
	FXLSM_PROF_TIME_ID(PATH_RMDIR, "rmdir"),
	FXLSM_PROF_TIME_ID(PATH_MKNOD, "mknod"),
	FXLSM_PROF_TIME_ID(PATH_SYMLINK, "symlink"),
	FXLSM_PROF_TIME_ID(PATH_LINK, "link"),
	FXLSM_PROF_TIME_ID(PATH_RENAME, "rename"),
	FXLSM_PROF_TIME_ID(PATH_CHMOD, "chmod"),
	FXLSM_PROF_TIME_ID(PATH_CHOWN, "chown"),
	FXLSM_PROF_TIME_ID(PATH_CHROOT, "chroot"),
	FXLSM_PROF_TIME_ID(PATH_TRUNC, "truncate"),
	FXLSM_PROF_TIME_ID(VOID, "")
};

struct fxlsm_prof_time_id fxlsm_ops_prof_funcs[] = {
	FXLSM_PROF_TIME_ID(FILES_OK, "files(ok)"),
	FXLSM_PROF_TIME_ID(FILES_ERR, "files(err)"),
	FXLSM_PROF_TIME_ID(COMMS_OK, "comms(ok)"),
	FXLSM_PROF_TIME_ID(COMMS_ERR, "comms(err)"),
	FXLSM_PROF_TIME_ID(LDS_OK, "lds(ok)"),
	FXLSM_PROF_TIME_ID(LDS_ERR, "lds(err)"),
	FXLSM_PROF_TIME_ID(CRMX, "crmx(err)"),
	FXLSM_PROF_TIME_ID(VOID, "")
};

struct fxlsm_prof_time_id fxlsm_ops_prof_sums[] = {
	FXLSM_PROF_TIME_ID(FILES_COMMS, "files+comms"),
	FXLSM_PROF_TIME_ID(LDS, "lds"),
	FXLSM_PROF_TIME_ID(VOID, "")
};
#endif /* FXLSM_USE_PROF_TIME */
