diff options
author | Timothy Pearson <tpearson@raptorengineering.com> | 2017-08-23 14:45:25 -0500 |
---|---|---|
committer | Timothy Pearson <tpearson@raptorengineering.com> | 2017-08-23 14:45:25 -0500 |
commit | fcbb27b0ec6dcbc5a5108cb8fb19eae64593d204 (patch) | |
tree | 22962a4387943edc841c72a4e636a068c66d58fd /fs/9p | |
download | ast2050-linux-kernel-fcbb27b0ec6dcbc5a5108cb8fb19eae64593d204.zip ast2050-linux-kernel-fcbb27b0ec6dcbc5a5108cb8fb19eae64593d204.tar.gz |
Initial import of modified Linux 2.6.28 tree
Original upstream URL:
git://git.kernel.org/pub/scm/linux/kernel/git/stable/linux-stable.git | branch linux-2.6.28.y
Diffstat (limited to 'fs/9p')
-rw-r--r-- | fs/9p/Makefile | 12 | ||||
-rw-r--r-- | fs/9p/fid.c | 209 | ||||
-rw-r--r-- | fs/9p/fid.h | 47 | ||||
-rw-r--r-- | fs/9p/v9fs.c | 328 | ||||
-rw-r--r-- | fs/9p/v9fs.h | 123 | ||||
-rw-r--r-- | fs/9p/v9fs_vfs.h | 56 | ||||
-rw-r--r-- | fs/9p/vfs_addr.c | 77 | ||||
-rw-r--r-- | fs/9p/vfs_dentry.c | 115 | ||||
-rw-r--r-- | fs/9p/vfs_dir.c | 152 | ||||
-rw-r--r-- | fs/9p/vfs_file.c | 272 | ||||
-rw-r--r-- | fs/9p/vfs_inode.c | 1203 | ||||
-rw-r--r-- | fs/9p/vfs_super.c | 249 |
12 files changed, 2843 insertions, 0 deletions
diff --git a/fs/9p/Makefile b/fs/9p/Makefile new file mode 100644 index 0000000..bc7f0d1 --- /dev/null +++ b/fs/9p/Makefile @@ -0,0 +1,12 @@ +obj-$(CONFIG_9P_FS) := 9p.o + +9p-objs := \ + vfs_super.o \ + vfs_inode.o \ + vfs_addr.o \ + vfs_file.o \ + vfs_dir.o \ + vfs_dentry.o \ + v9fs.o \ + fid.o \ + diff --git a/fs/9p/fid.c b/fs/9p/fid.c new file mode 100644 index 0000000..2a983d4 --- /dev/null +++ b/fs/9p/fid.c @@ -0,0 +1,209 @@ +/* + * V9FS FID Management + * + * Copyright (C) 2007 by Latchesar Ionkov <lucho@ionkov.net> + * Copyright (C) 2005, 2006 by Eric Van Hensbergen <ericvh@gmail.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * 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: + * Free Software Foundation + * 51 Franklin Street, Fifth Floor + * Boston, MA 02111-1301 USA + * + */ + +#include <linux/module.h> +#include <linux/errno.h> +#include <linux/fs.h> +#include <linux/sched.h> +#include <linux/idr.h> +#include <net/9p/9p.h> +#include <net/9p/client.h> + +#include "v9fs.h" +#include "v9fs_vfs.h" +#include "fid.h" + +/** + * v9fs_fid_add - add a fid to a dentry + * @dentry: dentry that the fid is being added to + * @fid: fid to add + * + */ + +int v9fs_fid_add(struct dentry *dentry, struct p9_fid *fid) +{ + struct v9fs_dentry *dent; + + P9_DPRINTK(P9_DEBUG_VFS, "fid %d dentry %s\n", + fid->fid, dentry->d_name.name); + + dent = dentry->d_fsdata; + if (!dent) { + dent = kmalloc(sizeof(struct v9fs_dentry), GFP_KERNEL); + if (!dent) + return -ENOMEM; + + spin_lock_init(&dent->lock); + INIT_LIST_HEAD(&dent->fidlist); + dentry->d_fsdata = dent; + } + + spin_lock(&dent->lock); + list_add(&fid->dlist, &dent->fidlist); + spin_unlock(&dent->lock); + + return 0; +} + +/** + * v9fs_fid_find - retrieve a fid that belongs to the specified uid + * @dentry: dentry to look for fid in + * @uid: return fid that belongs to the specified user + * @any: if non-zero, return any fid associated with the dentry + * + */ + +static struct p9_fid *v9fs_fid_find(struct dentry *dentry, u32 uid, int any) +{ + struct v9fs_dentry *dent; + struct p9_fid *fid, *ret; + + P9_DPRINTK(P9_DEBUG_VFS, " dentry: %s (%p) uid %d any %d\n", + dentry->d_name.name, dentry, uid, any); + dent = (struct v9fs_dentry *) dentry->d_fsdata; + ret = NULL; + if (dent) { + spin_lock(&dent->lock); + list_for_each_entry(fid, &dent->fidlist, dlist) { + if (any || fid->uid == uid) { + ret = fid; + break; + } + } + spin_unlock(&dent->lock); + } + + return ret; +} + +/** + * v9fs_fid_lookup - lookup for a fid, try to walk if not found + * @dentry: dentry to look for fid in + * + * Look for a fid in the specified dentry for the current user. + * If no fid is found, try to create one walking from a fid from the parent + * dentry (if it has one), or the root dentry. If the user haven't accessed + * the fs yet, attach now and walk from the root. + */ + +struct p9_fid *v9fs_fid_lookup(struct dentry *dentry) +{ + int i, n, l, clone, any, access; + u32 uid; + struct p9_fid *fid; + struct dentry *d, *ds; + struct v9fs_session_info *v9ses; + char **wnames, *uname; + + v9ses = v9fs_inode2v9ses(dentry->d_inode); + access = v9ses->flags & V9FS_ACCESS_MASK; + switch (access) { + case V9FS_ACCESS_SINGLE: + case V9FS_ACCESS_USER: + uid = current->fsuid; + any = 0; + break; + + case V9FS_ACCESS_ANY: + uid = v9ses->uid; + any = 1; + break; + + default: + uid = ~0; + any = 0; + break; + } + + fid = v9fs_fid_find(dentry, uid, any); + if (fid) + return fid; + + ds = dentry->d_parent; + fid = v9fs_fid_find(ds, uid, any); + if (!fid) { /* walk from the root */ + n = 0; + for (ds = dentry; !IS_ROOT(ds); ds = ds->d_parent) + n++; + + fid = v9fs_fid_find(ds, uid, any); + if (!fid) { /* the user is not attached to the fs yet */ + if (access == V9FS_ACCESS_SINGLE) + return ERR_PTR(-EPERM); + + if (v9fs_extended(v9ses)) + uname = NULL; + else + uname = v9ses->uname; + + fid = p9_client_attach(v9ses->clnt, NULL, uname, uid, + v9ses->aname); + + if (IS_ERR(fid)) + return fid; + + v9fs_fid_add(ds, fid); + } + } else /* walk from the parent */ + n = 1; + + if (ds == dentry) + return fid; + + wnames = kmalloc(sizeof(char *) * n, GFP_KERNEL); + if (!wnames) + return ERR_PTR(-ENOMEM); + + for (d = dentry, i = (n-1); i >= 0; i--, d = d->d_parent) + wnames[i] = (char *) d->d_name.name; + + clone = 1; + i = 0; + while (i < n) { + l = min(n - i, P9_MAXWELEM); + fid = p9_client_walk(fid, l, &wnames[i], clone); + if (IS_ERR(fid)) { + kfree(wnames); + return fid; + } + + i += l; + clone = 0; + } + + kfree(wnames); + v9fs_fid_add(dentry, fid); + return fid; +} + +struct p9_fid *v9fs_fid_clone(struct dentry *dentry) +{ + struct p9_fid *fid, *ret; + + fid = v9fs_fid_lookup(dentry); + if (IS_ERR(fid)) + return fid; + + ret = p9_client_walk(fid, 0, NULL, 1); + return ret; +} diff --git a/fs/9p/fid.h b/fs/9p/fid.h new file mode 100644 index 0000000..c3bbd6a --- /dev/null +++ b/fs/9p/fid.h @@ -0,0 +1,47 @@ +/* + * V9FS FID Management + * + * Copyright (C) 2005 by Eric Van Hensbergen <ericvh@gmail.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * 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: + * Free Software Foundation + * 51 Franklin Street, Fifth Floor + * Boston, MA 02111-1301 USA + * + */ + +#include <linux/list.h> + +/** + * struct v9fs_dentry - 9p private data stored in dentry d_fsdata + * @lock: protects the fidlist + * @fidlist: list of FIDs currently associated with this dentry + * + * This structure defines the 9p private data associated with + * a particular dentry. In particular, this private data is used + * to lookup which 9P FID handle should be used for a particular VFS + * operation. FID handles are associated with dentries instead of + * inodes in order to more closely map functionality to the Plan 9 + * expected behavior for FID reclaimation and tracking. + * + * See Also: Mapping FIDs to Linux VFS model in + * Design and Implementation of the Linux 9P File System documentation + */ +struct v9fs_dentry { + spinlock_t lock; /* protect fidlist */ + struct list_head fidlist; +}; + +struct p9_fid *v9fs_fid_lookup(struct dentry *dentry); +struct p9_fid *v9fs_fid_clone(struct dentry *dentry); +int v9fs_fid_add(struct dentry *dentry, struct p9_fid *fid); diff --git a/fs/9p/v9fs.c b/fs/9p/v9fs.c new file mode 100644 index 0000000..332b5ff --- /dev/null +++ b/fs/9p/v9fs.c @@ -0,0 +1,328 @@ +/* + * linux/fs/9p/v9fs.c + * + * This file contains functions assisting in mapping VFS to 9P2000 + * + * Copyright (C) 2004-2008 by Eric Van Hensbergen <ericvh@gmail.com> + * Copyright (C) 2002 by Ron Minnich <rminnich@lanl.gov> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * 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: + * Free Software Foundation + * 51 Franklin Street, Fifth Floor + * Boston, MA 02111-1301 USA + * + */ + +#include <linux/module.h> +#include <linux/errno.h> +#include <linux/fs.h> +#include <linux/sched.h> +#include <linux/parser.h> +#include <linux/idr.h> +#include <net/9p/9p.h> +#include <net/9p/client.h> +#include <net/9p/transport.h> +#include "v9fs.h" +#include "v9fs_vfs.h" + +/* + * Option Parsing (code inspired by NFS code) + * NOTE: each transport will parse its own options + */ + +enum { + /* Options that take integer arguments */ + Opt_debug, Opt_dfltuid, Opt_dfltgid, Opt_afid, + /* String options */ + Opt_uname, Opt_remotename, Opt_trans, + /* Options that take no arguments */ + Opt_nodevmap, + /* Cache options */ + Opt_cache_loose, + /* Access options */ + Opt_access, + /* Error token */ + Opt_err +}; + +static const match_table_t tokens = { + {Opt_debug, "debug=%x"}, + {Opt_dfltuid, "dfltuid=%u"}, + {Opt_dfltgid, "dfltgid=%u"}, + {Opt_afid, "afid=%u"}, + {Opt_uname, "uname=%s"}, + {Opt_remotename, "aname=%s"}, + {Opt_nodevmap, "nodevmap"}, + {Opt_cache_loose, "cache=loose"}, + {Opt_cache_loose, "loose"}, + {Opt_access, "access=%s"}, + {Opt_err, NULL} +}; + +/** + * v9fs_parse_options - parse mount options into session structure + * @v9ses: existing v9fs session information + * + * Return 0 upon success, -ERRNO upon failure. + */ + +static int v9fs_parse_options(struct v9fs_session_info *v9ses) +{ + char *options; + substring_t args[MAX_OPT_ARGS]; + char *p; + int option = 0; + char *s, *e; + int ret = 0; + + /* setup defaults */ + v9ses->afid = ~0; + v9ses->debug = 0; + v9ses->cache = 0; + + if (!v9ses->options) + return 0; + + options = kstrdup(v9ses->options, GFP_KERNEL); + if (!options) { + P9_DPRINTK(P9_DEBUG_ERROR, + "failed to allocate copy of option string\n"); + return -ENOMEM; + } + + while ((p = strsep(&options, ",")) != NULL) { + int token; + if (!*p) + continue; + token = match_token(p, tokens, args); + if (token < Opt_uname) { + int r = match_int(&args[0], &option); + if (r < 0) { + P9_DPRINTK(P9_DEBUG_ERROR, + "integer field, but no integer?\n"); + ret = r; + continue; + } + } + switch (token) { + case Opt_debug: + v9ses->debug = option; +#ifdef CONFIG_NET_9P_DEBUG + p9_debug_level = option; +#endif + break; + + case Opt_dfltuid: + v9ses->dfltuid = option; + break; + case Opt_dfltgid: + v9ses->dfltgid = option; + break; + case Opt_afid: + v9ses->afid = option; + break; + case Opt_uname: + match_strlcpy(v9ses->uname, &args[0], PATH_MAX); + break; + case Opt_remotename: + match_strlcpy(v9ses->aname, &args[0], PATH_MAX); + break; + case Opt_nodevmap: + v9ses->nodev = 1; + break; + case Opt_cache_loose: + v9ses->cache = CACHE_LOOSE; + break; + + case Opt_access: + s = match_strdup(&args[0]); + if (!s) { + P9_DPRINTK(P9_DEBUG_ERROR, + "failed to allocate copy" + " of option argument\n"); + ret = -ENOMEM; + break; + } + v9ses->flags &= ~V9FS_ACCESS_MASK; + if (strcmp(s, "user") == 0) + v9ses->flags |= V9FS_ACCESS_USER; + else if (strcmp(s, "any") == 0) + v9ses->flags |= V9FS_ACCESS_ANY; + else { + v9ses->flags |= V9FS_ACCESS_SINGLE; + v9ses->uid = simple_strtoul(s, &e, 10); + if (*e != '\0') + v9ses->uid = ~0; + } + kfree(s); + break; + + default: + continue; + } + } + kfree(options); + return ret; +} + +/** + * v9fs_session_init - initialize session + * @v9ses: session information structure + * @dev_name: device being mounted + * @data: options + * + */ + +struct p9_fid *v9fs_session_init(struct v9fs_session_info *v9ses, + const char *dev_name, char *data) +{ + int retval = -EINVAL; + struct p9_fid *fid; + int rc; + + v9ses->uname = __getname(); + if (!v9ses->uname) + return ERR_PTR(-ENOMEM); + + v9ses->aname = __getname(); + if (!v9ses->aname) { + __putname(v9ses->uname); + return ERR_PTR(-ENOMEM); + } + + v9ses->flags = V9FS_EXTENDED | V9FS_ACCESS_USER; + strcpy(v9ses->uname, V9FS_DEFUSER); + strcpy(v9ses->aname, V9FS_DEFANAME); + v9ses->uid = ~0; + v9ses->dfltuid = V9FS_DEFUID; + v9ses->dfltgid = V9FS_DEFGID; + if (data) { + v9ses->options = kstrdup(data, GFP_KERNEL); + if (!v9ses->options) { + P9_DPRINTK(P9_DEBUG_ERROR, + "failed to allocate copy of option string\n"); + retval = -ENOMEM; + goto error; + } + } + + rc = v9fs_parse_options(v9ses); + if (rc < 0) { + retval = rc; + goto error; + } + + v9ses->clnt = p9_client_create(dev_name, v9ses->options); + + if (IS_ERR(v9ses->clnt)) { + retval = PTR_ERR(v9ses->clnt); + v9ses->clnt = NULL; + P9_DPRINTK(P9_DEBUG_ERROR, "problem initializing 9p client\n"); + goto error; + } + + if (!v9ses->clnt->dotu) + v9ses->flags &= ~V9FS_EXTENDED; + + v9ses->maxdata = v9ses->clnt->msize - P9_IOHDRSZ; + + /* for legacy mode, fall back to V9FS_ACCESS_ANY */ + if (!v9fs_extended(v9ses) && + ((v9ses->flags&V9FS_ACCESS_MASK) == V9FS_ACCESS_USER)) { + + v9ses->flags &= ~V9FS_ACCESS_MASK; + v9ses->flags |= V9FS_ACCESS_ANY; + v9ses->uid = ~0; + } + + fid = p9_client_attach(v9ses->clnt, NULL, v9ses->uname, ~0, + v9ses->aname); + if (IS_ERR(fid)) { + retval = PTR_ERR(fid); + fid = NULL; + P9_DPRINTK(P9_DEBUG_ERROR, "cannot attach\n"); + goto error; + } + + if ((v9ses->flags & V9FS_ACCESS_MASK) == V9FS_ACCESS_SINGLE) + fid->uid = v9ses->uid; + else + fid->uid = ~0; + + return fid; + +error: + return ERR_PTR(retval); +} + +/** + * v9fs_session_close - shutdown a session + * @v9ses: session information structure + * + */ + +void v9fs_session_close(struct v9fs_session_info *v9ses) +{ + if (v9ses->clnt) { + p9_client_destroy(v9ses->clnt); + v9ses->clnt = NULL; + } + + __putname(v9ses->uname); + __putname(v9ses->aname); + kfree(v9ses->options); +} + +/** + * v9fs_session_cancel - terminate a session + * @v9ses: session to terminate + * + * mark transport as disconnected and cancel all pending requests. + */ + +void v9fs_session_cancel(struct v9fs_session_info *v9ses) { + P9_DPRINTK(P9_DEBUG_ERROR, "cancel session %p\n", v9ses); + p9_client_disconnect(v9ses->clnt); +} + +extern int v9fs_error_init(void); + +/** + * v9fs_init - Initialize module + * + */ + +static int __init init_v9fs(void) +{ + printk(KERN_INFO "Installing v9fs 9p2000 file system support\n"); + /* TODO: Setup list of registered trasnport modules */ + return register_filesystem(&v9fs_fs_type); +} + +/** + * v9fs_init - shutdown module + * + */ + +static void __exit exit_v9fs(void) +{ + unregister_filesystem(&v9fs_fs_type); +} + +module_init(init_v9fs) +module_exit(exit_v9fs) + +MODULE_AUTHOR("Latchesar Ionkov <lucho@ionkov.net>"); +MODULE_AUTHOR("Eric Van Hensbergen <ericvh@gmail.com>"); +MODULE_AUTHOR("Ron Minnich <rminnich@lanl.gov>"); +MODULE_LICENSE("GPL"); diff --git a/fs/9p/v9fs.h b/fs/9p/v9fs.h new file mode 100644 index 0000000..a7d5671 --- /dev/null +++ b/fs/9p/v9fs.h @@ -0,0 +1,123 @@ +/* + * V9FS definitions. + * + * Copyright (C) 2004-2008 by Eric Van Hensbergen <ericvh@gmail.com> + * Copyright (C) 2002 by Ron Minnich <rminnich@lanl.gov> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * 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: + * Free Software Foundation + * 51 Franklin Street, Fifth Floor + * Boston, MA 02111-1301 USA + * + */ + +/** + * enum p9_session_flags - option flags for each 9P session + * @V9FS_EXTENDED: whether or not to use 9P2000.u extensions + * @V9FS_ACCESS_SINGLE: only the mounting user can access the hierarchy + * @V9FS_ACCESS_USER: a new attach will be issued for every user (default) + * @V9FS_ACCESS_ANY: use a single attach for all users + * @V9FS_ACCESS_MASK: bit mask of different ACCESS options + * + * Session flags reflect options selected by users at mount time + */ +enum p9_session_flags { + V9FS_EXTENDED = 0x01, + V9FS_ACCESS_SINGLE = 0x02, + V9FS_ACCESS_USER = 0x04, + V9FS_ACCESS_ANY = 0x06, + V9FS_ACCESS_MASK = 0x06, +}; + +/* possible values of ->cache */ +/** + * enum p9_cache_modes - user specified cache preferences + * @CACHE_NONE: do not cache data, dentries, or directory contents (default) + * @CACHE_LOOSE: cache data, dentries, and directory contents w/no consistency + * + * eventually support loose, tight, time, session, default always none + */ + +enum p9_cache_modes { + CACHE_NONE, + CACHE_LOOSE, +}; + +/** + * struct v9fs_session_info - per-instance session information + * @flags: session options of type &p9_session_flags + * @nodev: set to 1 to disable device mapping + * @debug: debug level + * @afid: authentication handle + * @cache: cache mode of type &p9_cache_modes + * @options: copy of options string given by user + * @uname: string user name to mount hierarchy as + * @aname: mount specifier for remote hierarchy + * @maxdata: maximum data to be sent/recvd per protocol message + * @dfltuid: default numeric userid to mount hierarchy as + * @dfltgid: default numeric groupid to mount hierarchy as + * @uid: if %V9FS_ACCESS_SINGLE, the numeric uid which mounted the hierarchy + * @clnt: reference to 9P network client instantiated for this session + * @debugfs_dir: reference to debugfs_dir which can be used for add'l debug + * + * This structure holds state for each session instance established during + * a sys_mount() . + * + * Bugs: there seems to be a lot of state which could be condensed and/or + * removed. + */ + +struct v9fs_session_info { + /* options */ + unsigned char flags; + unsigned char nodev; + unsigned short debug; + unsigned int afid; + unsigned int cache; + + char *options; /* copy of mount options */ + char *uname; /* user name to mount as */ + char *aname; /* name of remote hierarchy being mounted */ + unsigned int maxdata; /* max data for client interface */ + unsigned int dfltuid; /* default uid/muid for legacy support */ + unsigned int dfltgid; /* default gid for legacy support */ + u32 uid; /* if ACCESS_SINGLE, the uid that has access */ + struct p9_client *clnt; /* 9p client */ + struct dentry *debugfs_dir; +}; + +extern struct dentry *v9fs_debugfs_root; + +struct p9_fid *v9fs_session_init(struct v9fs_session_info *, const char *, + char *); +void v9fs_session_close(struct v9fs_session_info *v9ses); +void v9fs_session_cancel(struct v9fs_session_info *v9ses); + +#define V9FS_MAGIC 0x01021997 + +/* other default globals */ +#define V9FS_PORT 564 +#define V9FS_DEFUSER "nobody" +#define V9FS_DEFANAME "" +#define V9FS_DEFUID (-2) +#define V9FS_DEFGID (-2) + +static inline struct v9fs_session_info *v9fs_inode2v9ses(struct inode *inode) +{ + return (inode->i_sb->s_fs_info); +} + +static inline int v9fs_extended(struct v9fs_session_info *v9ses) +{ + return v9ses->flags & V9FS_EXTENDED; +} diff --git a/fs/9p/v9fs_vfs.h b/fs/9p/v9fs_vfs.h new file mode 100644 index 0000000..c295ba7 --- /dev/null +++ b/fs/9p/v9fs_vfs.h @@ -0,0 +1,56 @@ +/* + * V9FS VFS extensions. + * + * Copyright (C) 2004 by Eric Van Hensbergen <ericvh@gmail.com> + * Copyright (C) 2002 by Ron Minnich <rminnich@lanl.gov> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * 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: + * Free Software Foundation + * 51 Franklin Street, Fifth Floor + * Boston, MA 02111-1301 USA + * + */ + +/* plan9 semantics are that created files are implicitly opened. + * But linux semantics are that you call create, then open. + * the plan9 approach is superior as it provides an atomic + * open. + * we track the create fid here. When the file is opened, if fidopen is + * non-zero, we use the fid and can skip some steps. + * there may be a better way to do this, but I don't know it. + * one BAD way is to clunk the fid on create, then open it again: + * you lose the atomicity of file open + */ + +/* special case: + * unlink calls remove, which is an implicit clunk. So we have to track + * that kind of thing so that we don't try to clunk a dead fid. + */ + +extern struct file_system_type v9fs_fs_type; +extern const struct address_space_operations v9fs_addr_operations; +extern const struct file_operations v9fs_file_operations; +extern const struct file_operations v9fs_dir_operations; +extern struct dentry_operations v9fs_dentry_operations; +extern struct dentry_operations v9fs_cached_dentry_operations; + +struct inode *v9fs_get_inode(struct super_block *sb, int mode); +ino_t v9fs_qid2ino(struct p9_qid *qid); +void v9fs_stat2inode(struct p9_wstat *, struct inode *, struct super_block *); +int v9fs_dir_release(struct inode *inode, struct file *filp); +int v9fs_file_open(struct inode *inode, struct file *file); +void v9fs_inode2stat(struct inode *inode, struct p9_wstat *stat); +void v9fs_dentry_release(struct dentry *); +int v9fs_uflags2omode(int uflags, int extended); + +ssize_t v9fs_file_readn(struct file *, char *, char __user *, u32, u64); diff --git a/fs/9p/vfs_addr.c b/fs/9p/vfs_addr.c new file mode 100644 index 0000000..6fcb1e7 --- /dev/null +++ b/fs/9p/vfs_addr.c @@ -0,0 +1,77 @@ +/* + * linux/fs/9p/vfs_addr.c + * + * This file contians vfs address (mmap) ops for 9P2000. + * + * Copyright (C) 2005 by Eric Van Hensbergen <ericvh@gmail.com> + * Copyright (C) 2002 by Ron Minnich <rminnich@lanl.gov> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * 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: + * Free Software Foundation + * 51 Franklin Street, Fifth Floor + * Boston, MA 02111-1301 USA + * + */ + +#include <linux/module.h> +#include <linux/errno.h> +#include <linux/fs.h> +#include <linux/file.h> +#include <linux/stat.h> +#include <linux/string.h> +#include <linux/inet.h> +#include <linux/pagemap.h> +#include <linux/idr.h> +#include <linux/sched.h> +#include <net/9p/9p.h> +#include <net/9p/client.h> + +#include "v9fs.h" +#include "v9fs_vfs.h" + +/** + * v9fs_vfs_readpage - read an entire page in from 9P + * + * @filp: file being read + * @page: structure to page + * + */ + +static int v9fs_vfs_readpage(struct file *filp, struct page *page) +{ + int retval; + loff_t offset; + char *buffer; + + P9_DPRINTK(P9_DEBUG_VFS, "\n"); + buffer = kmap(page); + offset = page_offset(page); + + retval = v9fs_file_readn(filp, buffer, NULL, offset, PAGE_CACHE_SIZE); + if (retval < 0) + goto done; + + memset(buffer + retval, 0, PAGE_CACHE_SIZE - retval); + flush_dcache_page(page); + SetPageUptodate(page); + retval = 0; + +done: + kunmap(page); + unlock_page(page); + return retval; +} + +const struct address_space_operations v9fs_addr_operations = { + .readpage = v9fs_vfs_readpage, +}; diff --git a/fs/9p/vfs_dentry.c b/fs/9p/vfs_dentry.c new file mode 100644 index 0000000..06dcc7c --- /dev/null +++ b/fs/9p/vfs_dentry.c @@ -0,0 +1,115 @@ +/* + * linux/fs/9p/vfs_dentry.c + * + * This file contians vfs dentry ops for the 9P2000 protocol. + * + * Copyright (C) 2004 by Eric Van Hensbergen <ericvh@gmail.com> + * Copyright (C) 2002 by Ron Minnich <rminnich@lanl.gov> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * 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: + * Free Software Foundation + * 51 Franklin Street, Fifth Floor + * Boston, MA 02111-1301 USA + * + */ + +#include <linux/module.h> +#include <linux/errno.h> +#include <linux/fs.h> +#include <linux/file.h> +#include <linux/pagemap.h> +#include <linux/stat.h> +#include <linux/string.h> +#include <linux/inet.h> +#include <linux/namei.h> +#include <linux/idr.h> +#include <linux/sched.h> +#include <net/9p/9p.h> +#include <net/9p/client.h> + +#include "v9fs.h" +#include "v9fs_vfs.h" +#include "fid.h" + +/** + * v9fs_dentry_delete - called when dentry refcount equals 0 + * @dentry: dentry in question + * + * By returning 1 here we should remove cacheing of unused + * dentry components. + * + */ + +static int v9fs_dentry_delete(struct dentry *dentry) +{ + P9_DPRINTK(P9_DEBUG_VFS, " dentry: %s (%p)\n", dentry->d_name.name, + dentry); + + return 1; +} + +/** + * v9fs_cached_dentry_delete - called when dentry refcount equals 0 + * @dentry: dentry in question + * + * Only return 1 if our inode is invalid. Only non-synthetic files + * (ones without mtime == 0) should be calling this function. + * + */ + +static int v9fs_cached_dentry_delete(struct dentry *dentry) +{ + struct inode *inode = dentry->d_inode; + P9_DPRINTK(P9_DEBUG_VFS, " dentry: %s (%p)\n", dentry->d_name.name, + dentry); + + if(!inode) + return 1; + + return 0; +} + +/** + * v9fs_dentry_release - called when dentry is going to be freed + * @dentry: dentry that is being release + * + */ + +void v9fs_dentry_release(struct dentry *dentry) +{ + struct v9fs_dentry *dent; + struct p9_fid *temp, *current_fid; + + P9_DPRINTK(P9_DEBUG_VFS, " dentry: %s (%p)\n", dentry->d_name.name, + dentry); + dent = dentry->d_fsdata; + if (dent) { + list_for_each_entry_safe(current_fid, temp, &dent->fidlist, + dlist) { + p9_client_clunk(current_fid); + } + + kfree(dent); + dentry->d_fsdata = NULL; + } +} + +struct dentry_operations v9fs_cached_dentry_operations = { + .d_delete = v9fs_cached_dentry_delete, + .d_release = v9fs_dentry_release, +}; + +struct dentry_operations v9fs_dentry_operations = { + .d_delete = v9fs_dentry_delete, + .d_release = v9fs_dentry_release, +}; diff --git a/fs/9p/vfs_dir.c b/fs/9p/vfs_dir.c new file mode 100644 index 0000000..873cd31 --- /dev/null +++ b/fs/9p/vfs_dir.c @@ -0,0 +1,152 @@ +/* + * linux/fs/9p/vfs_dir.c + * + * This file contains vfs directory ops for the 9P2000 protocol. + * + * Copyright (C) 2004 by Eric Van Hensbergen <ericvh@gmail.com> + * Copyright (C) 2002 by Ron Minnich <rminnich@lanl.gov> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * 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: + * Free Software Foundation + * 51 Franklin Street, Fifth Floor + * Boston, MA 02111-1301 USA + * + */ + +#include <linux/module.h> +#include <linux/errno.h> +#include <linux/fs.h> +#include <linux/file.h> +#include <linux/stat.h> +#include <linux/string.h> +#include <linux/sched.h> +#include <linux/inet.h> +#include <linux/idr.h> +#include <net/9p/9p.h> +#include <net/9p/client.h> + +#include "v9fs.h" +#include "v9fs_vfs.h" +#include "fid.h" + +/** + * dt_type - return file type + * @mistat: mistat structure + * + */ + +static inline int dt_type(struct p9_wstat *mistat) +{ + unsigned long perm = mistat->mode; + int rettype = DT_REG; + + if (perm & P9_DMDIR) + rettype = DT_DIR; + if (perm & P9_DMSYMLINK) + rettype = DT_LNK; + + return rettype; +} + +/** + * v9fs_dir_readdir - read a directory + * @filp: opened file structure + * @dirent: directory structure ??? + * @filldir: function to populate directory structure ??? + * + */ + +static int v9fs_dir_readdir(struct file *filp, void *dirent, filldir_t filldir) +{ + int over; + struct p9_wstat st; + int err; + struct p9_fid *fid; + int buflen; + char *statbuf; + int n, i = 0; + + P9_DPRINTK(P9_DEBUG_VFS, "name %s\n", filp->f_path.dentry->d_name.name); + fid = filp->private_data; + + buflen = fid->clnt->msize - P9_IOHDRSZ; + statbuf = kmalloc(buflen, GFP_KERNEL); + if (!statbuf) + return -ENOMEM; + + while (1) { + err = v9fs_file_readn(filp, statbuf, NULL, buflen, + fid->rdir_fpos); + if (err <= 0) + break; + + n = err; + while (i < n) { + err = p9stat_read(statbuf + i, buflen-i, &st, + fid->clnt->dotu); + if (err) { + P9_DPRINTK(P9_DEBUG_VFS, "returned %d\n", err); + err = -EIO; + p9stat_free(&st); + goto free_and_exit; + } + + i += st.size+2; + fid->rdir_fpos += st.size+2; + + over = filldir(dirent, st.name, strlen(st.name), + filp->f_pos, v9fs_qid2ino(&st.qid), dt_type(&st)); + + filp->f_pos += st.size+2; + + p9stat_free(&st); + + if (over) { + err = 0; + goto free_and_exit; + } + } + } + +free_and_exit: + kfree(statbuf); + return err; +} + + +/** + * v9fs_dir_release - close a directory + * @inode: inode of the directory + * @filp: file pointer to a directory + * + */ + +int v9fs_dir_release(struct inode *inode, struct file *filp) +{ + struct p9_fid *fid; + + fid = filp->private_data; + P9_DPRINTK(P9_DEBUG_VFS, + "inode: %p filp: %p fid: %d\n", inode, filp, fid->fid); + filemap_write_and_wait(inode->i_mapping); + p9_client_clunk(fid); + return 0; +} + +const struct file_operations v9fs_dir_operations = { + .read = generic_read_dir, + .llseek = generic_file_llseek, + .readdir = v9fs_dir_readdir, + .open = v9fs_file_open, + .release = v9fs_dir_release, +}; diff --git a/fs/9p/vfs_file.c b/fs/9p/vfs_file.c new file mode 100644 index 0000000..68bf2af --- /dev/null +++ b/fs/9p/vfs_file.c @@ -0,0 +1,272 @@ +/* + * linux/fs/9p/vfs_file.c + * + * This file contians vfs file ops for 9P2000. + * + * Copyright (C) 2004 by Eric Van Hensbergen <ericvh@gmail.com> + * Copyright (C) 2002 by Ron Minnich <rminnich@lanl.gov> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * 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: + * Free Software Foundation + * 51 Franklin Street, Fifth Floor + * Boston, MA 02111-1301 USA + * + */ + +#include <linux/module.h> +#include <linux/errno.h> +#include <linux/fs.h> +#include <linux/sched.h> +#include <linux/file.h> +#include <linux/stat.h> +#include <linux/string.h> +#include <linux/inet.h> +#include <linux/list.h> +#include <asm/uaccess.h> +#include <linux/idr.h> +#include <net/9p/9p.h> +#include <net/9p/client.h> + +#include "v9fs.h" +#include "v9fs_vfs.h" +#include "fid.h" + +static const struct file_operations v9fs_cached_file_operations; + +/** + * v9fs_file_open - open a file (or directory) + * @inode: inode to be opened + * @file: file being opened + * + */ + +int v9fs_file_open(struct inode *inode, struct file *file) +{ + int err; + struct v9fs_session_info *v9ses; + struct p9_fid *fid; + int omode; + + P9_DPRINTK(P9_DEBUG_VFS, "inode: %p file: %p \n", inode, file); + v9ses = v9fs_inode2v9ses(inode); + omode = v9fs_uflags2omode(file->f_flags, v9fs_extended(v9ses)); + fid = file->private_data; + if (!fid) { + fid = v9fs_fid_clone(file->f_path.dentry); + if (IS_ERR(fid)) + return PTR_ERR(fid); + + err = p9_client_open(fid, omode); + if (err < 0) { + p9_client_clunk(fid); + return err; + } + if (omode & P9_OTRUNC) { + inode->i_size = 0; + inode->i_blocks = 0; + } + if ((file->f_flags & O_APPEND) && (!v9fs_extended(v9ses))) + generic_file_llseek(file, 0, SEEK_END); + } + + file->private_data = fid; + if ((fid->qid.version) && (v9ses->cache)) { + P9_DPRINTK(P9_DEBUG_VFS, "cached"); + /* enable cached file options */ + if(file->f_op == &v9fs_file_operations) + file->f_op = &v9fs_cached_file_operations; + } + + return 0; +} + +/** + * v9fs_file_lock - lock a file (or directory) + * @filp: file to be locked + * @cmd: lock command + * @fl: file lock structure + * + * Bugs: this looks like a local only lock, we should extend into 9P + * by using open exclusive + */ + +static int v9fs_file_lock(struct file *filp, int cmd, struct file_lock *fl) +{ + int res = 0; + struct inode *inode = filp->f_path.dentry->d_inode; + + P9_DPRINTK(P9_DEBUG_VFS, "filp: %p lock: %p\n", filp, fl); + + /* No mandatory locks */ + if (__mandatory_lock(inode)) + return -ENOLCK; + + if ((IS_SETLK(cmd) || IS_SETLKW(cmd)) && fl->fl_type != F_UNLCK) { + filemap_write_and_wait(inode->i_mapping); + invalidate_mapping_pages(&inode->i_data, 0, -1); + } + + return res; +} + +/** + * v9fs_file_readn - read from a file + * @filp: file pointer to read + * @data: data buffer to read data into + * @udata: user data buffer to read data into + * @count: size of buffer + * @offset: offset at which to read data + * + */ + +ssize_t +v9fs_file_readn(struct file *filp, char *data, char __user *udata, u32 count, + u64 offset) +{ + int n, total; + struct p9_fid *fid = filp->private_data; + + P9_DPRINTK(P9_DEBUG_VFS, "fid %d offset %llu count %d\n", fid->fid, + (long long unsigned) offset, count); + + n = 0; + total = 0; + do { + n = p9_client_read(fid, data, udata, offset, count); + if (n <= 0) + break; + + if (data) + data += n; + if (udata) + udata += n; + + offset += n; + count -= n; + total += n; + } while (count > 0 && n == (fid->clnt->msize - P9_IOHDRSZ)); + + if (n < 0) + total = n; + + return total; +} + +/** + * v9fs_file_read - read from a file + * @filp: file pointer to read + * @udata: user data buffer to read data into + * @count: size of buffer + * @offset: offset at which to read data + * + */ + +static ssize_t +v9fs_file_read(struct file *filp, char __user *udata, size_t count, + loff_t * offset) +{ + int ret; + struct p9_fid *fid; + + P9_DPRINTK(P9_DEBUG_VFS, "count %zu offset %lld\n", count, *offset); + fid = filp->private_data; + + if (count > (fid->clnt->msize - P9_IOHDRSZ)) + ret = v9fs_file_readn(filp, NULL, udata, count, *offset); + else + ret = p9_client_read(fid, NULL, udata, *offset, count); + + if (ret > 0) + *offset += ret; + + return ret; +} + +/** + * v9fs_file_write - write to a file + * @filp: file pointer to write + * @data: data buffer to write data from + * @count: size of buffer + * @offset: offset at which to write data + * + */ + +static ssize_t +v9fs_file_write(struct file *filp, const char __user * data, + size_t count, loff_t * offset) +{ + int n, rsize, total = 0; + struct p9_fid *fid; + struct p9_client *clnt; + struct inode *inode = filp->f_path.dentry->d_inode; + int origin = *offset; + + P9_DPRINTK(P9_DEBUG_VFS, "data %p count %d offset %x\n", data, + (int)count, (int)*offset); + + fid = filp->private_data; + clnt = fid->clnt; + + rsize = fid->iounit; + if (!rsize || rsize > clnt->msize-P9_IOHDRSZ) + rsize = clnt->msize - P9_IOHDRSZ; + + do { + if (count < rsize) + rsize = count; + + n = p9_client_write(fid, NULL, data+total, *offset+total, + rsize); + if (n <= 0) + break; + count -= n; + total += n; + } while (count > 0); + + if (total > 0) { + invalidate_inode_pages2_range(inode->i_mapping, origin, + origin+total); + *offset += total; + } + + if (*offset > inode->i_size) { + inode->i_size = *offset; + inode->i_blocks = (inode->i_size + 512 - 1) >> 9; + } + + if (n < 0) + return n; + + return total; +} + +static const struct file_operations v9fs_cached_file_operations = { + .llseek = generic_file_llseek, + .read = do_sync_read, + .aio_read = generic_file_aio_read, + .write = v9fs_file_write, + .open = v9fs_file_open, + .release = v9fs_dir_release, + .lock = v9fs_file_lock, + .mmap = generic_file_readonly_mmap, +}; + +const struct file_operations v9fs_file_operations = { + .llseek = generic_file_llseek, + .read = v9fs_file_read, + .write = v9fs_file_write, + .open = v9fs_file_open, + .release = v9fs_dir_release, + .lock = v9fs_file_lock, + .mmap = generic_file_readonly_mmap, +}; diff --git a/fs/9p/vfs_inode.c b/fs/9p/vfs_inode.c new file mode 100644 index 0000000..2dfcf54 --- /dev/null +++ b/fs/9p/vfs_inode.c @@ -0,0 +1,1203 @@ +/* + * linux/fs/9p/vfs_inode.c + * + * This file contains vfs inode ops for the 9P2000 protocol. + * + * Copyright (C) 2004 by Eric Van Hensbergen <ericvh@gmail.com> + * Copyright (C) 2002 by Ron Minnich <rminnich@lanl.gov> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * 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: + * Free Software Foundation + * 51 Franklin Street, Fifth Floor + * Boston, MA 02111-1301 USA + * + */ + +#include <linux/module.h> +#include <linux/errno.h> +#include <linux/fs.h> +#include <linux/file.h> +#include <linux/pagemap.h> +#include <linux/stat.h> +#include <linux/string.h> +#include <linux/inet.h> +#include <linux/namei.h> +#include <linux/idr.h> +#include <linux/sched.h> +#include <net/9p/9p.h> +#include <net/9p/client.h> + +#include "v9fs.h" +#include "v9fs_vfs.h" +#include "fid.h" + +static const struct inode_operations v9fs_dir_inode_operations; +static const struct inode_operations v9fs_dir_inode_operations_ext; +static const struct inode_operations v9fs_file_inode_operations; +static const struct inode_operations v9fs_symlink_inode_operations; + +/** + * unixmode2p9mode - convert unix mode bits to plan 9 + * @v9ses: v9fs session information + * @mode: mode to convert + * + */ + +static int unixmode2p9mode(struct v9fs_session_info *v9ses, int mode) +{ + int res; + res = mode & 0777; + if (S_ISDIR(mode)) + res |= P9_DMDIR; + if (v9fs_extended(v9ses)) { + if (S_ISLNK(mode)) + res |= P9_DMSYMLINK; + if (v9ses->nodev == 0) { + if (S_ISSOCK(mode)) + res |= P9_DMSOCKET; + if (S_ISFIFO(mode)) + res |= P9_DMNAMEDPIPE; + if (S_ISBLK(mode)) + res |= P9_DMDEVICE; + if (S_ISCHR(mode)) + res |= P9_DMDEVICE; + } + + if ((mode & S_ISUID) == S_ISUID) + res |= P9_DMSETUID; + if ((mode & S_ISGID) == S_ISGID) + res |= P9_DMSETGID; + if ((mode & S_ISVTX) == S_ISVTX) + res |= P9_DMSETVTX; + if ((mode & P9_DMLINK)) + res |= P9_DMLINK; + } + + return res; +} + +/** + * p9mode2unixmode- convert plan9 mode bits to unix mode bits + * @v9ses: v9fs session information + * @mode: mode to convert + * + */ + +static int p9mode2unixmode(struct v9fs_session_info *v9ses, int mode) +{ + int res; + + res = mode & 0777; + + if ((mode & P9_DMDIR) == P9_DMDIR) + res |= S_IFDIR; + else if ((mode & P9_DMSYMLINK) && (v9fs_extended(v9ses))) + res |= S_IFLNK; + else if ((mode & P9_DMSOCKET) && (v9fs_extended(v9ses)) + && (v9ses->nodev == 0)) + res |= S_IFSOCK; + else if ((mode & P9_DMNAMEDPIPE) && (v9fs_extended(v9ses)) + && (v9ses->nodev == 0)) + res |= S_IFIFO; + else if ((mode & P9_DMDEVICE) && (v9fs_extended(v9ses)) + && (v9ses->nodev == 0)) + res |= S_IFBLK; + else + res |= S_IFREG; + + if (v9fs_extended(v9ses)) { + if ((mode & P9_DMSETUID) == P9_DMSETUID) + res |= S_ISUID; + + if ((mode & P9_DMSETGID) == P9_DMSETGID) + res |= S_ISGID; + + if ((mode & P9_DMSETVTX) == P9_DMSETVTX) + res |= S_ISVTX; + } + + return res; +} + +/** + * v9fs_uflags2omode- convert posix open flags to plan 9 mode bits + * @uflags: flags to convert + * @extended: if .u extensions are active + */ + +int v9fs_uflags2omode(int uflags, int extended) +{ + int ret; + + ret = 0; + switch (uflags&3) { + default: + case O_RDONLY: + ret = P9_OREAD; + break; + + case O_WRONLY: + ret = P9_OWRITE; + break; + + case O_RDWR: + ret = P9_ORDWR; + break; + } + + if (uflags & O_TRUNC) + ret |= P9_OTRUNC; + + if (extended) { + if (uflags & O_EXCL) + ret |= P9_OEXCL; + + if (uflags & O_APPEND) + ret |= P9_OAPPEND; + } + + return ret; +} + +/** + * v9fs_blank_wstat - helper function to setup a 9P stat structure + * @v9ses: 9P session info (for determining extended mode) + * @wstat: structure to initialize + * + */ + +static void +v9fs_blank_wstat(struct p9_wstat *wstat) +{ + wstat->type = ~0; + wstat->dev = ~0; + wstat->qid.type = ~0; + wstat->qid.version = ~0; + *((long long *)&wstat->qid.path) = ~0; + wstat->mode = ~0; + wstat->atime = ~0; + wstat->mtime = ~0; + wstat->length = ~0; + wstat->name = NULL; + wstat->uid = NULL; + wstat->gid = NULL; + wstat->muid = NULL; + wstat->n_uid = ~0; + wstat->n_gid = ~0; + wstat->n_muid = ~0; + wstat->extension = NULL; +} + +/** + * v9fs_get_inode - helper function to setup an inode + * @sb: superblock + * @mode: mode to setup inode with + * + */ + +struct inode *v9fs_get_inode(struct super_block *sb, int mode) +{ + struct inode *inode; + struct v9fs_session_info *v9ses = sb->s_fs_info; + + P9_DPRINTK(P9_DEBUG_VFS, "super block: %p mode: %o\n", sb, mode); + + inode = new_inode(sb); + if (inode) { + inode->i_mode = mode; + inode->i_uid = current->fsuid; + inode->i_gid = current->fsgid; + inode->i_blocks = 0; + inode->i_rdev = 0; + inode->i_atime = inode->i_mtime = inode->i_ctime = CURRENT_TIME; + inode->i_mapping->a_ops = &v9fs_addr_operations; + + switch (mode & S_IFMT) { + case S_IFIFO: + case S_IFBLK: + case S_IFCHR: + case S_IFSOCK: + if (!v9fs_extended(v9ses)) { + P9_DPRINTK(P9_DEBUG_ERROR, + "special files without extended mode\n"); + return ERR_PTR(-EINVAL); + } + init_special_inode(inode, inode->i_mode, + inode->i_rdev); + break; + case S_IFREG: + inode->i_op = &v9fs_file_inode_operations; + inode->i_fop = &v9fs_file_operations; + break; + case S_IFLNK: + if (!v9fs_extended(v9ses)) { + P9_DPRINTK(P9_DEBUG_ERROR, + "extended modes used w/o 9P2000.u\n"); + return ERR_PTR(-EINVAL); + } + inode->i_op = &v9fs_symlink_inode_operations; + break; + case S_IFDIR: + inc_nlink(inode); + if (v9fs_extended(v9ses)) + inode->i_op = &v9fs_dir_inode_operations_ext; + else + inode->i_op = &v9fs_dir_inode_operations; + inode->i_fop = &v9fs_dir_operations; + break; + default: + P9_DPRINTK(P9_DEBUG_ERROR, + "BAD mode 0x%x S_IFMT 0x%x\n", + mode, mode & S_IFMT); + return ERR_PTR(-EINVAL); + } + } else { + P9_EPRINTK(KERN_WARNING, "Problem allocating inode\n"); + return ERR_PTR(-ENOMEM); + } + return inode; +} + +/* +static struct v9fs_fid* +v9fs_clone_walk(struct v9fs_session_info *v9ses, u32 fid, struct dentry *dentry) +{ + int err; + int nfid; + struct v9fs_fid *ret; + struct v9fs_fcall *fcall; + + nfid = v9fs_get_idpool(&v9ses->fidpool); + if (nfid < 0) { + eprintk(KERN_WARNING, "no free fids available\n"); + return ERR_PTR(-ENOSPC); + } + + err = v9fs_t_walk(v9ses, fid, nfid, (char *) dentry->d_name.name, + &fcall); + + if (err < 0) { + if (fcall && fcall->id == RWALK) + goto clunk_fid; + + PRINT_FCALL_ERROR("walk error", fcall); + v9fs_put_idpool(nfid, &v9ses->fidpool); + goto error; + } + + kfree(fcall); + fcall = NULL; + ret = v9fs_fid_create(v9ses, nfid); + if (!ret) { + err = -ENOMEM; + goto clunk_fid; + } + + err = v9fs_fid_insert(ret, dentry); + if (err < 0) { + v9fs_fid_destroy(ret); + goto clunk_fid; + } + + return ret; + +clunk_fid: + v9fs_t_clunk(v9ses, nfid); + +error: + kfree(fcall); + return ERR_PTR(err); +} +*/ + +/** + * v9fs_inode_from_fid - populate an inode by issuing a attribute request + * @v9ses: session information + * @fid: fid to issue attribute request for + * @sb: superblock on which to create inode + * + */ + +static struct inode * +v9fs_inode_from_fid(struct v9fs_session_info *v9ses, struct p9_fid *fid, + struct super_block *sb) +{ + int err, umode; + struct inode *ret; + struct p9_wstat *st; + + ret = NULL; + st = p9_client_stat(fid); + if (IS_ERR(st)) { + err = PTR_ERR(st); + st = NULL; + goto error; + } + + umode = p9mode2unixmode(v9ses, st->mode); + ret = v9fs_get_inode(sb, umode); + if (IS_ERR(ret)) { + err = PTR_ERR(ret); + ret = NULL; + goto error; + } + + v9fs_stat2inode(st, ret, sb); + ret->i_ino = v9fs_qid2ino(&st->qid); + kfree(st); + return ret; + +error: + kfree(st); + if (ret) + iput(ret); + + return ERR_PTR(err); +} + +/** + * v9fs_remove - helper function to remove files and directories + * @dir: directory inode that is being deleted + * @file: dentry that is being deleted + * @rmdir: removing a directory + * + */ + +static int v9fs_remove(struct inode *dir, struct dentry *file, int rmdir) +{ + struct inode *file_inode; + struct v9fs_session_info *v9ses; + struct p9_fid *v9fid; + + P9_DPRINTK(P9_DEBUG_VFS, "inode: %p dentry: %p rmdir: %d\n", dir, file, + rmdir); + + file_inode = file->d_inode; + v9ses = v9fs_inode2v9ses(file_inode); + v9fid = v9fs_fid_clone(file); + if (IS_ERR(v9fid)) + return PTR_ERR(v9fid); + + return p9_client_remove(v9fid); +} + +static int +v9fs_open_created(struct inode *inode, struct file *file) +{ + return 0; +} + + +/** + * v9fs_create - Create a file + * @v9ses: session information + * @dir: directory that dentry is being created in + * @dentry: dentry that is being created + * @perm: create permissions + * @mode: open mode + * @extension: 9p2000.u extension string to support devices, etc. + * + */ +static struct p9_fid * +v9fs_create(struct v9fs_session_info *v9ses, struct inode *dir, + struct dentry *dentry, char *extension, u32 perm, u8 mode) +{ + int err; + char *name; + struct p9_fid *dfid, *ofid, *fid; + struct inode *inode; + + P9_DPRINTK(P9_DEBUG_VFS, "name %s\n", dentry->d_name.name); + + err = 0; + ofid = NULL; + fid = NULL; + name = (char *) dentry->d_name.name; + dfid = v9fs_fid_clone(dentry->d_parent); + if (IS_ERR(dfid)) { + err = PTR_ERR(dfid); + P9_DPRINTK(P9_DEBUG_VFS, "fid clone failed %d\n", err); + dfid = NULL; + goto error; + } + + /* clone a fid to use for creation */ + ofid = p9_client_walk(dfid, 0, NULL, 1); + if (IS_ERR(ofid)) { + err = PTR_ERR(ofid); + P9_DPRINTK(P9_DEBUG_VFS, "p9_client_walk failed %d\n", err); + ofid = NULL; + goto error; + } + + err = p9_client_fcreate(ofid, name, perm, mode, extension); + if (err < 0) { + P9_DPRINTK(P9_DEBUG_VFS, "p9_client_fcreate failed %d\n", err); + goto error; + } + + /* now walk from the parent so we can get unopened fid */ + fid = p9_client_walk(dfid, 1, &name, 0); + if (IS_ERR(fid)) { + err = PTR_ERR(fid); + P9_DPRINTK(P9_DEBUG_VFS, "p9_client_walk failed %d\n", err); + fid = NULL; + goto error; + } else + dfid = NULL; + + /* instantiate inode and assign the unopened fid to the dentry */ + inode = v9fs_inode_from_fid(v9ses, fid, dir->i_sb); + if (IS_ERR(inode)) { + err = PTR_ERR(inode); + P9_DPRINTK(P9_DEBUG_VFS, "inode creation failed %d\n", err); + goto error; + } + + if (v9ses->cache) + dentry->d_op = &v9fs_cached_dentry_operations; + else + dentry->d_op = &v9fs_dentry_operations; + + d_instantiate(dentry, inode); + v9fs_fid_add(dentry, fid); + return ofid; + +error: + if (dfid) + p9_client_clunk(dfid); + + if (ofid) + p9_client_clunk(ofid); + + if (fid) + p9_client_clunk(fid); + + return ERR_PTR(err); +} + +/** + * v9fs_vfs_create - VFS hook to create files + * @dir: directory inode that is being created + * @dentry: dentry that is being deleted + * @mode: create permissions + * @nd: path information + * + */ + +static int +v9fs_vfs_create(struct inode *dir, struct dentry *dentry, int mode, + struct nameidata *nd) +{ + int err; + u32 perm; + int flags; + struct v9fs_session_info *v9ses; + struct p9_fid *fid; + struct file *filp; + + err = 0; + fid = NULL; + v9ses = v9fs_inode2v9ses(dir); + perm = unixmode2p9mode(v9ses, mode); + if (nd && nd->flags & LOOKUP_OPEN) + flags = nd->intent.open.flags - 1; + else + flags = O_RDWR; + + fid = v9fs_create(v9ses, dir, dentry, NULL, perm, + v9fs_uflags2omode(flags, v9fs_extended(v9ses))); + if (IS_ERR(fid)) { + err = PTR_ERR(fid); + fid = NULL; + goto error; + } + + /* if we are opening a file, assign the open fid to the file */ + if (nd && nd->flags & LOOKUP_OPEN) { + filp = lookup_instantiate_filp(nd, dentry, v9fs_open_created); + if (IS_ERR(filp)) { + err = PTR_ERR(filp); + goto error; + } + + filp->private_data = fid; + } else + p9_client_clunk(fid); + + return 0; + +error: + if (fid) + p9_client_clunk(fid); + + return err; +} + +/** + * v9fs_vfs_mkdir - VFS mkdir hook to create a directory + * @dir: inode that is being unlinked + * @dentry: dentry that is being unlinked + * @mode: mode for new directory + * + */ + +static int v9fs_vfs_mkdir(struct inode *dir, struct dentry *dentry, int mode) +{ + int err; + u32 perm; + struct v9fs_session_info *v9ses; + struct p9_fid *fid; + + P9_DPRINTK(P9_DEBUG_VFS, "name %s\n", dentry->d_name.name); + err = 0; + v9ses = v9fs_inode2v9ses(dir); + perm = unixmode2p9mode(v9ses, mode | S_IFDIR); + fid = v9fs_create(v9ses, dir, dentry, NULL, perm, P9_OREAD); + if (IS_ERR(fid)) { + err = PTR_ERR(fid); + fid = NULL; + } + + if (fid) + p9_client_clunk(fid); + + return err; +} + +/** + * v9fs_vfs_lookup - VFS lookup hook to "walk" to a new inode + * @dir: inode that is being walked from + * @dentry: dentry that is being walked to? + * @nameidata: path data + * + */ + +static struct dentry *v9fs_vfs_lookup(struct inode *dir, struct dentry *dentry, + struct nameidata *nameidata) +{ + struct super_block *sb; + struct v9fs_session_info *v9ses; + struct p9_fid *dfid, *fid; + struct inode *inode; + char *name; + int result = 0; + + P9_DPRINTK(P9_DEBUG_VFS, "dir: %p dentry: (%s) %p nameidata: %p\n", + dir, dentry->d_name.name, dentry, nameidata); + + sb = dir->i_sb; + v9ses = v9fs_inode2v9ses(dir); + dfid = v9fs_fid_lookup(dentry->d_parent); + if (IS_ERR(dfid)) + return ERR_CAST(dfid); + + name = (char *) dentry->d_name.name; + fid = p9_client_walk(dfid, 1, &name, 1); + if (IS_ERR(fid)) { + result = PTR_ERR(fid); + if (result == -ENOENT) { + d_add(dentry, NULL); + return NULL; + } + + return ERR_PTR(result); + } + + inode = v9fs_inode_from_fid(v9ses, fid, dir->i_sb); + if (IS_ERR(inode)) { + result = PTR_ERR(inode); + inode = NULL; + goto error; + } + + result = v9fs_fid_add(dentry, fid); + if (result < 0) + goto error; + + if ((fid->qid.version) && (v9ses->cache)) + dentry->d_op = &v9fs_cached_dentry_operations; + else + dentry->d_op = &v9fs_dentry_operations; + + d_add(dentry, inode); + return NULL; + +error: + p9_client_clunk(fid); + + return ERR_PTR(result); +} + +/** + * v9fs_vfs_unlink - VFS unlink hook to delete an inode + * @i: inode that is being unlinked + * @d: dentry that is being unlinked + * + */ + +static int v9fs_vfs_unlink(struct inode *i, struct dentry *d) +{ + return v9fs_remove(i, d, 0); +} + +/** + * v9fs_vfs_rmdir - VFS unlink hook to delete a directory + * @i: inode that is being unlinked + * @d: dentry that is being unlinked + * + */ + +static int v9fs_vfs_rmdir(struct inode *i, struct dentry *d) +{ + return v9fs_remove(i, d, 1); +} + +/** + * v9fs_vfs_rename - VFS hook to rename an inode + * @old_dir: old dir inode + * @old_dentry: old dentry + * @new_dir: new dir inode + * @new_dentry: new dentry + * + */ + +static int +v9fs_vfs_rename(struct inode *old_dir, struct dentry *old_dentry, + struct inode *new_dir, struct dentry *new_dentry) +{ + struct inode *old_inode; + struct v9fs_session_info *v9ses; + struct p9_fid *oldfid; + struct p9_fid *olddirfid; + struct p9_fid *newdirfid; + struct p9_wstat wstat; + int retval; + + P9_DPRINTK(P9_DEBUG_VFS, "\n"); + retval = 0; + old_inode = old_dentry->d_inode; + v9ses = v9fs_inode2v9ses(old_inode); + oldfid = v9fs_fid_lookup(old_dentry); + if (IS_ERR(oldfid)) + return PTR_ERR(oldfid); + + olddirfid = v9fs_fid_clone(old_dentry->d_parent); + if (IS_ERR(olddirfid)) { + retval = PTR_ERR(olddirfid); + goto done; + } + + newdirfid = v9fs_fid_clone(new_dentry->d_parent); + if (IS_ERR(newdirfid)) { + retval = PTR_ERR(newdirfid); + goto clunk_olddir; + } + + /* 9P can only handle file rename in the same directory */ + if (memcmp(&olddirfid->qid, &newdirfid->qid, sizeof(newdirfid->qid))) { + P9_DPRINTK(P9_DEBUG_ERROR, + "old dir and new dir are different\n"); + retval = -EXDEV; + goto clunk_newdir; + } + + v9fs_blank_wstat(&wstat); + wstat.muid = v9ses->uname; + wstat.name = (char *) new_dentry->d_name.name; + retval = p9_client_wstat(oldfid, &wstat); + +clunk_newdir: + p9_client_clunk(newdirfid); + +clunk_olddir: + p9_client_clunk(olddirfid); + +done: + return retval; +} + +/** + * v9fs_vfs_getattr - retrieve file metadata + * @mnt: mount information + * @dentry: file to get attributes on + * @stat: metadata structure to populate + * + */ + +static int +v9fs_vfs_getattr(struct vfsmount *mnt, struct dentry *dentry, + struct kstat *stat) +{ + int err; + struct v9fs_session_info *v9ses; + struct p9_fid *fid; + struct p9_wstat *st; + + P9_DPRINTK(P9_DEBUG_VFS, "dentry: %p\n", dentry); + err = -EPERM; + v9ses = v9fs_inode2v9ses(dentry->d_inode); + if (v9ses->cache == CACHE_LOOSE) + return simple_getattr(mnt, dentry, stat); + + fid = v9fs_fid_lookup(dentry); + if (IS_ERR(fid)) + return PTR_ERR(fid); + + st = p9_client_stat(fid); + if (IS_ERR(st)) + return PTR_ERR(st); + + v9fs_stat2inode(st, dentry->d_inode, dentry->d_inode->i_sb); + generic_fillattr(dentry->d_inode, stat); + + kfree(st); + return 0; +} + +/** + * v9fs_vfs_setattr - set file metadata + * @dentry: file whose metadata to set + * @iattr: metadata assignment structure + * + */ + +static int v9fs_vfs_setattr(struct dentry *dentry, struct iattr *iattr) +{ + int retval; + struct v9fs_session_info *v9ses; + struct p9_fid *fid; + struct p9_wstat wstat; + + P9_DPRINTK(P9_DEBUG_VFS, "\n"); + retval = -EPERM; + v9ses = v9fs_inode2v9ses(dentry->d_inode); + fid = v9fs_fid_lookup(dentry); + if(IS_ERR(fid)) + return PTR_ERR(fid); + + v9fs_blank_wstat(&wstat); + if (iattr->ia_valid & ATTR_MODE) + wstat.mode = unixmode2p9mode(v9ses, iattr->ia_mode); + + if (iattr->ia_valid & ATTR_MTIME) + wstat.mtime = iattr->ia_mtime.tv_sec; + + if (iattr->ia_valid & ATTR_ATIME) + wstat.atime = iattr->ia_atime.tv_sec; + + if (iattr->ia_valid & ATTR_SIZE) + wstat.length = iattr->ia_size; + + if (v9fs_extended(v9ses)) { + if (iattr->ia_valid & ATTR_UID) + wstat.n_uid = iattr->ia_uid; + + if (iattr->ia_valid & ATTR_GID) + wstat.n_gid = iattr->ia_gid; + } + + retval = p9_client_wstat(fid, &wstat); + if (retval >= 0) + retval = inode_setattr(dentry->d_inode, iattr); + + return retval; +} + +/** + * v9fs_stat2inode - populate an inode structure with mistat info + * @stat: Plan 9 metadata (mistat) structure + * @inode: inode to populate + * @sb: superblock of filesystem + * + */ + +void +v9fs_stat2inode(struct p9_wstat *stat, struct inode *inode, + struct super_block *sb) +{ + char ext[32]; + struct v9fs_session_info *v9ses = sb->s_fs_info; + + inode->i_nlink = 1; + + inode->i_atime.tv_sec = stat->atime; + inode->i_mtime.tv_sec = stat->mtime; + inode->i_ctime.tv_sec = stat->mtime; + + inode->i_uid = v9ses->dfltuid; + inode->i_gid = v9ses->dfltgid; + + if (v9fs_extended(v9ses)) { + inode->i_uid = stat->n_uid; + inode->i_gid = stat->n_gid; + } + + inode->i_mode = p9mode2unixmode(v9ses, stat->mode); + if ((S_ISBLK(inode->i_mode)) || (S_ISCHR(inode->i_mode))) { + char type = 0; + int major = -1; + int minor = -1; + + strncpy(ext, stat->extension, sizeof(ext)); + sscanf(ext, "%c %u %u", &type, &major, &minor); + switch (type) { + case 'c': + inode->i_mode &= ~S_IFBLK; + inode->i_mode |= S_IFCHR; + break; + case 'b': + break; + default: + P9_DPRINTK(P9_DEBUG_ERROR, + "Unknown special type %c %s\n", type, + stat->extension); + }; + inode->i_rdev = MKDEV(major, minor); + init_special_inode(inode, inode->i_mode, inode->i_rdev); + } else + inode->i_rdev = 0; + + inode->i_size = stat->length; + + /* not real number of blocks, but 512 byte ones ... */ + inode->i_blocks = (inode->i_size + 512 - 1) >> 9; +} + +/** + * v9fs_qid2ino - convert qid into inode number + * @qid: qid to hash + * + * BUG: potential for inode number collisions? + */ + +ino_t v9fs_qid2ino(struct p9_qid *qid) +{ + u64 path = qid->path + 2; + ino_t i = 0; + + if (sizeof(ino_t) == sizeof(path)) + memcpy(&i, &path, sizeof(ino_t)); + else + i = (ino_t) (path ^ (path >> 32)); + + return i; +} + +/** + * v9fs_readlink - read a symlink's location (internal version) + * @dentry: dentry for symlink + * @buffer: buffer to load symlink location into + * @buflen: length of buffer + * + */ + +static int v9fs_readlink(struct dentry *dentry, char *buffer, int buflen) +{ + int retval; + + struct v9fs_session_info *v9ses; + struct p9_fid *fid; + struct p9_wstat *st; + + P9_DPRINTK(P9_DEBUG_VFS, " %s\n", dentry->d_name.name); + retval = -EPERM; + v9ses = v9fs_inode2v9ses(dentry->d_inode); + fid = v9fs_fid_lookup(dentry); + if (IS_ERR(fid)) + return PTR_ERR(fid); + + if (!v9fs_extended(v9ses)) + return -EBADF; + + st = p9_client_stat(fid); + if (IS_ERR(st)) + return PTR_ERR(st); + + if (!(st->mode & P9_DMSYMLINK)) { + retval = -EINVAL; + goto done; + } + + /* copy extension buffer into buffer */ + strncpy(buffer, st->extension, buflen); + + P9_DPRINTK(P9_DEBUG_VFS, + "%s -> %s (%s)\n", dentry->d_name.name, st->extension, buffer); + + retval = buflen; + +done: + kfree(st); + return retval; +} + +/** + * v9fs_vfs_readlink - read a symlink's location + * @dentry: dentry for symlink + * @buffer: buffer to load symlink location into + * @buflen: length of buffer + * + */ + +static int v9fs_vfs_readlink(struct dentry *dentry, char __user * buffer, + int buflen) +{ + int retval; + int ret; + char *link = __getname(); + + if (unlikely(!link)) + return -ENOMEM; + + if (buflen > PATH_MAX) + buflen = PATH_MAX; + + P9_DPRINTK(P9_DEBUG_VFS, " dentry: %s (%p)\n", dentry->d_name.name, + dentry); + + retval = v9fs_readlink(dentry, link, buflen); + + if (retval > 0) { + if ((ret = copy_to_user(buffer, link, retval)) != 0) { + P9_DPRINTK(P9_DEBUG_ERROR, + "problem copying to user: %d\n", ret); + retval = ret; + } + } + + __putname(link); + return retval; +} + +/** + * v9fs_vfs_follow_link - follow a symlink path + * @dentry: dentry for symlink + * @nd: nameidata + * + */ + +static void *v9fs_vfs_follow_link(struct dentry *dentry, struct nameidata *nd) +{ + int len = 0; + char *link = __getname(); + + P9_DPRINTK(P9_DEBUG_VFS, "%s n", dentry->d_name.name); + + if (!link) + link = ERR_PTR(-ENOMEM); + else { + len = v9fs_readlink(dentry, link, PATH_MAX); + + if (len < 0) { + __putname(link); + link = ERR_PTR(len); + } else + link[len] = 0; + } + nd_set_link(nd, link); + + return NULL; +} + +/** + * v9fs_vfs_put_link - release a symlink path + * @dentry: dentry for symlink + * @nd: nameidata + * @p: unused + * + */ + +static void +v9fs_vfs_put_link(struct dentry *dentry, struct nameidata *nd, void *p) +{ + char *s = nd_get_link(nd); + + P9_DPRINTK(P9_DEBUG_VFS, " %s %s\n", dentry->d_name.name, + IS_ERR(s) ? "<error>" : s); + if (!IS_ERR(s)) + __putname(s); +} + +/** + * v9fs_vfs_mkspecial - create a special file + * @dir: inode to create special file in + * @dentry: dentry to create + * @mode: mode to create special file + * @extension: 9p2000.u format extension string representing special file + * + */ + +static int v9fs_vfs_mkspecial(struct inode *dir, struct dentry *dentry, + int mode, const char *extension) +{ + u32 perm; + struct v9fs_session_info *v9ses; + struct p9_fid *fid; + + v9ses = v9fs_inode2v9ses(dir); + if (!v9fs_extended(v9ses)) { + P9_DPRINTK(P9_DEBUG_ERROR, "not extended\n"); + return -EPERM; + } + + perm = unixmode2p9mode(v9ses, mode); + fid = v9fs_create(v9ses, dir, dentry, (char *) extension, perm, + P9_OREAD); + if (IS_ERR(fid)) + return PTR_ERR(fid); + + p9_client_clunk(fid); + return 0; +} + +/** + * v9fs_vfs_symlink - helper function to create symlinks + * @dir: directory inode containing symlink + * @dentry: dentry for symlink + * @symname: symlink data + * + * See Also: 9P2000.u RFC for more information + * + */ + +static int +v9fs_vfs_symlink(struct inode *dir, struct dentry *dentry, const char *symname) +{ + P9_DPRINTK(P9_DEBUG_VFS, " %lu,%s,%s\n", dir->i_ino, + dentry->d_name.name, symname); + + return v9fs_vfs_mkspecial(dir, dentry, S_IFLNK, symname); +} + +/** + * v9fs_vfs_link - create a hardlink + * @old_dentry: dentry for file to link to + * @dir: inode destination for new link + * @dentry: dentry for link + * + */ + +static int +v9fs_vfs_link(struct dentry *old_dentry, struct inode *dir, + struct dentry *dentry) +{ + int retval; + struct p9_fid *oldfid; + char *name; + + P9_DPRINTK(P9_DEBUG_VFS, + " %lu,%s,%s\n", dir->i_ino, dentry->d_name.name, + old_dentry->d_name.name); + + oldfid = v9fs_fid_clone(old_dentry); + if (IS_ERR(oldfid)) + return PTR_ERR(oldfid); + + name = __getname(); + if (unlikely(!name)) { + retval = -ENOMEM; + goto clunk_fid; + } + + sprintf(name, "%d\n", oldfid->fid); + retval = v9fs_vfs_mkspecial(dir, dentry, P9_DMLINK, name); + __putname(name); + +clunk_fid: + p9_client_clunk(oldfid); + return retval; +} + +/** + * v9fs_vfs_mknod - create a special file + * @dir: inode destination for new link + * @dentry: dentry for file + * @mode: mode for creation + * @rdev: device associated with special file + * + */ + +static int +v9fs_vfs_mknod(struct inode *dir, struct dentry *dentry, int mode, dev_t rdev) +{ + int retval; + char *name; + + P9_DPRINTK(P9_DEBUG_VFS, + " %lu,%s mode: %x MAJOR: %u MINOR: %u\n", dir->i_ino, + dentry->d_name.name, mode, MAJOR(rdev), MINOR(rdev)); + + if (!new_valid_dev(rdev)) + return -EINVAL; + + name = __getname(); + if (!name) + return -ENOMEM; + /* build extension */ + if (S_ISBLK(mode)) + sprintf(name, "b %u %u", MAJOR(rdev), MINOR(rdev)); + else if (S_ISCHR(mode)) + sprintf(name, "c %u %u", MAJOR(rdev), MINOR(rdev)); + else if (S_ISFIFO(mode)) + *name = 0; + else { + __putname(name); + return -EINVAL; + } + + retval = v9fs_vfs_mkspecial(dir, dentry, mode, name); + __putname(name); + + return retval; +} + +static const struct inode_operations v9fs_dir_inode_operations_ext = { + .create = v9fs_vfs_create, + .lookup = v9fs_vfs_lookup, + .symlink = v9fs_vfs_symlink, + .link = v9fs_vfs_link, + .unlink = v9fs_vfs_unlink, + .mkdir = v9fs_vfs_mkdir, + .rmdir = v9fs_vfs_rmdir, + .mknod = v9fs_vfs_mknod, + .rename = v9fs_vfs_rename, + .readlink = v9fs_vfs_readlink, + .getattr = v9fs_vfs_getattr, + .setattr = v9fs_vfs_setattr, +}; + +static const struct inode_operations v9fs_dir_inode_operations = { + .create = v9fs_vfs_create, + .lookup = v9fs_vfs_lookup, + .unlink = v9fs_vfs_unlink, + .mkdir = v9fs_vfs_mkdir, + .rmdir = v9fs_vfs_rmdir, + .mknod = v9fs_vfs_mknod, + .rename = v9fs_vfs_rename, + .getattr = v9fs_vfs_getattr, + .setattr = v9fs_vfs_setattr, +}; + +static const struct inode_operations v9fs_file_inode_operations = { + .getattr = v9fs_vfs_getattr, + .setattr = v9fs_vfs_setattr, +}; + +static const struct inode_operations v9fs_symlink_inode_operations = { + .readlink = v9fs_vfs_readlink, + .follow_link = v9fs_vfs_follow_link, + .put_link = v9fs_vfs_put_link, + .getattr = v9fs_vfs_getattr, + .setattr = v9fs_vfs_setattr, +}; diff --git a/fs/9p/vfs_super.c b/fs/9p/vfs_super.c new file mode 100644 index 0000000..d6cb1a0 --- /dev/null +++ b/fs/9p/vfs_super.c @@ -0,0 +1,249 @@ +/* + * linux/fs/9p/vfs_super.c + * + * This file contians superblock ops for 9P2000. It is intended that + * you mount this file system on directories. + * + * Copyright (C) 2004 by Eric Van Hensbergen <ericvh@gmail.com> + * Copyright (C) 2002 by Ron Minnich <rminnich@lanl.gov> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * 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: + * Free Software Foundation + * 51 Franklin Street, Fifth Floor + * Boston, MA 02111-1301 USA + * + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/errno.h> +#include <linux/fs.h> +#include <linux/file.h> +#include <linux/stat.h> +#include <linux/string.h> +#include <linux/inet.h> +#include <linux/pagemap.h> +#include <linux/seq_file.h> +#include <linux/mount.h> +#include <linux/idr.h> +#include <linux/sched.h> +#include <net/9p/9p.h> +#include <net/9p/client.h> + +#include "v9fs.h" +#include "v9fs_vfs.h" +#include "fid.h" + +static void v9fs_clear_inode(struct inode *); +static const struct super_operations v9fs_super_ops; + +/** + * v9fs_clear_inode - release an inode + * @inode: inode to release + * + */ + +static void v9fs_clear_inode(struct inode *inode) +{ + filemap_fdatawrite(inode->i_mapping); +} + +/** + * v9fs_set_super - set the superblock + * @s: super block + * @data: file system specific data + * + */ + +static int v9fs_set_super(struct super_block *s, void *data) +{ + s->s_fs_info = data; + return set_anon_super(s, data); +} + +/** + * v9fs_fill_super - populate superblock with info + * @sb: superblock + * @v9ses: session information + * @flags: flags propagated from v9fs_get_sb() + * + */ + +static void +v9fs_fill_super(struct super_block *sb, struct v9fs_session_info *v9ses, + int flags) +{ + sb->s_maxbytes = MAX_LFS_FILESIZE; + sb->s_blocksize_bits = fls(v9ses->maxdata - 1); + sb->s_blocksize = 1 << sb->s_blocksize_bits; + sb->s_magic = V9FS_MAGIC; + sb->s_op = &v9fs_super_ops; + + sb->s_flags = flags | MS_ACTIVE | MS_SYNCHRONOUS | MS_DIRSYNC | + MS_NOATIME; +} + +/** + * v9fs_get_sb - mount a superblock + * @fs_type: file system type + * @flags: mount flags + * @dev_name: device name that was mounted + * @data: mount options + * @mnt: mountpoint record to be instantiated + * + */ + +static int v9fs_get_sb(struct file_system_type *fs_type, int flags, + const char *dev_name, void *data, + struct vfsmount *mnt) +{ + struct super_block *sb = NULL; + struct inode *inode = NULL; + struct dentry *root = NULL; + struct v9fs_session_info *v9ses = NULL; + struct p9_wstat *st = NULL; + int mode = S_IRWXUGO | S_ISVTX; + uid_t uid = current->fsuid; + gid_t gid = current->fsgid; + struct p9_fid *fid; + int retval = 0; + + P9_DPRINTK(P9_DEBUG_VFS, " \n"); + + st = NULL; + v9ses = kzalloc(sizeof(struct v9fs_session_info), GFP_KERNEL); + if (!v9ses) + return -ENOMEM; + + fid = v9fs_session_init(v9ses, dev_name, data); + if (IS_ERR(fid)) { + retval = PTR_ERR(fid); + goto close_session; + } + + st = p9_client_stat(fid); + if (IS_ERR(st)) { + retval = PTR_ERR(st); + goto clunk_fid; + } + + sb = sget(fs_type, NULL, v9fs_set_super, v9ses); + if (IS_ERR(sb)) { + retval = PTR_ERR(sb); + goto free_stat; + } + v9fs_fill_super(sb, v9ses, flags); + + inode = v9fs_get_inode(sb, S_IFDIR | mode); + if (IS_ERR(inode)) { + retval = PTR_ERR(inode); + goto release_sb; + } + + inode->i_uid = uid; + inode->i_gid = gid; + + root = d_alloc_root(inode); + if (!root) { + retval = -ENOMEM; + goto release_sb; + } + + sb->s_root = root; + root->d_inode->i_ino = v9fs_qid2ino(&st->qid); + + v9fs_stat2inode(st, root->d_inode, sb); + + v9fs_fid_add(root, fid); + p9stat_free(st); + kfree(st); + +P9_DPRINTK(P9_DEBUG_VFS, " return simple set mount\n"); + return simple_set_mnt(mnt, sb); + +release_sb: + if (sb) { + up_write(&sb->s_umount); + deactivate_super(sb); + } + +free_stat: + kfree(st); + +clunk_fid: + p9_client_clunk(fid); + +close_session: + v9fs_session_close(v9ses); + kfree(v9ses); + + return retval; +} + +/** + * v9fs_kill_super - Kill Superblock + * @s: superblock + * + */ + +static void v9fs_kill_super(struct super_block *s) +{ + struct v9fs_session_info *v9ses = s->s_fs_info; + + P9_DPRINTK(P9_DEBUG_VFS, " %p\n", s); + + v9fs_dentry_release(s->s_root); /* clunk root */ + + kill_anon_super(s); + + v9fs_session_close(v9ses); + kfree(v9ses); + P9_DPRINTK(P9_DEBUG_VFS, "exiting kill_super\n"); +} + +/** + * v9fs_show_options - Show mount options in /proc/mounts + * @m: seq_file to write to + * @mnt: mount descriptor + * + */ + +static int v9fs_show_options(struct seq_file *m, struct vfsmount *mnt) +{ + struct v9fs_session_info *v9ses = mnt->mnt_sb->s_fs_info; + + seq_printf(m, "%s", v9ses->options); + return 0; +} + +static void +v9fs_umount_begin(struct super_block *sb) +{ + struct v9fs_session_info *v9ses = sb->s_fs_info; + + v9fs_session_cancel(v9ses); +} + +static const struct super_operations v9fs_super_ops = { + .statfs = simple_statfs, + .clear_inode = v9fs_clear_inode, + .show_options = v9fs_show_options, + .umount_begin = v9fs_umount_begin, +}; + +struct file_system_type v9fs_fs_type = { + .name = "9p", + .get_sb = v9fs_get_sb, + .kill_sb = v9fs_kill_super, + .owner = THIS_MODULE, +}; |