From 4352b8aee98005853aa63f57d6377282de17a33f Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Sun, 23 Aug 2026 13:28:57 -0700 Subject: Merge tag 'i3c/for-7.3' of git://git.kernel.org/pub/scm/linux/kernel/git/i3c/linux Pull I3C updates from Alexandre Belloni: "A new driver this cycle, for the AMD AXI I3C controller. There are also new features such as IBI wakeup support, SETAASA device discovery and ACPI support for the the DAA methods, meaning we can now communicate with the SPD devices on DDR5 modules. New driver: - AMD AXI I3C master controller Subsystem: - I3C Common Command Code (CCC) handling improvements, especially around GET CCCs - SETAASA device discovery support - ACPI support for all existing DAA methods like SETDASA, SETNEWDA as well as I2C devices on I3C bus - IBI-based wakeup support Drivers: - dw: SETAASA support - mipi-i3c-hci: advertise IBI wakeup capability, AMD_PT I3C controller support, PIO queue management support for HCI v1.2 - renesas: improve suspend to RAM support, add runtime PM support" * tag 'i3c/for-7.3' of git://git.kernel.org/pub/scm/linux/kernel/git/i3c/linux: (70 commits) i3c: dw: reduce do_daa time if there's no client i3c: mipi-i3c-hci: Add support for AMD_PT I3C controller i3c: mipi-i3c-hci: Add PIO queue management support for HCI v1.2 i3c: mipi-i3c-hci: Fix missing STAT_IBI_STATUS_THLD in PIO mode i3c: dw: rename "pclk" to "apb" to match dt-binding i3c: dw: make struct dw_i3c_cmd smaller i3c: dw: use COMMAND_PORT_TRANSFER_ARG instead of hardcoding i3c: renesas: Don't register devices when ENTDAA times out i3c: master: dw-i3c-master: fix OD timing for first broadcast i3c: mipi-i3c-hci: Advertise IBI wakeup capability i3c: mipi-i3c-hci: Factor out i3c_hci_sysdev() i3c: mipi-i3c-hci-pci: Propagate I3C wakeup requirements to PCI i3c: master: Reject IBI requests from non-IBI-capable devices i3c: master: Add helper to query bus wakeup requirements i3c: master: Report wakeup events for IBIs i3c: master: Support IBI-based wakeup capability i3c: master: Fix potential UAF in i3c_device_match() i3c: master: Fix potential UAF in i3c_device_uevent() i3c: Make dev->desc locking assumptions explicit i3c: master: Fix use-after-free of master->this ... --- fs/9p/Kconfig | 45 ++ fs/9p/Makefile | 17 + fs/9p/acl.c | 336 ++++++++++++ fs/9p/acl.h | 54 ++ fs/9p/cache.c | 76 +++ fs/9p/cache.h | 26 + fs/9p/fid.c | 316 +++++++++++ fs/9p/fid.h | 61 +++ fs/9p/v9fs.c | 786 +++++++++++++++++++++++++++ fs/9p/v9fs.h | 269 ++++++++++ fs/9p/v9fs_vfs.h | 93 ++++ fs/9p/vfs_addr.c | 201 +++++++ fs/9p/vfs_dentry.c | 228 ++++++++ fs/9p/vfs_dir.c | 266 +++++++++ fs/9p/vfs_file.c | 534 ++++++++++++++++++ fs/9p/vfs_inode.c | 1402 ++++++++++++++++++++++++++++++++++++++++++++++++ fs/9p/vfs_inode_dotl.c | 994 ++++++++++++++++++++++++++++++++++ fs/9p/vfs_super.c | 366 +++++++++++++ fs/9p/xattr.c | 193 +++++++ fs/9p/xattr.h | 25 + 20 files changed, 6288 insertions(+) create mode 100644 fs/9p/Kconfig create mode 100644 fs/9p/Makefile create mode 100644 fs/9p/acl.c create mode 100644 fs/9p/acl.h create mode 100644 fs/9p/cache.c create mode 100644 fs/9p/cache.h create mode 100644 fs/9p/fid.c create mode 100644 fs/9p/fid.h create mode 100644 fs/9p/v9fs.c create mode 100644 fs/9p/v9fs.h create mode 100644 fs/9p/v9fs_vfs.h create mode 100644 fs/9p/vfs_addr.c create mode 100644 fs/9p/vfs_dentry.c create mode 100644 fs/9p/vfs_dir.c create mode 100644 fs/9p/vfs_file.c create mode 100644 fs/9p/vfs_inode.c create mode 100644 fs/9p/vfs_inode_dotl.c create mode 100644 fs/9p/vfs_super.c create mode 100644 fs/9p/xattr.c create mode 100644 fs/9p/xattr.h (limited to 'fs/9p') diff --git a/fs/9p/Kconfig b/fs/9p/Kconfig new file mode 100644 index 000000000..0c63df574 --- /dev/null +++ b/fs/9p/Kconfig @@ -0,0 +1,45 @@ +# SPDX-License-Identifier: GPL-2.0-only +config 9P_FS + tristate "Plan 9 Resource Sharing Support (9P2000)" + depends on NET_9P + select NETFS_SUPPORT + help + If you say Y here, you will get experimental support for + Plan 9 resource sharing via the 9P2000 protocol. + + See 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. + + 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 new file mode 100644 index 000000000..e7800a5c7 --- /dev/null +++ b/fs/9p/Makefile @@ -0,0 +1,17 @@ +# SPDX-License-Identifier: GPL-2.0 +obj-$(CONFIG_9P_FS) := 9p.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 \ + v9fs.o \ + fid.o \ + xattr.o + +9p-$(CONFIG_9P_FSCACHE) += cache.o +9p-$(CONFIG_9P_FS_POSIX_ACL) += acl.o diff --git a/fs/9p/acl.c b/fs/9p/acl.c new file mode 100644 index 000000000..ae7e7cf75 --- /dev/null +++ b/fs/9p/acl.c @@ -0,0 +1,336 @@ +// SPDX-License-Identifier: LGPL-2.1 +/* + * Copyright IBM Corporation, 2010 + * Author Aneesh Kumar K.V + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "xattr.h" +#include "acl.h" +#include "v9fs.h" +#include "v9fs_vfs.h" +#include "fid.h" + +static struct posix_acl *v9fs_fid_get_acl(struct p9_fid *fid, const 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) + return ERR_PTR(size); + if (size == 0) + return ERR_PTR(-ENODATA); + + value = kzalloc(size, GFP_NOFS); + if (!value) + return ERR_PTR(-ENOMEM); + + size = v9fs_fid_xattr_get(fid, name, value, size); + if (size < 0) + acl = ERR_PTR(size); + else if (size == 0) + acl = ERR_PTR(-ENODATA); + else + acl = posix_acl_from_xattr(&init_user_ns, value, size); + kfree(value); + return acl; +} + +static struct posix_acl *v9fs_acl_get(struct dentry *dentry, const char *name) +{ + struct p9_fid *fid; + struct posix_acl *acl = NULL; + + fid = v9fs_fid_lookup(dentry); + if (IS_ERR(fid)) + return ERR_CAST(fid); + + acl = v9fs_fid_get_acl(fid, name); + p9_fid_put(fid); + return acl; +} + +static struct posix_acl *__v9fs_get_acl(struct p9_fid *fid, const char *name) +{ + int retval; + struct posix_acl *acl = NULL; + + acl = v9fs_fid_get_acl(fid, name); + if (!IS_ERR(acl)) + return acl; + + retval = PTR_ERR(acl); + if (retval == -ENODATA || retval == -ENOSYS || retval == -EOPNOTSUPP) + return NULL; + + /* map everything else to -EIO */ + return ERR_PTR(-EIO); +} + +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, XATTR_NAME_POSIX_ACL_DEFAULT); + pacl = __v9fs_get_acl(fid, XATTR_NAME_POSIX_ACL_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(is_uncached_acl(acl)); + return acl; +} + +struct posix_acl *v9fs_iop_get_inode_acl(struct inode *inode, int type, bool rcu) +{ + struct v9fs_session_info *v9ses; + + if (rcu) + return ERR_PTR(-ECHILD); + + 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); + +} + +struct posix_acl *v9fs_iop_get_acl(struct mnt_idmap *idmap, + struct dentry *dentry, int type) +{ + struct v9fs_session_info *v9ses; + + 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_acl_get(dentry, posix_acl_xattr_name(type)); + return v9fs_get_cached_acl(d_inode(dentry), type); +} + +int v9fs_iop_set_acl(struct mnt_idmap *idmap, struct dentry *dentry, + struct posix_acl *acl, int type) +{ + int retval; + size_t size = 0; + void *value = NULL; + const char *acl_name; + struct v9fs_session_info *v9ses; + struct inode *inode = d_inode(dentry); + + if (acl) { + retval = posix_acl_valid(inode->i_sb->s_user_ns, acl); + if (retval) + goto err_out; + + value = posix_acl_to_xattr(&init_user_ns, acl, &size, GFP_NOFS); + if (!value) { + retval = -ENOMEM; + goto err_out; + } + } + + /* + * set the attribute on the remote. Without even looking at the + * xattr value. We leave it to the server to validate + */ + acl_name = posix_acl_xattr_name(type); + v9ses = v9fs_dentry2v9ses(dentry); + if ((v9ses->flags & V9FS_ACCESS_MASK) != V9FS_ACCESS_CLIENT) { + retval = v9fs_xattr_set(dentry, acl_name, value, size, 0); + goto err_out; + } + + if (S_ISLNK(inode->i_mode)) { + retval = -EOPNOTSUPP; + goto err_out; + } + + if (!inode_owner_or_capable(&nop_mnt_idmap, inode)) { + retval = -EPERM; + goto err_out; + } + + switch (type) { + case ACL_TYPE_ACCESS: + if (acl) { + struct iattr iattr = {}; + struct posix_acl *acl_mode = acl; + + retval = posix_acl_update_mode(&nop_mnt_idmap, inode, + &iattr.ia_mode, + &acl_mode); + if (retval) + goto err_out; + if (!acl_mode) { + /* + * ACL can be represented by the mode bits. + * So don't update ACL below. + */ + kfree(value); + value = NULL; + size = 0; + } + iattr.ia_valid = ATTR_MODE; + /* + * FIXME should we update ctime ? + * What is the following setxattr update the mode ? + */ + v9fs_vfs_setattr_dotl(&nop_mnt_idmap, dentry, &iattr); + } + break; + case ACL_TYPE_DEFAULT: + if (!S_ISDIR(inode->i_mode)) { + retval = acl ? -EINVAL : 0; + goto err_out; + } + break; + } + + retval = v9fs_xattr_set(dentry, acl_name, value, size, 0); + if (!retval) + set_cached_acl(inode, type, acl); + +err_out: + kfree(value); + return retval; +} + +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 */ + buffer = posix_acl_to_xattr(&init_user_ns, acl, &size, GFP_KERNEL); + if (!buffer) + return -ENOMEM; + + switch (type) { + case ACL_TYPE_ACCESS: + name = XATTR_NAME_POSIX_ACL_ACCESS; + break; + case ACL_TYPE_DEFAULT: + name = XATTR_NAME_POSIX_ACL_DEFAULT; + break; + default: + BUG(); + } + retval = v9fs_fid_xattr_set(fid, name, buffer, size, 0); + 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; +} diff --git a/fs/9p/acl.h b/fs/9p/acl.h new file mode 100644 index 000000000..333cfcc28 --- /dev/null +++ b/fs/9p/acl.h @@ -0,0 +1,54 @@ +/* SPDX-License-Identifier: LGPL-2.1 */ +/* + * Copyright IBM Corporation, 2010 + * Author Aneesh Kumar K.V + */ +#ifndef FS_9P_ACL_H +#define FS_9P_ACL_H + +#ifdef CONFIG_9P_FS_POSIX_ACL +int v9fs_get_acl(struct inode *inode, struct p9_fid *fid); +struct posix_acl *v9fs_iop_get_inode_acl(struct inode *inode, int type, + bool rcu); +struct posix_acl *v9fs_iop_get_acl(struct mnt_idmap *idmap, + struct dentry *dentry, int type); +int v9fs_iop_set_acl(struct mnt_idmap *idmap, struct dentry *dentry, + struct posix_acl *acl, int type); +int v9fs_acl_chmod(struct inode *inode, struct p9_fid *fid); +int v9fs_set_create_acl(struct inode *inode, struct p9_fid *fid, + struct posix_acl *dacl, struct posix_acl *acl); +int v9fs_acl_mode(struct inode *dir, umode_t *modep, + struct posix_acl **dpacl, struct posix_acl **pacl); +void v9fs_put_acl(struct posix_acl *dacl, struct posix_acl *acl); +#else +#define v9fs_iop_get_inode_acl NULL +#define v9fs_iop_get_acl NULL +#define v9fs_iop_set_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 000000000..12c0ae29f --- /dev/null +++ b/fs/9p/cache.c @@ -0,0 +1,76 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * V9FS cache definitions. + * + * Copyright (C) 2009 by Abhishek Kulkarni + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "v9fs.h" +#include "cache.h" + +int v9fs_cache_session_get_cookie(struct v9fs_session_info *v9ses, + const char *dev_name) +{ + struct fscache_volume *vcookie; + char *name, *p; + + name = kasprintf(GFP_KERNEL, "9p,%s,%s", + dev_name, v9ses->cachetag ?: v9ses->aname); + if (!name) + return -ENOMEM; + + for (p = name; *p; p++) + if (*p == '/') + *p = ';'; + + vcookie = fscache_acquire_volume(name, NULL, NULL, 0); + p9_debug(P9_DEBUG_FSC, "session %p get volume %p (%s)\n", + v9ses, vcookie, name); + if (IS_ERR(vcookie)) { + if (vcookie != ERR_PTR(-EBUSY)) { + kfree(name); + return PTR_ERR(vcookie); + } + pr_err("Cache volume key already in use (%s)\n", name); + vcookie = NULL; + } + v9ses->fscache = vcookie; + kfree(name); + return 0; +} + +void v9fs_cache_inode_get_cookie(struct inode *inode) +{ + struct v9fs_inode *v9inode = V9FS_I(inode); + struct v9fs_session_info *v9ses; + __le32 version; + __le64 path; + + if (!S_ISREG(inode->i_mode)) + return; + if (WARN_ON(v9fs_inode_cookie(v9inode))) + return; + + version = cpu_to_le32(v9inode->qid.version); + path = cpu_to_le64(v9inode->qid.path); + v9ses = v9fs_inode2v9ses(inode); + v9inode->netfs.cache = + fscache_acquire_cookie(v9fs_session_cache(v9ses), + 0, + &path, sizeof(path), + &version, sizeof(version), + i_size_read(&v9inode->netfs.inode)); + if (v9inode->netfs.cache) + mapping_set_release_always(inode->i_mapping); + + p9_debug(P9_DEBUG_FSC, "inode %p get cookie %p\n", + inode, v9fs_inode_cookie(v9inode)); +} diff --git a/fs/9p/cache.h b/fs/9p/cache.h new file mode 100644 index 000000000..ee1b6b06a --- /dev/null +++ b/fs/9p/cache.h @@ -0,0 +1,26 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * V9FS cache definitions. + * + * Copyright (C) 2009 by Abhishek Kulkarni + */ + +#ifndef _9P_CACHE_H +#define _9P_CACHE_H + +#ifdef CONFIG_9P_FSCACHE +#include + +extern int v9fs_cache_session_get_cookie(struct v9fs_session_info *v9ses, + const char *dev_name); + +extern void v9fs_cache_inode_get_cookie(struct inode *inode); + +#else /* CONFIG_9P_FSCACHE */ + +static inline void v9fs_cache_inode_get_cookie(struct inode *inode) +{ +} + +#endif /* CONFIG_9P_FSCACHE */ +#endif /* _9P_CACHE_H */ diff --git a/fs/9p/fid.c b/fs/9p/fid.c new file mode 100644 index 000000000..76242d450 --- /dev/null +++ b/fs/9p/fid.c @@ -0,0 +1,316 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * V9FS FID Management + * + * Copyright (C) 2007 by Latchesar Ionkov + * Copyright (C) 2005, 2006 by Eric Van Hensbergen + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "v9fs.h" +#include "v9fs_vfs.h" +#include "fid.h" + +static inline void __add_fid(struct dentry *dentry, struct p9_fid *fid) +{ + struct v9fs_dentry *v9fs_dentry = to_v9fs_dentry(dentry); + + hlist_add_head(&fid->dlist, &v9fs_dentry->head); +} + + +/** + * v9fs_fid_add - add a fid to a dentry + * @dentry: dentry that the fid is being added to + * @pfid: fid to add, NULLed out + * + */ +void v9fs_fid_add(struct dentry *dentry, struct p9_fid **pfid) +{ + struct p9_fid *fid = *pfid; + + spin_lock(&dentry->d_lock); + __add_fid(dentry, fid); + spin_unlock(&dentry->d_lock); + + *pfid = NULL; +} + +static bool v9fs_is_writeable(int mode) +{ + if (mode & (P9_OWRITE|P9_ORDWR)) + return true; + else + return false; +} + +/** + * v9fs_fid_find_inode - search for an open fid off of the inode list + * @inode: return a fid pointing to a specific inode + * @want_writeable: only consider fids which are writeable + * @uid: return a fid belonging to the specified user + * @any: ignore uid as a selection criteria + * + */ +struct p9_fid *v9fs_fid_find_inode(struct inode *inode, bool want_writeable, + kuid_t uid, bool any) +{ + struct hlist_head *h; + struct p9_fid *fid, *ret = NULL; + + p9_debug(P9_DEBUG_VFS, " inode: %p\n", inode); + + spin_lock(&inode->i_lock); + h = (struct hlist_head *)&inode->i_private; + hlist_for_each_entry(fid, h, ilist) { + if (any || uid_eq(fid->uid, uid)) { + if (want_writeable && !v9fs_is_writeable(fid->mode)) { + p9_debug(P9_DEBUG_VFS, " mode: %x not writeable?\n", + fid->mode); + continue; + } + p9_fid_get(fid); + ret = fid; + break; + } + } + spin_unlock(&inode->i_lock); + return ret; +} + +/** + * v9fs_open_fid_add - add an open fid to an inode + * @inode: inode that the fid is being added to + * @pfid: fid to add, NULLed out + * + */ + +void v9fs_open_fid_add(struct inode *inode, struct p9_fid **pfid) +{ + struct p9_fid *fid = *pfid; + + spin_lock(&inode->i_lock); + hlist_add_head(&fid->ilist, (struct hlist_head *)&inode->i_private); + spin_unlock(&inode->i_lock); + + *pfid = NULL; +} + + +/** + * v9fs_fid_find - retrieve a fid that belongs to the specified uid + * @dentry: dentry to look for fid in + * @uid: return fid that belongs to the specified user + * @any: if non-zero, return any fid associated with the dentry + * + */ + +static struct p9_fid *v9fs_fid_find(struct dentry *dentry, kuid_t uid, int any) +{ + struct v9fs_dentry *v9fs_dentry = to_v9fs_dentry(dentry); + struct p9_fid *fid, *ret; + + p9_debug(P9_DEBUG_VFS, " dentry: %pd (%p) uid %d any %d\n", + dentry, dentry, from_kuid(&init_user_ns, uid), + any); + ret = NULL; + /* we'll recheck under lock if there's anything to look in */ + if (!hlist_empty(&v9fs_dentry->head)) { + spin_lock(&dentry->d_lock); + hlist_for_each_entry(fid, &v9fs_dentry->head, dlist) { + if (any || uid_eq(fid->uid, uid)) { + ret = fid; + p9_fid_get(ret); + break; + } + } + spin_unlock(&dentry->d_lock); + } + if (!ret && dentry->d_inode) + ret = v9fs_fid_find_inode(dentry->d_inode, false, uid, any); + + return ret; +} + +/* + * We need to hold v9ses->rename_sem as long as we hold references + * to returned path array. Array element contain pointers to + * dentry names. + */ +static int build_path_from_dentry(struct v9fs_session_info *v9ses, + struct dentry *dentry, const unsigned char ***names) +{ + int n = 0, i; + const unsigned char **wnames; + struct dentry *ds; + + for (ds = dentry; !IS_ROOT(ds); ds = ds->d_parent) + n++; + + wnames = kmalloc_array(n, sizeof(char *), GFP_KERNEL); + if (!wnames) + goto err_out; + + for (ds = dentry, i = (n-1); i >= 0; i--, ds = ds->d_parent) + wnames[i] = ds->d_name.name; + + *names = wnames; + return n; +err_out: + return -ENOMEM; +} + +static struct p9_fid *v9fs_fid_lookup_with_uid(struct dentry *dentry, + kuid_t uid, int any) +{ + struct dentry *ds; + const unsigned char **wnames, *uname; + int i, n, l, access; + struct v9fs_session_info *v9ses; + struct p9_fid *fid, *root_fid, *old_fid; + + 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 */ + old_fid = fid; + + fid = p9_client_walk(old_fid, 1, &dentry->d_name.name, 1); + p9_fid_put(old_fid); + goto fid_out; + } + up_read(&v9ses->rename_sem); + + /* start from the root and try to do a lookup */ + root_fid = v9fs_fid_find(dentry->d_sb->s_root, uid, any); + if (!root_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; + + root_fid = p9_fid_get(fid); + v9fs_fid_add(dentry->d_sb->s_root, &fid); + } + /* If we are root ourself just return that */ + if (dentry->d_sb->s_root == dentry) + return root_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; + } + fid = root_fid; + old_fid = root_fid; + 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 path components change + */ + fid = p9_client_walk(old_fid, l, &wnames[i], + old_fid == root_fid /* clone */); + /* non-cloning walk will return the same fid */ + if (fid != old_fid) { + p9_fid_put(old_fid); + old_fid = fid; + } + if (IS_ERR(fid)) { + kfree(wnames); + goto err_out; + } + i += l; + } + kfree(wnames); +fid_out: + if (!IS_ERR(fid)) { + spin_lock(&dentry->d_lock); + if (d_unhashed(dentry)) { + spin_unlock(&dentry->d_lock); + p9_fid_put(fid); + fid = ERR_PTR(-ENOENT); + } else { + __add_fid(dentry, fid); + p9_fid_get(fid); + spin_unlock(&dentry->d_lock); + } + } +err_out: + up_read(&v9ses->rename_sem); + return fid; +} + +/** + * v9fs_fid_lookup - lookup for a fid, try to walk if not found + * @dentry: dentry to look for fid in + * + * Look for a fid in the specified dentry for the current user. + * If no fid is found, try to create one walking from a fid from the parent + * dentry (if it has one), or the root dentry. If the user haven't accessed + * the fs yet, attach now and walk from the root. + */ + +struct p9_fid *v9fs_fid_lookup(struct dentry *dentry) +{ + kuid_t uid; + int any, access; + struct v9fs_session_info *v9ses; + + 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); +} + diff --git a/fs/9p/fid.h b/fs/9p/fid.h new file mode 100644 index 000000000..0d6138bee --- /dev/null +++ b/fs/9p/fid.h @@ -0,0 +1,61 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * V9FS FID Management + * + * Copyright (C) 2005 by Eric Van Hensbergen + */ +#ifndef FS_9P_FID_H +#define FS_9P_FID_H +#include +#include "v9fs.h" + +struct p9_fid *v9fs_fid_find_inode(struct inode *inode, bool want_writeable, + kuid_t uid, bool any); +struct p9_fid *v9fs_fid_lookup(struct dentry *dentry); +static inline struct p9_fid *v9fs_parent_fid(struct dentry *dentry) +{ + return v9fs_fid_lookup(dentry->d_parent); +} +void v9fs_fid_add(struct dentry *dentry, struct p9_fid **fid); +void v9fs_open_fid_add(struct inode *inode, struct p9_fid **fid); +static inline struct p9_fid *clone_fid(struct p9_fid *fid) +{ + return IS_ERR(fid) ? fid : p9_client_walk(fid, 0, NULL, 1); +} +static inline struct p9_fid *v9fs_fid_clone(struct dentry *dentry) +{ + struct p9_fid *fid, *nfid; + + fid = v9fs_fid_lookup(dentry); + if (!fid || IS_ERR(fid)) + return fid; + + nfid = clone_fid(fid); + p9_fid_put(fid); + return nfid; +} +/** + * v9fs_fid_addmodes - add cache flags to fid mode (for client use only) + * @fid: fid to augment + * @s_flags: session info mount flags + * @s_cache: session info cache flags + * @f_flags: unix open flags + * + * make sure mode reflects flags of underlying mounts + * also qid.version == 0 reflects a synthetic or legacy file system + * NOTE: these are set after open so only reflect 9p client not + * underlying file system on server. + */ +static inline void v9fs_fid_add_modes(struct p9_fid *fid, unsigned int s_flags, + unsigned int s_cache, unsigned int f_flags) +{ + if ((!s_cache) || + ((fid->qid.version == 0) && !(s_flags & V9FS_IGNORE_QV)) || + (s_flags & V9FS_DIRECT_IO) || (f_flags & O_DIRECT)) { + fid->mode |= P9L_DIRECT; /* no read or write cache */ + } else if ((!(s_cache & CACHE_WRITEBACK)) || + (f_flags & O_DSYNC) || (s_flags & V9FS_SYNC)) { + fid->mode |= P9L_NOWRITECACHE; + } +} +#endif diff --git a/fs/9p/v9fs.c b/fs/9p/v9fs.c new file mode 100644 index 000000000..274c51571 --- /dev/null +++ b/fs/9p/v9fs.c @@ -0,0 +1,786 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * This file contains functions assisting in mapping VFS to 9P2000 + * + * Copyright (C) 2004-2008 by Eric Van Hensbergen + * Copyright (C) 2002 by Ron Minnich + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "v9fs.h" +#include "v9fs_vfs.h" +#include "cache.h" + +/* cache=loose default negative dentry retention time is 24hours */ +#define CACHE_LOOSE_NDENTRY_TIMEOUT_DEFAULT (24 * 60 * 60 * 1000) + +static DEFINE_SPINLOCK(v9fs_sessionlist_lock); +static LIST_HEAD(v9fs_sessionlist); +struct kmem_cache *v9fs_inode_cache; + +/* + * Option Parsing (code inspired by NFS code) + * NOTE: each transport will parse its own options + */ + +enum { + /* Mount-point source, we need to handle this explicitly because + * the code below accepts unknown args and the vfs layer only handles + * source if we rejected it as EINVAL */ + Opt_source, + /* Options that take integer arguments */ + Opt_debug, Opt_dfltuid, Opt_dfltgid, Opt_afid, Opt_negtimeout, + /* String options */ + Opt_uname, Opt_remotename, Opt_cache, Opt_cachetag, + /* Options that take no arguments */ + Opt_nodevmap, Opt_noxattr, Opt_directio, Opt_ignoreqv, + /* Access options */ + Opt_access, Opt_posixacl, + /* Lock timeout option */ + Opt_locktimeout, + + /* Client options */ + Opt_msize, Opt_trans, Opt_legacy, Opt_version, + + /* fd transport options */ + /* Options that take integer arguments */ + Opt_rfdno, Opt_wfdno, + /* Options that take no arguments */ + + /* rdma transport options */ + /* Options that take integer arguments */ + Opt_rq_depth, Opt_sq_depth, Opt_timeout, + + /* Options for both fd and rdma transports */ + Opt_port, Opt_privport, +}; + +static const struct constant_table p9_versions[] = { + { "9p2000", p9_proto_legacy }, + { "9p2000.u", p9_proto_2000u }, + { "9p2000.L", p9_proto_2000L }, + {} +}; + +/* + * This structure contains all parameters used for the core code, + * the client, and all the transports. + */ +const struct fs_parameter_spec v9fs_param_spec[] = { + fsparam_string ("source", Opt_source), + fsparam_u32hex ("debug", Opt_debug), + fsparam_uid ("dfltuid", Opt_dfltuid), + fsparam_gid ("dfltgid", Opt_dfltgid), + fsparam_u32 ("afid", Opt_afid), + fsparam_string ("uname", Opt_uname), + fsparam_string ("aname", Opt_remotename), + fsparam_flag ("nodevmap", Opt_nodevmap), + fsparam_flag ("noxattr", Opt_noxattr), + fsparam_flag ("directio", Opt_directio), + fsparam_flag ("ignoreqv", Opt_ignoreqv), + fsparam_string ("cache", Opt_cache), + fsparam_string ("cachetag", Opt_cachetag), + fsparam_string ("access", Opt_access), + fsparam_flag ("posixacl", Opt_posixacl), + fsparam_u32 ("locktimeout", Opt_locktimeout), + fsparam_s32 ("negtimeout", Opt_negtimeout), + + /* client options */ + fsparam_u32 ("msize", Opt_msize), + fsparam_flag ("noextend", Opt_legacy), + fsparam_string ("trans", Opt_trans), + fsparam_enum ("version", Opt_version, p9_versions), + + /* fd transport options */ + fsparam_u32 ("rfdno", Opt_rfdno), + fsparam_u32 ("wfdno", Opt_wfdno), + + /* rdma transport options */ + fsparam_u32 ("sq", Opt_sq_depth), + fsparam_u32 ("rq", Opt_rq_depth), + fsparam_u32 ("timeout", Opt_timeout), + + /* fd and rdma transprt options */ + fsparam_u32 ("port", Opt_port), + fsparam_flag ("privport", Opt_privport), + {} +}; + +/* Interpret mount options for cache mode */ +static int get_cache_mode(char *s) +{ + int version = -EINVAL; + + if (!strcmp(s, "loose")) { + version = CACHE_SC_LOOSE; + p9_debug(P9_DEBUG_9P, "Cache mode: loose\n"); + } else if (!strcmp(s, "fscache")) { + version = CACHE_SC_FSCACHE; + p9_debug(P9_DEBUG_9P, "Cache mode: fscache\n"); + } else if (!strcmp(s, "mmap")) { + version = CACHE_SC_MMAP; + p9_debug(P9_DEBUG_9P, "Cache mode: mmap\n"); + } else if (!strcmp(s, "readahead")) { + version = CACHE_SC_READAHEAD; + p9_debug(P9_DEBUG_9P, "Cache mode: readahead\n"); + } else if (!strcmp(s, "none")) { + version = CACHE_SC_NONE; + p9_debug(P9_DEBUG_9P, "Cache mode: none\n"); + } else if (kstrtoint(s, 0, &version) != 0) { + version = -EINVAL; + pr_info("Unknown Cache mode or invalid value %s\n", s); + } + return version; +} + +/* + * Display the mount options in /proc/mounts. + */ +int v9fs_show_options(struct seq_file *m, struct dentry *root) +{ + struct v9fs_session_info *v9ses = root->d_sb->s_fs_info; + + if (v9ses->debug) + seq_printf(m, ",debug=%#x", v9ses->debug); + if (!uid_eq(v9ses->dfltuid, V9FS_DEFUID)) + seq_printf(m, ",dfltuid=%u", + from_kuid_munged(&init_user_ns, v9ses->dfltuid)); + if (!gid_eq(v9ses->dfltgid, V9FS_DEFGID)) + seq_printf(m, ",dfltgid=%u", + from_kgid_munged(&init_user_ns, v9ses->dfltgid)); + if (v9ses->afid != ~0) + seq_printf(m, ",afid=%u", v9ses->afid); + if (v9ses->flags & V9FS_NDENTRY_TIMEOUT_SET) + seq_printf(m, ",negtimeout=%d", + (int)v9ses->ndentry_timeout_ms); + if (strcmp(v9ses->uname, V9FS_DEFUSER) != 0) + seq_printf(m, ",uname=%s", v9ses->uname); + if (strcmp(v9ses->aname, V9FS_DEFANAME) != 0) + seq_printf(m, ",aname=%s", v9ses->aname); + if (v9ses->nodev) + seq_puts(m, ",nodevmap"); + if (v9ses->cache) + seq_printf(m, ",cache=%#x", v9ses->cache); +#ifdef CONFIG_9P_FSCACHE + if (v9ses->cachetag && (v9ses->cache & CACHE_FSCACHE)) + seq_printf(m, ",cachetag=%s", v9ses->cachetag); +#endif + + switch (v9ses->flags & V9FS_ACCESS_MASK) { + case V9FS_ACCESS_USER: + seq_puts(m, ",access=user"); + break; + case V9FS_ACCESS_ANY: + seq_puts(m, ",access=any"); + break; + case V9FS_ACCESS_CLIENT: + seq_puts(m, ",access=client"); + break; + case V9FS_ACCESS_SINGLE: + seq_printf(m, ",access=%u", + from_kuid_munged(&init_user_ns, v9ses->uid)); + break; + } + + if (v9ses->flags & V9FS_IGNORE_QV) + seq_puts(m, ",ignoreqv"); + if (v9ses->flags & V9FS_DIRECT_IO) + seq_puts(m, ",directio"); + if (v9ses->flags & V9FS_POSIX_ACL) + seq_puts(m, ",posixacl"); + + if (v9ses->flags & V9FS_NO_XATTR) + seq_puts(m, ",noxattr"); + + return p9_show_client_options(m, v9ses->clnt); +} + +/** + * v9fs_parse_param - parse a mount option into the filesystem context + * @fc: the filesystem context + * @param: the parameter to parse + * + * Return 0 upon success, -ERRNO upon failure. + */ +int v9fs_parse_param(struct fs_context *fc, struct fs_parameter *param) +{ + struct v9fs_context *ctx = fc->fs_private; + struct fs_parse_result result; + char *s; + int r; + int opt; + struct p9_client_opts *clnt = &ctx->client_opts; + struct p9_fd_opts *fd_opts = &ctx->fd_opts; + struct p9_rdma_opts *rdma_opts = &ctx->rdma_opts; + struct p9_session_opts *session_opts = &ctx->session_opts; + + opt = fs_parse(fc, v9fs_param_spec, param, &result); + if (opt < 0) { + /* + * We might like to report bad mount options here, but + * traditionally 9p has ignored unknown mount options + */ + if (opt == -ENOPARAM) + return 0; + + return opt; + } + + switch (opt) { + case Opt_source: + if (fc->source) { + pr_info("p9: multiple sources not supported\n"); + return -EINVAL; + } + fc->source = param->string; + param->string = NULL; + break; + case Opt_debug: + session_opts->debug = result.uint_32; +#ifdef CONFIG_NET_9P_DEBUG + p9_debug_level = result.uint_32; +#endif + break; + + case Opt_dfltuid: + session_opts->dfltuid = result.uid; + break; + case Opt_dfltgid: + session_opts->dfltgid = result.gid; + break; + case Opt_afid: + session_opts->afid = result.uint_32; + break; + case Opt_uname: + kfree(session_opts->uname); + session_opts->uname = param->string; + param->string = NULL; + break; + case Opt_remotename: + kfree(session_opts->aname); + session_opts->aname = param->string; + param->string = NULL; + break; + case Opt_nodevmap: + session_opts->nodev = 1; + break; + case Opt_noxattr: + session_opts->flags |= V9FS_NO_XATTR; + break; + case Opt_directio: + session_opts->flags |= V9FS_DIRECT_IO; + break; + case Opt_ignoreqv: + session_opts->flags |= V9FS_IGNORE_QV; + break; + case Opt_cachetag: +#ifdef CONFIG_9P_FSCACHE + kfree(session_opts->cachetag); + session_opts->cachetag = param->string; + param->string = NULL; +#endif + break; + case Opt_cache: + r = get_cache_mode(param->string); + if (r < 0) + return r; + session_opts->cache = r; + break; + case Opt_access: + s = param->string; + session_opts->flags &= ~V9FS_ACCESS_MASK; + if (strcmp(s, "user") == 0) { + session_opts->flags |= V9FS_ACCESS_USER; + } else if (strcmp(s, "any") == 0) { + session_opts->flags |= V9FS_ACCESS_ANY; + } else if (strcmp(s, "client") == 0) { + session_opts->flags |= V9FS_ACCESS_CLIENT; + } else { + uid_t uid; + + session_opts->flags |= V9FS_ACCESS_SINGLE; + r = kstrtouint(s, 10, &uid); + if (r) { + pr_info("Unknown access argument %s: %d\n", + param->string, r); + return r; + } + session_opts->uid = make_kuid(current_user_ns(), uid); + if (!uid_valid(session_opts->uid)) { + pr_info("Unknown uid %s\n", s); + return -EINVAL; + } + } + break; + + case Opt_posixacl: +#ifdef CONFIG_9P_FS_POSIX_ACL + session_opts->flags |= V9FS_POSIX_ACL; +#else + p9_debug(P9_DEBUG_ERROR, + "Not defined CONFIG_9P_FS_POSIX_ACL. Ignoring posixacl option\n"); +#endif + break; + + case Opt_locktimeout: + if (result.uint_32 < 1) { + p9_debug(P9_DEBUG_ERROR, + "locktimeout must be a greater than zero integer.\n"); + return -EINVAL; + } + session_opts->session_lock_timeout = (long)result.uint_32 * HZ; + break; + + case Opt_negtimeout: + session_opts->flags |= V9FS_NDENTRY_TIMEOUT_SET; + if (result.int_32 < 0) { + session_opts->ndentry_timeout_ms = + NDENTRY_TIMEOUT_NEVER; + } else { + session_opts->ndentry_timeout_ms = result.int_32; + } + break; + + /* Options for client */ + case Opt_msize: + if (result.uint_32 < 4096) { + p9_debug(P9_DEBUG_ERROR, "msize should be at least 4k\n"); + return -EINVAL; + } + if (result.uint_32 > INT_MAX) { + p9_debug(P9_DEBUG_ERROR, "msize too big\n"); + return -EINVAL; + } + clnt->msize = result.uint_32; + break; + case Opt_trans: + v9fs_put_trans(clnt->trans_mod); + clnt->trans_mod = v9fs_get_trans_by_name(param->string); + if (!clnt->trans_mod) { + pr_info("Could not find request transport: %s\n", + param->string); + return -EINVAL; + } + break; + case Opt_legacy: + clnt->proto_version = p9_proto_legacy; + break; + case Opt_version: + clnt->proto_version = result.uint_32; + p9_debug(P9_DEBUG_9P, "Protocol version: %s\n", param->string); + break; + /* Options for fd transport */ + case Opt_rfdno: + fd_opts->rfd = result.uint_32; + break; + case Opt_wfdno: + fd_opts->wfd = result.uint_32; + break; + /* Options for rdma transport */ + case Opt_sq_depth: + rdma_opts->sq_depth = result.uint_32; + break; + case Opt_rq_depth: + rdma_opts->rq_depth = result.uint_32; + break; + case Opt_timeout: + rdma_opts->timeout = result.uint_32; + break; + /* Options for both fd and rdma transports */ + case Opt_port: + fd_opts->port = result.uint_32; + rdma_opts->port = result.uint_32; + break; + case Opt_privport: + fd_opts->privport = true; + rdma_opts->port = true; + break; + } + + return 0; +} + +static void v9fs_apply_options(struct v9fs_session_info *v9ses, + struct fs_context *fc) +{ + struct v9fs_context *ctx = fc->fs_private; + + v9ses->debug = ctx->session_opts.debug; + v9ses->dfltuid = ctx->session_opts.dfltuid; + v9ses->dfltgid = ctx->session_opts.dfltgid; + v9ses->afid = ctx->session_opts.afid; + v9ses->uname = ctx->session_opts.uname; + ctx->session_opts.uname = NULL; + v9ses->aname = ctx->session_opts.aname; + ctx->session_opts.aname = NULL; + v9ses->nodev = ctx->session_opts.nodev; + /* + * Note that we must |= flags here as session_init already + * set basic flags. This adds in flags from parsed options. + * Default access flags must be cleared if session options + * changes them to avoid mangling the setting. + */ + if (ctx->session_opts.flags & V9FS_ACCESS_MASK) + v9ses->flags &= ~V9FS_ACCESS_MASK; + v9ses->flags |= ctx->session_opts.flags; +#ifdef CONFIG_9P_FSCACHE + v9ses->cachetag = ctx->session_opts.cachetag; + ctx->session_opts.cachetag = NULL; +#endif + v9ses->cache = ctx->session_opts.cache; + v9ses->uid = ctx->session_opts.uid; + v9ses->session_lock_timeout = ctx->session_opts.session_lock_timeout; + v9ses->ndentry_timeout_ms = ctx->session_opts.ndentry_timeout_ms; + + /* If negative dentry timeout has not been overridden set default for + * cache=loose + */ + if (!(v9ses->flags & V9FS_NDENTRY_TIMEOUT_SET) && + (v9ses->cache & CACHE_LOOSE)) + v9ses->ndentry_timeout_ms = CACHE_LOOSE_NDENTRY_TIMEOUT_DEFAULT; +} + +/** + * v9fs_session_init - initialize session + * @v9ses: session information structure + * @fc: the filesystem mount context + * + */ + +struct p9_fid *v9fs_session_init(struct v9fs_session_info *v9ses, + struct fs_context *fc) +{ + struct p9_fid *fid; + int rc = -ENOMEM; + + init_rwsem(&v9ses->rename_sem); + + v9ses->clnt = p9_client_create(fc); + if (IS_ERR(v9ses->clnt)) { + rc = PTR_ERR(v9ses->clnt); + p9_debug(P9_DEBUG_ERROR, "problem initializing 9p client\n"); + goto err_names; + } + + /* + * Initialize flags on the real v9ses. v9fs_apply_options below + * will |= the additional flags from parsed options. + */ + 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; + } + + v9fs_apply_options(v9ses, fc); + + 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 client only if the protocol is + * 9P2000.L and access is V9FS_ACCESS_CLIENT. + */ + v9ses->flags &= ~V9FS_ACL_MASK; + } + + fid = p9_client_attach(v9ses->clnt, NULL, v9ses->uname, INVALID_UID, + v9ses->aname); + if (IS_ERR(fid)) { + rc = PTR_ERR(fid); + p9_debug(P9_DEBUG_ERROR, "cannot attach\n"); + goto err_clnt; + } + + if ((v9ses->flags & V9FS_ACCESS_MASK) == V9FS_ACCESS_SINGLE) + fid->uid = v9ses->uid; + else + fid->uid = INVALID_UID; + +#ifdef CONFIG_9P_FSCACHE + /* register the session for caching */ + if (v9ses->cache & CACHE_FSCACHE) { + rc = v9fs_cache_session_get_cookie(v9ses, fc->source); + if (rc < 0) + goto err_clnt; + } +#endif + spin_lock(&v9fs_sessionlist_lock); + list_add(&v9ses->slist, &v9fs_sessionlist); + spin_unlock(&v9fs_sessionlist_lock); + + return fid; + +err_clnt: +#ifdef CONFIG_9P_FSCACHE + kfree(v9ses->cachetag); +#endif + p9_client_destroy(v9ses->clnt); +err_names: + kfree(v9ses->uname); + kfree(v9ses->aname); + return ERR_PTR(rc); +} + +/** + * v9fs_session_close - shutdown a session + * @v9ses: session information structure + * + */ + +void v9fs_session_close(struct v9fs_session_info *v9ses) +{ + if (v9ses->clnt) { + p9_client_destroy(v9ses->clnt); + v9ses->clnt = NULL; + } + +#ifdef CONFIG_9P_FSCACHE + fscache_relinquish_volume(v9fs_session_cache(v9ses), NULL, false); + kfree(v9ses->cachetag); +#endif + kfree(v9ses->uname); + kfree(v9ses->aname); + + spin_lock(&v9fs_sessionlist_lock); + list_del(&v9ses->slist); + spin_unlock(&v9fs_sessionlist_lock); +} + +/** + * v9fs_session_cancel - terminate a session + * @v9ses: session to terminate + * + * mark transport as disconnected and cancel all pending requests. + */ + +void v9fs_session_cancel(struct v9fs_session_info *v9ses) +{ + p9_debug(P9_DEBUG_ERROR, "cancel session %p\n", v9ses); + p9_client_disconnect(v9ses->clnt); +} + +/** + * 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. + */ + +void v9fs_session_begin_cancel(struct v9fs_session_info *v9ses) +{ + p9_debug(P9_DEBUG_ERROR, "begin cancel session %p\n", v9ses); + p9_client_begin_disconnect(v9ses->clnt); +} + +static struct kobject *v9fs_kobj; + +#ifdef CONFIG_9P_FSCACHE +/* + * List caches associated with a session + */ +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 + count, limit, "%s\n", v9ses->cachetag); + if (n < 0) { + count = n; + break; + } + + count += n; + limit -= n; + } + } + + spin_unlock(&v9fs_sessionlist_lock); + return count; +} + +static struct kobj_attribute v9fs_attr_cache = __ATTR_RO(caches); +#endif /* CONFIG_9P_FSCACHE */ + +static struct attribute *v9fs_attrs[] = { +#ifdef CONFIG_9P_FSCACHE + &v9fs_attr_cache.attr, +#endif + NULL, +}; + +static const struct attribute_group v9fs_attr_group = { + .attrs = v9fs_attrs, +}; + +/** + * v9fs_sysfs_init - Initialize the v9fs sysfs interface + * + */ + +static int __init v9fs_sysfs_init(void) +{ + int ret; + + v9fs_kobj = kobject_create_and_add("9p", fs_kobj); + if (!v9fs_kobj) + return -ENOMEM; + + ret = sysfs_create_group(v9fs_kobj, &v9fs_attr_group); + if (ret) { + kobject_put(v9fs_kobj); + return ret; + } + + return 0; +} + +/** + * v9fs_sysfs_cleanup - Unregister the v9fs sysfs interface + * + */ + +static void v9fs_sysfs_cleanup(void) +{ + sysfs_remove_group(v9fs_kobj, &v9fs_attr_group); + kobject_put(v9fs_kobj); +} + +static void v9fs_inode_init_once(void *foo) +{ + struct v9fs_inode *v9inode = (struct v9fs_inode *)foo; + + memset(&v9inode->qid, 0, sizeof(v9inode->qid)); + inode_init_once(&v9inode->netfs.inode); +} + +/** + * 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_ACCOUNT), + v9fs_inode_init_once); + if (!v9fs_inode_cache) + return -ENOMEM; + + return 0; +} + +/** + * v9fs_destroy_inode_cache - destroy the cache of 9P inode + * + */ +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); +} + +/** + * init_v9fs - Initialize module + * + */ + +static int __init init_v9fs(void) +{ + int err; + + pr_info("Installing v9fs 9p2000 file system support\n"); + /* TODO: Setup list of registered transport modules */ + + err = v9fs_init_inode_cache(); + if (err < 0) { + pr_err("Failed to register v9fs for caching\n"); + return err; + } + + 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; + } + + return 0; + +out_sysfs_cleanup: + v9fs_sysfs_cleanup(); + +out_cache: + v9fs_destroy_inode_cache(); + + return err; +} + +/** + * exit_v9fs - shutdown module + * + */ + +static void __exit exit_v9fs(void) +{ + v9fs_sysfs_cleanup(); + v9fs_destroy_inode_cache(); + unregister_filesystem(&v9fs_fs_type); +} + +module_init(init_v9fs) +module_exit(exit_v9fs) + +MODULE_AUTHOR("Latchesar Ionkov "); +MODULE_AUTHOR("Eric Van Hensbergen "); +MODULE_AUTHOR("Ron Minnich "); +MODULE_DESCRIPTION("9P Client File System"); +MODULE_LICENSE("GPL"); diff --git a/fs/9p/v9fs.h b/fs/9p/v9fs.h new file mode 100644 index 000000000..a462bcbfc --- /dev/null +++ b/fs/9p/v9fs.h @@ -0,0 +1,269 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * V9FS definitions. + * + * Copyright (C) 2004-2008 by Eric Van Hensbergen + * Copyright (C) 2002 by Ron Minnich + */ +#ifndef FS_9P_V9FS_H +#define FS_9P_V9FS_H + +#include +#include +#include +#include +#include + +/** + * 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 + * @V9FS_NDENTRY_TIMEOUT_SET: Has negative dentry timeout retention time been + * overridden by negtimeout mount option + * + * 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 + +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, + V9FS_NO_XATTR = 0x40, + V9FS_IGNORE_QV = 0x80, /* ignore qid.version for cache hints */ + V9FS_DIRECT_IO = 0x100, + V9FS_SYNC = 0x200, + V9FS_NDENTRY_TIMEOUT_SET = 0x400, +}; + +/** + * enum p9_cache_shortcuts - human readable cache preferences + * @CACHE_SC_NONE: disable all caches + * @CACHE_SC_READAHEAD: only provide caching for readahead + * @CACHE_SC_MMAP: provide caching to enable mmap + * @CACHE_SC_LOOSE: non-coherent caching for files and meta data + * @CACHE_SC_FSCACHE: persistent non-coherent caching for files and meta-data + * + */ + +enum p9_cache_shortcuts { + CACHE_SC_NONE = 0b00000000, + CACHE_SC_READAHEAD = 0b00000001, + CACHE_SC_MMAP = 0b00000101, + CACHE_SC_LOOSE = 0b00001111, + CACHE_SC_FSCACHE = 0b10001111, +}; + +/** + * enum p9_cache_bits - possible values of ->cache + * @CACHE_NONE: caches disabled + * @CACHE_FILE: file caching (open to close) + * @CACHE_META: meta-data and directory caching + * @CACHE_WRITEBACK: write-back caching for files + * @CACHE_LOOSE: don't check cache consistency + * @CACHE_FSCACHE: local persistent caches + * + */ + +enum p9_cache_bits { + CACHE_NONE = 0b00000000, + CACHE_FILE = 0b00000001, + CACHE_META = 0b00000010, + CACHE_WRITEBACK = 0b00000100, + CACHE_LOOSE = 0b00001000, + CACHE_FSCACHE = 0b10000000, +}; + +/** + * 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_bits + * @ndentry_timeout: Negative dentry lookup cache retention time in ms + * @cachetag: the tag of the cache associated with this session + * @fscache: session cookie associated with FS-Cache + * @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 + * @ndentry_timeout_ms: Negative dentry caching retention time + * + * 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 flags; + unsigned char nodev; + unsigned short debug; + unsigned int afid; + unsigned int cache; + unsigned int ndentry_timeout_ms; +#ifdef CONFIG_9P_FSCACHE + char *cachetag; + struct fscache_volume *fscache; +#endif + + 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 rw_semaphore rename_sem; + long session_lock_timeout; /* retry interval for blocking locks */ +}; + +#define NDENTRY_TIMEOUT_NEVER (-1U) + +/* cache_validity flags */ +#define V9FS_INO_INVALID_ATTR 0x01 + +struct v9fs_inode { + struct netfs_inode netfs; /* Netfslib context and vfs inode */ + struct p9_qid qid; + unsigned int cache_validity; + struct mutex v_mutex; +}; + +static inline struct v9fs_inode *V9FS_I(const struct inode *inode) +{ + return container_of(inode, struct v9fs_inode, netfs.inode); +} + +static inline struct fscache_cookie *v9fs_inode_cookie(struct v9fs_inode *v9inode) +{ +#ifdef CONFIG_9P_FSCACHE + return netfs_i_cookie(&v9inode->netfs); +#else + return NULL; +#endif +} + +static inline struct fscache_volume *v9fs_session_cache(struct v9fs_session_info *v9ses) +{ +#ifdef CONFIG_9P_FSCACHE + return v9ses->fscache; +#else + return NULL; +#endif +} + +extern const struct fs_parameter_spec v9fs_param_spec[]; + +extern int v9fs_parse_param(struct fs_context *fc, struct fs_parameter *param); +extern int v9fs_show_options(struct seq_file *m, struct dentry *root); + +struct p9_fid *v9fs_session_init(struct v9fs_session_info *v9ses, + struct fs_context *fc); +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 mnt_idmap *idmap, + struct inode *old_dir, struct dentry *old_dentry, + struct inode *new_dir, struct dentry *new_dentry, + unsigned int flags); +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 const struct netfs_request_ops v9fs_req_ops; +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_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(const 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); +} + +#endif diff --git a/fs/9p/v9fs_vfs.h b/fs/9p/v9fs_vfs.h new file mode 100644 index 000000000..1856d91f8 --- /dev/null +++ b/fs/9p/v9fs_vfs.h @@ -0,0 +1,93 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * V9FS VFS extensions. + * + * Copyright (C) 2004 by Eric Van Hensbergen + * Copyright (C) 2002 by Ron Minnich + */ +#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. + * the plan9 approach is superior as it provides an atomic + * open. + * we track the create fid here. When the file is opened, if fidopen is + * non-zero, we use the fid and can skip some steps. + * there may be a better way to do this, but I don't know it. + * one BAD way is to clunk the fid on create, then open it again: + * you lose the atomicity of file open + */ + +/* special case: + * unlink calls remove, which is an implicit clunk. So we have to track + * that kind of thing so that we don't try to clunk a dead fid. + */ +#define P9_LOCK_TIMEOUT (30*HZ) + +/* flags for v9fs_stat2inode() & v9fs_stat2inode_dotl() */ +#define V9FS_STAT2INODE_KEEP_ISIZE 1 + +/** + * struct v9fs_dentry - v9fs specific dentry data + * @head: List of fid associated with this dentry + * @expire_time: Lookup cache expiration time for negative dentries + * @rcu: used by kfree_rcu to schedule clean up job + */ +struct v9fs_dentry { + struct hlist_head head; + u64 expire_time; + struct rcu_head rcu; +}; +#define to_v9fs_dentry(d) ((struct v9fs_dentry *)((d)->d_fsdata)) + +extern struct file_system_type v9fs_fs_type; +extern const struct address_space_operations v9fs_addr_operations; +extern const struct file_operations v9fs_file_operations; +extern const struct file_operations v9fs_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 void v9fs_ndentry_refresh_timeout(struct dentry *dentry); +extern void v9fs_dentry_fid_remove(struct dentry *dentry); +extern const struct dentry_operations v9fs_cached_dentry_operations; +extern struct kmem_cache *v9fs_inode_cache; + +struct inode *v9fs_alloc_inode(struct super_block *sb); +void v9fs_free_inode(struct inode *inode); +void v9fs_set_netfs_context(struct inode *inode); +int v9fs_init_inode(struct v9fs_session_info *v9ses, + struct inode *inode, umode_t mode, dev_t rdev); +void v9fs_evict_inode(struct inode *inode); +#if (BITS_PER_LONG == 32) +#define QID2INO(q) ((ino_t) (((q)->path+2) ^ (((q)->path) >> 32))) +#else +#define QID2INO(q) ((ino_t) ((q)->path+2)) +#endif + +void v9fs_stat2inode(struct p9_wstat *stat, struct inode *inode, + struct super_block *sb, unsigned int flags); +void v9fs_stat2inode_dotl(struct p9_stat_dotl *stat, struct inode *inode, + unsigned int flags); +int v9fs_dir_release(struct inode *inode, struct file *filp); +int v9fs_file_open(struct inode *inode, struct file *file); +int v9fs_uflags2omode(int uflags, int extended); + +void v9fs_blank_wstat(struct p9_wstat *wstat); +int v9fs_vfs_setattr_dotl(struct mnt_idmap *idmap, + struct dentry *dentry, struct iattr *iattr); +int v9fs_file_fsync_dotl(struct file *filp, loff_t start, loff_t end, + int datasync); +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; +} + +int v9fs_open_to_dotl_flags(int flags); + +#endif diff --git a/fs/9p/vfs_addr.c b/fs/9p/vfs_addr.c new file mode 100644 index 000000000..1ac0b3dcc --- /dev/null +++ b/fs/9p/vfs_addr.c @@ -0,0 +1,201 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * This file contians vfs address (mmap) ops for 9P2000. + * + * Copyright (C) 2005 by Eric Van Hensbergen + * Copyright (C) 2002 by Ron Minnich + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "v9fs.h" +#include "v9fs_vfs.h" +#include "cache.h" +#include "fid.h" + +/* + * Writeback calls this when it finds a folio that needs uploading. This isn't + * called if writeback only has copy-to-cache to deal with. + */ +static void v9fs_begin_writeback(struct netfs_io_request *wreq) +{ + struct p9_fid *fid; + + fid = v9fs_fid_find_inode(wreq->inode, true, INVALID_UID, true); + if (!fid) { + WARN_ONCE(1, "folio expected an open fid inode->i_ino=%llx\n", + wreq->inode->i_ino); + return; + } + + wreq->wsize = fid->clnt->msize - P9_IOHDRSZ; + if (fid->iounit) + wreq->wsize = min(wreq->wsize, fid->iounit); + wreq->netfs_priv = fid; + wreq->io_streams[0].avail = true; +} + +/* + * Issue a subrequest to write to the server. + */ +static void v9fs_issue_write(struct netfs_io_subrequest *subreq) +{ + struct p9_fid *fid = subreq->rreq->netfs_priv; + int err, len; + + len = p9_client_write(fid, subreq->start, &subreq->io_iter, &err); + if (len > 0) + __set_bit(NETFS_SREQ_MADE_PROGRESS, &subreq->flags); + netfs_write_subrequest_terminated(subreq, len ?: err); +} + +/** + * v9fs_issue_read - Issue a read from 9P + * @subreq: The read to make + */ +static void v9fs_issue_read(struct netfs_io_subrequest *subreq) +{ + struct netfs_io_request *rreq = subreq->rreq; + struct p9_fid *fid = rreq->netfs_priv; + char *target; + unsigned long long pos = subreq->start + subreq->transferred; + int total = 0, err, len, n; + + if (S_ISLNK(rreq->inode->i_mode)) { + /* p9_client_readlink() must not be called for legacy protocols + * 9p2000 or 9p2000.u. + */ + BUG_ON(!p9_is_proto_dotl(fid->clnt)); + if (WARN_ON_ONCE(pos)) { + /* reading a link at a non null offset should + * not happen + */ + err = -EIO; + goto fill_subreq; + } + err = p9_client_readlink(fid, &target); + if (err != 0) + goto fill_subreq; + len = strlen(target); + n = copy_to_iter(target, len, &subreq->io_iter); + kfree(target); + total = n; + } else { + total = p9_client_read(fid, pos, &subreq->io_iter, &err); + } + +fill_subreq: + /* if we just extended the file size, any portion not in + * cache won't be on server and is zeroes */ + if (subreq->rreq->origin != NETFS_UNBUFFERED_READ && + subreq->rreq->origin != NETFS_DIO_READ) + __set_bit(NETFS_SREQ_CLEAR_TAIL, &subreq->flags); + if (pos + total >= i_size_read(rreq->inode)) + __set_bit(NETFS_SREQ_HIT_EOF, &subreq->flags); + if (!err && total) { + subreq->transferred += total; + __set_bit(NETFS_SREQ_MADE_PROGRESS, &subreq->flags); + } + + subreq->error = err; + netfs_read_subreq_terminated(subreq); +} + +/** + * v9fs_init_request - Initialise a request + * @rreq: The read request + * @file: The file being read from + */ +static int v9fs_init_request(struct netfs_io_request *rreq, struct file *file) +{ + struct p9_fid *fid; + struct dentry *dentry; + bool writing = (rreq->origin == NETFS_READ_FOR_WRITE || + rreq->origin == NETFS_WRITETHROUGH || + rreq->origin == NETFS_UNBUFFERED_WRITE || + rreq->origin == NETFS_DIO_WRITE); + + if (rreq->origin == NETFS_WRITEBACK) + return 0; /* We don't get the write handle until we find we + * have actually dirty data and not just + * copy-to-cache data. + */ + + if (file) { + fid = file->private_data; + if (!fid) + goto no_fid; + p9_fid_get(fid); + } else if (S_ISLNK(rreq->inode->i_mode)) { + dentry = d_find_any_alias(rreq->inode); + if (!dentry) + goto no_fid; + fid = v9fs_fid_lookup(dentry); + dput(dentry); + if (IS_ERR(fid)) + goto no_fid; + } else { + fid = v9fs_fid_find_inode(rreq->inode, writing, INVALID_UID, true); + if (!fid) + goto no_fid; + } + + rreq->wsize = fid->clnt->msize - P9_IOHDRSZ; + if (fid->iounit) + rreq->wsize = min(rreq->wsize, fid->iounit); + + /* we might need to read from a fid that was opened write-only + * for read-modify-write of page cache, use the writeback fid + * for that */ + WARN_ON(rreq->origin == NETFS_READ_FOR_WRITE && !(fid->mode & P9_ORDWR)); + rreq->netfs_priv = fid; + return 0; + +no_fid: + WARN_ONCE(1, "folio expected an open fid inode->i_ino=%llx\n", + rreq->inode->i_ino); + return -EINVAL; +} + +/** + * v9fs_free_request - Cleanup request initialized by v9fs_init_rreq + * @rreq: The I/O request to clean up + */ +static void v9fs_free_request(struct netfs_io_request *rreq) +{ + struct p9_fid *fid = rreq->netfs_priv; + + p9_fid_put(fid); +} + +const struct netfs_request_ops v9fs_req_ops = { + .init_request = v9fs_init_request, + .free_request = v9fs_free_request, + .issue_read = v9fs_issue_read, + .begin_writeback = v9fs_begin_writeback, + .issue_write = v9fs_issue_write, +}; + +const struct address_space_operations v9fs_addr_operations = { + .read_folio = netfs_read_folio, + .readahead = netfs_readahead, + .dirty_folio = netfs_dirty_folio, + .release_folio = netfs_release_folio, + .invalidate_folio = netfs_invalidate_folio, + .direct_IO = noop_direct_IO, + .writepages = netfs_writepages, + .migrate_folio = filemap_migrate_folio, +}; diff --git a/fs/9p/vfs_dentry.c b/fs/9p/vfs_dentry.c new file mode 100644 index 000000000..e549e2226 --- /dev/null +++ b/fs/9p/vfs_dentry.c @@ -0,0 +1,228 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * This file contians vfs dentry ops for the 9P2000 protocol. + * + * Copyright (C) 2004 by Eric Van Hensbergen + * Copyright (C) 2002 by Ron Minnich + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "v9fs.h" +#include "v9fs_vfs.h" +#include "fid.h" + +/** + * v9fs_ndentry_is_expired - Check if negative dentry lookup has expired + * + * This should be called to know if a negative dentry should be removed from + * cache. + * + * @dentry: dentry in question + * + */ +static bool v9fs_ndentry_is_expired(struct dentry const *dentry) +{ + struct v9fs_session_info *v9ses = v9fs_dentry2v9ses(dentry); + struct v9fs_dentry *v9fs_dentry = to_v9fs_dentry(dentry); + + if (v9ses->ndentry_timeout_ms == NDENTRY_TIMEOUT_NEVER) + return false; + + return time_before_eq64(v9fs_dentry->expire_time, get_jiffies_64()); +} + +/** + * v9fs_ndentry_refresh_timeout - Refresh negative dentry lookup cache timeout + * + * This should be called when a look up yields a negative entry. + * + * @dentry: dentry in question + * + */ +void v9fs_ndentry_refresh_timeout(struct dentry *dentry) +{ + struct v9fs_session_info *v9ses = v9fs_dentry2v9ses(dentry); + struct v9fs_dentry *v9fs_dentry = to_v9fs_dentry(dentry); + + if (v9ses->ndentry_timeout_ms == NDENTRY_TIMEOUT_NEVER) + return; + + v9fs_dentry->expire_time = get_jiffies_64() + + msecs_to_jiffies(v9ses->ndentry_timeout_ms); +} + +/** + * v9fs_cached_dentry_delete - called when dentry refcount equals 0 + * @dentry: dentry in question + * + */ +static int v9fs_cached_dentry_delete(const struct dentry *dentry) +{ + p9_debug(P9_DEBUG_VFS, " dentry: %pd (%p)\n", + dentry, dentry); + + if (!d_really_is_negative(dentry)) + return 0; + + return v9fs_ndentry_is_expired(dentry); +} + +static void __v9fs_dentry_fid_remove(struct dentry *dentry) +{ + struct v9fs_dentry *v9fs_dentry = to_v9fs_dentry(dentry); + struct hlist_node *p, *n; + struct hlist_head head; + + p9_debug(P9_DEBUG_VFS, " dentry: %pd (%p)\n", + dentry, dentry); + + spin_lock(&dentry->d_lock); + hlist_move_list(&v9fs_dentry->head, &head); + spin_unlock(&dentry->d_lock); + + hlist_for_each_safe(p, n, &head) + p9_fid_put(hlist_entry(p, struct p9_fid, dlist)); +} + +/** + * v9fs_dentry_fid_remove - Release all dentry's fids + * @dentry: dentry in question + * + */ +void v9fs_dentry_fid_remove(struct dentry *dentry) +{ + __v9fs_dentry_fid_remove(dentry); +} + +/** + * v9fs_dentry_init - Initialize v9fs dentry data + * @dentry: dentry in question + * + */ +static int v9fs_dentry_init(struct dentry *dentry) +{ + struct v9fs_dentry *v9fs_dentry = kzalloc(sizeof(*v9fs_dentry), + GFP_KERNEL); + + if (!v9fs_dentry) + return -ENOMEM; + + INIT_HLIST_HEAD(&v9fs_dentry->head); + dentry->d_fsdata = (void *)v9fs_dentry; + return 0; +} + +/** + * v9fs_dentry_release - called when dentry is going to be freed + * @dentry: dentry that is being released + * + */ +static void v9fs_dentry_release(struct dentry *dentry) +{ + struct v9fs_dentry *v9fs_dentry = to_v9fs_dentry(dentry); + + __v9fs_dentry_fid_remove(dentry); + kfree_rcu(v9fs_dentry, rcu); +} + +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 = d_inode(dentry); + if (!inode) + return !v9fs_ndentry_is_expired(dentry); + + 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)) { + p9_debug( + P9_DEBUG_VFS, + "v9fs_fid_lookup: dentry = %pd (%p), got error %pe\n", + dentry, dentry, 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); + p9_fid_put(fid); + + if (retval == -ENOENT) { + p9_debug(P9_DEBUG_VFS, "dentry: %pd (%p) invalidated due to ENOENT\n", + dentry, dentry); + return 0; + } + if (v9inode->cache_validity & V9FS_INO_INVALID_ATTR) { + p9_debug(P9_DEBUG_VFS, "dentry: %pd (%p) invalidated due to type change\n", + dentry, dentry); + return 0; + } + if (retval < 0) { + p9_debug(P9_DEBUG_VFS, + "refresh inode: dentry = %pd (%p), got error %pe\n", + dentry, dentry, ERR_PTR(retval)); + return retval; + } + } + p9_debug(P9_DEBUG_VFS, "dentry: %pd (%p) is valid\n", dentry, dentry); + return 1; +} + +static int v9fs_lookup_revalidate(struct inode *dir, const struct qstr *name, + struct dentry *dentry, unsigned int flags) +{ + return __v9fs_lookup_revalidate(dentry, flags); +} + +static bool v9fs_dentry_unalias_trylock(const struct dentry *dentry) +{ + struct v9fs_session_info *v9ses = v9fs_dentry2v9ses(dentry); + return down_write_trylock(&v9ses->rename_sem); +} + +static void v9fs_dentry_unalias_unlock(const struct dentry *dentry) +{ + struct v9fs_session_info *v9ses = v9fs_dentry2v9ses(dentry); + up_write(&v9ses->rename_sem); +} + +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_init = v9fs_dentry_init, + .d_release = v9fs_dentry_release, + .d_unalias_trylock = v9fs_dentry_unalias_trylock, + .d_unalias_unlock = v9fs_dentry_unalias_unlock, +}; + +const struct dentry_operations v9fs_dentry_operations = { + .d_init = v9fs_dentry_init, + .d_release = v9fs_dentry_release, + .d_unalias_trylock = v9fs_dentry_unalias_trylock, + .d_unalias_unlock = v9fs_dentry_unalias_unlock, +}; diff --git a/fs/9p/vfs_dir.c b/fs/9p/vfs_dir.c new file mode 100644 index 000000000..323f85352 --- /dev/null +++ b/fs/9p/vfs_dir.c @@ -0,0 +1,266 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * This file contains vfs directory ops for the 9P2000 protocol. + * + * Copyright (C) 2004 by Eric Van Hensbergen + * Copyright (C) 2002 by Ron Minnich + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "v9fs.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 + * @offset: file position the data at @head corresponds to + * @buf: dirread buffer + * + * private structure for keeping track of readdir + * allocated on demand + */ + +struct p9_rdir { + int head; + int tail; + loff_t offset; + uint8_t buf[]; +}; + +/** + * dt_type - return file type + * @mistat: mistat structure + * + */ + +static inline int dt_type(struct p9_wstat *mistat) +{ + unsigned long perm = mistat->mode; + int rettype = DT_REG; + + if (perm & P9_DMDIR) + rettype = DT_DIR; + if (perm & P9_DMSYMLINK) + rettype = DT_LNK; + + return rettype; +} + +/** + * v9fs_alloc_rdir_buf - Allocate buffer used for read and readdir + * @filp: opened file structure + * @buflen: Length in bytes of buffer to allocate + * + */ + +static struct p9_rdir *v9fs_alloc_rdir_buf(struct file *filp, int buflen) +{ + struct p9_fid *fid = filp->private_data; + + if (!fid->rdir) + fid->rdir = kvzalloc(sizeof(struct p9_rdir) + buflen, GFP_KERNEL); + return fid->rdir; +} + +/** + * v9fs_dir_readdir - iterate through a directory + * @file: opened file structure + * @ctx: actor we feed the entries to + * + */ + +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; + struct p9_rdir *rdir; + struct kvec kvec; + + p9_debug(P9_DEBUG_VFS, "name %pD\n", file); + fid = file->private_data; + + buflen = fid->clnt->msize - P9_IOHDRSZ; + + rdir = v9fs_alloc_rdir_buf(file, buflen); + if (!rdir) + return -ENOMEM; + kvec.iov_base = rdir->buf; + kvec.iov_len = buflen; + + if (rdir->head < rdir->tail && rdir->offset != ctx->pos) + rdir->head = rdir->tail = 0; + + while (1) { + if (rdir->tail == rdir->head) { + struct iov_iter to; + int n; + + iov_iter_kvec(&to, ITER_DEST, &kvec, 1, buflen); + n = p9_client_read(file->private_data, ctx->pos, &to, + &err); + if (err) + return err; + if (n == 0) + return 0; + + rdir->head = 0; + rdir->tail = n; + rdir->offset = ctx->pos; + } + while (rdir->head < rdir->tail) { + err = p9stat_read(fid->clnt, rdir->buf + rdir->head, + rdir->tail - rdir->head, &st); + if (err <= 0) { + p9_debug(P9_DEBUG_VFS, "returned %d\n", err); + return -EIO; + } + + over = !dir_emit(ctx, st.name, strlen(st.name), + QID2INO(&st.qid), dt_type(&st)); + p9stat_free(&st); + if (over) + return 0; + + rdir->head += err; + ctx->pos += err; + rdir->offset = ctx->pos; + } + } +} + +/** + * 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 %pD\n", file); + fid = file->private_data; + + buflen = fid->clnt->msize - P9_READDIRHDRSZ; + + rdir = v9fs_alloc_rdir_buf(file, buflen); + if (!rdir) + return -ENOMEM; + + if (rdir->head < rdir->tail && rdir->offset != ctx->pos) + rdir->head = rdir->tail = 0; + + 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; + rdir->offset = ctx->pos; + } + + while (rdir->head < rdir->tail) { + + 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; + } + + if (!dir_emit(ctx, curdirent.d_name, + strlen(curdirent.d_name), + QID2INO(&curdirent.qid), + curdirent.d_type)) + return 0; + + ctx->pos = curdirent.d_off; + rdir->head += err; + rdir->offset = ctx->pos; + } + } +} + + +/** + * v9fs_dir_release - close a directory or a file + * @inode: inode of the directory or file + * @filp: file pointer to a directory or file + * + */ + +int v9fs_dir_release(struct inode *inode, struct file *filp) +{ + struct v9fs_inode *v9inode = V9FS_I(inode); + struct p9_fid *fid; + __le32 version; + loff_t i_size; + int retval = 0, put_err; + + fid = filp->private_data; + p9_debug(P9_DEBUG_VFS, "inode: %p filp: %p fid: %d\n", + inode, filp, fid ? fid->fid : -1); + + if (fid) { + if ((S_ISREG(inode->i_mode)) && (filp->f_mode & FMODE_WRITE)) + retval = filemap_fdatawrite(inode->i_mapping); + + spin_lock(&inode->i_lock); + hlist_del(&fid->ilist); + spin_unlock(&inode->i_lock); + put_err = p9_fid_put(fid); + retval = retval < 0 ? retval : put_err; + } + + if ((filp->f_mode & FMODE_WRITE)) { + version = cpu_to_le32(v9inode->qid.version); + i_size = i_size_read(inode); + fscache_unuse_cookie(v9fs_inode_cookie(v9inode), + &version, &i_size); + } else { + fscache_unuse_cookie(v9fs_inode_cookie(v9inode), NULL, NULL); + } + return retval; +} + +const struct file_operations v9fs_dir_operations = { + .read = generic_read_dir, + .llseek = generic_file_llseek, + .iterate_shared = v9fs_dir_readdir, + .open = v9fs_file_open, + .release = v9fs_dir_release, +}; + +const struct file_operations v9fs_dir_operations_dotl = { + .read = generic_read_dir, + .llseek = generic_file_llseek, + .iterate_shared = 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 new file mode 100644 index 000000000..cddfb4b7c --- /dev/null +++ b/fs/9p/vfs_file.c @@ -0,0 +1,534 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * This file contians vfs file ops for 9P2000. + * + * Copyright (C) 2004 by Eric Van Hensbergen + * Copyright (C) 2002 by Ron Minnich + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "v9fs.h" +#include "v9fs_vfs.h" +#include "fid.h" +#include "cache.h" + +static const struct vm_operations_struct v9fs_mmap_file_vm_ops; + +/** + * v9fs_file_open - open a file (or directory) + * @inode: inode to be opened + * @file: file being opened + * + */ + +int v9fs_file_open(struct inode *inode, struct file *file) +{ + int err; + struct v9fs_session_info *v9ses; + struct p9_fid *fid; + int omode; + int o_append; + + p9_debug(P9_DEBUG_VFS, "inode: %p file: %p\n", inode, file); + v9ses = v9fs_inode2v9ses(inode); + if (v9fs_proto_dotl(v9ses)) { + omode = v9fs_open_to_dotl_flags(file->f_flags); + o_append = P9_DOTL_APPEND; + } else { + omode = v9fs_uflags2omode(file->f_flags, + v9fs_proto_dotu(v9ses)); + o_append = P9_OAPPEND; + } + fid = file->private_data; + if (!fid) { + fid = v9fs_fid_clone(file_dentry(file)); + if (IS_ERR(fid)) + return PTR_ERR(fid); + + if ((v9ses->cache & CACHE_WRITEBACK) && (omode & P9_OWRITE)) { + int writeback_omode = (omode & ~(P9_OWRITE | o_append)) | P9_ORDWR; + + p9_debug(P9_DEBUG_CACHE, "write-only file with writeback enabled, try opening O_RDWR\n"); + + err = p9_client_open(fid, writeback_omode); + if (err < 0) { + p9_debug(P9_DEBUG_CACHE, "could not open O_RDWR, disabling caches\n"); + err = p9_client_open(fid, omode); + fid->mode |= P9L_DIRECT; + } + } else { + err = p9_client_open(fid, omode); + } + if (err < 0) { + p9_fid_put(fid); + return err; + } + if ((file->f_flags & O_APPEND) && + (!v9fs_proto_dotu(v9ses) && !v9fs_proto_dotl(v9ses))) + generic_file_llseek(file, 0, SEEK_END); + + file->private_data = fid; + } + +#ifdef CONFIG_9P_FSCACHE + if (v9ses->cache & CACHE_FSCACHE) + fscache_use_cookie(v9fs_inode_cookie(V9FS_I(inode)), + file->f_mode & FMODE_WRITE); +#endif + v9fs_fid_add_modes(fid, v9ses->flags, v9ses->cache, file->f_flags); + v9fs_open_fid_add(inode, &fid); + return 0; +} + +/** + * v9fs_file_lock - lock a file (or directory) + * @filp: file to be locked + * @cmd: lock command + * @fl: file lock structure + * + * Bugs: this looks like a local only lock, we should extend into 9P + * by using open exclusive + */ + +static int v9fs_file_lock(struct file *filp, int cmd, struct file_lock *fl) +{ + struct inode *inode = file_inode(filp); + + p9_debug(P9_DEBUG_VFS, "filp: %p lock: %p\n", filp, fl); + + if ((IS_SETLK(cmd) || IS_SETLKW(cmd)) && fl->c.flc_type != F_UNLCK) { + filemap_write_and_wait(inode->i_mapping); + invalidate_mapping_pages(&inode->i_data, 0, -1); + } + + return 0; +} + +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 = P9_LOCK_ERROR; + int res = 0; + struct v9fs_session_info *v9ses; + + fid = filp->private_data; + BUG_ON(fid == NULL); + + BUG_ON((fl->c.flc_flags & FL_POSIX) != FL_POSIX); + + res = locks_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->c.flc_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->c.flc_pid; + flock.client_id = fid->clnt->name; + if (IS_SETLKW(cmd)) + flock.flags = P9_LOCK_FLAGS_BLOCK; + + v9ses = v9fs_inode2v9ses(file_inode(filp)); + + /* + * 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) + goto out_unlock; + + if (status != P9_LOCK_BLOCKED) + break; + if (status == P9_LOCK_BLOCKED && !IS_SETLKW(cmd)) + break; + if (schedule_timeout_interruptible(v9ses->session_lock_timeout) + != 0) + break; + /* + * p9_client_lock_dotl overwrites flock.client_id with the + * server message, free and reuse the client name + */ + if (flock.client_id != fid->clnt->name) { + kfree(flock.client_id); + flock.client_id = fid->clnt->name; + } + } + + /* map 9p status to VFS status */ + switch (status) { + case P9_LOCK_SUCCESS: + res = 0; + break; + case P9_LOCK_BLOCKED: + res = -EAGAIN; + break; + default: + p9_debug(P9_DEBUG_ERROR, "unknown lock status code: %d\n", status); + fallthrough; + case P9_LOCK_ERROR: + case P9_LOCK_GRACE: + res = -ENOLCK; + break; + } + +out_unlock: + /* + * incase server returned error for lock request, revert + * it locally + */ + if (res < 0 && fl->c.flc_type != F_UNLCK) { + unsigned char type = fl->c.flc_type; + + fl->c.flc_type = F_UNLCK; + /* Even if this fails we want to return the remote error */ + locks_lock_file_wait(filp, fl); + fl->c.flc_type = type; + } + if (flock.client_id != fid->clnt->name) + kfree(flock.client_id); +out: + return res; +} + +static int v9fs_file_getlock(struct file *filp, struct file_lock *fl) +{ + struct p9_getlock glock; + struct p9_fid *fid; + int res = 0; + + 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->c.flc_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->c.flc_pid; + glock.client_id = fid->clnt->name; + + res = p9_client_getlock_dotl(fid, &glock); + if (res < 0) + goto out; + /* map 9p lock type to os lock type */ + switch (glock.type) { + case P9_LOCK_TYPE_RDLCK: + fl->c.flc_type = F_RDLCK; + break; + case P9_LOCK_TYPE_WRLCK: + fl->c.flc_type = F_WRLCK; + break; + case P9_LOCK_TYPE_UNLCK: + fl->c.flc_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->c.flc_pid = -glock.proc_id; + } +out: + if (glock.client_id != fid->clnt->name) + kfree(glock.client_id); + return res; +} + +/** + * 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: %pD\n", + filp, cmd, fl, filp); + + if ((IS_SETLK(cmd) || IS_SETLKW(cmd)) && fl->c.flc_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; + return ret; +} + +/** + * v9fs_file_flock_dotl - lock a file + * @filp: file to be locked + * @cmd: lock command + * @fl: file lock structure + * + */ + +static int v9fs_file_flock_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: %pD\n", + filp, cmd, fl, filp); + + if (!(fl->c.flc_flags & FL_FLOCK)) + goto out_err; + + if ((IS_SETLK(cmd) || IS_SETLKW(cmd)) && fl->c.flc_type != F_UNLCK) { + filemap_write_and_wait(inode->i_mapping); + invalidate_mapping_pages(&inode->i_data, 0, -1); + } + /* Convert flock to posix lock */ + fl->c.flc_flags |= FL_POSIX; + fl->c.flc_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; +} + +/** + * v9fs_file_read_iter - read from a file + * @iocb: The operation parameters + * @to: The buffer to read into + * + */ +static ssize_t +v9fs_file_read_iter(struct kiocb *iocb, struct iov_iter *to) +{ + struct p9_fid *fid = iocb->ki_filp->private_data; + + p9_debug(P9_DEBUG_VFS, "fid %d count %zu offset %lld\n", + fid->fid, iov_iter_count(to), iocb->ki_pos); + + if (fid->mode & P9L_DIRECT) + return netfs_unbuffered_read_iter(iocb, to); + + p9_debug(P9_DEBUG_VFS, "(cached)\n"); + return netfs_file_read_iter(iocb, to); +} + +/* + * v9fs_file_splice_read - splice-read from a file + * @in: The 9p file to read from + * @ppos: Where to find/update the file position + * @pipe: The pipe to splice into + * @len: The maximum amount of data to splice + * @flags: SPLICE_F_* flags + */ +static ssize_t v9fs_file_splice_read(struct file *in, loff_t *ppos, + struct pipe_inode_info *pipe, + size_t len, unsigned int flags) +{ + struct p9_fid *fid = in->private_data; + + p9_debug(P9_DEBUG_VFS, "fid %d count %zu offset %lld\n", + fid->fid, len, *ppos); + + if (fid->mode & P9L_DIRECT) + return copy_splice_read(in, ppos, pipe, len, flags); + return filemap_splice_read(in, ppos, pipe, len, flags); +} + +/** + * v9fs_file_write_iter - write to a file + * @iocb: The operation parameters + * @from: The data to write + * + */ +static ssize_t +v9fs_file_write_iter(struct kiocb *iocb, struct iov_iter *from) +{ + struct file *file = iocb->ki_filp; + struct p9_fid *fid = file->private_data; + + p9_debug(P9_DEBUG_VFS, "fid %d\n", fid->fid); + + if (fid->mode & (P9L_DIRECT | P9L_NOWRITECACHE)) + return netfs_unbuffered_write_iter(iocb, from); + + p9_debug(P9_DEBUG_CACHE, "(cached)\n"); + return netfs_file_write_iter(iocb, from); +} + +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 = file_write_and_wait_range(filp, start, end); + if (retval) + return retval; + + inode_lock(inode); + 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); + inode_unlock(inode); + + 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 = file_write_and_wait_range(filp, start, end); + if (retval) + return retval; + + inode_lock(inode); + p9_debug(P9_DEBUG_VFS, "filp %p datasync %x\n", filp, datasync); + + fid = filp->private_data; + + retval = p9_client_fsync(fid, datasync); + inode_unlock(inode); + + return retval; +} + +static int +v9fs_file_mmap_prepare(struct vm_area_desc *desc) +{ + int retval; + struct file *filp = desc->file; + struct inode *inode = file_inode(filp); + struct v9fs_session_info *v9ses = v9fs_inode2v9ses(inode); + + p9_debug(P9_DEBUG_MMAP, "filp :%p\n", filp); + + if (!(v9ses->cache & CACHE_WRITEBACK)) { + p9_debug(P9_DEBUG_CACHE, "(read-only mmap mode)"); + return generic_file_readonly_mmap_prepare(desc); + } + + retval = generic_file_mmap_prepare(desc); + if (!retval) + desc->vm_ops = &v9fs_mmap_file_vm_ops; + + return retval; +} + +static vm_fault_t +v9fs_vm_page_mkwrite(struct vm_fault *vmf) +{ + return netfs_page_mkwrite(vmf, NULL); +} + +static void v9fs_mmap_vm_close(struct vm_area_struct *vma) +{ + if (!(vma->vm_flags & VM_SHARED)) + return; + + p9_debug(P9_DEBUG_VFS, "9p VMA close, %p, flushing", vma); + + filemap_fdatawrite_range(file_inode(vma->vm_file)->i_mapping, + (loff_t)vma->vm_pgoff * PAGE_SIZE, + (loff_t)vma->vm_pgoff * PAGE_SIZE + + (vma->vm_end - vma->vm_start - 1)); +} + +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, +}; + +const struct file_operations v9fs_file_operations = { + .llseek = generic_file_llseek, + .read_iter = v9fs_file_read_iter, + .write_iter = v9fs_file_write_iter, + .open = v9fs_file_open, + .release = v9fs_dir_release, + .lock = v9fs_file_lock, + .mmap_prepare = generic_file_readonly_mmap_prepare, + .splice_read = v9fs_file_splice_read, + .splice_write = iter_file_splice_write, + .fsync = v9fs_file_fsync, +}; + +const struct file_operations v9fs_file_operations_dotl = { + .llseek = generic_file_llseek, + .read_iter = v9fs_file_read_iter, + .write_iter = v9fs_file_write_iter, + .open = v9fs_file_open, + .release = v9fs_dir_release, + .lock = v9fs_file_lock_dotl, + .flock = v9fs_file_flock_dotl, + .mmap_prepare = v9fs_file_mmap_prepare, + .splice_read = v9fs_file_splice_read, + .splice_write = iter_file_splice_write, + .fsync = v9fs_file_fsync_dotl, +}; diff --git a/fs/9p/vfs_inode.c b/fs/9p/vfs_inode.c new file mode 100644 index 000000000..3829554ca --- /dev/null +++ b/fs/9p/vfs_inode.c @@ -0,0 +1,1402 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * This file contains vfs inode ops for the 9P2000 protocol. + * + * Copyright (C) 2004 by Eric Van Hensbergen + * Copyright (C) 2002 by Ron Minnich + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 & 0777; /* S_IRWXUGO */ + 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, r; + 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; + int major = -1, minor = -1; + + r = sscanf(stat->extension, "%c %i %i", &type, &major, &minor); + if (r != 3) { + p9_debug(P9_DEBUG_ERROR, + "invalid device string, umode will be bogus: %s\n", + stat->extension); + return res; + } + 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; + + switch (uflags&3) { + default: + case O_RDONLY: + ret = P9_OREAD; + break; + + case O_WRONLY: + ret = P9_OWRITE; + break; + + case O_RDWR: + ret = P9_ORDWR; + break; + } + + if (uflags & O_TRUNC) + ret |= P9_OTRUNC; + + if (extended) { + if (uflags & O_EXCL) + ret |= P9_OEXCL; + + if (uflags & O_APPEND) + ret |= P9_OAPPEND; + } + + return ret; +} + +/** + * v9fs_blank_wstat - helper function to setup a 9P stat structure + * @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 + * @sb: The superblock to allocate the inode from + */ +struct inode *v9fs_alloc_inode(struct super_block *sb) +{ + struct v9fs_inode *v9inode; + + v9inode = alloc_inode_sb(sb, v9fs_inode_cache, GFP_KERNEL); + if (!v9inode) + return NULL; + v9inode->cache_validity = 0; + mutex_init(&v9inode->v_mutex); + return &v9inode->netfs.inode; +} + +/** + * v9fs_free_inode - destroy an inode + * @inode: The inode to be freed + */ + +void v9fs_free_inode(struct inode *inode) +{ + kmem_cache_free(v9fs_inode_cache, V9FS_I(inode)); +} + +/* + * Set parameters for the netfs library + */ +void v9fs_set_netfs_context(struct inode *inode) +{ + struct v9fs_inode *v9inode = V9FS_I(inode); + netfs_inode_init(&v9inode->netfs, &v9fs_req_ops, true); +} + +int v9fs_init_inode(struct v9fs_session_info *v9ses, + struct inode *inode, umode_t mode, dev_t rdev) +{ + int err = 0; + + inode_init_owner(&nop_mnt_idmap, inode, NULL, mode); + inode->i_blocks = 0; + inode->i_rdev = rdev; + simple_inode_init_ts(inode); + inode->i_mapping->a_ops = &v9fs_addr_operations; + inode->i_private = NULL; + + 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; + inode->i_fop = &v9fs_file_operations_dotl; + } else { + inode->i_op = &v9fs_file_inode_operations; + inode->i_fop = &v9fs_file_operations; + } + + 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; + inode_nohighmem(inode); + } 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; + } +error: + return err; + +} + +/** + * v9fs_evict_inode - Remove an inode from the inode cache + * @inode: inode to release + * + */ +void v9fs_evict_inode(struct inode *inode) +{ + struct v9fs_inode __maybe_unused *v9inode = V9FS_I(inode); + __le32 __maybe_unused version; + + if (!is_bad_inode(inode)) { + netfs_wait_for_outstanding_io(inode); + truncate_inode_pages_final(&inode->i_data); + + version = cpu_to_le32(v9inode->qid.version); + netfs_clear_inode_writeback(inode, &version); + + clear_inode(inode); + filemap_fdatawrite(&inode->i_data); + +#ifdef CONFIG_9P_FSCACHE + if (v9fs_inode_cookie(v9inode)) + fscache_relinquish_cookie(v9fs_inode_cookie(v9inode), false); +#endif + } else + clear_inode(inode); +} + +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_wrong_type(inode, umode)) + 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; + + if (v9inode->qid.path != st->qid.path) + return 0; + return 1; +} + +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; +} + +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; + struct inode *inode; + struct v9fs_session_info *v9ses = sb->s_fs_info; + int (*test)(struct inode *inode, void *data); + + if (new) + test = v9fs_test_new_inode; + else + test = v9fs_test_inode; + + inode = iget5_locked(sb, QID2INO(qid), test, v9fs_set_inode, st); + if (!inode) + return ERR_PTR(-ENOMEM); + if (!(inode_state_read_once(inode) & I_NEW)) + return inode; + /* + * initialize the inode with the stat info + * FIXME!! we may need support for stale inodes + * later. + */ + inode->i_ino = QID2INO(qid); + umode = p9mode2unixmode(v9ses, st, &rdev); + retval = v9fs_init_inode(v9ses, inode, umode, rdev); + if (retval) + goto error; + + v9fs_stat2inode(st, inode, sb, 0); + v9fs_set_netfs_context(inode); + v9fs_cache_inode_get_cookie(inode); + unlock_new_inode(inode); + return inode; +error: + iget_failed(inode); + return ERR_PTR(retval); + +} + +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; + + 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_dec_count - helper functon to drop i_nlink. + * + * If a directory had nlink <= 2 (including . and ..), then we should not drop + * the link count, which indicates the underlying exported fs doesn't maintain + * nlink accurately. e.g. + * - overlayfs sets nlink to 1 for merged dir + * - ext4 (with dir_nlink feature enabled) sets nlink to 1 if a dir has more + * than EXT4_LINK_MAX (65000) links. + * + * In cacheless mode the server is the source of truth for nlink and the + * inode is going away immediately, so locally adjusting i_nlink buys + * nothing and races with concurrent metadata fetches that may already + * have observed the post-unlink value (nlink == 0). + * + * @inode: inode whose nlink is being dropped + */ +static void v9fs_dec_count(struct inode *inode) +{ + struct v9fs_session_info *v9ses = v9fs_inode2v9ses(inode); + + if (!(v9ses->cache & (CACHE_META | CACHE_LOOSE))) + return; + if (!S_ISDIR(inode->i_mode) || inode->i_nlink > 2) + drop_nlink(inode); +} + +/** + * v9fs_remove - helper function to remove files and directories + * @dir: directory inode that is being deleted + * @dentry: dentry that is being deleted + * @flags: removing a directory + * + */ + +static int v9fs_remove(struct inode *dir, struct dentry *dentry, int flags) +{ + 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 = d_inode(dentry); + dfid = v9fs_parent_fid(dentry); + 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)); + p9_fid_put(dfid); + 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); + v9fs_dec_count(dir); + } else + v9fs_dec_count(inode); + + v9fs_invalidate_inode_attr(inode); + v9fs_invalidate_inode_attr(dir); + + /* invalidate all fids associated with dentry */ + /* NOTE: This will not include open fids */ + v9fs_dentry_fid_remove(dentry); + } + return retval; +} + +/** + * 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; + const unsigned char *name; + struct p9_fid *dfid, *ofid = NULL, *fid = NULL; + struct inode *inode; + + p9_debug(P9_DEBUG_VFS, "name %pd\n", dentry); + + name = dentry->d_name.name; + dfid = v9fs_parent_fid(dentry); + if (IS_ERR(dfid)) { + err = PTR_ERR(dfid); + p9_debug(P9_DEBUG_VFS, "fid lookup failed %d\n", err); + return ERR_PTR(err); + } + + /* clone a fid to use for creation */ + ofid = clone_fid(dfid); + if (IS_ERR(ofid)) { + err = PTR_ERR(ofid); + p9_debug(P9_DEBUG_VFS, "p9_client_walk failed %d\n", err); + goto error; + } + + 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); + 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); + } + p9_fid_put(dfid); + return ofid; +error: + p9_fid_put(dfid); + p9_fid_put(ofid); + p9_fid_put(fid); + return ERR_PTR(err); +} + +/** + * v9fs_vfs_create - VFS hook to create a regular file + * @idmap: idmap of the mount + * @dir: The parent directory + * @dentry: The name of file to be created + * @mode: The UNIX file mode to set + * + * open(.., O_CREAT) is handled in v9fs_vfs_atomic_open(). This is only called + * for mknod(2). + * + */ + +static int +v9fs_vfs_create(struct mnt_idmap *idmap, struct inode *dir, + struct dentry *dentry, umode_t mode) +{ + 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_fid_put(fid); + + return 0; +} + +/** + * v9fs_vfs_mkdir - VFS mkdir hook to create a directory + * @idmap: idmap of the mount + * @dir: inode that is being unlinked + * @dentry: dentry that is being unlinked + * @mode: mode for new directory + * + */ + +static struct dentry *v9fs_vfs_mkdir(struct mnt_idmap *idmap, struct inode *dir, + struct dentry *dentry, umode_t mode) +{ + u32 perm; + struct p9_fid *fid; + struct v9fs_session_info *v9ses; + + p9_debug(P9_DEBUG_VFS, "name %pd\n", dentry); + v9ses = v9fs_inode2v9ses(dir); + perm = unixmode2p9mode(v9ses, mode); + fid = v9fs_create(v9ses, dir, dentry, NULL, perm, P9_OREAD); + if (IS_ERR(fid)) + return ERR_CAST(fid); + inc_nlink(dir); + v9fs_invalidate_inode_attr(dir); + p9_fid_put(fid); + return NULL; +} + +/** + * 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? + * @flags: lookup flags (unused) + * + */ + +struct dentry *v9fs_vfs_lookup(struct inode *dir, struct dentry *dentry, + unsigned int flags) +{ + struct dentry *res; + struct v9fs_session_info *v9ses; + struct p9_fid *dfid, *fid; + struct inode *inode; + const unsigned char *name; + + p9_debug(P9_DEBUG_VFS, "dir: %p dentry: (%pd) %p flags: %x\n", + dir, dentry, dentry, flags); + + if (dentry->d_name.len > NAME_MAX) + return ERR_PTR(-ENAMETOOLONG); + + v9ses = v9fs_inode2v9ses(dir); + /* We can walk d_parent because we hold the dir->i_mutex */ + dfid = v9fs_parent_fid(dentry); + if (IS_ERR(dfid)) + return ERR_CAST(dfid); + + /* + * 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. + */ + name = dentry->d_name.name; + fid = p9_client_walk(dfid, 1, &name, 1); + p9_fid_put(dfid); + if (fid == ERR_PTR(-ENOENT)) { + inode = NULL; + v9fs_ndentry_refresh_timeout(dentry); + } else if (IS_ERR(fid)) { + inode = ERR_CAST(fid); + } else if (v9ses->cache & (CACHE_META|CACHE_LOOSE)) { + 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 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_splice_alias(inode, dentry); + if (!IS_ERR(fid)) { + if (!res) + v9fs_fid_add(dentry, &fid); + else if (!IS_ERR(res)) + v9fs_fid_add(res, &fid); + else + p9_fid_put(fid); + } + return res; +} + +static int +v9fs_vfs_atomic_open(struct inode *dir, struct dentry *dentry, + struct file *file, unsigned int flags, umode_t mode) +{ + int err; + u32 perm; + struct v9fs_inode __maybe_unused *v9inode; + struct v9fs_session_info *v9ses; + struct p9_fid *fid; + struct inode *inode; + int p9_omode; + + if (d_in_lookup(dentry)) { + struct dentry *res = v9fs_vfs_lookup(dir, dentry, 0); + if (res || d_really_is_positive(dentry)) + return finish_no_open(file, res); + } + + /* Only creates */ + if (!(flags & O_CREAT)) + return finish_no_open(file, NULL); + + v9ses = v9fs_inode2v9ses(dir); + perm = unixmode2p9mode(v9ses, mode); + p9_omode = v9fs_uflags2omode(flags, v9fs_proto_dotu(v9ses)); + + if ((v9ses->cache & CACHE_WRITEBACK) && (p9_omode & P9_OWRITE)) { + p9_omode = (p9_omode & ~(P9_OWRITE | P9_OAPPEND)) | P9_ORDWR; + p9_debug(P9_DEBUG_CACHE, + "write-only file with writeback enabled, creating w/ O_RDWR\n"); + } + fid = v9fs_create(v9ses, dir, dentry, NULL, perm, p9_omode); + if (IS_ERR(fid)) + return PTR_ERR(fid); + + v9fs_invalidate_inode_attr(dir); + inode = d_inode(dentry); + v9inode = V9FS_I(inode); + err = finish_open(file, dentry, generic_file_open); + if (unlikely(err)) { + p9_fid_put(fid); + return err; + } + + file->private_data = fid; +#ifdef CONFIG_9P_FSCACHE + if (v9ses->cache & CACHE_FSCACHE) + fscache_use_cookie(v9fs_inode_cookie(v9inode), + file->f_mode & FMODE_WRITE); +#endif + + v9fs_fid_add_modes(fid, v9ses->flags, v9ses->cache, file->f_flags); + v9fs_open_fid_add(inode, &fid); + + file->f_mode |= FMODE_CREATED; + return 0; +} + +/** + * v9fs_vfs_unlink - VFS unlink hook to delete an inode + * @i: inode that is being unlinked + * @d: dentry that is being unlinked + * + */ + +int v9fs_vfs_unlink(struct inode *i, struct dentry *d) +{ + return v9fs_remove(i, d, 0); +} + +/** + * v9fs_vfs_rmdir - VFS unlink hook to delete a directory + * @i: inode that is being unlinked + * @d: dentry that is being unlinked + * + */ + +int v9fs_vfs_rmdir(struct inode *i, struct dentry *d) +{ + return v9fs_remove(i, d, AT_REMOVEDIR); +} + +/** + * v9fs_vfs_rename - VFS hook to rename an inode + * @idmap: The idmap of the mount + * @old_dir: old dir inode + * @old_dentry: old dentry + * @new_dir: new dir inode + * @new_dentry: new dentry + * @flags: RENAME_* flags + * + */ + +int +v9fs_vfs_rename(struct mnt_idmap *idmap, struct inode *old_dir, + struct dentry *old_dentry, struct inode *new_dir, + struct dentry *new_dentry, unsigned int flags) +{ + int retval; + struct inode *old_inode; + struct inode *new_inode; + struct v9fs_session_info *v9ses; + struct p9_fid *oldfid = NULL, *dfid = NULL; + struct p9_fid *olddirfid = NULL; + struct p9_fid *newdirfid = NULL; + struct p9_wstat wstat; + + if (flags) + return -EINVAL; + + p9_debug(P9_DEBUG_VFS, "\n"); + old_inode = d_inode(old_dentry); + new_inode = d_inode(new_dentry); + v9ses = v9fs_inode2v9ses(old_inode); + oldfid = v9fs_fid_lookup(old_dentry); + if (IS_ERR(oldfid)) + return PTR_ERR(oldfid); + + dfid = v9fs_parent_fid(old_dentry); + olddirfid = clone_fid(dfid); + p9_fid_put(dfid); + dfid = NULL; + + if (IS_ERR(olddirfid)) { + retval = PTR_ERR(olddirfid); + goto error; + } + + dfid = v9fs_parent_fid(new_dentry); + newdirfid = clone_fid(dfid); + p9_fid_put(dfid); + dfid = NULL; + + if (IS_ERR(newdirfid)) { + retval = PTR_ERR(newdirfid); + goto error; + } + + 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 error_locked; + } + 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 error_locked; + } + v9fs_blank_wstat(&wstat); + wstat.muid = v9ses->uname; + wstat.name = new_dentry->d_name.name; + retval = p9_client_wstat(oldfid, &wstat); + +error_locked: + if (!retval) { + if (new_inode) { + if (S_ISDIR(new_inode->i_mode)) + clear_nlink(new_inode); + else + v9fs_dec_count(new_inode); + } + if (S_ISDIR(old_inode->i_mode)) { + if (!new_inode) + inc_nlink(new_dir); + v9fs_dec_count(old_dir); + } + v9fs_invalidate_inode_attr(old_inode); + v9fs_invalidate_inode_attr(old_dir); + v9fs_invalidate_inode_attr(new_dir); + + /* successful rename */ + d_move(old_dentry, new_dentry); + } + up_write(&v9ses->rename_sem); + +error: + p9_fid_put(newdirfid); + p9_fid_put(olddirfid); + p9_fid_put(oldfid); + return retval; +} + +/** + * v9fs_vfs_getattr - retrieve file metadata + * @idmap: idmap of the mount + * @path: Object to query + * @stat: metadata structure to populate + * @request_mask: Mask of STATX_xxx flags indicating the caller's interests + * @flags: AT_STATX_xxx setting + * + */ + +static int +v9fs_vfs_getattr(struct mnt_idmap *idmap, const struct path *path, + struct kstat *stat, u32 request_mask, unsigned int flags) +{ + struct dentry *dentry = path->dentry; + struct inode *inode = d_inode(dentry); + struct v9fs_session_info *v9ses; + struct p9_fid *fid; + struct p9_wstat *st; + + p9_debug(P9_DEBUG_VFS, "dentry: %p\n", dentry); + v9ses = v9fs_dentry2v9ses(dentry); + if (v9ses->cache & (CACHE_META|CACHE_LOOSE)) { + generic_fillattr(&nop_mnt_idmap, request_mask, inode, stat); + return 0; + } else if (v9ses->cache & CACHE_WRITEBACK) { + if (S_ISREG(inode->i_mode)) { + int retval = filemap_fdatawrite(inode->i_mapping); + + if (retval) + p9_debug(P9_DEBUG_ERROR, + "flushing writeback during getattr returned %d\n", retval); + } + } + fid = v9fs_fid_lookup(dentry); + if (IS_ERR(fid)) + return PTR_ERR(fid); + + st = p9_client_stat(fid); + p9_fid_put(fid); + if (IS_ERR(st)) + return PTR_ERR(st); + + v9fs_stat2inode(st, d_inode(dentry), dentry->d_sb, 0); + generic_fillattr(&nop_mnt_idmap, request_mask, d_inode(dentry), stat); + + p9stat_free(st); + kfree(st); + return 0; +} + +/** + * v9fs_vfs_setattr - set file metadata + * @idmap: idmap of the mount + * @dentry: file whose metadata to set + * @iattr: metadata assignment structure + * + */ + +static int v9fs_vfs_setattr(struct mnt_idmap *idmap, + struct dentry *dentry, struct iattr *iattr) +{ + int retval, use_dentry = 0; + struct inode *inode = d_inode(dentry); + struct v9fs_session_info *v9ses; + struct p9_fid *fid = NULL; + struct p9_wstat wstat; + + p9_debug(P9_DEBUG_VFS, "\n"); + retval = setattr_prepare(&nop_mnt_idmap, dentry, iattr); + if (retval) + return retval; + + v9ses = v9fs_dentry2v9ses(dentry); + if (iattr->ia_valid & ATTR_FILE) { + fid = iattr->ia_file->private_data; + WARN_ON(!fid); + } + if (!fid) { + fid = v9fs_fid_lookup(dentry); + use_dentry = 1; + } + if (IS_ERR(fid)) + return PTR_ERR(fid); + + v9fs_blank_wstat(&wstat); + if (iattr->ia_valid & ATTR_MODE) + wstat.mode = unixmode2p9mode(v9ses, iattr->ia_mode); + + if (iattr->ia_valid & ATTR_MTIME) + wstat.mtime = iattr->ia_mtime.tv_sec; + + if (iattr->ia_valid & ATTR_ATIME) + wstat.atime = iattr->ia_atime.tv_sec; + + if (iattr->ia_valid & ATTR_SIZE) + wstat.length = iattr->ia_size; + + if (v9fs_proto_dotu(v9ses)) { + if (iattr->ia_valid & ATTR_UID) + wstat.n_uid = iattr->ia_uid; + + if (iattr->ia_valid & ATTR_GID) + wstat.n_gid = iattr->ia_gid; + } + + /* Write all dirty data */ + if (d_is_reg(dentry)) { + retval = filemap_fdatawrite(inode->i_mapping); + if (retval) + p9_debug(P9_DEBUG_ERROR, + "flushing writeback during setattr returned %d\n", retval); + } + + retval = p9_client_wstat(fid, &wstat); + + if (use_dentry) + p9_fid_put(fid); + + if (retval < 0) + return retval; + + if ((iattr->ia_valid & ATTR_SIZE) && + iattr->ia_size != i_size_read(inode)) { + truncate_setsize(inode, iattr->ia_size); + netfs_resize_file(netfs_inode(inode), iattr->ia_size, true); + +#ifdef CONFIG_9P_FSCACHE + if (v9ses->cache & CACHE_FSCACHE) { + struct v9fs_inode *v9inode = V9FS_I(inode); + + fscache_resize_cookie(v9fs_inode_cookie(v9inode), iattr->ia_size); + } +#endif + } + + v9fs_invalidate_inode_attr(inode); + + setattr_copy(&nop_mnt_idmap, inode, iattr); + mark_inode_dirty(inode); + return 0; +} + +/** + * v9fs_stat2inode - populate an inode structure with mistat info + * @stat: Plan 9 metadata (mistat) structure + * @inode: inode to populate + * @sb: superblock of filesystem + * @flags: control flags (e.g. V9FS_STAT2INODE_KEEP_ISIZE) + * + */ + +void +v9fs_stat2inode(struct p9_wstat *stat, struct inode *inode, + struct super_block *sb, unsigned int flags) +{ + umode_t mode; + struct v9fs_session_info *v9ses = sb->s_fs_info; + struct v9fs_inode *v9inode = V9FS_I(inode); + + inode_set_atime(inode, stat->atime, 0); + inode_set_mtime(inode, stat->mtime, 0); + inode_set_ctime(inode, stat->mtime, 0); + + inode->i_uid = v9ses->dfltuid; + inode->i_gid = v9ses->dfltgid; + + if (v9fs_proto_dotu(v9ses)) { + inode->i_uid = stat->n_uid; + inode->i_gid = stat->n_gid; + } + if ((S_ISREG(inode->i_mode)) || (S_ISDIR(inode->i_mode))) { + if (v9fs_proto_dotu(v9ses)) { + unsigned int i_nlink; + /* + * Hadlink support got added later to the .u extension. + * So there can be a server out there that doesn't + * support this even with .u extension. That would + * just leave us with stat->extension being an empty + * string, though. + */ + /* HARDLINKCOUNT %u */ + if (sscanf(stat->extension, + " HARDLINKCOUNT %u", &i_nlink) == 1) + set_nlink(inode, i_nlink); + } + } + mode = p9mode2perm(v9ses, stat); + mode |= inode->i_mode & ~S_IALLUGO; + inode->i_mode = mode; + + spin_lock(&inode->i_lock); + netfs_write_remote_i_size(inode, stat->length); + if (!(flags & V9FS_STAT2INODE_KEEP_ISIZE)) + i_size_write(inode, stat->length); + /* not real number of blocks, but 512 byte ones ... */ + inode->i_blocks = (stat->length + 512 - 1) >> 9; + spin_unlock(&inode->i_lock); + v9inode->cache_validity &= ~V9FS_INO_INVALID_ATTR; +} + +/** + * v9fs_vfs_get_link - follow a symlink path + * @dentry: dentry for symlink + * @inode: inode for symlink + * @done: delayed call for when we are done with the return value + */ + +static const char *v9fs_vfs_get_link(struct dentry *dentry, + struct inode *inode, + struct delayed_call *done) +{ + struct v9fs_session_info *v9ses; + struct p9_fid *fid; + struct p9_wstat *st; + char *res; + + if (!dentry) + return ERR_PTR(-ECHILD); + + v9ses = v9fs_dentry2v9ses(dentry); + if (!v9fs_proto_dotu(v9ses)) + return ERR_PTR(-EBADF); + + p9_debug(P9_DEBUG_VFS, "%pd\n", dentry); + fid = v9fs_fid_lookup(dentry); + + if (IS_ERR(fid)) + return ERR_CAST(fid); + + st = p9_client_stat(fid); + p9_fid_put(fid); + if (IS_ERR(st)) + return ERR_CAST(st); + + if (!(st->mode & P9_DMSYMLINK)) { + p9stat_free(st); + kfree(st); + return ERR_PTR(-EINVAL); + } + res = st->extension; + st->extension = NULL; + if (strlen(res) >= PATH_MAX) + res[PATH_MAX - 1] = '\0'; + + p9stat_free(st); + kfree(st); + set_delayed_call(done, kfree_link, res); + return res; +} + +/** + * 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, + u32 perm, const char *extension) +{ + struct p9_fid *fid; + struct v9fs_session_info *v9ses; + + v9ses = v9fs_inode2v9ses(dir); + if (!v9fs_proto_dotu(v9ses)) { + p9_debug(P9_DEBUG_ERROR, "not extended\n"); + return -EPERM; + } + + fid = v9fs_create(v9ses, dir, dentry, (char *) extension, perm, + P9_OREAD); + if (IS_ERR(fid)) + return PTR_ERR(fid); + + v9fs_invalidate_inode_attr(dir); + p9_fid_put(fid); + return 0; +} + +/** + * v9fs_vfs_symlink - helper function to create symlinks + * @idmap: idmap of the mount + * @dir: directory inode containing symlink + * @dentry: dentry for symlink + * @symname: symlink data + * + * See Also: 9P2000.u RFC for more information + * + */ + +static int +v9fs_vfs_symlink(struct mnt_idmap *idmap, struct inode *dir, + struct dentry *dentry, const char *symname) +{ + p9_debug(P9_DEBUG_VFS, " %llu,%pd,%s\n", + dir->i_ino, dentry, symname); + + return v9fs_vfs_mkspecial(dir, dentry, P9_DMSYMLINK, symname); +} + +#define U32_MAX_DIGITS 10 + +/** + * v9fs_vfs_link - create a hardlink + * @old_dentry: dentry for file to link to + * @dir: inode destination for new link + * @dentry: dentry for link + * + */ + +static int +v9fs_vfs_link(struct dentry *old_dentry, struct inode *dir, + struct dentry *dentry) +{ + int retval; + char name[1 + U32_MAX_DIGITS + 2]; /* sign + number + \n + \0 */ + struct p9_fid *oldfid; + + p9_debug(P9_DEBUG_VFS, " %llu,%pd,%pd\n", + dir->i_ino, dentry, old_dentry); + + oldfid = v9fs_fid_clone(old_dentry); + if (IS_ERR(oldfid)) + return PTR_ERR(oldfid); + + sprintf(name, "%d\n", oldfid->fid); + retval = v9fs_vfs_mkspecial(dir, dentry, P9_DMLINK, name); + if (!retval) { + v9fs_refresh_inode(oldfid, d_inode(old_dentry)); + v9fs_invalidate_inode_attr(dir); + } + p9_fid_put(oldfid); + return retval; +} + +/** + * v9fs_vfs_mknod - create a special file + * @idmap: idmap of the mount + * @dir: inode destination for new link + * @dentry: dentry for file + * @mode: mode for creation + * @rdev: device associated with special file + * + */ + +static int +v9fs_vfs_mknod(struct mnt_idmap *idmap, struct inode *dir, + struct dentry *dentry, umode_t mode, dev_t rdev) +{ + struct v9fs_session_info *v9ses = v9fs_inode2v9ses(dir); + int retval; + char name[2 + U32_MAX_DIGITS + 1 + U32_MAX_DIGITS + 1]; + u32 perm; + + p9_debug(P9_DEBUG_VFS, " %llu,%pd mode: %x MAJOR: %u MINOR: %u\n", + dir->i_ino, dentry, mode, + MAJOR(rdev), MINOR(rdev)); + + /* build extension */ + if (S_ISBLK(mode)) + sprintf(name, "b %u %u", MAJOR(rdev), MINOR(rdev)); + else if (S_ISCHR(mode)) + sprintf(name, "c %u %u", MAJOR(rdev), MINOR(rdev)); + else + *name = 0; + + perm = unixmode2p9mode(v9ses, mode); + retval = v9fs_vfs_mkspecial(dir, dentry, perm, name); + + return retval; +} + +int v9fs_refresh_inode(struct p9_fid *fid, struct inode *inode) +{ + int umode; + dev_t rdev; + struct p9_wstat *st; + struct v9fs_session_info *v9ses; + unsigned int flags; + + 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_wrong_type(inode, umode)) + goto out; + + /* + * We don't want to refresh inode->i_size, + * because we may have cached data + */ + flags = (v9ses->cache & CACHE_LOOSE) ? + V9FS_STAT2INODE_KEEP_ISIZE : 0; + v9fs_stat2inode(st, inode, inode->i_sb, flags); +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, + .mkdir = v9fs_vfs_mkdir, + .rmdir = v9fs_vfs_rmdir, + .mknod = v9fs_vfs_mknod, + .rename = v9fs_vfs_rename, + .getattr = v9fs_vfs_getattr, + .setattr = v9fs_vfs_setattr, +}; + +static const struct inode_operations v9fs_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, + .mknod = v9fs_vfs_mknod, + .rename = v9fs_vfs_rename, + .getattr = v9fs_vfs_getattr, + .setattr = v9fs_vfs_setattr, +}; + +static const struct inode_operations v9fs_file_inode_operations = { + .getattr = v9fs_vfs_getattr, + .setattr = v9fs_vfs_setattr, +}; + +static const struct inode_operations v9fs_symlink_inode_operations = { + .get_link = v9fs_vfs_get_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 000000000..116b29e95 --- /dev/null +++ b/fs/9p/vfs_inode_dotl.c @@ -0,0 +1,994 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * This file contains vfs inode ops for the 9P2000.L protocol. + * + * Copyright (C) 2004 by Eric Van Hensbergen + * Copyright (C) 2002 by Ron Minnich + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 mnt_idmap *idmap, struct inode *dir, + struct dentry *dentry, umode_t omode, dev_t rdev); + +/** + * v9fs_get_fsgid_for_create - Helper function to get the gid for a new object + * @dir_inode: The directory inode + * + * 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_wrong_type(inode, st->st_mode)) + 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; + + if (v9inode->qid.path != st->qid.path) + 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; + struct inode *inode; + struct v9fs_session_info *v9ses = sb->s_fs_info; + int (*test)(struct inode *inode, void *data); + + if (new) + test = v9fs_test_new_inode_dotl; + else + test = v9fs_test_inode_dotl; + + inode = iget5_locked(sb, QID2INO(qid), test, v9fs_set_inode_dotl, st); + if (!inode) + return ERR_PTR(-ENOMEM); + if (!(inode_state_read_once(inode) & I_NEW)) + return inode; + /* + * initialize the inode with the stat info + * FIXME!! we may need support for stale inodes + * later. + */ + inode->i_ino = QID2INO(qid); + retval = v9fs_init_inode(v9ses, inode, + st->st_mode, new_decode_dev(st->st_rdev)); + if (retval) + goto error; + + v9fs_stat2inode_dotl(st, inode, 0); + v9fs_set_netfs_context(inode); + v9fs_cache_inode_get_cookie(inode); + retval = v9fs_get_acl(inode, fid); + if (retval) + goto error; + + unlock_new_inode(inode); + return inode; +error: + iget_failed(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. + * @idmap: The user namespace of the mount + * @dir: directory inode that is being created + * @dentry: dentry that is being deleted + * @omode: create permissions + * + */ +static int +v9fs_vfs_create_dotl(struct mnt_idmap *idmap, struct inode *dir, + struct dentry *dentry, umode_t omode) +{ + return v9fs_vfs_mknod_dotl(idmap, dir, dentry, omode, 0); +} + +static int +v9fs_vfs_atomic_open_dotl(struct inode *dir, struct dentry *dentry, + struct file *file, unsigned int flags, umode_t omode) +{ + int err = 0; + kgid_t gid; + umode_t mode; + int p9_omode = v9fs_open_to_dotl_flags(flags); + const unsigned char *name = NULL; + struct p9_qid qid; + struct inode *inode; + struct p9_fid *fid = NULL; + struct p9_fid *dfid = NULL, *ofid = NULL; + struct v9fs_session_info *v9ses; + struct posix_acl *pacl = NULL, *dacl = NULL; + + if (d_in_lookup(dentry)) { + struct dentry *res = v9fs_vfs_lookup(dir, dentry, 0); + if (res || d_really_is_positive(dentry)) + return finish_no_open(file, res); + } + + /* Only creates */ + if (!(flags & O_CREAT)) + return finish_no_open(file, NULL); + + v9ses = v9fs_inode2v9ses(dir); + + name = dentry->d_name.name; + p9_debug(P9_DEBUG_VFS, "name:%s flags:0x%x mode:0x%x\n", + name, flags, omode); + + dfid = v9fs_parent_fid(dentry); + 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 = clone_fid(dfid); + 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 create %d\n", + err); + goto out; + } + + if ((v9ses->cache & CACHE_WRITEBACK) && (p9_omode & P9_OWRITE)) { + p9_omode = (p9_omode & ~(P9_OWRITE | P9_DOTL_APPEND)) | P9_ORDWR; + p9_debug(P9_DEBUG_CACHE, + "write-only file with writeback enabled, creating w/ O_RDWR\n"); + } + err = p9_client_create_dotl(ofid, name, p9_omode, mode, gid, &qid); + if (err < 0) { + p9_debug(P9_DEBUG_VFS, "p9_client_open_dotl failed in create %d\n", + err); + goto out; + } + 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); + goto out; + } + 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 out; + } + /* 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); + + /* Since we are opening a file, assign the open fid to the file */ + err = finish_open(file, dentry, generic_file_open); + if (err) + goto out; + file->private_data = ofid; +#ifdef CONFIG_9P_FSCACHE + if (v9ses->cache & CACHE_FSCACHE) { + struct v9fs_inode *v9inode = V9FS_I(inode); + fscache_use_cookie(v9fs_inode_cookie(v9inode), + file->f_mode & FMODE_WRITE); + } +#endif + v9fs_fid_add_modes(ofid, v9ses->flags, v9ses->cache, flags); + v9fs_open_fid_add(inode, &ofid); + file->f_mode |= FMODE_CREATED; +out: + p9_fid_put(dfid); + p9_fid_put(ofid); + p9_fid_put(fid); + v9fs_put_acl(dacl, pacl); + return err; +} + +/** + * v9fs_vfs_mkdir_dotl - VFS mkdir hook to create a directory + * @idmap: The idmap of the mount + * @dir: inode that is being unlinked + * @dentry: dentry that is being unlinked + * @omode: mode for new directory + * + */ + +static struct dentry *v9fs_vfs_mkdir_dotl(struct mnt_idmap *idmap, + struct inode *dir, struct dentry *dentry, + umode_t omode) +{ + int err = 0; + struct v9fs_session_info *v9ses; + struct p9_fid *fid = NULL, *dfid = NULL; + kgid_t gid; + const unsigned char *name; + umode_t mode; + struct inode *inode; + struct p9_qid qid; + struct posix_acl *dacl = NULL, *pacl = NULL; + + p9_debug(P9_DEBUG_VFS, "name %pd\n", dentry); + v9ses = v9fs_inode2v9ses(dir); + + if (dir->i_mode & S_ISGID) + omode |= S_ISGID; + + dfid = v9fs_parent_fid(dentry); + if (IS_ERR(dfid)) { + err = PTR_ERR(dfid); + p9_debug(P9_DEBUG_VFS, "fid lookup failed %d\n", err); + 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 = 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); + 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_set_create_acl(inode, fid, dacl, pacl); + v9fs_fid_add(dentry, &fid); + d_instantiate(dentry, inode); + err = 0; + inc_nlink(dir); + v9fs_invalidate_inode_attr(dir); +error: + p9_fid_put(fid); + v9fs_put_acl(dacl, pacl); + p9_fid_put(dfid); + return err ? ERR_PTR(err) : NULL; +} + +static int +v9fs_vfs_getattr_dotl(struct mnt_idmap *idmap, + const struct path *path, struct kstat *stat, + u32 request_mask, unsigned int flags) +{ + struct dentry *dentry = path->dentry; + struct v9fs_session_info *v9ses; + struct p9_fid *fid; + struct inode *inode = d_inode(dentry); + struct p9_stat_dotl *st; + + p9_debug(P9_DEBUG_VFS, "dentry: %p\n", dentry); + v9ses = v9fs_dentry2v9ses(dentry); + if (v9ses->cache & (CACHE_META|CACHE_LOOSE)) { + generic_fillattr(&nop_mnt_idmap, request_mask, inode, stat); + return 0; + } else if (v9ses->cache) { + if (S_ISREG(inode->i_mode)) { + int retval = filemap_fdatawrite(inode->i_mapping); + + if (retval) + p9_debug(P9_DEBUG_ERROR, + "flushing writeback during getattr returned %d\n", retval); + } + } + 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); + p9_fid_put(fid); + if (IS_ERR(st)) + return PTR_ERR(st); + + v9fs_stat2inode_dotl(st, d_inode(dentry), 0); + generic_fillattr(&nop_mnt_idmap, request_mask, d_inode(dentry), 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 + * @idmap: idmap of the mount + * @dentry: file whose metadata to set + * @iattr: metadata assignment structure + * + */ + +int v9fs_vfs_setattr_dotl(struct mnt_idmap *idmap, + struct dentry *dentry, struct iattr *iattr) +{ + int retval, use_dentry = 0; + struct inode *inode = d_inode(dentry); + struct v9fs_session_info __maybe_unused *v9ses; + struct p9_fid *fid = NULL; + struct p9_iattr_dotl p9attr = { + .uid = INVALID_UID, + .gid = INVALID_GID, + }; + + p9_debug(P9_DEBUG_VFS, "\n"); + + retval = setattr_prepare(&nop_mnt_idmap, dentry, iattr); + if (retval) + return retval; + + v9ses = v9fs_dentry2v9ses(dentry); + + p9attr.valid = v9fs_mapped_iattr_valid(iattr->ia_valid); + if (iattr->ia_valid & ATTR_MODE) + p9attr.mode = iattr->ia_mode; + if (iattr->ia_valid & ATTR_UID) + p9attr.uid = iattr->ia_uid; + if (iattr->ia_valid & ATTR_GID) + p9attr.gid = iattr->ia_gid; + if (iattr->ia_valid & ATTR_SIZE) + p9attr.size = iattr->ia_size; + if (iattr->ia_valid & ATTR_ATIME_SET) { + p9attr.atime_sec = iattr->ia_atime.tv_sec; + p9attr.atime_nsec = iattr->ia_atime.tv_nsec; + } + if (iattr->ia_valid & ATTR_MTIME_SET) { + p9attr.mtime_sec = iattr->ia_mtime.tv_sec; + p9attr.mtime_nsec = iattr->ia_mtime.tv_nsec; + } + + if (iattr->ia_valid & ATTR_FILE) { + fid = iattr->ia_file->private_data; + WARN_ON(!fid); + } + if (!fid) { + fid = v9fs_fid_lookup(dentry); + use_dentry = 1; + } + if (IS_ERR(fid)) + return PTR_ERR(fid); + + /* Write all dirty data */ + if (S_ISREG(inode->i_mode)) { + retval = filemap_fdatawrite(inode->i_mapping); + if (retval < 0) + p9_debug(P9_DEBUG_ERROR, + "Flushing file prior to setattr failed: %d\n", retval); + } + + retval = p9_client_setattr(fid, &p9attr); + if (retval < 0) { + if (use_dentry) + p9_fid_put(fid); + return retval; + } + + if ((iattr->ia_valid & ATTR_SIZE) && iattr->ia_size != + i_size_read(inode)) { + truncate_setsize(inode, iattr->ia_size); + netfs_resize_file(netfs_inode(inode), iattr->ia_size, true); + +#ifdef CONFIG_9P_FSCACHE + if (v9ses->cache & CACHE_FSCACHE) + fscache_resize_cookie(v9fs_inode_cookie(V9FS_I(inode)), + iattr->ia_size); +#endif + } + + v9fs_invalidate_inode_attr(inode); + setattr_copy(&nop_mnt_idmap, 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) { + if (use_dentry) + p9_fid_put(fid); + return retval; + } + } + if (use_dentry) + p9_fid_put(fid); + + return 0; +} + +/** + * v9fs_stat2inode_dotl - populate an inode structure with stat info + * @stat: stat structure + * @inode: inode to populate + * @flags: ctrl flags (e.g. V9FS_STAT2INODE_KEEP_ISIZE) + * + */ + +void +v9fs_stat2inode_dotl(struct p9_stat_dotl *stat, struct inode *inode, + unsigned int flags) +{ + umode_t mode; + struct v9fs_inode *v9inode = V9FS_I(inode); + + if ((stat->st_result_mask & P9_STATS_BASIC) == P9_STATS_BASIC) { + inode_set_atime(inode, stat->st_atime_sec, + stat->st_atime_nsec); + inode_set_mtime(inode, stat->st_mtime_sec, + stat->st_mtime_nsec); + inode_set_ctime(inode, stat->st_ctime_sec, + 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; + + spin_lock(&inode->i_lock); + netfs_write_remote_i_size(inode, stat->st_size); + if (!(flags & V9FS_STAT2INODE_KEEP_ISIZE)) + i_size_write(inode, stat->st_size); + inode->i_blocks = stat->st_blocks; + spin_unlock(&inode->i_lock); + } else { + if (stat->st_result_mask & P9_STATS_ATIME) { + inode_set_atime(inode, stat->st_atime_sec, + stat->st_atime_nsec); + } + if (stat->st_result_mask & P9_STATS_MTIME) { + inode_set_mtime(inode, stat->st_mtime_sec, + stat->st_mtime_nsec); + } + if (stat->st_result_mask & P9_STATS_CTIME) { + inode_set_ctime(inode, stat->st_ctime_sec, + 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) { + mode = stat->st_mode & S_IALLUGO; + mode |= inode->i_mode & ~S_IALLUGO; + inode->i_mode = mode; + } + spin_lock(&inode->i_lock); + if (!(flags & V9FS_STAT2INODE_KEEP_ISIZE) && + stat->st_result_mask & P9_STATS_SIZE) { + netfs_write_remote_i_size(inode, stat->st_size); + i_size_write(inode, stat->st_size); + } + if (stat->st_result_mask & P9_STATS_BLOCKS) + inode->i_blocks = stat->st_blocks; + spin_unlock(&inode->i_lock); + } + 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 mnt_idmap *idmap, struct inode *dir, + struct dentry *dentry, const char *symname) +{ + int err; + kgid_t gid; + const unsigned char *name; + struct v9fs_session_info *v9ses; + struct p9_qid qid; + struct p9_fid *dfid; + struct p9_fid *fid = NULL; + struct inode *inode; + + name = dentry->d_name.name; + p9_debug(P9_DEBUG_VFS, "%llu,%s,%s\n", dir->i_ino, name, symname); + + dfid = v9fs_parent_fid(dentry); + 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, 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); + + /* 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); + goto error; + } + + v9ses = v9fs_inode2v9ses(dir); + 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); + err = 0; +error: + p9_fid_put(fid); + p9_fid_put(dfid); + 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 p9_fid *dfid, *oldfid; + struct v9fs_session_info *v9ses; + + p9_debug(P9_DEBUG_VFS, "dir ino: %llu, old_name: %pd, new_name: %pd\n", + dir->i_ino, old_dentry, dentry); + + v9ses = v9fs_inode2v9ses(dir); + dfid = v9fs_parent_fid(dentry); + if (IS_ERR(dfid)) + return PTR_ERR(dfid); + + oldfid = v9fs_fid_lookup(old_dentry); + if (IS_ERR(oldfid)) { + p9_fid_put(dfid); + return PTR_ERR(oldfid); + } + + err = p9_client_link(dfid, oldfid, dentry->d_name.name); + + p9_fid_put(dfid); + p9_fid_put(oldfid); + 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_META|CACHE_LOOSE)) { + /* 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, d_inode(old_dentry)); + p9_fid_put(fid); + } + ihold(d_inode(old_dentry)); + d_instantiate(dentry, d_inode(old_dentry)); + + return err; +} + +/** + * v9fs_vfs_mknod_dotl - create a special file + * @idmap: The idmap of the mount + * @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 mnt_idmap *idmap, struct inode *dir, + struct dentry *dentry, umode_t omode, dev_t rdev) +{ + int err; + kgid_t gid; + const unsigned 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 posix_acl *dacl = NULL, *pacl = NULL; + + p9_debug(P9_DEBUG_VFS, " %llu,%pd mode: %x MAJOR: %u MINOR: %u\n", + dir->i_ino, dentry, omode, + MAJOR(rdev), MINOR(rdev)); + + v9ses = v9fs_inode2v9ses(dir); + dfid = v9fs_parent_fid(dentry); + if (IS_ERR(dfid)) { + err = PTR_ERR(dfid); + p9_debug(P9_DEBUG_VFS, "fid lookup failed %d\n", err); + 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 = 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); + 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; + } + v9fs_set_create_acl(inode, fid, dacl, pacl); + v9fs_fid_add(dentry, &fid); + d_instantiate(dentry, inode); + err = 0; +error: + p9_fid_put(fid); + v9fs_put_acl(dacl, pacl); + p9_fid_put(dfid); + + return err; +} + +/** + * v9fs_vfs_get_link_nocache_dotl - Resolve a symlink directly. + * + * To be used when symlink caching is not enabled. + * + * @dentry: dentry for symlink + * @inode: inode for symlink + * @done: destructor for return value + */ +static const char * +v9fs_vfs_get_link_nocache_dotl(struct dentry *dentry, + struct inode *inode, + struct delayed_call *done) +{ + struct p9_fid *fid; + char *target; + int retval; + + if (!dentry) + return ERR_PTR(-ECHILD); + + p9_debug(P9_DEBUG_VFS, "%pd\n", dentry); + + fid = v9fs_fid_lookup(dentry); + if (IS_ERR(fid)) + return ERR_CAST(fid); + retval = p9_client_readlink(fid, &target); + p9_fid_put(fid); + if (retval) + return ERR_PTR(retval); + set_delayed_call(done, kfree_link, target); + return target; +} + +/** + * v9fs_vfs_get_link_dotl - follow a symlink path + * @dentry: dentry for symlink + * @inode: inode for symlink + * @done: destructor for return value + */ +static const char * +v9fs_vfs_get_link_dotl(struct dentry *dentry, + struct inode *inode, + struct delayed_call *done) +{ + struct v9fs_session_info *v9ses; + + v9ses = v9fs_inode2v9ses(inode); + if (v9ses->cache & (CACHE_META|CACHE_LOOSE)) + return page_get_link(dentry, inode, done); + + return v9fs_vfs_get_link_nocache_dotl(dentry, inode, done); +} + +int v9fs_refresh_inode_dotl(struct p9_fid *fid, struct inode *inode) +{ + struct p9_stat_dotl *st; + struct v9fs_session_info *v9ses; + unsigned int flags; + + 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_wrong_type(inode, st->st_mode)) + goto out; + + /* + * We don't want to refresh inode->i_size, + * because we may have cached data + */ + flags = (v9ses->cache & CACHE_LOOSE) ? + V9FS_STAT2INODE_KEEP_ISIZE : 0; + v9fs_stat2inode_dotl(st, inode, flags); +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, + .listxattr = v9fs_listxattr, + .get_inode_acl = v9fs_iop_get_inode_acl, + .get_acl = v9fs_iop_get_acl, + .set_acl = v9fs_iop_set_acl, +}; + +const struct inode_operations v9fs_file_inode_operations_dotl = { + .getattr = v9fs_vfs_getattr_dotl, + .setattr = v9fs_vfs_setattr_dotl, + .listxattr = v9fs_listxattr, + .get_inode_acl = v9fs_iop_get_inode_acl, + .get_acl = v9fs_iop_get_acl, + .set_acl = v9fs_iop_set_acl, +}; + +const struct inode_operations v9fs_symlink_inode_operations_dotl = { + .get_link = v9fs_vfs_get_link_dotl, + .getattr = v9fs_vfs_getattr_dotl, + .setattr = v9fs_vfs_setattr_dotl, + .listxattr = v9fs_listxattr, +}; diff --git a/fs/9p/vfs_super.c b/fs/9p/vfs_super.c new file mode 100644 index 000000000..94d6b02c2 --- /dev/null +++ b/fs/9p/vfs_super.c @@ -0,0 +1,366 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * + * Copyright (C) 2004 by Eric Van Hensbergen + * Copyright (C) 2002 by Ron Minnich + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "v9fs.h" +#include "v9fs_vfs.h" +#include "fid.h" +#include "xattr.h" +#include "acl.h" + +static const struct super_operations v9fs_super_ops, v9fs_super_ops_dotl; + +static int v9fs_fill_super(struct super_block *sb) +{ + int ret; + struct v9fs_session_info *v9ses = v9ses = sb->s_fs_info; + + 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; + if (v9fs_proto_dotl(v9ses)) { + sb->s_op = &v9fs_super_ops_dotl; + if (!(v9ses->flags & V9FS_NO_XATTR)) + sb->s_xattr = v9fs_xattr_handlers; + } else { + sb->s_op = &v9fs_super_ops; + sb->s_time_max = U32_MAX; + } + + sb->s_time_min = 0; + + ret = super_setup_bdi(sb); + if (ret) + return ret; + + if (!v9ses->cache) { + sb->s_bdi->ra_pages = 0; + sb->s_bdi->io_pages = 0; + } else { + sb->s_bdi->ra_pages = v9ses->maxdata >> PAGE_SHIFT; + sb->s_bdi->io_pages = v9ses->maxdata >> PAGE_SHIFT; + } + + sb->s_flags |= SB_ACTIVE; + +#ifdef CONFIG_9P_FS_POSIX_ACL + if ((v9ses->flags & V9FS_ACL_MASK) == V9FS_POSIX_ACL) + sb->s_flags |= SB_POSIXACL; +#endif + + return 0; +} + +/** + * v9fs_get_tree - create the mountable root and superblock + * @fc: the filesystem context + * + */ + +static int v9fs_get_tree(struct fs_context *fc) +{ + struct super_block *sb = NULL; + struct inode *inode = NULL; + struct dentry *root = NULL; + struct v9fs_session_info *v9ses = NULL; + struct p9_fid *fid; + int retval = 0; + + p9_debug(P9_DEBUG_VFS, "\n"); + + v9ses = kzalloc_obj(struct v9fs_session_info); + if (!v9ses) + return -ENOMEM; + + fid = v9fs_session_init(v9ses, fc); + if (IS_ERR(fid)) { + retval = PTR_ERR(fid); + goto free_session; + } + + fc->s_fs_info = v9ses; + sb = sget_fc(fc, NULL, set_anon_super_fc); + if (IS_ERR(sb)) { + retval = PTR_ERR(sb); + goto clunk_fid; + } + retval = v9fs_fill_super(sb); + if (retval) + goto release_sb; + + if (v9ses->cache & (CACHE_META|CACHE_LOOSE)) { + set_default_d_op(sb, &v9fs_cached_dentry_operations); + } else { + set_default_d_op(sb, &v9fs_dentry_operations); + sb->s_d_flags |= DCACHE_DONTCACHE; + } + + inode = v9fs_get_new_inode_from_fid(v9ses, fid, sb); + if (IS_ERR(inode)) { + retval = PTR_ERR(inode); + goto release_sb; + } + + root = d_make_root(inode); + if (!root) { + retval = -ENOMEM; + goto release_sb; + } + sb->s_root = root; + retval = v9fs_get_acl(inode, fid); + if (retval) + goto release_sb; + v9fs_fid_add(root, &fid); + + p9_debug(P9_DEBUG_VFS, " simple set mount, return 0\n"); + fc->root = dget(sb->s_root); + return 0; + +clunk_fid: + p9_fid_put(fid); + v9fs_session_close(v9ses); +free_session: + kfree(v9ses); + return retval; + +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_fid_put(fid); + deactivate_locked_super(sb); + return retval; +} + +/** + * v9fs_kill_super - Kill Superblock + * @s: superblock + * + */ + +static void v9fs_kill_super(struct super_block *s) +{ + struct v9fs_session_info *v9ses = s->s_fs_info; + + p9_debug(P9_DEBUG_VFS, " %p\n", s); + + kill_anon_super(s); + + v9fs_session_cancel(v9ses); + v9fs_session_close(v9ses); + kfree(v9ses); + s->s_fs_info = NULL; + p9_debug(P9_DEBUG_VFS, "exiting kill_super\n"); +} + +static void +v9fs_umount_begin(struct super_block *sb) +{ + struct v9fs_session_info *v9ses; + + 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; + 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 = u64_to_fsid(rs.fsid); + buf->f_namelen = rs.namelen; + } + if (res != -ENOSYS) + goto done; + } + res = simple_statfs(dentry, buf); +done: + p9_fid_put(fid); + return res; +} + +static int v9fs_drop_inode(struct inode *inode) +{ + struct v9fs_session_info *v9ses; + + v9ses = v9fs_inode2v9ses(inode); + if (v9ses->cache & (CACHE_META|CACHE_LOOSE)) + return inode_generic_drop(inode); + /* + * in case of non cached mode always drop the + * inode because we want the inode attribute + * to always match that on the server. + */ + return 1; +} + +static int v9fs_write_inode(struct inode *inode, + struct writeback_control *wbc) +{ + /* + * send an fsync request to server irrespective of + * wbc->sync_mode. + */ + p9_debug(P9_DEBUG_VFS, "%s: inode %p\n", __func__, inode); + return netfs_unpin_writeback(inode, wbc); +} + +static int v9fs_write_inode_dotl(struct inode *inode, + struct writeback_control *wbc) +{ + + p9_debug(P9_DEBUG_VFS, "%s: inode %p\n", __func__, inode); + + return netfs_unpin_writeback(inode, wbc); +} + +static const struct super_operations v9fs_super_ops = { + .alloc_inode = v9fs_alloc_inode, + .free_inode = v9fs_free_inode, + .statfs = simple_statfs, + .drop_inode = v9fs_drop_inode, + .evict_inode = v9fs_evict_inode, + .show_options = v9fs_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, + .free_inode = v9fs_free_inode, + .statfs = v9fs_statfs, + .drop_inode = v9fs_drop_inode, + .evict_inode = v9fs_evict_inode, + .show_options = v9fs_show_options, + .umount_begin = v9fs_umount_begin, + .write_inode = v9fs_write_inode_dotl, +}; + +static void v9fs_free_fc(struct fs_context *fc) +{ + struct v9fs_context *ctx = fc->fs_private; + + if (!ctx) + return; + + /* These should be NULL by now but guard against leaks */ + kfree(ctx->session_opts.uname); + kfree(ctx->session_opts.aname); +#ifdef CONFIG_9P_FSCACHE + kfree(ctx->session_opts.cachetag); +#endif + if (ctx->client_opts.trans_mod) + v9fs_put_trans(ctx->client_opts.trans_mod); + kfree(ctx); +} + +static const struct fs_context_operations v9fs_context_ops = { + .parse_param = v9fs_parse_param, + .get_tree = v9fs_get_tree, + .free = v9fs_free_fc, +}; + +static int v9fs_init_fs_context(struct fs_context *fc) +{ + struct v9fs_context *ctx; + + ctx = kzalloc_obj(*ctx); + if (!ctx) + return -ENOMEM; + + fc->ops = &v9fs_context_ops; + fc->fs_private = ctx; + + /* initialize core options */ + ctx->session_opts.afid = ~0; + ctx->session_opts.cache = CACHE_NONE; + ctx->session_opts.session_lock_timeout = P9_LOCK_TIMEOUT; + ctx->session_opts.uname = kstrdup(V9FS_DEFUSER, GFP_KERNEL); + if (!ctx->session_opts.uname) + goto error; + + ctx->session_opts.aname = kstrdup(V9FS_DEFANAME, GFP_KERNEL); + if (!ctx->session_opts.aname) + goto error; + + ctx->session_opts.uid = INVALID_UID; + ctx->session_opts.dfltuid = V9FS_DEFUID; + ctx->session_opts.dfltgid = V9FS_DEFGID; + ctx->session_opts.ndentry_timeout_ms = 0; + + /* initialize client options */ + ctx->client_opts.proto_version = p9_proto_2000L; + ctx->client_opts.msize = DEFAULT_MSIZE; + + /* initialize fd transport options */ + ctx->fd_opts.port = P9_FD_PORT; + ctx->fd_opts.rfd = ~0; + ctx->fd_opts.wfd = ~0; + ctx->fd_opts.privport = false; + + /* initialize rdma transport options */ + ctx->rdma_opts.port = P9_RDMA_PORT; + ctx->rdma_opts.sq_depth = P9_RDMA_SQ_DEPTH; + ctx->rdma_opts.rq_depth = P9_RDMA_RQ_DEPTH; + ctx->rdma_opts.timeout = P9_RDMA_TIMEOUT; + ctx->rdma_opts.privport = false; + + return 0; +error: + fc->need_free = 1; + return -ENOMEM; +} + +struct file_system_type v9fs_fs_type = { + .name = "9p", + .kill_sb = v9fs_kill_super, + .owner = THIS_MODULE, + .fs_flags = FS_RENAME_DOES_D_MOVE, + .init_fs_context = v9fs_init_fs_context, + .parameters = v9fs_param_spec, +}; +MODULE_ALIAS_FS("9p"); diff --git a/fs/9p/xattr.c b/fs/9p/xattr.c new file mode 100644 index 000000000..8604e3377 --- /dev/null +++ b/fs/9p/xattr.c @@ -0,0 +1,193 @@ +// SPDX-License-Identifier: LGPL-2.1 +/* + * Copyright IBM Corporation, 2010 + * Author Aneesh Kumar K.V + */ + +#include +#include +#include +#include +#include +#include +#include + +#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; + u64 attr_size; + struct p9_fid *attr_fid; + struct kvec kvec = {.iov_base = buffer, .iov_len = buffer_size}; + struct iov_iter to; + int err; + + iov_iter_kvec(&to, ITER_DEST, &kvec, 1, buffer_size); + + 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); + return retval; + } + if (attr_size > buffer_size) { + if (buffer_size) + retval = -ERANGE; + else if (attr_size > SSIZE_MAX) + retval = -EOVERFLOW; + else /* request to get the attr_size */ + retval = attr_size; + } else { + iov_iter_truncate(&to, attr_size); + retval = p9_client_read(attr_fid, 0, &to, &err); + if (err) + retval = err; + } + p9_fid_put(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; + int ret; + + 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); + ret = v9fs_fid_xattr_get(fid, name, buffer, buffer_size); + p9_fid_put(fid); + + return ret; +} + +/* + * 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) +{ + int ret; + struct p9_fid *fid; + + fid = v9fs_fid_lookup(dentry); + if (IS_ERR(fid)) + return PTR_ERR(fid); + ret = v9fs_fid_xattr_set(fid, name, value, value_len, flags); + p9_fid_put(fid); + return ret; +} + +int v9fs_fid_xattr_set(struct p9_fid *fid, const char *name, + const void *value, size_t value_len, int flags) +{ + struct kvec kvec = {.iov_base = (void *)value, .iov_len = value_len}; + struct iov_iter from; + int retval, err; + + iov_iter_kvec(&from, ITER_SOURCE, &kvec, 1, value_len); + + p9_debug(P9_DEBUG_VFS, "name = %s value_len = %zu flags = %d\n", + name, value_len, flags); + + /* Clone it */ + fid = clone_fid(fid); + 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); + else + p9_client_write(fid, 0, &from, &retval); + err = p9_fid_put(fid); + if (!retval && err) + retval = err; + return retval; +} + +ssize_t v9fs_listxattr(struct dentry *dentry, char *buffer, size_t buffer_size) +{ + /* Txattrwalk with an empty string lists xattrs instead */ + return v9fs_xattr_get(dentry, "", buffer, buffer_size); +} + +static int v9fs_xattr_handler_get(const struct xattr_handler *handler, + struct dentry *dentry, struct inode *inode, + const char *name, void *buffer, size_t size) +{ + const char *full_name = xattr_full_name(handler, name); + + return v9fs_xattr_get(dentry, full_name, buffer, size); +} + +static int v9fs_xattr_handler_set(const struct xattr_handler *handler, + struct mnt_idmap *idmap, + struct dentry *dentry, struct inode *inode, + const char *name, const void *value, + size_t size, int flags) +{ + const char *full_name = xattr_full_name(handler, name); + + return v9fs_xattr_set(dentry, full_name, value, size, flags); +} + +static const struct xattr_handler v9fs_xattr_user_handler = { + .prefix = XATTR_USER_PREFIX, + .get = v9fs_xattr_handler_get, + .set = v9fs_xattr_handler_set, +}; + +static const struct xattr_handler v9fs_xattr_trusted_handler = { + .prefix = XATTR_TRUSTED_PREFIX, + .get = v9fs_xattr_handler_get, + .set = v9fs_xattr_handler_set, +}; + +#ifdef CONFIG_9P_FS_SECURITY +static const struct xattr_handler v9fs_xattr_security_handler = { + .prefix = XATTR_SECURITY_PREFIX, + .get = v9fs_xattr_handler_get, + .set = v9fs_xattr_handler_set, +}; +#endif + +const struct xattr_handler * const v9fs_xattr_handlers[] = { + &v9fs_xattr_user_handler, + &v9fs_xattr_trusted_handler, +#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 000000000..3ad5a8023 --- /dev/null +++ b/fs/9p/xattr.h @@ -0,0 +1,25 @@ +/* SPDX-License-Identifier: LGPL-2.1 */ +/* + * Copyright IBM Corporation, 2010 + * Author Aneesh Kumar K.V + */ +#ifndef FS_9P_XATTR_H +#define FS_9P_XATTR_H + +#include +#include +#include + +extern const struct xattr_handler * const v9fs_xattr_handlers[]; + +ssize_t v9fs_fid_xattr_get(struct p9_fid *fid, const char *name, + void *buffer, size_t buffer_size); +ssize_t v9fs_xattr_get(struct dentry *dentry, const char *name, + void *buffer, size_t buffer_size); +int v9fs_fid_xattr_set(struct p9_fid *fid, const char *name, + const void *value, size_t value_len, int flags); +int v9fs_xattr_set(struct dentry *dentry, const char *name, + const void *value, size_t value_len, int flags); +ssize_t v9fs_listxattr(struct dentry *dentry, char *buffer, + size_t buffer_size); +#endif /* FS_9P_XATTR_H */ -- cgit v1.3.1