diff options
Diffstat (limited to 'fs/9p')
| -rw-r--r-- | fs/9p/9p.c | 429 | ||||
| -rw-r--r-- | fs/9p/9p.h | 376 | ||||
| -rw-r--r-- | fs/9p/Kconfig | 46 | ||||
| -rw-r--r-- | fs/9p/Makefile | 19 | ||||
| -rw-r--r-- | fs/9p/acl.c | 381 | ||||
| -rw-r--r-- | fs/9p/acl.h | 55 | ||||
| -rw-r--r-- | fs/9p/cache.c | 414 | ||||
| -rw-r--r-- | fs/9p/cache.h | 151 | ||||
| -rw-r--r-- | fs/9p/conv.c | 836 | ||||
| -rw-r--r-- | fs/9p/conv.h | 51 | ||||
| -rw-r--r-- | fs/9p/debug.h | 77 | ||||
| -rw-r--r-- | fs/9p/error.c | 94 | ||||
| -rw-r--r-- | fs/9p/error.h | 178 | ||||
| -rw-r--r-- | fs/9p/fid.c | 402 | ||||
| -rw-r--r-- | fs/9p/fid.h | 48 | ||||
| -rw-r--r-- | fs/9p/mux.c | 977 | ||||
| -rw-r--r-- | fs/9p/mux.h | 58 | ||||
| -rw-r--r-- | fs/9p/trans_fd.c | 215 | ||||
| -rw-r--r-- | fs/9p/trans_sock.c | 334 | ||||
| -rw-r--r-- | fs/9p/transport.h | 46 | ||||
| -rw-r--r-- | fs/9p/v9fs.c | 810 | ||||
| -rw-r--r-- | fs/9p/v9fs.h | 252 | ||||
| -rw-r--r-- | fs/9p/v9fs_vfs.h | 61 | ||||
| -rw-r--r-- | fs/9p/vfs_addr.c | 345 | ||||
| -rw-r--r-- | fs/9p/vfs_dentry.c | 125 | ||||
| -rw-r--r-- | fs/9p/vfs_dir.c | 286 | ||||
| -rw-r--r-- | fs/9p/vfs_file.c | 969 | ||||
| -rw-r--r-- | fs/9p/vfs_inode.c | 1608 | ||||
| -rw-r--r-- | fs/9p/vfs_inode_dotl.c | 1016 | ||||
| -rw-r--r-- | fs/9p/vfs_super.c | 343 | ||||
| -rw-r--r-- | fs/9p/xattr.c | 181 | ||||
| -rw-r--r-- | fs/9p/xattr.h | 37 | ||||
| -rw-r--r-- | fs/9p/xattr_security.c | 80 | ||||
| -rw-r--r-- | fs/9p/xattr_trusted.c | 80 | ||||
| -rw-r--r-- | fs/9p/xattr_user.c | 80 |
35 files changed, 6038 insertions, 5422 deletions
diff --git a/fs/9p/9p.c b/fs/9p/9p.c deleted file mode 100644 index 1a6d08761f3..00000000000 --- a/fs/9p/9p.c +++ /dev/null @@ -1,429 +0,0 @@ -/* - * linux/fs/9p/9p.c - * - * This file contains functions to perform synchronous 9P calls - * - * Copyright (C) 2004 by Latchesar Ionkov <lucho@ionkov.net> - * 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 as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to: - * Free Software Foundation - * 51 Franklin Street, Fifth Floor - * Boston, MA 02111-1301 USA - * - */ - -#include <linux/config.h> -#include <linux/module.h> -#include <linux/errno.h> -#include <linux/fs.h> -#include <linux/idr.h> - -#include "debug.h" -#include "v9fs.h" -#include "9p.h" -#include "conv.h" -#include "mux.h" - -/** - * v9fs_t_version - negotiate protocol parameters with sever - * @v9ses: 9P2000 session information - * @msize: requested max size packet - * @version: requested version.extension string - * @fcall: pointer to response fcall pointer - * - */ - -int -v9fs_t_version(struct v9fs_session_info *v9ses, u32 msize, - char *version, struct v9fs_fcall **rcp) -{ - int ret; - struct v9fs_fcall *tc; - - dprintk(DEBUG_9P, "msize: %d version: %s\n", msize, version); - tc = v9fs_create_tversion(msize, version); - - if (!IS_ERR(tc)) { - ret = v9fs_mux_rpc(v9ses->mux, tc, rcp); - kfree(tc); - } else - ret = PTR_ERR(tc); - - return ret; -} - -/** - * v9fs_t_attach - mount the server - * @v9ses: 9P2000 session information - * @uname: user name doing the attach - * @aname: remote name being attached to - * @fid: mount fid to attatch to root node - * @afid: authentication fid (in this case result key) - * @fcall: pointer to response fcall pointer - * - */ - -int -v9fs_t_attach(struct v9fs_session_info *v9ses, char *uname, char *aname, - u32 fid, u32 afid, struct v9fs_fcall **rcp) -{ - int ret; - struct v9fs_fcall* tc; - - dprintk(DEBUG_9P, "uname '%s' aname '%s' fid %d afid %d\n", uname, - aname, fid, afid); - - tc = v9fs_create_tattach(fid, afid, uname, aname); - if (!IS_ERR(tc)) { - ret = v9fs_mux_rpc(v9ses->mux, tc, rcp); - kfree(tc); - } else - ret = PTR_ERR(tc); - - return ret; -} - -static void v9fs_t_clunk_cb(void *a, struct v9fs_fcall *tc, - struct v9fs_fcall *rc, int err) -{ - int fid; - struct v9fs_session_info *v9ses; - - if (err) - return; - - fid = tc->params.tclunk.fid; - kfree(tc); - - if (!rc) - return; - - dprintk(DEBUG_9P, "tcall id %d rcall id %d\n", tc->id, rc->id); - v9ses = a; - if (rc->id == RCLUNK) - v9fs_put_idpool(fid, &v9ses->fidpool); - - kfree(rc); -} - -/** - * v9fs_t_clunk - release a fid (finish a transaction) - * @v9ses: 9P2000 session information - * @fid: fid to release - * @fcall: pointer to response fcall pointer - * - */ - -int -v9fs_t_clunk(struct v9fs_session_info *v9ses, u32 fid) -{ - int ret; - struct v9fs_fcall *tc, *rc; - - dprintk(DEBUG_9P, "fid %d\n", fid); - - rc = NULL; - tc = v9fs_create_tclunk(fid); - if (!IS_ERR(tc)) - ret = v9fs_mux_rpc(v9ses->mux, tc, &rc); - else - ret = PTR_ERR(tc); - - if (ret) - dprintk(DEBUG_ERROR, "failed fid %d err %d\n", fid, ret); - - v9fs_t_clunk_cb(v9ses, tc, rc, ret); - return ret; -} - -/** - * v9fs_v9fs_t_flush - flush a pending transaction - * @v9ses: 9P2000 session information - * @tag: tid to release - * - */ - -int v9fs_t_flush(struct v9fs_session_info *v9ses, u16 oldtag) -{ - int ret; - struct v9fs_fcall *tc; - - dprintk(DEBUG_9P, "oldtag %d\n", oldtag); - - tc = v9fs_create_tflush(oldtag); - if (!IS_ERR(tc)) { - ret = v9fs_mux_rpc(v9ses->mux, tc, NULL); - kfree(tc); - } else - ret = PTR_ERR(tc); - - return ret; -} - -/** - * v9fs_t_stat - read a file's meta-data - * @v9ses: 9P2000 session information - * @fid: fid pointing to file or directory to get info about - * @fcall: pointer to response fcall - * - */ - -int -v9fs_t_stat(struct v9fs_session_info *v9ses, u32 fid, struct v9fs_fcall **rcp) -{ - int ret; - struct v9fs_fcall *tc; - - dprintk(DEBUG_9P, "fid %d\n", fid); - - ret = -ENOMEM; - tc = v9fs_create_tstat(fid); - if (!IS_ERR(tc)) { - ret = v9fs_mux_rpc(v9ses->mux, tc, rcp); - kfree(tc); - } else - ret = PTR_ERR(tc); - - return ret; -} - -/** - * v9fs_t_wstat - write a file's meta-data - * @v9ses: 9P2000 session information - * @fid: fid pointing to file or directory to write info about - * @stat: metadata - * @fcall: pointer to response fcall - * - */ - -int -v9fs_t_wstat(struct v9fs_session_info *v9ses, u32 fid, - struct v9fs_wstat *wstat, struct v9fs_fcall **rcp) -{ - int ret; - struct v9fs_fcall *tc; - - dprintk(DEBUG_9P, "fid %d\n", fid); - - tc = v9fs_create_twstat(fid, wstat, v9ses->extended); - if (!IS_ERR(tc)) { - ret = v9fs_mux_rpc(v9ses->mux, tc, rcp); - kfree(tc); - } else - ret = PTR_ERR(tc); - - return ret; -} - -/** - * v9fs_t_walk - walk a fid to a new file or directory - * @v9ses: 9P2000 session information - * @fid: fid to walk - * @newfid: new fid (for clone operations) - * @name: path to walk fid to - * @fcall: pointer to response fcall - * - */ - -/* TODO: support multiple walk */ - -int -v9fs_t_walk(struct v9fs_session_info *v9ses, u32 fid, u32 newfid, - char *name, struct v9fs_fcall **rcp) -{ - int ret; - struct v9fs_fcall *tc; - int nwname; - - dprintk(DEBUG_9P, "fid %d newfid %d wname '%s'\n", fid, newfid, name); - - if (name) - nwname = 1; - else - nwname = 0; - - tc = v9fs_create_twalk(fid, newfid, nwname, &name); - if (!IS_ERR(tc)) { - ret = v9fs_mux_rpc(v9ses->mux, tc, rcp); - kfree(tc); - } else - ret = PTR_ERR(tc); - - return ret; -} - -/** - * v9fs_t_open - open a file - * - * @v9ses - 9P2000 session information - * @fid - fid to open - * @mode - mode to open file (R, RW, etc) - * @fcall - pointer to response fcall - * - */ - -int -v9fs_t_open(struct v9fs_session_info *v9ses, u32 fid, u8 mode, - struct v9fs_fcall **rcp) -{ - int ret; - struct v9fs_fcall *tc; - - dprintk(DEBUG_9P, "fid %d mode %d\n", fid, mode); - - tc = v9fs_create_topen(fid, mode); - if (!IS_ERR(tc)) { - ret = v9fs_mux_rpc(v9ses->mux, tc, rcp); - kfree(tc); - } else - ret = PTR_ERR(tc); - - return ret; -} - -/** - * v9fs_t_remove - remove a file or directory - * @v9ses: 9P2000 session information - * @fid: fid to remove - * @fcall: pointer to response fcall - * - */ - -int -v9fs_t_remove(struct v9fs_session_info *v9ses, u32 fid, - struct v9fs_fcall **rcp) -{ - int ret; - struct v9fs_fcall *tc; - - dprintk(DEBUG_9P, "fid %d\n", fid); - - tc = v9fs_create_tremove(fid); - if (!IS_ERR(tc)) { - ret = v9fs_mux_rpc(v9ses->mux, tc, rcp); - kfree(tc); - } else - ret = PTR_ERR(tc); - - return ret; -} - -/** - * v9fs_t_create - create a file or directory - * @v9ses: 9P2000 session information - * @fid: fid to create - * @name: name of the file or directory to create - * @perm: permissions to create with - * @mode: mode to open file (R, RW, etc) - * @fcall: pointer to response fcall - * - */ - -int -v9fs_t_create(struct v9fs_session_info *v9ses, u32 fid, char *name, - u32 perm, u8 mode, struct v9fs_fcall **rcp) -{ - int ret; - struct v9fs_fcall *tc; - - dprintk(DEBUG_9P, "fid %d name '%s' perm %x mode %d\n", - fid, name, perm, mode); - - tc = v9fs_create_tcreate(fid, name, perm, mode); - if (!IS_ERR(tc)) { - ret = v9fs_mux_rpc(v9ses->mux, tc, rcp); - kfree(tc); - } else - ret = PTR_ERR(tc); - - return ret; -} - -/** - * v9fs_t_read - read data - * @v9ses: 9P2000 session information - * @fid: fid to read from - * @offset: offset to start read at - * @count: how many bytes to read - * @fcall: pointer to response fcall (with data) - * - */ - -int -v9fs_t_read(struct v9fs_session_info *v9ses, u32 fid, u64 offset, - u32 count, struct v9fs_fcall **rcp) -{ - int ret; - struct v9fs_fcall *tc, *rc; - - dprintk(DEBUG_9P, "fid %d offset 0x%llux count 0x%x\n", fid, - (long long unsigned) offset, count); - - tc = v9fs_create_tread(fid, offset, count); - if (!IS_ERR(tc)) { - ret = v9fs_mux_rpc(v9ses->mux, tc, &rc); - if (!ret) - ret = rc->params.rread.count; - if (rcp) - *rcp = rc; - else - kfree(rc); - - kfree(tc); - } else - ret = PTR_ERR(tc); - - return ret; -} - -/** - * v9fs_t_write - write data - * @v9ses: 9P2000 session information - * @fid: fid to write to - * @offset: offset to start write at - * @count: how many bytes to write - * @fcall: pointer to response fcall - * - */ - -int -v9fs_t_write(struct v9fs_session_info *v9ses, u32 fid, u64 offset, u32 count, - const char __user *data, struct v9fs_fcall **rcp) -{ - int ret; - struct v9fs_fcall *tc, *rc; - - dprintk(DEBUG_9P, "fid %d offset 0x%llux count 0x%x\n", fid, - (long long unsigned) offset, count); - - tc = v9fs_create_twrite(fid, offset, count, data); - if (!IS_ERR(tc)) { - ret = v9fs_mux_rpc(v9ses->mux, tc, &rc); - - if (!ret) - ret = rc->params.rwrite.count; - if (rcp) - *rcp = rc; - else - kfree(rc); - - kfree(tc); - } else - ret = PTR_ERR(tc); - - return ret; -} - diff --git a/fs/9p/9p.h b/fs/9p/9p.h deleted file mode 100644 index 0cd374d9471..00000000000 --- a/fs/9p/9p.h +++ /dev/null @@ -1,376 +0,0 @@ -/* - * linux/fs/9p/9p.h - * - * 9P protocol definitions. - * - * Copyright (C) 2005 by Latchesar Ionkov <lucho@ionkov.net> - * 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 as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to: - * Free Software Foundation - * 51 Franklin Street, Fifth Floor - * Boston, MA 02111-1301 USA - * - */ - -/* Message Types */ -enum { - TVERSION = 100, - RVERSION, - TAUTH = 102, - RAUTH, - TATTACH = 104, - RATTACH, - TERROR = 106, - RERROR, - TFLUSH = 108, - RFLUSH, - TWALK = 110, - RWALK, - TOPEN = 112, - ROPEN, - TCREATE = 114, - RCREATE, - TREAD = 116, - RREAD, - TWRITE = 118, - RWRITE, - TCLUNK = 120, - RCLUNK, - TREMOVE = 122, - RREMOVE, - TSTAT = 124, - RSTAT, - TWSTAT = 126, - RWSTAT, -}; - -/* modes */ -enum { - V9FS_OREAD = 0x00, - V9FS_OWRITE = 0x01, - V9FS_ORDWR = 0x02, - V9FS_OEXEC = 0x03, - V9FS_OEXCL = 0x04, - V9FS_OTRUNC = 0x10, - V9FS_OREXEC = 0x20, - V9FS_ORCLOSE = 0x40, - V9FS_OAPPEND = 0x80, -}; - -/* permissions */ -enum { - V9FS_DMDIR = 0x80000000, - V9FS_DMAPPEND = 0x40000000, - V9FS_DMEXCL = 0x20000000, - V9FS_DMMOUNT = 0x10000000, - V9FS_DMAUTH = 0x08000000, - V9FS_DMTMP = 0x04000000, - V9FS_DMSYMLINK = 0x02000000, - V9FS_DMLINK = 0x01000000, - /* 9P2000.u extensions */ - V9FS_DMDEVICE = 0x00800000, - V9FS_DMNAMEDPIPE = 0x00200000, - V9FS_DMSOCKET = 0x00100000, - V9FS_DMSETUID = 0x00080000, - V9FS_DMSETGID = 0x00040000, -}; - -/* qid.types */ -enum { - V9FS_QTDIR = 0x80, - V9FS_QTAPPEND = 0x40, - V9FS_QTEXCL = 0x20, - V9FS_QTMOUNT = 0x10, - V9FS_QTAUTH = 0x08, - V9FS_QTTMP = 0x04, - V9FS_QTSYMLINK = 0x02, - V9FS_QTLINK = 0x01, - V9FS_QTFILE = 0x00, -}; - -#define V9FS_NOTAG (u16)(~0) -#define V9FS_NOFID (u32)(~0) -#define V9FS_MAXWELEM 16 - -/* ample room for Twrite/Rread header (iounit) */ -#define V9FS_IOHDRSZ 24 - -struct v9fs_str { - u16 len; - char *str; -}; - -/* qids are the unique ID for a file (like an inode */ -struct v9fs_qid { - u8 type; - u32 version; - u64 path; -}; - -/* Plan 9 file metadata (stat) structure */ -struct v9fs_stat { - u16 size; - u16 type; - u32 dev; - struct v9fs_qid qid; - u32 mode; - u32 atime; - u32 mtime; - u64 length; - struct v9fs_str name; - struct v9fs_str uid; - struct v9fs_str gid; - struct v9fs_str muid; - struct v9fs_str extension; /* 9p2000.u extensions */ - u32 n_uid; /* 9p2000.u extensions */ - u32 n_gid; /* 9p2000.u extensions */ - u32 n_muid; /* 9p2000.u extensions */ -}; - -/* file metadata (stat) structure used to create Twstat message - The is similar to v9fs_stat, but the strings don't point to - the same memory block and should be freed separately -*/ -struct v9fs_wstat { - u16 size; - u16 type; - u32 dev; - struct v9fs_qid qid; - u32 mode; - u32 atime; - u32 mtime; - u64 length; - char *name; - char *uid; - char *gid; - char *muid; - char *extension; /* 9p2000.u extensions */ - u32 n_uid; /* 9p2000.u extensions */ - u32 n_gid; /* 9p2000.u extensions */ - u32 n_muid; /* 9p2000.u extensions */ -}; - -/* Structures for Protocol Operations */ - -struct Tversion { - u32 msize; - struct v9fs_str version; -}; - -struct Rversion { - u32 msize; - struct v9fs_str version; -}; - -struct Tauth { - u32 afid; - struct v9fs_str uname; - struct v9fs_str aname; -}; - -struct Rauth { - struct v9fs_qid qid; -}; - -struct Rerror { - struct v9fs_str error; - u32 errno; /* 9p2000.u extension */ -}; - -struct Tflush { - u16 oldtag; -}; - -struct Rflush { -}; - -struct Tattach { - u32 fid; - u32 afid; - struct v9fs_str uname; - struct v9fs_str aname; -}; - -struct Rattach { - struct v9fs_qid qid; -}; - -struct Twalk { - u32 fid; - u32 newfid; - u16 nwname; - struct v9fs_str wnames[16]; -}; - -struct Rwalk { - u16 nwqid; - struct v9fs_qid wqids[16]; -}; - -struct Topen { - u32 fid; - u8 mode; -}; - -struct Ropen { - struct v9fs_qid qid; - u32 iounit; -}; - -struct Tcreate { - u32 fid; - struct v9fs_str name; - u32 perm; - u8 mode; -}; - -struct Rcreate { - struct v9fs_qid qid; - u32 iounit; -}; - -struct Tread { - u32 fid; - u64 offset; - u32 count; -}; - -struct Rread { - u32 count; - u8 *data; -}; - -struct Twrite { - u32 fid; - u64 offset; - u32 count; - u8 *data; -}; - -struct Rwrite { - u32 count; -}; - -struct Tclunk { - u32 fid; -}; - -struct Rclunk { -}; - -struct Tremove { - u32 fid; -}; - -struct Rremove { -}; - -struct Tstat { - u32 fid; -}; - -struct Rstat { - struct v9fs_stat stat; -}; - -struct Twstat { - u32 fid; - struct v9fs_stat stat; -}; - -struct Rwstat { -}; - -/* - * fcall is the primary packet structure - * - */ - -struct v9fs_fcall { - u32 size; - u8 id; - u16 tag; - void *sdata; - - union { - struct Tversion tversion; - struct Rversion rversion; - struct Tauth tauth; - struct Rauth rauth; - struct Rerror rerror; - struct Tflush tflush; - struct Rflush rflush; - struct Tattach tattach; - struct Rattach rattach; - struct Twalk twalk; - struct Rwalk rwalk; - struct Topen topen; - struct Ropen ropen; - struct Tcreate tcreate; - struct Rcreate rcreate; - struct Tread tread; - struct Rread rread; - struct Twrite twrite; - struct Rwrite rwrite; - struct Tclunk tclunk; - struct Rclunk rclunk; - struct Tremove tremove; - struct Rremove rremove; - struct Tstat tstat; - struct Rstat rstat; - struct Twstat twstat; - struct Rwstat rwstat; - } params; -}; - -#define PRINT_FCALL_ERROR(s, fcall) dprintk(DEBUG_ERROR, "%s: %.*s\n", s, \ - fcall?fcall->params.rerror.error.len:0, \ - fcall?fcall->params.rerror.error.str:""); - -int v9fs_t_version(struct v9fs_session_info *v9ses, u32 msize, - char *version, struct v9fs_fcall **rcall); - -int v9fs_t_attach(struct v9fs_session_info *v9ses, char *uname, char *aname, - u32 fid, u32 afid, struct v9fs_fcall **rcall); - -int v9fs_t_clunk(struct v9fs_session_info *v9ses, u32 fid); - -int v9fs_t_flush(struct v9fs_session_info *v9ses, u16 oldtag); - -int v9fs_t_stat(struct v9fs_session_info *v9ses, u32 fid, - struct v9fs_fcall **rcall); - -int v9fs_t_wstat(struct v9fs_session_info *v9ses, u32 fid, - struct v9fs_wstat *wstat, struct v9fs_fcall **rcall); - -int v9fs_t_walk(struct v9fs_session_info *v9ses, u32 fid, u32 newfid, - char *name, struct v9fs_fcall **rcall); - -int v9fs_t_open(struct v9fs_session_info *v9ses, u32 fid, u8 mode, - struct v9fs_fcall **rcall); - -int v9fs_t_remove(struct v9fs_session_info *v9ses, u32 fid, - struct v9fs_fcall **rcall); - -int v9fs_t_create(struct v9fs_session_info *v9ses, u32 fid, char *name, - u32 perm, u8 mode, struct v9fs_fcall **rcall); - -int v9fs_t_read(struct v9fs_session_info *v9ses, u32 fid, - u64 offset, u32 count, struct v9fs_fcall **rcall); - -int v9fs_t_write(struct v9fs_session_info *v9ses, u32 fid, u64 offset, - u32 count, const char __user * data, - struct v9fs_fcall **rcall); diff --git a/fs/9p/Kconfig b/fs/9p/Kconfig new file mode 100644 index 00000000000..6489e1fc1af --- /dev/null +++ b/fs/9p/Kconfig @@ -0,0 +1,46 @@ +config 9P_FS + tristate "Plan 9 Resource Sharing Support (9P2000)" + depends on INET && NET_9P + help + If you say Y here, you will get experimental support for + Plan 9 resource sharing via the 9P2000 protocol. + + See <http://v9fs.sf.net> for more information. + + If unsure, say N. + +if 9P_FS +config 9P_FSCACHE + bool "Enable 9P client caching support" + depends on 9P_FS=m && FSCACHE || 9P_FS=y && FSCACHE=y + help + Choose Y here to enable persistent, read-only local + caching support for 9p clients using FS-Cache + + +config 9P_FS_POSIX_ACL + bool "9P POSIX Access Control Lists" + select FS_POSIX_ACL + help + POSIX Access Control Lists (ACLs) support permissions for users and + groups beyond the owner/group/world scheme. + + To learn more about Access Control Lists, visit the POSIX ACLs for + Linux website <http://acl.bestbits.at/>. + + If you don't know what Access Control Lists are, say N + +endif + + +config 9P_FS_SECURITY + bool "9P Security Labels" + depends on 9P_FS + help + Security labels support alternative access control models + implemented by security modules like SELinux. This option + enables an extended attribute handler for file security + labels in the 9P filesystem. + + If you are not using a security module that requires using + extended attributes for file security labels, say N. diff --git a/fs/9p/Makefile b/fs/9p/Makefile index 2f4ce43f7b6..ff7be98f84f 100644 --- a/fs/9p/Makefile +++ b/fs/9p/Makefile @@ -1,18 +1,19 @@ -obj-$(CONFIG_9P_FS) := 9p2000.o +obj-$(CONFIG_9P_FS) := 9p.o -9p2000-objs := \ - trans_fd.o \ - trans_sock.o \ - mux.o \ - 9p.o \ - conv.o \ +9p-objs := \ vfs_super.o \ vfs_inode.o \ + vfs_inode_dotl.o \ vfs_addr.o \ vfs_file.o \ vfs_dir.o \ vfs_dentry.o \ - error.o \ v9fs.o \ - fid.o + fid.o \ + xattr.o \ + xattr_user.o \ + xattr_trusted.o +9p-$(CONFIG_9P_FSCACHE) += cache.o +9p-$(CONFIG_9P_FS_POSIX_ACL) += acl.o +9p-$(CONFIG_9P_FS_SECURITY) += xattr_security.o diff --git a/fs/9p/acl.c b/fs/9p/acl.c new file mode 100644 index 00000000000..8482f2d1160 --- /dev/null +++ b/fs/9p/acl.c @@ -0,0 +1,381 @@ +/* + * Copyright IBM Corporation, 2010 + * Author Aneesh Kumar K.V <aneesh.kumar@linux.vnet.ibm.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2.1 of the GNU Lesser General Public License + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it would be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + */ + +#include <linux/module.h> +#include <linux/fs.h> +#include <net/9p/9p.h> +#include <net/9p/client.h> +#include <linux/slab.h> +#include <linux/sched.h> +#include <linux/posix_acl_xattr.h> +#include "xattr.h" +#include "acl.h" +#include "v9fs.h" +#include "v9fs_vfs.h" +#include "fid.h" + +static struct posix_acl *__v9fs_get_acl(struct p9_fid *fid, char *name) +{ + ssize_t size; + void *value = NULL; + struct posix_acl *acl = NULL; + + size = v9fs_fid_xattr_get(fid, name, NULL, 0); + if (size > 0) { + value = kzalloc(size, GFP_NOFS); + if (!value) + return ERR_PTR(-ENOMEM); + size = v9fs_fid_xattr_get(fid, name, value, size); + if (size > 0) { + acl = posix_acl_from_xattr(&init_user_ns, value, size); + if (IS_ERR(acl)) + goto err_out; + } + } else if (size == -ENODATA || size == 0 || + size == -ENOSYS || size == -EOPNOTSUPP) { + acl = NULL; + } else + acl = ERR_PTR(-EIO); + +err_out: + kfree(value); + return acl; +} + +int v9fs_get_acl(struct inode *inode, struct p9_fid *fid) +{ + int retval = 0; + struct posix_acl *pacl, *dacl; + struct v9fs_session_info *v9ses; + + v9ses = v9fs_inode2v9ses(inode); + if (((v9ses->flags & V9FS_ACCESS_MASK) != V9FS_ACCESS_CLIENT) || + ((v9ses->flags & V9FS_ACL_MASK) != V9FS_POSIX_ACL)) { + set_cached_acl(inode, ACL_TYPE_DEFAULT, NULL); + set_cached_acl(inode, ACL_TYPE_ACCESS, NULL); + return 0; + } + /* get the default/access acl values and cache them */ + dacl = __v9fs_get_acl(fid, POSIX_ACL_XATTR_DEFAULT); + pacl = __v9fs_get_acl(fid, POSIX_ACL_XATTR_ACCESS); + + if (!IS_ERR(dacl) && !IS_ERR(pacl)) { + set_cached_acl(inode, ACL_TYPE_DEFAULT, dacl); + set_cached_acl(inode, ACL_TYPE_ACCESS, pacl); + } else + retval = -EIO; + + if (!IS_ERR(dacl)) + posix_acl_release(dacl); + + if (!IS_ERR(pacl)) + posix_acl_release(pacl); + + return retval; +} + +static struct posix_acl *v9fs_get_cached_acl(struct inode *inode, int type) +{ + struct posix_acl *acl; + /* + * 9p Always cache the acl value when + * instantiating the inode (v9fs_inode_from_fid) + */ + acl = get_cached_acl(inode, type); + BUG_ON(acl == ACL_NOT_CACHED); + return acl; +} + +struct posix_acl *v9fs_iop_get_acl(struct inode *inode, int type) +{ + struct v9fs_session_info *v9ses; + + v9ses = v9fs_inode2v9ses(inode); + if (((v9ses->flags & V9FS_ACCESS_MASK) != V9FS_ACCESS_CLIENT) || + ((v9ses->flags & V9FS_ACL_MASK) != V9FS_POSIX_ACL)) { + /* + * On access = client and acl = on mode get the acl + * values from the server + */ + return NULL; + } + return v9fs_get_cached_acl(inode, type); + +} + +static int v9fs_set_acl(struct p9_fid *fid, int type, struct posix_acl *acl) +{ + int retval; + char *name; + size_t size; + void *buffer; + if (!acl) + return 0; + + /* Set a setxattr request to server */ + size = posix_acl_xattr_size(acl->a_count); + buffer = kmalloc(size, GFP_KERNEL); + if (!buffer) + return -ENOMEM; + retval = posix_acl_to_xattr(&init_user_ns, acl, buffer, size); + if (retval < 0) + goto err_free_out; + switch (type) { + case ACL_TYPE_ACCESS: + name = POSIX_ACL_XATTR_ACCESS; + break; + case ACL_TYPE_DEFAULT: + name = POSIX_ACL_XATTR_DEFAULT; + break; + default: + BUG(); + } + retval = v9fs_fid_xattr_set(fid, name, buffer, size, 0); +err_free_out: + kfree(buffer); + return retval; +} + +int v9fs_acl_chmod(struct inode *inode, struct p9_fid *fid) +{ + int retval = 0; + struct posix_acl *acl; + + if (S_ISLNK(inode->i_mode)) + return -EOPNOTSUPP; + acl = v9fs_get_cached_acl(inode, ACL_TYPE_ACCESS); + if (acl) { + retval = __posix_acl_chmod(&acl, GFP_KERNEL, inode->i_mode); + if (retval) + return retval; + set_cached_acl(inode, ACL_TYPE_ACCESS, acl); + retval = v9fs_set_acl(fid, ACL_TYPE_ACCESS, acl); + posix_acl_release(acl); + } + return retval; +} + +int v9fs_set_create_acl(struct inode *inode, struct p9_fid *fid, + struct posix_acl *dacl, struct posix_acl *acl) +{ + set_cached_acl(inode, ACL_TYPE_DEFAULT, dacl); + set_cached_acl(inode, ACL_TYPE_ACCESS, acl); + v9fs_set_acl(fid, ACL_TYPE_DEFAULT, dacl); + v9fs_set_acl(fid, ACL_TYPE_ACCESS, acl); + return 0; +} + +void v9fs_put_acl(struct posix_acl *dacl, + struct posix_acl *acl) +{ + posix_acl_release(dacl); + posix_acl_release(acl); +} + +int v9fs_acl_mode(struct inode *dir, umode_t *modep, + struct posix_acl **dpacl, struct posix_acl **pacl) +{ + int retval = 0; + umode_t mode = *modep; + struct posix_acl *acl = NULL; + + if (!S_ISLNK(mode)) { + acl = v9fs_get_cached_acl(dir, ACL_TYPE_DEFAULT); + if (IS_ERR(acl)) + return PTR_ERR(acl); + if (!acl) + mode &= ~current_umask(); + } + if (acl) { + if (S_ISDIR(mode)) + *dpacl = posix_acl_dup(acl); + retval = __posix_acl_create(&acl, GFP_NOFS, &mode); + if (retval < 0) + return retval; + if (retval > 0) + *pacl = acl; + else + posix_acl_release(acl); + } + *modep = mode; + return 0; +} + +static int v9fs_remote_get_acl(struct dentry *dentry, const char *name, + void *buffer, size_t size, int type) +{ + char *full_name; + + switch (type) { + case ACL_TYPE_ACCESS: + full_name = POSIX_ACL_XATTR_ACCESS; + break; + case ACL_TYPE_DEFAULT: + full_name = POSIX_ACL_XATTR_DEFAULT; + break; + default: + BUG(); + } + return v9fs_xattr_get(dentry, full_name, buffer, size); +} + +static int v9fs_xattr_get_acl(struct dentry *dentry, const char *name, + void *buffer, size_t size, int type) +{ + struct v9fs_session_info *v9ses; + struct posix_acl *acl; + int error; + + if (strcmp(name, "") != 0) + return -EINVAL; + + v9ses = v9fs_dentry2v9ses(dentry); + /* + * We allow set/get/list of acl when access=client is not specified + */ + if ((v9ses->flags & V9FS_ACCESS_MASK) != V9FS_ACCESS_CLIENT) + return v9fs_remote_get_acl(dentry, name, buffer, size, type); + + acl = v9fs_get_cached_acl(dentry->d_inode, type); + if (IS_ERR(acl)) + return PTR_ERR(acl); + if (acl == NULL) + return -ENODATA; + error = posix_acl_to_xattr(&init_user_ns, acl, buffer, size); + posix_acl_release(acl); + + return error; +} + +static int v9fs_remote_set_acl(struct dentry *dentry, const char *name, + const void *value, size_t size, + int flags, int type) +{ + char *full_name; + + switch (type) { + case ACL_TYPE_ACCESS: + full_name = POSIX_ACL_XATTR_ACCESS; + break; + case ACL_TYPE_DEFAULT: + full_name = POSIX_ACL_XATTR_DEFAULT; + break; + default: + BUG(); + } + return v9fs_xattr_set(dentry, full_name, value, size, flags); +} + + +static int v9fs_xattr_set_acl(struct dentry *dentry, const char *name, + const void *value, size_t size, + int flags, int type) +{ + int retval; + struct posix_acl *acl; + struct v9fs_session_info *v9ses; + struct inode *inode = dentry->d_inode; + + if (strcmp(name, "") != 0) + return -EINVAL; + + v9ses = v9fs_dentry2v9ses(dentry); + /* + * set the attribute on the remote. Without even looking at the + * xattr value. We leave it to the server to validate + */ + if ((v9ses->flags & V9FS_ACCESS_MASK) != V9FS_ACCESS_CLIENT) + return v9fs_remote_set_acl(dentry, name, + value, size, flags, type); + + if (S_ISLNK(inode->i_mode)) + return -EOPNOTSUPP; + if (!inode_owner_or_capable(inode)) + return -EPERM; + if (value) { + /* update the cached acl value */ + acl = posix_acl_from_xattr(&init_user_ns, value, size); + if (IS_ERR(acl)) + return PTR_ERR(acl); + else if (acl) { + retval = posix_acl_valid(acl); + if (retval) + goto err_out; + } + } else + acl = NULL; + + switch (type) { + case ACL_TYPE_ACCESS: + name = POSIX_ACL_XATTR_ACCESS; + if (acl) { + umode_t mode = inode->i_mode; + retval = posix_acl_equiv_mode(acl, &mode); + if (retval < 0) + goto err_out; + else { + struct iattr iattr; + if (retval == 0) { + /* + * ACL can be represented + * by the mode bits. So don't + * update ACL. + */ + acl = NULL; + value = NULL; + size = 0; + } + /* Updte the mode bits */ + iattr.ia_mode = ((mode & S_IALLUGO) | + (inode->i_mode & ~S_IALLUGO)); + iattr.ia_valid = ATTR_MODE; + /* FIXME should we update ctime ? + * What is the following setxattr update the + * mode ? + */ + v9fs_vfs_setattr_dotl(dentry, &iattr); + } + } + break; + case ACL_TYPE_DEFAULT: + name = POSIX_ACL_XATTR_DEFAULT; + if (!S_ISDIR(inode->i_mode)) { + retval = acl ? -EINVAL : 0; + goto err_out; + } + break; + default: + BUG(); + } + retval = v9fs_xattr_set(dentry, name, value, size, flags); + if (!retval) + set_cached_acl(inode, type, acl); +err_out: + posix_acl_release(acl); + return retval; +} + +const struct xattr_handler v9fs_xattr_acl_access_handler = { + .prefix = POSIX_ACL_XATTR_ACCESS, + .flags = ACL_TYPE_ACCESS, + .get = v9fs_xattr_get_acl, + .set = v9fs_xattr_set_acl, +}; + +const struct xattr_handler v9fs_xattr_acl_default_handler = { + .prefix = POSIX_ACL_XATTR_DEFAULT, + .flags = ACL_TYPE_DEFAULT, + .get = v9fs_xattr_get_acl, + .set = v9fs_xattr_set_acl, +}; diff --git a/fs/9p/acl.h b/fs/9p/acl.h new file mode 100644 index 00000000000..e4f7e882272 --- /dev/null +++ b/fs/9p/acl.h @@ -0,0 +1,55 @@ +/* + * Copyright IBM Corporation, 2010 + * Author Aneesh Kumar K.V <aneesh.kumar@linux.vnet.ibm.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2.1 of the GNU Lesser General Public License + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it would be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + */ +#ifndef FS_9P_ACL_H +#define FS_9P_ACL_H + +#ifdef CONFIG_9P_FS_POSIX_ACL +extern int v9fs_get_acl(struct inode *, struct p9_fid *); +extern struct posix_acl *v9fs_iop_get_acl(struct inode *inode, int type); +extern int v9fs_acl_chmod(struct inode *, struct p9_fid *); +extern int v9fs_set_create_acl(struct inode *, struct p9_fid *, + struct posix_acl *, struct posix_acl *); +extern int v9fs_acl_mode(struct inode *dir, umode_t *modep, + struct posix_acl **dpacl, struct posix_acl **pacl); +extern void v9fs_put_acl(struct posix_acl *dacl, struct posix_acl *acl); +#else +#define v9fs_iop_get_acl NULL +static inline int v9fs_get_acl(struct inode *inode, struct p9_fid *fid) +{ + return 0; +} +static inline int v9fs_acl_chmod(struct inode *inode, struct p9_fid *fid) +{ + return 0; +} +static inline int v9fs_set_create_acl(struct inode *inode, + struct p9_fid *fid, + struct posix_acl *dacl, + struct posix_acl *acl) +{ + return 0; +} +static inline void v9fs_put_acl(struct posix_acl *dacl, + struct posix_acl *acl) +{ +} +static inline int v9fs_acl_mode(struct inode *dir, umode_t *modep, + struct posix_acl **dpacl, + struct posix_acl **pacl) +{ + return 0; +} + +#endif +#endif /* FS_9P_XATTR_H */ diff --git a/fs/9p/cache.c b/fs/9p/cache.c new file mode 100644 index 00000000000..a69260f2755 --- /dev/null +++ b/fs/9p/cache.c @@ -0,0 +1,414 @@ +/* + * V9FS cache definitions. + * + * Copyright (C) 2009 by Abhishek Kulkarni <adkulkar@umail.iu.edu> + * + * 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/jiffies.h> +#include <linux/file.h> +#include <linux/slab.h> +#include <linux/stat.h> +#include <linux/sched.h> +#include <linux/fs.h> +#include <net/9p/9p.h> + +#include "v9fs.h" +#include "cache.h" + +#define CACHETAG_LEN 11 + +struct fscache_netfs v9fs_cache_netfs = { + .name = "9p", + .version = 0, +}; + +/** + * v9fs_random_cachetag - Generate a random tag to be associated + * with a new cache session. + * + * The value of jiffies is used for a fairly randomly cache tag. + */ + +static +int v9fs_random_cachetag(struct v9fs_session_info *v9ses) +{ + v9ses->cachetag = kmalloc(CACHETAG_LEN, GFP_KERNEL); + if (!v9ses->cachetag) + return -ENOMEM; + + return scnprintf(v9ses->cachetag, CACHETAG_LEN, "%lu", jiffies); +} + +static uint16_t v9fs_cache_session_get_key(const void *cookie_netfs_data, + void *buffer, uint16_t bufmax) +{ + struct v9fs_session_info *v9ses; + uint16_t klen = 0; + + v9ses = (struct v9fs_session_info *)cookie_netfs_data; + p9_debug(P9_DEBUG_FSC, "session %p buf %p size %u\n", + v9ses, buffer, bufmax); + + if (v9ses->cachetag) + klen = strlen(v9ses->cachetag); + + if (klen > bufmax) + return 0; + + memcpy(buffer, v9ses->cachetag, klen); + p9_debug(P9_DEBUG_FSC, "cache session tag %s\n", v9ses->cachetag); + return klen; +} + +const struct fscache_cookie_def v9fs_cache_session_index_def = { + .name = "9P.session", + .type = FSCACHE_COOKIE_TYPE_INDEX, + .get_key = v9fs_cache_session_get_key, +}; + +void v9fs_cache_session_get_cookie(struct v9fs_session_info *v9ses) +{ + /* If no cache session tag was specified, we generate a random one. */ + if (!v9ses->cachetag) + v9fs_random_cachetag(v9ses); + + v9ses->fscache = fscache_acquire_cookie(v9fs_cache_netfs.primary_index, + &v9fs_cache_session_index_def, + v9ses, true); + p9_debug(P9_DEBUG_FSC, "session %p get cookie %p\n", + v9ses, v9ses->fscache); +} + +void v9fs_cache_session_put_cookie(struct v9fs_session_info *v9ses) +{ + p9_debug(P9_DEBUG_FSC, "session %p put cookie %p\n", + v9ses, v9ses->fscache); + fscache_relinquish_cookie(v9ses->fscache, 0); + v9ses->fscache = NULL; +} + + +static uint16_t v9fs_cache_inode_get_key(const void *cookie_netfs_data, + void *buffer, uint16_t bufmax) +{ + const struct v9fs_inode *v9inode = cookie_netfs_data; + memcpy(buffer, &v9inode->qid.path, sizeof(v9inode->qid.path)); + p9_debug(P9_DEBUG_FSC, "inode %p get key %llu\n", + &v9inode->vfs_inode, v9inode->qid.path); + return sizeof(v9inode->qid.path); +} + +static void v9fs_cache_inode_get_attr(const void *cookie_netfs_data, + uint64_t *size) +{ + const struct v9fs_inode *v9inode = cookie_netfs_data; + *size = i_size_read(&v9inode->vfs_inode); + + p9_debug(P9_DEBUG_FSC, "inode %p get attr %llu\n", + &v9inode->vfs_inode, *size); +} + +static uint16_t v9fs_cache_inode_get_aux(const void *cookie_netfs_data, + void *buffer, uint16_t buflen) +{ + const struct v9fs_inode *v9inode = cookie_netfs_data; + memcpy(buffer, &v9inode->qid.version, sizeof(v9inode->qid.version)); + p9_debug(P9_DEBUG_FSC, "inode %p get aux %u\n", + &v9inode->vfs_inode, v9inode->qid.version); + return sizeof(v9inode->qid.version); +} + +static enum +fscache_checkaux v9fs_cache_inode_check_aux(void *cookie_netfs_data, + const void *buffer, + uint16_t buflen) +{ + const struct v9fs_inode *v9inode = cookie_netfs_data; + + if (buflen != sizeof(v9inode->qid.version)) + return FSCACHE_CHECKAUX_OBSOLETE; + + if (memcmp(buffer, &v9inode->qid.version, + sizeof(v9inode->qid.version))) + return FSCACHE_CHECKAUX_OBSOLETE; + + return FSCACHE_CHECKAUX_OKAY; +} + +static void v9fs_cache_inode_now_uncached(void *cookie_netfs_data) +{ + struct v9fs_inode *v9inode = cookie_netfs_data; + struct pagevec pvec; + pgoff_t first; + int loop, nr_pages; + + pagevec_init(&pvec, 0); + first = 0; + + for (;;) { + nr_pages = pagevec_lookup(&pvec, v9inode->vfs_inode.i_mapping, + first, + PAGEVEC_SIZE - pagevec_count(&pvec)); + if (!nr_pages) + break; + + for (loop = 0; loop < nr_pages; loop++) + ClearPageFsCache(pvec.pages[loop]); + + first = pvec.pages[nr_pages - 1]->index + 1; + + pvec.nr = nr_pages; + pagevec_release(&pvec); + cond_resched(); + } +} + +const struct fscache_cookie_def v9fs_cache_inode_index_def = { + .name = "9p.inode", + .type = FSCACHE_COOKIE_TYPE_DATAFILE, + .get_key = v9fs_cache_inode_get_key, + .get_attr = v9fs_cache_inode_get_attr, + .get_aux = v9fs_cache_inode_get_aux, + .check_aux = v9fs_cache_inode_check_aux, + .now_uncached = v9fs_cache_inode_now_uncached, +}; + +void v9fs_cache_inode_get_cookie(struct inode *inode) +{ + struct v9fs_inode *v9inode; + struct v9fs_session_info *v9ses; + + if (!S_ISREG(inode->i_mode)) + return; + + v9inode = V9FS_I(inode); + if (v9inode->fscache) + return; + + v9ses = v9fs_inode2v9ses(inode); + v9inode->fscache = fscache_acquire_cookie(v9ses->fscache, + &v9fs_cache_inode_index_def, + v9inode, true); + + p9_debug(P9_DEBUG_FSC, "inode %p get cookie %p\n", + inode, v9inode->fscache); +} + +void v9fs_cache_inode_put_cookie(struct inode *inode) +{ + struct v9fs_inode *v9inode = V9FS_I(inode); + + if (!v9inode->fscache) + return; + p9_debug(P9_DEBUG_FSC, "inode %p put cookie %p\n", + inode, v9inode->fscache); + + fscache_relinquish_cookie(v9inode->fscache, 0); + v9inode->fscache = NULL; +} + +void v9fs_cache_inode_flush_cookie(struct inode *inode) +{ + struct v9fs_inode *v9inode = V9FS_I(inode); + + if (!v9inode->fscache) + return; + p9_debug(P9_DEBUG_FSC, "inode %p flush cookie %p\n", + inode, v9inode->fscache); + + fscache_relinquish_cookie(v9inode->fscache, 1); + v9inode->fscache = NULL; +} + +void v9fs_cache_inode_set_cookie(struct inode *inode, struct file *filp) +{ + struct v9fs_inode *v9inode = V9FS_I(inode); + + if (!v9inode->fscache) + return; + + spin_lock(&v9inode->fscache_lock); + + if ((filp->f_flags & O_ACCMODE) != O_RDONLY) + v9fs_cache_inode_flush_cookie(inode); + else + v9fs_cache_inode_get_cookie(inode); + + spin_unlock(&v9inode->fscache_lock); +} + +void v9fs_cache_inode_reset_cookie(struct inode *inode) +{ + struct v9fs_inode *v9inode = V9FS_I(inode); + struct v9fs_session_info *v9ses; + struct fscache_cookie *old; + + if (!v9inode->fscache) + return; + + old = v9inode->fscache; + + spin_lock(&v9inode->fscache_lock); + fscache_relinquish_cookie(v9inode->fscache, 1); + + v9ses = v9fs_inode2v9ses(inode); + v9inode->fscache = fscache_acquire_cookie(v9ses->fscache, + &v9fs_cache_inode_index_def, + v9inode, true); + p9_debug(P9_DEBUG_FSC, "inode %p revalidating cookie old %p new %p\n", + inode, old, v9inode->fscache); + + spin_unlock(&v9inode->fscache_lock); +} + +int __v9fs_fscache_release_page(struct page *page, gfp_t gfp) +{ + struct inode *inode = page->mapping->host; + struct v9fs_inode *v9inode = V9FS_I(inode); + + BUG_ON(!v9inode->fscache); + + return fscache_maybe_release_page(v9inode->fscache, page, gfp); +} + +void __v9fs_fscache_invalidate_page(struct page *page) +{ + struct inode *inode = page->mapping->host; + struct v9fs_inode *v9inode = V9FS_I(inode); + + BUG_ON(!v9inode->fscache); + + if (PageFsCache(page)) { + fscache_wait_on_page_write(v9inode->fscache, page); + BUG_ON(!PageLocked(page)); + fscache_uncache_page(v9inode->fscache, page); + } +} + +static void v9fs_vfs_readpage_complete(struct page *page, void *data, + int error) +{ + if (!error) + SetPageUptodate(page); + + unlock_page(page); +} + +/** + * __v9fs_readpage_from_fscache - read a page from cache + * + * Returns 0 if the pages are in cache and a BIO is submitted, + * 1 if the pages are not in cache and -error otherwise. + */ + +int __v9fs_readpage_from_fscache(struct inode *inode, struct page *page) +{ + int ret; + const struct v9fs_inode *v9inode = V9FS_I(inode); + + p9_debug(P9_DEBUG_FSC, "inode %p page %p\n", inode, page); + if (!v9inode->fscache) + return -ENOBUFS; + + ret = fscache_read_or_alloc_page(v9inode->fscache, + page, + v9fs_vfs_readpage_complete, + NULL, + GFP_KERNEL); + switch (ret) { + case -ENOBUFS: + case -ENODATA: + p9_debug(P9_DEBUG_FSC, "page/inode not in cache %d\n", ret); + return 1; + case 0: + p9_debug(P9_DEBUG_FSC, "BIO submitted\n"); + return ret; + default: + p9_debug(P9_DEBUG_FSC, "ret %d\n", ret); + return ret; + } +} + +/** + * __v9fs_readpages_from_fscache - read multiple pages from cache + * + * Returns 0 if the pages are in cache and a BIO is submitted, + * 1 if the pages are not in cache and -error otherwise. + */ + +int __v9fs_readpages_from_fscache(struct inode *inode, + struct address_space *mapping, + struct list_head *pages, + unsigned *nr_pages) +{ + int ret; + const struct v9fs_inode *v9inode = V9FS_I(inode); + + p9_debug(P9_DEBUG_FSC, "inode %p pages %u\n", inode, *nr_pages); + if (!v9inode->fscache) + return -ENOBUFS; + + ret = fscache_read_or_alloc_pages(v9inode->fscache, + mapping, pages, nr_pages, + v9fs_vfs_readpage_complete, + NULL, + mapping_gfp_mask(mapping)); + switch (ret) { + case -ENOBUFS: + case -ENODATA: + p9_debug(P9_DEBUG_FSC, "pages/inodes not in cache %d\n", ret); + return 1; + case 0: + BUG_ON(!list_empty(pages)); + BUG_ON(*nr_pages != 0); + p9_debug(P9_DEBUG_FSC, "BIO submitted\n"); + return ret; + default: + p9_debug(P9_DEBUG_FSC, "ret %d\n", ret); + return ret; + } +} + +/** + * __v9fs_readpage_to_fscache - write a page to the cache + * + */ + +void __v9fs_readpage_to_fscache(struct inode *inode, struct page *page) +{ + int ret; + const struct v9fs_inode *v9inode = V9FS_I(inode); + + p9_debug(P9_DEBUG_FSC, "inode %p page %p\n", inode, page); + ret = fscache_write_page(v9inode->fscache, page, GFP_KERNEL); + p9_debug(P9_DEBUG_FSC, "ret = %d\n", ret); + if (ret != 0) + v9fs_uncache_page(inode, page); +} + +/* + * wait for a page to complete writing to the cache + */ +void __v9fs_fscache_wait_on_page_write(struct inode *inode, struct page *page) +{ + const struct v9fs_inode *v9inode = V9FS_I(inode); + p9_debug(P9_DEBUG_FSC, "inode %p page %p\n", inode, page); + if (PageFsCache(page)) + fscache_wait_on_page_write(v9inode->fscache, page); +} diff --git a/fs/9p/cache.h b/fs/9p/cache.h new file mode 100644 index 00000000000..2f967549109 --- /dev/null +++ b/fs/9p/cache.h @@ -0,0 +1,151 @@ +/* + * V9FS cache definitions. + * + * Copyright (C) 2009 by Abhishek Kulkarni <adkulkar@umail.iu.edu> + * + * 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 + * + */ + +#ifndef _9P_CACHE_H +#ifdef CONFIG_9P_FSCACHE +#include <linux/fscache.h> +#include <linux/spinlock.h> + +extern struct fscache_netfs v9fs_cache_netfs; +extern const struct fscache_cookie_def v9fs_cache_session_index_def; +extern const struct fscache_cookie_def v9fs_cache_inode_index_def; + +extern void v9fs_cache_session_get_cookie(struct v9fs_session_info *v9ses); +extern void v9fs_cache_session_put_cookie(struct v9fs_session_info *v9ses); + +extern void v9fs_cache_inode_get_cookie(struct inode *inode); +extern void v9fs_cache_inode_put_cookie(struct inode *inode); +extern void v9fs_cache_inode_flush_cookie(struct inode *inode); +extern void v9fs_cache_inode_set_cookie(struct inode *inode, struct file *filp); +extern void v9fs_cache_inode_reset_cookie(struct inode *inode); + +extern int __v9fs_cache_register(void); +extern void __v9fs_cache_unregister(void); + +extern int __v9fs_fscache_release_page(struct page *page, gfp_t gfp); +extern void __v9fs_fscache_invalidate_page(struct page *page); +extern int __v9fs_readpage_from_fscache(struct inode *inode, + struct page *page); +extern int __v9fs_readpages_from_fscache(struct inode *inode, + struct address_space *mapping, + struct list_head *pages, + unsigned *nr_pages); +extern void __v9fs_readpage_to_fscache(struct inode *inode, struct page *page); +extern void __v9fs_fscache_wait_on_page_write(struct inode *inode, + struct page *page); + +static inline int v9fs_fscache_release_page(struct page *page, + gfp_t gfp) +{ + return __v9fs_fscache_release_page(page, gfp); +} + +static inline void v9fs_fscache_invalidate_page(struct page *page) +{ + __v9fs_fscache_invalidate_page(page); +} + +static inline int v9fs_readpage_from_fscache(struct inode *inode, + struct page *page) +{ + return __v9fs_readpage_from_fscache(inode, page); +} + +static inline int v9fs_readpages_from_fscache(struct inode *inode, + struct address_space *mapping, + struct list_head *pages, + unsigned *nr_pages) +{ + return __v9fs_readpages_from_fscache(inode, mapping, pages, + nr_pages); +} + +static inline void v9fs_readpage_to_fscache(struct inode *inode, + struct page *page) +{ + if (PageFsCache(page)) + __v9fs_readpage_to_fscache(inode, page); +} + +static inline void v9fs_uncache_page(struct inode *inode, struct page *page) +{ + struct v9fs_inode *v9inode = V9FS_I(inode); + fscache_uncache_page(v9inode->fscache, page); + BUG_ON(PageFsCache(page)); +} + +static inline void v9fs_fscache_wait_on_page_write(struct inode *inode, + struct page *page) +{ + return __v9fs_fscache_wait_on_page_write(inode, page); +} + +#else /* CONFIG_9P_FSCACHE */ + +static inline void v9fs_cache_inode_get_cookie(struct inode *inode) +{ +} + +static inline void v9fs_cache_inode_put_cookie(struct inode *inode) +{ +} + +static inline void v9fs_cache_inode_set_cookie(struct inode *inode, struct file *file) +{ +} + +static inline int v9fs_fscache_release_page(struct page *page, + gfp_t gfp) { + return 1; +} + +static inline void v9fs_fscache_invalidate_page(struct page *page) {} + +static inline int v9fs_readpage_from_fscache(struct inode *inode, + struct page *page) +{ + return -ENOBUFS; +} + +static inline int v9fs_readpages_from_fscache(struct inode *inode, + struct address_space *mapping, + struct list_head *pages, + unsigned *nr_pages) +{ + return -ENOBUFS; +} + +static inline void v9fs_readpage_to_fscache(struct inode *inode, + struct page *page) +{} + +static inline void v9fs_uncache_page(struct inode *inode, struct page *page) +{} + +static inline void v9fs_fscache_wait_on_page_write(struct inode *inode, + struct page *page) +{ + return; +} + +#endif /* CONFIG_9P_FSCACHE */ +#endif /* _9P_CACHE_H */ diff --git a/fs/9p/conv.c b/fs/9p/conv.c deleted file mode 100644 index bf1f1006796..00000000000 --- a/fs/9p/conv.c +++ /dev/null @@ -1,836 +0,0 @@ -/* - * linux/fs/9p/conv.c - * - * 9P protocol conversion functions - * - * Copyright (C) 2004, 2005 by Latchesar Ionkov <lucho@ionkov.net> - * 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 as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to: - * Free Software Foundation - * 51 Franklin Street, Fifth Floor - * Boston, MA 02111-1301 USA - * - */ - -#include <linux/config.h> -#include <linux/module.h> -#include <linux/errno.h> -#include <linux/fs.h> -#include <linux/idr.h> -#include <asm/uaccess.h> -#include "debug.h" -#include "v9fs.h" -#include "9p.h" -#include "conv.h" - -/* - * Buffer to help with string parsing - */ -struct cbuf { - unsigned char *sp; - unsigned char *p; - unsigned char *ep; -}; - -static inline void buf_init(struct cbuf *buf, void *data, int datalen) -{ - buf->sp = buf->p = data; - buf->ep = data + datalen; -} - -static inline int buf_check_overflow(struct cbuf *buf) -{ - return buf->p > buf->ep; -} - -static int buf_check_size(struct cbuf *buf, int len) -{ - if (buf->p + len > buf->ep) { - if (buf->p < buf->ep) { - eprintk(KERN_ERR, "buffer overflow: want %d has %d\n", - len, (int)(buf->ep - buf->p)); - dump_stack(); - buf->p = buf->ep + 1; - } - - return 0; - } - - return 1; -} - -static void *buf_alloc(struct cbuf *buf, int len) -{ - void *ret = NULL; - - if (buf_check_size(buf, len)) { - ret = buf->p; - buf->p += len; - } - - return ret; -} - -static void buf_put_int8(struct cbuf *buf, u8 val) -{ - if (buf_check_size(buf, 1)) { - buf->p[0] = val; - buf->p++; - } -} - -static void buf_put_int16(struct cbuf *buf, u16 val) -{ - if (buf_check_size(buf, 2)) { - *(__le16 *) buf->p = cpu_to_le16(val); - buf->p += 2; - } -} - -static void buf_put_int32(struct cbuf *buf, u32 val) -{ - if (buf_check_size(buf, 4)) { - *(__le32 *)buf->p = cpu_to_le32(val); - buf->p += 4; - } -} - -static void buf_put_int64(struct cbuf *buf, u64 val) -{ - if (buf_check_size(buf, 8)) { - *(__le64 *)buf->p = cpu_to_le64(val); - buf->p += 8; - } -} - -static char *buf_put_stringn(struct cbuf *buf, const char *s, u16 slen) -{ - char *ret; - - ret = NULL; - if (buf_check_size(buf, slen + 2)) { - buf_put_int16(buf, slen); - ret = buf->p; - memcpy(buf->p, s, slen); - buf->p += slen; - } - - return ret; -} - -static inline void buf_put_string(struct cbuf *buf, const char *s) -{ - buf_put_stringn(buf, s, strlen(s)); -} - -static u8 buf_get_int8(struct cbuf *buf) -{ - u8 ret = 0; - - if (buf_check_size(buf, 1)) { - ret = buf->p[0]; - buf->p++; - } - - return ret; -} - -static u16 buf_get_int16(struct cbuf *buf) -{ - u16 ret = 0; - - if (buf_check_size(buf, 2)) { - ret = le16_to_cpu(*(__le16 *)buf->p); - buf->p += 2; - } - - return ret; -} - -static u32 buf_get_int32(struct cbuf *buf) -{ - u32 ret = 0; - - if (buf_check_size(buf, 4)) { - ret = le32_to_cpu(*(__le32 *)buf->p); - buf->p += 4; - } - - return ret; -} - -static u64 buf_get_int64(struct cbuf *buf) -{ - u64 ret = 0; - - if (buf_check_size(buf, 8)) { - ret = le64_to_cpu(*(__le64 *)buf->p); - buf->p += 8; - } - - return ret; -} - -static void buf_get_str(struct cbuf *buf, struct v9fs_str *vstr) -{ - vstr->len = buf_get_int16(buf); - if (!buf_check_overflow(buf) && buf_check_size(buf, vstr->len)) { - vstr->str = buf->p; - buf->p += vstr->len; - } else { - vstr->len = 0; - vstr->str = NULL; - } -} - -static void buf_get_qid(struct cbuf *bufp, struct v9fs_qid *qid) -{ - qid->type = buf_get_int8(bufp); - qid->version = buf_get_int32(bufp); - qid->path = buf_get_int64(bufp); -} - -/** - * v9fs_size_wstat - calculate the size of a variable length stat struct - * @stat: metadata (stat) structure - * @extended: non-zero if 9P2000.u - * - */ - -static int v9fs_size_wstat(struct v9fs_wstat *wstat, int extended) -{ - int size = 0; - - if (wstat == NULL) { - eprintk(KERN_ERR, "v9fs_size_stat: got a NULL stat pointer\n"); - return 0; - } - - size = /* 2 + *//* size[2] */ - 2 + /* type[2] */ - 4 + /* dev[4] */ - 1 + /* qid.type[1] */ - 4 + /* qid.vers[4] */ - 8 + /* qid.path[8] */ - 4 + /* mode[4] */ - 4 + /* atime[4] */ - 4 + /* mtime[4] */ - 8 + /* length[8] */ - 8; /* minimum sum of string lengths */ - - if (wstat->name) - size += strlen(wstat->name); - if (wstat->uid) - size += strlen(wstat->uid); - if (wstat->gid) - size += strlen(wstat->gid); - if (wstat->muid) - size += strlen(wstat->muid); - - if (extended) { - size += 4 + /* n_uid[4] */ - 4 + /* n_gid[4] */ - 4 + /* n_muid[4] */ - 2; /* string length of extension[4] */ - if (wstat->extension) - size += strlen(wstat->extension); - } - - return size; -} - -/** - * buf_get_stat - safely decode a recieved metadata (stat) structure - * @bufp: buffer to deserialize - * @stat: metadata (stat) structure - * @extended: non-zero if 9P2000.u - * - */ - -static void -buf_get_stat(struct cbuf *bufp, struct v9fs_stat *stat, int extended) -{ - stat->size = buf_get_int16(bufp); - stat->type = buf_get_int16(bufp); - stat->dev = buf_get_int32(bufp); - stat->qid.type = buf_get_int8(bufp); - stat->qid.version = buf_get_int32(bufp); - stat->qid.path = buf_get_int64(bufp); - stat->mode = buf_get_int32(bufp); - stat->atime = buf_get_int32(bufp); - stat->mtime = buf_get_int32(bufp); - stat->length = buf_get_int64(bufp); - buf_get_str(bufp, &stat->name); - buf_get_str(bufp, &stat->uid); - buf_get_str(bufp, &stat->gid); - buf_get_str(bufp, &stat->muid); - - if (extended) { - buf_get_str(bufp, &stat->extension); - stat->n_uid = buf_get_int32(bufp); - stat->n_gid = buf_get_int32(bufp); - stat->n_muid = buf_get_int32(bufp); - } -} - -/** - * v9fs_deserialize_stat - decode a received metadata structure - * @buf: buffer to deserialize - * @buflen: length of received buffer - * @stat: metadata structure to decode into - * @extended: non-zero if 9P2000.u - * - * Note: stat will point to the buf region. - */ - -int -v9fs_deserialize_stat(void *buf, u32 buflen, struct v9fs_stat *stat, - int extended) -{ - struct cbuf buffer; - struct cbuf *bufp = &buffer; - unsigned char *p; - - buf_init(bufp, buf, buflen); - p = bufp->p; - buf_get_stat(bufp, stat, extended); - - if (buf_check_overflow(bufp)) - return 0; - else - return bufp->p - p; -} - -/** - * deserialize_fcall - unmarshal a response - * @buf: recieved buffer - * @buflen: length of received buffer - * @rcall: fcall structure to populate - * @rcalllen: length of fcall structure to populate - * @extended: non-zero if 9P2000.u - * - */ - -int -v9fs_deserialize_fcall(void *buf, u32 buflen, struct v9fs_fcall *rcall, - int extended) -{ - - struct cbuf buffer; - struct cbuf *bufp = &buffer; - int i = 0; - - buf_init(bufp, buf, buflen); - - rcall->size = buf_get_int32(bufp); - rcall->id = buf_get_int8(bufp); - rcall->tag = buf_get_int16(bufp); - - dprintk(DEBUG_CONV, "size %d id %d tag %d\n", rcall->size, rcall->id, - rcall->tag); - - switch (rcall->id) { - default: - eprintk(KERN_ERR, "unknown message type: %d\n", rcall->id); - return -EPROTO; - case RVERSION: - rcall->params.rversion.msize = buf_get_int32(bufp); - buf_get_str(bufp, &rcall->params.rversion.version); - break; - case RFLUSH: - break; - case RATTACH: - rcall->params.rattach.qid.type = buf_get_int8(bufp); - rcall->params.rattach.qid.version = buf_get_int32(bufp); - rcall->params.rattach.qid.path = buf_get_int64(bufp); - break; - case RWALK: - rcall->params.rwalk.nwqid = buf_get_int16(bufp); - if (rcall->params.rwalk.nwqid > V9FS_MAXWELEM) { - eprintk(KERN_ERR, "Rwalk with more than %d qids: %d\n", - V9FS_MAXWELEM, rcall->params.rwalk.nwqid); - return -EPROTO; - } - - for (i = 0; i < rcall->params.rwalk.nwqid; i++) - buf_get_qid(bufp, &rcall->params.rwalk.wqids[i]); - break; - case ROPEN: - buf_get_qid(bufp, &rcall->params.ropen.qid); - rcall->params.ropen.iounit = buf_get_int32(bufp); - break; - case RCREATE: - buf_get_qid(bufp, &rcall->params.rcreate.qid); - rcall->params.rcreate.iounit = buf_get_int32(bufp); - break; - case RREAD: - rcall->params.rread.count = buf_get_int32(bufp); - rcall->params.rread.data = bufp->p; - buf_check_size(bufp, rcall->params.rread.count); - break; - case RWRITE: - rcall->params.rwrite.count = buf_get_int32(bufp); - break; - case RCLUNK: - break; - case RREMOVE: - break; - case RSTAT: - buf_get_int16(bufp); - buf_get_stat(bufp, &rcall->params.rstat.stat, extended); - break; - case RWSTAT: - break; - case RERROR: - buf_get_str(bufp, &rcall->params.rerror.error); - if (extended) - rcall->params.rerror.errno = buf_get_int16(bufp); - break; - } - - if (buf_check_overflow(bufp)) { - dprintk(DEBUG_ERROR, "buffer overflow\n"); - return -EIO; - } - - return bufp->p - bufp->sp; -} - -static inline void v9fs_put_int8(struct cbuf *bufp, u8 val, u8 * p) -{ - *p = val; - buf_put_int8(bufp, val); -} - -static inline void v9fs_put_int16(struct cbuf *bufp, u16 val, u16 * p) -{ - *p = val; - buf_put_int16(bufp, val); -} - -static inline void v9fs_put_int32(struct cbuf *bufp, u32 val, u32 * p) -{ - *p = val; - buf_put_int32(bufp, val); -} - -static inline void v9fs_put_int64(struct cbuf *bufp, u64 val, u64 * p) -{ - *p = val; - buf_put_int64(bufp, val); -} - -static void -v9fs_put_str(struct cbuf *bufp, char *data, struct v9fs_str *str) -{ - int len; - char *s; - - if (data) - len = strlen(data); - else - len = 0; - - s = buf_put_stringn(bufp, data, len); - if (str) { - str->len = len; - str->str = s; - } -} - -static int -v9fs_put_user_data(struct cbuf *bufp, const char __user * data, int count, - unsigned char **pdata) -{ - *pdata = buf_alloc(bufp, count); - return copy_from_user(*pdata, data, count); -} - -static void -v9fs_put_wstat(struct cbuf *bufp, struct v9fs_wstat *wstat, - struct v9fs_stat *stat, int statsz, int extended) -{ - v9fs_put_int16(bufp, statsz, &stat->size); - v9fs_put_int16(bufp, wstat->type, &stat->type); - v9fs_put_int32(bufp, wstat->dev, &stat->dev); - v9fs_put_int8(bufp, wstat->qid.type, &stat->qid.type); - v9fs_put_int32(bufp, wstat->qid.version, &stat->qid.version); - v9fs_put_int64(bufp, wstat->qid.path, &stat->qid.path); - v9fs_put_int32(bufp, wstat->mode, &stat->mode); - v9fs_put_int32(bufp, wstat->atime, &stat->atime); - v9fs_put_int32(bufp, wstat->mtime, &stat->mtime); - v9fs_put_int64(bufp, wstat->length, &stat->length); - - v9fs_put_str(bufp, wstat->name, &stat->name); - v9fs_put_str(bufp, wstat->uid, &stat->uid); - v9fs_put_str(bufp, wstat->gid, &stat->gid); - v9fs_put_str(bufp, wstat->muid, &stat->muid); - - if (extended) { - v9fs_put_str(bufp, wstat->extension, &stat->extension); - v9fs_put_int32(bufp, wstat->n_uid, &stat->n_uid); - v9fs_put_int32(bufp, wstat->n_gid, &stat->n_gid); - v9fs_put_int32(bufp, wstat->n_muid, &stat->n_muid); - } -} - -static struct v9fs_fcall * -v9fs_create_common(struct cbuf *bufp, u32 size, u8 id) -{ - struct v9fs_fcall *fc; - - size += 4 + 1 + 2; /* size[4] id[1] tag[2] */ - fc = kmalloc(sizeof(struct v9fs_fcall) + size, GFP_KERNEL); - if (!fc) - return ERR_PTR(-ENOMEM); - - fc->sdata = (char *)fc + sizeof(*fc); - - buf_init(bufp, (char *)fc->sdata, size); - v9fs_put_int32(bufp, size, &fc->size); - v9fs_put_int8(bufp, id, &fc->id); - v9fs_put_int16(bufp, V9FS_NOTAG, &fc->tag); - - return fc; -} - -void v9fs_set_tag(struct v9fs_fcall *fc, u16 tag) -{ - fc->tag = tag; - *(__le16 *) (fc->sdata + 5) = cpu_to_le16(tag); -} - -struct v9fs_fcall *v9fs_create_tversion(u32 msize, char *version) -{ - int size; - struct v9fs_fcall *fc; - struct cbuf buffer; - struct cbuf *bufp = &buffer; - - size = 4 + 2 + strlen(version); /* msize[4] version[s] */ - fc = v9fs_create_common(bufp, size, TVERSION); - if (IS_ERR(fc)) - goto error; - - v9fs_put_int32(bufp, msize, &fc->params.tversion.msize); - v9fs_put_str(bufp, version, &fc->params.tversion.version); - - if (buf_check_overflow(bufp)) { - kfree(fc); - fc = ERR_PTR(-ENOMEM); - } - error: - return fc; -} - -struct v9fs_fcall *v9fs_create_tauth(u32 afid, char *uname, char *aname) -{ - int size; - struct v9fs_fcall *fc; - struct cbuf buffer; - struct cbuf *bufp = &buffer; - - size = 4 + 2 + strlen(uname) + 2 + strlen(aname); /* afid[4] uname[s] aname[s] */ - fc = v9fs_create_common(bufp, size, TAUTH); - if (IS_ERR(fc)) - goto error; - - v9fs_put_int32(bufp, afid, &fc->params.tauth.afid); - v9fs_put_str(bufp, uname, &fc->params.tauth.uname); - v9fs_put_str(bufp, aname, &fc->params.tauth.aname); - - if (buf_check_overflow(bufp)) { - kfree(fc); - fc = ERR_PTR(-ENOMEM); - } - error: - return fc; -} - -struct v9fs_fcall * -v9fs_create_tattach(u32 fid, u32 afid, char *uname, char *aname) -{ - int size; - struct v9fs_fcall *fc; - struct cbuf buffer; - struct cbuf *bufp = &buffer; - - size = 4 + 4 + 2 + strlen(uname) + 2 + strlen(aname); /* fid[4] afid[4] uname[s] aname[s] */ - fc = v9fs_create_common(bufp, size, TATTACH); - if (IS_ERR(fc)) - goto error; - - v9fs_put_int32(bufp, fid, &fc->params.tattach.fid); - v9fs_put_int32(bufp, afid, &fc->params.tattach.afid); - v9fs_put_str(bufp, uname, &fc->params.tattach.uname); - v9fs_put_str(bufp, aname, &fc->params.tattach.aname); - - error: - return fc; -} - -struct v9fs_fcall *v9fs_create_tflush(u16 oldtag) -{ - int size; - struct v9fs_fcall *fc; - struct cbuf buffer; - struct cbuf *bufp = &buffer; - - size = 2; /* oldtag[2] */ - fc = v9fs_create_common(bufp, size, TFLUSH); - if (IS_ERR(fc)) - goto error; - - v9fs_put_int16(bufp, oldtag, &fc->params.tflush.oldtag); - - if (buf_check_overflow(bufp)) { - kfree(fc); - fc = ERR_PTR(-ENOMEM); - } - error: - return fc; -} - -struct v9fs_fcall *v9fs_create_twalk(u32 fid, u32 newfid, u16 nwname, - char **wnames) -{ - int i, size; - struct v9fs_fcall *fc; - struct cbuf buffer; - struct cbuf *bufp = &buffer; - - if (nwname > V9FS_MAXWELEM) { - dprintk(DEBUG_ERROR, "nwname > %d\n", V9FS_MAXWELEM); - return NULL; - } - - size = 4 + 4 + 2; /* fid[4] newfid[4] nwname[2] ... */ - for (i = 0; i < nwname; i++) { - size += 2 + strlen(wnames[i]); /* wname[s] */ - } - - fc = v9fs_create_common(bufp, size, TWALK); - if (IS_ERR(fc)) - goto error; - - v9fs_put_int32(bufp, fid, &fc->params.twalk.fid); - v9fs_put_int32(bufp, newfid, &fc->params.twalk.newfid); - v9fs_put_int16(bufp, nwname, &fc->params.twalk.nwname); - for (i = 0; i < nwname; i++) { - v9fs_put_str(bufp, wnames[i], &fc->params.twalk.wnames[i]); - } - - if (buf_check_overflow(bufp)) { - kfree(fc); - fc = ERR_PTR(-ENOMEM); - } - error: - return fc; -} - -struct v9fs_fcall *v9fs_create_topen(u32 fid, u8 mode) -{ - int size; - struct v9fs_fcall *fc; - struct cbuf buffer; - struct cbuf *bufp = &buffer; - - size = 4 + 1; /* fid[4] mode[1] */ - fc = v9fs_create_common(bufp, size, TOPEN); - if (IS_ERR(fc)) - goto error; - - v9fs_put_int32(bufp, fid, &fc->params.topen.fid); - v9fs_put_int8(bufp, mode, &fc->params.topen.mode); - - if (buf_check_overflow(bufp)) { - kfree(fc); - fc = ERR_PTR(-ENOMEM); - } - error: - return fc; -} - -struct v9fs_fcall *v9fs_create_tcreate(u32 fid, char *name, u32 perm, u8 mode) -{ - int size; - struct v9fs_fcall *fc; - struct cbuf buffer; - struct cbuf *bufp = &buffer; - - size = 4 + 2 + strlen(name) + 4 + 1; /* fid[4] name[s] perm[4] mode[1] */ - fc = v9fs_create_common(bufp, size, TCREATE); - if (IS_ERR(fc)) - goto error; - - v9fs_put_int32(bufp, fid, &fc->params.tcreate.fid); - v9fs_put_str(bufp, name, &fc->params.tcreate.name); - v9fs_put_int32(bufp, perm, &fc->params.tcreate.perm); - v9fs_put_int8(bufp, mode, &fc->params.tcreate.mode); - - if (buf_check_overflow(bufp)) { - kfree(fc); - fc = ERR_PTR(-ENOMEM); - } - error: - return fc; -} - -struct v9fs_fcall *v9fs_create_tread(u32 fid, u64 offset, u32 count) -{ - int size; - struct v9fs_fcall *fc; - struct cbuf buffer; - struct cbuf *bufp = &buffer; - - size = 4 + 8 + 4; /* fid[4] offset[8] count[4] */ - fc = v9fs_create_common(bufp, size, TREAD); - if (IS_ERR(fc)) - goto error; - - v9fs_put_int32(bufp, fid, &fc->params.tread.fid); - v9fs_put_int64(bufp, offset, &fc->params.tread.offset); - v9fs_put_int32(bufp, count, &fc->params.tread.count); - - if (buf_check_overflow(bufp)) { - kfree(fc); - fc = ERR_PTR(-ENOMEM); - } - error: - return fc; -} - -struct v9fs_fcall *v9fs_create_twrite(u32 fid, u64 offset, u32 count, - const char __user * data) -{ - int size, err; - struct v9fs_fcall *fc; - struct cbuf buffer; - struct cbuf *bufp = &buffer; - - size = 4 + 8 + 4 + count; /* fid[4] offset[8] count[4] data[count] */ - fc = v9fs_create_common(bufp, size, TWRITE); - if (IS_ERR(fc)) - goto error; - - v9fs_put_int32(bufp, fid, &fc->params.twrite.fid); - v9fs_put_int64(bufp, offset, &fc->params.twrite.offset); - v9fs_put_int32(bufp, count, &fc->params.twrite.count); - err = v9fs_put_user_data(bufp, data, count, &fc->params.twrite.data); - if (err) { - kfree(fc); - fc = ERR_PTR(err); - } - - if (buf_check_overflow(bufp)) { - kfree(fc); - fc = ERR_PTR(-ENOMEM); - } - error: - return fc; -} - -struct v9fs_fcall *v9fs_create_tclunk(u32 fid) -{ - int size; - struct v9fs_fcall *fc; - struct cbuf buffer; - struct cbuf *bufp = &buffer; - - size = 4; /* fid[4] */ - fc = v9fs_create_common(bufp, size, TCLUNK); - if (IS_ERR(fc)) - goto error; - - v9fs_put_int32(bufp, fid, &fc->params.tclunk.fid); - - if (buf_check_overflow(bufp)) { - kfree(fc); - fc = ERR_PTR(-ENOMEM); - } - error: - return fc; -} - -struct v9fs_fcall *v9fs_create_tremove(u32 fid) -{ - int size; - struct v9fs_fcall *fc; - struct cbuf buffer; - struct cbuf *bufp = &buffer; - - size = 4; /* fid[4] */ - fc = v9fs_create_common(bufp, size, TREMOVE); - if (IS_ERR(fc)) - goto error; - - v9fs_put_int32(bufp, fid, &fc->params.tremove.fid); - - if (buf_check_overflow(bufp)) { - kfree(fc); - fc = ERR_PTR(-ENOMEM); - } - error: - return fc; -} - -struct v9fs_fcall *v9fs_create_tstat(u32 fid) -{ - int size; - struct v9fs_fcall *fc; - struct cbuf buffer; - struct cbuf *bufp = &buffer; - - size = 4; /* fid[4] */ - fc = v9fs_create_common(bufp, size, TSTAT); - if (IS_ERR(fc)) - goto error; - - v9fs_put_int32(bufp, fid, &fc->params.tstat.fid); - - if (buf_check_overflow(bufp)) { - kfree(fc); - fc = ERR_PTR(-ENOMEM); - } - error: - return fc; -} - -struct v9fs_fcall *v9fs_create_twstat(u32 fid, struct v9fs_wstat *wstat, - int extended) -{ - int size, statsz; - struct v9fs_fcall *fc; - struct cbuf buffer; - struct cbuf *bufp = &buffer; - - statsz = v9fs_size_wstat(wstat, extended); - size = 4 + 2 + 2 + statsz; /* fid[4] stat[n] */ - fc = v9fs_create_common(bufp, size, TWSTAT); - if (IS_ERR(fc)) - goto error; - - v9fs_put_int32(bufp, fid, &fc->params.twstat.fid); - buf_put_int16(bufp, statsz + 2); - v9fs_put_wstat(bufp, wstat, &fc->params.twstat.stat, statsz, extended); - - if (buf_check_overflow(bufp)) { - kfree(fc); - fc = ERR_PTR(-ENOMEM); - } - error: - return fc; -} diff --git a/fs/9p/conv.h b/fs/9p/conv.h deleted file mode 100644 index 26a736e4a2e..00000000000 --- a/fs/9p/conv.h +++ /dev/null @@ -1,51 +0,0 @@ -/* - * linux/fs/9p/conv.h - * - * 9P protocol conversion definitions. - * - * Copyright (C) 2005 by Latchesar Ionkov <lucho@ionkov.net> - * 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 as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to: - * Free Software Foundation - * 51 Franklin Street, Fifth Floor - * Boston, MA 02111-1301 USA - * - */ - -int v9fs_deserialize_stat(void *buf, u32 buflen, struct v9fs_stat *stat, - int extended); -int v9fs_deserialize_fcall(void *buf, u32 buflen, struct v9fs_fcall *rcall, - int extended); - -void v9fs_set_tag(struct v9fs_fcall *fc, u16 tag); - -struct v9fs_fcall *v9fs_create_tversion(u32 msize, char *version); -struct v9fs_fcall *v9fs_create_tauth(u32 afid, char *uname, char *aname); -struct v9fs_fcall *v9fs_create_tattach(u32 fid, u32 afid, char *uname, - char *aname); -struct v9fs_fcall *v9fs_create_tflush(u16 oldtag); -struct v9fs_fcall *v9fs_create_twalk(u32 fid, u32 newfid, u16 nwname, - char **wnames); -struct v9fs_fcall *v9fs_create_topen(u32 fid, u8 mode); -struct v9fs_fcall *v9fs_create_tcreate(u32 fid, char *name, u32 perm, u8 mode); -struct v9fs_fcall *v9fs_create_tread(u32 fid, u64 offset, u32 count); -struct v9fs_fcall *v9fs_create_twrite(u32 fid, u64 offset, u32 count, - const char __user *data); -struct v9fs_fcall *v9fs_create_tclunk(u32 fid); -struct v9fs_fcall *v9fs_create_tremove(u32 fid); -struct v9fs_fcall *v9fs_create_tstat(u32 fid); -struct v9fs_fcall *v9fs_create_twstat(u32 fid, struct v9fs_wstat *wstat, - int extended); diff --git a/fs/9p/debug.h b/fs/9p/debug.h deleted file mode 100644 index fe551032788..00000000000 --- a/fs/9p/debug.h +++ /dev/null @@ -1,77 +0,0 @@ -/* - * linux/fs/9p/debug.h - V9FS Debug Definitions - * - * 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 as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to: - * Free Software Foundation - * 51 Franklin Street, Fifth Floor - * Boston, MA 02111-1301 USA - * - */ - -#define DEBUG_ERROR (1<<0) -#define DEBUG_CURRENT (1<<1) -#define DEBUG_9P (1<<2) -#define DEBUG_VFS (1<<3) -#define DEBUG_CONV (1<<4) -#define DEBUG_MUX (1<<5) -#define DEBUG_TRANS (1<<6) -#define DEBUG_SLABS (1<<7) - -#define DEBUG_DUMP_PKT 0 - -extern int v9fs_debug_level; - -#define dprintk(level, format, arg...) \ -do { \ - if((v9fs_debug_level & level)==level) \ - printk(KERN_NOTICE "-- %s (%d): " \ - format , __FUNCTION__, current->pid , ## arg); \ -} while(0) - -#define eprintk(level, format, arg...) \ -do { \ - printk(level "v9fs: %s (%d): " \ - format , __FUNCTION__, current->pid , ## arg); \ -} while(0) - -#if DEBUG_DUMP_PKT -static inline void dump_data(const unsigned char *data, unsigned int datalen) -{ - int i, n; - char buf[5*8]; - - n = 0; - i = 0; - while (i < datalen) { - n += snprintf(buf+n, sizeof(buf)-n, "%02x", data[i++]); - if (i%4 == 0) - n += snprintf(buf+n, sizeof(buf)-n, " "); - - if (i%16 == 0) { - dprintk(DEBUG_ERROR, "%s\n", buf); - n = 0; - } - } - - dprintk(DEBUG_ERROR, "%s\n", buf); -} -#else /* DEBUG_DUMP_PKT */ -static inline void dump_data(const unsigned char *data, unsigned int datalen) -{ - -} -#endif /* DEBUG_DUMP_PKT */ diff --git a/fs/9p/error.c b/fs/9p/error.c deleted file mode 100644 index e4b6f8f38b6..00000000000 --- a/fs/9p/error.c +++ /dev/null @@ -1,94 +0,0 @@ -/* - * linux/fs/9p/error.c - * - * Error string handling - * - * Plan 9 uses error strings, Unix uses error numbers. These functions - * try to help manage that and provide for dynamically adding error - * mappings. - * - * 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 as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to: - * Free Software Foundation - * 51 Franklin Street, Fifth Floor - * Boston, MA 02111-1301 USA - * - */ - -#include <linux/config.h> -#include <linux/module.h> - -#include <linux/list.h> -#include <linux/jhash.h> - -#include "debug.h" -#include "error.h" - -/** - * v9fs_error_init - preload - * @errstr: error string - * - */ - -int v9fs_error_init(void) -{ - struct errormap *c; - int bucket; - - /* initialize hash table */ - for (bucket = 0; bucket < ERRHASHSZ; bucket++) - INIT_HLIST_HEAD(&hash_errmap[bucket]); - - /* load initial error map into hash table */ - for (c = errmap; c->name != NULL; c++) { - c->namelen = strlen(c->name); - bucket = jhash(c->name, c->namelen, 0) % ERRHASHSZ; - INIT_HLIST_NODE(&c->list); - hlist_add_head(&c->list, &hash_errmap[bucket]); - } - - return 1; -} - -/** - * errstr2errno - convert error string to error number - * @errstr: error string - * - */ - -int v9fs_errstr2errno(char *errstr, int len) -{ - int errno = 0; - struct hlist_node *p = NULL; - struct errormap *c = NULL; - int bucket = jhash(errstr, len, 0) % ERRHASHSZ; - - hlist_for_each_entry(c, p, &hash_errmap[bucket], list) { - if (c->namelen==len && !memcmp(c->name, errstr, len)) { - errno = c->val; - break; - } - } - - if (errno == 0) { - /* TODO: if error isn't found, add it dynamically */ - printk(KERN_ERR "%s: errstr :%s: not found\n", __FUNCTION__, - errstr); - errno = 1; - } - - return -errno; -} diff --git a/fs/9p/error.h b/fs/9p/error.h deleted file mode 100644 index a9794e85fe5..00000000000 --- a/fs/9p/error.h +++ /dev/null @@ -1,178 +0,0 @@ -/* - * linux/fs/9p/error.h - * - * Huge Nasty Error Table - * - * Plan 9 uses error strings, Unix uses error numbers. This table tries to - * match UNIX strings and Plan 9 strings to unix error numbers. It is used - * to preload the dynamic error table which can also track user-specific error - * strings. - * - * 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 as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to: - * Free Software Foundation - * 51 Franklin Street, Fifth Floor - * Boston, MA 02111-1301 USA - * - */ - -#include <linux/errno.h> -#include <asm/errno.h> - -struct errormap { - char *name; - int val; - - int namelen; - struct hlist_node list; -}; - -#define ERRHASHSZ 32 -static struct hlist_head hash_errmap[ERRHASHSZ]; - -/* FixMe - reduce to a reasonable size */ -static struct errormap errmap[] = { - {"Operation not permitted", EPERM}, - {"wstat prohibited", EPERM}, - {"No such file or directory", ENOENT}, - {"directory entry not found", ENOENT}, - {"file not found", ENOENT}, - {"Interrupted system call", EINTR}, - {"Input/output error", EIO}, - {"No such device or address", ENXIO}, - {"Argument list too long", E2BIG}, - {"Bad file descriptor", EBADF}, - {"Resource temporarily unavailable", EAGAIN}, - {"Cannot allocate memory", ENOMEM}, - {"Permission denied", EACCES}, - {"Bad address", EFAULT}, - {"Block device required", ENOTBLK}, - {"Device or resource busy", EBUSY}, - {"File exists", EEXIST}, - {"Invalid cross-device link", EXDEV}, - {"No such device", ENODEV}, - {"Not a directory", ENOTDIR}, - {"Is a directory", EISDIR}, - {"Invalid argument", EINVAL}, - {"Too many open files in system", ENFILE}, - {"Too many open files", EMFILE}, - {"Text file busy", ETXTBSY}, - {"File too large", EFBIG}, - {"No space left on device", ENOSPC}, - {"Illegal seek", ESPIPE}, - {"Read-only file system", EROFS}, - {"Too many links", EMLINK}, - {"Broken pipe", EPIPE}, - {"Numerical argument out of domain", EDOM}, - {"Numerical result out of range", ERANGE}, - {"Resource deadlock avoided", EDEADLK}, - {"File name too long", ENAMETOOLONG}, - {"No locks available", ENOLCK}, - {"Function not implemented", ENOSYS}, - {"Directory not empty", ENOTEMPTY}, - {"Too many levels of symbolic links", ELOOP}, - {"No message of desired type", ENOMSG}, - {"Identifier removed", EIDRM}, - {"No data available", ENODATA}, - {"Machine is not on the network", ENONET}, - {"Package not installed", ENOPKG}, - {"Object is remote", EREMOTE}, - {"Link has been severed", ENOLINK}, - {"Communication error on send", ECOMM}, - {"Protocol error", EPROTO}, - {"Bad message", EBADMSG}, - {"File descriptor in bad state", EBADFD}, - {"Streams pipe error", ESTRPIPE}, - {"Too many users", EUSERS}, - {"Socket operation on non-socket", ENOTSOCK}, - {"Message too long", EMSGSIZE}, - {"Protocol not available", ENOPROTOOPT}, - {"Protocol not supported", EPROTONOSUPPORT}, - {"Socket type not supported", ESOCKTNOSUPPORT}, - {"Operation not supported", EOPNOTSUPP}, - {"Protocol family not supported", EPFNOSUPPORT}, - {"Network is down", ENETDOWN}, - {"Network is unreachable", ENETUNREACH}, - {"Network dropped connection on reset", ENETRESET}, - {"Software caused connection abort", ECONNABORTED}, - {"Connection reset by peer", ECONNRESET}, - {"No buffer space available", ENOBUFS}, - {"Transport endpoint is already connected", EISCONN}, - {"Transport endpoint is not connected", ENOTCONN}, - {"Cannot send after transport endpoint shutdown", ESHUTDOWN}, - {"Connection timed out", ETIMEDOUT}, - {"Connection refused", ECONNREFUSED}, - {"Host is down", EHOSTDOWN}, - {"No route to host", EHOSTUNREACH}, - {"Operation already in progress", EALREADY}, - {"Operation now in progress", EINPROGRESS}, - {"Is a named type file", EISNAM}, - {"Remote I/O error", EREMOTEIO}, - {"Disk quota exceeded", EDQUOT}, -/* errors from fossil, vacfs, and u9fs */ - {"fid unknown or out of range", EBADF}, - {"permission denied", EACCES}, - {"file does not exist", ENOENT}, - {"authentication failed", ECONNREFUSED}, - {"bad offset in directory read", ESPIPE}, - {"bad use of fid", EBADF}, - {"wstat can't convert between files and directories", EPERM}, - {"directory is not empty", ENOTEMPTY}, - {"file exists", EEXIST}, - {"file already exists", EEXIST}, - {"file or directory already exists", EEXIST}, - {"fid already in use", EBADF}, - {"file in use", ETXTBSY}, - {"i/o error", EIO}, - {"file already open for I/O", ETXTBSY}, - {"illegal mode", EINVAL}, - {"illegal name", ENAMETOOLONG}, - {"not a directory", ENOTDIR}, - {"not a member of proposed group", EPERM}, - {"not owner", EACCES}, - {"only owner can change group in wstat", EACCES}, - {"read only file system", EROFS}, - {"no access to special file", EPERM}, - {"i/o count too large", EIO}, - {"unknown group", EINVAL}, - {"unknown user", EINVAL}, - {"bogus wstat buffer", EPROTO}, - {"exclusive use file already open", EAGAIN}, - {"corrupted directory entry", EIO}, - {"corrupted file entry", EIO}, - {"corrupted block label", EIO}, - {"corrupted meta data", EIO}, - {"illegal offset", EINVAL}, - {"illegal path element", ENOENT}, - {"root of file system is corrupted", EIO}, - {"corrupted super block", EIO}, - {"protocol botch", EPROTO}, - {"file system is full", ENOSPC}, - {"file is in use", EAGAIN}, - {"directory entry is not allocated", ENOENT}, - {"file is read only", EROFS}, - {"file has been removed", EIDRM}, - {"only support truncation to zero length", EPERM}, - {"cannot remove root", EPERM}, - {"file too big", EFBIG}, - {"venti i/o error", EIO}, - /* these are not errors */ - {"u9fs rhostsauth: no authentication required", 0}, - {"u9fs authnone: no authentication required", 0}, - {NULL, -1} -}; - -extern int v9fs_error_init(void); diff --git a/fs/9p/fid.c b/fs/9p/fid.c index eda449778fa..d51ec9fafcc 100644 --- a/fs/9p/fid.c +++ b/fs/9p/fid.c @@ -1,12 +1,12 @@ /* * V9FS FID Management * - * Copyright (C) 2005 by Eric Van Hensbergen <ericvh@gmail.com> + * 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 as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. + * 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 @@ -21,232 +21,286 @@ * */ -#include <linux/config.h> #include <linux/module.h> #include <linux/errno.h> #include <linux/fs.h> +#include <linux/slab.h> +#include <linux/sched.h> #include <linux/idr.h> +#include <net/9p/9p.h> +#include <net/9p/client.h> -#include "debug.h" #include "v9fs.h" -#include "9p.h" #include "v9fs_vfs.h" #include "fid.h" /** - * v9fs_fid_insert - add a fid to a dentry + * v9fs_fid_add - add a fid to a dentry + * @dentry: dentry that the fid is being added to * @fid: fid to add - * @dentry: dentry that it is being added to * */ -static int v9fs_fid_insert(struct v9fs_fid *fid, struct dentry *dentry) +static inline void __add_fid(struct dentry *dentry, struct p9_fid *fid) { - struct list_head *fid_list = (struct list_head *)dentry->d_fsdata; - dprintk(DEBUG_9P, "fid %d (%p) dentry %s (%p)\n", fid->fid, fid, - dentry->d_iname, dentry); - if (dentry->d_fsdata == NULL) { - dentry->d_fsdata = - kmalloc(sizeof(struct list_head), GFP_KERNEL); - if (dentry->d_fsdata == NULL) { - dprintk(DEBUG_ERROR, "Out of memory\n"); - return -ENOMEM; - } - fid_list = (struct list_head *)dentry->d_fsdata; - INIT_LIST_HEAD(fid_list); /* Initialize list head */ - } + hlist_add_head(&fid->dlist, (struct hlist_head *)&dentry->d_fsdata); +} - fid->uid = current->uid; - fid->pid = current->pid; - list_add(&fid->list, fid_list); - return 0; +void v9fs_fid_add(struct dentry *dentry, struct p9_fid *fid) +{ + spin_lock(&dentry->d_lock); + __add_fid(dentry, fid); + spin_unlock(&dentry->d_lock); } /** - * v9fs_fid_create - allocate a FID structure - * @dentry - dentry to link newly created fid to + * 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 * */ -struct v9fs_fid *v9fs_fid_create(struct dentry *dentry, - struct v9fs_session_info *v9ses, int fid, int create) +static struct p9_fid *v9fs_fid_find(struct dentry *dentry, kuid_t uid, int any) { - struct v9fs_fid *new; - - dprintk(DEBUG_9P, "fid create dentry %p, fid %d, create %d\n", - dentry, fid, create); + struct p9_fid *fid, *ret; - new = kmalloc(sizeof(struct v9fs_fid), GFP_KERNEL); - if (new == NULL) { - dprintk(DEBUG_ERROR, "Out of Memory\n"); - return ERR_PTR(-ENOMEM); + p9_debug(P9_DEBUG_VFS, " dentry: %s (%p) uid %d any %d\n", + dentry->d_name.name, dentry, from_kuid(&init_user_ns, uid), + any); + ret = NULL; + /* we'll recheck under lock if there's anything to look in */ + if (dentry->d_fsdata) { + struct hlist_head *h = (struct hlist_head *)&dentry->d_fsdata; + spin_lock(&dentry->d_lock); + hlist_for_each_entry(fid, h, dlist) { + if (any || uid_eq(fid->uid, uid)) { + ret = fid; + break; + } + } + spin_unlock(&dentry->d_lock); } - new->fid = fid; - new->v9ses = v9ses; - new->fidopen = 0; - new->fidcreate = create; - new->fidclunked = 0; - new->iounit = 0; - new->rdir_pos = 0; - new->rdir_fcall = NULL; - - if (v9fs_fid_insert(new, dentry) == 0) - return new; - else { - dprintk(DEBUG_ERROR, "Problems inserting to dentry\n"); - kfree(new); - return NULL; - } + return ret; } -/** - * v9fs_fid_destroy - deallocate a FID structure - * @fid: fid to destroy - * +/* + * We need to hold v9ses->rename_sem as long as we hold references + * to returned path array. Array element contain pointers to + * dentry names. */ - -void v9fs_fid_destroy(struct v9fs_fid *fid) +static int build_path_from_dentry(struct v9fs_session_info *v9ses, + struct dentry *dentry, char ***names) { - list_del(&fid->list); - kfree(fid); + int n = 0, i; + char **wnames; + struct dentry *ds; + + for (ds = dentry; !IS_ROOT(ds); ds = ds->d_parent) + n++; + + wnames = kmalloc(sizeof(char *) * n, GFP_KERNEL); + if (!wnames) + goto err_out; + + for (ds = dentry, i = (n-1); i >= 0; i--, ds = ds->d_parent) + wnames[i] = (char *)ds->d_name.name; + + *names = wnames; + return n; +err_out: + return -ENOMEM; } -/** - * v9fs_fid_walk_up - walks from the process current directory - * up to the specified dentry. - */ -static struct v9fs_fid *v9fs_fid_walk_up(struct dentry *dentry) +static struct p9_fid *v9fs_fid_lookup_with_uid(struct dentry *dentry, + kuid_t uid, int any) { - int fidnum, cfidnum, err; - struct v9fs_fid *cfid; - struct dentry *cde; + struct dentry *ds; + char **wnames, *uname; + int i, n, l, clone, access; struct v9fs_session_info *v9ses; - - v9ses = v9fs_inode2v9ses(current->fs->pwd->d_inode); - cfid = v9fs_fid_lookup(current->fs->pwd); - if (cfid == NULL) { - dprintk(DEBUG_ERROR, "process cwd doesn't have a fid\n"); - return ERR_PTR(-ENOENT); + struct p9_fid *fid, *old_fid = NULL; + + v9ses = v9fs_dentry2v9ses(dentry); + access = v9ses->flags & V9FS_ACCESS_MASK; + fid = v9fs_fid_find(dentry, uid, any); + if (fid) + return fid; + /* + * we don't have a matching fid. To do a TWALK we need + * parent fid. We need to prevent rename when we want to + * look at the parent. + */ + down_read(&v9ses->rename_sem); + ds = dentry->d_parent; + fid = v9fs_fid_find(ds, uid, any); + if (fid) { + /* Found the parent fid do a lookup with that */ + fid = p9_client_walk(fid, 1, (char **)&dentry->d_name.name, 1); + goto fid_out; } - - cfidnum = cfid->fid; - cde = current->fs->pwd; - /* TODO: take advantage of multiwalk */ - - fidnum = v9fs_get_idpool(&v9ses->fidpool); - if (fidnum < 0) { - dprintk(DEBUG_ERROR, "could not get a new fid num\n"); - err = -ENOENT; - goto clunk_fid; + up_read(&v9ses->rename_sem); + + /* start from the root and try to do a lookup */ + fid = v9fs_fid_find(dentry->d_sb->s_root, uid, any); + if (!fid) { + /* the user is not attached to the fs yet */ + if (access == V9FS_ACCESS_SINGLE) + return ERR_PTR(-EPERM); + + if (v9fs_proto_dotu(v9ses) || v9fs_proto_dotl(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(dentry->d_sb->s_root, fid); } - - while (cde != dentry) { - if (cde == cde->d_parent) { - dprintk(DEBUG_ERROR, "can't find dentry\n"); - err = -ENOENT; - goto clunk_fid; + /* If we are root ourself just return that */ + if (dentry->d_sb->s_root == dentry) + return fid; + /* + * Do a multipath walk with attached root. + * When walking parent we need to make sure we + * don't have a parallel rename happening + */ + down_read(&v9ses->rename_sem); + n = build_path_from_dentry(v9ses, dentry, &wnames); + if (n < 0) { + fid = ERR_PTR(n); + goto err_out; + } + clone = 1; + i = 0; + while (i < n) { + l = min(n - i, P9_MAXWELEM); + /* + * We need to hold rename lock when doing a multipath + * walk to ensure none of the patch component change + */ + fid = p9_client_walk(fid, l, &wnames[i], clone); + if (IS_ERR(fid)) { + if (old_fid) { + /* + * If we fail, clunk fid which are mapping + * to path component and not the last component + * of the path. + */ + p9_client_clunk(old_fid); + } + kfree(wnames); + goto err_out; } - - err = v9fs_t_walk(v9ses, cfidnum, fidnum, "..", NULL); - if (err < 0) { - dprintk(DEBUG_ERROR, "problem walking to parent\n"); - goto clunk_fid; + old_fid = fid; + i += l; + clone = 0; + } + kfree(wnames); +fid_out: + if (!IS_ERR(fid)) { + spin_lock(&dentry->d_lock); + if (d_unhashed(dentry)) { + spin_unlock(&dentry->d_lock); + p9_client_clunk(fid); + fid = ERR_PTR(-ENOENT); + } else { + __add_fid(dentry, fid); + spin_unlock(&dentry->d_lock); } - - cfidnum = fidnum; - cde = cde->d_parent; } - - return v9fs_fid_create(dentry, v9ses, fidnum, 0); - -clunk_fid: - v9fs_t_clunk(v9ses, fidnum); - return ERR_PTR(err); +err_out: + up_read(&v9ses->rename_sem); + return fid; } /** - * v9fs_fid_lookup - retrieve the right fid from a particular dentry + * v9fs_fid_lookup - lookup for a fid, try to walk if not found * @dentry: dentry to look for fid in - * @type: intent of lookup (operation or traversal) - * - * search list of fids associated with a dentry for a fid with a matching - * thread id or uid. If that fails, look up the dentry's parents to see if you - * can find a matching fid. * + * 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 v9fs_fid *v9fs_fid_lookup(struct dentry *dentry) +struct p9_fid *v9fs_fid_lookup(struct dentry *dentry) { - struct list_head *fid_list = (struct list_head *)dentry->d_fsdata; - struct v9fs_fid *current_fid = NULL; - struct v9fs_fid *temp = NULL; - struct v9fs_fid *return_fid = NULL; - - dprintk(DEBUG_9P, " dentry: %s (%p)\n", dentry->d_iname, dentry); - - if (fid_list) { - list_for_each_entry_safe(current_fid, temp, fid_list, list) { - if (!current_fid->fidcreate) { - return_fid = current_fid; - break; - } - } - - if (!return_fid) - return_fid = current_fid; - } + kuid_t uid; + int any, access; + struct v9fs_session_info *v9ses; - /* we are at the root but didn't match */ - if ((!return_fid) && (dentry->d_parent == dentry)) { - /* TODO: clone attach with new uid */ - return_fid = current_fid; + v9ses = v9fs_dentry2v9ses(dentry); + access = v9ses->flags & V9FS_ACCESS_MASK; + switch (access) { + case V9FS_ACCESS_SINGLE: + case V9FS_ACCESS_USER: + case V9FS_ACCESS_CLIENT: + uid = current_fsuid(); + any = 0; + break; + + case V9FS_ACCESS_ANY: + uid = v9ses->uid; + any = 1; + break; + + default: + uid = INVALID_UID; + any = 0; + break; } + return v9fs_fid_lookup_with_uid(dentry, uid, any); +} - if (!return_fid) { - struct dentry *par = current->fs->pwd->d_parent; - int count = 1; - while (par != NULL) { - if (par == dentry) - break; - count++; - if (par == par->d_parent) { - dprintk(DEBUG_ERROR, - "got to root without finding dentry\n"); - break; - } - par = par->d_parent; - } +struct p9_fid *v9fs_fid_clone(struct dentry *dentry) +{ + struct p9_fid *fid, *ret; -/* XXX - there may be some duplication we can get rid of */ - if (par == dentry) { - return_fid = v9fs_fid_walk_up(dentry); - if (IS_ERR(return_fid)) - return_fid = NULL; - } - } + fid = v9fs_fid_lookup(dentry); + if (IS_ERR(fid)) + return fid; - return return_fid; + ret = p9_client_walk(fid, 0, NULL, 1); + return ret; } -struct v9fs_fid *v9fs_fid_get_created(struct dentry *dentry) +static struct p9_fid *v9fs_fid_clone_with_uid(struct dentry *dentry, kuid_t uid) { - struct list_head *fid_list; - struct v9fs_fid *fid, *ftmp, *ret; + struct p9_fid *fid, *ret; - dprintk(DEBUG_9P, " dentry: %s (%p)\n", dentry->d_iname, dentry); - fid_list = (struct list_head *)dentry->d_fsdata; - ret = NULL; - if (fid_list) { - list_for_each_entry_safe(fid, ftmp, fid_list, list) { - if (fid->fidcreate && fid->pid == current->pid) { - list_del(&fid->list); - ret = fid; - break; - } - } - } + fid = v9fs_fid_lookup_with_uid(dentry, uid, 0); + if (IS_ERR(fid)) + return fid; - dprintk(DEBUG_9P, "return %p\n", ret); + ret = p9_client_walk(fid, 0, NULL, 1); return ret; } + +struct p9_fid *v9fs_writeback_fid(struct dentry *dentry) +{ + int err; + struct p9_fid *fid; + + fid = v9fs_fid_clone_with_uid(dentry, GLOBAL_ROOT_UID); + if (IS_ERR(fid)) + goto error_out; + /* + * writeback fid will only be used to write back the + * dirty pages. We always request for the open fid in read-write + * mode so that a partial page write which result in page + * read can work. + */ + err = p9_client_open(fid, O_RDWR); + if (err < 0) { + p9_client_clunk(fid); + fid = ERR_PTR(err); + goto error_out; + } +error_out: + return fid; +} diff --git a/fs/9p/fid.h b/fs/9p/fid.h index 84c673a44c8..2b6787fcb62 100644 --- a/fs/9p/fid.h +++ b/fs/9p/fid.h @@ -4,9 +4,8 @@ * 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 as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. + * 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 @@ -20,41 +19,12 @@ * Boston, MA 02111-1301 USA * */ - +#ifndef FS_9P_FID_H +#define FS_9P_FID_H #include <linux/list.h> -#define FID_OP 0 -#define FID_WALK 1 -#define FID_CREATE 2 - -struct v9fs_fid { - struct list_head list; /* list of fids associated with a dentry */ - struct list_head active; /* XXX - debug */ - - u32 fid; - unsigned char fidopen; /* set when fid is opened */ - unsigned char fidcreate; /* set when fid was just created */ - unsigned char fidclunked; /* set when fid has already been clunked */ - - struct v9fs_qid qid; - u32 iounit; - - /* readdir stuff */ - int rdir_fpos; - loff_t rdir_pos; - struct v9fs_fcall *rdir_fcall; - - /* management stuff */ - pid_t pid; /* thread associated with this fid */ - uid_t uid; /* user associated with this fid */ - - /* private data */ - struct file *filp; /* backpointer to File struct for open files */ - struct v9fs_session_info *v9ses; /* session info for this FID */ -}; - -struct v9fs_fid *v9fs_fid_lookup(struct dentry *dentry); -struct v9fs_fid *v9fs_fid_get_created(struct dentry *); -void v9fs_fid_destroy(struct v9fs_fid *fid); -struct v9fs_fid *v9fs_fid_create(struct dentry *, - struct v9fs_session_info *v9ses, int fid, int create); +struct p9_fid *v9fs_fid_lookup(struct dentry *dentry); +struct p9_fid *v9fs_fid_clone(struct dentry *dentry); +void v9fs_fid_add(struct dentry *dentry, struct p9_fid *fid); +struct p9_fid *v9fs_writeback_fid(struct dentry *dentry); +#endif diff --git a/fs/9p/mux.c b/fs/9p/mux.c deleted file mode 100644 index ea1134eb47c..00000000000 --- a/fs/9p/mux.c +++ /dev/null @@ -1,977 +0,0 @@ -/* - * linux/fs/9p/mux.c - * - * Protocol Multiplexer - * - * Copyright (C) 2004 by Eric Van Hensbergen <ericvh@gmail.com> - * Copyright (C) 2004-2005 by Latchesar Ionkov <lucho@ionkov.net> - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to: - * Free Software Foundation - * 51 Franklin Street, Fifth Floor - * Boston, MA 02111-1301 USA - * - */ - -#include <linux/config.h> -#include <linux/module.h> -#include <linux/errno.h> -#include <linux/fs.h> -#include <linux/poll.h> -#include <linux/kthread.h> -#include <linux/idr.h> - -#include "debug.h" -#include "v9fs.h" -#include "9p.h" -#include "conv.h" -#include "transport.h" -#include "mux.h" - -#define ERREQFLUSH 1 -#define SCHED_TIMEOUT 10 -#define MAXPOLLWADDR 2 - -enum { - Rworksched = 1, /* read work scheduled or running */ - Rpending = 2, /* can read */ - Wworksched = 4, /* write work scheduled or running */ - Wpending = 8, /* can write */ -}; - -struct v9fs_mux_poll_task; - -struct v9fs_req { - int tag; - struct v9fs_fcall *tcall; - struct v9fs_fcall *rcall; - int err; - v9fs_mux_req_callback cb; - void *cba; - struct list_head req_list; -}; - -struct v9fs_mux_data { - spinlock_t lock; - struct list_head mux_list; - struct v9fs_mux_poll_task *poll_task; - int msize; - unsigned char *extended; - struct v9fs_transport *trans; - struct v9fs_idpool tidpool; - int err; - wait_queue_head_t equeue; - struct list_head req_list; - struct list_head unsent_req_list; - struct v9fs_fcall *rcall; - int rpos; - char *rbuf; - int wpos; - int wsize; - char *wbuf; - wait_queue_t poll_wait[MAXPOLLWADDR]; - wait_queue_head_t *poll_waddr[MAXPOLLWADDR]; - poll_table pt; - struct work_struct rq; - struct work_struct wq; - unsigned long wsched; -}; - -struct v9fs_mux_poll_task { - struct task_struct *task; - struct list_head mux_list; - int muxnum; -}; - -struct v9fs_mux_rpc { - struct v9fs_mux_data *m; - struct v9fs_req *req; - int err; - struct v9fs_fcall *rcall; - wait_queue_head_t wqueue; -}; - -static int v9fs_poll_proc(void *); -static void v9fs_read_work(void *); -static void v9fs_write_work(void *); -static void v9fs_pollwait(struct file *filp, wait_queue_head_t * wait_address, - poll_table * p); -static u16 v9fs_mux_get_tag(struct v9fs_mux_data *); -static void v9fs_mux_put_tag(struct v9fs_mux_data *, u16); - -static DECLARE_MUTEX(v9fs_mux_task_lock); -static struct workqueue_struct *v9fs_mux_wq; - -static int v9fs_mux_num; -static int v9fs_mux_poll_task_num; -static struct v9fs_mux_poll_task v9fs_mux_poll_tasks[100]; - -int v9fs_mux_global_init(void) -{ - int i; - - for (i = 0; i < ARRAY_SIZE(v9fs_mux_poll_tasks); i++) - v9fs_mux_poll_tasks[i].task = NULL; - - v9fs_mux_wq = create_workqueue("v9fs"); - if (!v9fs_mux_wq) - return -ENOMEM; - - return 0; -} - -void v9fs_mux_global_exit(void) -{ - destroy_workqueue(v9fs_mux_wq); -} - -/** - * v9fs_mux_calc_poll_procs - calculates the number of polling procs - * based on the number of mounted v9fs filesystems. - * - * The current implementation returns sqrt of the number of mounts. - */ -inline int v9fs_mux_calc_poll_procs(int muxnum) -{ - int n; - - if (v9fs_mux_poll_task_num) - n = muxnum / v9fs_mux_poll_task_num + - (muxnum % v9fs_mux_poll_task_num ? 1 : 0); - else - n = 1; - - if (n > ARRAY_SIZE(v9fs_mux_poll_tasks)) - n = ARRAY_SIZE(v9fs_mux_poll_tasks); - - return n; -} - -static int v9fs_mux_poll_start(struct v9fs_mux_data *m) -{ - int i, n; - struct v9fs_mux_poll_task *vpt, *vptlast; - struct task_struct *pproc; - - dprintk(DEBUG_MUX, "mux %p muxnum %d procnum %d\n", m, v9fs_mux_num, - v9fs_mux_poll_task_num); - up(&v9fs_mux_task_lock); - - n = v9fs_mux_calc_poll_procs(v9fs_mux_num + 1); - if (n > v9fs_mux_poll_task_num) { - for (i = 0; i < ARRAY_SIZE(v9fs_mux_poll_tasks); i++) { - if (v9fs_mux_poll_tasks[i].task == NULL) { - vpt = &v9fs_mux_poll_tasks[i]; - dprintk(DEBUG_MUX, "create proc %p\n", vpt); - pproc = kthread_create(v9fs_poll_proc, vpt, - "v9fs-poll"); - - if (!IS_ERR(pproc)) { - vpt->task = pproc; - INIT_LIST_HEAD(&vpt->mux_list); - vpt->muxnum = 0; - v9fs_mux_poll_task_num++; - wake_up_process(vpt->task); - } - break; - } - } - - if (i >= ARRAY_SIZE(v9fs_mux_poll_tasks)) - dprintk(DEBUG_ERROR, "warning: no free poll slots\n"); - } - - n = (v9fs_mux_num + 1) / v9fs_mux_poll_task_num + - ((v9fs_mux_num + 1) % v9fs_mux_poll_task_num ? 1 : 0); - - vptlast = NULL; - for (i = 0; i < ARRAY_SIZE(v9fs_mux_poll_tasks); i++) { - vpt = &v9fs_mux_poll_tasks[i]; - if (vpt->task != NULL) { - vptlast = vpt; - if (vpt->muxnum < n) { - dprintk(DEBUG_MUX, "put in proc %d\n", i); - list_add(&m->mux_list, &vpt->mux_list); - vpt->muxnum++; - m->poll_task = vpt; - memset(&m->poll_waddr, 0, sizeof(m->poll_waddr)); - init_poll_funcptr(&m->pt, v9fs_pollwait); - break; - } - } - } - - if (i >= ARRAY_SIZE(v9fs_mux_poll_tasks)) { - if (vptlast == NULL) - return -ENOMEM; - - dprintk(DEBUG_MUX, "put in proc %d\n", i); - list_add(&m->mux_list, &vptlast->mux_list); - vptlast->muxnum++; - m->poll_task = vptlast; - memset(&m->poll_waddr, 0, sizeof(m->poll_waddr)); - init_poll_funcptr(&m->pt, v9fs_pollwait); - } - - v9fs_mux_num++; - down(&v9fs_mux_task_lock); - - return 0; -} - -static void v9fs_mux_poll_stop(struct v9fs_mux_data *m) -{ - int i; - struct v9fs_mux_poll_task *vpt; - - up(&v9fs_mux_task_lock); - vpt = m->poll_task; - list_del(&m->mux_list); - for(i = 0; i < ARRAY_SIZE(m->poll_waddr); i++) { - if (m->poll_waddr[i] != NULL) { - remove_wait_queue(m->poll_waddr[i], &m->poll_wait[i]); - m->poll_waddr[i] = NULL; - } - } - vpt->muxnum--; - if (!vpt->muxnum) { - dprintk(DEBUG_MUX, "destroy proc %p\n", vpt); - send_sig(SIGKILL, vpt->task, 1); - vpt->task = NULL; - v9fs_mux_poll_task_num--; - } - v9fs_mux_num--; - down(&v9fs_mux_task_lock); -} - -/** - * v9fs_mux_init - allocate and initialize the per-session mux data - * Creates the polling task if this is the first session. - * - * @trans - transport structure - * @msize - maximum message size - * @extended - pointer to the extended flag - */ -struct v9fs_mux_data *v9fs_mux_init(struct v9fs_transport *trans, int msize, - unsigned char *extended) -{ - int i, n; - struct v9fs_mux_data *m, *mtmp; - - dprintk(DEBUG_MUX, "transport %p msize %d\n", trans, msize); - m = kmalloc(sizeof(struct v9fs_mux_data), GFP_KERNEL); - if (!m) - return ERR_PTR(-ENOMEM); - - spin_lock_init(&m->lock); - INIT_LIST_HEAD(&m->mux_list); - m->msize = msize; - m->extended = extended; - m->trans = trans; - idr_init(&m->tidpool.pool); - init_MUTEX(&m->tidpool.lock); - m->err = 0; - init_waitqueue_head(&m->equeue); - INIT_LIST_HEAD(&m->req_list); - INIT_LIST_HEAD(&m->unsent_req_list); - m->rcall = NULL; - m->rpos = 0; - m->rbuf = NULL; - m->wpos = m->wsize = 0; - m->wbuf = NULL; - INIT_WORK(&m->rq, v9fs_read_work, m); - INIT_WORK(&m->wq, v9fs_write_work, m); - m->wsched = 0; - memset(&m->poll_waddr, 0, sizeof(m->poll_waddr)); - m->poll_task = NULL; - n = v9fs_mux_poll_start(m); - if (n) - return ERR_PTR(n); - - n = trans->poll(trans, &m->pt); - if (n & POLLIN) { - dprintk(DEBUG_MUX, "mux %p can read\n", m); - set_bit(Rpending, &m->wsched); - } - - if (n & POLLOUT) { - dprintk(DEBUG_MUX, "mux %p can write\n", m); - set_bit(Wpending, &m->wsched); - } - - for(i = 0; i < ARRAY_SIZE(m->poll_waddr); i++) { - if (IS_ERR(m->poll_waddr[i])) { - v9fs_mux_poll_stop(m); - mtmp = (void *)m->poll_waddr; /* the error code */ - kfree(m); - m = mtmp; - break; - } - } - - return m; -} - -/** - * v9fs_mux_destroy - cancels all pending requests and frees mux resources - */ -void v9fs_mux_destroy(struct v9fs_mux_data *m) -{ - dprintk(DEBUG_MUX, "mux %p prev %p next %p\n", m, - m->mux_list.prev, m->mux_list.next); - v9fs_mux_cancel(m, -ECONNRESET); - - if (!list_empty(&m->req_list)) { - /* wait until all processes waiting on this session exit */ - dprintk(DEBUG_MUX, "mux %p waiting for empty request queue\n", - m); - wait_event_timeout(m->equeue, (list_empty(&m->req_list)), 5000); - dprintk(DEBUG_MUX, "mux %p request queue empty: %d\n", m, - list_empty(&m->req_list)); - } - - v9fs_mux_poll_stop(m); - m->trans = NULL; - - kfree(m); -} - -/** - * v9fs_pollwait - called by files poll operation to add v9fs-poll task - * to files wait queue - */ -static void -v9fs_pollwait(struct file *filp, wait_queue_head_t * wait_address, - poll_table * p) -{ - int i; - struct v9fs_mux_data *m; - - m = container_of(p, struct v9fs_mux_data, pt); - for(i = 0; i < ARRAY_SIZE(m->poll_waddr); i++) - if (m->poll_waddr[i] == NULL) - break; - - if (i >= ARRAY_SIZE(m->poll_waddr)) { - dprintk(DEBUG_ERROR, "not enough wait_address slots\n"); - return; - } - - m->poll_waddr[i] = wait_address; - - if (!wait_address) { - dprintk(DEBUG_ERROR, "no wait_address\n"); - m->poll_waddr[i] = ERR_PTR(-EIO); - return; - } - - init_waitqueue_entry(&m->poll_wait[i], m->poll_task->task); - add_wait_queue(wait_address, &m->poll_wait[i]); -} - -/** - * v9fs_poll_mux - polls a mux and schedules read or write works if necessary - */ -static inline void v9fs_poll_mux(struct v9fs_mux_data *m) -{ - int n; - - if (m->err < 0) - return; - - n = m->trans->poll(m->trans, NULL); - if (n < 0 || n & (POLLERR | POLLHUP | POLLNVAL)) { - dprintk(DEBUG_MUX, "error mux %p err %d\n", m, n); - if (n >= 0) - n = -ECONNRESET; - v9fs_mux_cancel(m, n); - } - - if (n & POLLIN) { - set_bit(Rpending, &m->wsched); - dprintk(DEBUG_MUX, "mux %p can read\n", m); - if (!test_and_set_bit(Rworksched, &m->wsched)) { - dprintk(DEBUG_MUX, "schedule read work mux %p\n", m); - queue_work(v9fs_mux_wq, &m->rq); - } - } - - if (n & POLLOUT) { - set_bit(Wpending, &m->wsched); - dprintk(DEBUG_MUX, "mux %p can write\n", m); - if ((m->wsize || !list_empty(&m->unsent_req_list)) - && !test_and_set_bit(Wworksched, &m->wsched)) { - dprintk(DEBUG_MUX, "schedule write work mux %p\n", m); - queue_work(v9fs_mux_wq, &m->wq); - } - } -} - -/** - * v9fs_poll_proc - polls all v9fs transports for new events and queues - * the appropriate work to the work queue - */ -static int v9fs_poll_proc(void *a) -{ - struct v9fs_mux_data *m, *mtmp; - struct v9fs_mux_poll_task *vpt; - - vpt = a; - dprintk(DEBUG_MUX, "start %p %p\n", current, vpt); - allow_signal(SIGKILL); - while (!kthread_should_stop()) { - set_current_state(TASK_INTERRUPTIBLE); - if (signal_pending(current)) - break; - - list_for_each_entry_safe(m, mtmp, &vpt->mux_list, mux_list) { - v9fs_poll_mux(m); - } - - dprintk(DEBUG_MUX, "sleeping...\n"); - schedule_timeout(SCHED_TIMEOUT * HZ); - } - - __set_current_state(TASK_RUNNING); - dprintk(DEBUG_MUX, "finish\n"); - return 0; -} - -/** - * v9fs_write_work - called when a transport can send some data - */ -static void v9fs_write_work(void *a) -{ - int n, err; - struct v9fs_mux_data *m; - struct v9fs_req *req; - - m = a; - - if (m->err < 0) { - clear_bit(Wworksched, &m->wsched); - return; - } - - if (!m->wsize) { - if (list_empty(&m->unsent_req_list)) { - clear_bit(Wworksched, &m->wsched); - return; - } - - spin_lock(&m->lock); -again: - req = list_entry(m->unsent_req_list.next, struct v9fs_req, - req_list); - list_move_tail(&req->req_list, &m->req_list); - if (req->err == ERREQFLUSH) - goto again; - - m->wbuf = req->tcall->sdata; - m->wsize = req->tcall->size; - m->wpos = 0; - dump_data(m->wbuf, m->wsize); - spin_unlock(&m->lock); - } - - dprintk(DEBUG_MUX, "mux %p pos %d size %d\n", m, m->wpos, m->wsize); - clear_bit(Wpending, &m->wsched); - err = m->trans->write(m->trans, m->wbuf + m->wpos, m->wsize - m->wpos); - dprintk(DEBUG_MUX, "mux %p sent %d bytes\n", m, err); - if (err == -EAGAIN) { - clear_bit(Wworksched, &m->wsched); - return; - } - - if (err <= 0) - goto error; - - m->wpos += err; - if (m->wpos == m->wsize) - m->wpos = m->wsize = 0; - - if (m->wsize == 0 && !list_empty(&m->unsent_req_list)) { - if (test_and_clear_bit(Wpending, &m->wsched)) - n = POLLOUT; - else - n = m->trans->poll(m->trans, NULL); - - if (n & POLLOUT) { - dprintk(DEBUG_MUX, "schedule write work mux %p\n", m); - queue_work(v9fs_mux_wq, &m->wq); - } else - clear_bit(Wworksched, &m->wsched); - } else - clear_bit(Wworksched, &m->wsched); - - return; - - error: - v9fs_mux_cancel(m, err); - clear_bit(Wworksched, &m->wsched); -} - -static void process_request(struct v9fs_mux_data *m, struct v9fs_req *req) -{ - int ecode, tag; - struct v9fs_str *ename; - - tag = req->tag; - if (!req->err && req->rcall->id == RERROR) { - ecode = req->rcall->params.rerror.errno; - ename = &req->rcall->params.rerror.error; - - dprintk(DEBUG_MUX, "Rerror %.*s\n", ename->len, ename->str); - - if (*m->extended) - req->err = -ecode; - - if (!req->err) { - req->err = v9fs_errstr2errno(ename->str, ename->len); - - if (!req->err) { /* string match failed */ - PRINT_FCALL_ERROR("unknown error", req->rcall); - } - - if (!req->err) - req->err = -ESERVERFAULT; - } - } else if (req->tcall && req->rcall->id != req->tcall->id + 1) { - dprintk(DEBUG_ERROR, "fcall mismatch: expected %d, got %d\n", - req->tcall->id + 1, req->rcall->id); - if (!req->err) - req->err = -EIO; - } - - if (req->err == ERREQFLUSH) - return; - - if (req->cb) { - dprintk(DEBUG_MUX, "calling callback tcall %p rcall %p\n", - req->tcall, req->rcall); - - (*req->cb) (req->cba, req->tcall, req->rcall, req->err); - req->cb = NULL; - } else - kfree(req->rcall); - - v9fs_mux_put_tag(m, tag); - - wake_up(&m->equeue); - kfree(req); -} - -/** - * v9fs_read_work - called when there is some data to be read from a transport - */ -static void v9fs_read_work(void *a) -{ - int n, err; - struct v9fs_mux_data *m; - struct v9fs_req *req, *rptr, *rreq; - struct v9fs_fcall *rcall; - char *rbuf; - - m = a; - - if (m->err < 0) - return; - - rcall = NULL; - dprintk(DEBUG_MUX, "start mux %p pos %d\n", m, m->rpos); - - if (!m->rcall) { - m->rcall = - kmalloc(sizeof(struct v9fs_fcall) + m->msize, GFP_KERNEL); - if (!m->rcall) { - err = -ENOMEM; - goto error; - } - - m->rbuf = (char *)m->rcall + sizeof(struct v9fs_fcall); - m->rpos = 0; - } - - clear_bit(Rpending, &m->wsched); - err = m->trans->read(m->trans, m->rbuf + m->rpos, m->msize - m->rpos); - dprintk(DEBUG_MUX, "mux %p got %d bytes\n", m, err); - if (err == -EAGAIN) { - clear_bit(Rworksched, &m->wsched); - return; - } - - if (err <= 0) - goto error; - - m->rpos += err; - while (m->rpos > 4) { - n = le32_to_cpu(*(__le32 *) m->rbuf); - if (n >= m->msize) { - dprintk(DEBUG_ERROR, - "requested packet size too big: %d\n", n); - err = -EIO; - goto error; - } - - if (m->rpos < n) - break; - - dump_data(m->rbuf, n); - err = - v9fs_deserialize_fcall(m->rbuf, n, m->rcall, *m->extended); - if (err < 0) { - goto error; - } - - rcall = m->rcall; - rbuf = m->rbuf; - if (m->rpos > n) { - m->rcall = kmalloc(sizeof(struct v9fs_fcall) + m->msize, - GFP_KERNEL); - if (!m->rcall) { - err = -ENOMEM; - goto error; - } - - m->rbuf = (char *)m->rcall + sizeof(struct v9fs_fcall); - memmove(m->rbuf, rbuf + n, m->rpos - n); - m->rpos -= n; - } else { - m->rcall = NULL; - m->rbuf = NULL; - m->rpos = 0; - } - - dprintk(DEBUG_MUX, "mux %p fcall id %d tag %d\n", m, rcall->id, - rcall->tag); - - req = NULL; - spin_lock(&m->lock); - list_for_each_entry_safe(rreq, rptr, &m->req_list, req_list) { - if (rreq->tag == rcall->tag) { - req = rreq; - req->rcall = rcall; - list_del(&req->req_list); - spin_unlock(&m->lock); - process_request(m, req); - break; - } - - } - - if (!req) { - spin_unlock(&m->lock); - if (err >= 0 && rcall->id != RFLUSH) - dprintk(DEBUG_ERROR, - "unexpected response mux %p id %d tag %d\n", - m, rcall->id, rcall->tag); - kfree(rcall); - } - } - - if (!list_empty(&m->req_list)) { - if (test_and_clear_bit(Rpending, &m->wsched)) - n = POLLIN; - else - n = m->trans->poll(m->trans, NULL); - - if (n & POLLIN) { - dprintk(DEBUG_MUX, "schedule read work mux %p\n", m); - queue_work(v9fs_mux_wq, &m->rq); - } else - clear_bit(Rworksched, &m->wsched); - } else - clear_bit(Rworksched, &m->wsched); - - return; - - error: - v9fs_mux_cancel(m, err); - clear_bit(Rworksched, &m->wsched); -} - -/** - * v9fs_send_request - send 9P request - * The function can sleep until the request is scheduled for sending. - * The function can be interrupted. Return from the function is not - * a guarantee that the request is sent succesfully. Can return errors - * that can be retrieved by PTR_ERR macros. - * - * @m: mux data - * @tc: request to be sent - * @cb: callback function to call when response is received - * @cba: parameter to pass to the callback function - */ -static struct v9fs_req *v9fs_send_request(struct v9fs_mux_data *m, - struct v9fs_fcall *tc, - v9fs_mux_req_callback cb, void *cba) -{ - int n; - struct v9fs_req *req; - - dprintk(DEBUG_MUX, "mux %p task %p tcall %p id %d\n", m, current, - tc, tc->id); - if (m->err < 0) - return ERR_PTR(m->err); - - req = kmalloc(sizeof(struct v9fs_req), GFP_KERNEL); - if (!req) - return ERR_PTR(-ENOMEM); - - if (tc->id == TVERSION) - n = V9FS_NOTAG; - else - n = v9fs_mux_get_tag(m); - - if (n < 0) - return ERR_PTR(-ENOMEM); - - v9fs_set_tag(tc, n); - - req->tag = n; - req->tcall = tc; - req->rcall = NULL; - req->err = 0; - req->cb = cb; - req->cba = cba; - - spin_lock(&m->lock); - list_add_tail(&req->req_list, &m->unsent_req_list); - spin_unlock(&m->lock); - - if (test_and_clear_bit(Wpending, &m->wsched)) - n = POLLOUT; - else - n = m->trans->poll(m->trans, NULL); - - if (n & POLLOUT && !test_and_set_bit(Wworksched, &m->wsched)) - queue_work(v9fs_mux_wq, &m->wq); - - return req; -} - -static inline void -v9fs_mux_flush_cb(void *a, struct v9fs_fcall *tc, struct v9fs_fcall *rc, - int err) -{ - v9fs_mux_req_callback cb; - int tag; - struct v9fs_mux_data *m; - struct v9fs_req *req, *rptr; - - m = a; - dprintk(DEBUG_MUX, "mux %p tc %p rc %p err %d oldtag %d\n", m, tc, - rc, err, tc->params.tflush.oldtag); - - spin_lock(&m->lock); - cb = NULL; - tag = tc->params.tflush.oldtag; - list_for_each_entry_safe(req, rptr, &m->req_list, req_list) { - if (req->tag == tag) { - list_del(&req->req_list); - if (req->cb) { - cb = req->cb; - req->cb = NULL; - spin_unlock(&m->lock); - (*cb) (req->cba, req->tcall, req->rcall, - req->err); - } - kfree(req); - wake_up(&m->equeue); - break; - } - } - - if (!cb) - spin_unlock(&m->lock); - - v9fs_mux_put_tag(m, tag); - kfree(tc); - kfree(rc); -} - -static void -v9fs_mux_flush_request(struct v9fs_mux_data *m, struct v9fs_req *req) -{ - struct v9fs_fcall *fc; - - dprintk(DEBUG_MUX, "mux %p req %p tag %d\n", m, req, req->tag); - - fc = v9fs_create_tflush(req->tag); - v9fs_send_request(m, fc, v9fs_mux_flush_cb, m); -} - -static void -v9fs_mux_rpc_cb(void *a, struct v9fs_fcall *tc, struct v9fs_fcall *rc, int err) -{ - struct v9fs_mux_rpc *r; - - if (err == ERREQFLUSH) { - kfree(rc); - dprintk(DEBUG_MUX, "err req flush\n"); - return; - } - - r = a; - dprintk(DEBUG_MUX, "mux %p req %p tc %p rc %p err %d\n", r->m, r->req, - tc, rc, err); - r->rcall = rc; - r->err = err; - wake_up(&r->wqueue); -} - -/** - * v9fs_mux_rpc - sends 9P request and waits until a response is available. - * The function can be interrupted. - * @m: mux data - * @tc: request to be sent - * @rc: pointer where a pointer to the response is stored - */ -int -v9fs_mux_rpc(struct v9fs_mux_data *m, struct v9fs_fcall *tc, - struct v9fs_fcall **rc) -{ - int err; - unsigned long flags; - struct v9fs_req *req; - struct v9fs_mux_rpc r; - - r.err = 0; - r.rcall = NULL; - r.m = m; - init_waitqueue_head(&r.wqueue); - - if (rc) - *rc = NULL; - - req = v9fs_send_request(m, tc, v9fs_mux_rpc_cb, &r); - if (IS_ERR(req)) { - err = PTR_ERR(req); - dprintk(DEBUG_MUX, "error %d\n", err); - return PTR_ERR(req); - } - - r.req = req; - dprintk(DEBUG_MUX, "mux %p tc %p tag %d rpc %p req %p\n", m, tc, - req->tag, &r, req); - err = wait_event_interruptible(r.wqueue, r.rcall != NULL || r.err < 0); - if (r.err < 0) - err = r.err; - - if (err == -ERESTARTSYS && m->trans->status == Connected && m->err == 0) { - spin_lock(&m->lock); - req->tcall = NULL; - req->err = ERREQFLUSH; - spin_unlock(&m->lock); - - clear_thread_flag(TIF_SIGPENDING); - v9fs_mux_flush_request(m, req); - spin_lock_irqsave(¤t->sighand->siglock, flags); - recalc_sigpending(); - spin_unlock_irqrestore(¤t->sighand->siglock, flags); - } - - if (!err) { - if (r.rcall) - dprintk(DEBUG_MUX, "got response id %d tag %d\n", - r.rcall->id, r.rcall->tag); - - if (rc) - *rc = r.rcall; - else - kfree(r.rcall); - } else { - kfree(r.rcall); - dprintk(DEBUG_MUX, "got error %d\n", err); - if (err > 0) - err = -EIO; - } - - return err; -} - -/** - * v9fs_mux_rpcnb - sends 9P request without waiting for response. - * @m: mux data - * @tc: request to be sent - * @cb: callback function to be called when response arrives - * @cba: value to pass to the callback function - */ -int v9fs_mux_rpcnb(struct v9fs_mux_data *m, struct v9fs_fcall *tc, - v9fs_mux_req_callback cb, void *a) -{ - int err; - struct v9fs_req *req; - - req = v9fs_send_request(m, tc, cb, a); - if (IS_ERR(req)) { - err = PTR_ERR(req); - dprintk(DEBUG_MUX, "error %d\n", err); - return PTR_ERR(req); - } - - dprintk(DEBUG_MUX, "mux %p tc %p tag %d\n", m, tc, req->tag); - return 0; -} - -/** - * v9fs_mux_cancel - cancel all pending requests with error - * @m: mux data - * @err: error code - */ -void v9fs_mux_cancel(struct v9fs_mux_data *m, int err) -{ - struct v9fs_req *req, *rtmp; - LIST_HEAD(cancel_list); - - dprintk(DEBUG_MUX, "mux %p err %d\n", m, err); - m->err = err; - spin_lock(&m->lock); - list_for_each_entry_safe(req, rtmp, &m->req_list, req_list) { - list_move(&req->req_list, &cancel_list); - } - spin_unlock(&m->lock); - - list_for_each_entry_safe(req, rtmp, &cancel_list, req_list) { - list_del(&req->req_list); - if (!req->err) - req->err = err; - - if (req->cb) - (*req->cb) (req->cba, req->tcall, req->rcall, req->err); - else - kfree(req->rcall); - - kfree(req); - } - - wake_up(&m->equeue); -} - -static u16 v9fs_mux_get_tag(struct v9fs_mux_data *m) -{ - int tag; - - tag = v9fs_get_idpool(&m->tidpool); - if (tag < 0) - return V9FS_NOTAG; - else - return (u16) tag; -} - -static void v9fs_mux_put_tag(struct v9fs_mux_data *m, u16 tag) -{ - if (tag != V9FS_NOTAG && v9fs_check_idpool(tag, &m->tidpool)) - v9fs_put_idpool(tag, &m->tidpool); -} diff --git a/fs/9p/mux.h b/fs/9p/mux.h deleted file mode 100644 index 9473b84f24b..00000000000 --- a/fs/9p/mux.h +++ /dev/null @@ -1,58 +0,0 @@ -/* - * linux/fs/9p/mux.h - * - * Multiplexer Definitions - * - * Copyright (C) 2005 by Latchesar Ionkov <lucho@ionkov.net> - * Copyright (C) 2004 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 as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to: - * Free Software Foundation - * 51 Franklin Street, Fifth Floor - * Boston, MA 02111-1301 USA - * - */ - -struct v9fs_mux_data; - -/** - * v9fs_mux_req_callback - callback function that is called when the - * response of a request is received. The callback is called from - * a workqueue and shouldn't block. - * - * @a - the pointer that was specified when the request was send to be - * passed to the callback - * @tc - request call - * @rc - response call - * @err - error code (non-zero if error occured) - */ -typedef void (*v9fs_mux_req_callback)(void *a, struct v9fs_fcall *tc, - struct v9fs_fcall *rc, int err); - -int v9fs_mux_global_init(void); -void v9fs_mux_global_exit(void); - -struct v9fs_mux_data *v9fs_mux_init(struct v9fs_transport *trans, int msize, - unsigned char *extended); -void v9fs_mux_destroy(struct v9fs_mux_data *); - -int v9fs_mux_send(struct v9fs_mux_data *m, struct v9fs_fcall *tc); -struct v9fs_fcall *v9fs_mux_recv(struct v9fs_mux_data *m); -int v9fs_mux_rpc(struct v9fs_mux_data *m, struct v9fs_fcall *tc, struct v9fs_fcall **rc); -int v9fs_mux_rpcnb(struct v9fs_mux_data *m, struct v9fs_fcall *tc, - v9fs_mux_req_callback cb, void *a); - -void v9fs_mux_flush(struct v9fs_mux_data *m, int sendflush); -void v9fs_mux_cancel(struct v9fs_mux_data *m, int err); -int v9fs_errstr2errno(char *errstr, int len); diff --git a/fs/9p/trans_fd.c b/fs/9p/trans_fd.c deleted file mode 100644 index 1a28ef97a3d..00000000000 --- a/fs/9p/trans_fd.c +++ /dev/null @@ -1,215 +0,0 @@ -/* - * linux/fs/9p/trans_fd.c - * - * File Descriptor Transport Layer - * - * Copyright (C) 2005 by Latchesar Ionkov <lucho@ionkov.net> - * 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 as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to: - * Free Software Foundation - * 51 Franklin Street, Fifth Floor - * Boston, MA 02111-1301 USA - * - */ - -#include <linux/config.h> -#include <linux/module.h> -#include <linux/net.h> -#include <linux/ipv6.h> -#include <linux/errno.h> -#include <linux/kernel.h> -#include <linux/un.h> -#include <asm/uaccess.h> -#include <linux/inet.h> -#include <linux/idr.h> -#include <linux/file.h> - -#include "debug.h" -#include "v9fs.h" -#include "transport.h" - -struct v9fs_trans_fd { - struct file *in_file; - struct file *out_file; -}; - -/** - * v9fs_fd_recv - receive from a socket - * @v9ses: session information - * @v: buffer to receive data into - * @len: size of receive buffer - * - */ - -static int v9fs_fd_recv(struct v9fs_transport *trans, void *v, int len) -{ - struct v9fs_trans_fd *ts = trans ? trans->priv : NULL; - - if (!trans || trans->status != Connected || !ts) - return -EIO; - - return kernel_read(ts->in_file, ts->in_file->f_pos, v, len); -} - -/** - * v9fs_fd_send - send to a socket - * @v9ses: session information - * @v: buffer to send data from - * @len: size of send buffer - * - */ - -static int v9fs_fd_send(struct v9fs_transport *trans, void *v, int len) -{ - struct v9fs_trans_fd *ts = trans ? trans->priv : NULL; - mm_segment_t oldfs = get_fs(); - int ret = 0; - - if (!trans || trans->status != Connected || !ts) - return -EIO; - - set_fs(get_ds()); - /* The cast to a user pointer is valid due to the set_fs() */ - ret = vfs_write(ts->out_file, (void __user *)v, len, &ts->out_file->f_pos); - set_fs(oldfs); - - return ret; -} - -/** - * v9fs_fd_init - initialize file descriptor transport - * @v9ses: session information - * @addr: address of server to mount - * @data: mount options - * - */ - -static int -v9fs_fd_init(struct v9fs_session_info *v9ses, const char *addr, char *data) -{ - struct v9fs_trans_fd *ts = NULL; - struct v9fs_transport *trans = v9ses->transport; - - if((v9ses->wfdno == ~0) || (v9ses->rfdno == ~0)) { - printk(KERN_ERR "v9fs: Insufficient options for proto=fd\n"); - return -ENOPROTOOPT; - } - - ts = kmalloc(sizeof(struct v9fs_trans_fd), GFP_KERNEL); - - if (!ts) - return -ENOMEM; - - ts->in_file = fget( v9ses->rfdno ); - ts->out_file = fget( v9ses->wfdno ); - - if (!ts->in_file || !ts->out_file) { - if (ts->in_file) - fput(ts->in_file); - - if (ts->out_file) - fput(ts->out_file); - - kfree(ts); - return -EIO; - } - - trans->priv = ts; - trans->status = Connected; - - return 0; -} - - -/** - * v9fs_fd_close - shutdown file descriptor - * @trans: private socket structure - * - */ - -static void v9fs_fd_close(struct v9fs_transport *trans) -{ - struct v9fs_trans_fd *ts; - - if (!trans) - return; - - ts = xchg(&trans->priv, NULL); - - if (!ts) - return; - - trans->status = Disconnected; - if (ts->in_file) - fput(ts->in_file); - - if (ts->out_file) - fput(ts->out_file); - - kfree(ts); -} - -static unsigned int -v9fs_fd_poll(struct v9fs_transport *trans, struct poll_table_struct *pt) -{ - int ret, n; - struct v9fs_trans_fd *ts; - mm_segment_t oldfs; - - if (!trans) - return -EIO; - - ts = trans->priv; - if (trans->status != Connected || !ts) - return -EIO; - - oldfs = get_fs(); - set_fs(get_ds()); - - if (!ts->in_file->f_op || !ts->in_file->f_op->poll) { - ret = -EIO; - goto end; - } - - ret = ts->in_file->f_op->poll(ts->in_file, pt); - - if (ts->out_file != ts->in_file) { - if (!ts->out_file->f_op || !ts->out_file->f_op->poll) { - ret = -EIO; - goto end; - } - - n = ts->out_file->f_op->poll(ts->out_file, pt); - - ret &= ~POLLOUT; - n &= ~POLLIN; - - ret |= n; - } - -end: - set_fs(oldfs); - return ret; -} - - -struct v9fs_transport v9fs_trans_fd = { - .init = v9fs_fd_init, - .write = v9fs_fd_send, - .read = v9fs_fd_recv, - .close = v9fs_fd_close, - .poll = v9fs_fd_poll, -}; - diff --git a/fs/9p/trans_sock.c b/fs/9p/trans_sock.c deleted file mode 100644 index 44e830697ac..00000000000 --- a/fs/9p/trans_sock.c +++ /dev/null @@ -1,334 +0,0 @@ -/* - * linux/fs/9p/trans_socket.c - * - * Socket Transport Layer - * - * Copyright (C) 2004-2005 by Latchesar Ionkov <lucho@ionkov.net> - * Copyright (C) 2004 by Eric Van Hensbergen <ericvh@gmail.com> - * Copyright (C) 1997-2002 by Ron Minnich <rminnich@sarnoff.com> - * Copyright (C) 1995, 1996 by Olaf Kirch <okir@monad.swb.de> - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to: - * Free Software Foundation - * 51 Franklin Street, Fifth Floor - * Boston, MA 02111-1301 USA - * - */ - -#include <linux/config.h> -#include <linux/in.h> -#include <linux/module.h> -#include <linux/net.h> -#include <linux/ipv6.h> -#include <linux/errno.h> -#include <linux/kernel.h> -#include <linux/un.h> -#include <asm/uaccess.h> -#include <linux/inet.h> -#include <linux/idr.h> -#include <linux/file.h> - -#include "debug.h" -#include "v9fs.h" -#include "transport.h" - -#define V9FS_PORT 564 - -struct v9fs_trans_sock { - struct socket *s; - struct file *filp; -}; - -/** - * v9fs_sock_recv - receive from a socket - * @v9ses: session information - * @v: buffer to receive data into - * @len: size of receive buffer - * - */ - -static int v9fs_sock_recv(struct v9fs_transport *trans, void *v, int len) -{ - int ret; - struct v9fs_trans_sock *ts; - - if (!trans || trans->status == Disconnected) { - dprintk(DEBUG_ERROR, "disconnected ...\n"); - return -EREMOTEIO; - } - - ts = trans->priv; - - if (!(ts->filp->f_flags & O_NONBLOCK)) - dprintk(DEBUG_ERROR, "blocking read ...\n"); - - ret = kernel_read(ts->filp, ts->filp->f_pos, v, len); - if (ret <= 0) { - if (ret != -ERESTARTSYS && ret != -EAGAIN) - trans->status = Disconnected; - } - - return ret; -} - -/** - * v9fs_sock_send - send to a socket - * @v9ses: session information - * @v: buffer to send data from - * @len: size of send buffer - * - */ - -static int v9fs_sock_send(struct v9fs_transport *trans, void *v, int len) -{ - int ret; - mm_segment_t oldfs; - struct v9fs_trans_sock *ts; - - if (!trans || trans->status == Disconnected) { - dprintk(DEBUG_ERROR, "disconnected ...\n"); - return -EREMOTEIO; - } - - ts = trans->priv; - if (!ts) { - dprintk(DEBUG_ERROR, "no transport ...\n"); - return -EREMOTEIO; - } - - if (!(ts->filp->f_flags & O_NONBLOCK)) - dprintk(DEBUG_ERROR, "blocking write ...\n"); - - oldfs = get_fs(); - set_fs(get_ds()); - ret = vfs_write(ts->filp, (void __user *)v, len, &ts->filp->f_pos); - set_fs(oldfs); - - if (ret < 0) { - if (ret != -ERESTARTSYS) - trans->status = Disconnected; - } - - return ret; -} - -static unsigned int v9fs_sock_poll(struct v9fs_transport *trans, - struct poll_table_struct *pt) { - - int ret; - struct v9fs_trans_sock *ts; - mm_segment_t oldfs; - - if (!trans) { - dprintk(DEBUG_ERROR, "no transport\n"); - return -EIO; - } - - ts = trans->priv; - if (trans->status != Connected || !ts) { - dprintk(DEBUG_ERROR, "transport disconnected: %d\n", trans->status); - return -EIO; - } - - oldfs = get_fs(); - set_fs(get_ds()); - - if (!ts->filp->f_op || !ts->filp->f_op->poll) { - dprintk(DEBUG_ERROR, "no poll operation\n"); - ret = -EIO; - goto end; - } - - ret = ts->filp->f_op->poll(ts->filp, pt); - -end: - set_fs(oldfs); - return ret; -} - - -/** - * v9fs_tcp_init - initialize TCP socket - * @v9ses: session information - * @addr: address of server to mount - * @data: mount options - * - */ - -static int -v9fs_tcp_init(struct v9fs_session_info *v9ses, const char *addr, char *data) -{ - struct socket *csocket = NULL; - struct sockaddr_in sin_server; - int rc = 0; - struct v9fs_trans_sock *ts = NULL; - struct v9fs_transport *trans = v9ses->transport; - int fd; - - trans->status = Disconnected; - - ts = kmalloc(sizeof(struct v9fs_trans_sock), GFP_KERNEL); - - if (!ts) - return -ENOMEM; - - trans->priv = ts; - ts->s = NULL; - ts->filp = NULL; - - if (!addr) - return -EINVAL; - - dprintk(DEBUG_TRANS, "Connecting to %s\n", addr); - - sin_server.sin_family = AF_INET; - sin_server.sin_addr.s_addr = in_aton(addr); - sin_server.sin_port = htons(v9ses->port); - sock_create_kern(PF_INET, SOCK_STREAM, IPPROTO_TCP, &csocket); - rc = csocket->ops->connect(csocket, - (struct sockaddr *)&sin_server, - sizeof(struct sockaddr_in), 0); - if (rc < 0) { - eprintk(KERN_ERR, - "v9fs_trans_tcp: problem connecting socket to %s\n", - addr); - return rc; - } - csocket->sk->sk_allocation = GFP_NOIO; - - fd = sock_map_fd(csocket); - if (fd < 0) { - sock_release(csocket); - kfree(ts); - trans->priv = NULL; - return fd; - } - - ts->s = csocket; - ts->filp = fget(fd); - ts->filp->f_flags |= O_NONBLOCK; - trans->status = Connected; - - return 0; -} - -/** - * v9fs_unix_init - initialize UNIX domain socket - * @v9ses: session information - * @dev_name: path to named pipe - * @data: mount options - * - */ - -static int -v9fs_unix_init(struct v9fs_session_info *v9ses, const char *dev_name, - char *data) -{ - int rc, fd; - struct socket *csocket; - struct sockaddr_un sun_server; - struct v9fs_transport *trans; - struct v9fs_trans_sock *ts; - - rc = 0; - csocket = NULL; - trans = v9ses->transport; - - trans->status = Disconnected; - - if (strlen(dev_name) > UNIX_PATH_MAX) { - eprintk(KERN_ERR, "v9fs_trans_unix: address too long: %s\n", - dev_name); - return -ENOMEM; - } - - ts = kmalloc(sizeof(struct v9fs_trans_sock), GFP_KERNEL); - if (!ts) - return -ENOMEM; - - trans->priv = ts; - ts->s = NULL; - ts->filp = NULL; - - sun_server.sun_family = PF_UNIX; - strcpy(sun_server.sun_path, dev_name); - sock_create_kern(PF_UNIX, SOCK_STREAM, 0, &csocket); - rc = csocket->ops->connect(csocket, (struct sockaddr *)&sun_server, - sizeof(struct sockaddr_un) - 1, 0); /* -1 *is* important */ - if (rc < 0) { - eprintk(KERN_ERR, - "v9fs_trans_unix: problem connecting socket: %s: %d\n", - dev_name, rc); - return rc; - } - csocket->sk->sk_allocation = GFP_NOIO; - - fd = sock_map_fd(csocket); - if (fd < 0) { - sock_release(csocket); - kfree(ts); - trans->priv = NULL; - return fd; - } - - ts->s = csocket; - ts->filp = fget(fd); - ts->filp->f_flags |= O_NONBLOCK; - trans->status = Connected; - - return 0; -} - -/** - * v9fs_sock_close - shutdown socket - * @trans: private socket structure - * - */ - -static void v9fs_sock_close(struct v9fs_transport *trans) -{ - struct v9fs_trans_sock *ts; - - if (!trans) - return; - - ts = trans->priv; - - if ((ts) && (ts->filp)) { - fput(ts->filp); - ts->filp = NULL; - ts->s = NULL; - trans->status = Disconnected; - } - - kfree(ts); - - trans->priv = NULL; -} - -struct v9fs_transport v9fs_trans_tcp = { - .init = v9fs_tcp_init, - .write = v9fs_sock_send, - .read = v9fs_sock_recv, - .close = v9fs_sock_close, - .poll = v9fs_sock_poll, -}; - -struct v9fs_transport v9fs_trans_unix = { - .init = v9fs_unix_init, - .write = v9fs_sock_send, - .read = v9fs_sock_recv, - .close = v9fs_sock_close, - .poll = v9fs_sock_poll, -}; diff --git a/fs/9p/transport.h b/fs/9p/transport.h deleted file mode 100644 index 91fcdb94b36..00000000000 --- a/fs/9p/transport.h +++ /dev/null @@ -1,46 +0,0 @@ -/* - * linux/fs/9p/transport.h - * - * Transport Definition - * - * Copyright (C) 2005 by Latchesar Ionkov <lucho@ionkov.net> - * Copyright (C) 2004 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 as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to: - * Free Software Foundation - * 51 Franklin Street, Fifth Floor - * Boston, MA 02111-1301 USA - * - */ - -enum v9fs_transport_status { - Connected, - Disconnected, - Hung, -}; - -struct v9fs_transport { - enum v9fs_transport_status status; - void *priv; - - int (*init) (struct v9fs_session_info *, const char *, char *); - int (*write) (struct v9fs_transport *, void *, int); - int (*read) (struct v9fs_transport *, void *, int); - void (*close) (struct v9fs_transport *); - unsigned int (*poll)(struct v9fs_transport *, struct poll_table_struct *); -}; - -extern struct v9fs_transport v9fs_trans_tcp; -extern struct v9fs_transport v9fs_trans_unix; -extern struct v9fs_transport v9fs_trans_fd; diff --git a/fs/9p/v9fs.c b/fs/9p/v9fs.c index 5250c428fc1..6894b085f0e 100644 --- a/fs/9p/v9fs.c +++ b/fs/9p/v9fs.c @@ -3,13 +3,12 @@ * * This file contains functions assisting in mapping VFS to 9P2000 * - * Copyright (C) 2004 by Eric Van Hensbergen <ericvh@gmail.com> + * 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 as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. + * 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 @@ -24,462 +23,663 @@ * */ -#include <linux/config.h> +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + #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 "debug.h" +#include <linux/slab.h> +#include <net/9p/9p.h> +#include <net/9p/client.h> +#include <net/9p/transport.h> #include "v9fs.h" -#include "9p.h" #include "v9fs_vfs.h" -#include "transport.h" -#include "mux.h" +#include "cache.h" -/* TODO: sysfs or debugfs interface */ -int v9fs_debug_level = 0; /* feature-rific global debug level */ +static DEFINE_SPINLOCK(v9fs_sessionlist_lock); +static LIST_HEAD(v9fs_sessionlist); +struct kmem_cache *v9fs_inode_cache; /* - * Option Parsing (code inspired by NFS code) - * - */ + * Option Parsing (code inspired by NFS code) + * NOTE: each transport will parse its own options + */ enum { /* Options that take integer arguments */ - Opt_port, Opt_msize, Opt_uid, Opt_gid, Opt_afid, Opt_debug, - Opt_rfdno, Opt_wfdno, + Opt_debug, Opt_dfltuid, Opt_dfltgid, Opt_afid, /* String options */ - Opt_name, Opt_remotename, + Opt_uname, Opt_remotename, Opt_trans, Opt_cache, Opt_cachetag, /* Options that take no arguments */ - Opt_legacy, Opt_nodevmap, Opt_unix, Opt_tcp, Opt_fd, + Opt_nodevmap, + /* Cache options */ + Opt_cache_loose, Opt_fscache, Opt_mmap, + /* Access options */ + Opt_access, Opt_posixacl, /* Error token */ Opt_err }; -static match_table_t tokens = { - {Opt_port, "port=%u"}, - {Opt_msize, "msize=%u"}, - {Opt_uid, "uid=%u"}, - {Opt_gid, "gid=%u"}, +static const match_table_t tokens = { + {Opt_debug, "debug=%x"}, + {Opt_dfltuid, "dfltuid=%u"}, + {Opt_dfltgid, "dfltgid=%u"}, {Opt_afid, "afid=%u"}, - {Opt_rfdno, "rfdno=%u"}, - {Opt_wfdno, "wfdno=%u"}, - {Opt_debug, "debug=%u"}, - {Opt_name, "name=%s"}, + {Opt_uname, "uname=%s"}, {Opt_remotename, "aname=%s"}, - {Opt_unix, "proto=unix"}, - {Opt_tcp, "proto=tcp"}, - {Opt_fd, "proto=fd"}, - {Opt_tcp, "tcp"}, - {Opt_unix, "unix"}, - {Opt_fd, "fd"}, - {Opt_legacy, "noextend"}, {Opt_nodevmap, "nodevmap"}, + {Opt_cache, "cache=%s"}, + {Opt_cache_loose, "loose"}, + {Opt_fscache, "fscache"}, + {Opt_mmap, "mmap"}, + {Opt_cachetag, "cachetag=%s"}, + {Opt_access, "access=%s"}, + {Opt_posixacl, "posixacl"}, {Opt_err, NULL} }; -/* - * Parse option string. - */ +/* Interpret mount options for cache mode */ +static int get_cache_mode(char *s) +{ + int version = -EINVAL; + + if (!strcmp(s, "loose")) { + version = CACHE_LOOSE; + p9_debug(P9_DEBUG_9P, "Cache mode: loose\n"); + } else if (!strcmp(s, "fscache")) { + version = CACHE_FSCACHE; + p9_debug(P9_DEBUG_9P, "Cache mode: fscache\n"); + } else if (!strcmp(s, "mmap")) { + version = CACHE_MMAP; + p9_debug(P9_DEBUG_9P, "Cache mode: mmap\n"); + } else if (!strcmp(s, "none")) { + version = CACHE_NONE; + p9_debug(P9_DEBUG_9P, "Cache mode: none\n"); + } else + pr_info("Unknown Cache mode %s\n", s); + return version; +} /** * v9fs_parse_options - parse mount options into session structure - * @options: options string passed from mount * @v9ses: existing v9fs session information * + * Return 0 upon success, -ERRNO upon failure. */ -static void v9fs_parse_options(char *options, struct v9fs_session_info *v9ses) +static int v9fs_parse_options(struct v9fs_session_info *v9ses, char *opts) { - char *p; + char *options, *tmp_options; substring_t args[MAX_OPT_ARGS]; - int option; - int ret; + char *p; + int option = 0; + char *s, *e; + int ret = 0; /* setup defaults */ - v9ses->port = V9FS_PORT; - v9ses->maxdata = 9000; - v9ses->proto = PROTO_TCP; - v9ses->extended = 1; v9ses->afid = ~0; v9ses->debug = 0; - v9ses->rfdno = ~0; - v9ses->wfdno = ~0; + v9ses->cache = CACHE_NONE; +#ifdef CONFIG_9P_FSCACHE + v9ses->cachetag = NULL; +#endif - if (!options) - return; + if (!opts) + return 0; + + tmp_options = kstrdup(opts, GFP_KERNEL); + if (!tmp_options) { + ret = -ENOMEM; + goto fail_option_alloc; + } + options = tmp_options; while ((p = strsep(&options, ",")) != NULL) { - int token; + int token, r; if (!*p) continue; token = match_token(p, tokens, args); - if (token < Opt_name) { - if ((ret = match_int(&args[0], &option)) < 0) { - dprintk(DEBUG_ERROR, - "integer field, but no integer?\n"); + switch (token) { + case Opt_debug: + r = match_int(&args[0], &option); + if (r < 0) { + p9_debug(P9_DEBUG_ERROR, + "integer field, but no integer?\n"); + ret = r; continue; } - - } - switch (token) { - case Opt_port: - v9ses->port = option; - break; - case Opt_msize: - v9ses->maxdata = option; + v9ses->debug = option; +#ifdef CONFIG_NET_9P_DEBUG + p9_debug_level = option; +#endif break; - case Opt_uid: - v9ses->uid = option; + + case Opt_dfltuid: + r = match_int(&args[0], &option); + if (r < 0) { + p9_debug(P9_DEBUG_ERROR, + "integer field, but no integer?\n"); + ret = r; + continue; + } + v9ses->dfltuid = make_kuid(current_user_ns(), option); + if (!uid_valid(v9ses->dfltuid)) { + p9_debug(P9_DEBUG_ERROR, + "uid field, but not a uid?\n"); + ret = -EINVAL; + continue; + } break; - case Opt_gid: - v9ses->gid = option; + case Opt_dfltgid: + r = match_int(&args[0], &option); + if (r < 0) { + p9_debug(P9_DEBUG_ERROR, + "integer field, but no integer?\n"); + ret = r; + continue; + } + v9ses->dfltgid = make_kgid(current_user_ns(), option); + if (!gid_valid(v9ses->dfltgid)) { + p9_debug(P9_DEBUG_ERROR, + "gid field, but not a gid?\n"); + ret = -EINVAL; + continue; + } break; case Opt_afid: + r = match_int(&args[0], &option); + if (r < 0) { + p9_debug(P9_DEBUG_ERROR, + "integer field, but no integer?\n"); + ret = r; + continue; + } v9ses->afid = option; break; - case Opt_rfdno: - v9ses->rfdno = option; + case Opt_uname: + kfree(v9ses->uname); + v9ses->uname = match_strdup(&args[0]); + if (!v9ses->uname) { + ret = -ENOMEM; + goto free_and_return; + } break; - case Opt_wfdno: - v9ses->wfdno = option; + case Opt_remotename: + kfree(v9ses->aname); + v9ses->aname = match_strdup(&args[0]); + if (!v9ses->aname) { + ret = -ENOMEM; + goto free_and_return; + } break; - case Opt_debug: - v9ses->debug = option; + case Opt_nodevmap: + v9ses->nodev = 1; break; - case Opt_tcp: - v9ses->proto = PROTO_TCP; + case Opt_cache_loose: + v9ses->cache = CACHE_LOOSE; break; - case Opt_unix: - v9ses->proto = PROTO_UNIX; + case Opt_fscache: + v9ses->cache = CACHE_FSCACHE; break; - case Opt_fd: - v9ses->proto = PROTO_FD; + case Opt_mmap: + v9ses->cache = CACHE_MMAP; break; - case Opt_name: - match_strcpy(v9ses->name, &args[0]); + case Opt_cachetag: +#ifdef CONFIG_9P_FSCACHE + v9ses->cachetag = match_strdup(&args[0]); +#endif break; - case Opt_remotename: - match_strcpy(v9ses->remotename, &args[0]); + case Opt_cache: + s = match_strdup(&args[0]); + if (!s) { + ret = -ENOMEM; + p9_debug(P9_DEBUG_ERROR, + "problem allocating copy of cache arg\n"); + goto free_and_return; + } + ret = get_cache_mode(s); + if (ret == -EINVAL) { + kfree(s); + goto free_and_return; + } + + v9ses->cache = ret; + kfree(s); break; - case Opt_legacy: - v9ses->extended = 0; + + case Opt_access: + s = match_strdup(&args[0]); + if (!s) { + ret = -ENOMEM; + p9_debug(P9_DEBUG_ERROR, + "problem allocating copy of access arg\n"); + goto free_and_return; + } + + 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 if (strcmp(s, "client") == 0) { + v9ses->flags |= V9FS_ACCESS_CLIENT; + } else { + uid_t uid; + v9ses->flags |= V9FS_ACCESS_SINGLE; + uid = simple_strtoul(s, &e, 10); + if (*e != '\0') { + ret = -EINVAL; + pr_info("Unknown access argument %s\n", + s); + kfree(s); + goto free_and_return; + } + v9ses->uid = make_kuid(current_user_ns(), uid); + if (!uid_valid(v9ses->uid)) { + ret = -EINVAL; + pr_info("Uknown uid %s\n", s); + kfree(s); + goto free_and_return; + } + } + + kfree(s); break; - case Opt_nodevmap: - v9ses->nodev = 1; + + case Opt_posixacl: +#ifdef CONFIG_9P_FS_POSIX_ACL + v9ses->flags |= V9FS_POSIX_ACL; +#else + p9_debug(P9_DEBUG_ERROR, + "Not defined CONFIG_9P_FS_POSIX_ACL. Ignoring posixacl option\n"); +#endif break; + default: continue; } } + +free_and_return: + kfree(tmp_options); +fail_option_alloc: + return ret; } /** - * v9fs_inode2v9ses - safely extract v9fs session info from super block - * @inode: inode to extract information from - * - * Paranoid function to extract v9ses information from superblock, - * if anything is missing it will report an error. + * v9fs_session_init - initialize session + * @v9ses: session information structure + * @dev_name: device being mounted + * @data: options * */ -struct v9fs_session_info *v9fs_inode2v9ses(struct inode *inode) +struct p9_fid *v9fs_session_init(struct v9fs_session_info *v9ses, + const char *dev_name, char *data) { - return (inode->i_sb->s_fs_info); -} + int retval = -EINVAL; + struct p9_fid *fid; + int rc; -/** - * v9fs_get_idpool - allocate numeric id from pool - * @p - pool to allocate from - * - * XXX - This seems to be an awful generic function, should it be in idr.c with - * the lock included in struct idr? - */ + v9ses->uname = kstrdup(V9FS_DEFUSER, GFP_KERNEL); + if (!v9ses->uname) + return ERR_PTR(-ENOMEM); -int v9fs_get_idpool(struct v9fs_idpool *p) -{ - int i = 0; - int error; + v9ses->aname = kstrdup(V9FS_DEFANAME, GFP_KERNEL); + if (!v9ses->aname) { + kfree(v9ses->uname); + return ERR_PTR(-ENOMEM); + } + init_rwsem(&v9ses->rename_sem); -retry: - if (idr_pre_get(&p->pool, GFP_KERNEL) == 0) - return 0; + rc = bdi_setup_and_register(&v9ses->bdi, "9p", BDI_CAP_MAP_COPY); + if (rc) { + kfree(v9ses->aname); + kfree(v9ses->uname); + return ERR_PTR(rc); + } + + spin_lock(&v9fs_sessionlist_lock); + list_add(&v9ses->slist, &v9fs_sessionlist); + spin_unlock(&v9fs_sessionlist_lock); + + v9ses->uid = INVALID_UID; + v9ses->dfltuid = V9FS_DEFUID; + v9ses->dfltgid = V9FS_DEFGID; + + v9ses->clnt = p9_client_create(dev_name, data); + if (IS_ERR(v9ses->clnt)) { + retval = PTR_ERR(v9ses->clnt); + v9ses->clnt = NULL; + p9_debug(P9_DEBUG_ERROR, "problem initializing 9p client\n"); + goto error; + } + + v9ses->flags = V9FS_ACCESS_USER; + + if (p9_is_proto_dotl(v9ses->clnt)) { + v9ses->flags = V9FS_ACCESS_CLIENT; + v9ses->flags |= V9FS_PROTO_2000L; + } else if (p9_is_proto_dotu(v9ses->clnt)) { + v9ses->flags |= V9FS_PROTO_2000U; + } + + rc = v9fs_parse_options(v9ses, data); + if (rc < 0) { + retval = rc; + goto error; + } + + v9ses->maxdata = v9ses->clnt->msize - P9_IOHDRSZ; + + if (!v9fs_proto_dotl(v9ses) && + ((v9ses->flags & V9FS_ACCESS_MASK) == V9FS_ACCESS_CLIENT)) { + /* + * We support ACCESS_CLIENT only for dotl. + * Fall back to ACCESS_USER + */ + v9ses->flags &= ~V9FS_ACCESS_MASK; + v9ses->flags |= V9FS_ACCESS_USER; + } + /*FIXME !! */ + /* for legacy mode, fall back to V9FS_ACCESS_ANY */ + if (!(v9fs_proto_dotu(v9ses) || v9fs_proto_dotl(v9ses)) && + ((v9ses->flags&V9FS_ACCESS_MASK) == V9FS_ACCESS_USER)) { + + v9ses->flags &= ~V9FS_ACCESS_MASK; + v9ses->flags |= V9FS_ACCESS_ANY; + v9ses->uid = INVALID_UID; + } + if (!v9fs_proto_dotl(v9ses) || + !((v9ses->flags & V9FS_ACCESS_MASK) == V9FS_ACCESS_CLIENT)) { + /* + * We support ACL checks on clinet only if the protocol is + * 9P2000.L and access is V9FS_ACCESS_CLIENT. + */ + v9ses->flags &= ~V9FS_ACL_MASK; + } - if (down_interruptible(&p->lock) == -EINTR) { - eprintk(KERN_WARNING, "Interrupted while locking\n"); - return -1; + fid = p9_client_attach(v9ses->clnt, NULL, v9ses->uname, INVALID_UID, + v9ses->aname); + if (IS_ERR(fid)) { + retval = PTR_ERR(fid); + fid = NULL; + p9_debug(P9_DEBUG_ERROR, "cannot attach\n"); + goto error; } - /* no need to store exactly p, we just need something non-null */ - error = idr_get_new(&p->pool, p, &i); - up(&p->lock); + if ((v9ses->flags & V9FS_ACCESS_MASK) == V9FS_ACCESS_SINGLE) + fid->uid = v9ses->uid; + else + fid->uid = INVALID_UID; - if (error == -EAGAIN) - goto retry; - else if (error) - return -1; +#ifdef CONFIG_9P_FSCACHE + /* register the session for caching */ + v9fs_cache_session_get_cookie(v9ses); +#endif - return i; + return fid; + +error: + bdi_destroy(&v9ses->bdi); + return ERR_PTR(retval); } /** - * v9fs_put_idpool - release numeric id from pool - * @p - pool to allocate from + * v9fs_session_close - shutdown a session + * @v9ses: session information structure * - * XXX - This seems to be an awful generic function, should it be in idr.c with - * the lock included in struct idr? */ -void v9fs_put_idpool(int id, struct v9fs_idpool *p) +void v9fs_session_close(struct v9fs_session_info *v9ses) { - if (down_interruptible(&p->lock) == -EINTR) { - eprintk(KERN_WARNING, "Interrupted while locking\n"); - return; + if (v9ses->clnt) { + p9_client_destroy(v9ses->clnt); + v9ses->clnt = NULL; + } + +#ifdef CONFIG_9P_FSCACHE + if (v9ses->fscache) { + v9fs_cache_session_put_cookie(v9ses); + kfree(v9ses->cachetag); } - idr_remove(&p->pool, id); - up(&p->lock); +#endif + kfree(v9ses->uname); + kfree(v9ses->aname); + + bdi_destroy(&v9ses->bdi); + + spin_lock(&v9fs_sessionlist_lock); + list_del(&v9ses->slist); + spin_unlock(&v9fs_sessionlist_lock); } /** - * v9fs_check_idpool - check if the specified id is available - * @id - id to check - * @p - pool + * v9fs_session_cancel - terminate a session + * @v9ses: session to terminate + * + * mark transport as disconnected and cancel all pending requests. */ -int v9fs_check_idpool(int id, struct v9fs_idpool *p) -{ - return idr_find(&p->pool, id) != NULL; + +void v9fs_session_cancel(struct v9fs_session_info *v9ses) { + p9_debug(P9_DEBUG_ERROR, "cancel session %p\n", v9ses); + p9_client_disconnect(v9ses->clnt); } /** - * v9fs_session_init - initialize session - * @v9ses: session information structure - * @dev_name: device being mounted - * @data: options + * v9fs_session_begin_cancel - Begin terminate of a session + * @v9ses: session to terminate * + * After this call we don't allow any request other than clunk. */ -int -v9fs_session_init(struct v9fs_session_info *v9ses, - const char *dev_name, char *data) +void v9fs_session_begin_cancel(struct v9fs_session_info *v9ses) { - struct v9fs_fcall *fcall = NULL; - struct v9fs_transport *trans_proto; - int n = 0; - int newfid = -1; - int retval = -EINVAL; - struct v9fs_str *version; - - v9ses->name = __getname(); - if (!v9ses->name) - return -ENOMEM; - - v9ses->remotename = __getname(); - if (!v9ses->remotename) { - __putname(v9ses->name); - return -ENOMEM; - } - - strcpy(v9ses->name, V9FS_DEFUSER); - strcpy(v9ses->remotename, V9FS_DEFANAME); - - v9fs_parse_options(data, v9ses); - - /* set global debug level */ - v9fs_debug_level = v9ses->debug; - - /* id pools that are session-dependent: FIDs and TIDs */ - idr_init(&v9ses->fidpool.pool); - init_MUTEX(&v9ses->fidpool.lock); - - switch (v9ses->proto) { - case PROTO_TCP: - trans_proto = &v9fs_trans_tcp; - break; - case PROTO_UNIX: - trans_proto = &v9fs_trans_unix; - *v9ses->remotename = 0; - break; - case PROTO_FD: - trans_proto = &v9fs_trans_fd; - *v9ses->remotename = 0; - break; - default: - printk(KERN_ERR "v9fs: Bad mount protocol %d\n", v9ses->proto); - retval = -ENOPROTOOPT; - goto SessCleanUp; - }; - - v9ses->transport = kmalloc(sizeof(*v9ses->transport), GFP_KERNEL); - if (!v9ses->transport) { - retval = -ENOMEM; - goto SessCleanUp; - } + p9_debug(P9_DEBUG_ERROR, "begin cancel session %p\n", v9ses); + p9_client_begin_disconnect(v9ses->clnt); +} - memmove(v9ses->transport, trans_proto, sizeof(*v9ses->transport)); +extern int v9fs_error_init(void); - if ((retval = v9ses->transport->init(v9ses, dev_name, data)) < 0) { - eprintk(KERN_ERR, "problem initializing transport\n"); - goto SessCleanUp; - } +static struct kobject *v9fs_kobj; - v9ses->inprogress = 0; - v9ses->shutdown = 0; - v9ses->session_hung = 0; +#ifdef CONFIG_9P_FSCACHE +/** + * caches_show - list caches associated with a session + * + * Returns the size of buffer written. + */ - v9ses->mux = v9fs_mux_init(v9ses->transport, v9ses->maxdata + V9FS_IOHDRSZ, - &v9ses->extended); +static ssize_t caches_show(struct kobject *kobj, + struct kobj_attribute *attr, + char *buf) +{ + ssize_t n = 0, count = 0, limit = PAGE_SIZE; + struct v9fs_session_info *v9ses; + + spin_lock(&v9fs_sessionlist_lock); + list_for_each_entry(v9ses, &v9fs_sessionlist, slist) { + if (v9ses->cachetag) { + n = snprintf(buf, limit, "%s\n", v9ses->cachetag); + if (n < 0) { + count = n; + break; + } - if (IS_ERR(v9ses->mux)) { - retval = PTR_ERR(v9ses->mux); - v9ses->mux = NULL; - dprintk(DEBUG_ERROR, "problem initializing mux\n"); - goto SessCleanUp; + count += n; + limit -= n; + } } - if (v9ses->afid == ~0) { - if (v9ses->extended) - retval = - v9fs_t_version(v9ses, v9ses->maxdata, "9P2000.u", - &fcall); - else - retval = v9fs_t_version(v9ses, v9ses->maxdata, "9P2000", - &fcall); - - if (retval < 0) { - dprintk(DEBUG_ERROR, "v9fs_t_version failed\n"); - goto FreeFcall; - } + spin_unlock(&v9fs_sessionlist_lock); + return count; +} - version = &fcall->params.rversion.version; - if (version->len==8 && !memcmp(version->str, "9P2000.u", 8)) { - dprintk(DEBUG_9P, "9P2000 UNIX extensions enabled\n"); - v9ses->extended = 1; - } else if (version->len==6 && !memcmp(version->str, "9P2000", 6)) { - dprintk(DEBUG_9P, "9P2000 legacy mode enabled\n"); - v9ses->extended = 0; - } else { - retval = -EREMOTEIO; - goto FreeFcall; - } +static struct kobj_attribute v9fs_attr_cache = __ATTR_RO(caches); +#endif /* CONFIG_9P_FSCACHE */ - n = fcall->params.rversion.msize; - kfree(fcall); +static struct attribute *v9fs_attrs[] = { +#ifdef CONFIG_9P_FSCACHE + &v9fs_attr_cache.attr, +#endif + NULL, +}; - if (n < v9ses->maxdata) - v9ses->maxdata = n; - } +static struct attribute_group v9fs_attr_group = { + .attrs = v9fs_attrs, +}; - newfid = v9fs_get_idpool(&v9ses->fidpool); - if (newfid < 0) { - eprintk(KERN_WARNING, "couldn't allocate FID\n"); - retval = -ENOMEM; - goto SessCleanUp; - } - /* it is a little bit ugly, but we have to prevent newfid */ - /* being the same as afid, so if it is, get a new fid */ - if (v9ses->afid != ~0 && newfid == v9ses->afid) { - newfid = v9fs_get_idpool(&v9ses->fidpool); - if (newfid < 0) { - eprintk(KERN_WARNING, "couldn't allocate FID\n"); - retval = -ENOMEM; - goto SessCleanUp; - } - } +/** + * v9fs_sysfs_init - Initialize the v9fs sysfs interface + * + */ - if ((retval = - v9fs_t_attach(v9ses, v9ses->name, v9ses->remotename, newfid, - v9ses->afid, NULL)) - < 0) { - dprintk(DEBUG_ERROR, "cannot attach\n"); - goto SessCleanUp; - } +static int __init v9fs_sysfs_init(void) +{ + v9fs_kobj = kobject_create_and_add("9p", fs_kobj); + if (!v9fs_kobj) + return -ENOMEM; - if (v9ses->afid != ~0) { - if (v9fs_t_clunk(v9ses, v9ses->afid)) - dprintk(DEBUG_ERROR, "clunk failed\n"); + if (sysfs_create_group(v9fs_kobj, &v9fs_attr_group)) { + kobject_put(v9fs_kobj); + return -ENOMEM; } - return newfid; - - FreeFcall: - kfree(fcall); - - SessCleanUp: - v9fs_session_close(v9ses); - return retval; + return 0; } /** - * v9fs_session_close - shutdown a session - * @v9ses: session information structure + * v9fs_sysfs_cleanup - Unregister the v9fs sysfs interface * */ -void v9fs_session_close(struct v9fs_session_info *v9ses) +static void v9fs_sysfs_cleanup(void) { - if (v9ses->mux) { - v9fs_mux_destroy(v9ses->mux); - v9ses->mux = NULL; - } + sysfs_remove_group(v9fs_kobj, &v9fs_attr_group); + kobject_put(v9fs_kobj); +} - if (v9ses->transport) { - v9ses->transport->close(v9ses->transport); - kfree(v9ses->transport); - v9ses->transport = NULL; - } +static void v9fs_inode_init_once(void *foo) +{ + struct v9fs_inode *v9inode = (struct v9fs_inode *)foo; +#ifdef CONFIG_9P_FSCACHE + v9inode->fscache = NULL; +#endif + memset(&v9inode->qid, 0, sizeof(v9inode->qid)); + inode_init_once(&v9inode->vfs_inode); +} - __putname(v9ses->name); - __putname(v9ses->remotename); +/** + * v9fs_init_inode_cache - initialize a cache for 9P + * Returns 0 on success. + */ +static int v9fs_init_inode_cache(void) +{ + v9fs_inode_cache = kmem_cache_create("v9fs_inode_cache", + sizeof(struct v9fs_inode), + 0, (SLAB_RECLAIM_ACCOUNT| + SLAB_MEM_SPREAD), + v9fs_inode_init_once); + if (!v9fs_inode_cache) + return -ENOMEM; + + return 0; } /** - * v9fs_session_cancel - mark transport as disconnected - * and cancel all pending requests. + * v9fs_destroy_inode_cache - destroy the cache of 9P inode + * */ -void v9fs_session_cancel(struct v9fs_session_info *v9ses) { - dprintk(DEBUG_ERROR, "cancel session %p\n", v9ses); - v9ses->transport->status = Disconnected; - v9fs_mux_cancel(v9ses->mux, -EIO); +static void v9fs_destroy_inode_cache(void) +{ + /* + * Make sure all delayed rcu free inodes are flushed before we + * destroy cache. + */ + rcu_barrier(); + kmem_cache_destroy(v9fs_inode_cache); } -extern int v9fs_error_init(void); +static int v9fs_cache_register(void) +{ + int ret; + ret = v9fs_init_inode_cache(); + if (ret < 0) + return ret; +#ifdef CONFIG_9P_FSCACHE + ret = fscache_register_netfs(&v9fs_cache_netfs); + if (ret < 0) + v9fs_destroy_inode_cache(); +#endif + return ret; +} + +static void v9fs_cache_unregister(void) +{ + v9fs_destroy_inode_cache(); +#ifdef CONFIG_9P_FSCACHE + fscache_unregister_netfs(&v9fs_cache_netfs); +#endif +} /** - * v9fs_init - Initialize module + * init_v9fs - Initialize module * */ static int __init init_v9fs(void) { - int ret; + int err; + pr_info("Installing v9fs 9p2000 file system support\n"); + /* TODO: Setup list of registered trasnport modules */ + + err = v9fs_cache_register(); + if (err < 0) { + pr_err("Failed to register v9fs for caching\n"); + return err; + } - v9fs_error_init(); + err = v9fs_sysfs_init(); + if (err < 0) { + pr_err("Failed to register with sysfs\n"); + goto out_cache; + } + err = register_filesystem(&v9fs_fs_type); + if (err < 0) { + pr_err("Failed to register filesystem\n"); + goto out_sysfs_cleanup; + } - printk(KERN_INFO "Installing v9fs 9P2000 file system support\n"); + return 0; - ret = v9fs_mux_global_init(); - if (!ret) - ret = register_filesystem(&v9fs_fs_type); +out_sysfs_cleanup: + v9fs_sysfs_cleanup(); - return ret; +out_cache: + v9fs_cache_unregister(); + + return err; } /** - * v9fs_init - shutdown module + * exit_v9fs - shutdown module * */ static void __exit exit_v9fs(void) { - v9fs_mux_global_exit(); + v9fs_sysfs_cleanup(); + v9fs_cache_unregister(); 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 index f337da7a0ee..099c7712631 100644 --- a/fs/9p/v9fs.h +++ b/fs/9p/v9fs.h @@ -1,13 +1,12 @@ /* * V9FS definitions. * - * Copyright (C) 2004 by Eric Van Hensbergen <ericvh@gmail.com> + * 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 as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. + * 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 @@ -21,76 +20,209 @@ * Boston, MA 02111-1301 USA * */ +#ifndef FS_9P_V9FS_H +#define FS_9P_V9FS_H -/* - * Idpool structure provides lock and id management - * - */ +#include <linux/backing-dev.h> + +/** + * enum p9_session_flags - option flags for each 9P session + * @V9FS_PROTO_2000U: whether or not to use 9P2000.u extensions + * @V9FS_PROTO_2000L: whether or not to use 9P2000.l 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_CLIENT: Just like user, but access check is performed on client. + * @V9FS_ACCESS_ANY: use a single attach for all users + * @V9FS_ACCESS_MASK: bit mask of different ACCESS options + * @V9FS_POSIX_ACL: POSIX ACLs are enforced + * + * Session flags reflect options selected by users at mount time + */ +#define V9FS_ACCESS_ANY (V9FS_ACCESS_SINGLE | \ + V9FS_ACCESS_USER | \ + V9FS_ACCESS_CLIENT) +#define V9FS_ACCESS_MASK V9FS_ACCESS_ANY +#define V9FS_ACL_MASK V9FS_POSIX_ACL -struct v9fs_idpool { - struct semaphore lock; - struct idr pool; +enum p9_session_flags { + V9FS_PROTO_2000U = 0x01, + V9FS_PROTO_2000L = 0x02, + V9FS_ACCESS_SINGLE = 0x04, + V9FS_ACCESS_USER = 0x08, + V9FS_ACCESS_CLIENT = 0x10, + V9FS_POSIX_ACL = 0x20 }; -/* - * Session structure provides information for an opened session - * - */ +/* 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_MMAP, + CACHE_LOOSE, + CACHE_FSCACHE, +}; + +/** + * 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 + * @cachetag: the tag of the cache associated with this session + * @fscache: session cookie associated with FS-Cache + * @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 + * @slist: reference to list of registered 9p sessions + * + * 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 int maxdata; - unsigned char extended; /* set to 1 if we are using UNIX extensions */ - unsigned char nodev; /* set to 1 if no disable device mapping */ - unsigned short port; /* port to connect to */ - unsigned short debug; /* debug level */ - unsigned short proto; /* protocol to use */ - unsigned int afid; /* authentication fid */ - unsigned int rfdno; /* read file descriptor number */ - unsigned int wfdno; /* write file descriptor number */ - - - char *name; /* user name to mount as */ - char *remotename; /* name of remote hierarchy being mounted */ - unsigned int uid; /* default uid/muid for legacy support */ - unsigned int gid; /* default gid for legacy support */ - - /* book keeping */ - struct v9fs_idpool fidpool; /* The FID pool for file descriptors */ - - struct v9fs_transport *transport; - struct v9fs_mux_data *mux; - - int inprogress; /* session in progress => true */ - int shutdown; /* session shutting down. no more attaches. */ - unsigned char session_hung; - struct dentry *debugfs_dir; -}; + unsigned char flags; + unsigned char nodev; + unsigned short debug; + unsigned int afid; + unsigned int cache; +#ifdef CONFIG_9P_FSCACHE + char *cachetag; + struct fscache_cookie *fscache; +#endif -/* possible values of ->proto */ -enum { - PROTO_TCP, - PROTO_UNIX, - PROTO_FD, + char *uname; /* user name to mount as */ + char *aname; /* name of remote hierarchy being mounted */ + unsigned int maxdata; /* max data for client interface */ + kuid_t dfltuid; /* default uid/muid for legacy support */ + kgid_t dfltgid; /* default gid for legacy support */ + kuid_t uid; /* if ACCESS_SINGLE, the uid that has access */ + struct p9_client *clnt; /* 9p client */ + struct list_head slist; /* list of sessions registered with v9fs */ + struct backing_dev_info bdi; + struct rw_semaphore rename_sem; }; -extern struct dentry *v9fs_debugfs_root; +/* cache_validity flags */ +#define V9FS_INO_INVALID_ATTR 0x01 + +struct v9fs_inode { +#ifdef CONFIG_9P_FSCACHE + spinlock_t fscache_lock; + struct fscache_cookie *fscache; +#endif + struct p9_qid qid; + unsigned int cache_validity; + struct p9_fid *writeback_fid; + struct mutex v_mutex; + struct inode vfs_inode; +}; -int v9fs_session_init(struct v9fs_session_info *, const char *, char *); -struct v9fs_session_info *v9fs_inode2v9ses(struct inode *); -void v9fs_session_close(struct v9fs_session_info *v9ses); -int v9fs_get_idpool(struct v9fs_idpool *p); -void v9fs_put_idpool(int id, struct v9fs_idpool *p); -int v9fs_check_idpool(int id, struct v9fs_idpool *p); -void v9fs_session_cancel(struct v9fs_session_info *v9ses); +static inline struct v9fs_inode *V9FS_I(const struct inode *inode) +{ + return container_of(inode, struct v9fs_inode, vfs_inode); +} -#define V9FS_MAGIC 0x01021997 +struct p9_fid *v9fs_session_init(struct v9fs_session_info *, const char *, + char *); +extern void v9fs_session_close(struct v9fs_session_info *v9ses); +extern void v9fs_session_cancel(struct v9fs_session_info *v9ses); +extern void v9fs_session_begin_cancel(struct v9fs_session_info *v9ses); +extern struct dentry *v9fs_vfs_lookup(struct inode *dir, struct dentry *dentry, + unsigned int flags); +extern int v9fs_vfs_unlink(struct inode *i, struct dentry *d); +extern int v9fs_vfs_rmdir(struct inode *i, struct dentry *d); +extern int v9fs_vfs_rename(struct inode *old_dir, struct dentry *old_dentry, + struct inode *new_dir, struct dentry *new_dentry); +extern void v9fs_vfs_put_link(struct dentry *dentry, struct nameidata *nd, + void *p); +extern struct inode *v9fs_inode_from_fid(struct v9fs_session_info *v9ses, + struct p9_fid *fid, + struct super_block *sb, int new); +extern const struct inode_operations v9fs_dir_inode_operations_dotl; +extern const struct inode_operations v9fs_file_inode_operations_dotl; +extern const struct inode_operations v9fs_symlink_inode_operations_dotl; +extern struct inode *v9fs_inode_from_fid_dotl(struct v9fs_session_info *v9ses, + struct p9_fid *fid, + struct super_block *sb, int new); /* other default globals */ -#define V9FS_PORT 564 +#define V9FS_PORT 564 #define V9FS_DEFUSER "nobody" #define V9FS_DEFANAME "" +#define V9FS_DEFUID KUIDT_INIT(-2) +#define V9FS_DEFGID KGIDT_INIT(-2) + +static inline struct v9fs_session_info *v9fs_inode2v9ses(struct inode *inode) +{ + return (inode->i_sb->s_fs_info); +} + +static inline struct v9fs_session_info *v9fs_dentry2v9ses(struct dentry *dentry) +{ + return dentry->d_sb->s_fs_info; +} + +static inline int v9fs_proto_dotu(struct v9fs_session_info *v9ses) +{ + return v9ses->flags & V9FS_PROTO_2000U; +} + +static inline int v9fs_proto_dotl(struct v9fs_session_info *v9ses) +{ + return v9ses->flags & V9FS_PROTO_2000L; +} + +/** + * v9fs_get_inode_from_fid - Helper routine to 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 inline struct inode * +v9fs_get_inode_from_fid(struct v9fs_session_info *v9ses, struct p9_fid *fid, + struct super_block *sb) +{ + if (v9fs_proto_dotl(v9ses)) + return v9fs_inode_from_fid_dotl(v9ses, fid, sb, 0); + else + return v9fs_inode_from_fid(v9ses, fid, sb, 0); +} + +/** + * v9fs_get_new_inode_from_fid - Helper routine to 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 inline struct inode * +v9fs_get_new_inode_from_fid(struct v9fs_session_info *v9ses, struct p9_fid *fid, + struct super_block *sb) +{ + if (v9fs_proto_dotl(v9ses)) + return v9fs_inode_from_fid_dotl(v9ses, fid, sb, 1); + else + return v9fs_inode_from_fid(v9ses, fid, sb, 1); +} -/* inital pool sizes for fids and tags */ -#define V9FS_START_FIDS 8192 -#define V9FS_START_TIDS 256 +#endif diff --git a/fs/9p/v9fs_vfs.h b/fs/9p/v9fs_vfs.h index 69cf2905dc9..b83ebfbf3fd 100644 --- a/fs/9p/v9fs_vfs.h +++ b/fs/9p/v9fs_vfs.h @@ -5,9 +5,8 @@ * 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 as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. + * 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 @@ -21,6 +20,8 @@ * Boston, MA 02111-1301 USA * */ +#ifndef FS_9P_V9FS_VFS_H +#define FS_9P_V9FS_VFS_H /* plan9 semantics are that created files are implicitly opened. * But linux semantics are that you call create, then open. @@ -37,17 +38,53 @@ * 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. */ +#define P9_LOCK_TIMEOUT (30*HZ) extern struct file_system_type v9fs_fs_type; -extern struct address_space_operations v9fs_addr_operations; -extern struct file_operations v9fs_file_operations; -extern struct file_operations v9fs_dir_operations; -extern struct dentry_operations v9fs_dentry_operations; +extern const struct address_space_operations v9fs_addr_operations; +extern const struct file_operations v9fs_file_operations; +extern const struct file_operations v9fs_file_operations_dotl; +extern const struct file_operations v9fs_dir_operations; +extern const struct file_operations v9fs_dir_operations_dotl; +extern const struct dentry_operations v9fs_dentry_operations; +extern const struct dentry_operations v9fs_cached_dentry_operations; +extern const struct file_operations v9fs_cached_file_operations; +extern const struct file_operations v9fs_cached_file_operations_dotl; +extern const struct file_operations v9fs_mmap_file_operations; +extern const struct file_operations v9fs_mmap_file_operations_dotl; +extern struct kmem_cache *v9fs_inode_cache; -struct inode *v9fs_get_inode(struct super_block *sb, int mode); -ino_t v9fs_qid2ino(struct v9fs_qid *qid); -void v9fs_stat2inode(struct v9fs_stat *, struct inode *, struct super_block *); +struct inode *v9fs_alloc_inode(struct super_block *sb); +void v9fs_destroy_inode(struct inode *inode); +struct inode *v9fs_get_inode(struct super_block *sb, umode_t mode, dev_t); +int v9fs_init_inode(struct v9fs_session_info *v9ses, + struct inode *inode, umode_t mode, dev_t); +void v9fs_evict_inode(struct inode *inode); +ino_t v9fs_qid2ino(struct p9_qid *qid); +void v9fs_stat2inode(struct p9_wstat *, struct inode *, struct super_block *); +void v9fs_stat2inode_dotl(struct p9_stat_dotl *, struct inode *); 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 v9fs_stat *stat); -void v9fs_dentry_release(struct dentry *); +void v9fs_inode2stat(struct inode *inode, struct p9_wstat *stat); +int v9fs_uflags2omode(int uflags, int extended); + +ssize_t v9fs_file_readn(struct file *, char *, char __user *, u32, u64); +ssize_t v9fs_fid_readn(struct p9_fid *, char *, char __user *, u32, u64); +void v9fs_blank_wstat(struct p9_wstat *wstat); +int v9fs_vfs_setattr_dotl(struct dentry *, struct iattr *); +int v9fs_file_fsync_dotl(struct file *filp, loff_t start, loff_t end, + int datasync); +ssize_t v9fs_file_write_internal(struct inode *, struct p9_fid *, + const char __user *, size_t, loff_t *, int); +int v9fs_refresh_inode(struct p9_fid *fid, struct inode *inode); +int v9fs_refresh_inode_dotl(struct p9_fid *fid, struct inode *inode); +static inline void v9fs_invalidate_inode_attr(struct inode *inode) +{ + struct v9fs_inode *v9inode; + v9inode = V9FS_I(inode); + v9inode->cache_validity |= V9FS_INO_INVALID_ATTR; + return; +} + +int v9fs_open_to_dotl_flags(int flags); +#endif diff --git a/fs/9p/vfs_addr.c b/fs/9p/vfs_addr.c index 8100fb5171b..cc1cfae726b 100644 --- a/fs/9p/vfs_addr.c +++ b/fs/9p/vfs_addr.c @@ -7,9 +7,8 @@ * 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 as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. + * 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 @@ -30,80 +29,332 @@ #include <linux/file.h> #include <linux/stat.h> #include <linux/string.h> -#include <linux/smp_lock.h> #include <linux/inet.h> -#include <linux/version.h> #include <linux/pagemap.h> #include <linux/idr.h> +#include <linux/sched.h> +#include <linux/aio.h> +#include <net/9p/9p.h> +#include <net/9p/client.h> -#include "debug.h" #include "v9fs.h" -#include "9p.h" #include "v9fs_vfs.h" +#include "cache.h" #include "fid.h" /** + * v9fs_fid_readpage - read an entire page in from 9P + * + * @fid: fid being read + * @page: structure to page + * + */ +static int v9fs_fid_readpage(struct p9_fid *fid, struct page *page) +{ + int retval; + loff_t offset; + char *buffer; + struct inode *inode; + + inode = page->mapping->host; + p9_debug(P9_DEBUG_VFS, "\n"); + + BUG_ON(!PageLocked(page)); + + retval = v9fs_readpage_from_fscache(inode, page); + if (retval == 0) + return retval; + + buffer = kmap(page); + offset = page_offset(page); + + retval = v9fs_fid_readn(fid, buffer, NULL, PAGE_CACHE_SIZE, offset); + if (retval < 0) { + v9fs_uncache_page(inode, page); + goto done; + } + + memset(buffer + retval, 0, PAGE_CACHE_SIZE - retval); + flush_dcache_page(page); + SetPageUptodate(page); + + v9fs_readpage_to_fscache(inode, page); + retval = 0; + +done: + kunmap(page); + unlock_page(page); + return retval; +} + +/** * v9fs_vfs_readpage - read an entire page in from 9P * - * @file: file being read + * @filp: file being read * @page: structure to page * */ static int v9fs_vfs_readpage(struct file *filp, struct page *page) { - char *buffer = NULL; - int retval = -EIO; - loff_t offset = page_offset(page); - int count = PAGE_CACHE_SIZE; - struct inode *inode = filp->f_dentry->d_inode; - struct v9fs_session_info *v9ses = v9fs_inode2v9ses(inode); - int rsize = v9ses->maxdata - V9FS_IOHDRSZ; - struct v9fs_fid *v9f = filp->private_data; - struct v9fs_fcall *fcall = NULL; - int fid = v9f->fid; - int total = 0; - int result = 0; + return v9fs_fid_readpage(filp->private_data, page); +} + +/** + * v9fs_vfs_readpages - read a set of pages from 9P + * + * @filp: file being read + * @mapping: the address space + * @pages: list of pages to read + * @nr_pages: count of pages to read + * + */ + +static int v9fs_vfs_readpages(struct file *filp, struct address_space *mapping, + struct list_head *pages, unsigned nr_pages) +{ + int ret = 0; + struct inode *inode; - buffer = kmap(page); - do { - if (count < rsize) - rsize = count; + inode = mapping->host; + p9_debug(P9_DEBUG_VFS, "inode: %p file: %p\n", inode, filp); - result = v9fs_t_read(v9ses, fid, offset, rsize, &fcall); + ret = v9fs_readpages_from_fscache(inode, mapping, pages, &nr_pages); + if (ret == 0) + return ret; - if (result < 0) { - printk(KERN_ERR "v9fs_t_read returned %d\n", - result); + ret = read_cache_pages(mapping, pages, (void *)v9fs_vfs_readpage, filp); + p9_debug(P9_DEBUG_VFS, " = %d\n", ret); + return ret; +} - kfree(fcall); - goto UnmapAndUnlock; - } else - offset += result; +/** + * v9fs_release_page - release the private state associated with a page + * + * Returns 1 if the page can be released, false otherwise. + */ - memcpy(buffer, fcall->params.rread.data, result); +static int v9fs_release_page(struct page *page, gfp_t gfp) +{ + if (PagePrivate(page)) + return 0; + return v9fs_fscache_release_page(page, gfp); +} - count -= result; - buffer += result; - total += result; +/** + * v9fs_invalidate_page - Invalidate a page completely or partially + * + * @page: structure to page + * @offset: offset in the page + */ - kfree(fcall); +static void v9fs_invalidate_page(struct page *page, unsigned int offset, + unsigned int length) +{ + /* + * If called with zero offset, we should release + * the private state assocated with the page + */ + if (offset == 0 && length == PAGE_CACHE_SIZE) + v9fs_fscache_invalidate_page(page); +} - if (result < rsize) - break; - } while (count); +static int v9fs_vfs_writepage_locked(struct page *page) +{ + char *buffer; + int retval, len; + loff_t offset, size; + mm_segment_t old_fs; + struct v9fs_inode *v9inode; + struct inode *inode = page->mapping->host; - memset(buffer, 0, count); - flush_dcache_page(page); - SetPageUptodate(page); - retval = 0; + v9inode = V9FS_I(inode); + size = i_size_read(inode); + if (page->index == size >> PAGE_CACHE_SHIFT) + len = size & ~PAGE_CACHE_MASK; + else + len = PAGE_CACHE_SIZE; -UnmapAndUnlock: + set_page_writeback(page); + + buffer = kmap(page); + offset = page_offset(page); + + old_fs = get_fs(); + set_fs(get_ds()); + /* We should have writeback_fid always set */ + BUG_ON(!v9inode->writeback_fid); + + retval = v9fs_file_write_internal(inode, + v9inode->writeback_fid, + (__force const char __user *)buffer, + len, &offset, 0); + if (retval > 0) + retval = 0; + + set_fs(old_fs); kunmap(page); + end_page_writeback(page); + return retval; +} + +static int v9fs_vfs_writepage(struct page *page, struct writeback_control *wbc) +{ + int retval; + + p9_debug(P9_DEBUG_VFS, "page %p\n", page); + + retval = v9fs_vfs_writepage_locked(page); + if (retval < 0) { + if (retval == -EAGAIN) { + redirty_page_for_writepage(wbc, page); + retval = 0; + } else { + SetPageError(page); + mapping_set_error(page->mapping, retval); + } + } else + retval = 0; + unlock_page(page); return retval; } -struct address_space_operations v9fs_addr_operations = { - .readpage = v9fs_vfs_readpage, +/** + * v9fs_launder_page - Writeback a dirty page + * Returns 0 on success. + */ + +static int v9fs_launder_page(struct page *page) +{ + int retval; + struct inode *inode = page->mapping->host; + + v9fs_fscache_wait_on_page_write(inode, page); + if (clear_page_dirty_for_io(page)) { + retval = v9fs_vfs_writepage_locked(page); + if (retval) + return retval; + } + return 0; +} + +/** + * v9fs_direct_IO - 9P address space operation for direct I/O + * @rw: direction (read or write) + * @iocb: target I/O control block + * @iov: array of vectors that define I/O buffer + * @pos: offset in file to begin the operation + * @nr_segs: size of iovec array + * + * The presence of v9fs_direct_IO() in the address space ops vector + * allowes open() O_DIRECT flags which would have failed otherwise. + * + * In the non-cached mode, we shunt off direct read and write requests before + * the VFS gets them, so this method should never be called. + * + * Direct IO is not 'yet' supported in the cached mode. Hence when + * this routine is called through generic_file_aio_read(), the read/write fails + * with an error. + * + */ +static ssize_t +v9fs_direct_IO(int rw, struct kiocb *iocb, struct iov_iter *iter, loff_t pos) +{ + /* + * FIXME + * Now that we do caching with cache mode enabled, We need + * to support direct IO + */ + p9_debug(P9_DEBUG_VFS, "v9fs_direct_IO: v9fs_direct_IO (%s) off/no(%lld/%lu) EINVAL\n", + iocb->ki_filp->f_path.dentry->d_name.name, + (long long)pos, iter->nr_segs); + + return -EINVAL; +} + +static int v9fs_write_begin(struct file *filp, struct address_space *mapping, + loff_t pos, unsigned len, unsigned flags, + struct page **pagep, void **fsdata) +{ + int retval = 0; + struct page *page; + struct v9fs_inode *v9inode; + pgoff_t index = pos >> PAGE_CACHE_SHIFT; + struct inode *inode = mapping->host; + + + p9_debug(P9_DEBUG_VFS, "filp %p, mapping %p\n", filp, mapping); + + v9inode = V9FS_I(inode); +start: + page = grab_cache_page_write_begin(mapping, index, flags); + if (!page) { + retval = -ENOMEM; + goto out; + } + BUG_ON(!v9inode->writeback_fid); + if (PageUptodate(page)) + goto out; + + if (len == PAGE_CACHE_SIZE) + goto out; + + retval = v9fs_fid_readpage(v9inode->writeback_fid, page); + page_cache_release(page); + if (!retval) + goto start; +out: + *pagep = page; + return retval; +} + +static int v9fs_write_end(struct file *filp, struct address_space *mapping, + loff_t pos, unsigned len, unsigned copied, + struct page *page, void *fsdata) +{ + loff_t last_pos = pos + copied; + struct inode *inode = page->mapping->host; + + p9_debug(P9_DEBUG_VFS, "filp %p, mapping %p\n", filp, mapping); + + if (unlikely(copied < len)) { + /* + * zero out the rest of the area + */ + unsigned from = pos & (PAGE_CACHE_SIZE - 1); + + zero_user(page, from + copied, len - copied); + flush_dcache_page(page); + } + + if (!PageUptodate(page)) + SetPageUptodate(page); + /* + * No need to use i_size_read() here, the i_size + * cannot change under us because we hold the i_mutex. + */ + if (last_pos > inode->i_size) { + inode_add_bytes(inode, last_pos - inode->i_size); + i_size_write(inode, last_pos); + } + set_page_dirty(page); + unlock_page(page); + page_cache_release(page); + + return copied; +} + + +const struct address_space_operations v9fs_addr_operations = { + .readpage = v9fs_vfs_readpage, + .readpages = v9fs_vfs_readpages, + .set_page_dirty = __set_page_dirty_nobuffers, + .writepage = v9fs_vfs_writepage, + .write_begin = v9fs_write_begin, + .write_end = v9fs_write_end, + .releasepage = v9fs_release_page, + .invalidatepage = v9fs_invalidate_page, + .launder_page = v9fs_launder_page, + .direct_IO = v9fs_direct_IO, }; diff --git a/fs/9p/vfs_dentry.c b/fs/9p/vfs_dentry.c index 2dd806dac9f..b03dd23feda 100644 --- a/fs/9p/vfs_dentry.c +++ b/fs/9p/vfs_dentry.c @@ -7,9 +7,8 @@ * 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 as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. + * 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 @@ -31,58 +30,31 @@ #include <linux/pagemap.h> #include <linux/stat.h> #include <linux/string.h> -#include <linux/smp_lock.h> #include <linux/inet.h> #include <linux/namei.h> #include <linux/idr.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <net/9p/9p.h> +#include <net/9p/client.h> -#include "debug.h" #include "v9fs.h" -#include "9p.h" #include "v9fs_vfs.h" #include "fid.h" /** - * v9fs_dentry_validate - VFS dcache hook to validate cache - * @dentry: dentry that is being validated - * @nd: path data - * - * dcache really shouldn't be used for 9P2000 as at all due to - * potential attached semantics to directory traversal (walk). - * - * FUTURE: look into how to use dcache to allow multi-stage - * walks in Plan 9 & potential for better dcache operation which - * would remain valid for Plan 9 semantics. Older versions - * had validation via stat for those interested. However, since - * stat has the same approximate overhead as walk there really - * is no difference. The only improvement would be from a - * time-decay cache like NFS has and that undermines the - * synchronous nature of 9P2000. + * v9fs_cached_dentry_delete - called when dentry refcount equals 0 + * @dentry: dentry in question * */ - -static int v9fs_dentry_validate(struct dentry *dentry, struct nameidata *nd) +static int v9fs_cached_dentry_delete(const struct dentry *dentry) { - struct dentry *dc = current->fs->pwd; + p9_debug(P9_DEBUG_VFS, " dentry: %s (%p)\n", + dentry->d_name.name, dentry); - dprintk(DEBUG_VFS, "dentry: %s (%p)\n", dentry->d_iname, dentry); - if (v9fs_fid_lookup(dentry)) { - dprintk(DEBUG_VFS, "VALID\n"); + /* Don't cache negative dentries */ + if (!dentry->d_inode) return 1; - } - - while (dc != NULL) { - if (dc == dentry) { - dprintk(DEBUG_VFS, "VALID\n"); - return 1; - } - if (dc == dc->d_parent) - break; - - dc = dc->d_parent; - } - - dprintk(DEBUG_VFS, "INVALID\n"); return 0; } @@ -92,32 +64,59 @@ static int v9fs_dentry_validate(struct dentry *dentry, struct nameidata *nd) * */ -void v9fs_dentry_release(struct dentry *dentry) +static void v9fs_dentry_release(struct dentry *dentry) { - int err; - - dprintk(DEBUG_VFS, " dentry: %s (%p)\n", dentry->d_iname, dentry); - - if (dentry->d_fsdata != NULL) { - struct list_head *fid_list = dentry->d_fsdata; - struct v9fs_fid *temp = NULL; - struct v9fs_fid *current_fid = NULL; - - list_for_each_entry_safe(current_fid, temp, fid_list, list) { - err = v9fs_t_clunk(current_fid->v9ses, current_fid->fid); - - if (err < 0) - dprintk(DEBUG_ERROR, "clunk failed: %d name %s\n", - err, dentry->d_iname); - - v9fs_fid_destroy(current_fid); - } + struct hlist_node *p, *n; + p9_debug(P9_DEBUG_VFS, " dentry: %s (%p)\n", + dentry->d_name.name, dentry); + hlist_for_each_safe(p, n, (struct hlist_head *)&dentry->d_fsdata) + p9_client_clunk(hlist_entry(p, struct p9_fid, dlist)); + dentry->d_fsdata = NULL; +} - kfree(dentry->d_fsdata); /* free the list_head */ +static int v9fs_lookup_revalidate(struct dentry *dentry, unsigned int flags) +{ + struct p9_fid *fid; + struct inode *inode; + struct v9fs_inode *v9inode; + + if (flags & LOOKUP_RCU) + return -ECHILD; + + inode = dentry->d_inode; + if (!inode) + goto out_valid; + + v9inode = V9FS_I(inode); + if (v9inode->cache_validity & V9FS_INO_INVALID_ATTR) { + int retval; + struct v9fs_session_info *v9ses; + fid = v9fs_fid_lookup(dentry); + if (IS_ERR(fid)) + return PTR_ERR(fid); + + v9ses = v9fs_inode2v9ses(inode); + if (v9fs_proto_dotl(v9ses)) + retval = v9fs_refresh_inode_dotl(fid, inode); + else + retval = v9fs_refresh_inode(fid, inode); + if (retval == -ENOENT) + return 0; + if (retval < 0) + return retval; } +out_valid: + return 1; } -struct dentry_operations v9fs_dentry_operations = { - .d_revalidate = v9fs_dentry_validate, +const struct dentry_operations v9fs_cached_dentry_operations = { + .d_revalidate = v9fs_lookup_revalidate, + .d_weak_revalidate = v9fs_lookup_revalidate, + .d_delete = v9fs_cached_dentry_delete, + .d_release = v9fs_dentry_release, +}; + +const struct dentry_operations v9fs_dentry_operations = { + .d_delete = always_delete_dentry, .d_release = v9fs_dentry_release, }; diff --git a/fs/9p/vfs_dir.c b/fs/9p/vfs_dir.c index ae6d032b9b5..0b3bfa303dd 100644 --- a/fs/9p/vfs_dir.c +++ b/fs/9p/vfs_dir.c @@ -7,9 +7,8 @@ * 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 as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. + * 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 @@ -30,146 +29,192 @@ #include <linux/file.h> #include <linux/stat.h> #include <linux/string.h> -#include <linux/smp_lock.h> +#include <linux/sched.h> #include <linux/inet.h> #include <linux/idr.h> +#include <linux/slab.h> +#include <net/9p/9p.h> +#include <net/9p/client.h> -#include "debug.h" #include "v9fs.h" -#include "9p.h" -#include "conv.h" #include "v9fs_vfs.h" #include "fid.h" /** + * struct p9_rdir - readdir accounting + * @head: start offset of current dirread buffer + * @tail: end offset of current dirread buffer + * @buf: dirread buffer + * + * private structure for keeping track of readdir + * allocated on demand + */ + +struct p9_rdir { + int head; + int tail; + uint8_t buf[]; +}; + +/** * dt_type - return file type * @mistat: mistat structure * */ -static inline int dt_type(struct v9fs_stat *mistat) +static inline int dt_type(struct p9_wstat *mistat) { unsigned long perm = mistat->mode; int rettype = DT_REG; - if (perm & V9FS_DMDIR) + if (perm & P9_DMDIR) rettype = DT_DIR; - if (perm & V9FS_DMSYMLINK) + if (perm & P9_DMSYMLINK) rettype = DT_LNK; return rettype; } +static void p9stat_init(struct p9_wstat *stbuf) +{ + stbuf->name = NULL; + stbuf->uid = NULL; + stbuf->gid = NULL; + stbuf->muid = NULL; + stbuf->extension = NULL; +} + /** - * v9fs_dir_readdir - read a directory - * @filep: opened file structure - * @dirent: directory structure ??? - * @filldir: function to populate directory structure ??? + * v9fs_alloc_rdir_buf - Allocate buffer used for read and readdir + * @filp: opened file structure + * @buflen: Length in bytes of buffer to allocate * */ -static int v9fs_dir_readdir(struct file *filp, void *dirent, filldir_t filldir) +static struct p9_rdir *v9fs_alloc_rdir_buf(struct file *filp, int buflen) { - struct v9fs_fcall *fcall = NULL; - struct inode *inode = filp->f_dentry->d_inode; - struct v9fs_session_info *v9ses = v9fs_inode2v9ses(inode); - struct v9fs_fid *file = filp->private_data; - unsigned int i, n, s; - int fid = -1; - int ret = 0; - struct v9fs_stat stat; - int over = 0; - - dprintk(DEBUG_VFS, "name %s\n", filp->f_dentry->d_name.name); - - fid = file->fid; - - if (file->rdir_fcall && (filp->f_pos != file->rdir_pos)) { - kfree(file->rdir_fcall); - file->rdir_fcall = NULL; - } - - if (file->rdir_fcall) { - n = file->rdir_fcall->params.rread.count; - i = file->rdir_fpos; - while (i < n) { - s = v9fs_deserialize_stat( - file->rdir_fcall->params.rread.data + i, - n - i, &stat, v9ses->extended); - - if (s == 0) { - dprintk(DEBUG_ERROR, - "error while deserializing stat\n"); - ret = -EIO; - goto FreeStructs; - } + struct p9_fid *fid = filp->private_data; + if (!fid->rdir) + fid->rdir = kzalloc(sizeof(struct p9_rdir) + buflen, GFP_KERNEL); + return fid->rdir; +} - over = filldir(dirent, stat.name.str, stat.name.len, - filp->f_pos, v9fs_qid2ino(&stat.qid), - dt_type(&stat)); +/** + * v9fs_dir_readdir - iterate through a directory + * @file: opened file structure + * @ctx: actor we feed the entries to + * + */ - if (over) { - file->rdir_fpos = i; - file->rdir_pos = filp->f_pos; - break; +static int v9fs_dir_readdir(struct file *file, struct dir_context *ctx) +{ + bool over; + struct p9_wstat st; + int err = 0; + struct p9_fid *fid; + int buflen; + int reclen = 0; + struct p9_rdir *rdir; + + p9_debug(P9_DEBUG_VFS, "name %s\n", file->f_path.dentry->d_name.name); + fid = file->private_data; + + buflen = fid->clnt->msize - P9_IOHDRSZ; + + rdir = v9fs_alloc_rdir_buf(file, buflen); + if (!rdir) + return -ENOMEM; + + while (1) { + if (rdir->tail == rdir->head) { + err = v9fs_file_readn(file, rdir->buf, NULL, + buflen, ctx->pos); + if (err <= 0) + return err; + + rdir->head = 0; + rdir->tail = err; + } + while (rdir->head < rdir->tail) { + p9stat_init(&st); + err = p9stat_read(fid->clnt, rdir->buf + rdir->head, + rdir->tail - rdir->head, &st); + if (err) { + p9_debug(P9_DEBUG_VFS, "returned %d\n", err); + p9stat_free(&st); + return -EIO; } + reclen = st.size+2; - i += s; - filp->f_pos += s; - } + over = !dir_emit(ctx, st.name, strlen(st.name), + v9fs_qid2ino(&st.qid), dt_type(&st)); + p9stat_free(&st); + if (over) + return 0; - if (!over) { - kfree(file->rdir_fcall); - file->rdir_fcall = NULL; + rdir->head += reclen; + ctx->pos += reclen; } } +} - while (!over) { - ret = v9fs_t_read(v9ses, fid, filp->f_pos, - v9ses->maxdata-V9FS_IOHDRSZ, &fcall); - if (ret < 0) { - dprintk(DEBUG_ERROR, "error while reading: %d: %p\n", - ret, fcall); - goto FreeStructs; - } else if (ret == 0) - break; - - n = ret; - i = 0; - while (i < n) { - s = v9fs_deserialize_stat(fcall->params.rread.data + i, - n - i, &stat, v9ses->extended); - - if (s == 0) { - dprintk(DEBUG_ERROR, - "error while deserializing stat\n"); - return -EIO; - } +/** + * v9fs_dir_readdir_dotl - iterate through a directory + * @file: opened file structure + * @ctx: actor we feed the entries to + * + */ +static int v9fs_dir_readdir_dotl(struct file *file, struct dir_context *ctx) +{ + int err = 0; + struct p9_fid *fid; + int buflen; + struct p9_rdir *rdir; + struct p9_dirent curdirent; + + p9_debug(P9_DEBUG_VFS, "name %s\n", file->f_path.dentry->d_name.name); + fid = file->private_data; + + buflen = fid->clnt->msize - P9_READDIRHDRSZ; + + rdir = v9fs_alloc_rdir_buf(file, buflen); + if (!rdir) + return -ENOMEM; + + while (1) { + if (rdir->tail == rdir->head) { + err = p9_client_readdir(fid, rdir->buf, buflen, + ctx->pos); + if (err <= 0) + return err; + + rdir->head = 0; + rdir->tail = err; + } - over = filldir(dirent, stat.name.str, stat.name.len, - filp->f_pos, v9fs_qid2ino(&stat.qid), - dt_type(&stat)); + while (rdir->head < rdir->tail) { - if (over) { - file->rdir_fcall = fcall; - file->rdir_fpos = i; - file->rdir_pos = filp->f_pos; - fcall = NULL; - break; + err = p9dirent_read(fid->clnt, rdir->buf + rdir->head, + rdir->tail - rdir->head, + &curdirent); + if (err < 0) { + p9_debug(P9_DEBUG_VFS, "returned %d\n", err); + return -EIO; } - i += s; - filp->f_pos += s; - } + if (!dir_emit(ctx, curdirent.d_name, + strlen(curdirent.d_name), + v9fs_qid2ino(&curdirent.qid), + curdirent.d_type)) + return 0; - kfree(fcall); + ctx->pos = curdirent.d_off; + rdir->head += err; + } } - - FreeStructs: - kfree(fcall); - return ret; } + /** * v9fs_dir_release - close a directory * @inode: inode of the directory @@ -179,36 +224,29 @@ static int v9fs_dir_readdir(struct file *filp, void *dirent, filldir_t filldir) int v9fs_dir_release(struct inode *inode, struct file *filp) { - struct v9fs_session_info *v9ses = v9fs_inode2v9ses(inode); - struct v9fs_fid *fid = filp->private_data; - int fidnum = -1; - - dprintk(DEBUG_VFS, "inode: %p filp: %p fid: %d\n", inode, filp, - fid->fid); - fidnum = fid->fid; + struct p9_fid *fid; - filemap_write_and_wait(inode->i_mapping); - - if (fidnum >= 0) { - dprintk(DEBUG_VFS, "fidopen: %d v9f->fid: %d\n", fid->fidopen, - fid->fid); - - if (v9fs_t_clunk(v9ses, fidnum)) - dprintk(DEBUG_ERROR, "clunk failed\n"); - - kfree(fid->rdir_fcall); - kfree(fid); - - filp->private_data = NULL; - } - - d_drop(filp->f_dentry); + fid = filp->private_data; + p9_debug(P9_DEBUG_VFS, "inode: %p filp: %p fid: %d\n", + inode, filp, fid ? fid->fid : -1); + if (fid) + p9_client_clunk(fid); return 0; } -struct file_operations v9fs_dir_operations = { +const struct file_operations v9fs_dir_operations = { + .read = generic_read_dir, + .llseek = generic_file_llseek, + .iterate = v9fs_dir_readdir, + .open = v9fs_file_open, + .release = v9fs_dir_release, +}; + +const struct file_operations v9fs_dir_operations_dotl = { .read = generic_read_dir, - .readdir = v9fs_dir_readdir, + .llseek = generic_file_llseek, + .iterate = v9fs_dir_readdir_dotl, .open = v9fs_file_open, .release = v9fs_dir_release, + .fsync = v9fs_file_fsync_dotl, }; diff --git a/fs/9p/vfs_file.c b/fs/9p/vfs_file.c index c7e14d91721..520c11c2dcc 100644 --- a/fs/9p/vfs_file.c +++ b/fs/9p/vfs_file.c @@ -7,9 +7,8 @@ * 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 as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. + * 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 @@ -27,21 +26,26 @@ #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/smp_lock.h> #include <linux/inet.h> -#include <linux/version.h> #include <linux/list.h> +#include <linux/pagemap.h> +#include <linux/utsname.h> #include <asm/uaccess.h> #include <linux/idr.h> +#include <net/9p/9p.h> +#include <net/9p/client.h> -#include "debug.h" #include "v9fs.h" -#include "9p.h" #include "v9fs_vfs.h" #include "fid.h" +#include "cache.h" + +static const struct vm_operations_struct v9fs_file_vm_ops; +static const struct vm_operations_struct v9fs_mmap_file_vm_ops; /** * v9fs_file_open - open a file (or directory) @@ -52,255 +56,862 @@ int v9fs_file_open(struct inode *inode, struct file *file) { - struct v9fs_session_info *v9ses = v9fs_inode2v9ses(inode); - struct v9fs_fid *v9fid, *fid; - struct v9fs_fcall *fcall = NULL; - int open_mode = 0; - unsigned int iounit = 0; - int newfid = -1; - long result = -1; - - dprintk(DEBUG_VFS, "inode: %p file: %p \n", inode, file); - - v9fid = v9fs_fid_get_created(file->f_dentry); - if (!v9fid) - v9fid = v9fs_fid_lookup(file->f_dentry); - - if (!v9fid) { - dprintk(DEBUG_ERROR, "Couldn't resolve fid from dentry\n"); - return -EBADF; + int err; + struct v9fs_inode *v9inode; + struct v9fs_session_info *v9ses; + struct p9_fid *fid; + int omode; + + p9_debug(P9_DEBUG_VFS, "inode: %p file: %p\n", inode, file); + v9inode = V9FS_I(inode); + v9ses = v9fs_inode2v9ses(inode); + if (v9fs_proto_dotl(v9ses)) + omode = v9fs_open_to_dotl_flags(file->f_flags); + else + omode = v9fs_uflags2omode(file->f_flags, + v9fs_proto_dotu(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 ((file->f_flags & O_APPEND) && + (!v9fs_proto_dotu(v9ses) && !v9fs_proto_dotl(v9ses))) + generic_file_llseek(file, 0, SEEK_END); } - if (!v9fid->fidcreate) { - fid = kmalloc(sizeof(struct v9fs_fid), GFP_KERNEL); - if (fid == NULL) { - dprintk(DEBUG_ERROR, "Out of Memory\n"); - return -ENOMEM; + file->private_data = fid; + mutex_lock(&v9inode->v_mutex); + if ((v9ses->cache == CACHE_LOOSE || v9ses->cache == CACHE_FSCACHE) && + !v9inode->writeback_fid && + ((file->f_flags & O_ACCMODE) != O_RDONLY)) { + /* + * clone a fid and add it to writeback_fid + * we do it during open time instead of + * page dirty time via write_begin/page_mkwrite + * because we want write after unlink usecase + * to work. + */ + fid = v9fs_writeback_fid(file->f_path.dentry); + if (IS_ERR(fid)) { + err = PTR_ERR(fid); + mutex_unlock(&v9inode->v_mutex); + goto out_error; } + v9inode->writeback_fid = (void *) fid; + } + mutex_unlock(&v9inode->v_mutex); + if (v9ses->cache == CACHE_LOOSE || v9ses->cache == CACHE_FSCACHE) + v9fs_cache_inode_set_cookie(inode, file); + return 0; +out_error: + p9_client_clunk(file->private_data); + file->private_data = NULL; + return err; +} - fid->fidopen = 0; - fid->fidcreate = 0; - fid->fidclunked = 0; - fid->iounit = 0; - fid->v9ses = v9ses; +/** + * 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 + */ - newfid = v9fs_get_idpool(&v9ses->fidpool); - if (newfid < 0) { - eprintk(KERN_WARNING, "newfid fails!\n"); - return -ENOSPC; - } +static int v9fs_file_lock(struct file *filp, int cmd, struct file_lock *fl) +{ + int res = 0; + struct inode *inode = file_inode(filp); - result = - v9fs_t_walk(v9ses, v9fid->fid, newfid, NULL, NULL); + p9_debug(P9_DEBUG_VFS, "filp: %p lock: %p\n", filp, fl); - if (result < 0) { - v9fs_put_idpool(newfid, &v9ses->fidpool); - dprintk(DEBUG_ERROR, "rewalk didn't work\n"); - return -EBADF; - } + /* No mandatory locks */ + if (__mandatory_lock(inode) && fl->fl_type != F_UNLCK) + return -ENOLCK; - fid->fid = newfid; - v9fid = fid; - /* TODO: do special things for O_EXCL, O_NOFOLLOW, O_SYNC */ - /* translate open mode appropriately */ - open_mode = file->f_flags & 0x3; + 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); + } - if (file->f_flags & O_EXCL) - open_mode |= V9FS_OEXCL; + return res; +} - if (v9ses->extended) { - if (file->f_flags & O_TRUNC) - open_mode |= V9FS_OTRUNC; +static int v9fs_file_do_lock(struct file *filp, int cmd, struct file_lock *fl) +{ + struct p9_flock flock; + struct p9_fid *fid; + uint8_t status; + int res = 0; + unsigned char fl_type; + + fid = filp->private_data; + BUG_ON(fid == NULL); + + if ((fl->fl_flags & FL_POSIX) != FL_POSIX) + BUG(); + + res = posix_lock_file_wait(filp, fl); + if (res < 0) + goto out; + + /* convert posix lock to p9 tlock args */ + memset(&flock, 0, sizeof(flock)); + /* map the lock type */ + switch (fl->fl_type) { + case F_RDLCK: + flock.type = P9_LOCK_TYPE_RDLCK; + break; + case F_WRLCK: + flock.type = P9_LOCK_TYPE_WRLCK; + break; + case F_UNLCK: + flock.type = P9_LOCK_TYPE_UNLCK; + break; + } + flock.start = fl->fl_start; + if (fl->fl_end == OFFSET_MAX) + flock.length = 0; + else + flock.length = fl->fl_end - fl->fl_start + 1; + flock.proc_id = fl->fl_pid; + flock.client_id = fid->clnt->name; + if (IS_SETLKW(cmd)) + flock.flags = P9_LOCK_FLAGS_BLOCK; + + /* + * if its a blocked request and we get P9_LOCK_BLOCKED as the status + * for lock request, keep on trying + */ + for (;;) { + res = p9_client_lock_dotl(fid, &flock, &status); + if (res < 0) + break; - if (file->f_flags & O_APPEND) - open_mode |= V9FS_OAPPEND; - } + if (status != P9_LOCK_BLOCKED) + break; + if (status == P9_LOCK_BLOCKED && !IS_SETLKW(cmd)) + break; + if (schedule_timeout_interruptible(P9_LOCK_TIMEOUT) != 0) + break; + } - result = v9fs_t_open(v9ses, newfid, open_mode, &fcall); - if (result < 0) { - PRINT_FCALL_ERROR("open failed", fcall); - kfree(fcall); - return result; - } + /* map 9p status to VFS status */ + switch (status) { + case P9_LOCK_SUCCESS: + res = 0; + break; + case P9_LOCK_BLOCKED: + res = -EAGAIN; + break; + case P9_LOCK_ERROR: + case P9_LOCK_GRACE: + res = -ENOLCK; + break; + default: + BUG(); + } - iounit = fcall->params.ropen.iounit; - kfree(fcall); - } else { - /* create case */ - newfid = v9fid->fid; - iounit = v9fid->iounit; - v9fid->fidcreate = 0; + /* + * incase server returned error for lock request, revert + * it locally + */ + if (res < 0 && fl->fl_type != F_UNLCK) { + fl_type = fl->fl_type; + fl->fl_type = F_UNLCK; + res = posix_lock_file_wait(filp, fl); + fl->fl_type = fl_type; } +out: + return res; +} - file->private_data = v9fid; +static int v9fs_file_getlock(struct file *filp, struct file_lock *fl) +{ + struct p9_getlock glock; + struct p9_fid *fid; + int res = 0; - v9fid->rdir_pos = 0; - v9fid->rdir_fcall = NULL; - v9fid->fidopen = 1; - v9fid->filp = file; - v9fid->iounit = iounit; + fid = filp->private_data; + BUG_ON(fid == NULL); + + posix_test_lock(filp, fl); + /* + * if we have a conflicting lock locally, no need to validate + * with server + */ + if (fl->fl_type != F_UNLCK) + return res; + + /* convert posix lock to p9 tgetlock args */ + memset(&glock, 0, sizeof(glock)); + glock.type = P9_LOCK_TYPE_UNLCK; + glock.start = fl->fl_start; + if (fl->fl_end == OFFSET_MAX) + glock.length = 0; + else + glock.length = fl->fl_end - fl->fl_start + 1; + glock.proc_id = fl->fl_pid; + glock.client_id = fid->clnt->name; + + res = p9_client_getlock_dotl(fid, &glock); + if (res < 0) + return res; + /* map 9p lock type to os lock type */ + switch (glock.type) { + case P9_LOCK_TYPE_RDLCK: + fl->fl_type = F_RDLCK; + break; + case P9_LOCK_TYPE_WRLCK: + fl->fl_type = F_WRLCK; + break; + case P9_LOCK_TYPE_UNLCK: + fl->fl_type = F_UNLCK; + break; + } + if (glock.type != P9_LOCK_TYPE_UNLCK) { + fl->fl_start = glock.start; + if (glock.length == 0) + fl->fl_end = OFFSET_MAX; + else + fl->fl_end = glock.start + glock.length - 1; + fl->fl_pid = glock.proc_id; + } + return res; +} - return 0; +/** + * v9fs_file_lock_dotl - lock a file (or directory) + * @filp: file to be locked + * @cmd: lock command + * @fl: file lock structure + * + */ + +static int v9fs_file_lock_dotl(struct file *filp, int cmd, struct file_lock *fl) +{ + struct inode *inode = file_inode(filp); + int ret = -ENOLCK; + + p9_debug(P9_DEBUG_VFS, "filp: %p cmd:%d lock: %p name: %s\n", + filp, cmd, fl, filp->f_path.dentry->d_name.name); + + /* No mandatory locks */ + if (__mandatory_lock(inode) && fl->fl_type != F_UNLCK) + goto out_err; + + 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); + } + + if (IS_SETLK(cmd) || IS_SETLKW(cmd)) + ret = v9fs_file_do_lock(filp, cmd, fl); + else if (IS_GETLK(cmd)) + ret = v9fs_file_getlock(filp, fl); + else + ret = -EINVAL; +out_err: + return ret; } /** - * v9fs_file_lock - lock a file (or directory) - * @inode: inode to be opened - * @file: file being opened + * v9fs_file_flock_dotl - lock a file + * @filp: file to be locked + * @cmd: lock command + * @fl: file lock structure * - * XXX - 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) +static int v9fs_file_flock_dotl(struct file *filp, int cmd, + struct file_lock *fl) { - int res = 0; - struct inode *inode = filp->f_dentry->d_inode; + struct inode *inode = file_inode(filp); + int ret = -ENOLCK; - dprintk(DEBUG_VFS, "filp: %p lock: %p\n", filp, fl); + p9_debug(P9_DEBUG_VFS, "filp: %p cmd:%d lock: %p name: %s\n", + filp, cmd, fl, filp->f_path.dentry->d_name.name); /* No mandatory locks */ - if ((inode->i_mode & (S_ISGID | S_IXGRP)) == S_ISGID) - return -ENOLCK; + if (__mandatory_lock(inode) && fl->fl_type != F_UNLCK) + goto out_err; + + if (!(fl->fl_flags & FL_FLOCK)) + goto out_err; if ((IS_SETLK(cmd) || IS_SETLKW(cmd)) && fl->fl_type != F_UNLCK) { filemap_write_and_wait(inode->i_mapping); - invalidate_inode_pages(&inode->i_data); + invalidate_mapping_pages(&inode->i_data, 0, -1); } + /* Convert flock to posix lock */ + fl->fl_flags |= FL_POSIX; + fl->fl_flags ^= FL_FLOCK; + + if (IS_SETLK(cmd) | IS_SETLKW(cmd)) + ret = v9fs_file_do_lock(filp, cmd, fl); + else + ret = -EINVAL; +out_err: + return ret; +} - return res; +/** + * v9fs_fid_readn - read from a fid + * @fid: fid 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_fid_readn(struct p9_fid *fid, char *data, char __user *udata, u32 count, + u64 offset) +{ + int n, total, size; + + p9_debug(P9_DEBUG_VFS, "fid %d offset %llu count %d\n", + fid->fid, (long long unsigned)offset, count); + n = 0; + total = 0; + size = fid->iounit ? fid->iounit : fid->clnt->msize - P9_IOHDRSZ; + 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 == size); + + if (n < 0) + total = n; + + return total; } /** - * v9fs_file_read - read from a file - * @filep: file pointer to read + * 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) +{ + return v9fs_fid_readn(filp->private_data, data, udata, count, offset); +} + +/** + * 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 * data, size_t count, +v9fs_file_read(struct file *filp, char __user *udata, size_t count, loff_t * offset) { - struct inode *inode = filp->f_dentry->d_inode; - struct v9fs_session_info *v9ses = v9fs_inode2v9ses(inode); - struct v9fs_fid *v9f = filp->private_data; - struct v9fs_fcall *fcall = NULL; - int fid = v9f->fid; - int rsize = 0; - int result = 0; - int total = 0; - int n; + int ret; + struct p9_fid *fid; + size_t size; + + p9_debug(P9_DEBUG_VFS, "count %zu offset %lld\n", count, *offset); + fid = filp->private_data; - dprintk(DEBUG_VFS, "\n"); + size = fid->iounit ? fid->iounit : fid->clnt->msize - P9_IOHDRSZ; + if (count > size) + ret = v9fs_file_readn(filp, NULL, udata, count, *offset); + else + ret = p9_client_read(fid, NULL, udata, *offset, count); - rsize = v9ses->maxdata - V9FS_IOHDRSZ; - if (v9f->iounit != 0 && rsize > v9f->iounit) - rsize = v9f->iounit; + if (ret > 0) + *offset += ret; + + return ret; +} + +ssize_t +v9fs_file_write_internal(struct inode *inode, struct p9_fid *fid, + const char __user *data, size_t count, + loff_t *offset, int invalidate) +{ + int n; + loff_t i_size; + size_t total = 0; + loff_t origin = *offset; + unsigned long pg_start, pg_end; + + p9_debug(P9_DEBUG_VFS, "data %p count %d offset %x\n", + data, (int)count, (int)*offset); do { - if (count < rsize) - rsize = count; + n = p9_client_write(fid, NULL, data+total, origin+total, count); + if (n <= 0) + break; + count -= n; + total += n; + } while (count > 0); + + if (invalidate && (total > 0)) { + pg_start = origin >> PAGE_CACHE_SHIFT; + pg_end = (origin + total - 1) >> PAGE_CACHE_SHIFT; + if (inode->i_mapping && inode->i_mapping->nrpages) + invalidate_inode_pages2_range(inode->i_mapping, + pg_start, pg_end); + *offset += total; + i_size = i_size_read(inode); + if (*offset > i_size) { + inode_add_bytes(inode, *offset - i_size); + i_size_write(inode, *offset); + } + } + if (n < 0) + return n; - result = v9fs_t_read(v9ses, fid, *offset, rsize, &fcall); + return total; +} - if (result < 0) { - printk(KERN_ERR "9P2000: v9fs_t_read returned %d\n", - result); +/** + * 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) +{ + ssize_t retval = 0; + loff_t origin = *offset; + + + retval = generic_write_checks(filp, &origin, &count, 0); + if (retval) + goto out; + + retval = -EINVAL; + if ((ssize_t) count < 0) + goto out; + retval = 0; + if (!count) + goto out; + + retval = v9fs_file_write_internal(file_inode(filp), + filp->private_data, + data, count, &origin, 1); + /* update offset on successful write */ + if (retval > 0) + *offset = origin; +out: + return retval; +} + + +static int v9fs_file_fsync(struct file *filp, loff_t start, loff_t end, + int datasync) +{ + struct p9_fid *fid; + struct inode *inode = filp->f_mapping->host; + struct p9_wstat wstat; + int retval; + + retval = filemap_write_and_wait_range(inode->i_mapping, start, end); + if (retval) + return retval; + + mutex_lock(&inode->i_mutex); + p9_debug(P9_DEBUG_VFS, "filp %p datasync %x\n", filp, datasync); + + fid = filp->private_data; + v9fs_blank_wstat(&wstat); + + retval = p9_client_wstat(fid, &wstat); + mutex_unlock(&inode->i_mutex); + + return retval; +} + +int v9fs_file_fsync_dotl(struct file *filp, loff_t start, loff_t end, + int datasync) +{ + struct p9_fid *fid; + struct inode *inode = filp->f_mapping->host; + int retval; + + retval = filemap_write_and_wait_range(inode->i_mapping, start, end); + if (retval) + return retval; + + mutex_lock(&inode->i_mutex); + p9_debug(P9_DEBUG_VFS, "filp %p datasync %x\n", filp, datasync); + + fid = filp->private_data; - kfree(fcall); - return total; - } else - *offset += result; + retval = p9_client_fsync(fid, datasync); + mutex_unlock(&inode->i_mutex); - n = copy_to_user(data, fcall->params.rread.data, result); - if (n) { - dprintk(DEBUG_ERROR, "Problem copying to user %d\n", n); - kfree(fcall); - return -EFAULT; + return retval; +} + +static int +v9fs_file_mmap(struct file *filp, struct vm_area_struct *vma) +{ + int retval; + + + retval = generic_file_mmap(filp, vma); + if (!retval) + vma->vm_ops = &v9fs_file_vm_ops; + + return retval; +} + +static int +v9fs_mmap_file_mmap(struct file *filp, struct vm_area_struct *vma) +{ + int retval; + struct inode *inode; + struct v9fs_inode *v9inode; + struct p9_fid *fid; + + inode = file_inode(filp); + v9inode = V9FS_I(inode); + mutex_lock(&v9inode->v_mutex); + if (!v9inode->writeback_fid && + (vma->vm_flags & VM_WRITE)) { + /* + * clone a fid and add it to writeback_fid + * we do it during mmap instead of + * page dirty time via write_begin/page_mkwrite + * because we want write after unlink usecase + * to work. + */ + fid = v9fs_writeback_fid(filp->f_path.dentry); + if (IS_ERR(fid)) { + retval = PTR_ERR(fid); + mutex_unlock(&v9inode->v_mutex); + return retval; } + v9inode->writeback_fid = (void *) fid; + } + mutex_unlock(&v9inode->v_mutex); - count -= result; - data += result; - total += result; + retval = generic_file_mmap(filp, vma); + if (!retval) + vma->vm_ops = &v9fs_mmap_file_vm_ops; - kfree(fcall); + return retval; +} - if (result < rsize) - break; - } while (count); +static int +v9fs_vm_page_mkwrite(struct vm_area_struct *vma, struct vm_fault *vmf) +{ + struct v9fs_inode *v9inode; + struct page *page = vmf->page; + struct file *filp = vma->vm_file; + struct inode *inode = file_inode(filp); + + + p9_debug(P9_DEBUG_VFS, "page %p fid %lx\n", + page, (unsigned long)filp->private_data); + + /* Update file times before taking page lock */ + file_update_time(filp); + + v9inode = V9FS_I(inode); + /* make sure the cache has finished storing the page */ + v9fs_fscache_wait_on_page_write(inode, page); + BUG_ON(!v9inode->writeback_fid); + lock_page(page); + if (page->mapping != inode->i_mapping) + goto out_unlock; + wait_for_stable_page(page); + + return VM_FAULT_LOCKED; +out_unlock: + unlock_page(page); + return VM_FAULT_NOPAGE; +} - return total; +static ssize_t +v9fs_direct_read(struct file *filp, char __user *udata, size_t count, + loff_t *offsetp) +{ + loff_t size, offset; + struct inode *inode; + struct address_space *mapping; + + offset = *offsetp; + mapping = filp->f_mapping; + inode = mapping->host; + if (!count) + return 0; + size = i_size_read(inode); + if (offset < size) + filemap_write_and_wait_range(mapping, offset, + offset + count - 1); + + return v9fs_file_read(filp, udata, count, offsetp); } /** - * v9fs_file_write - write to a file - * @filep: file pointer to write + * v9fs_cached_file_read - read from a file + * @filp: file pointer to read + * @data: user data buffer to read data into + * @count: size of buffer + * @offset: offset at which to read data + * + */ +static ssize_t +v9fs_cached_file_read(struct file *filp, char __user *data, size_t count, + loff_t *offset) +{ + if (filp->f_flags & O_DIRECT) + return v9fs_direct_read(filp, data, count, offset); + return new_sync_read(filp, data, count, offset); +} + +/** + * v9fs_mmap_file_read - read from a file + * @filp: file pointer to read + * @data: user data buffer to read data into + * @count: size of buffer + * @offset: offset at which to read data + * + */ +static ssize_t +v9fs_mmap_file_read(struct file *filp, char __user *data, size_t count, + loff_t *offset) +{ + /* TODO: Check if there are dirty pages */ + return v9fs_file_read(filp, data, count, offset); +} + +static ssize_t +v9fs_direct_write(struct file *filp, const char __user * data, + size_t count, loff_t *offsetp) +{ + loff_t offset; + ssize_t retval; + struct inode *inode; + struct address_space *mapping; + + offset = *offsetp; + mapping = filp->f_mapping; + inode = mapping->host; + if (!count) + return 0; + + mutex_lock(&inode->i_mutex); + retval = filemap_write_and_wait_range(mapping, offset, + offset + count - 1); + if (retval) + goto err_out; + /* + * After a write we want buffered reads to be sure to go to disk to get + * the new data. We invalidate clean cached page from the region we're + * about to write. We do this *before* the write so that if we fail + * here we fall back to buffered write + */ + if (mapping->nrpages) { + pgoff_t pg_start = offset >> PAGE_CACHE_SHIFT; + pgoff_t pg_end = (offset + count - 1) >> PAGE_CACHE_SHIFT; + + retval = invalidate_inode_pages2_range(mapping, + pg_start, pg_end); + /* + * If a page can not be invalidated, fall back + * to buffered write. + */ + if (retval) { + if (retval == -EBUSY) + goto buff_write; + goto err_out; + } + } + retval = v9fs_file_write(filp, data, count, offsetp); +err_out: + mutex_unlock(&inode->i_mutex); + return retval; + +buff_write: + mutex_unlock(&inode->i_mutex); + return new_sync_write(filp, data, count, offsetp); +} + +/** + * v9fs_cached_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_cached_file_write(struct file *filp, const char __user * data, + size_t count, loff_t *offset) +{ + + if (filp->f_flags & O_DIRECT) + return v9fs_direct_write(filp, data, count, offset); + return new_sync_write(filp, data, count, offset); +} + +/** + * v9fs_mmap_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) +v9fs_mmap_file_write(struct file *filp, const char __user *data, + size_t count, loff_t *offset) { - struct inode *inode = filp->f_dentry->d_inode; - struct v9fs_session_info *v9ses = v9fs_inode2v9ses(inode); - struct v9fs_fid *v9fid = filp->private_data; - struct v9fs_fcall *fcall; - int fid = v9fid->fid; - int result = -EIO; - int rsize = 0; - int total = 0; - - dprintk(DEBUG_VFS, "data %p count %d offset %x\n", data, (int)count, - (int)*offset); - rsize = v9ses->maxdata - V9FS_IOHDRSZ; - if (v9fid->iounit != 0 && rsize > v9fid->iounit) - rsize = v9fid->iounit; + /* + * TODO: invalidate mmaps on filp's inode between + * offset and offset+count + */ + return v9fs_file_write(filp, data, count, offset); +} - do { - if (count < rsize) - rsize = count; - - result = v9fs_t_write(v9ses, fid, *offset, rsize, data, &fcall); - if (result < 0) { - PRINT_FCALL_ERROR("error while writing", fcall); - kfree(fcall); - return result; - } else - *offset += result; - - kfree(fcall); - fcall = NULL; - - if (result != rsize) { - eprintk(KERN_ERR, - "short write: v9fs_t_write returned %d\n", - result); - break; - } +static void v9fs_mmap_vm_close(struct vm_area_struct *vma) +{ + struct inode *inode; - count -= result; - data += result; - total += result; - } while (count); + struct writeback_control wbc = { + .nr_to_write = LONG_MAX, + .sync_mode = WB_SYNC_ALL, + .range_start = vma->vm_pgoff * PAGE_SIZE, + /* absolute end, byte at end included */ + .range_end = vma->vm_pgoff * PAGE_SIZE + + (vma->vm_end - vma->vm_start - 1), + }; - if(inode->i_mapping->nrpages) - invalidate_inode_pages2(inode->i_mapping); - return total; + p9_debug(P9_DEBUG_VFS, "9p VMA close, %p, flushing", vma); + + inode = file_inode(vma->vm_file); + + if (!mapping_cap_writeback_dirty(inode->i_mapping)) + wbc.nr_to_write = 0; + + might_sleep(); + sync_inode(inode, &wbc); } -struct file_operations v9fs_file_operations = { + +static const struct vm_operations_struct v9fs_file_vm_ops = { + .fault = filemap_fault, + .map_pages = filemap_map_pages, + .page_mkwrite = v9fs_vm_page_mkwrite, + .remap_pages = generic_file_remap_pages, +}; + +static const struct vm_operations_struct v9fs_mmap_file_vm_ops = { + .close = v9fs_mmap_vm_close, + .fault = filemap_fault, + .map_pages = filemap_map_pages, + .page_mkwrite = v9fs_vm_page_mkwrite, + .remap_pages = generic_file_remap_pages, +}; + + +const struct file_operations v9fs_cached_file_operations = { + .llseek = generic_file_llseek, + .read = v9fs_cached_file_read, + .write = v9fs_cached_file_write, + .read_iter = generic_file_read_iter, + .write_iter = generic_file_write_iter, + .open = v9fs_file_open, + .release = v9fs_dir_release, + .lock = v9fs_file_lock, + .mmap = v9fs_file_mmap, + .fsync = v9fs_file_fsync, +}; + +const struct file_operations v9fs_cached_file_operations_dotl = { + .llseek = generic_file_llseek, + .read = v9fs_cached_file_read, + .write = v9fs_cached_file_write, + .read_iter = generic_file_read_iter, + .write_iter = generic_file_write_iter, + .open = v9fs_file_open, + .release = v9fs_dir_release, + .lock = v9fs_file_lock_dotl, + .flock = v9fs_file_flock_dotl, + .mmap = v9fs_file_mmap, + .fsync = v9fs_file_fsync_dotl, +}; + +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, + .fsync = v9fs_file_fsync, +}; + +const struct file_operations v9fs_file_operations_dotl = { .llseek = generic_file_llseek, .read = v9fs_file_read, .write = v9fs_file_write, .open = v9fs_file_open, .release = v9fs_dir_release, + .lock = v9fs_file_lock_dotl, + .flock = v9fs_file_flock_dotl, + .mmap = generic_file_readonly_mmap, + .fsync = v9fs_file_fsync_dotl, +}; + +const struct file_operations v9fs_mmap_file_operations = { + .llseek = generic_file_llseek, + .read = v9fs_mmap_file_read, + .write = v9fs_mmap_file_write, + .open = v9fs_file_open, + .release = v9fs_dir_release, .lock = v9fs_file_lock, - .mmap = generic_file_mmap, + .mmap = v9fs_mmap_file_mmap, + .fsync = v9fs_file_fsync, +}; + +const struct file_operations v9fs_mmap_file_operations_dotl = { + .llseek = generic_file_llseek, + .read = v9fs_mmap_file_read, + .write = v9fs_mmap_file_write, + .open = v9fs_file_open, + .release = v9fs_dir_release, + .lock = v9fs_file_lock_dotl, + .flock = v9fs_file_flock_dotl, + .mmap = v9fs_mmap_file_mmap, + .fsync = v9fs_file_fsync_dotl, }; diff --git a/fs/9p/vfs_inode.c b/fs/9p/vfs_inode.c index 63e5b0398e8..7fa4f7a7653 100644 --- a/fs/9p/vfs_inode.c +++ b/fs/9p/vfs_inode.c @@ -7,9 +7,8 @@ * 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 as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. + * 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 @@ -24,6 +23,8 @@ * */ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + #include <linux/module.h> #include <linux/errno.h> #include <linux/fs.h> @@ -31,21 +32,27 @@ #include <linux/pagemap.h> #include <linux/stat.h> #include <linux/string.h> -#include <linux/smp_lock.h> #include <linux/inet.h> #include <linux/namei.h> #include <linux/idr.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/xattr.h> +#include <linux/posix_acl.h> +#include <net/9p/9p.h> +#include <net/9p/client.h> -#include "debug.h" #include "v9fs.h" -#include "9p.h" #include "v9fs_vfs.h" #include "fid.h" +#include "cache.h" +#include "xattr.h" +#include "acl.h" -static struct inode_operations v9fs_dir_inode_operations; -static struct inode_operations v9fs_dir_inode_operations_ext; -static struct inode_operations v9fs_file_inode_operations; -static struct inode_operations v9fs_symlink_inode_operations; +static const struct inode_operations v9fs_dir_inode_operations; +static const struct inode_operations v9fs_dir_inode_operations_dotu; +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 @@ -54,86 +61,156 @@ static struct inode_operations v9fs_symlink_inode_operations; * */ -static int unixmode2p9mode(struct v9fs_session_info *v9ses, int mode) +static u32 unixmode2p9mode(struct v9fs_session_info *v9ses, umode_t mode) { int res; res = mode & 0777; if (S_ISDIR(mode)) - res |= V9FS_DMDIR; - if (v9ses->extended) { - if (S_ISLNK(mode)) - res |= V9FS_DMSYMLINK; + res |= P9_DMDIR; + if (v9fs_proto_dotu(v9ses)) { if (v9ses->nodev == 0) { if (S_ISSOCK(mode)) - res |= V9FS_DMSOCKET; + res |= P9_DMSOCKET; if (S_ISFIFO(mode)) - res |= V9FS_DMNAMEDPIPE; + res |= P9_DMNAMEDPIPE; if (S_ISBLK(mode)) - res |= V9FS_DMDEVICE; + res |= P9_DMDEVICE; if (S_ISCHR(mode)) - res |= V9FS_DMDEVICE; + res |= P9_DMDEVICE; } if ((mode & S_ISUID) == S_ISUID) - res |= V9FS_DMSETUID; + res |= P9_DMSETUID; if ((mode & S_ISGID) == S_ISGID) - res |= V9FS_DMSETGID; - if ((mode & V9FS_DMLINK)) - res |= V9FS_DMLINK; + res |= P9_DMSETGID; + if ((mode & S_ISVTX) == S_ISVTX) + res |= P9_DMSETVTX; } + return res; +} + +/** + * p9mode2perm- convert plan9 mode bits to unix permission bits + * @v9ses: v9fs session information + * @stat: p9_wstat from which mode need to be derived + * + */ +static int p9mode2perm(struct v9fs_session_info *v9ses, + struct p9_wstat *stat) +{ + int res; + int mode = stat->mode; + + res = mode & S_IALLUGO; + if (v9fs_proto_dotu(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; } /** * p9mode2unixmode- convert plan9 mode bits to unix mode bits * @v9ses: v9fs session information - * @mode: mode to convert + * @stat: p9_wstat from which mode need to be derived + * @rdev: major number, minor number in case of device files. * */ - -static int p9mode2unixmode(struct v9fs_session_info *v9ses, int mode) +static umode_t p9mode2unixmode(struct v9fs_session_info *v9ses, + struct p9_wstat *stat, dev_t *rdev) { int res; + u32 mode = stat->mode; - res = mode & 0777; + *rdev = 0; + res = p9mode2perm(v9ses, stat); - if ((mode & V9FS_DMDIR) == V9FS_DMDIR) + if ((mode & P9_DMDIR) == P9_DMDIR) res |= S_IFDIR; - else if ((mode & V9FS_DMSYMLINK) && (v9ses->extended)) + else if ((mode & P9_DMSYMLINK) && (v9fs_proto_dotu(v9ses))) res |= S_IFLNK; - else if ((mode & V9FS_DMSOCKET) && (v9ses->extended) + else if ((mode & P9_DMSOCKET) && (v9fs_proto_dotu(v9ses)) && (v9ses->nodev == 0)) res |= S_IFSOCK; - else if ((mode & V9FS_DMNAMEDPIPE) && (v9ses->extended) + else if ((mode & P9_DMNAMEDPIPE) && (v9fs_proto_dotu(v9ses)) && (v9ses->nodev == 0)) res |= S_IFIFO; - else if ((mode & V9FS_DMDEVICE) && (v9ses->extended) - && (v9ses->nodev == 0)) - res |= S_IFBLK; - else + else if ((mode & P9_DMDEVICE) && (v9fs_proto_dotu(v9ses)) + && (v9ses->nodev == 0)) { + char type = 0, ext[32]; + int major = -1, minor = -1; + + strlcpy(ext, stat->extension, sizeof(ext)); + sscanf(ext, "%c %i %i", &type, &major, &minor); + switch (type) { + case 'c': + res |= S_IFCHR; + break; + case 'b': + res |= S_IFBLK; + break; + default: + p9_debug(P9_DEBUG_ERROR, "Unknown special type %c %s\n", + type, stat->extension); + }; + *rdev = MKDEV(major, minor); + } else res |= S_IFREG; - if (v9ses->extended) { - if ((mode & V9FS_DMSETUID) == V9FS_DMSETUID) - res |= S_ISUID; + return res; +} - if ((mode & V9FS_DMSETGID) == V9FS_DMSETGID) - res |= S_ISGID; +/** + * 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; } - return res; + 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 v9fs_wstat *wstat) +void +v9fs_blank_wstat(struct p9_wstat *wstat) { wstat->type = ~0; wstat->dev = ~0; @@ -148,429 +225,700 @@ v9fs_blank_wstat(struct v9fs_wstat *wstat) wstat->uid = NULL; wstat->gid = NULL; wstat->muid = NULL; - wstat->n_uid = ~0; - wstat->n_gid = ~0; - wstat->n_muid = ~0; + wstat->n_uid = INVALID_UID; + wstat->n_gid = INVALID_GID; + wstat->n_muid = INVALID_UID; wstat->extension = NULL; } /** - * v9fs_get_inode - helper function to setup an inode - * @sb: superblock - * @mode: mode to setup inode with + * v9fs_alloc_inode - helper function to allocate an inode + * + */ +struct inode *v9fs_alloc_inode(struct super_block *sb) +{ + struct v9fs_inode *v9inode; + v9inode = (struct v9fs_inode *)kmem_cache_alloc(v9fs_inode_cache, + GFP_KERNEL); + if (!v9inode) + return NULL; +#ifdef CONFIG_9P_FSCACHE + v9inode->fscache = NULL; + spin_lock_init(&v9inode->fscache_lock); +#endif + v9inode->writeback_fid = NULL; + v9inode->cache_validity = 0; + mutex_init(&v9inode->v_mutex); + return &v9inode->vfs_inode; +} + +/** + * v9fs_destroy_inode - destroy an inode * */ -struct inode *v9fs_get_inode(struct super_block *sb, int mode) +static void v9fs_i_callback(struct rcu_head *head) { - struct inode *inode = NULL; - struct v9fs_session_info *v9ses = sb->s_fs_info; + struct inode *inode = container_of(head, struct inode, i_rcu); + kmem_cache_free(v9fs_inode_cache, V9FS_I(inode)); +} - dprintk(DEBUG_VFS, "super block: %p mode: %o\n", sb, mode); +void v9fs_destroy_inode(struct inode *inode) +{ + call_rcu(&inode->i_rcu, v9fs_i_callback); +} - inode = new_inode(sb); - if (inode) { - inode->i_mode = mode; - inode->i_uid = current->fsuid; - inode->i_gid = current->fsgid; - inode->i_blksize = sb->s_blocksize; - 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(!v9ses->extended) { - dprintk(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: +int v9fs_init_inode(struct v9fs_session_info *v9ses, + struct inode *inode, umode_t mode, dev_t rdev) +{ + int err = 0; + + inode_init_owner(inode, NULL, mode); + inode->i_blocks = 0; + inode->i_rdev = rdev; + 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_proto_dotl(v9ses)) { + inode->i_op = &v9fs_file_inode_operations_dotl; + } else if (v9fs_proto_dotu(v9ses)) { inode->i_op = &v9fs_file_inode_operations; - inode->i_fop = &v9fs_file_operations; - break; - case S_IFLNK: - if(!v9ses->extended) { - dprintk(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: - inode->i_nlink++; - if(v9ses->extended) - inode->i_op = &v9fs_dir_inode_operations_ext; + } else { + p9_debug(P9_DEBUG_ERROR, + "special files without extended mode\n"); + err = -EINVAL; + goto error; + } + init_special_inode(inode, inode->i_mode, inode->i_rdev); + break; + case S_IFREG: + if (v9fs_proto_dotl(v9ses)) { + inode->i_op = &v9fs_file_inode_operations_dotl; + if (v9ses->cache == CACHE_LOOSE || + v9ses->cache == CACHE_FSCACHE) + inode->i_fop = + &v9fs_cached_file_operations_dotl; + else if (v9ses->cache == CACHE_MMAP) + inode->i_fop = &v9fs_mmap_file_operations_dotl; else - inode->i_op = &v9fs_dir_inode_operations; - inode->i_fop = &v9fs_dir_operations; - break; - default: - dprintk(DEBUG_ERROR, "BAD mode 0x%x S_IFMT 0x%x\n", - mode, mode & S_IFMT); - return ERR_PTR(-EINVAL); + inode->i_fop = &v9fs_file_operations_dotl; + } else { + inode->i_op = &v9fs_file_inode_operations; + if (v9ses->cache == CACHE_LOOSE || + v9ses->cache == CACHE_FSCACHE) + inode->i_fop = + &v9fs_cached_file_operations; + else if (v9ses->cache == CACHE_MMAP) + inode->i_fop = &v9fs_mmap_file_operations; + else + inode->i_fop = &v9fs_file_operations; } - } else { - eprintk(KERN_WARNING, "Problem allocating inode\n"); - return ERR_PTR(-ENOMEM); + + break; + case S_IFLNK: + if (!v9fs_proto_dotu(v9ses) && !v9fs_proto_dotl(v9ses)) { + p9_debug(P9_DEBUG_ERROR, + "extended modes used with legacy protocol\n"); + err = -EINVAL; + goto error; + } + + if (v9fs_proto_dotl(v9ses)) + inode->i_op = &v9fs_symlink_inode_operations_dotl; + else + inode->i_op = &v9fs_symlink_inode_operations; + + break; + case S_IFDIR: + inc_nlink(inode); + if (v9fs_proto_dotl(v9ses)) + inode->i_op = &v9fs_dir_inode_operations_dotl; + else if (v9fs_proto_dotu(v9ses)) + inode->i_op = &v9fs_dir_inode_operations_dotu; + else + inode->i_op = &v9fs_dir_inode_operations; + + if (v9fs_proto_dotl(v9ses)) + inode->i_fop = &v9fs_dir_operations_dotl; + else + inode->i_fop = &v9fs_dir_operations; + + break; + default: + p9_debug(P9_DEBUG_ERROR, "BAD mode 0x%hx S_IFMT 0x%x\n", + mode, mode & S_IFMT); + err = -EINVAL; + goto error; } - return inode; +error: + return err; + } /** - * v9fs_create - helper function to create files and directories - * @dir: directory inode file is being created in - * @file_dentry: dentry file is being created in - * @perm: permissions file is being created with - * @open_mode: resulting open mode for file + * v9fs_get_inode - helper function to setup an inode + * @sb: superblock + * @mode: mode to setup inode with * */ -static int -v9fs_create(struct inode *dir, - struct dentry *file_dentry, - unsigned int perm, unsigned int open_mode) +struct inode *v9fs_get_inode(struct super_block *sb, umode_t mode, dev_t rdev) { - struct v9fs_session_info *v9ses = v9fs_inode2v9ses(dir); - struct super_block *sb = dir->i_sb; - struct v9fs_fid *dirfid = - v9fs_fid_lookup(file_dentry->d_parent); - struct v9fs_fid *fid = NULL; - struct inode *file_inode = NULL; - struct v9fs_fcall *fcall = NULL; - struct v9fs_qid qid; - int dirfidnum = -1; - long newfid = -1; - int result = 0; - unsigned int iounit = 0; - int wfidno = -1; int err; + struct inode *inode; + struct v9fs_session_info *v9ses = sb->s_fs_info; - perm = unixmode2p9mode(v9ses, perm); - - dprintk(DEBUG_VFS, "dir: %p dentry: %p perm: %o mode: %o\n", dir, - file_dentry, perm, open_mode); - - if (!dirfid) - return -EBADF; + p9_debug(P9_DEBUG_VFS, "super block: %p mode: %ho\n", sb, mode); - dirfidnum = dirfid->fid; - if (dirfidnum < 0) { - dprintk(DEBUG_ERROR, "No fid for the directory #%lu\n", - dir->i_ino); - return -EBADF; + inode = new_inode(sb); + if (!inode) { + pr_warn("%s (%d): Problem allocating inode\n", + __func__, task_pid_nr(current)); + return ERR_PTR(-ENOMEM); } - - if (file_dentry->d_inode) { - dprintk(DEBUG_ERROR, - "Odd. There is an inode for dir %lu, name :%s:\n", - dir->i_ino, file_dentry->d_name.name); - return -EEXIST; + err = v9fs_init_inode(v9ses, inode, mode, rdev); + if (err) { + iput(inode); + return ERR_PTR(err); } + return inode; +} - newfid = v9fs_get_idpool(&v9ses->fidpool); - if (newfid < 0) { +/* +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 -ENOSPC; + return ERR_PTR(-ENOSPC); } - result = v9fs_t_walk(v9ses, dirfidnum, newfid, NULL, &fcall); - if (result < 0) { - PRINT_FCALL_ERROR("clone error", fcall); - v9fs_put_idpool(newfid, &v9ses->fidpool); - newfid = -1; - goto CleanUpFid; + 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; + } - result = v9fs_t_create(v9ses, newfid, (char *)file_dentry->d_name.name, - perm, open_mode, &fcall); - if (result < 0) { - PRINT_FCALL_ERROR("create fails", fcall); - goto CleanUpFid; + err = v9fs_fid_insert(ret, dentry); + if (err < 0) { + v9fs_fid_destroy(ret); + goto clunk_fid; } - iounit = fcall->params.rcreate.iounit; - qid = fcall->params.rcreate.qid; + return ret; + +clunk_fid: + v9fs_t_clunk(v9ses, nfid); + +error: kfree(fcall); - fcall = NULL; + return ERR_PTR(err); +} +*/ - if (!(perm&V9FS_DMDIR)) { - fid = v9fs_fid_create(file_dentry, v9ses, newfid, 1); - dprintk(DEBUG_VFS, "fid %p %d\n", fid, fid->fidcreate); - if (!fid) { - result = -ENOMEM; - goto CleanUpFid; - } - fid->qid = qid; - fid->iounit = iounit; - } else { - err = v9fs_t_clunk(v9ses, newfid); - newfid = -1; - if (err < 0) - dprintk(DEBUG_ERROR, "clunk for mkdir failed: %d\n", err); - } +/** + * v9fs_clear_inode - release an inode + * @inode: inode to release + * + */ +void v9fs_evict_inode(struct inode *inode) +{ + struct v9fs_inode *v9inode = V9FS_I(inode); - /* walk to the newly created file and put the fid in the dentry */ - wfidno = v9fs_get_idpool(&v9ses->fidpool); - if (wfidno < 0) { - eprintk(KERN_WARNING, "no free fids available\n"); - return -ENOSPC; - } + truncate_inode_pages_final(inode->i_mapping); + clear_inode(inode); + filemap_fdatawrite(inode->i_mapping); - result = v9fs_t_walk(v9ses, dirfidnum, wfidno, - (char *) file_dentry->d_name.name, &fcall); - if (result < 0) { - PRINT_FCALL_ERROR("clone error", fcall); - v9fs_put_idpool(wfidno, &v9ses->fidpool); - wfidno = -1; - goto CleanUpFid; + v9fs_cache_inode_put_cookie(inode); + /* clunk the fid stashed in writeback_fid */ + if (v9inode->writeback_fid) { + p9_client_clunk(v9inode->writeback_fid); + v9inode->writeback_fid = NULL; } - kfree(fcall); - fcall = NULL; +} - if (!v9fs_fid_create(file_dentry, v9ses, wfidno, 0)) { - v9fs_put_idpool(wfidno, &v9ses->fidpool); +static int v9fs_test_inode(struct inode *inode, void *data) +{ + int umode; + dev_t rdev; + struct v9fs_inode *v9inode = V9FS_I(inode); + struct p9_wstat *st = (struct p9_wstat *)data; + struct v9fs_session_info *v9ses = v9fs_inode2v9ses(inode); + + umode = p9mode2unixmode(v9ses, st, &rdev); + /* don't match inode of different type */ + if ((inode->i_mode & S_IFMT) != (umode & S_IFMT)) + return 0; - goto CleanUpFid; - } + /* compare qid details */ + if (memcmp(&v9inode->qid.version, + &st->qid.version, sizeof(v9inode->qid.version))) + return 0; - if ((perm & V9FS_DMSYMLINK) || (perm & V9FS_DMLINK) || - (perm & V9FS_DMNAMEDPIPE) || (perm & V9FS_DMSOCKET) || - (perm & V9FS_DMDEVICE)) + if (v9inode->qid.type != st->qid.type) return 0; + return 1; +} - result = v9fs_t_stat(v9ses, wfidno, &fcall); - if (result < 0) { - PRINT_FCALL_ERROR("stat error", fcall); - goto CleanUpFid; - } +static int v9fs_test_new_inode(struct inode *inode, void *data) +{ + return 0; +} + +static int v9fs_set_inode(struct inode *inode, void *data) +{ + struct v9fs_inode *v9inode = V9FS_I(inode); + struct p9_wstat *st = (struct p9_wstat *)data; + memcpy(&v9inode->qid, &st->qid, sizeof(st->qid)); + return 0; +} - file_inode = v9fs_get_inode(sb, - p9mode2unixmode(v9ses, fcall->params.rstat.stat.mode)); +static struct inode *v9fs_qid_iget(struct super_block *sb, + struct p9_qid *qid, + struct p9_wstat *st, + int new) +{ + dev_t rdev; + int retval; + umode_t umode; + unsigned long i_ino; + struct inode *inode; + struct v9fs_session_info *v9ses = sb->s_fs_info; + int (*test)(struct inode *, void *); - if ((!file_inode) || IS_ERR(file_inode)) { - dprintk(DEBUG_ERROR, "create inode failed\n"); - result = -EBADF; - goto CleanUpFid; - } + if (new) + test = v9fs_test_new_inode; + else + test = v9fs_test_inode; - v9fs_stat2inode(&fcall->params.rstat.stat, file_inode, sb); - kfree(fcall); - fcall = NULL; - file_dentry->d_op = &v9fs_dentry_operations; - d_instantiate(file_dentry, file_inode); + i_ino = v9fs_qid2ino(qid); + inode = iget5_locked(sb, i_ino, test, v9fs_set_inode, st); + if (!inode) + return ERR_PTR(-ENOMEM); + if (!(inode->i_state & I_NEW)) + return inode; + /* + * initialize the inode with the stat info + * FIXME!! we may need support for stale inodes + * later. + */ + inode->i_ino = i_ino; + umode = p9mode2unixmode(v9ses, st, &rdev); + retval = v9fs_init_inode(v9ses, inode, umode, rdev); + if (retval) + goto error; + + v9fs_stat2inode(st, inode, sb); + v9fs_cache_inode_get_cookie(inode); + unlock_new_inode(inode); + return inode; +error: + unlock_new_inode(inode); + iput(inode); + return ERR_PTR(retval); - return 0; +} - CleanUpFid: - kfree(fcall); - fcall = NULL; +struct inode * +v9fs_inode_from_fid(struct v9fs_session_info *v9ses, struct p9_fid *fid, + struct super_block *sb, int new) +{ + struct p9_wstat *st; + struct inode *inode = NULL; - if (newfid >= 0) { - err = v9fs_t_clunk(v9ses, newfid); - if (err < 0) - dprintk(DEBUG_ERROR, "clunk failed: %d\n", err); - } - if (wfidno >= 0) { - err = v9fs_t_clunk(v9ses, wfidno); - if (err < 0) - dprintk(DEBUG_ERROR, "clunk failed: %d\n", err); - } - return result; + st = p9_client_stat(fid); + if (IS_ERR(st)) + return ERR_CAST(st); + + inode = v9fs_qid_iget(sb, &st->qid, st, new); + p9stat_free(st); + kfree(st); + return inode; +} + +/** + * v9fs_at_to_dotl_flags- convert Linux specific AT flags to + * plan 9 AT flag. + * @flags: flags to convert + */ +static int v9fs_at_to_dotl_flags(int flags) +{ + int rflags = 0; + if (flags & AT_REMOVEDIR) + rflags |= P9_DOTL_AT_REMOVEDIR; + return rflags; } /** * 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 + * @dentry: dentry that is being deleted + * @flags: removing a directory * */ -static int v9fs_remove(struct inode *dir, struct dentry *file, int rmdir) +static int v9fs_remove(struct inode *dir, struct dentry *dentry, int flags) { - struct v9fs_fcall *fcall = NULL; - struct super_block *sb = NULL; - struct v9fs_session_info *v9ses = NULL; - struct v9fs_fid *v9fid = NULL; - struct inode *file_inode = NULL; - int fid = -1; - int result = 0; - - dprintk(DEBUG_VFS, "inode: %p dentry: %p rmdir: %d\n", dir, file, - rmdir); - - file_inode = file->d_inode; - sb = file_inode->i_sb; - v9ses = v9fs_inode2v9ses(file_inode); - v9fid = v9fs_fid_lookup(file); - - if (!v9fid) { - dprintk(DEBUG_ERROR, - "no v9fs_fid\n"); - return -EBADF; + struct inode *inode; + int retval = -EOPNOTSUPP; + struct p9_fid *v9fid, *dfid; + struct v9fs_session_info *v9ses; + + p9_debug(P9_DEBUG_VFS, "inode: %p dentry: %p rmdir: %x\n", + dir, dentry, flags); + + v9ses = v9fs_inode2v9ses(dir); + inode = dentry->d_inode; + dfid = v9fs_fid_lookup(dentry->d_parent); + if (IS_ERR(dfid)) { + retval = PTR_ERR(dfid); + p9_debug(P9_DEBUG_VFS, "fid lookup failed %d\n", retval); + return retval; + } + if (v9fs_proto_dotl(v9ses)) + retval = p9_client_unlinkat(dfid, dentry->d_name.name, + v9fs_at_to_dotl_flags(flags)); + if (retval == -EOPNOTSUPP) { + /* Try the one based on path */ + v9fid = v9fs_fid_clone(dentry); + if (IS_ERR(v9fid)) + return PTR_ERR(v9fid); + retval = p9_client_remove(v9fid); } + if (!retval) { + /* + * directories on unlink should have zero + * link count + */ + if (flags & AT_REMOVEDIR) { + clear_nlink(inode); + drop_nlink(dir); + } else + drop_nlink(inode); - fid = v9fid->fid; - if (fid < 0) { - dprintk(DEBUG_ERROR, "inode #%lu, no fid!\n", - file_inode->i_ino); - return -EBADF; + v9fs_invalidate_inode_attr(inode); + v9fs_invalidate_inode_attr(dir); } + return retval; +} - result = v9fs_t_remove(v9ses, fid, &fcall); - if (result < 0) { - PRINT_FCALL_ERROR("remove fails", fcall); - } else { - v9fs_put_idpool(fid, &v9ses->fidpool); - v9fs_fid_destroy(v9fid); +/** + * v9fs_create - Create a file + * @v9ses: session information + * @dir: directory that dentry is being created in + * @dentry: dentry that is being created + * @extension: 9p2000.u extension string to support devices, etc. + * @perm: create permissions + * @mode: open mode + * + */ +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_debug(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_lookup(dentry->d_parent); + if (IS_ERR(dfid)) { + err = PTR_ERR(dfid); + p9_debug(P9_DEBUG_VFS, "fid lookup failed %d\n", err); + return ERR_PTR(err); } - kfree(fcall); - return result; + /* clone a fid to use for creation */ + ofid = p9_client_walk(dfid, 0, NULL, 1); + if (IS_ERR(ofid)) { + err = PTR_ERR(ofid); + p9_debug(P9_DEBUG_VFS, "p9_client_walk failed %d\n", err); + return ERR_PTR(err); + } + + err = p9_client_fcreate(ofid, name, perm, mode, extension); + if (err < 0) { + p9_debug(P9_DEBUG_VFS, "p9_client_fcreate failed %d\n", err); + goto error; + } + + if (!(perm & P9_DMLINK)) { + /* now walk from the parent so we can get unopened fid */ + fid = p9_client_walk(dfid, 1, &name, 1); + if (IS_ERR(fid)) { + err = PTR_ERR(fid); + p9_debug(P9_DEBUG_VFS, + "p9_client_walk failed %d\n", err); + fid = NULL; + goto error; + } + /* + * instantiate inode and assign the unopened fid to the dentry + */ + inode = v9fs_get_new_inode_from_fid(v9ses, fid, dir->i_sb); + if (IS_ERR(inode)) { + err = PTR_ERR(inode); + p9_debug(P9_DEBUG_VFS, + "inode creation failed %d\n", err); + goto error; + } + v9fs_fid_add(dentry, fid); + d_instantiate(dentry, inode); + } + return ofid; +error: + if (ofid) + p9_client_clunk(ofid); + + if (fid) + p9_client_clunk(fid); + + return ERR_PTR(err); } /** - * v9fs_vfs_create - VFS hook to create files - * @inode: directory inode that is being deleted + * v9fs_vfs_create - VFS hook to create a regular file + * + * open(.., O_CREAT) is handled in v9fs_vfs_atomic_open(). This is only called + * for mknod(2). + * + * @dir: directory inode that is being created * @dentry: dentry that is being deleted - * @perm: create permissions - * @nd: path information + * @mode: create permissions * */ static int -v9fs_vfs_create(struct inode *inode, struct dentry *dentry, int perm, - struct nameidata *nd) +v9fs_vfs_create(struct inode *dir, struct dentry *dentry, umode_t mode, + bool excl) { - return v9fs_create(inode, dentry, perm, O_RDWR); + struct v9fs_session_info *v9ses = v9fs_inode2v9ses(dir); + u32 perm = unixmode2p9mode(v9ses, mode); + struct p9_fid *fid; + + /* P9_OEXCL? */ + fid = v9fs_create(v9ses, dir, dentry, NULL, perm, P9_ORDWR); + if (IS_ERR(fid)) + return PTR_ERR(fid); + + v9fs_invalidate_inode_attr(dir); + p9_client_clunk(fid); + + return 0; } /** * v9fs_vfs_mkdir - VFS mkdir hook to create a directory - * @inode: inode that is being unlinked + * @dir: inode that is being unlinked * @dentry: dentry that is being unlinked * @mode: mode for new directory * */ -static int v9fs_vfs_mkdir(struct inode *inode, struct dentry *dentry, int mode) +static int v9fs_vfs_mkdir(struct inode *dir, struct dentry *dentry, umode_t mode) { - return v9fs_create(inode, dentry, mode | S_IFDIR, O_RDONLY); + int err; + u32 perm; + struct p9_fid *fid; + struct v9fs_session_info *v9ses; + + p9_debug(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; + } else { + inc_nlink(dir); + v9fs_invalidate_inode_attr(dir); + } + + 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 + * @flags: lookup flags (unused) * */ -static struct dentry *v9fs_vfs_lookup(struct inode *dir, struct dentry *dentry, - struct nameidata *nameidata) +struct dentry *v9fs_vfs_lookup(struct inode *dir, struct dentry *dentry, + unsigned int flags) { - struct super_block *sb; + struct dentry *res; struct v9fs_session_info *v9ses; - struct v9fs_fid *dirfid; - struct v9fs_fid *fid; + struct p9_fid *dfid, *fid; struct inode *inode; - struct v9fs_fcall *fcall = NULL; - int dirfidnum = -1; - int newfid = -1; - int result = 0; - - dprintk(DEBUG_VFS, "dir: %p dentry: (%s) %p nameidata: %p\n", - dir, dentry->d_iname, dentry, nameidata); - - sb = dir->i_sb; - v9ses = v9fs_inode2v9ses(dir); - dirfid = v9fs_fid_lookup(dentry->d_parent); - - if (!dirfid) { - dprintk(DEBUG_ERROR, "no dirfid\n"); - return ERR_PTR(-EINVAL); - } - - dirfidnum = dirfid->fid; + char *name; - if (dirfidnum < 0) { - dprintk(DEBUG_ERROR, "no dirfid for inode %p, #%lu\n", - dir, dir->i_ino); - return ERR_PTR(-EBADF); - } + p9_debug(P9_DEBUG_VFS, "dir: %p dentry: (%s) %p flags: %x\n", + dir, dentry->d_name.name, dentry, flags); - newfid = v9fs_get_idpool(&v9ses->fidpool); - if (newfid < 0) { - eprintk(KERN_WARNING, "newfid fails!\n"); - return ERR_PTR(-ENOSPC); - } + if (dentry->d_name.len > NAME_MAX) + return ERR_PTR(-ENAMETOOLONG); - result = - v9fs_t_walk(v9ses, dirfidnum, newfid, (char *)dentry->d_name.name, - NULL); - if (result < 0) { - v9fs_put_idpool(newfid, &v9ses->fidpool); - if (result == -ENOENT) { + v9ses = v9fs_inode2v9ses(dir); + /* We can walk d_parent because we hold the dir->i_mutex */ + 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)) { + if (fid == ERR_PTR(-ENOENT)) { d_add(dentry, NULL); - dprintk(DEBUG_VFS, - "Return negative dentry %p count %d\n", - dentry, atomic_read(&dentry->d_count)); return NULL; } - dprintk(DEBUG_ERROR, "walk error:%d\n", result); - goto FreeFcall; + return ERR_CAST(fid); } - - result = v9fs_t_stat(v9ses, newfid, &fcall); - if (result < 0) { - dprintk(DEBUG_ERROR, "stat error\n"); - goto FreeFcall; + /* + * Make sure we don't use a wrong inode due to parallel + * unlink. For cached mode create calls request for new + * inode. But with cache disabled, lookup should do this. + */ + if (v9ses->cache == CACHE_LOOSE || v9ses->cache == CACHE_FSCACHE) + inode = v9fs_get_inode_from_fid(v9ses, fid, dir->i_sb); + else + inode = v9fs_get_new_inode_from_fid(v9ses, fid, dir->i_sb); + if (IS_ERR(inode)) { + p9_client_clunk(fid); + return ERR_CAST(inode); } + /* + * If we had a rename on the server and a parallel lookup + * for the new name, then make sure we instantiate with + * the new name. ie look up for a/b, while on server somebody + * moved b under k and client parallely did a lookup for + * k/b. + */ + res = d_materialise_unique(dentry, inode); + if (!res) + v9fs_fid_add(dentry, fid); + else if (!IS_ERR(res)) + v9fs_fid_add(res, fid); + else + p9_client_clunk(fid); + return res; +} - inode = v9fs_get_inode(sb, p9mode2unixmode(v9ses, - fcall->params.rstat.stat.mode)); - - if (IS_ERR(inode) && (PTR_ERR(inode) == -ENOSPC)) { - eprintk(KERN_WARNING, "inode alloc failes, returns %ld\n", - PTR_ERR(inode)); - - result = -ENOSPC; - goto FreeFcall; - } +static int +v9fs_vfs_atomic_open(struct inode *dir, struct dentry *dentry, + struct file *file, unsigned flags, umode_t mode, + int *opened) +{ + int err; + u32 perm; + struct v9fs_inode *v9inode; + struct v9fs_session_info *v9ses; + struct p9_fid *fid, *inode_fid; + struct dentry *res = NULL; - inode->i_ino = v9fs_qid2ino(&fcall->params.rstat.stat.qid); + if (d_unhashed(dentry)) { + res = v9fs_vfs_lookup(dir, dentry, 0); + if (IS_ERR(res)) + return PTR_ERR(res); - fid = v9fs_fid_create(dentry, v9ses, newfid, 0); - if (fid == NULL) { - dprintk(DEBUG_ERROR, "couldn't insert\n"); - result = -ENOMEM; - goto FreeFcall; + if (res) + dentry = res; } - fid->qid = fcall->params.rstat.stat.qid; + /* Only creates */ + if (!(flags & O_CREAT) || dentry->d_inode) + return finish_no_open(file, res); - dentry->d_op = &v9fs_dentry_operations; - v9fs_stat2inode(&fcall->params.rstat.stat, inode, inode->i_sb); + err = 0; - d_add(dentry, inode); - kfree(fcall); + v9ses = v9fs_inode2v9ses(dir); + perm = unixmode2p9mode(v9ses, mode); + fid = v9fs_create(v9ses, dir, dentry, NULL, perm, + v9fs_uflags2omode(flags, + v9fs_proto_dotu(v9ses))); + if (IS_ERR(fid)) { + err = PTR_ERR(fid); + fid = NULL; + goto error; + } - return NULL; + v9fs_invalidate_inode_attr(dir); + v9inode = V9FS_I(dentry->d_inode); + mutex_lock(&v9inode->v_mutex); + if ((v9ses->cache == CACHE_LOOSE || v9ses->cache == CACHE_FSCACHE) && + !v9inode->writeback_fid && + ((flags & O_ACCMODE) != O_RDONLY)) { + /* + * clone a fid and add it to writeback_fid + * we do it during open time instead of + * page dirty time via write_begin/page_mkwrite + * because we want write after unlink usecase + * to work. + */ + inode_fid = v9fs_writeback_fid(dentry); + if (IS_ERR(inode_fid)) { + err = PTR_ERR(inode_fid); + mutex_unlock(&v9inode->v_mutex); + goto error; + } + v9inode->writeback_fid = (void *) inode_fid; + } + mutex_unlock(&v9inode->v_mutex); + err = finish_open(file, dentry, generic_file_open, opened); + if (err) + goto error; + + file->private_data = fid; + if (v9ses->cache == CACHE_LOOSE || v9ses->cache == CACHE_FSCACHE) + v9fs_cache_inode_set_cookie(dentry->d_inode, file); + + *opened |= FILE_CREATED; +out: + dput(res); + return err; - FreeFcall: - kfree(fcall); - return ERR_PTR(result); +error: + if (fid) + p9_client_clunk(fid); + goto out; } /** @@ -580,7 +928,7 @@ static struct dentry *v9fs_vfs_lookup(struct inode *dir, struct dentry *dentry, * */ -static int v9fs_vfs_unlink(struct inode *i, struct dentry *d) +int v9fs_vfs_unlink(struct inode *i, struct dentry *d) { return v9fs_remove(i, d, 0); } @@ -592,9 +940,9 @@ static int v9fs_vfs_unlink(struct inode *i, struct dentry *d) * */ -static int v9fs_vfs_rmdir(struct inode *i, struct dentry *d) +int v9fs_vfs_rmdir(struct inode *i, struct dentry *d) { - return v9fs_remove(i, d, 1); + return v9fs_remove(i, d, AT_REMOVEDIR); } /** @@ -606,68 +954,99 @@ static int v9fs_vfs_rmdir(struct inode *i, struct dentry *d) * */ -static int +int v9fs_vfs_rename(struct inode *old_dir, struct dentry *old_dentry, struct inode *new_dir, struct dentry *new_dentry) { - struct inode *old_inode = old_dentry->d_inode; - struct v9fs_session_info *v9ses = v9fs_inode2v9ses(old_inode); - struct v9fs_fid *oldfid = v9fs_fid_lookup(old_dentry); - struct v9fs_fid *olddirfid = - v9fs_fid_lookup(old_dentry->d_parent); - struct v9fs_fid *newdirfid = - v9fs_fid_lookup(new_dentry->d_parent); - struct v9fs_wstat wstat; - struct v9fs_fcall *fcall = NULL; - int fid = -1; - int olddirfidnum = -1; - int newdirfidnum = -1; - int retval = 0; - - dprintk(DEBUG_VFS, "\n"); - - if ((!oldfid) || (!olddirfid) || (!newdirfid)) { - dprintk(DEBUG_ERROR, "problem with arguments\n"); - return -EBADF; - } + int retval; + struct inode *old_inode; + struct inode *new_inode; + struct v9fs_session_info *v9ses; + struct p9_fid *oldfid; + struct p9_fid *olddirfid; + struct p9_fid *newdirfid; + struct p9_wstat wstat; + + p9_debug(P9_DEBUG_VFS, "\n"); + retval = 0; + old_inode = old_dentry->d_inode; + new_inode = new_dentry->d_inode; + v9ses = v9fs_inode2v9ses(old_inode); + oldfid = v9fs_fid_lookup(old_dentry); + if (IS_ERR(oldfid)) + return PTR_ERR(oldfid); - /* 9P can only handle file rename in the same directory */ - if (memcmp(&olddirfid->qid, &newdirfid->qid, sizeof(newdirfid->qid))) { - dprintk(DEBUG_ERROR, "old dir and new dir are different\n"); - retval = -EPERM; - goto FreeFcallnBail; + olddirfid = v9fs_fid_clone(old_dentry->d_parent); + if (IS_ERR(olddirfid)) { + retval = PTR_ERR(olddirfid); + goto done; } - fid = oldfid->fid; - olddirfidnum = olddirfid->fid; - newdirfidnum = newdirfid->fid; - - if (fid < 0) { - dprintk(DEBUG_ERROR, "no fid for old file #%lu\n", - old_inode->i_ino); - retval = -EBADF; - goto FreeFcallnBail; + newdirfid = v9fs_fid_clone(new_dentry->d_parent); + if (IS_ERR(newdirfid)) { + retval = PTR_ERR(newdirfid); + goto clunk_olddir; } + down_write(&v9ses->rename_sem); + if (v9fs_proto_dotl(v9ses)) { + retval = p9_client_renameat(olddirfid, old_dentry->d_name.name, + newdirfid, new_dentry->d_name.name); + if (retval == -EOPNOTSUPP) + retval = p9_client_rename(oldfid, newdirfid, + new_dentry->d_name.name); + if (retval != -EOPNOTSUPP) + goto clunk_newdir; + } + if (old_dentry->d_parent != new_dentry->d_parent) { + /* + * 9P .u can only handle file rename in the same directory + */ + + p9_debug(P9_DEBUG_ERROR, "old dir and new dir are different\n"); + retval = -EXDEV; + goto clunk_newdir; + } v9fs_blank_wstat(&wstat); - wstat.muid = v9ses->name; + wstat.muid = v9ses->uname; wstat.name = (char *) new_dentry->d_name.name; + retval = p9_client_wstat(oldfid, &wstat); - retval = v9fs_t_wstat(v9ses, fid, &wstat, &fcall); +clunk_newdir: + if (!retval) { + if (new_inode) { + if (S_ISDIR(new_inode->i_mode)) + clear_nlink(new_inode); + else + drop_nlink(new_inode); + } + if (S_ISDIR(old_inode->i_mode)) { + if (!new_inode) + inc_nlink(new_dir); + drop_nlink(old_dir); + } + v9fs_invalidate_inode_attr(old_inode); + v9fs_invalidate_inode_attr(old_dir); + v9fs_invalidate_inode_attr(new_dir); - FreeFcallnBail: - if (retval < 0) - PRINT_FCALL_ERROR("wstat error", fcall); + /* successful rename */ + d_move(old_dentry, new_dentry); + } + up_write(&v9ses->rename_sem); + p9_client_clunk(newdirfid); - kfree(fcall); +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 + * @mnt: mount information + * @dentry: file to get attributes on + * @stat: metadata structure to populate * */ @@ -675,30 +1054,30 @@ static int v9fs_vfs_getattr(struct vfsmount *mnt, struct dentry *dentry, struct kstat *stat) { - struct v9fs_fcall *fcall = NULL; - struct v9fs_session_info *v9ses = v9fs_inode2v9ses(dentry->d_inode); - struct v9fs_fid *fid = v9fs_fid_lookup(dentry); - int err = -EPERM; - - dprintk(DEBUG_VFS, "dentry: %p\n", dentry); - if (!fid) { - dprintk(DEBUG_ERROR, - "couldn't find fid associated with dentry\n"); - return -EBADF; - } - - err = v9fs_t_stat(v9ses, fid->fid, &fcall); + struct v9fs_session_info *v9ses; + struct p9_fid *fid; + struct p9_wstat *st; - if (err < 0) - dprintk(DEBUG_ERROR, "stat error\n"); - else { - v9fs_stat2inode(&fcall->params.rstat.stat, dentry->d_inode, - dentry->d_inode->i_sb); + p9_debug(P9_DEBUG_VFS, "dentry: %p\n", dentry); + v9ses = v9fs_dentry2v9ses(dentry); + if (v9ses->cache == CACHE_LOOSE || v9ses->cache == CACHE_FSCACHE) { generic_fillattr(dentry->d_inode, stat); + return 0; } + fid = v9fs_fid_lookup(dentry); + if (IS_ERR(fid)) + return PTR_ERR(fid); - kfree(fcall); - return err; + 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); + + p9stat_free(st); + kfree(st); + return 0; } /** @@ -710,19 +1089,21 @@ v9fs_vfs_getattr(struct vfsmount *mnt, struct dentry *dentry, static int v9fs_vfs_setattr(struct dentry *dentry, struct iattr *iattr) { - struct v9fs_session_info *v9ses = v9fs_inode2v9ses(dentry->d_inode); - struct v9fs_fid *fid = v9fs_fid_lookup(dentry); - struct v9fs_fcall *fcall = NULL; - struct v9fs_wstat wstat; - int res = -EPERM; + int retval; + struct v9fs_session_info *v9ses; + struct p9_fid *fid; + struct p9_wstat wstat; - dprintk(DEBUG_VFS, "\n"); + p9_debug(P9_DEBUG_VFS, "\n"); + retval = inode_change_ok(dentry->d_inode, iattr); + if (retval) + return retval; - if (!fid) { - dprintk(DEBUG_ERROR, - "Couldn't find fid associated with dentry\n"); - return -EBADF; - } + retval = -EPERM; + v9ses = v9fs_dentry2v9ses(dentry); + fid = v9fs_fid_lookup(dentry); + if(IS_ERR(fid)) + return PTR_ERR(fid); v9fs_blank_wstat(&wstat); if (iattr->ia_valid & ATTR_MODE) @@ -737,7 +1118,7 @@ static int v9fs_vfs_setattr(struct dentry *dentry, struct iattr *iattr) if (iattr->ia_valid & ATTR_SIZE) wstat.length = iattr->ia_size; - if (v9ses->extended) { + if (v9fs_proto_dotu(v9ses)) { if (iattr->ia_valid & ATTR_UID) wstat.n_uid = iattr->ia_uid; @@ -745,16 +1126,23 @@ static int v9fs_vfs_setattr(struct dentry *dentry, struct iattr *iattr) wstat.n_gid = iattr->ia_gid; } - res = v9fs_t_wstat(v9ses, fid->fid, &wstat, &fcall); + /* Write all dirty data */ + if (S_ISREG(dentry->d_inode->i_mode)) + filemap_write_and_wait(dentry->d_inode->i_mapping); + + retval = p9_client_wstat(fid, &wstat); + if (retval < 0) + return retval; - if (res < 0) - PRINT_FCALL_ERROR("wstat error", fcall); + if ((iattr->ia_valid & ATTR_SIZE) && + iattr->ia_size != i_size_read(dentry->d_inode)) + truncate_setsize(dentry->d_inode, iattr->ia_size); - kfree(fcall); - if (res >= 0) - res = inode_setattr(dentry->d_inode, iattr); + v9fs_invalidate_inode_attr(dentry->d_inode); - return res; + setattr_copy(dentry->d_inode, iattr); + mark_inode_dirty(dentry->d_inode); + return 0; } /** @@ -766,59 +1154,53 @@ static int v9fs_vfs_setattr(struct dentry *dentry, struct iattr *iattr) */ void -v9fs_stat2inode(struct v9fs_stat *stat, struct inode *inode, +v9fs_stat2inode(struct p9_wstat *stat, struct inode *inode, struct super_block *sb) { - int n; + umode_t mode; char ext[32]; + char tag_name[14]; + unsigned int i_nlink; struct v9fs_session_info *v9ses = sb->s_fs_info; + struct v9fs_inode *v9inode = V9FS_I(inode); - inode->i_nlink = 1; + set_nlink(inode, 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->uid; - inode->i_gid = v9ses->gid; + inode->i_uid = v9ses->dfltuid; + inode->i_gid = v9ses->dfltgid; - if (v9ses->extended) { + if (v9fs_proto_dotu(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; - - n = stat->extension.len; - if (n > sizeof(ext)-1) - n = sizeof(ext)-1; - memmove(ext, stat->extension.str, n); - ext[n] = 0; - 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: - dprintk(DEBUG_ERROR, "Unknown special type %c (%.*s)\n", - type, stat->extension.len, stat->extension.str); - }; - inode->i_rdev = MKDEV(major, minor); - } else - inode->i_rdev = 0; - - inode->i_size = stat->length; - - inode->i_blksize = sb->s_blocksize; - inode->i_blocks = - (inode->i_size + inode->i_blksize - 1) >> sb->s_blocksize_bits; + if ((S_ISREG(inode->i_mode)) || (S_ISDIR(inode->i_mode))) { + if (v9fs_proto_dotu(v9ses) && (stat->extension[0] != '\0')) { + /* + * Hadlink support got added later to + * to the .u extension. So there can be + * server out there that doesn't support + * this even with .u extension. So check + * for non NULL stat->extension + */ + strlcpy(ext, stat->extension, sizeof(ext)); + /* HARDLINKCOUNT %u */ + sscanf(ext, "%13s %u", tag_name, &i_nlink); + if (!strncmp(tag_name, "HARDLINKCOUNT", 13)) + set_nlink(inode, i_nlink); + } + } + mode = p9mode2perm(v9ses, stat); + mode |= inode->i_mode & ~S_IALLUGO; + inode->i_mode = mode; + i_size_write(inode, stat->length); + + /* not real number of blocks, but 512 byte ones ... */ + inode->i_blocks = (i_size_read(inode) + 512 - 1) >> 9; + v9inode->cache_validity &= ~V9FS_INO_INVALID_ATTR; } /** @@ -828,7 +1210,7 @@ v9fs_stat2inode(struct v9fs_stat *stat, struct inode *inode, * BUG: potential for inode number collisions? */ -ino_t v9fs_qid2ino(struct v9fs_qid *qid) +ino_t v9fs_qid2ino(struct p9_qid *qid) { u64 path = qid->path + 2; ino_t i = 0; @@ -851,86 +1233,41 @@ ino_t v9fs_qid2ino(struct v9fs_qid *qid) static int v9fs_readlink(struct dentry *dentry, char *buffer, int buflen) { - int retval = -EPERM; - - struct v9fs_fcall *fcall = NULL; - struct v9fs_session_info *v9ses = v9fs_inode2v9ses(dentry->d_inode); - struct v9fs_fid *fid = v9fs_fid_lookup(dentry); - - if (!fid) { - dprintk(DEBUG_ERROR, "could not resolve fid from dentry\n"); - retval = -EBADF; - goto FreeFcall; - } + int retval; - if (!v9ses->extended) { - retval = -EBADF; - dprintk(DEBUG_ERROR, "not extended\n"); - goto FreeFcall; - } + struct v9fs_session_info *v9ses; + struct p9_fid *fid; + struct p9_wstat *st; - dprintk(DEBUG_VFS, " %s\n", dentry->d_name.name); - retval = v9fs_t_stat(v9ses, fid->fid, &fcall); + p9_debug(P9_DEBUG_VFS, " %s\n", dentry->d_name.name); + retval = -EPERM; + v9ses = v9fs_dentry2v9ses(dentry); + fid = v9fs_fid_lookup(dentry); + if (IS_ERR(fid)) + return PTR_ERR(fid); - if (retval < 0) { - dprintk(DEBUG_ERROR, "stat error\n"); - goto FreeFcall; - } + if (!v9fs_proto_dotu(v9ses)) + return -EBADF; - if (!fcall) - return -EIO; + st = p9_client_stat(fid); + if (IS_ERR(st)) + return PTR_ERR(st); - if (!(fcall->params.rstat.stat.mode & V9FS_DMSYMLINK)) { + if (!(st->mode & P9_DMSYMLINK)) { retval = -EINVAL; - goto FreeFcall; + goto done; } /* copy extension buffer into buffer */ - if (fcall->params.rstat.stat.extension.len+1 < buflen) - buflen = fcall->params.rstat.stat.extension.len + 1; - - memcpy(buffer, fcall->params.rstat.stat.extension.str, buflen - 1); - buffer[buflen-1] = 0; - - retval = buflen; - - FreeFcall: - kfree(fcall); - - return retval; -} - -/** - * v9fs_vfs_readlink - read a symlink's location - * @dentry: dentry for symlink - * @buf: 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 (buflen > PATH_MAX) - buflen = PATH_MAX; - - dprintk(DEBUG_VFS, " dentry: %s (%p)\n", dentry->d_iname, dentry); + retval = min(strlen(st->extension)+1, (size_t)buflen); + memcpy(buffer, st->extension, retval); - retval = v9fs_readlink(dentry, link, buflen); - - if (retval > 0) { - if ((ret = copy_to_user(buffer, link, retval)) != 0) { - dprintk(DEBUG_ERROR, "problem copying to user: %d\n", - ret); - retval = ret; - } - } + p9_debug(P9_DEBUG_VFS, "%s -> %s (%.*s)\n", + dentry->d_name.name, st->extension, buflen, buffer); - __putname(link); +done: + p9stat_free(st); + kfree(st); return retval; } @@ -946,7 +1283,7 @@ static void *v9fs_vfs_follow_link(struct dentry *dentry, struct nameidata *nd) int len = 0; char *link = __getname(); - dprintk(DEBUG_VFS, "%s n", dentry->d_name.name); + p9_debug(P9_DEBUG_VFS, "%s\n", dentry->d_name.name); if (!link) link = ERR_PTR(-ENOMEM); @@ -957,7 +1294,7 @@ static void *v9fs_vfs_follow_link(struct dentry *dentry, struct nameidata *nd) __putname(link); link = ERR_PTR(len); } else - link[len] = 0; + link[min(len, PATH_MAX-1)] = 0; } nd_set_link(nd, link); @@ -968,68 +1305,50 @@ static void *v9fs_vfs_follow_link(struct dentry *dentry, struct nameidata *nd) * 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) +void +v9fs_vfs_put_link(struct dentry *dentry, struct nameidata *nd, void *p) { char *s = nd_get_link(nd); - dprintk(DEBUG_VFS, " %s %s\n", dentry->d_name.name, s); + p9_debug(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 + * @perm: 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, const char *extension) { - int err, retval; + struct p9_fid *fid; struct v9fs_session_info *v9ses; - struct v9fs_fcall *fcall; - struct v9fs_fid *fid; - struct v9fs_wstat wstat; v9ses = v9fs_inode2v9ses(dir); - retval = -EPERM; - fcall = NULL; - - if (!v9ses->extended) { - dprintk(DEBUG_ERROR, "not extended\n"); - goto free_mem; - } - - /* issue a create */ - retval = v9fs_create(dir, dentry, mode, 0); - if (retval != 0) - goto free_mem; - - fid = v9fs_fid_get_created(dentry); - if (!fid) { - dprintk(DEBUG_ERROR, "couldn't resolve fid from dentry\n"); - goto free_mem; - } - - /* issue a Twstat */ - v9fs_blank_wstat(&wstat); - wstat.muid = v9ses->name; - wstat.extension = (char *) extension; - retval = v9fs_t_wstat(v9ses, fid->fid, &wstat, &fcall); - if (retval < 0) { - PRINT_FCALL_ERROR("wstat error", fcall); - goto free_mem; - } - - err = v9fs_t_clunk(v9ses, fid->fid); - if (err < 0) { - dprintk(DEBUG_ERROR, "clunk failed: %d\n", err); - goto free_mem; + if (!v9fs_proto_dotu(v9ses)) { + p9_debug(P9_DEBUG_ERROR, "not extended\n"); + return -EPERM; } - d_drop(dentry); /* FID - will this also clunk? */ + fid = v9fs_create(v9ses, dir, dentry, (char *) extension, perm, + P9_OREAD); + if (IS_ERR(fid)) + return PTR_ERR(fid); -free_mem: - kfree(fcall); - return retval; + v9fs_invalidate_inode_attr(dir); + p9_client_clunk(fid); + return 0; } /** @@ -1038,17 +1357,17 @@ free_mem: * @dentry: dentry for symlink * @symname: symlink data * - * See 9P2000.u RFC for more information + * See Also: 9P2000.u RFC for more information * */ static int v9fs_vfs_symlink(struct inode *dir, struct dentry *dentry, const char *symname) { - dprintk(DEBUG_VFS, " %lu,%s,%s\n", dir->i_ino, dentry->d_name.name, - symname); + p9_debug(P9_DEBUG_VFS, " %lu,%s,%s\n", + dir->i_ino, dentry->d_name.name, symname); - return v9fs_vfs_mkspecial(dir, dentry, S_IFLNK, symname); + return v9fs_vfs_mkspecial(dir, dentry, P9_DMSYMLINK, symname); } /** @@ -1059,32 +1378,36 @@ v9fs_vfs_symlink(struct inode *dir, struct dentry *dentry, const char *symname) * */ -/* XXX - lots of code dup'd from symlink and creates, - * figure out a better reuse strategy - */ - static int v9fs_vfs_link(struct dentry *old_dentry, struct inode *dir, struct dentry *dentry) { int retval; - struct v9fs_fid *oldfid; char *name; + struct p9_fid *oldfid; - dprintk(DEBUG_VFS, " %lu,%s,%s\n", dir->i_ino, dentry->d_name.name, - old_dentry->d_name.name); + p9_debug(P9_DEBUG_VFS, " %lu,%s,%s\n", + dir->i_ino, dentry->d_name.name, old_dentry->d_name.name); - oldfid = v9fs_fid_lookup(old_dentry); - if (!oldfid) { - dprintk(DEBUG_ERROR, "can't find oldfid\n"); - return -EPERM; - } + oldfid = v9fs_fid_clone(old_dentry); + if (IS_ERR(oldfid)) + return PTR_ERR(oldfid); name = __getname(); - sprintf(name, "hardlink(%d)\n", oldfid->fid); - retval = v9fs_vfs_mkspecial(dir, dentry, V9FS_DMLINK, name); - __putname(name); + 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); + if (!retval) { + v9fs_refresh_inode(oldfid, old_dentry->d_inode); + v9fs_invalidate_inode_attr(dir); + } +clunk_fid: + p9_client_clunk(oldfid); return retval; } @@ -1093,23 +1416,28 @@ v9fs_vfs_link(struct dentry *old_dentry, struct inode *dir, * @dir: inode destination for new link * @dentry: dentry for file * @mode: mode for creation - * @dev_t: device associated with special file + * @rdev: device associated with special file * */ static int -v9fs_vfs_mknod(struct inode *dir, struct dentry *dentry, int mode, dev_t rdev) +v9fs_vfs_mknod(struct inode *dir, struct dentry *dentry, umode_t mode, dev_t rdev) { + struct v9fs_session_info *v9ses = v9fs_inode2v9ses(dir); int retval; char *name; + u32 perm; - dprintk(DEBUG_VFS, " %lu,%s mode: %x MAJOR: %u MINOR: %u\n", dir->i_ino, - dentry->d_name.name, mode, MAJOR(rdev), MINOR(rdev)); + p9_debug(P9_DEBUG_VFS, " %lu,%s mode: %hx 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)); @@ -1117,20 +1445,59 @@ v9fs_vfs_mknod(struct inode *dir, struct dentry *dentry, int mode, dev_t rdev) sprintf(name, "c %u %u", MAJOR(rdev), MINOR(rdev)); else if (S_ISFIFO(mode)) *name = 0; + else if (S_ISSOCK(mode)) + *name = 0; else { __putname(name); return -EINVAL; } - retval = v9fs_vfs_mkspecial(dir, dentry, mode, name); + perm = unixmode2p9mode(v9ses, mode); + retval = v9fs_vfs_mkspecial(dir, dentry, perm, name); __putname(name); return retval; } -static struct inode_operations v9fs_dir_inode_operations_ext = { +int v9fs_refresh_inode(struct p9_fid *fid, struct inode *inode) +{ + int umode; + dev_t rdev; + loff_t i_size; + struct p9_wstat *st; + struct v9fs_session_info *v9ses; + + v9ses = v9fs_inode2v9ses(inode); + st = p9_client_stat(fid); + if (IS_ERR(st)) + return PTR_ERR(st); + /* + * Don't update inode if the file type is different + */ + umode = p9mode2unixmode(v9ses, st, &rdev); + if ((inode->i_mode & S_IFMT) != (umode & S_IFMT)) + goto out; + + spin_lock(&inode->i_lock); + /* + * We don't want to refresh inode->i_size, + * because we may have cached data + */ + i_size = inode->i_size; + v9fs_stat2inode(st, inode, inode->i_sb); + if (v9ses->cache == CACHE_LOOSE || v9ses->cache == CACHE_FSCACHE) + inode->i_size = i_size; + spin_unlock(&inode->i_lock); +out: + p9stat_free(st); + kfree(st); + return 0; +} + +static const struct inode_operations v9fs_dir_inode_operations_dotu = { .create = v9fs_vfs_create, .lookup = v9fs_vfs_lookup, + .atomic_open = v9fs_vfs_atomic_open, .symlink = v9fs_vfs_symlink, .link = v9fs_vfs_link, .unlink = v9fs_vfs_unlink, @@ -1138,14 +1505,14 @@ static struct inode_operations v9fs_dir_inode_operations_ext = { .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 struct inode_operations v9fs_dir_inode_operations = { +static const struct inode_operations v9fs_dir_inode_operations = { .create = v9fs_vfs_create, .lookup = v9fs_vfs_lookup, + .atomic_open = v9fs_vfs_atomic_open, .unlink = v9fs_vfs_unlink, .mkdir = v9fs_vfs_mkdir, .rmdir = v9fs_vfs_rmdir, @@ -1155,15 +1522,16 @@ static struct inode_operations v9fs_dir_inode_operations = { .setattr = v9fs_vfs_setattr, }; -static struct inode_operations v9fs_file_inode_operations = { +static const struct inode_operations v9fs_file_inode_operations = { .getattr = v9fs_vfs_getattr, .setattr = v9fs_vfs_setattr, }; -static struct inode_operations v9fs_symlink_inode_operations = { - .readlink = v9fs_vfs_readlink, +static const struct inode_operations v9fs_symlink_inode_operations = { + .readlink = generic_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_inode_dotl.c b/fs/9p/vfs_inode_dotl.c new file mode 100644 index 00000000000..1fa85aae24d --- /dev/null +++ b/fs/9p/vfs_inode_dotl.c @@ -0,0 +1,1016 @@ +/* + * linux/fs/9p/vfs_inode_dotl.c + * + * This file contains vfs inode ops for the 9P2000.L 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 <linux/slab.h> +#include <linux/xattr.h> +#include <linux/posix_acl.h> +#include <net/9p/9p.h> +#include <net/9p/client.h> + +#include "v9fs.h" +#include "v9fs_vfs.h" +#include "fid.h" +#include "cache.h" +#include "xattr.h" +#include "acl.h" + +static int +v9fs_vfs_mknod_dotl(struct inode *dir, struct dentry *dentry, umode_t omode, + dev_t rdev); + +/** + * v9fs_get_fsgid_for_create - Helper function to get the gid for creating a + * new file system object. This checks the S_ISGID to determine the owning + * group of the new file system object. + */ + +static kgid_t v9fs_get_fsgid_for_create(struct inode *dir_inode) +{ + BUG_ON(dir_inode == NULL); + + if (dir_inode->i_mode & S_ISGID) { + /* set_gid bit is set.*/ + return dir_inode->i_gid; + } + return current_fsgid(); +} + +static int v9fs_test_inode_dotl(struct inode *inode, void *data) +{ + struct v9fs_inode *v9inode = V9FS_I(inode); + struct p9_stat_dotl *st = (struct p9_stat_dotl *)data; + + /* don't match inode of different type */ + if ((inode->i_mode & S_IFMT) != (st->st_mode & S_IFMT)) + return 0; + + if (inode->i_generation != st->st_gen) + return 0; + + /* compare qid details */ + if (memcmp(&v9inode->qid.version, + &st->qid.version, sizeof(v9inode->qid.version))) + return 0; + + if (v9inode->qid.type != st->qid.type) + return 0; + return 1; +} + +/* Always get a new inode */ +static int v9fs_test_new_inode_dotl(struct inode *inode, void *data) +{ + return 0; +} + +static int v9fs_set_inode_dotl(struct inode *inode, void *data) +{ + struct v9fs_inode *v9inode = V9FS_I(inode); + struct p9_stat_dotl *st = (struct p9_stat_dotl *)data; + + memcpy(&v9inode->qid, &st->qid, sizeof(st->qid)); + inode->i_generation = st->st_gen; + return 0; +} + +static struct inode *v9fs_qid_iget_dotl(struct super_block *sb, + struct p9_qid *qid, + struct p9_fid *fid, + struct p9_stat_dotl *st, + int new) +{ + int retval; + unsigned long i_ino; + struct inode *inode; + struct v9fs_session_info *v9ses = sb->s_fs_info; + int (*test)(struct inode *, void *); + + if (new) + test = v9fs_test_new_inode_dotl; + else + test = v9fs_test_inode_dotl; + + i_ino = v9fs_qid2ino(qid); + inode = iget5_locked(sb, i_ino, test, v9fs_set_inode_dotl, st); + if (!inode) + return ERR_PTR(-ENOMEM); + if (!(inode->i_state & I_NEW)) + return inode; + /* + * initialize the inode with the stat info + * FIXME!! we may need support for stale inodes + * later. + */ + inode->i_ino = i_ino; + retval = v9fs_init_inode(v9ses, inode, + st->st_mode, new_decode_dev(st->st_rdev)); + if (retval) + goto error; + + v9fs_stat2inode_dotl(st, inode); + v9fs_cache_inode_get_cookie(inode); + retval = v9fs_get_acl(inode, fid); + if (retval) + goto error; + + unlock_new_inode(inode); + return inode; +error: + unlock_new_inode(inode); + iput(inode); + return ERR_PTR(retval); + +} + +struct inode * +v9fs_inode_from_fid_dotl(struct v9fs_session_info *v9ses, struct p9_fid *fid, + struct super_block *sb, int new) +{ + struct p9_stat_dotl *st; + struct inode *inode = NULL; + + st = p9_client_getattr_dotl(fid, P9_STATS_BASIC | P9_STATS_GEN); + if (IS_ERR(st)) + return ERR_CAST(st); + + inode = v9fs_qid_iget_dotl(sb, &st->qid, fid, st, new); + kfree(st); + return inode; +} + +struct dotl_openflag_map { + int open_flag; + int dotl_flag; +}; + +static int v9fs_mapped_dotl_flags(int flags) +{ + int i; + int rflags = 0; + struct dotl_openflag_map dotl_oflag_map[] = { + { O_CREAT, P9_DOTL_CREATE }, + { O_EXCL, P9_DOTL_EXCL }, + { O_NOCTTY, P9_DOTL_NOCTTY }, + { O_APPEND, P9_DOTL_APPEND }, + { O_NONBLOCK, P9_DOTL_NONBLOCK }, + { O_DSYNC, P9_DOTL_DSYNC }, + { FASYNC, P9_DOTL_FASYNC }, + { O_DIRECT, P9_DOTL_DIRECT }, + { O_LARGEFILE, P9_DOTL_LARGEFILE }, + { O_DIRECTORY, P9_DOTL_DIRECTORY }, + { O_NOFOLLOW, P9_DOTL_NOFOLLOW }, + { O_NOATIME, P9_DOTL_NOATIME }, + { O_CLOEXEC, P9_DOTL_CLOEXEC }, + { O_SYNC, P9_DOTL_SYNC}, + }; + for (i = 0; i < ARRAY_SIZE(dotl_oflag_map); i++) { + if (flags & dotl_oflag_map[i].open_flag) + rflags |= dotl_oflag_map[i].dotl_flag; + } + return rflags; +} + +/** + * v9fs_open_to_dotl_flags- convert Linux specific open flags to + * plan 9 open flag. + * @flags: flags to convert + */ +int v9fs_open_to_dotl_flags(int flags) +{ + int rflags = 0; + + /* + * We have same bits for P9_DOTL_READONLY, P9_DOTL_WRONLY + * and P9_DOTL_NOACCESS + */ + rflags |= flags & O_ACCMODE; + rflags |= v9fs_mapped_dotl_flags(flags); + + return rflags; +} + +/** + * v9fs_vfs_create_dotl - VFS hook to create files for 9P2000.L protocol. + * @dir: directory inode that is being created + * @dentry: dentry that is being deleted + * @omode: create permissions + * + */ + +static int +v9fs_vfs_create_dotl(struct inode *dir, struct dentry *dentry, umode_t omode, + bool excl) +{ + return v9fs_vfs_mknod_dotl(dir, dentry, omode, 0); +} + +static int +v9fs_vfs_atomic_open_dotl(struct inode *dir, struct dentry *dentry, + struct file *file, unsigned flags, umode_t omode, + int *opened) +{ + int err = 0; + kgid_t gid; + umode_t mode; + char *name = NULL; + struct p9_qid qid; + struct inode *inode; + struct p9_fid *fid = NULL; + struct v9fs_inode *v9inode; + struct p9_fid *dfid, *ofid, *inode_fid; + struct v9fs_session_info *v9ses; + struct posix_acl *pacl = NULL, *dacl = NULL; + struct dentry *res = NULL; + + if (d_unhashed(dentry)) { + res = v9fs_vfs_lookup(dir, dentry, 0); + if (IS_ERR(res)) + return PTR_ERR(res); + + if (res) + dentry = res; + } + + /* Only creates */ + if (!(flags & O_CREAT) || dentry->d_inode) + return finish_no_open(file, res); + + v9ses = v9fs_inode2v9ses(dir); + + name = (char *) dentry->d_name.name; + p9_debug(P9_DEBUG_VFS, "name:%s flags:0x%x mode:0x%hx\n", + name, flags, omode); + + dfid = v9fs_fid_lookup(dentry->d_parent); + if (IS_ERR(dfid)) { + err = PTR_ERR(dfid); + p9_debug(P9_DEBUG_VFS, "fid lookup failed %d\n", err); + goto out; + } + + /* clone a fid to use for creation */ + ofid = p9_client_walk(dfid, 0, NULL, 1); + if (IS_ERR(ofid)) { + err = PTR_ERR(ofid); + p9_debug(P9_DEBUG_VFS, "p9_client_walk failed %d\n", err); + goto out; + } + + gid = v9fs_get_fsgid_for_create(dir); + + mode = omode; + /* Update mode based on ACL value */ + err = v9fs_acl_mode(dir, &mode, &dacl, &pacl); + if (err) { + p9_debug(P9_DEBUG_VFS, "Failed to get acl values in creat %d\n", + err); + goto error; + } + err = p9_client_create_dotl(ofid, name, v9fs_open_to_dotl_flags(flags), + mode, gid, &qid); + if (err < 0) { + p9_debug(P9_DEBUG_VFS, "p9_client_open_dotl failed in creat %d\n", + err); + goto error; + } + v9fs_invalidate_inode_attr(dir); + + /* instantiate inode and assign the unopened fid to the dentry */ + fid = p9_client_walk(dfid, 1, &name, 1); + if (IS_ERR(fid)) { + err = PTR_ERR(fid); + p9_debug(P9_DEBUG_VFS, "p9_client_walk failed %d\n", err); + fid = NULL; + goto error; + } + inode = v9fs_get_new_inode_from_fid(v9ses, fid, dir->i_sb); + if (IS_ERR(inode)) { + err = PTR_ERR(inode); + p9_debug(P9_DEBUG_VFS, "inode creation failed %d\n", err); + goto error; + } + /* Now set the ACL based on the default value */ + v9fs_set_create_acl(inode, fid, dacl, pacl); + + v9fs_fid_add(dentry, fid); + d_instantiate(dentry, inode); + + v9inode = V9FS_I(inode); + mutex_lock(&v9inode->v_mutex); + if ((v9ses->cache == CACHE_LOOSE || v9ses->cache == CACHE_FSCACHE) && + !v9inode->writeback_fid && + ((flags & O_ACCMODE) != O_RDONLY)) { + /* + * clone a fid and add it to writeback_fid + * we do it during open time instead of + * page dirty time via write_begin/page_mkwrite + * because we want write after unlink usecase + * to work. + */ + inode_fid = v9fs_writeback_fid(dentry); + if (IS_ERR(inode_fid)) { + err = PTR_ERR(inode_fid); + mutex_unlock(&v9inode->v_mutex); + goto err_clunk_old_fid; + } + v9inode->writeback_fid = (void *) inode_fid; + } + mutex_unlock(&v9inode->v_mutex); + /* Since we are opening a file, assign the open fid to the file */ + err = finish_open(file, dentry, generic_file_open, opened); + if (err) + goto err_clunk_old_fid; + file->private_data = ofid; + if (v9ses->cache == CACHE_LOOSE || v9ses->cache == CACHE_FSCACHE) + v9fs_cache_inode_set_cookie(inode, file); + *opened |= FILE_CREATED; +out: + v9fs_put_acl(dacl, pacl); + dput(res); + return err; + +error: + if (fid) + p9_client_clunk(fid); +err_clunk_old_fid: + if (ofid) + p9_client_clunk(ofid); + goto out; +} + +/** + * v9fs_vfs_mkdir_dotl - VFS mkdir hook to create a directory + * @dir: inode that is being unlinked + * @dentry: dentry that is being unlinked + * @omode: mode for new directory + * + */ + +static int v9fs_vfs_mkdir_dotl(struct inode *dir, + struct dentry *dentry, umode_t omode) +{ + int err; + struct v9fs_session_info *v9ses; + struct p9_fid *fid = NULL, *dfid = NULL; + kgid_t gid; + char *name; + umode_t mode; + struct inode *inode; + struct p9_qid qid; + struct dentry *dir_dentry; + struct posix_acl *dacl = NULL, *pacl = NULL; + + p9_debug(P9_DEBUG_VFS, "name %s\n", dentry->d_name.name); + err = 0; + v9ses = v9fs_inode2v9ses(dir); + + omode |= S_IFDIR; + if (dir->i_mode & S_ISGID) + omode |= S_ISGID; + + dir_dentry = dentry->d_parent; + dfid = v9fs_fid_lookup(dir_dentry); + if (IS_ERR(dfid)) { + err = PTR_ERR(dfid); + p9_debug(P9_DEBUG_VFS, "fid lookup failed %d\n", err); + dfid = NULL; + goto error; + } + + gid = v9fs_get_fsgid_for_create(dir); + mode = omode; + /* Update mode based on ACL value */ + err = v9fs_acl_mode(dir, &mode, &dacl, &pacl); + if (err) { + p9_debug(P9_DEBUG_VFS, "Failed to get acl values in mkdir %d\n", + err); + goto error; + } + name = (char *) dentry->d_name.name; + err = p9_client_mkdir_dotl(dfid, name, mode, gid, &qid); + if (err < 0) + goto error; + + fid = p9_client_walk(dfid, 1, &name, 1); + if (IS_ERR(fid)) { + err = PTR_ERR(fid); + p9_debug(P9_DEBUG_VFS, "p9_client_walk failed %d\n", + err); + fid = NULL; + goto error; + } + + /* instantiate inode and assign the unopened fid to the dentry */ + if (v9ses->cache == CACHE_LOOSE || v9ses->cache == CACHE_FSCACHE) { + inode = v9fs_get_new_inode_from_fid(v9ses, fid, dir->i_sb); + if (IS_ERR(inode)) { + err = PTR_ERR(inode); + p9_debug(P9_DEBUG_VFS, "inode creation failed %d\n", + err); + goto error; + } + v9fs_fid_add(dentry, fid); + v9fs_set_create_acl(inode, fid, dacl, pacl); + d_instantiate(dentry, inode); + fid = NULL; + err = 0; + } else { + /* + * Not in cached mode. No need to populate + * inode with stat. We need to get an inode + * so that we can set the acl with dentry + */ + inode = v9fs_get_inode(dir->i_sb, mode, 0); + if (IS_ERR(inode)) { + err = PTR_ERR(inode); + goto error; + } + v9fs_set_create_acl(inode, fid, dacl, pacl); + d_instantiate(dentry, inode); + } + inc_nlink(dir); + v9fs_invalidate_inode_attr(dir); +error: + if (fid) + p9_client_clunk(fid); + v9fs_put_acl(dacl, pacl); + return err; +} + +static int +v9fs_vfs_getattr_dotl(struct vfsmount *mnt, struct dentry *dentry, + struct kstat *stat) +{ + struct v9fs_session_info *v9ses; + struct p9_fid *fid; + struct p9_stat_dotl *st; + + p9_debug(P9_DEBUG_VFS, "dentry: %p\n", dentry); + v9ses = v9fs_dentry2v9ses(dentry); + if (v9ses->cache == CACHE_LOOSE || v9ses->cache == CACHE_FSCACHE) { + generic_fillattr(dentry->d_inode, stat); + return 0; + } + fid = v9fs_fid_lookup(dentry); + if (IS_ERR(fid)) + return PTR_ERR(fid); + + /* Ask for all the fields in stat structure. Server will return + * whatever it supports + */ + + st = p9_client_getattr_dotl(fid, P9_STATS_ALL); + if (IS_ERR(st)) + return PTR_ERR(st); + + v9fs_stat2inode_dotl(st, dentry->d_inode); + generic_fillattr(dentry->d_inode, stat); + /* Change block size to what the server returned */ + stat->blksize = st->st_blksize; + + kfree(st); + return 0; +} + +/* + * Attribute flags. + */ +#define P9_ATTR_MODE (1 << 0) +#define P9_ATTR_UID (1 << 1) +#define P9_ATTR_GID (1 << 2) +#define P9_ATTR_SIZE (1 << 3) +#define P9_ATTR_ATIME (1 << 4) +#define P9_ATTR_MTIME (1 << 5) +#define P9_ATTR_CTIME (1 << 6) +#define P9_ATTR_ATIME_SET (1 << 7) +#define P9_ATTR_MTIME_SET (1 << 8) + +struct dotl_iattr_map { + int iattr_valid; + int p9_iattr_valid; +}; + +static int v9fs_mapped_iattr_valid(int iattr_valid) +{ + int i; + int p9_iattr_valid = 0; + struct dotl_iattr_map dotl_iattr_map[] = { + { ATTR_MODE, P9_ATTR_MODE }, + { ATTR_UID, P9_ATTR_UID }, + { ATTR_GID, P9_ATTR_GID }, + { ATTR_SIZE, P9_ATTR_SIZE }, + { ATTR_ATIME, P9_ATTR_ATIME }, + { ATTR_MTIME, P9_ATTR_MTIME }, + { ATTR_CTIME, P9_ATTR_CTIME }, + { ATTR_ATIME_SET, P9_ATTR_ATIME_SET }, + { ATTR_MTIME_SET, P9_ATTR_MTIME_SET }, + }; + for (i = 0; i < ARRAY_SIZE(dotl_iattr_map); i++) { + if (iattr_valid & dotl_iattr_map[i].iattr_valid) + p9_iattr_valid |= dotl_iattr_map[i].p9_iattr_valid; + } + return p9_iattr_valid; +} + +/** + * v9fs_vfs_setattr_dotl - set file metadata + * @dentry: file whose metadata to set + * @iattr: metadata assignment structure + * + */ + +int v9fs_vfs_setattr_dotl(struct dentry *dentry, struct iattr *iattr) +{ + int retval; + struct p9_fid *fid; + struct p9_iattr_dotl p9attr; + struct inode *inode = dentry->d_inode; + + p9_debug(P9_DEBUG_VFS, "\n"); + + retval = inode_change_ok(inode, iattr); + if (retval) + return retval; + + p9attr.valid = v9fs_mapped_iattr_valid(iattr->ia_valid); + p9attr.mode = iattr->ia_mode; + p9attr.uid = iattr->ia_uid; + p9attr.gid = iattr->ia_gid; + p9attr.size = iattr->ia_size; + p9attr.atime_sec = iattr->ia_atime.tv_sec; + p9attr.atime_nsec = iattr->ia_atime.tv_nsec; + p9attr.mtime_sec = iattr->ia_mtime.tv_sec; + p9attr.mtime_nsec = iattr->ia_mtime.tv_nsec; + + fid = v9fs_fid_lookup(dentry); + if (IS_ERR(fid)) + return PTR_ERR(fid); + + /* Write all dirty data */ + if (S_ISREG(inode->i_mode)) + filemap_write_and_wait(inode->i_mapping); + + retval = p9_client_setattr(fid, &p9attr); + if (retval < 0) + return retval; + + if ((iattr->ia_valid & ATTR_SIZE) && + iattr->ia_size != i_size_read(inode)) + truncate_setsize(inode, iattr->ia_size); + + v9fs_invalidate_inode_attr(inode); + setattr_copy(inode, iattr); + mark_inode_dirty(inode); + if (iattr->ia_valid & ATTR_MODE) { + /* We also want to update ACL when we update mode bits */ + retval = v9fs_acl_chmod(inode, fid); + if (retval < 0) + return retval; + } + return 0; +} + +/** + * v9fs_stat2inode_dotl - populate an inode structure with stat info + * @stat: stat structure + * @inode: inode to populate + * + */ + +void +v9fs_stat2inode_dotl(struct p9_stat_dotl *stat, struct inode *inode) +{ + umode_t mode; + struct v9fs_inode *v9inode = V9FS_I(inode); + + if ((stat->st_result_mask & P9_STATS_BASIC) == P9_STATS_BASIC) { + inode->i_atime.tv_sec = stat->st_atime_sec; + inode->i_atime.tv_nsec = stat->st_atime_nsec; + inode->i_mtime.tv_sec = stat->st_mtime_sec; + inode->i_mtime.tv_nsec = stat->st_mtime_nsec; + inode->i_ctime.tv_sec = stat->st_ctime_sec; + inode->i_ctime.tv_nsec = stat->st_ctime_nsec; + inode->i_uid = stat->st_uid; + inode->i_gid = stat->st_gid; + set_nlink(inode, stat->st_nlink); + + mode = stat->st_mode & S_IALLUGO; + mode |= inode->i_mode & ~S_IALLUGO; + inode->i_mode = mode; + + i_size_write(inode, stat->st_size); + inode->i_blocks = stat->st_blocks; + } else { + if (stat->st_result_mask & P9_STATS_ATIME) { + inode->i_atime.tv_sec = stat->st_atime_sec; + inode->i_atime.tv_nsec = stat->st_atime_nsec; + } + if (stat->st_result_mask & P9_STATS_MTIME) { + inode->i_mtime.tv_sec = stat->st_mtime_sec; + inode->i_mtime.tv_nsec = stat->st_mtime_nsec; + } + if (stat->st_result_mask & P9_STATS_CTIME) { + inode->i_ctime.tv_sec = stat->st_ctime_sec; + inode->i_ctime.tv_nsec = stat->st_ctime_nsec; + } + if (stat->st_result_mask & P9_STATS_UID) + inode->i_uid = stat->st_uid; + if (stat->st_result_mask & P9_STATS_GID) + inode->i_gid = stat->st_gid; + if (stat->st_result_mask & P9_STATS_NLINK) + set_nlink(inode, stat->st_nlink); + if (stat->st_result_mask & P9_STATS_MODE) { + inode->i_mode = stat->st_mode; + if ((S_ISBLK(inode->i_mode)) || + (S_ISCHR(inode->i_mode))) + init_special_inode(inode, inode->i_mode, + inode->i_rdev); + } + if (stat->st_result_mask & P9_STATS_RDEV) + inode->i_rdev = new_decode_dev(stat->st_rdev); + if (stat->st_result_mask & P9_STATS_SIZE) + i_size_write(inode, stat->st_size); + if (stat->st_result_mask & P9_STATS_BLOCKS) + inode->i_blocks = stat->st_blocks; + } + if (stat->st_result_mask & P9_STATS_GEN) + inode->i_generation = stat->st_gen; + + /* Currently we don't support P9_STATS_BTIME and P9_STATS_DATA_VERSION + * because the inode structure does not have fields for them. + */ + v9inode->cache_validity &= ~V9FS_INO_INVALID_ATTR; +} + +static int +v9fs_vfs_symlink_dotl(struct inode *dir, struct dentry *dentry, + const char *symname) +{ + int err; + kgid_t gid; + char *name; + struct p9_qid qid; + struct inode *inode; + struct p9_fid *dfid; + struct p9_fid *fid = NULL; + struct v9fs_session_info *v9ses; + + name = (char *) dentry->d_name.name; + p9_debug(P9_DEBUG_VFS, "%lu,%s,%s\n", dir->i_ino, name, symname); + v9ses = v9fs_inode2v9ses(dir); + + dfid = v9fs_fid_lookup(dentry->d_parent); + if (IS_ERR(dfid)) { + err = PTR_ERR(dfid); + p9_debug(P9_DEBUG_VFS, "fid lookup failed %d\n", err); + return err; + } + + gid = v9fs_get_fsgid_for_create(dir); + + /* Server doesn't alter fid on TSYMLINK. Hence no need to clone it. */ + err = p9_client_symlink(dfid, name, (char *)symname, gid, &qid); + + if (err < 0) { + p9_debug(P9_DEBUG_VFS, "p9_client_symlink failed %d\n", err); + goto error; + } + + v9fs_invalidate_inode_attr(dir); + if (v9ses->cache == CACHE_LOOSE || v9ses->cache == CACHE_FSCACHE) { + /* Now walk from the parent so we can get an unopened fid. */ + fid = p9_client_walk(dfid, 1, &name, 1); + if (IS_ERR(fid)) { + err = PTR_ERR(fid); + p9_debug(P9_DEBUG_VFS, "p9_client_walk failed %d\n", + err); + fid = NULL; + goto error; + } + + /* instantiate inode and assign the unopened fid to dentry */ + inode = v9fs_get_new_inode_from_fid(v9ses, fid, dir->i_sb); + if (IS_ERR(inode)) { + err = PTR_ERR(inode); + p9_debug(P9_DEBUG_VFS, "inode creation failed %d\n", + err); + goto error; + } + v9fs_fid_add(dentry, fid); + d_instantiate(dentry, inode); + fid = NULL; + err = 0; + } else { + /* Not in cached mode. No need to populate inode with stat */ + inode = v9fs_get_inode(dir->i_sb, S_IFLNK, 0); + if (IS_ERR(inode)) { + err = PTR_ERR(inode); + goto error; + } + d_instantiate(dentry, inode); + } + +error: + if (fid) + p9_client_clunk(fid); + + return err; +} + +/** + * v9fs_vfs_link_dotl - create a hardlink for dotl + * @old_dentry: dentry for file to link to + * @dir: inode destination for new link + * @dentry: dentry for link + * + */ + +static int +v9fs_vfs_link_dotl(struct dentry *old_dentry, struct inode *dir, + struct dentry *dentry) +{ + int err; + struct dentry *dir_dentry; + struct p9_fid *dfid, *oldfid; + struct v9fs_session_info *v9ses; + + p9_debug(P9_DEBUG_VFS, "dir ino: %lu, old_name: %s, new_name: %s\n", + dir->i_ino, old_dentry->d_name.name, dentry->d_name.name); + + v9ses = v9fs_inode2v9ses(dir); + dir_dentry = dentry->d_parent; + dfid = v9fs_fid_lookup(dir_dentry); + if (IS_ERR(dfid)) + return PTR_ERR(dfid); + + oldfid = v9fs_fid_lookup(old_dentry); + if (IS_ERR(oldfid)) + return PTR_ERR(oldfid); + + err = p9_client_link(dfid, oldfid, (char *)dentry->d_name.name); + + if (err < 0) { + p9_debug(P9_DEBUG_VFS, "p9_client_link failed %d\n", err); + return err; + } + + v9fs_invalidate_inode_attr(dir); + if (v9ses->cache == CACHE_LOOSE || v9ses->cache == CACHE_FSCACHE) { + /* Get the latest stat info from server. */ + struct p9_fid *fid; + fid = v9fs_fid_lookup(old_dentry); + if (IS_ERR(fid)) + return PTR_ERR(fid); + + v9fs_refresh_inode_dotl(fid, old_dentry->d_inode); + } + ihold(old_dentry->d_inode); + d_instantiate(dentry, old_dentry->d_inode); + + return err; +} + +/** + * v9fs_vfs_mknod_dotl - create a special file + * @dir: inode destination for new link + * @dentry: dentry for file + * @omode: mode for creation + * @rdev: device associated with special file + * + */ +static int +v9fs_vfs_mknod_dotl(struct inode *dir, struct dentry *dentry, umode_t omode, + dev_t rdev) +{ + int err; + kgid_t gid; + char *name; + umode_t mode; + struct v9fs_session_info *v9ses; + struct p9_fid *fid = NULL, *dfid = NULL; + struct inode *inode; + struct p9_qid qid; + struct dentry *dir_dentry; + struct posix_acl *dacl = NULL, *pacl = NULL; + + p9_debug(P9_DEBUG_VFS, " %lu,%s mode: %hx MAJOR: %u MINOR: %u\n", + dir->i_ino, dentry->d_name.name, omode, + MAJOR(rdev), MINOR(rdev)); + + if (!new_valid_dev(rdev)) + return -EINVAL; + + v9ses = v9fs_inode2v9ses(dir); + dir_dentry = dentry->d_parent; + dfid = v9fs_fid_lookup(dir_dentry); + if (IS_ERR(dfid)) { + err = PTR_ERR(dfid); + p9_debug(P9_DEBUG_VFS, "fid lookup failed %d\n", err); + dfid = NULL; + goto error; + } + + gid = v9fs_get_fsgid_for_create(dir); + mode = omode; + /* Update mode based on ACL value */ + err = v9fs_acl_mode(dir, &mode, &dacl, &pacl); + if (err) { + p9_debug(P9_DEBUG_VFS, "Failed to get acl values in mknod %d\n", + err); + goto error; + } + name = (char *) dentry->d_name.name; + + err = p9_client_mknod_dotl(dfid, name, mode, rdev, gid, &qid); + if (err < 0) + goto error; + + v9fs_invalidate_inode_attr(dir); + fid = p9_client_walk(dfid, 1, &name, 1); + if (IS_ERR(fid)) { + err = PTR_ERR(fid); + p9_debug(P9_DEBUG_VFS, "p9_client_walk failed %d\n", + err); + fid = NULL; + goto error; + } + + /* instantiate inode and assign the unopened fid to the dentry */ + if (v9ses->cache == CACHE_LOOSE || v9ses->cache == CACHE_FSCACHE) { + inode = v9fs_get_new_inode_from_fid(v9ses, fid, dir->i_sb); + if (IS_ERR(inode)) { + err = PTR_ERR(inode); + p9_debug(P9_DEBUG_VFS, "inode creation failed %d\n", + err); + goto error; + } + v9fs_set_create_acl(inode, fid, dacl, pacl); + v9fs_fid_add(dentry, fid); + d_instantiate(dentry, inode); + fid = NULL; + err = 0; + } else { + /* + * Not in cached mode. No need to populate inode with stat. + * socket syscall returns a fd, so we need instantiate + */ + inode = v9fs_get_inode(dir->i_sb, mode, rdev); + if (IS_ERR(inode)) { + err = PTR_ERR(inode); + goto error; + } + v9fs_set_create_acl(inode, fid, dacl, pacl); + d_instantiate(dentry, inode); + } +error: + if (fid) + p9_client_clunk(fid); + v9fs_put_acl(dacl, pacl); + return err; +} + +/** + * v9fs_vfs_follow_link_dotl - follow a symlink path + * @dentry: dentry for symlink + * @nd: nameidata + * + */ + +static void * +v9fs_vfs_follow_link_dotl(struct dentry *dentry, struct nameidata *nd) +{ + int retval; + struct p9_fid *fid; + char *link = __getname(); + char *target; + + p9_debug(P9_DEBUG_VFS, "%s\n", dentry->d_name.name); + + if (!link) { + link = ERR_PTR(-ENOMEM); + goto ndset; + } + fid = v9fs_fid_lookup(dentry); + if (IS_ERR(fid)) { + __putname(link); + link = ERR_CAST(fid); + goto ndset; + } + retval = p9_client_readlink(fid, &target); + if (!retval) { + strcpy(link, target); + kfree(target); + goto ndset; + } + __putname(link); + link = ERR_PTR(retval); +ndset: + nd_set_link(nd, link); + return NULL; +} + +int v9fs_refresh_inode_dotl(struct p9_fid *fid, struct inode *inode) +{ + loff_t i_size; + struct p9_stat_dotl *st; + struct v9fs_session_info *v9ses; + + v9ses = v9fs_inode2v9ses(inode); + st = p9_client_getattr_dotl(fid, P9_STATS_ALL); + if (IS_ERR(st)) + return PTR_ERR(st); + /* + * Don't update inode if the file type is different + */ + if ((inode->i_mode & S_IFMT) != (st->st_mode & S_IFMT)) + goto out; + + spin_lock(&inode->i_lock); + /* + * We don't want to refresh inode->i_size, + * because we may have cached data + */ + i_size = inode->i_size; + v9fs_stat2inode_dotl(st, inode); + if (v9ses->cache == CACHE_LOOSE || v9ses->cache == CACHE_FSCACHE) + inode->i_size = i_size; + spin_unlock(&inode->i_lock); +out: + kfree(st); + return 0; +} + +const struct inode_operations v9fs_dir_inode_operations_dotl = { + .create = v9fs_vfs_create_dotl, + .atomic_open = v9fs_vfs_atomic_open_dotl, + .lookup = v9fs_vfs_lookup, + .link = v9fs_vfs_link_dotl, + .symlink = v9fs_vfs_symlink_dotl, + .unlink = v9fs_vfs_unlink, + .mkdir = v9fs_vfs_mkdir_dotl, + .rmdir = v9fs_vfs_rmdir, + .mknod = v9fs_vfs_mknod_dotl, + .rename = v9fs_vfs_rename, + .getattr = v9fs_vfs_getattr_dotl, + .setattr = v9fs_vfs_setattr_dotl, + .setxattr = generic_setxattr, + .getxattr = generic_getxattr, + .removexattr = generic_removexattr, + .listxattr = v9fs_listxattr, + .get_acl = v9fs_iop_get_acl, +}; + +const struct inode_operations v9fs_file_inode_operations_dotl = { + .getattr = v9fs_vfs_getattr_dotl, + .setattr = v9fs_vfs_setattr_dotl, + .setxattr = generic_setxattr, + .getxattr = generic_getxattr, + .removexattr = generic_removexattr, + .listxattr = v9fs_listxattr, + .get_acl = v9fs_iop_get_acl, +}; + +const struct inode_operations v9fs_symlink_inode_operations_dotl = { + .readlink = generic_readlink, + .follow_link = v9fs_vfs_follow_link_dotl, + .put_link = v9fs_vfs_put_link, + .getattr = v9fs_vfs_getattr_dotl, + .setattr = v9fs_vfs_setattr_dotl, + .setxattr = generic_setxattr, + .getxattr = generic_getxattr, + .removexattr = generic_removexattr, + .listxattr = v9fs_listxattr, +}; diff --git a/fs/9p/vfs_super.c b/fs/9p/vfs_super.c index 2c4fa75be02..0afd0382822 100644 --- a/fs/9p/vfs_super.c +++ b/fs/9p/vfs_super.c @@ -8,9 +8,8 @@ * 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 as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. + * 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 @@ -26,39 +25,31 @@ */ #include <linux/kernel.h> -#include <linux/config.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/smp_lock.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 <linux/slab.h> +#include <linux/statfs.h> +#include <linux/magic.h> +#include <net/9p/9p.h> +#include <net/9p/client.h> -#include "debug.h" #include "v9fs.h" -#include "9p.h" #include "v9fs_vfs.h" #include "fid.h" +#include "xattr.h" +#include "acl.h" -static void v9fs_clear_inode(struct inode *); -static 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); -} +static const struct super_operations v9fs_super_ops, v9fs_super_ops_dotl; /** * v9fs_set_super - set the superblock @@ -77,25 +68,41 @@ static int v9fs_set_super(struct super_block *s, void *data) * v9fs_fill_super - populate superblock with info * @sb: superblock * @v9ses: session information + * @flags: flags propagated from v9fs_mount() * */ static void v9fs_fill_super(struct super_block *sb, struct v9fs_session_info *v9ses, - int flags) + int flags, void *data) { 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; + if (v9fs_proto_dotl(v9ses)) { + sb->s_op = &v9fs_super_ops_dotl; + sb->s_xattr = v9fs_xattr_handlers; + } else + sb->s_op = &v9fs_super_ops; + sb->s_bdi = &v9ses->bdi; + if (v9ses->cache) + sb->s_bdi->ra_pages = (VM_MAX_READAHEAD * 1024)/PAGE_CACHE_SIZE; + + sb->s_flags |= MS_ACTIVE | MS_DIRSYNC | MS_NOATIME; + if (!v9ses->cache) + sb->s_flags |= MS_SYNCHRONOUS; + +#ifdef CONFIG_9P_FS_POSIX_ACL + if ((v9ses->flags & V9FS_ACL_MASK) == V9FS_POSIX_ACL) + sb->s_flags |= MS_POSIXACL; +#endif + + save_mount_options(sb, data); } /** - * v9fs_get_sb - mount a superblock + * v9fs_mount - mount a superblock * @fs_type: file system type * @flags: mount flags * @dev_name: device name that was mounted @@ -103,89 +110,105 @@ v9fs_fill_super(struct super_block *sb, struct v9fs_session_info *v9ses, * */ -static struct super_block *v9fs_get_sb(struct file_system_type - *fs_type, int flags, - const char *dev_name, void *data) +static struct dentry *v9fs_mount(struct file_system_type *fs_type, int flags, + const char *dev_name, void *data) { struct super_block *sb = NULL; - struct v9fs_fcall *fcall = NULL; struct inode *inode = NULL; struct dentry *root = NULL; struct v9fs_session_info *v9ses = NULL; - struct v9fs_fid *root_fid = NULL; - int mode = S_IRWXUGO | S_ISVTX; - uid_t uid = current->fsuid; - gid_t gid = current->fsgid; - int stat_result = 0; - int newfid = 0; + umode_t mode = S_IRWXUGO | S_ISVTX; + struct p9_fid *fid; int retval = 0; - dprintk(DEBUG_VFS, " \n"); + p9_debug(P9_DEBUG_VFS, "\n"); v9ses = kzalloc(sizeof(struct v9fs_session_info), GFP_KERNEL); if (!v9ses) return ERR_PTR(-ENOMEM); - if ((newfid = v9fs_session_init(v9ses, dev_name, data)) < 0) { - dprintk(DEBUG_ERROR, "problem initiating session\n"); - kfree(v9ses); - return ERR_PTR(newfid); + fid = v9fs_session_init(v9ses, dev_name, data); + if (IS_ERR(fid)) { + retval = PTR_ERR(fid); + /* + * we need to call session_close to tear down some + * of the data structure setup by session_init + */ + goto close_session; } - sb = sget(fs_type, NULL, v9fs_set_super, v9ses); + sb = sget(fs_type, NULL, v9fs_set_super, flags, v9ses); + if (IS_ERR(sb)) { + retval = PTR_ERR(sb); + goto clunk_fid; + } + v9fs_fill_super(sb, v9ses, flags, data); - v9fs_fill_super(sb, v9ses, flags); + if (v9ses->cache == CACHE_LOOSE || v9ses->cache == CACHE_FSCACHE) + sb->s_d_op = &v9fs_cached_dentry_operations; + else + sb->s_d_op = &v9fs_dentry_operations; - inode = v9fs_get_inode(sb, S_IFDIR | mode); + inode = v9fs_get_inode(sb, S_IFDIR | mode, 0); if (IS_ERR(inode)) { retval = PTR_ERR(inode); - goto put_back_sb; + goto release_sb; } - inode->i_uid = uid; - inode->i_gid = gid; - - root = d_alloc_root(inode); - + root = d_make_root(inode); if (!root) { retval = -ENOMEM; - goto put_back_sb; + goto release_sb; } - sb->s_root = root; - - stat_result = v9fs_t_stat(v9ses, newfid, &fcall); - if (stat_result < 0) { - dprintk(DEBUG_ERROR, "stat error\n"); - v9fs_t_clunk(v9ses, newfid); - v9fs_put_idpool(newfid, &v9ses->fidpool); + if (v9fs_proto_dotl(v9ses)) { + struct p9_stat_dotl *st = NULL; + st = p9_client_getattr_dotl(fid, P9_STATS_BASIC); + if (IS_ERR(st)) { + retval = PTR_ERR(st); + goto release_sb; + } + root->d_inode->i_ino = v9fs_qid2ino(&st->qid); + v9fs_stat2inode_dotl(st, root->d_inode); + kfree(st); } else { - /* Setup the Root Inode */ - root_fid = v9fs_fid_create(root, v9ses, newfid, 0); - if (root_fid == NULL) { - retval = -ENOMEM; - goto put_back_sb; + struct p9_wstat *st = NULL; + st = p9_client_stat(fid); + if (IS_ERR(st)) { + retval = PTR_ERR(st); + goto release_sb; } - root_fid->qid = fcall->params.rstat.stat.qid; - root->d_inode->i_ino = - v9fs_qid2ino(&fcall->params.rstat.stat.qid); - v9fs_stat2inode(&fcall->params.rstat.stat, root->d_inode, sb); - } - - kfree(fcall); + root->d_inode->i_ino = v9fs_qid2ino(&st->qid); + v9fs_stat2inode(st, root->d_inode, sb); - if (stat_result < 0) { - retval = stat_result; - goto put_back_sb; + p9stat_free(st); + kfree(st); } + retval = v9fs_get_acl(inode, fid); + if (retval) + goto release_sb; + v9fs_fid_add(root, fid); - return sb; + p9_debug(P9_DEBUG_VFS, " simple set mount, return 0\n"); + return dget(sb->s_root); + +clunk_fid: + p9_client_clunk(fid); +close_session: + v9fs_session_close(v9ses); + kfree(v9ses); + return ERR_PTR(retval); -put_back_sb: - /* deactivate_super calls v9fs_kill_super which will frees the rest */ - up_write(&sb->s_umount); - deactivate_super(sb); +release_sb: + /* + * we will do the session_close and root dentry release + * in the below call. But we need to clunk fid, because we haven't + * attached the fid to dentry so it won't get clunked + * automatically. + */ + p9_client_clunk(fid); + deactivate_locked_super(sb); return ERR_PTR(retval); } @@ -199,67 +222,149 @@ static void v9fs_kill_super(struct super_block *s) { struct v9fs_session_info *v9ses = s->s_fs_info; - dprintk(DEBUG_VFS, " %p\n", s); - - v9fs_dentry_release(s->s_root); /* clunk root */ + p9_debug(P9_DEBUG_VFS, " %p\n", s); kill_anon_super(s); + v9fs_session_cancel(v9ses); v9fs_session_close(v9ses); kfree(v9ses); - dprintk(DEBUG_VFS, "exiting kill_super\n"); + s->s_fs_info = NULL; + p9_debug(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 void +v9fs_umount_begin(struct super_block *sb) +{ + struct v9fs_session_info *v9ses; -static int v9fs_show_options(struct seq_file *m, struct vfsmount *mnt) + v9ses = sb->s_fs_info; + v9fs_session_begin_cancel(v9ses); +} + +static int v9fs_statfs(struct dentry *dentry, struct kstatfs *buf) { - struct v9fs_session_info *v9ses = mnt->mnt_sb->s_fs_info; - - if (v9ses->debug != 0) - seq_printf(m, ",debug=%u", v9ses->debug); - if (v9ses->port != V9FS_PORT) - seq_printf(m, ",port=%u", v9ses->port); - if (v9ses->maxdata != 9000) - seq_printf(m, ",msize=%u", v9ses->maxdata); - if (v9ses->afid != ~0) - seq_printf(m, ",afid=%u", v9ses->afid); - if (v9ses->proto == PROTO_UNIX) - seq_puts(m, ",proto=unix"); - if (v9ses->extended == 0) - seq_puts(m, ",noextend"); - if (v9ses->nodev == 1) - seq_puts(m, ",nodevmap"); - seq_printf(m, ",name=%s", v9ses->name); - seq_printf(m, ",aname=%s", v9ses->remotename); - seq_printf(m, ",uid=%u", v9ses->uid); - seq_printf(m, ",gid=%u", v9ses->gid); - return 0; + struct v9fs_session_info *v9ses; + struct p9_fid *fid; + struct p9_rstatfs rs; + int res; + + fid = v9fs_fid_lookup(dentry); + if (IS_ERR(fid)) { + res = PTR_ERR(fid); + goto done; + } + + v9ses = v9fs_dentry2v9ses(dentry); + if (v9fs_proto_dotl(v9ses)) { + res = p9_client_statfs(fid, &rs); + if (res == 0) { + buf->f_type = rs.type; + buf->f_bsize = rs.bsize; + buf->f_blocks = rs.blocks; + buf->f_bfree = rs.bfree; + buf->f_bavail = rs.bavail; + buf->f_files = rs.files; + buf->f_ffree = rs.ffree; + buf->f_fsid.val[0] = rs.fsid & 0xFFFFFFFFUL; + buf->f_fsid.val[1] = (rs.fsid >> 32) & 0xFFFFFFFFUL; + buf->f_namelen = rs.namelen; + } + if (res != -ENOSYS) + goto done; + } + res = simple_statfs(dentry, buf); +done: + return res; } -static void -v9fs_umount_begin(struct super_block *sb) +static int v9fs_drop_inode(struct inode *inode) { - struct v9fs_session_info *v9ses = sb->s_fs_info; + struct v9fs_session_info *v9ses; + v9ses = v9fs_inode2v9ses(inode); + if (v9ses->cache == CACHE_LOOSE || v9ses->cache == CACHE_FSCACHE) + return generic_drop_inode(inode); + /* + * in case of non cached mode always drop the + * the inode because we want the inode attribute + * to always match that on the server. + */ + return 1; +} - v9fs_session_cancel(v9ses); +static int v9fs_write_inode(struct inode *inode, + struct writeback_control *wbc) +{ + int ret; + struct p9_wstat wstat; + struct v9fs_inode *v9inode; + /* + * send an fsync request to server irrespective of + * wbc->sync_mode. + */ + p9_debug(P9_DEBUG_VFS, "%s: inode %p\n", __func__, inode); + v9inode = V9FS_I(inode); + if (!v9inode->writeback_fid) + return 0; + v9fs_blank_wstat(&wstat); + + ret = p9_client_wstat(v9inode->writeback_fid, &wstat); + if (ret < 0) { + __mark_inode_dirty(inode, I_DIRTY_DATASYNC); + return ret; + } + return 0; +} + +static int v9fs_write_inode_dotl(struct inode *inode, + struct writeback_control *wbc) +{ + int ret; + struct v9fs_inode *v9inode; + /* + * send an fsync request to server irrespective of + * wbc->sync_mode. + */ + v9inode = V9FS_I(inode); + p9_debug(P9_DEBUG_VFS, "%s: inode %p, writeback_fid %p\n", + __func__, inode, v9inode->writeback_fid); + if (!v9inode->writeback_fid) + return 0; + + ret = p9_client_fsync(v9inode->writeback_fid, 0); + if (ret < 0) { + __mark_inode_dirty(inode, I_DIRTY_DATASYNC); + return ret; + } + return 0; } -static struct super_operations v9fs_super_ops = { +static const struct super_operations v9fs_super_ops = { + .alloc_inode = v9fs_alloc_inode, + .destroy_inode = v9fs_destroy_inode, .statfs = simple_statfs, - .clear_inode = v9fs_clear_inode, - .show_options = v9fs_show_options, + .evict_inode = v9fs_evict_inode, + .show_options = generic_show_options, + .umount_begin = v9fs_umount_begin, + .write_inode = v9fs_write_inode, +}; + +static const struct super_operations v9fs_super_ops_dotl = { + .alloc_inode = v9fs_alloc_inode, + .destroy_inode = v9fs_destroy_inode, + .statfs = v9fs_statfs, + .drop_inode = v9fs_drop_inode, + .evict_inode = v9fs_evict_inode, + .show_options = generic_show_options, .umount_begin = v9fs_umount_begin, + .write_inode = v9fs_write_inode_dotl, }; struct file_system_type v9fs_fs_type = { - .name = "9P", - .get_sb = v9fs_get_sb, + .name = "9p", + .mount = v9fs_mount, .kill_sb = v9fs_kill_super, .owner = THIS_MODULE, + .fs_flags = FS_RENAME_DOES_D_MOVE, }; +MODULE_ALIAS_FS("9p"); diff --git a/fs/9p/xattr.c b/fs/9p/xattr.c new file mode 100644 index 00000000000..f95e01e058e --- /dev/null +++ b/fs/9p/xattr.c @@ -0,0 +1,181 @@ +/* + * Copyright IBM Corporation, 2010 + * Author Aneesh Kumar K.V <aneesh.kumar@linux.vnet.ibm.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2.1 of the GNU Lesser General Public License + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it would be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + */ + +#include <linux/module.h> +#include <linux/fs.h> +#include <linux/sched.h> +#include <net/9p/9p.h> +#include <net/9p/client.h> + +#include "fid.h" +#include "xattr.h" + +ssize_t v9fs_fid_xattr_get(struct p9_fid *fid, const char *name, + void *buffer, size_t buffer_size) +{ + ssize_t retval; + int msize, read_count; + u64 offset = 0, attr_size; + struct p9_fid *attr_fid; + + attr_fid = p9_client_xattrwalk(fid, name, &attr_size); + if (IS_ERR(attr_fid)) { + retval = PTR_ERR(attr_fid); + p9_debug(P9_DEBUG_VFS, "p9_client_attrwalk failed %zd\n", + retval); + attr_fid = NULL; + goto error; + } + if (!buffer_size) { + /* request to get the attr_size */ + retval = attr_size; + goto error; + } + if (attr_size > buffer_size) { + retval = -ERANGE; + goto error; + } + msize = attr_fid->clnt->msize; + while (attr_size) { + if (attr_size > (msize - P9_IOHDRSZ)) + read_count = msize - P9_IOHDRSZ; + else + read_count = attr_size; + read_count = p9_client_read(attr_fid, ((char *)buffer)+offset, + NULL, offset, read_count); + if (read_count < 0) { + /* error in xattr read */ + retval = read_count; + goto error; + } + offset += read_count; + attr_size -= read_count; + } + /* Total read xattr bytes */ + retval = offset; +error: + if (attr_fid) + p9_client_clunk(attr_fid); + return retval; + +} + + +/* + * v9fs_xattr_get() + * + * Copy an extended attribute into the buffer + * provided, or compute the buffer size required. + * Buffer is NULL to compute the size of the buffer required. + * + * Returns a negative error number on failure, or the number of bytes + * used / required on success. + */ +ssize_t v9fs_xattr_get(struct dentry *dentry, const char *name, + void *buffer, size_t buffer_size) +{ + struct p9_fid *fid; + + p9_debug(P9_DEBUG_VFS, "name = %s value_len = %zu\n", + name, buffer_size); + fid = v9fs_fid_lookup(dentry); + if (IS_ERR(fid)) + return PTR_ERR(fid); + + return v9fs_fid_xattr_get(fid, name, buffer, buffer_size); +} + +/* + * v9fs_xattr_set() + * + * Create, replace or remove an extended attribute for this inode. Buffer + * is NULL to remove an existing extended attribute, and non-NULL to + * either replace an existing extended attribute, or create a new extended + * attribute. The flags XATTR_REPLACE and XATTR_CREATE + * specify that an extended attribute must exist and must not exist + * previous to the call, respectively. + * + * Returns 0, or a negative error number on failure. + */ +int v9fs_xattr_set(struct dentry *dentry, const char *name, + const void *value, size_t value_len, int flags) +{ + struct p9_fid *fid = v9fs_fid_lookup(dentry); + if (IS_ERR(fid)) + return PTR_ERR(fid); + return v9fs_fid_xattr_set(fid, name, value, value_len, flags); +} + +int v9fs_fid_xattr_set(struct p9_fid *fid, const char *name, + const void *value, size_t value_len, int flags) +{ + u64 offset = 0; + int retval, msize, write_count; + + p9_debug(P9_DEBUG_VFS, "name = %s value_len = %zu flags = %d\n", + name, value_len, flags); + + /* Clone it */ + fid = p9_client_walk(fid, 0, NULL, 1); + if (IS_ERR(fid)) + return PTR_ERR(fid); + + /* + * On success fid points to xattr + */ + retval = p9_client_xattrcreate(fid, name, value_len, flags); + if (retval < 0) { + p9_debug(P9_DEBUG_VFS, "p9_client_xattrcreate failed %d\n", + retval); + goto err; + } + msize = fid->clnt->msize; + while (value_len) { + if (value_len > (msize - P9_IOHDRSZ)) + write_count = msize - P9_IOHDRSZ; + else + write_count = value_len; + write_count = p9_client_write(fid, ((char *)value)+offset, + NULL, offset, write_count); + if (write_count < 0) { + /* error in xattr write */ + retval = write_count; + goto err; + } + offset += write_count; + value_len -= write_count; + } + retval = 0; +err: + p9_client_clunk(fid); + return retval; +} + +ssize_t v9fs_listxattr(struct dentry *dentry, char *buffer, size_t buffer_size) +{ + return v9fs_xattr_get(dentry, NULL, buffer, buffer_size); +} + +const struct xattr_handler *v9fs_xattr_handlers[] = { + &v9fs_xattr_user_handler, + &v9fs_xattr_trusted_handler, +#ifdef CONFIG_9P_FS_POSIX_ACL + &v9fs_xattr_acl_access_handler, + &v9fs_xattr_acl_default_handler, +#endif +#ifdef CONFIG_9P_FS_SECURITY + &v9fs_xattr_security_handler, +#endif + NULL +}; diff --git a/fs/9p/xattr.h b/fs/9p/xattr.h new file mode 100644 index 00000000000..d3e2ea3840b --- /dev/null +++ b/fs/9p/xattr.h @@ -0,0 +1,37 @@ +/* + * Copyright IBM Corporation, 2010 + * Author Aneesh Kumar K.V <aneesh.kumar@linux.vnet.ibm.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2.1 of the GNU Lesser General Public License + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it would be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + */ +#ifndef FS_9P_XATTR_H +#define FS_9P_XATTR_H + +#include <linux/xattr.h> +#include <net/9p/9p.h> +#include <net/9p/client.h> + +extern const struct xattr_handler *v9fs_xattr_handlers[]; +extern struct xattr_handler v9fs_xattr_user_handler; +extern struct xattr_handler v9fs_xattr_trusted_handler; +extern struct xattr_handler v9fs_xattr_security_handler; +extern const struct xattr_handler v9fs_xattr_acl_access_handler; +extern const struct xattr_handler v9fs_xattr_acl_default_handler; + +extern ssize_t v9fs_fid_xattr_get(struct p9_fid *, const char *, + void *, size_t); +extern ssize_t v9fs_xattr_get(struct dentry *, const char *, + void *, size_t); +extern int v9fs_fid_xattr_set(struct p9_fid *, const char *, + const void *, size_t, int); +extern int v9fs_xattr_set(struct dentry *, const char *, + const void *, size_t, int); +extern ssize_t v9fs_listxattr(struct dentry *, char *, size_t); +#endif /* FS_9P_XATTR_H */ diff --git a/fs/9p/xattr_security.c b/fs/9p/xattr_security.c new file mode 100644 index 00000000000..cb247a142a6 --- /dev/null +++ b/fs/9p/xattr_security.c @@ -0,0 +1,80 @@ +/* + * Copyright IBM Corporation, 2010 + * Author Aneesh Kumar K.V <aneesh.kumar@linux.vnet.ibm.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2.1 of the GNU Lesser General Public License + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it would be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + */ + + +#include <linux/module.h> +#include <linux/string.h> +#include <linux/fs.h> +#include <linux/slab.h> +#include "xattr.h" + +static int v9fs_xattr_security_get(struct dentry *dentry, const char *name, + void *buffer, size_t size, int type) +{ + int retval; + char *full_name; + size_t name_len; + size_t prefix_len = XATTR_SECURITY_PREFIX_LEN; + + if (name == NULL) + return -EINVAL; + + if (strcmp(name, "") == 0) + return -EINVAL; + + name_len = strlen(name); + full_name = kmalloc(prefix_len + name_len + 1 , GFP_KERNEL); + if (!full_name) + return -ENOMEM; + memcpy(full_name, XATTR_SECURITY_PREFIX, prefix_len); + memcpy(full_name+prefix_len, name, name_len); + full_name[prefix_len + name_len] = '\0'; + + retval = v9fs_xattr_get(dentry, full_name, buffer, size); + kfree(full_name); + return retval; +} + +static int v9fs_xattr_security_set(struct dentry *dentry, const char *name, + const void *value, size_t size, int flags, int type) +{ + int retval; + char *full_name; + size_t name_len; + size_t prefix_len = XATTR_SECURITY_PREFIX_LEN; + + if (name == NULL) + return -EINVAL; + + if (strcmp(name, "") == 0) + return -EINVAL; + + name_len = strlen(name); + full_name = kmalloc(prefix_len + name_len + 1 , GFP_KERNEL); + if (!full_name) + return -ENOMEM; + memcpy(full_name, XATTR_SECURITY_PREFIX, prefix_len); + memcpy(full_name + prefix_len, name, name_len); + full_name[prefix_len + name_len] = '\0'; + + retval = v9fs_xattr_set(dentry, full_name, value, size, flags); + kfree(full_name); + return retval; +} + +struct xattr_handler v9fs_xattr_security_handler = { + .prefix = XATTR_SECURITY_PREFIX, + .get = v9fs_xattr_security_get, + .set = v9fs_xattr_security_set, +}; diff --git a/fs/9p/xattr_trusted.c b/fs/9p/xattr_trusted.c new file mode 100644 index 00000000000..e30d33b8a3f --- /dev/null +++ b/fs/9p/xattr_trusted.c @@ -0,0 +1,80 @@ +/* + * Copyright IBM Corporation, 2010 + * Author Aneesh Kumar K.V <aneesh.kumar@linux.vnet.ibm.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2.1 of the GNU Lesser General Public License + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it would be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + */ + + +#include <linux/module.h> +#include <linux/string.h> +#include <linux/fs.h> +#include <linux/slab.h> +#include "xattr.h" + +static int v9fs_xattr_trusted_get(struct dentry *dentry, const char *name, + void *buffer, size_t size, int type) +{ + int retval; + char *full_name; + size_t name_len; + size_t prefix_len = XATTR_TRUSTED_PREFIX_LEN; + + if (name == NULL) + return -EINVAL; + + if (strcmp(name, "") == 0) + return -EINVAL; + + name_len = strlen(name); + full_name = kmalloc(prefix_len + name_len + 1 , GFP_KERNEL); + if (!full_name) + return -ENOMEM; + memcpy(full_name, XATTR_TRUSTED_PREFIX, prefix_len); + memcpy(full_name+prefix_len, name, name_len); + full_name[prefix_len + name_len] = '\0'; + + retval = v9fs_xattr_get(dentry, full_name, buffer, size); + kfree(full_name); + return retval; +} + +static int v9fs_xattr_trusted_set(struct dentry *dentry, const char *name, + const void *value, size_t size, int flags, int type) +{ + int retval; + char *full_name; + size_t name_len; + size_t prefix_len = XATTR_TRUSTED_PREFIX_LEN; + + if (name == NULL) + return -EINVAL; + + if (strcmp(name, "") == 0) + return -EINVAL; + + name_len = strlen(name); + full_name = kmalloc(prefix_len + name_len + 1 , GFP_KERNEL); + if (!full_name) + return -ENOMEM; + memcpy(full_name, XATTR_TRUSTED_PREFIX, prefix_len); + memcpy(full_name + prefix_len, name, name_len); + full_name[prefix_len + name_len] = '\0'; + + retval = v9fs_xattr_set(dentry, full_name, value, size, flags); + kfree(full_name); + return retval; +} + +struct xattr_handler v9fs_xattr_trusted_handler = { + .prefix = XATTR_TRUSTED_PREFIX, + .get = v9fs_xattr_trusted_get, + .set = v9fs_xattr_trusted_set, +}; diff --git a/fs/9p/xattr_user.c b/fs/9p/xattr_user.c new file mode 100644 index 00000000000..d0b701b7208 --- /dev/null +++ b/fs/9p/xattr_user.c @@ -0,0 +1,80 @@ +/* + * Copyright IBM Corporation, 2010 + * Author Aneesh Kumar K.V <aneesh.kumar@linux.vnet.ibm.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2.1 of the GNU Lesser General Public License + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it would be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + */ + + +#include <linux/module.h> +#include <linux/string.h> +#include <linux/fs.h> +#include <linux/slab.h> +#include "xattr.h" + +static int v9fs_xattr_user_get(struct dentry *dentry, const char *name, + void *buffer, size_t size, int type) +{ + int retval; + char *full_name; + size_t name_len; + size_t prefix_len = XATTR_USER_PREFIX_LEN; + + if (name == NULL) + return -EINVAL; + + if (strcmp(name, "") == 0) + return -EINVAL; + + name_len = strlen(name); + full_name = kmalloc(prefix_len + name_len + 1 , GFP_KERNEL); + if (!full_name) + return -ENOMEM; + memcpy(full_name, XATTR_USER_PREFIX, prefix_len); + memcpy(full_name+prefix_len, name, name_len); + full_name[prefix_len + name_len] = '\0'; + + retval = v9fs_xattr_get(dentry, full_name, buffer, size); + kfree(full_name); + return retval; +} + +static int v9fs_xattr_user_set(struct dentry *dentry, const char *name, + const void *value, size_t size, int flags, int type) +{ + int retval; + char *full_name; + size_t name_len; + size_t prefix_len = XATTR_USER_PREFIX_LEN; + + if (name == NULL) + return -EINVAL; + + if (strcmp(name, "") == 0) + return -EINVAL; + + name_len = strlen(name); + full_name = kmalloc(prefix_len + name_len + 1 , GFP_KERNEL); + if (!full_name) + return -ENOMEM; + memcpy(full_name, XATTR_USER_PREFIX, prefix_len); + memcpy(full_name + prefix_len, name, name_len); + full_name[prefix_len + name_len] = '\0'; + + retval = v9fs_xattr_set(dentry, full_name, value, size, flags); + kfree(full_name); + return retval; +} + +struct xattr_handler v9fs_xattr_user_handler = { + .prefix = XATTR_USER_PREFIX, + .get = v9fs_xattr_user_get, + .set = v9fs_xattr_user_set, +}; |
