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

#include "fxlsm_namespace.h"
#include "fxlsm_log.h"
#ifndef FXLSM_LIST_USER
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/version.h>
#include <linux/proc_ns.h>

#ifdef FXLSM_LOADABLE
#define FXLSM_DEF_ADR_NS_OPS(name) \
static unsigned long name; \
module_param( name , ulong , S_IRUGO ); \
MODULE_PARM_DESC( name , #name );

FXLSM_DEF_ADR_NS_OPS(adr_netns_operations);
FXLSM_DEF_ADR_NS_OPS(adr_utsns_operations);
FXLSM_DEF_ADR_NS_OPS(adr_ipcns_operations);
FXLSM_DEF_ADR_NS_OPS(adr_pidns_operations);
FXLSM_DEF_ADR_NS_OPS(adr_userns_operations);
FXLSM_DEF_ADR_NS_OPS(adr_mntns_operations);
FXLSM_DEF_ADR_NS_OPS(adr_cgroupns_operations);

struct fxlsm_ns_ops {
	unsigned char ns;
	unsigned long *ops;
};

static struct fxlsm_ns_ops ns_opss[] = {
	{FXLSM_NS_NET, &adr_netns_operations},
	{FXLSM_NS_UTS, &adr_utsns_operations},
	{FXLSM_NS_IPC, &adr_ipcns_operations},
	{FXLSM_NS_PID, &adr_pidns_operations},
	{FXLSM_NS_USER, &adr_userns_operations},
	{FXLSM_NS_MNT, &adr_mntns_operations},
	{FXLSM_NS_CGROUP, &adr_cgroupns_operations},
	{0, NULL}
};

static struct proc_ns_operations *find_ns_ops(unsigned char ns)
{
	int i;
	unsigned long adr = 0;

	for (i = 0; !adr && ns_opss[i].ns; i++) {
		if (ns_opss[i].ns == ns)
			adr = *(ns_opss[i].ops);
	}
	return (struct proc_ns_operations *)adr;
}
#else /* FXLSM_LOADABLE */
struct fxlsm_ns_ops {
	unsigned char ns;
	struct proc_ns_operations *ops;
};

static struct fxlsm_ns_ops ns_opss[] = {
#ifdef CONFIG_NET_NS
	{FXLSM_NS_NET, &netns_operations},
#else
	{FXLSM_NS_NET, NULL},
#endif
#ifdef CONFIG_UTS_NS
	{FXLSM_NS_UTS, &utsns_operations},
#else
	{FXLSM_NS_UTS, NULL},
#endif
#ifdef CONFIG_IPC_NS
	{FXLSM_NS_IPC, &ipcns_operations},
#else
	{FXLSM_NS_IPC, NULL},
#endif
#ifdef CONFIG_PID_NS
	{FXLSM_NS_PID, &pidns_operations},
#else
	{FXLSM_NS_PID, NULL},
#endif
#ifdef CONFIG_USER_NS
	{FXLSM_NS_USER, &userns_operations},
#else
	{FXLSM_NS_USER, NULL},
#endif
	{FXLSM_NS_MNT, &mntns_operations},
#ifdef CONFIG_CGROUPS
	{FXLSM_NS_CGROUP, &cgroupns_operations},
#else
	{FXLSM_NS_CGROUP, NULL},
#endif
	{0, NULL}
};

static struct proc_ns_operations *find_ns_ops(unsigned char ns)
{
	int i;

	for (i = 0; !adr && ns_opss[i].ns; i++) {
		if (ns_opss[i].ns == ns)
			reurtn ns_opss[i].ops;
	}
	return NULL;
}
#endif /* !FXLSM_LOADABLE */
#endif /* !FXLSM_LIST_USER */

struct fxlsm_ns_name {
	const char *name;
	int nlen;
	unsigned char ns;
};

static struct fxlsm_ns_name names[] = {
	{"net", 3, FXLSM_NS_NET},
	{"uts", 3, FXLSM_NS_UTS},
	{"ipc", 3, FXLSM_NS_IPC},
	{"pid", 3, FXLSM_NS_PID},
	{"user", 4, FXLSM_NS_USER},
	{"mnt", 3, FXLSM_NS_MNT},
	{"cgroup", 6, FXLSM_NS_CGROUP},
	{NULL, 0, 0}
};

#define NAMESPACE_NAME "namespace"
#define NAMESPACE_NLEN 9

static int __find_ns(unsigned char ns)
{
	int i;

	for (i = 0; names[i].name; i++) {
		if (ns == names[i].ns)
			return i;
	}
	return -1;
}

static int __find_name(const char *name, int nlen)
{
	int i;

	for (i = 0; names[i].name; i++) {
		if (nlen == names[i].nlen &&
			!memcmp(name, names[i].name, nlen))
			return i;
	}
	return -1;
}

int fxlsm_parse_namespace(unsigned char *ns, const char *s, int len)
{
	const char *w = s;
	int rem = len;
	const char *e;
	int wlen;
	int first = 1;
	int i;
	int ret = 0;

	/*
	 * Don't clear *ns.
	 */ 
	while (rem > 0) {
		e = memchr(w, ':', rem);
		if (!e)
			wlen = rem;
		else
			wlen = e - w;
		if (first) {
			if (wlen != NAMESPACE_NLEN ||
				memcmp(w, NAMESPACE_NAME, NAMESPACE_NLEN))
				break;
			first = 0;
			ret = 1;
		} else if (wlen > 0) {
			i = __find_name(w, wlen);
			if (i >= 0)
				*ns |= names[i].ns;
		}
		w = e + 1;
		rem -= (wlen + 1);
	}
	return ret;
}

int __fxlsm_ns_string(unsigned char ns, char *buf, int len)
{
	return snprintf(buf, len, "namespace%s%s%s%s%s%s%s",
		(ns & FXLSM_NS_NET) ? ":net" : "",
		(ns & FXLSM_NS_UTS) ? ":uts" : "",
		(ns & FXLSM_NS_IPC) ? ":ipc" : "",
		(ns & FXLSM_NS_PID) ? ":pid" : "",
		(ns & FXLSM_NS_USER) ? ":user" : "",
		(ns & FXLSM_NS_MNT) ? ":mnt" : "",
		(ns & FXLSM_NS_CGROUP) ? ":cgroup" : "");
}

static int find_name(struct fxlsm_string *path)
{
	const char *w = path->s;
	const char *e;
	int wlen;

	e = strchr(w, ':');
	if (!e)
		return -1;
	wlen = e - w;
	return __find_name(w, wlen);
}

static int get_inum(struct fxlsm_string *path, unsigned int *inum)
{
	const char *w, *e;
	int wlen;
#define MAX_INUM_LEN 20
	char buf[MAX_INUM_LEN + 1];

	w = strchr(path->s, '[');
	if (!w)
		return -1;
	++w;
	e = strchr(w, ']');
	if (!e)
		return -1;
	wlen = e - w;
	if (wlen <= 0 || wlen > MAX_INUM_LEN)
		return -1;
	memcpy(buf, w, wlen);
	*(buf + wlen) = 0;
	*inum = simple_strtoul(buf, NULL, 10);
	return 0;
}

int __fxlsm_is_namespace(struct fxlsm_string *path, unsigned int *inum)
{
	int index;

	index = find_name(path);
	if (index < 0)
		return 0;
	if (get_inum(path, inum))
		return 0;
	return names[index].ns;
}

const char *ns2name(unsigned char ns)
{
	int index = __find_ns(ns);

	if (index < 0)
		return "?";
	else return names[index].name;
}

#ifdef FXLSM_LIST_USER
static int get_cur_inum(unsigned char ns, unsigned int *inum)
{
#define PATH_LEN 128
	int len;
	char path[PATH_LEN];
	char link[PATH_LEN + 1];
	struct fxlsm_string __link;

	snprintf(path, PATH_LEN, "/proc/%d/ns/%s", getpid(), ns2name(ns));
	len = readlink(path, link, PATH_LEN); 
	if (len < 0) {
		ERR("readlink %s", path);
		return -1;
	}
	*(link + len) = 0;
	__link.s = link;
	__link.len = len;
	return get_inum(&__link, inum);
}
#else /* FXLSM_LIST_USER */
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,19,0)
static int get_cur_inum(unsigned char ns, unsigned int *inum)
{
	struct proc_ns_operations *ns_ops = find_ns_ops(ns);
	struct ns_common *__ns;

	if (!ns_ops) {
		WRN("no proc_ns_operation for %s", ns2name(ns));
		return -1;
	}
	__ns = ns_ops->get(current);
	*inum = __ns->inum;
	ns_ops->put(__ns);
	return 0;
}
#else /* LINUX_VERSION_CODE >= KERNEL_VERSION(3,19,0) */
static int get_cur_inum(unsigned char ns, unsigned int *inum)
{
	struct proc_ns_operations *ns_ops = find_ns_ops(ns);
	void *__ns;

	if (!ns_ops) {
		WRN("no proc_ns_operation for %s", ns2name(ns));
		return -1;
	}
	__ns = ns_ops->get(current);
	*inum = ns_ops->inum(__ns);
	ns_ops->put(__ns);
	return 0;
}
#endif /* ! LINUX_VERSION_CODE >= KERNEL_VERSION(3,19,0) */
#endif /* !FXLSM_LIST_USER */

