aboutsummaryrefslogtreecommitdiff
path: root/sys/contrib/openzfs/module/os/linux/zfs/zpl_inode.c
diff options
context:
space:
mode:
Diffstat (limited to 'sys/contrib/openzfs/module/os/linux/zfs/zpl_inode.c')
-rw-r--r--sys/contrib/openzfs/module/os/linux/zfs/zpl_inode.c878
1 files changed, 878 insertions, 0 deletions
diff --git a/sys/contrib/openzfs/module/os/linux/zfs/zpl_inode.c b/sys/contrib/openzfs/module/os/linux/zfs/zpl_inode.c
new file mode 100644
index 000000000000..f97662d052c7
--- /dev/null
+++ b/sys/contrib/openzfs/module/os/linux/zfs/zpl_inode.c
@@ -0,0 +1,878 @@
+// SPDX-License-Identifier: CDDL-1.0
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License (the "License").
+ * You may not use this file except in compliance with the License.
+ *
+ * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+ * or https://opensource.org/licenses/CDDL-1.0.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information: Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ */
+/*
+ * Copyright (c) 2011, Lawrence Livermore National Security, LLC.
+ * Copyright (c) 2015 by Chunwei Chen. All rights reserved.
+ * Copyright (c) 2025, Rob Norris <robn@despairlabs.com>
+ */
+
+
+#include <sys/sysmacros.h>
+#include <sys/zfs_ctldir.h>
+#include <sys/zfs_vfsops.h>
+#include <sys/zfs_vnops.h>
+#include <sys/zfs_znode.h>
+#include <sys/dmu_objset.h>
+#include <sys/spa_impl.h>
+#include <sys/vfs.h>
+#include <sys/zpl.h>
+#include <sys/file.h>
+
+static struct dentry *
+zpl_lookup(struct inode *dir, struct dentry *dentry, unsigned int flags)
+{
+ cred_t *cr = CRED();
+ struct inode *ip;
+ znode_t *zp;
+ int error;
+ fstrans_cookie_t cookie;
+ pathname_t *ppn = NULL;
+ pathname_t pn;
+ int zfs_flags = 0;
+ zfsvfs_t *zfsvfs = dentry->d_sb->s_fs_info;
+ dsl_dataset_t *ds = dmu_objset_ds(zfsvfs->z_os);
+ size_t dlen = dlen(dentry);
+
+ /*
+ * If z_longname is disabled, disallow create or rename of names
+ * longer than ZAP_MAXNAMELEN.
+ *
+ * This is needed in cases where longname was enabled first and some
+ * files/dirs with names > ZAP_MAXNAMELEN were created. And later
+ * longname was disabled. In such a case allow access to existing
+ * longnames. But disallow creation newer longnamed entities.
+ */
+ if (!zfsvfs->z_longname && (dlen >= ZAP_MAXNAMELEN)) {
+ /*
+ * If this is for create or rename fail it.
+ */
+ if (!dsl_dataset_feature_is_active(ds, SPA_FEATURE_LONGNAME) ||
+ (flags & (LOOKUP_CREATE | LOOKUP_RENAME_TARGET)))
+ return (ERR_PTR(-ENAMETOOLONG));
+ }
+ if (dlen >= ZAP_MAXNAMELEN_NEW) {
+ return (ERR_PTR(-ENAMETOOLONG));
+ }
+
+ crhold(cr);
+ cookie = spl_fstrans_mark();
+
+ /* If we are a case insensitive fs, we need the real name */
+ if (zfsvfs->z_case == ZFS_CASE_INSENSITIVE) {
+ zfs_flags = FIGNORECASE;
+ pn_alloc(&pn);
+ ppn = &pn;
+ }
+
+ error = -zfs_lookup(ITOZ(dir), dname(dentry), &zp,
+ zfs_flags, cr, NULL, ppn);
+ spl_fstrans_unmark(cookie);
+ ASSERT3S(error, <=, 0);
+ crfree(cr);
+
+ spin_lock(&dentry->d_lock);
+ dentry->d_time = jiffies;
+ spin_unlock(&dentry->d_lock);
+
+ if (error) {
+ /*
+ * If we have a case sensitive fs, we do not want to
+ * insert negative entries, so return NULL for ENOENT.
+ * Fall through if the error is not ENOENT. Also free memory.
+ */
+ if (ppn) {
+ pn_free(ppn);
+ if (error == -ENOENT)
+ return (NULL);
+ }
+
+ if (error == -ENOENT)
+ return (d_splice_alias(NULL, dentry));
+ else
+ return (ERR_PTR(error));
+ }
+ ip = ZTOI(zp);
+
+ /*
+ * If we are case insensitive, call the correct function
+ * to install the name.
+ */
+ if (ppn) {
+ struct dentry *new_dentry;
+ struct qstr ci_name;
+
+ if (strcmp(dname(dentry), pn.pn_buf) == 0) {
+ new_dentry = d_splice_alias(ip, dentry);
+ } else {
+ ci_name.name = pn.pn_buf;
+ ci_name.len = strlen(pn.pn_buf);
+ new_dentry = d_add_ci(dentry, ip, &ci_name);
+ }
+ pn_free(ppn);
+ return (new_dentry);
+ } else {
+ return (d_splice_alias(ip, dentry));
+ }
+}
+
+void
+zpl_vap_init(vattr_t *vap, struct inode *dir, umode_t mode, cred_t *cr,
+ zidmap_t *mnt_ns)
+{
+ vap->va_mask = ATTR_MODE;
+ vap->va_mode = mode;
+
+ vap->va_uid = zfs_vfsuid_to_uid(mnt_ns,
+ zfs_i_user_ns(dir), crgetuid(cr));
+
+ if (dir->i_mode & S_ISGID) {
+ vap->va_gid = KGID_TO_SGID(dir->i_gid);
+ if (S_ISDIR(mode))
+ vap->va_mode |= S_ISGID;
+ } else {
+ vap->va_gid = zfs_vfsgid_to_gid(mnt_ns,
+ zfs_i_user_ns(dir), crgetgid(cr));
+ }
+}
+
+static inline bool
+is_nametoolong(struct dentry *dentry)
+{
+ zfsvfs_t *zfsvfs = dentry->d_sb->s_fs_info;
+ size_t dlen = dlen(dentry);
+
+ return ((!zfsvfs->z_longname && dlen >= ZAP_MAXNAMELEN) ||
+ dlen >= ZAP_MAXNAMELEN_NEW);
+}
+
+static int
+#ifdef HAVE_IOPS_CREATE_USERNS
+zpl_create(struct user_namespace *user_ns, struct inode *dir,
+ struct dentry *dentry, umode_t mode, bool flag)
+#elif defined(HAVE_IOPS_CREATE_IDMAP)
+zpl_create(struct mnt_idmap *user_ns, struct inode *dir,
+ struct dentry *dentry, umode_t mode, bool flag)
+#else
+zpl_create(struct inode *dir, struct dentry *dentry, umode_t mode, bool flag)
+#endif
+{
+ cred_t *cr = CRED();
+ znode_t *zp;
+ vattr_t *vap;
+ int error;
+ fstrans_cookie_t cookie;
+#if !(defined(HAVE_IOPS_CREATE_USERNS) || defined(HAVE_IOPS_CREATE_IDMAP))
+ zidmap_t *user_ns = kcred->user_ns;
+#endif
+
+ if (is_nametoolong(dentry)) {
+ return (-ENAMETOOLONG);
+ }
+
+ crhold(cr);
+ vap = kmem_zalloc(sizeof (vattr_t), KM_SLEEP);
+ zpl_vap_init(vap, dir, mode, cr, user_ns);
+
+ cookie = spl_fstrans_mark();
+ error = -zfs_create(ITOZ(dir), dname(dentry), vap, 0,
+ mode, &zp, cr, 0, NULL, user_ns);
+ if (error == 0) {
+ error = zpl_xattr_security_init(ZTOI(zp), dir, &dentry->d_name);
+ if (error == 0)
+ error = zpl_init_acl(ZTOI(zp), dir);
+
+ if (error) {
+ (void) zfs_remove(ITOZ(dir), dname(dentry), cr, 0);
+ remove_inode_hash(ZTOI(zp));
+ iput(ZTOI(zp));
+ } else {
+ d_instantiate(dentry, ZTOI(zp));
+ }
+ }
+
+ spl_fstrans_unmark(cookie);
+ kmem_free(vap, sizeof (vattr_t));
+ crfree(cr);
+ ASSERT3S(error, <=, 0);
+
+ return (error);
+}
+
+static int
+#ifdef HAVE_IOPS_MKNOD_USERNS
+zpl_mknod(struct user_namespace *user_ns, struct inode *dir,
+ struct dentry *dentry, umode_t mode,
+#elif defined(HAVE_IOPS_MKNOD_IDMAP)
+zpl_mknod(struct mnt_idmap *user_ns, struct inode *dir,
+ struct dentry *dentry, umode_t mode,
+#else
+zpl_mknod(struct inode *dir, struct dentry *dentry, umode_t mode,
+#endif
+ dev_t rdev)
+{
+ cred_t *cr = CRED();
+ znode_t *zp;
+ vattr_t *vap;
+ int error;
+ fstrans_cookie_t cookie;
+#if !(defined(HAVE_IOPS_MKNOD_USERNS) || defined(HAVE_IOPS_MKNOD_IDMAP))
+ zidmap_t *user_ns = kcred->user_ns;
+#endif
+
+ if (is_nametoolong(dentry)) {
+ return (-ENAMETOOLONG);
+ }
+
+ /*
+ * We currently expect Linux to supply rdev=0 for all sockets
+ * and fifos, but we want to know if this behavior ever changes.
+ */
+ if (S_ISSOCK(mode) || S_ISFIFO(mode))
+ ASSERT0(rdev);
+
+ crhold(cr);
+ vap = kmem_zalloc(sizeof (vattr_t), KM_SLEEP);
+ zpl_vap_init(vap, dir, mode, cr, user_ns);
+ vap->va_rdev = rdev;
+
+ cookie = spl_fstrans_mark();
+ error = -zfs_create(ITOZ(dir), dname(dentry), vap, 0,
+ mode, &zp, cr, 0, NULL, user_ns);
+ if (error == 0) {
+ error = zpl_xattr_security_init(ZTOI(zp), dir, &dentry->d_name);
+ if (error == 0)
+ error = zpl_init_acl(ZTOI(zp), dir);
+
+ if (error) {
+ (void) zfs_remove(ITOZ(dir), dname(dentry), cr, 0);
+ remove_inode_hash(ZTOI(zp));
+ iput(ZTOI(zp));
+ } else {
+ d_instantiate(dentry, ZTOI(zp));
+ }
+ }
+
+ spl_fstrans_unmark(cookie);
+ kmem_free(vap, sizeof (vattr_t));
+ crfree(cr);
+ ASSERT3S(error, <=, 0);
+
+ return (error);
+}
+
+static int
+#ifdef HAVE_TMPFILE_IDMAP
+zpl_tmpfile(struct mnt_idmap *userns, struct inode *dir,
+ struct file *file, umode_t mode)
+#elif !defined(HAVE_TMPFILE_DENTRY)
+zpl_tmpfile(struct user_namespace *userns, struct inode *dir,
+ struct file *file, umode_t mode)
+#else
+#ifdef HAVE_TMPFILE_USERNS
+zpl_tmpfile(struct user_namespace *userns, struct inode *dir,
+ struct dentry *dentry, umode_t mode)
+#else
+zpl_tmpfile(struct inode *dir, struct dentry *dentry, umode_t mode)
+#endif
+#endif
+{
+ cred_t *cr = CRED();
+ struct inode *ip;
+ vattr_t *vap;
+ int error;
+ fstrans_cookie_t cookie;
+#if !(defined(HAVE_TMPFILE_USERNS) || defined(HAVE_TMPFILE_IDMAP))
+ zidmap_t *userns = kcred->user_ns;
+#endif
+
+ crhold(cr);
+ vap = kmem_zalloc(sizeof (vattr_t), KM_SLEEP);
+ /*
+ * The VFS does not apply the umask, therefore it is applied here
+ * when POSIX ACLs are not enabled.
+ */
+ if (!IS_POSIXACL(dir))
+ mode &= ~current_umask();
+ zpl_vap_init(vap, dir, mode, cr, userns);
+
+ cookie = spl_fstrans_mark();
+ error = -zfs_tmpfile(dir, vap, 0, mode, &ip, cr, 0, NULL, userns);
+ if (error == 0) {
+ /* d_tmpfile will do drop_nlink, so we should set it first */
+ set_nlink(ip, 1);
+#ifndef HAVE_TMPFILE_DENTRY
+ d_tmpfile(file, ip);
+
+ error = zpl_xattr_security_init(ip, dir,
+ &file->f_path.dentry->d_name);
+#else
+ d_tmpfile(dentry, ip);
+
+ error = zpl_xattr_security_init(ip, dir, &dentry->d_name);
+#endif
+ if (error == 0)
+ error = zpl_init_acl(ip, dir);
+#ifndef HAVE_TMPFILE_DENTRY
+ error = finish_open_simple(file, error);
+#endif
+ /*
+ * don't need to handle error here, file is already in
+ * unlinked set.
+ */
+ }
+
+ spl_fstrans_unmark(cookie);
+ kmem_free(vap, sizeof (vattr_t));
+ crfree(cr);
+ ASSERT3S(error, <=, 0);
+
+ return (error);
+}
+
+static int
+zpl_unlink(struct inode *dir, struct dentry *dentry)
+{
+ cred_t *cr = CRED();
+ int error;
+ fstrans_cookie_t cookie;
+ zfsvfs_t *zfsvfs = dentry->d_sb->s_fs_info;
+
+ crhold(cr);
+ cookie = spl_fstrans_mark();
+ error = -zfs_remove(ITOZ(dir), dname(dentry), cr, 0);
+
+ /*
+ * For a CI FS we must invalidate the dentry to prevent the
+ * creation of negative entries.
+ */
+ if (error == 0 && zfsvfs->z_case == ZFS_CASE_INSENSITIVE)
+ d_invalidate(dentry);
+
+ spl_fstrans_unmark(cookie);
+ crfree(cr);
+ ASSERT3S(error, <=, 0);
+
+ return (error);
+}
+
+#if defined(HAVE_IOPS_MKDIR_USERNS)
+static int
+zpl_mkdir(struct user_namespace *user_ns, struct inode *dir,
+ struct dentry *dentry, umode_t mode)
+#elif defined(HAVE_IOPS_MKDIR_IDMAP)
+static int
+zpl_mkdir(struct mnt_idmap *user_ns, struct inode *dir,
+ struct dentry *dentry, umode_t mode)
+#elif defined(HAVE_IOPS_MKDIR_DENTRY)
+static struct dentry *
+zpl_mkdir(struct mnt_idmap *user_ns, struct inode *dir,
+ struct dentry *dentry, umode_t mode)
+#else
+static int
+zpl_mkdir(struct inode *dir, struct dentry *dentry, umode_t mode)
+#endif
+{
+ cred_t *cr = CRED();
+ vattr_t *vap;
+ znode_t *zp;
+ int error;
+ fstrans_cookie_t cookie;
+#if !(defined(HAVE_IOPS_MKDIR_USERNS) || \
+ defined(HAVE_IOPS_MKDIR_IDMAP) || defined(HAVE_IOPS_MKDIR_DENTRY))
+ zidmap_t *user_ns = kcred->user_ns;
+#endif
+
+ if (is_nametoolong(dentry)) {
+ error = -ENAMETOOLONG;
+ goto err;
+ }
+
+ crhold(cr);
+ vap = kmem_zalloc(sizeof (vattr_t), KM_SLEEP);
+ zpl_vap_init(vap, dir, mode | S_IFDIR, cr, user_ns);
+
+ cookie = spl_fstrans_mark();
+ error = -zfs_mkdir(ITOZ(dir), dname(dentry), vap, &zp, cr, 0, NULL,
+ user_ns);
+ if (error == 0) {
+ error = zpl_xattr_security_init(ZTOI(zp), dir, &dentry->d_name);
+ if (error == 0)
+ error = zpl_init_acl(ZTOI(zp), dir);
+
+ if (error) {
+ (void) zfs_rmdir(ITOZ(dir), dname(dentry), NULL, cr, 0);
+ remove_inode_hash(ZTOI(zp));
+ iput(ZTOI(zp));
+ } else {
+ d_instantiate(dentry, ZTOI(zp));
+ }
+ }
+
+ spl_fstrans_unmark(cookie);
+ kmem_free(vap, sizeof (vattr_t));
+ crfree(cr);
+
+err:
+ ASSERT3S(error, <=, 0);
+#if defined(HAVE_IOPS_MKDIR_DENTRY)
+ return (error != 0 ? ERR_PTR(error) : NULL);
+#else
+ return (error);
+#endif
+}
+
+static int
+zpl_rmdir(struct inode *dir, struct dentry *dentry)
+{
+ cred_t *cr = CRED();
+ int error;
+ fstrans_cookie_t cookie;
+ zfsvfs_t *zfsvfs = dentry->d_sb->s_fs_info;
+
+ crhold(cr);
+ cookie = spl_fstrans_mark();
+ error = -zfs_rmdir(ITOZ(dir), dname(dentry), NULL, cr, 0);
+
+ /*
+ * For a CI FS we must invalidate the dentry to prevent the
+ * creation of negative entries.
+ */
+ if (error == 0 && zfsvfs->z_case == ZFS_CASE_INSENSITIVE)
+ d_invalidate(dentry);
+
+ spl_fstrans_unmark(cookie);
+ crfree(cr);
+ ASSERT3S(error, <=, 0);
+
+ return (error);
+}
+
+static int
+#ifdef HAVE_USERNS_IOPS_GETATTR
+zpl_getattr_impl(struct user_namespace *user_ns,
+ const struct path *path, struct kstat *stat, u32 request_mask,
+ unsigned int query_flags)
+#elif defined(HAVE_IDMAP_IOPS_GETATTR)
+zpl_getattr_impl(struct mnt_idmap *user_ns,
+ const struct path *path, struct kstat *stat, u32 request_mask,
+ unsigned int query_flags)
+#else
+zpl_getattr_impl(const struct path *path, struct kstat *stat, u32 request_mask,
+ unsigned int query_flags)
+#endif
+{
+ int error;
+ fstrans_cookie_t cookie;
+ struct inode *ip = path->dentry->d_inode;
+ znode_t *zp __maybe_unused = ITOZ(ip);
+
+ cookie = spl_fstrans_mark();
+
+ /*
+ * XXX query_flags currently ignored.
+ */
+
+#ifdef HAVE_GENERIC_FILLATTR_IDMAP_REQMASK
+ error = -zfs_getattr_fast(user_ns, request_mask, ip, stat);
+#elif (defined(HAVE_USERNS_IOPS_GETATTR) || defined(HAVE_IDMAP_IOPS_GETATTR))
+ error = -zfs_getattr_fast(user_ns, ip, stat);
+#else
+ error = -zfs_getattr_fast(kcred->user_ns, ip, stat);
+#endif
+
+#ifdef STATX_BTIME
+ if (request_mask & STATX_BTIME) {
+ stat->btime = zp->z_btime;
+ stat->result_mask |= STATX_BTIME;
+ }
+#endif
+
+#ifdef STATX_DIOALIGN
+ if (request_mask & STATX_DIOALIGN) {
+ uint64_t align;
+ if (zfs_get_direct_alignment(zp, &align) == 0) {
+ stat->dio_mem_align = PAGE_SIZE;
+ stat->dio_offset_align = align;
+ stat->result_mask |= STATX_DIOALIGN;
+ }
+ }
+#endif
+
+#ifdef STATX_ATTR_IMMUTABLE
+ if (zp->z_pflags & ZFS_IMMUTABLE)
+ stat->attributes |= STATX_ATTR_IMMUTABLE;
+ stat->attributes_mask |= STATX_ATTR_IMMUTABLE;
+#endif
+
+#ifdef STATX_ATTR_APPEND
+ if (zp->z_pflags & ZFS_APPENDONLY)
+ stat->attributes |= STATX_ATTR_APPEND;
+ stat->attributes_mask |= STATX_ATTR_APPEND;
+#endif
+
+#ifdef STATX_ATTR_NODUMP
+ if (zp->z_pflags & ZFS_NODUMP)
+ stat->attributes |= STATX_ATTR_NODUMP;
+ stat->attributes_mask |= STATX_ATTR_NODUMP;
+#endif
+
+ spl_fstrans_unmark(cookie);
+ ASSERT3S(error, <=, 0);
+
+ return (error);
+}
+ZPL_GETATTR_WRAPPER(zpl_getattr);
+
+static int
+#ifdef HAVE_USERNS_IOPS_SETATTR
+zpl_setattr(struct user_namespace *user_ns, struct dentry *dentry,
+ struct iattr *ia)
+#elif defined(HAVE_IDMAP_IOPS_SETATTR)
+zpl_setattr(struct mnt_idmap *user_ns, struct dentry *dentry,
+ struct iattr *ia)
+#else
+zpl_setattr(struct dentry *dentry, struct iattr *ia)
+#endif
+{
+ struct inode *ip = dentry->d_inode;
+ cred_t *cr = CRED();
+ vattr_t *vap;
+ int error;
+ fstrans_cookie_t cookie;
+
+#ifdef HAVE_SETATTR_PREPARE_USERNS
+ error = zpl_setattr_prepare(user_ns, dentry, ia);
+#elif defined(HAVE_SETATTR_PREPARE_IDMAP)
+ error = zpl_setattr_prepare(user_ns, dentry, ia);
+#else
+ error = zpl_setattr_prepare(zfs_init_idmap, dentry, ia);
+#endif
+ if (error)
+ return (error);
+
+ crhold(cr);
+ vap = kmem_zalloc(sizeof (vattr_t), KM_SLEEP);
+ vap->va_mask = ia->ia_valid & ATTR_IATTR_MASK;
+ vap->va_mode = ia->ia_mode;
+ if (ia->ia_valid & ATTR_UID)
+#ifdef HAVE_IATTR_VFSID
+ vap->va_uid = zfs_vfsuid_to_uid(user_ns, zfs_i_user_ns(ip),
+ __vfsuid_val(ia->ia_vfsuid));
+#else
+ vap->va_uid = KUID_TO_SUID(ia->ia_uid);
+#endif
+ if (ia->ia_valid & ATTR_GID)
+#ifdef HAVE_IATTR_VFSID
+ vap->va_gid = zfs_vfsgid_to_gid(user_ns, zfs_i_user_ns(ip),
+ __vfsgid_val(ia->ia_vfsgid));
+#else
+ vap->va_gid = KGID_TO_SGID(ia->ia_gid);
+#endif
+ vap->va_size = ia->ia_size;
+ vap->va_atime = ia->ia_atime;
+ vap->va_mtime = ia->ia_mtime;
+ vap->va_ctime = ia->ia_ctime;
+
+ if (vap->va_mask & ATTR_ATIME)
+ zpl_inode_set_atime_to_ts(ip,
+ zpl_inode_timestamp_truncate(ia->ia_atime, ip));
+
+ cookie = spl_fstrans_mark();
+#ifdef HAVE_USERNS_IOPS_SETATTR
+ error = -zfs_setattr(ITOZ(ip), vap, 0, cr, user_ns);
+#elif defined(HAVE_IDMAP_IOPS_SETATTR)
+ error = -zfs_setattr(ITOZ(ip), vap, 0, cr, user_ns);
+#else
+ error = -zfs_setattr(ITOZ(ip), vap, 0, cr, zfs_init_idmap);
+#endif
+ if (!error && (ia->ia_valid & ATTR_MODE))
+ error = zpl_chmod_acl(ip);
+
+ spl_fstrans_unmark(cookie);
+ kmem_free(vap, sizeof (vattr_t));
+ crfree(cr);
+ ASSERT3S(error, <=, 0);
+
+ return (error);
+}
+
+static int
+#ifdef HAVE_IOPS_RENAME_USERNS
+zpl_rename2(struct user_namespace *user_ns, struct inode *sdip,
+ struct dentry *sdentry, struct inode *tdip, struct dentry *tdentry,
+ unsigned int rflags)
+#elif defined(HAVE_IOPS_RENAME_IDMAP)
+zpl_rename2(struct mnt_idmap *user_ns, struct inode *sdip,
+ struct dentry *sdentry, struct inode *tdip, struct dentry *tdentry,
+ unsigned int rflags)
+#else
+zpl_rename2(struct inode *sdip, struct dentry *sdentry,
+ struct inode *tdip, struct dentry *tdentry, unsigned int rflags)
+#endif
+{
+ cred_t *cr = CRED();
+ vattr_t *wo_vap = NULL;
+ int error;
+ fstrans_cookie_t cookie;
+#if !(defined(HAVE_IOPS_RENAME_USERNS) || defined(HAVE_IOPS_RENAME_IDMAP))
+ zidmap_t *user_ns = kcred->user_ns;
+#endif
+
+ if (is_nametoolong(tdentry)) {
+ return (-ENAMETOOLONG);
+ }
+
+ crhold(cr);
+ if (rflags & RENAME_WHITEOUT) {
+ wo_vap = kmem_zalloc(sizeof (vattr_t), KM_SLEEP);
+ zpl_vap_init(wo_vap, sdip, S_IFCHR, cr, user_ns);
+ wo_vap->va_rdev = makedevice(0, 0);
+ }
+
+ cookie = spl_fstrans_mark();
+ error = -zfs_rename(ITOZ(sdip), dname(sdentry), ITOZ(tdip),
+ dname(tdentry), cr, 0, rflags, wo_vap, user_ns);
+ spl_fstrans_unmark(cookie);
+ if (wo_vap)
+ kmem_free(wo_vap, sizeof (vattr_t));
+ crfree(cr);
+ ASSERT3S(error, <=, 0);
+
+ return (error);
+}
+
+#if !defined(HAVE_IOPS_RENAME_USERNS) && \
+ !defined(HAVE_RENAME_WANTS_FLAGS) && \
+ !defined(HAVE_IOPS_RENAME_IDMAP)
+static int
+zpl_rename(struct inode *sdip, struct dentry *sdentry,
+ struct inode *tdip, struct dentry *tdentry)
+{
+ return (zpl_rename2(sdip, sdentry, tdip, tdentry, 0));
+}
+#endif
+
+static int
+#ifdef HAVE_IOPS_SYMLINK_USERNS
+zpl_symlink(struct user_namespace *user_ns, struct inode *dir,
+ struct dentry *dentry, const char *name)
+#elif defined(HAVE_IOPS_SYMLINK_IDMAP)
+zpl_symlink(struct mnt_idmap *user_ns, struct inode *dir,
+ struct dentry *dentry, const char *name)
+#else
+zpl_symlink(struct inode *dir, struct dentry *dentry, const char *name)
+#endif
+{
+ cred_t *cr = CRED();
+ vattr_t *vap;
+ znode_t *zp;
+ int error;
+ fstrans_cookie_t cookie;
+#if !(defined(HAVE_IOPS_SYMLINK_USERNS) || defined(HAVE_IOPS_SYMLINK_IDMAP))
+ zidmap_t *user_ns = kcred->user_ns;
+#endif
+
+ if (is_nametoolong(dentry)) {
+ return (-ENAMETOOLONG);
+ }
+
+ crhold(cr);
+ vap = kmem_zalloc(sizeof (vattr_t), KM_SLEEP);
+ zpl_vap_init(vap, dir, S_IFLNK | S_IRWXUGO, cr, user_ns);
+
+ cookie = spl_fstrans_mark();
+ error = -zfs_symlink(ITOZ(dir), dname(dentry), vap,
+ (char *)name, &zp, cr, 0, user_ns);
+ if (error == 0) {
+ error = zpl_xattr_security_init(ZTOI(zp), dir, &dentry->d_name);
+ if (error) {
+ (void) zfs_remove(ITOZ(dir), dname(dentry), cr, 0);
+ remove_inode_hash(ZTOI(zp));
+ iput(ZTOI(zp));
+ } else {
+ d_instantiate(dentry, ZTOI(zp));
+ }
+ }
+
+ spl_fstrans_unmark(cookie);
+ kmem_free(vap, sizeof (vattr_t));
+ crfree(cr);
+ ASSERT3S(error, <=, 0);
+
+ return (error);
+}
+
+static void
+zpl_put_link(void *ptr)
+{
+ kmem_free(ptr, MAXPATHLEN);
+}
+
+static int
+zpl_get_link_common(struct dentry *dentry, struct inode *ip, char **link)
+{
+ fstrans_cookie_t cookie;
+ cred_t *cr = CRED();
+ int error;
+
+ crhold(cr);
+ *link = NULL;
+
+ struct iovec iov;
+ iov.iov_len = MAXPATHLEN;
+ iov.iov_base = kmem_zalloc(MAXPATHLEN, KM_SLEEP);
+
+ zfs_uio_t uio;
+ zfs_uio_iovec_init(&uio, &iov, 1, 0, UIO_SYSSPACE, MAXPATHLEN - 1, 0);
+
+ cookie = spl_fstrans_mark();
+ error = -zfs_readlink(ip, &uio, cr);
+ spl_fstrans_unmark(cookie);
+ crfree(cr);
+
+ if (error)
+ kmem_free(iov.iov_base, MAXPATHLEN);
+ else
+ *link = iov.iov_base;
+
+ return (error);
+}
+
+static const char *
+zpl_get_link(struct dentry *dentry, struct inode *inode,
+ struct delayed_call *done)
+{
+ char *link = NULL;
+ int error;
+
+ if (!dentry)
+ return (ERR_PTR(-ECHILD));
+
+ error = zpl_get_link_common(dentry, inode, &link);
+ if (error)
+ return (ERR_PTR(error));
+
+ set_delayed_call(done, zpl_put_link, link);
+
+ return (link);
+}
+
+static int
+zpl_link(struct dentry *old_dentry, struct inode *dir, struct dentry *dentry)
+{
+ cred_t *cr = CRED();
+ struct inode *ip = old_dentry->d_inode;
+ int error;
+ fstrans_cookie_t cookie;
+
+ if (is_nametoolong(dentry)) {
+ return (-ENAMETOOLONG);
+ }
+
+ if (ip->i_nlink >= ZFS_LINK_MAX)
+ return (-EMLINK);
+
+ crhold(cr);
+ zpl_inode_set_ctime_to_ts(ip, current_time(ip));
+ /* Must have an existing ref, so igrab() cannot return NULL */
+ VERIFY3P(igrab(ip), !=, NULL);
+
+ cookie = spl_fstrans_mark();
+ error = -zfs_link(ITOZ(dir), ITOZ(ip), dname(dentry), cr, 0);
+ if (error) {
+ iput(ip);
+ goto out;
+ }
+
+ d_instantiate(dentry, ip);
+out:
+ spl_fstrans_unmark(cookie);
+ crfree(cr);
+ ASSERT3S(error, <=, 0);
+
+ return (error);
+}
+
+const struct inode_operations zpl_inode_operations = {
+ .setattr = zpl_setattr,
+ .getattr = zpl_getattr,
+ .listxattr = zpl_xattr_list,
+#if defined(CONFIG_FS_POSIX_ACL)
+ .set_acl = zpl_set_acl,
+#if defined(HAVE_GET_INODE_ACL)
+ .get_inode_acl = zpl_get_acl,
+#else
+ .get_acl = zpl_get_acl,
+#endif /* HAVE_GET_INODE_ACL */
+#endif /* CONFIG_FS_POSIX_ACL */
+};
+
+const struct inode_operations zpl_dir_inode_operations = {
+ .create = zpl_create,
+ .lookup = zpl_lookup,
+ .link = zpl_link,
+ .unlink = zpl_unlink,
+ .symlink = zpl_symlink,
+ .mkdir = zpl_mkdir,
+ .rmdir = zpl_rmdir,
+ .mknod = zpl_mknod,
+#if defined(HAVE_RENAME_WANTS_FLAGS) || defined(HAVE_IOPS_RENAME_USERNS)
+ .rename = zpl_rename2,
+#elif defined(HAVE_IOPS_RENAME_IDMAP)
+ .rename = zpl_rename2,
+#else
+ .rename = zpl_rename,
+#endif
+ .tmpfile = zpl_tmpfile,
+ .setattr = zpl_setattr,
+ .getattr = zpl_getattr,
+ .listxattr = zpl_xattr_list,
+#if defined(CONFIG_FS_POSIX_ACL)
+ .set_acl = zpl_set_acl,
+#if defined(HAVE_GET_INODE_ACL)
+ .get_inode_acl = zpl_get_acl,
+#else
+ .get_acl = zpl_get_acl,
+#endif /* HAVE_GET_INODE_ACL */
+#endif /* CONFIG_FS_POSIX_ACL */
+};
+
+const struct inode_operations zpl_symlink_inode_operations = {
+ .get_link = zpl_get_link,
+ .setattr = zpl_setattr,
+ .getattr = zpl_getattr,
+ .listxattr = zpl_xattr_list,
+};
+
+const struct inode_operations zpl_special_inode_operations = {
+ .setattr = zpl_setattr,
+ .getattr = zpl_getattr,
+ .listxattr = zpl_xattr_list,
+#if defined(CONFIG_FS_POSIX_ACL)
+ .set_acl = zpl_set_acl,
+#if defined(HAVE_GET_INODE_ACL)
+ .get_inode_acl = zpl_get_acl,
+#else
+ .get_acl = zpl_get_acl,
+#endif /* HAVE_GET_INODE_ACL */
+#endif /* CONFIG_FS_POSIX_ACL */
+};