162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci *  This file contains functions assisting in mapping VFS to 9P2000
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci *  Copyright (C) 2004-2008 by Eric Van Hensbergen <ericvh@gmail.com>
662306a36Sopenharmony_ci *  Copyright (C) 2002 by Ron Minnich <rminnich@lanl.gov>
762306a36Sopenharmony_ci */
862306a36Sopenharmony_ci
962306a36Sopenharmony_ci#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
1062306a36Sopenharmony_ci
1162306a36Sopenharmony_ci#include <linux/module.h>
1262306a36Sopenharmony_ci#include <linux/errno.h>
1362306a36Sopenharmony_ci#include <linux/fs.h>
1462306a36Sopenharmony_ci#include <linux/sched.h>
1562306a36Sopenharmony_ci#include <linux/cred.h>
1662306a36Sopenharmony_ci#include <linux/parser.h>
1762306a36Sopenharmony_ci#include <linux/slab.h>
1862306a36Sopenharmony_ci#include <linux/seq_file.h>
1962306a36Sopenharmony_ci#include <net/9p/9p.h>
2062306a36Sopenharmony_ci#include <net/9p/client.h>
2162306a36Sopenharmony_ci#include <net/9p/transport.h>
2262306a36Sopenharmony_ci#include "v9fs.h"
2362306a36Sopenharmony_ci#include "v9fs_vfs.h"
2462306a36Sopenharmony_ci#include "cache.h"
2562306a36Sopenharmony_ci
2662306a36Sopenharmony_cistatic DEFINE_SPINLOCK(v9fs_sessionlist_lock);
2762306a36Sopenharmony_cistatic LIST_HEAD(v9fs_sessionlist);
2862306a36Sopenharmony_cistruct kmem_cache *v9fs_inode_cache;
2962306a36Sopenharmony_ci
3062306a36Sopenharmony_ci/*
3162306a36Sopenharmony_ci * Option Parsing (code inspired by NFS code)
3262306a36Sopenharmony_ci *  NOTE: each transport will parse its own options
3362306a36Sopenharmony_ci */
3462306a36Sopenharmony_ci
3562306a36Sopenharmony_cienum {
3662306a36Sopenharmony_ci	/* Options that take integer arguments */
3762306a36Sopenharmony_ci	Opt_debug, Opt_dfltuid, Opt_dfltgid, Opt_afid,
3862306a36Sopenharmony_ci	/* String options */
3962306a36Sopenharmony_ci	Opt_uname, Opt_remotename, Opt_cache, Opt_cachetag,
4062306a36Sopenharmony_ci	/* Options that take no arguments */
4162306a36Sopenharmony_ci	Opt_nodevmap, Opt_noxattr, Opt_directio, Opt_ignoreqv,
4262306a36Sopenharmony_ci	/* Access options */
4362306a36Sopenharmony_ci	Opt_access, Opt_posixacl,
4462306a36Sopenharmony_ci	/* Lock timeout option */
4562306a36Sopenharmony_ci	Opt_locktimeout,
4662306a36Sopenharmony_ci	/* Error token */
4762306a36Sopenharmony_ci	Opt_err
4862306a36Sopenharmony_ci};
4962306a36Sopenharmony_ci
5062306a36Sopenharmony_cistatic const match_table_t tokens = {
5162306a36Sopenharmony_ci	{Opt_debug, "debug=%x"},
5262306a36Sopenharmony_ci	{Opt_dfltuid, "dfltuid=%u"},
5362306a36Sopenharmony_ci	{Opt_dfltgid, "dfltgid=%u"},
5462306a36Sopenharmony_ci	{Opt_afid, "afid=%u"},
5562306a36Sopenharmony_ci	{Opt_uname, "uname=%s"},
5662306a36Sopenharmony_ci	{Opt_remotename, "aname=%s"},
5762306a36Sopenharmony_ci	{Opt_nodevmap, "nodevmap"},
5862306a36Sopenharmony_ci	{Opt_noxattr, "noxattr"},
5962306a36Sopenharmony_ci	{Opt_directio, "directio"},
6062306a36Sopenharmony_ci	{Opt_ignoreqv, "ignoreqv"},
6162306a36Sopenharmony_ci	{Opt_cache, "cache=%s"},
6262306a36Sopenharmony_ci	{Opt_cachetag, "cachetag=%s"},
6362306a36Sopenharmony_ci	{Opt_access, "access=%s"},
6462306a36Sopenharmony_ci	{Opt_posixacl, "posixacl"},
6562306a36Sopenharmony_ci	{Opt_locktimeout, "locktimeout=%u"},
6662306a36Sopenharmony_ci	{Opt_err, NULL}
6762306a36Sopenharmony_ci};
6862306a36Sopenharmony_ci
6962306a36Sopenharmony_ci/* Interpret mount options for cache mode */
7062306a36Sopenharmony_cistatic int get_cache_mode(char *s)
7162306a36Sopenharmony_ci{
7262306a36Sopenharmony_ci	int version = -EINVAL;
7362306a36Sopenharmony_ci
7462306a36Sopenharmony_ci	if (!strcmp(s, "loose")) {
7562306a36Sopenharmony_ci		version = CACHE_SC_LOOSE;
7662306a36Sopenharmony_ci		p9_debug(P9_DEBUG_9P, "Cache mode: loose\n");
7762306a36Sopenharmony_ci	} else if (!strcmp(s, "fscache")) {
7862306a36Sopenharmony_ci		version = CACHE_SC_FSCACHE;
7962306a36Sopenharmony_ci		p9_debug(P9_DEBUG_9P, "Cache mode: fscache\n");
8062306a36Sopenharmony_ci	} else if (!strcmp(s, "mmap")) {
8162306a36Sopenharmony_ci		version = CACHE_SC_MMAP;
8262306a36Sopenharmony_ci		p9_debug(P9_DEBUG_9P, "Cache mode: mmap\n");
8362306a36Sopenharmony_ci	} else if (!strcmp(s, "readahead")) {
8462306a36Sopenharmony_ci		version = CACHE_SC_READAHEAD;
8562306a36Sopenharmony_ci		p9_debug(P9_DEBUG_9P, "Cache mode: readahead\n");
8662306a36Sopenharmony_ci	} else if (!strcmp(s, "none")) {
8762306a36Sopenharmony_ci		version = CACHE_SC_NONE;
8862306a36Sopenharmony_ci		p9_debug(P9_DEBUG_9P, "Cache mode: none\n");
8962306a36Sopenharmony_ci	} else if (kstrtoint(s, 0, &version) != 0) {
9062306a36Sopenharmony_ci		version = -EINVAL;
9162306a36Sopenharmony_ci		pr_info("Unknown Cache mode or invalid value %s\n", s);
9262306a36Sopenharmony_ci	}
9362306a36Sopenharmony_ci	return version;
9462306a36Sopenharmony_ci}
9562306a36Sopenharmony_ci
9662306a36Sopenharmony_ci/*
9762306a36Sopenharmony_ci * Display the mount options in /proc/mounts.
9862306a36Sopenharmony_ci */
9962306a36Sopenharmony_ciint v9fs_show_options(struct seq_file *m, struct dentry *root)
10062306a36Sopenharmony_ci{
10162306a36Sopenharmony_ci	struct v9fs_session_info *v9ses = root->d_sb->s_fs_info;
10262306a36Sopenharmony_ci
10362306a36Sopenharmony_ci	if (v9ses->debug)
10462306a36Sopenharmony_ci		seq_printf(m, ",debug=%x", v9ses->debug);
10562306a36Sopenharmony_ci	if (!uid_eq(v9ses->dfltuid, V9FS_DEFUID))
10662306a36Sopenharmony_ci		seq_printf(m, ",dfltuid=%u",
10762306a36Sopenharmony_ci			   from_kuid_munged(&init_user_ns, v9ses->dfltuid));
10862306a36Sopenharmony_ci	if (!gid_eq(v9ses->dfltgid, V9FS_DEFGID))
10962306a36Sopenharmony_ci		seq_printf(m, ",dfltgid=%u",
11062306a36Sopenharmony_ci			   from_kgid_munged(&init_user_ns, v9ses->dfltgid));
11162306a36Sopenharmony_ci	if (v9ses->afid != ~0)
11262306a36Sopenharmony_ci		seq_printf(m, ",afid=%u", v9ses->afid);
11362306a36Sopenharmony_ci	if (strcmp(v9ses->uname, V9FS_DEFUSER) != 0)
11462306a36Sopenharmony_ci		seq_printf(m, ",uname=%s", v9ses->uname);
11562306a36Sopenharmony_ci	if (strcmp(v9ses->aname, V9FS_DEFANAME) != 0)
11662306a36Sopenharmony_ci		seq_printf(m, ",aname=%s", v9ses->aname);
11762306a36Sopenharmony_ci	if (v9ses->nodev)
11862306a36Sopenharmony_ci		seq_puts(m, ",nodevmap");
11962306a36Sopenharmony_ci	if (v9ses->cache)
12062306a36Sopenharmony_ci		seq_printf(m, ",cache=%x", v9ses->cache);
12162306a36Sopenharmony_ci#ifdef CONFIG_9P_FSCACHE
12262306a36Sopenharmony_ci	if (v9ses->cachetag && (v9ses->cache & CACHE_FSCACHE))
12362306a36Sopenharmony_ci		seq_printf(m, ",cachetag=%s", v9ses->cachetag);
12462306a36Sopenharmony_ci#endif
12562306a36Sopenharmony_ci
12662306a36Sopenharmony_ci	switch (v9ses->flags & V9FS_ACCESS_MASK) {
12762306a36Sopenharmony_ci	case V9FS_ACCESS_USER:
12862306a36Sopenharmony_ci		seq_puts(m, ",access=user");
12962306a36Sopenharmony_ci		break;
13062306a36Sopenharmony_ci	case V9FS_ACCESS_ANY:
13162306a36Sopenharmony_ci		seq_puts(m, ",access=any");
13262306a36Sopenharmony_ci		break;
13362306a36Sopenharmony_ci	case V9FS_ACCESS_CLIENT:
13462306a36Sopenharmony_ci		seq_puts(m, ",access=client");
13562306a36Sopenharmony_ci		break;
13662306a36Sopenharmony_ci	case V9FS_ACCESS_SINGLE:
13762306a36Sopenharmony_ci		seq_printf(m, ",access=%u",
13862306a36Sopenharmony_ci			   from_kuid_munged(&init_user_ns, v9ses->uid));
13962306a36Sopenharmony_ci		break;
14062306a36Sopenharmony_ci	}
14162306a36Sopenharmony_ci
14262306a36Sopenharmony_ci	if (v9ses->flags & V9FS_IGNORE_QV)
14362306a36Sopenharmony_ci		seq_puts(m, ",ignoreqv");
14462306a36Sopenharmony_ci	if (v9ses->flags & V9FS_DIRECT_IO)
14562306a36Sopenharmony_ci		seq_puts(m, ",directio");
14662306a36Sopenharmony_ci	if (v9ses->flags & V9FS_POSIX_ACL)
14762306a36Sopenharmony_ci		seq_puts(m, ",posixacl");
14862306a36Sopenharmony_ci
14962306a36Sopenharmony_ci	if (v9ses->flags & V9FS_NO_XATTR)
15062306a36Sopenharmony_ci		seq_puts(m, ",noxattr");
15162306a36Sopenharmony_ci
15262306a36Sopenharmony_ci	return p9_show_client_options(m, v9ses->clnt);
15362306a36Sopenharmony_ci}
15462306a36Sopenharmony_ci
15562306a36Sopenharmony_ci/**
15662306a36Sopenharmony_ci * v9fs_parse_options - parse mount options into session structure
15762306a36Sopenharmony_ci * @v9ses: existing v9fs session information
15862306a36Sopenharmony_ci * @opts: The mount option string
15962306a36Sopenharmony_ci *
16062306a36Sopenharmony_ci * Return 0 upon success, -ERRNO upon failure.
16162306a36Sopenharmony_ci */
16262306a36Sopenharmony_ci
16362306a36Sopenharmony_cistatic int v9fs_parse_options(struct v9fs_session_info *v9ses, char *opts)
16462306a36Sopenharmony_ci{
16562306a36Sopenharmony_ci	char *options, *tmp_options;
16662306a36Sopenharmony_ci	substring_t args[MAX_OPT_ARGS];
16762306a36Sopenharmony_ci	char *p;
16862306a36Sopenharmony_ci	int option = 0;
16962306a36Sopenharmony_ci	char *s;
17062306a36Sopenharmony_ci	int ret = 0;
17162306a36Sopenharmony_ci
17262306a36Sopenharmony_ci	/* setup defaults */
17362306a36Sopenharmony_ci	v9ses->afid = ~0;
17462306a36Sopenharmony_ci	v9ses->debug = 0;
17562306a36Sopenharmony_ci	v9ses->cache = CACHE_NONE;
17662306a36Sopenharmony_ci#ifdef CONFIG_9P_FSCACHE
17762306a36Sopenharmony_ci	v9ses->cachetag = NULL;
17862306a36Sopenharmony_ci#endif
17962306a36Sopenharmony_ci	v9ses->session_lock_timeout = P9_LOCK_TIMEOUT;
18062306a36Sopenharmony_ci
18162306a36Sopenharmony_ci	if (!opts)
18262306a36Sopenharmony_ci		return 0;
18362306a36Sopenharmony_ci
18462306a36Sopenharmony_ci	tmp_options = kstrdup(opts, GFP_KERNEL);
18562306a36Sopenharmony_ci	if (!tmp_options) {
18662306a36Sopenharmony_ci		ret = -ENOMEM;
18762306a36Sopenharmony_ci		goto fail_option_alloc;
18862306a36Sopenharmony_ci	}
18962306a36Sopenharmony_ci	options = tmp_options;
19062306a36Sopenharmony_ci
19162306a36Sopenharmony_ci	while ((p = strsep(&options, ",")) != NULL) {
19262306a36Sopenharmony_ci		int token, r;
19362306a36Sopenharmony_ci
19462306a36Sopenharmony_ci		if (!*p)
19562306a36Sopenharmony_ci			continue;
19662306a36Sopenharmony_ci
19762306a36Sopenharmony_ci		token = match_token(p, tokens, args);
19862306a36Sopenharmony_ci		switch (token) {
19962306a36Sopenharmony_ci		case Opt_debug:
20062306a36Sopenharmony_ci			r = match_int(&args[0], &option);
20162306a36Sopenharmony_ci			if (r < 0) {
20262306a36Sopenharmony_ci				p9_debug(P9_DEBUG_ERROR,
20362306a36Sopenharmony_ci					 "integer field, but no integer?\n");
20462306a36Sopenharmony_ci				ret = r;
20562306a36Sopenharmony_ci			} else {
20662306a36Sopenharmony_ci				v9ses->debug = option;
20762306a36Sopenharmony_ci#ifdef CONFIG_NET_9P_DEBUG
20862306a36Sopenharmony_ci				p9_debug_level = option;
20962306a36Sopenharmony_ci#endif
21062306a36Sopenharmony_ci			}
21162306a36Sopenharmony_ci			break;
21262306a36Sopenharmony_ci
21362306a36Sopenharmony_ci		case Opt_dfltuid:
21462306a36Sopenharmony_ci			r = match_int(&args[0], &option);
21562306a36Sopenharmony_ci			if (r < 0) {
21662306a36Sopenharmony_ci				p9_debug(P9_DEBUG_ERROR,
21762306a36Sopenharmony_ci					 "integer field, but no integer?\n");
21862306a36Sopenharmony_ci				ret = r;
21962306a36Sopenharmony_ci				continue;
22062306a36Sopenharmony_ci			}
22162306a36Sopenharmony_ci			v9ses->dfltuid = make_kuid(current_user_ns(), option);
22262306a36Sopenharmony_ci			if (!uid_valid(v9ses->dfltuid)) {
22362306a36Sopenharmony_ci				p9_debug(P9_DEBUG_ERROR,
22462306a36Sopenharmony_ci					 "uid field, but not a uid?\n");
22562306a36Sopenharmony_ci				ret = -EINVAL;
22662306a36Sopenharmony_ci			}
22762306a36Sopenharmony_ci			break;
22862306a36Sopenharmony_ci		case Opt_dfltgid:
22962306a36Sopenharmony_ci			r = match_int(&args[0], &option);
23062306a36Sopenharmony_ci			if (r < 0) {
23162306a36Sopenharmony_ci				p9_debug(P9_DEBUG_ERROR,
23262306a36Sopenharmony_ci					 "integer field, but no integer?\n");
23362306a36Sopenharmony_ci				ret = r;
23462306a36Sopenharmony_ci				continue;
23562306a36Sopenharmony_ci			}
23662306a36Sopenharmony_ci			v9ses->dfltgid = make_kgid(current_user_ns(), option);
23762306a36Sopenharmony_ci			if (!gid_valid(v9ses->dfltgid)) {
23862306a36Sopenharmony_ci				p9_debug(P9_DEBUG_ERROR,
23962306a36Sopenharmony_ci					 "gid field, but not a gid?\n");
24062306a36Sopenharmony_ci				ret = -EINVAL;
24162306a36Sopenharmony_ci			}
24262306a36Sopenharmony_ci			break;
24362306a36Sopenharmony_ci		case Opt_afid:
24462306a36Sopenharmony_ci			r = match_int(&args[0], &option);
24562306a36Sopenharmony_ci			if (r < 0) {
24662306a36Sopenharmony_ci				p9_debug(P9_DEBUG_ERROR,
24762306a36Sopenharmony_ci					 "integer field, but no integer?\n");
24862306a36Sopenharmony_ci				ret = r;
24962306a36Sopenharmony_ci			} else {
25062306a36Sopenharmony_ci				v9ses->afid = option;
25162306a36Sopenharmony_ci			}
25262306a36Sopenharmony_ci			break;
25362306a36Sopenharmony_ci		case Opt_uname:
25462306a36Sopenharmony_ci			kfree(v9ses->uname);
25562306a36Sopenharmony_ci			v9ses->uname = match_strdup(&args[0]);
25662306a36Sopenharmony_ci			if (!v9ses->uname) {
25762306a36Sopenharmony_ci				ret = -ENOMEM;
25862306a36Sopenharmony_ci				goto free_and_return;
25962306a36Sopenharmony_ci			}
26062306a36Sopenharmony_ci			break;
26162306a36Sopenharmony_ci		case Opt_remotename:
26262306a36Sopenharmony_ci			kfree(v9ses->aname);
26362306a36Sopenharmony_ci			v9ses->aname = match_strdup(&args[0]);
26462306a36Sopenharmony_ci			if (!v9ses->aname) {
26562306a36Sopenharmony_ci				ret = -ENOMEM;
26662306a36Sopenharmony_ci				goto free_and_return;
26762306a36Sopenharmony_ci			}
26862306a36Sopenharmony_ci			break;
26962306a36Sopenharmony_ci		case Opt_nodevmap:
27062306a36Sopenharmony_ci			v9ses->nodev = 1;
27162306a36Sopenharmony_ci			break;
27262306a36Sopenharmony_ci		case Opt_noxattr:
27362306a36Sopenharmony_ci			v9ses->flags |= V9FS_NO_XATTR;
27462306a36Sopenharmony_ci			break;
27562306a36Sopenharmony_ci		case Opt_directio:
27662306a36Sopenharmony_ci			v9ses->flags |= V9FS_DIRECT_IO;
27762306a36Sopenharmony_ci			break;
27862306a36Sopenharmony_ci		case Opt_ignoreqv:
27962306a36Sopenharmony_ci			v9ses->flags |= V9FS_IGNORE_QV;
28062306a36Sopenharmony_ci			break;
28162306a36Sopenharmony_ci		case Opt_cachetag:
28262306a36Sopenharmony_ci#ifdef CONFIG_9P_FSCACHE
28362306a36Sopenharmony_ci			kfree(v9ses->cachetag);
28462306a36Sopenharmony_ci			v9ses->cachetag = match_strdup(&args[0]);
28562306a36Sopenharmony_ci			if (!v9ses->cachetag) {
28662306a36Sopenharmony_ci				ret = -ENOMEM;
28762306a36Sopenharmony_ci				goto free_and_return;
28862306a36Sopenharmony_ci			}
28962306a36Sopenharmony_ci#endif
29062306a36Sopenharmony_ci			break;
29162306a36Sopenharmony_ci		case Opt_cache:
29262306a36Sopenharmony_ci			s = match_strdup(&args[0]);
29362306a36Sopenharmony_ci			if (!s) {
29462306a36Sopenharmony_ci				ret = -ENOMEM;
29562306a36Sopenharmony_ci				p9_debug(P9_DEBUG_ERROR,
29662306a36Sopenharmony_ci					 "problem allocating copy of cache arg\n");
29762306a36Sopenharmony_ci				goto free_and_return;
29862306a36Sopenharmony_ci			}
29962306a36Sopenharmony_ci			r = get_cache_mode(s);
30062306a36Sopenharmony_ci			if (r < 0)
30162306a36Sopenharmony_ci				ret = r;
30262306a36Sopenharmony_ci			else
30362306a36Sopenharmony_ci				v9ses->cache = r;
30462306a36Sopenharmony_ci
30562306a36Sopenharmony_ci			kfree(s);
30662306a36Sopenharmony_ci			break;
30762306a36Sopenharmony_ci
30862306a36Sopenharmony_ci		case Opt_access:
30962306a36Sopenharmony_ci			s = match_strdup(&args[0]);
31062306a36Sopenharmony_ci			if (!s) {
31162306a36Sopenharmony_ci				ret = -ENOMEM;
31262306a36Sopenharmony_ci				p9_debug(P9_DEBUG_ERROR,
31362306a36Sopenharmony_ci					 "problem allocating copy of access arg\n");
31462306a36Sopenharmony_ci				goto free_and_return;
31562306a36Sopenharmony_ci			}
31662306a36Sopenharmony_ci
31762306a36Sopenharmony_ci			v9ses->flags &= ~V9FS_ACCESS_MASK;
31862306a36Sopenharmony_ci			if (strcmp(s, "user") == 0)
31962306a36Sopenharmony_ci				v9ses->flags |= V9FS_ACCESS_USER;
32062306a36Sopenharmony_ci			else if (strcmp(s, "any") == 0)
32162306a36Sopenharmony_ci				v9ses->flags |= V9FS_ACCESS_ANY;
32262306a36Sopenharmony_ci			else if (strcmp(s, "client") == 0) {
32362306a36Sopenharmony_ci				v9ses->flags |= V9FS_ACCESS_CLIENT;
32462306a36Sopenharmony_ci			} else {
32562306a36Sopenharmony_ci				uid_t uid;
32662306a36Sopenharmony_ci
32762306a36Sopenharmony_ci				v9ses->flags |= V9FS_ACCESS_SINGLE;
32862306a36Sopenharmony_ci				r = kstrtouint(s, 10, &uid);
32962306a36Sopenharmony_ci				if (r) {
33062306a36Sopenharmony_ci					ret = r;
33162306a36Sopenharmony_ci					pr_info("Unknown access argument %s: %d\n",
33262306a36Sopenharmony_ci						s, r);
33362306a36Sopenharmony_ci					kfree(s);
33462306a36Sopenharmony_ci					continue;
33562306a36Sopenharmony_ci				}
33662306a36Sopenharmony_ci				v9ses->uid = make_kuid(current_user_ns(), uid);
33762306a36Sopenharmony_ci				if (!uid_valid(v9ses->uid)) {
33862306a36Sopenharmony_ci					ret = -EINVAL;
33962306a36Sopenharmony_ci					pr_info("Unknown uid %s\n", s);
34062306a36Sopenharmony_ci				}
34162306a36Sopenharmony_ci			}
34262306a36Sopenharmony_ci
34362306a36Sopenharmony_ci			kfree(s);
34462306a36Sopenharmony_ci			break;
34562306a36Sopenharmony_ci
34662306a36Sopenharmony_ci		case Opt_posixacl:
34762306a36Sopenharmony_ci#ifdef CONFIG_9P_FS_POSIX_ACL
34862306a36Sopenharmony_ci			v9ses->flags |= V9FS_POSIX_ACL;
34962306a36Sopenharmony_ci#else
35062306a36Sopenharmony_ci			p9_debug(P9_DEBUG_ERROR,
35162306a36Sopenharmony_ci				 "Not defined CONFIG_9P_FS_POSIX_ACL. Ignoring posixacl option\n");
35262306a36Sopenharmony_ci#endif
35362306a36Sopenharmony_ci			break;
35462306a36Sopenharmony_ci
35562306a36Sopenharmony_ci		case Opt_locktimeout:
35662306a36Sopenharmony_ci			r = match_int(&args[0], &option);
35762306a36Sopenharmony_ci			if (r < 0) {
35862306a36Sopenharmony_ci				p9_debug(P9_DEBUG_ERROR,
35962306a36Sopenharmony_ci					 "integer field, but no integer?\n");
36062306a36Sopenharmony_ci				ret = r;
36162306a36Sopenharmony_ci				continue;
36262306a36Sopenharmony_ci			}
36362306a36Sopenharmony_ci			if (option < 1) {
36462306a36Sopenharmony_ci				p9_debug(P9_DEBUG_ERROR,
36562306a36Sopenharmony_ci					 "locktimeout must be a greater than zero integer.\n");
36662306a36Sopenharmony_ci				ret = -EINVAL;
36762306a36Sopenharmony_ci				continue;
36862306a36Sopenharmony_ci			}
36962306a36Sopenharmony_ci			v9ses->session_lock_timeout = (long)option * HZ;
37062306a36Sopenharmony_ci			break;
37162306a36Sopenharmony_ci
37262306a36Sopenharmony_ci		default:
37362306a36Sopenharmony_ci			continue;
37462306a36Sopenharmony_ci		}
37562306a36Sopenharmony_ci	}
37662306a36Sopenharmony_ci
37762306a36Sopenharmony_cifree_and_return:
37862306a36Sopenharmony_ci	kfree(tmp_options);
37962306a36Sopenharmony_cifail_option_alloc:
38062306a36Sopenharmony_ci	return ret;
38162306a36Sopenharmony_ci}
38262306a36Sopenharmony_ci
38362306a36Sopenharmony_ci/**
38462306a36Sopenharmony_ci * v9fs_session_init - initialize session
38562306a36Sopenharmony_ci * @v9ses: session information structure
38662306a36Sopenharmony_ci * @dev_name: device being mounted
38762306a36Sopenharmony_ci * @data: options
38862306a36Sopenharmony_ci *
38962306a36Sopenharmony_ci */
39062306a36Sopenharmony_ci
39162306a36Sopenharmony_cistruct p9_fid *v9fs_session_init(struct v9fs_session_info *v9ses,
39262306a36Sopenharmony_ci		  const char *dev_name, char *data)
39362306a36Sopenharmony_ci{
39462306a36Sopenharmony_ci	struct p9_fid *fid;
39562306a36Sopenharmony_ci	int rc = -ENOMEM;
39662306a36Sopenharmony_ci
39762306a36Sopenharmony_ci	v9ses->uname = kstrdup(V9FS_DEFUSER, GFP_KERNEL);
39862306a36Sopenharmony_ci	if (!v9ses->uname)
39962306a36Sopenharmony_ci		goto err_names;
40062306a36Sopenharmony_ci
40162306a36Sopenharmony_ci	v9ses->aname = kstrdup(V9FS_DEFANAME, GFP_KERNEL);
40262306a36Sopenharmony_ci	if (!v9ses->aname)
40362306a36Sopenharmony_ci		goto err_names;
40462306a36Sopenharmony_ci	init_rwsem(&v9ses->rename_sem);
40562306a36Sopenharmony_ci
40662306a36Sopenharmony_ci	v9ses->uid = INVALID_UID;
40762306a36Sopenharmony_ci	v9ses->dfltuid = V9FS_DEFUID;
40862306a36Sopenharmony_ci	v9ses->dfltgid = V9FS_DEFGID;
40962306a36Sopenharmony_ci
41062306a36Sopenharmony_ci	v9ses->clnt = p9_client_create(dev_name, data);
41162306a36Sopenharmony_ci	if (IS_ERR(v9ses->clnt)) {
41262306a36Sopenharmony_ci		rc = PTR_ERR(v9ses->clnt);
41362306a36Sopenharmony_ci		p9_debug(P9_DEBUG_ERROR, "problem initializing 9p client\n");
41462306a36Sopenharmony_ci		goto err_names;
41562306a36Sopenharmony_ci	}
41662306a36Sopenharmony_ci
41762306a36Sopenharmony_ci	v9ses->flags = V9FS_ACCESS_USER;
41862306a36Sopenharmony_ci
41962306a36Sopenharmony_ci	if (p9_is_proto_dotl(v9ses->clnt)) {
42062306a36Sopenharmony_ci		v9ses->flags = V9FS_ACCESS_CLIENT;
42162306a36Sopenharmony_ci		v9ses->flags |= V9FS_PROTO_2000L;
42262306a36Sopenharmony_ci	} else if (p9_is_proto_dotu(v9ses->clnt)) {
42362306a36Sopenharmony_ci		v9ses->flags |= V9FS_PROTO_2000U;
42462306a36Sopenharmony_ci	}
42562306a36Sopenharmony_ci
42662306a36Sopenharmony_ci	rc = v9fs_parse_options(v9ses, data);
42762306a36Sopenharmony_ci	if (rc < 0)
42862306a36Sopenharmony_ci		goto err_clnt;
42962306a36Sopenharmony_ci
43062306a36Sopenharmony_ci	v9ses->maxdata = v9ses->clnt->msize - P9_IOHDRSZ;
43162306a36Sopenharmony_ci
43262306a36Sopenharmony_ci	if (!v9fs_proto_dotl(v9ses) &&
43362306a36Sopenharmony_ci	    ((v9ses->flags & V9FS_ACCESS_MASK) == V9FS_ACCESS_CLIENT)) {
43462306a36Sopenharmony_ci		/*
43562306a36Sopenharmony_ci		 * We support ACCESS_CLIENT only for dotl.
43662306a36Sopenharmony_ci		 * Fall back to ACCESS_USER
43762306a36Sopenharmony_ci		 */
43862306a36Sopenharmony_ci		v9ses->flags &= ~V9FS_ACCESS_MASK;
43962306a36Sopenharmony_ci		v9ses->flags |= V9FS_ACCESS_USER;
44062306a36Sopenharmony_ci	}
44162306a36Sopenharmony_ci	/*FIXME !! */
44262306a36Sopenharmony_ci	/* for legacy mode, fall back to V9FS_ACCESS_ANY */
44362306a36Sopenharmony_ci	if (!(v9fs_proto_dotu(v9ses) || v9fs_proto_dotl(v9ses)) &&
44462306a36Sopenharmony_ci		((v9ses->flags&V9FS_ACCESS_MASK) == V9FS_ACCESS_USER)) {
44562306a36Sopenharmony_ci
44662306a36Sopenharmony_ci		v9ses->flags &= ~V9FS_ACCESS_MASK;
44762306a36Sopenharmony_ci		v9ses->flags |= V9FS_ACCESS_ANY;
44862306a36Sopenharmony_ci		v9ses->uid = INVALID_UID;
44962306a36Sopenharmony_ci	}
45062306a36Sopenharmony_ci	if (!v9fs_proto_dotl(v9ses) ||
45162306a36Sopenharmony_ci		!((v9ses->flags & V9FS_ACCESS_MASK) == V9FS_ACCESS_CLIENT)) {
45262306a36Sopenharmony_ci		/*
45362306a36Sopenharmony_ci		 * We support ACL checks on clinet only if the protocol is
45462306a36Sopenharmony_ci		 * 9P2000.L and access is V9FS_ACCESS_CLIENT.
45562306a36Sopenharmony_ci		 */
45662306a36Sopenharmony_ci		v9ses->flags &= ~V9FS_ACL_MASK;
45762306a36Sopenharmony_ci	}
45862306a36Sopenharmony_ci
45962306a36Sopenharmony_ci	fid = p9_client_attach(v9ses->clnt, NULL, v9ses->uname, INVALID_UID,
46062306a36Sopenharmony_ci							v9ses->aname);
46162306a36Sopenharmony_ci	if (IS_ERR(fid)) {
46262306a36Sopenharmony_ci		rc = PTR_ERR(fid);
46362306a36Sopenharmony_ci		p9_debug(P9_DEBUG_ERROR, "cannot attach\n");
46462306a36Sopenharmony_ci		goto err_clnt;
46562306a36Sopenharmony_ci	}
46662306a36Sopenharmony_ci
46762306a36Sopenharmony_ci	if ((v9ses->flags & V9FS_ACCESS_MASK) == V9FS_ACCESS_SINGLE)
46862306a36Sopenharmony_ci		fid->uid = v9ses->uid;
46962306a36Sopenharmony_ci	else
47062306a36Sopenharmony_ci		fid->uid = INVALID_UID;
47162306a36Sopenharmony_ci
47262306a36Sopenharmony_ci#ifdef CONFIG_9P_FSCACHE
47362306a36Sopenharmony_ci	/* register the session for caching */
47462306a36Sopenharmony_ci	if (v9ses->cache & CACHE_FSCACHE) {
47562306a36Sopenharmony_ci		rc = v9fs_cache_session_get_cookie(v9ses, dev_name);
47662306a36Sopenharmony_ci		if (rc < 0)
47762306a36Sopenharmony_ci			goto err_clnt;
47862306a36Sopenharmony_ci	}
47962306a36Sopenharmony_ci#endif
48062306a36Sopenharmony_ci	spin_lock(&v9fs_sessionlist_lock);
48162306a36Sopenharmony_ci	list_add(&v9ses->slist, &v9fs_sessionlist);
48262306a36Sopenharmony_ci	spin_unlock(&v9fs_sessionlist_lock);
48362306a36Sopenharmony_ci
48462306a36Sopenharmony_ci	return fid;
48562306a36Sopenharmony_ci
48662306a36Sopenharmony_cierr_clnt:
48762306a36Sopenharmony_ci#ifdef CONFIG_9P_FSCACHE
48862306a36Sopenharmony_ci	kfree(v9ses->cachetag);
48962306a36Sopenharmony_ci#endif
49062306a36Sopenharmony_ci	p9_client_destroy(v9ses->clnt);
49162306a36Sopenharmony_cierr_names:
49262306a36Sopenharmony_ci	kfree(v9ses->uname);
49362306a36Sopenharmony_ci	kfree(v9ses->aname);
49462306a36Sopenharmony_ci	return ERR_PTR(rc);
49562306a36Sopenharmony_ci}
49662306a36Sopenharmony_ci
49762306a36Sopenharmony_ci/**
49862306a36Sopenharmony_ci * v9fs_session_close - shutdown a session
49962306a36Sopenharmony_ci * @v9ses: session information structure
50062306a36Sopenharmony_ci *
50162306a36Sopenharmony_ci */
50262306a36Sopenharmony_ci
50362306a36Sopenharmony_civoid v9fs_session_close(struct v9fs_session_info *v9ses)
50462306a36Sopenharmony_ci{
50562306a36Sopenharmony_ci	if (v9ses->clnt) {
50662306a36Sopenharmony_ci		p9_client_destroy(v9ses->clnt);
50762306a36Sopenharmony_ci		v9ses->clnt = NULL;
50862306a36Sopenharmony_ci	}
50962306a36Sopenharmony_ci
51062306a36Sopenharmony_ci#ifdef CONFIG_9P_FSCACHE
51162306a36Sopenharmony_ci	fscache_relinquish_volume(v9fs_session_cache(v9ses), NULL, false);
51262306a36Sopenharmony_ci	kfree(v9ses->cachetag);
51362306a36Sopenharmony_ci#endif
51462306a36Sopenharmony_ci	kfree(v9ses->uname);
51562306a36Sopenharmony_ci	kfree(v9ses->aname);
51662306a36Sopenharmony_ci
51762306a36Sopenharmony_ci	spin_lock(&v9fs_sessionlist_lock);
51862306a36Sopenharmony_ci	list_del(&v9ses->slist);
51962306a36Sopenharmony_ci	spin_unlock(&v9fs_sessionlist_lock);
52062306a36Sopenharmony_ci}
52162306a36Sopenharmony_ci
52262306a36Sopenharmony_ci/**
52362306a36Sopenharmony_ci * v9fs_session_cancel - terminate a session
52462306a36Sopenharmony_ci * @v9ses: session to terminate
52562306a36Sopenharmony_ci *
52662306a36Sopenharmony_ci * mark transport as disconnected and cancel all pending requests.
52762306a36Sopenharmony_ci */
52862306a36Sopenharmony_ci
52962306a36Sopenharmony_civoid v9fs_session_cancel(struct v9fs_session_info *v9ses)
53062306a36Sopenharmony_ci{
53162306a36Sopenharmony_ci	p9_debug(P9_DEBUG_ERROR, "cancel session %p\n", v9ses);
53262306a36Sopenharmony_ci	p9_client_disconnect(v9ses->clnt);
53362306a36Sopenharmony_ci}
53462306a36Sopenharmony_ci
53562306a36Sopenharmony_ci/**
53662306a36Sopenharmony_ci * v9fs_session_begin_cancel - Begin terminate of a session
53762306a36Sopenharmony_ci * @v9ses: session to terminate
53862306a36Sopenharmony_ci *
53962306a36Sopenharmony_ci * After this call we don't allow any request other than clunk.
54062306a36Sopenharmony_ci */
54162306a36Sopenharmony_ci
54262306a36Sopenharmony_civoid v9fs_session_begin_cancel(struct v9fs_session_info *v9ses)
54362306a36Sopenharmony_ci{
54462306a36Sopenharmony_ci	p9_debug(P9_DEBUG_ERROR, "begin cancel session %p\n", v9ses);
54562306a36Sopenharmony_ci	p9_client_begin_disconnect(v9ses->clnt);
54662306a36Sopenharmony_ci}
54762306a36Sopenharmony_ci
54862306a36Sopenharmony_cistatic struct kobject *v9fs_kobj;
54962306a36Sopenharmony_ci
55062306a36Sopenharmony_ci#ifdef CONFIG_9P_FSCACHE
55162306a36Sopenharmony_ci/*
55262306a36Sopenharmony_ci * List caches associated with a session
55362306a36Sopenharmony_ci */
55462306a36Sopenharmony_cistatic ssize_t caches_show(struct kobject *kobj,
55562306a36Sopenharmony_ci			   struct kobj_attribute *attr,
55662306a36Sopenharmony_ci			   char *buf)
55762306a36Sopenharmony_ci{
55862306a36Sopenharmony_ci	ssize_t n = 0, count = 0, limit = PAGE_SIZE;
55962306a36Sopenharmony_ci	struct v9fs_session_info *v9ses;
56062306a36Sopenharmony_ci
56162306a36Sopenharmony_ci	spin_lock(&v9fs_sessionlist_lock);
56262306a36Sopenharmony_ci	list_for_each_entry(v9ses, &v9fs_sessionlist, slist) {
56362306a36Sopenharmony_ci		if (v9ses->cachetag) {
56462306a36Sopenharmony_ci			n = snprintf(buf, limit, "%s\n", v9ses->cachetag);
56562306a36Sopenharmony_ci			if (n < 0) {
56662306a36Sopenharmony_ci				count = n;
56762306a36Sopenharmony_ci				break;
56862306a36Sopenharmony_ci			}
56962306a36Sopenharmony_ci
57062306a36Sopenharmony_ci			count += n;
57162306a36Sopenharmony_ci			limit -= n;
57262306a36Sopenharmony_ci		}
57362306a36Sopenharmony_ci	}
57462306a36Sopenharmony_ci
57562306a36Sopenharmony_ci	spin_unlock(&v9fs_sessionlist_lock);
57662306a36Sopenharmony_ci	return count;
57762306a36Sopenharmony_ci}
57862306a36Sopenharmony_ci
57962306a36Sopenharmony_cistatic struct kobj_attribute v9fs_attr_cache = __ATTR_RO(caches);
58062306a36Sopenharmony_ci#endif /* CONFIG_9P_FSCACHE */
58162306a36Sopenharmony_ci
58262306a36Sopenharmony_cistatic struct attribute *v9fs_attrs[] = {
58362306a36Sopenharmony_ci#ifdef CONFIG_9P_FSCACHE
58462306a36Sopenharmony_ci	&v9fs_attr_cache.attr,
58562306a36Sopenharmony_ci#endif
58662306a36Sopenharmony_ci	NULL,
58762306a36Sopenharmony_ci};
58862306a36Sopenharmony_ci
58962306a36Sopenharmony_cistatic const struct attribute_group v9fs_attr_group = {
59062306a36Sopenharmony_ci	.attrs = v9fs_attrs,
59162306a36Sopenharmony_ci};
59262306a36Sopenharmony_ci
59362306a36Sopenharmony_ci/**
59462306a36Sopenharmony_ci * v9fs_sysfs_init - Initialize the v9fs sysfs interface
59562306a36Sopenharmony_ci *
59662306a36Sopenharmony_ci */
59762306a36Sopenharmony_ci
59862306a36Sopenharmony_cistatic int __init v9fs_sysfs_init(void)
59962306a36Sopenharmony_ci{
60062306a36Sopenharmony_ci	v9fs_kobj = kobject_create_and_add("9p", fs_kobj);
60162306a36Sopenharmony_ci	if (!v9fs_kobj)
60262306a36Sopenharmony_ci		return -ENOMEM;
60362306a36Sopenharmony_ci
60462306a36Sopenharmony_ci	if (sysfs_create_group(v9fs_kobj, &v9fs_attr_group)) {
60562306a36Sopenharmony_ci		kobject_put(v9fs_kobj);
60662306a36Sopenharmony_ci		return -ENOMEM;
60762306a36Sopenharmony_ci	}
60862306a36Sopenharmony_ci
60962306a36Sopenharmony_ci	return 0;
61062306a36Sopenharmony_ci}
61162306a36Sopenharmony_ci
61262306a36Sopenharmony_ci/**
61362306a36Sopenharmony_ci * v9fs_sysfs_cleanup - Unregister the v9fs sysfs interface
61462306a36Sopenharmony_ci *
61562306a36Sopenharmony_ci */
61662306a36Sopenharmony_ci
61762306a36Sopenharmony_cistatic void v9fs_sysfs_cleanup(void)
61862306a36Sopenharmony_ci{
61962306a36Sopenharmony_ci	sysfs_remove_group(v9fs_kobj, &v9fs_attr_group);
62062306a36Sopenharmony_ci	kobject_put(v9fs_kobj);
62162306a36Sopenharmony_ci}
62262306a36Sopenharmony_ci
62362306a36Sopenharmony_cistatic void v9fs_inode_init_once(void *foo)
62462306a36Sopenharmony_ci{
62562306a36Sopenharmony_ci	struct v9fs_inode *v9inode = (struct v9fs_inode *)foo;
62662306a36Sopenharmony_ci
62762306a36Sopenharmony_ci	memset(&v9inode->qid, 0, sizeof(v9inode->qid));
62862306a36Sopenharmony_ci	inode_init_once(&v9inode->netfs.inode);
62962306a36Sopenharmony_ci}
63062306a36Sopenharmony_ci
63162306a36Sopenharmony_ci/**
63262306a36Sopenharmony_ci * v9fs_init_inode_cache - initialize a cache for 9P
63362306a36Sopenharmony_ci * Returns 0 on success.
63462306a36Sopenharmony_ci */
63562306a36Sopenharmony_cistatic int v9fs_init_inode_cache(void)
63662306a36Sopenharmony_ci{
63762306a36Sopenharmony_ci	v9fs_inode_cache = kmem_cache_create("v9fs_inode_cache",
63862306a36Sopenharmony_ci					  sizeof(struct v9fs_inode),
63962306a36Sopenharmony_ci					  0, (SLAB_RECLAIM_ACCOUNT|
64062306a36Sopenharmony_ci					      SLAB_MEM_SPREAD|SLAB_ACCOUNT),
64162306a36Sopenharmony_ci					  v9fs_inode_init_once);
64262306a36Sopenharmony_ci	if (!v9fs_inode_cache)
64362306a36Sopenharmony_ci		return -ENOMEM;
64462306a36Sopenharmony_ci
64562306a36Sopenharmony_ci	return 0;
64662306a36Sopenharmony_ci}
64762306a36Sopenharmony_ci
64862306a36Sopenharmony_ci/**
64962306a36Sopenharmony_ci * v9fs_destroy_inode_cache - destroy the cache of 9P inode
65062306a36Sopenharmony_ci *
65162306a36Sopenharmony_ci */
65262306a36Sopenharmony_cistatic void v9fs_destroy_inode_cache(void)
65362306a36Sopenharmony_ci{
65462306a36Sopenharmony_ci	/*
65562306a36Sopenharmony_ci	 * Make sure all delayed rcu free inodes are flushed before we
65662306a36Sopenharmony_ci	 * destroy cache.
65762306a36Sopenharmony_ci	 */
65862306a36Sopenharmony_ci	rcu_barrier();
65962306a36Sopenharmony_ci	kmem_cache_destroy(v9fs_inode_cache);
66062306a36Sopenharmony_ci}
66162306a36Sopenharmony_ci
66262306a36Sopenharmony_cistatic int v9fs_cache_register(void)
66362306a36Sopenharmony_ci{
66462306a36Sopenharmony_ci	int ret;
66562306a36Sopenharmony_ci
66662306a36Sopenharmony_ci	ret = v9fs_init_inode_cache();
66762306a36Sopenharmony_ci	if (ret < 0)
66862306a36Sopenharmony_ci		return ret;
66962306a36Sopenharmony_ci	return ret;
67062306a36Sopenharmony_ci}
67162306a36Sopenharmony_ci
67262306a36Sopenharmony_cistatic void v9fs_cache_unregister(void)
67362306a36Sopenharmony_ci{
67462306a36Sopenharmony_ci	v9fs_destroy_inode_cache();
67562306a36Sopenharmony_ci}
67662306a36Sopenharmony_ci
67762306a36Sopenharmony_ci/**
67862306a36Sopenharmony_ci * init_v9fs - Initialize module
67962306a36Sopenharmony_ci *
68062306a36Sopenharmony_ci */
68162306a36Sopenharmony_ci
68262306a36Sopenharmony_cistatic int __init init_v9fs(void)
68362306a36Sopenharmony_ci{
68462306a36Sopenharmony_ci	int err;
68562306a36Sopenharmony_ci
68662306a36Sopenharmony_ci	pr_info("Installing v9fs 9p2000 file system support\n");
68762306a36Sopenharmony_ci	/* TODO: Setup list of registered trasnport modules */
68862306a36Sopenharmony_ci
68962306a36Sopenharmony_ci	err = v9fs_cache_register();
69062306a36Sopenharmony_ci	if (err < 0) {
69162306a36Sopenharmony_ci		pr_err("Failed to register v9fs for caching\n");
69262306a36Sopenharmony_ci		return err;
69362306a36Sopenharmony_ci	}
69462306a36Sopenharmony_ci
69562306a36Sopenharmony_ci	err = v9fs_sysfs_init();
69662306a36Sopenharmony_ci	if (err < 0) {
69762306a36Sopenharmony_ci		pr_err("Failed to register with sysfs\n");
69862306a36Sopenharmony_ci		goto out_cache;
69962306a36Sopenharmony_ci	}
70062306a36Sopenharmony_ci	err = register_filesystem(&v9fs_fs_type);
70162306a36Sopenharmony_ci	if (err < 0) {
70262306a36Sopenharmony_ci		pr_err("Failed to register filesystem\n");
70362306a36Sopenharmony_ci		goto out_sysfs_cleanup;
70462306a36Sopenharmony_ci	}
70562306a36Sopenharmony_ci
70662306a36Sopenharmony_ci	return 0;
70762306a36Sopenharmony_ci
70862306a36Sopenharmony_ciout_sysfs_cleanup:
70962306a36Sopenharmony_ci	v9fs_sysfs_cleanup();
71062306a36Sopenharmony_ci
71162306a36Sopenharmony_ciout_cache:
71262306a36Sopenharmony_ci	v9fs_cache_unregister();
71362306a36Sopenharmony_ci
71462306a36Sopenharmony_ci	return err;
71562306a36Sopenharmony_ci}
71662306a36Sopenharmony_ci
71762306a36Sopenharmony_ci/**
71862306a36Sopenharmony_ci * exit_v9fs - shutdown module
71962306a36Sopenharmony_ci *
72062306a36Sopenharmony_ci */
72162306a36Sopenharmony_ci
72262306a36Sopenharmony_cistatic void __exit exit_v9fs(void)
72362306a36Sopenharmony_ci{
72462306a36Sopenharmony_ci	v9fs_sysfs_cleanup();
72562306a36Sopenharmony_ci	v9fs_cache_unregister();
72662306a36Sopenharmony_ci	unregister_filesystem(&v9fs_fs_type);
72762306a36Sopenharmony_ci}
72862306a36Sopenharmony_ci
72962306a36Sopenharmony_cimodule_init(init_v9fs)
73062306a36Sopenharmony_cimodule_exit(exit_v9fs)
73162306a36Sopenharmony_ci
73262306a36Sopenharmony_ciMODULE_AUTHOR("Latchesar Ionkov <lucho@ionkov.net>");
73362306a36Sopenharmony_ciMODULE_AUTHOR("Eric Van Hensbergen <ericvh@gmail.com>");
73462306a36Sopenharmony_ciMODULE_AUTHOR("Ron Minnich <rminnich@lanl.gov>");
73562306a36Sopenharmony_ciMODULE_LICENSE("GPL");
736