int fxlsm_ns_is_cur_inum(unsigned char ns, unsigned int inum)
{
	unsigned int cur_inum;

	if (get_cur_inum(ns, &cur_inum)) {
		WRN("failed to get cur inum");
		return 0;
	}
	if (inum != cur_inum) {
		WRN("not cur inum %u for %s %u", inum, ns2name(ns), cur_inum);
		return 0;
	}
	return 1;
}

static int match(struct fxlsm_entry *e,
		unsigned char acc, unsigned char *__acc,
		struct fxlsm_find_params *par, unsigned int cur_inum)
{
	if (fxlsm_get_type(e) == FXLSM_TYPE_NS &&
		(e->acc & acc) &&
		e->ns == par->ns &&
		cur_inum == par->inum &&
		!strcmp(e->string, par->comm)) {
		*__acc = e->acc & acc;
		return 1;
	}
	return 0;
}

struct fxlsm_entry *fxlsm_ns_list_find(struct fxlsm_list *list,
		unsigned char acc, unsigned char *__acc,
		struct fxlsm_find_params *par)
{
	unsigned int cur_inum;
	struct fxlsm_entry *pos;

	if (get_cur_inum(par->ns, &cur_inum))
		return NULL;

	list_for_each_entry(pos, &list->list, node) {
		if (match(pos, acc, __acc, par, cur_inum)) {
			DBG("matched namespace %s[%u] in list %s %u",
				ns2name(par->ns), par->inum,
				list->name, pos->nr);
			return pos;
		}
	}
	return NULL;
}

struct fxlsm_entry *fxlsm_ns_array_find(struct fxlsm_arrays *arrays,
		unsigned char acc, unsigned char *__acc,
		struct fxlsm_find_params *par)
{
	struct fxlsm_array *array =
		arrays2array(arrays, FXLSM_ARRAY_LOST_FOUND);
	unsigned int cur_inum;
	struct fxlsm_entry *e;
	int i;

	if (get_cur_inum(par->ns, &cur_inum))
		return NULL;

	for (i = 0; i < array->num; i++) {
		e = *(array->entries + i);
		if (match(e, acc, __acc, par, cur_inum)) {
			DBG("matched namespace %s[%u] in array %s %u",
				ns2name(par->ns), par->inum,
				arrays->list->name, e->nr);
			return e;
		}
	}
	return NULL;
}
