/*
* linux/fs/9p/vfs_inode.c
*
* This file contains vfs inode ops for the 9P2000 protocol.
*
* Copyright (C) 2004 by Eric Van Hensbergen <ericvh@gmail.com>
* Copyright (C) 2002 by Ron Minnich <rminnich@lanl.gov>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to:
* Free Software Foundation
* 51 Franklin Street, Fifth Floor
* Boston, MA 02111-1301 USA
*
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#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 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
* @v9ses: v9fs session information
* @mode: mode to convert
*
*/
static u32 unixmode2p9mode(struct v9fs_session_info *v9ses, umode_t mode)
{
int res;
res = mode & 0777;
if (S_ISDIR(mode))
res |= P9_DMDIR;
if (v9fs_proto_dotu(v9ses)) {
if (v9ses->nodev == 0) {
if (S_ISSOCK(mode))
res |= P9_DMSOCKET;
if (S_ISFIFO(mode))
res |= P9_DMNAMEDPIPE;
if (S_ISBLK(mode))
res |= P9_DMDEVICE;
if (S_ISCHR(mode))
res |= P9_DMDEVICE;
}
if ((mode & S_ISUID) == S_ISUID)
res |= P9_DMSETUID;
if ((mode & S_ISGID) == S_ISGID)
res |= P9_DMSETGID;
if ((mode & S_ISVTX) == S_ISVTX)
res |= P9_DMSETVTX;
}
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
* @stat: p9_wstat from which mode need to be derived
* @rdev: major number, minor number in case of device files.
*
*/
static umode_t p9mode2unixmode(struct v9fs_session_info *v9ses,
struct p9_wstat *stat, dev_t *rdev)
{
int res;
u32 mode = stat->mode;
*rdev = 0;
res = p9mode2perm(v9ses, stat);
if ((mode & P9_DMDIR) == P9_DMDIR)
res |= S_IFDIR;
else if ((mode & P9_DMSYMLINK) && (v9fs_proto_dotu(v9ses)))
res |= S_IFLNK;
else if ((mode & P9_DMSOCKET) && (v9fs_proto_dotu(v9ses))
&& (v9ses->nodev == 0))
res |= S_IFSOCK;
else if ((mode & P9_DMNAMEDPIPE) && (v9fs_proto_dotu(v9ses))
&& (v9ses->nodev == 0))
res |= S_IFIFO;
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;
return res;
}
/**
* v9fs_uflags2omode- convert posix open flags to plan 9 mode bits
* @uflags: flags to convert
* @extended: if .u extensions are active
*/
int v9fs_uflags2omode(int uflags, int extended)
{
int ret;
ret = 0;
switch (uflags&3) {
default:
case O_RDONLY:
ret = P9_OREAD;
break;
case O_WRONLY:
ret = P9_OWRITE;
break;
case O_RDWR:
ret = P9_ORDWR;
break;
}
if (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
* @wstat: structure to initialize
*
*/
void
v9fs_blank_wstat(struct p9_wstat *wstat)
{
wstat->type = ~0;
wstat->dev = ~0;
wstat->qid.type = ~0;
wstat->qid.version = ~0;
*((long long *)&wstat->qid.path) = ~0;
wstat->mode = ~0;
wstat->atime = ~0;
wstat->mtime = ~0;
wstat->length = ~0;
wstat->name = NULL;
wstat->uid = NULL;
wstat->gid = NULL;
wstat->muid = NULL;
wstat->n_uid = INVALID_UID;
wstat->n_gid = INVALID_GID;
wstat->n_muid = INVALID_UID;
wstat->extension = NULL;
}
/**
* 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
*
*/
static void v9fs_i_callback(struct rcu_head *head)
{
struct inode *inode = container_of(head, struct inode, i_rcu);
kmem_cache_free(v9fs_inode_cache, V9FS_I(inode));
}
void v9fs_destroy_inode(struct inode *inode)
{
call_rcu(&inode->i_rcu, v9fs_i_callback);
}
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;
} 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;
|