From 9d317793aa66d05a8c44410ea24a9a4166a89bbd Mon Sep 17 00:00:00 2001 From: Richard Yao Date: Fri, 1 Jul 2011 15:56:35 -0700 Subject: [PATCH] Implement File Attribute Support We add support for lsattr and chattr to resolve a regression caused by 88c283952f0bfeab54612f9ce666601d83c4244f that broke Python's xattr.list(). That changet broke Gentoo Portage's FEATURES=xattr, which depended on Python's xattr.list(). Only attributes common to both Solaris and Linux are supported. These are 'a', 'd' and 'i' in Linux's lsattr and chattr commands. File attributes exclusive to Solaris are present in the ZFS code, but cannot be accessed or modified through this method. That was the case prior to this patch. The resolution of issue zfsonlinux/zfs#229 should implement some method to permit access and modification of Solaris-specific attributes. References: https://bugs.gentoo.org/show_bug.cgi?id=483516 Original-patch-by: Brian Behlendorf Signed-off-by: Richard Yao Signed-off-by: Brian Behlendorf Closes #1691 --- include/linux/vfs_compat.h | 3 -- module/zfs/zfs_znode.c | 20 ++++++++ module/zfs/zpl_file.c | 97 ++++++++++++++++++++++++++++++++++++-- 3 files changed, 114 insertions(+), 6 deletions(-) diff --git a/include/linux/vfs_compat.h b/include/linux/vfs_compat.h index 4358cd2883a..d7d79bd262c 100644 --- a/include/linux/vfs_compat.h +++ b/include/linux/vfs_compat.h @@ -152,9 +152,6 @@ typedef int zpl_umode_t; #define zpl_sget(type, cmp, set, fl, mtd) sget(type, cmp, set, mtd) #endif /* HAVE_5ARG_SGET */ -#define ZFS_IOC_GETFLAGS FS_IOC_GETFLAGS -#define ZFS_IOC_SETFLAGS FS_IOC_SETFLAGS - #if defined(SEEK_HOLE) && defined(SEEK_DATA) && !defined(HAVE_LSEEK_EXECUTE) static inline loff_t lseek_execute( diff --git a/module/zfs/zfs_znode.c b/module/zfs/zfs_znode.c index 2ab896fecf3..8cd0d8379af 100644 --- a/module/zfs/zfs_znode.c +++ b/module/zfs/zfs_znode.c @@ -446,6 +446,25 @@ zfs_znode_alloc(zfs_sb_t *zsb, dmu_buf_t *db, int blksz, return (NULL); } +void +zfs_set_inode_flags(znode_t *zp, struct inode *ip) +{ + /* + * Linux and Solaris have different sets of file attributes, so we + * restrict this conversion to the intersection of the two. + */ + + if (zp->z_pflags & ZFS_IMMUTABLE) + ip->i_flags |= S_IMMUTABLE; + else + ip->i_flags &= ~S_IMMUTABLE; + + if (zp->z_pflags & ZFS_APPENDONLY) + ip->i_flags |= S_APPEND; + else + ip->i_flags &= ~S_APPEND; +} + /* * Update the embedded inode given the znode. We should work toward * eliminating this function as soon as possible by removing values @@ -479,6 +498,7 @@ zfs_inode_update(znode_t *zp) ip->i_gid = SGID_TO_KGID(zp->z_gid); set_nlink(ip, zp->z_links); ip->i_mode = zp->z_mode; + zfs_set_inode_flags(zp, ip); ip->i_blkbits = SPA_MINBLOCKSHIFT; dmu_object_size_from_db(sa_get_db(zp->z_sa_hdl), &blksize, (u_longlong_t *)&ip->i_blocks); diff --git a/module/zfs/zpl_file.c b/module/zfs/zpl_file.c index 3737bb51911..2a7bcb9b0a6 100644 --- a/module/zfs/zpl_file.c +++ b/module/zfs/zpl_file.c @@ -520,13 +520,104 @@ zpl_fallocate(struct file *filp, int mode, loff_t offset, loff_t len) } #endif /* HAVE_FILE_FALLOCATE */ +/* + * Map zfs file z_pflags (xvattr_t) to linux file attributes. Only file + * attributes common to both Linux and Solaris are mapped. + */ +static int +zpl_ioctl_getflags(struct file *filp, void __user *arg) +{ + struct inode *ip = filp->f_dentry->d_inode; + unsigned int ioctl_flags = 0; + uint64_t zfs_flags = ITOZ(ip)->z_pflags; + int error; + + if (zfs_flags & ZFS_IMMUTABLE) + ioctl_flags |= FS_IMMUTABLE_FL; + + if (zfs_flags & ZFS_APPENDONLY) + ioctl_flags |= FS_APPEND_FL; + + if (zfs_flags & ZFS_NODUMP) + ioctl_flags |= FS_NODUMP_FL; + + ioctl_flags &= FS_FL_USER_VISIBLE; + + error = copy_to_user(arg, &ioctl_flags, sizeof (ioctl_flags)); + + return (error); +} + +/* + * fchange() is a helper macro to detect if we have been asked to change a + * flag. This is ugly, but the requirement that we do this is a consequence of + * how the Linux file attribute interface was designed. Another consequence is + * that concurrent modification of files suffers from a TOCTOU race. Neither + * are things we can fix without modifying the kernel-userland interface, which + * is outside of our jurisdiction. + */ + +#define fchange(f0, f1, b0, b1) ((((f0) & (b0)) == (b0)) != \ + (((b1) & (f1)) == (f1))) + +static int +zpl_ioctl_setflags(struct file *filp, void __user *arg) +{ + struct inode *ip = filp->f_dentry->d_inode; + uint64_t zfs_flags = ITOZ(ip)->z_pflags; + unsigned int ioctl_flags; + cred_t *cr = CRED(); + xvattr_t xva; + xoptattr_t *xoap; + int error; + + if (copy_from_user(&ioctl_flags, arg, sizeof (ioctl_flags))) + return (-EFAULT); + + if ((ioctl_flags & ~(FS_IMMUTABLE_FL | FS_APPEND_FL | FS_NODUMP_FL))) + return (-EOPNOTSUPP); + + if ((ioctl_flags & ~(FS_FL_USER_MODIFIABLE))) + return (-EACCES); + + if ((fchange(ioctl_flags, zfs_flags, FS_IMMUTABLE_FL, ZFS_IMMUTABLE) || + fchange(ioctl_flags, zfs_flags, FS_APPEND_FL, ZFS_APPENDONLY)) && + !capable(CAP_LINUX_IMMUTABLE)) + return (-EACCES); + + if (!zpl_inode_owner_or_capable(ip)) + return (-EACCES); + + xva_init(&xva); + xoap = xva_getxoptattr(&xva); + + XVA_SET_REQ(&xva, XAT_IMMUTABLE); + if (ioctl_flags & FS_IMMUTABLE_FL) + xoap->xoa_immutable = B_TRUE; + + XVA_SET_REQ(&xva, XAT_APPENDONLY); + if (ioctl_flags & FS_APPEND_FL) + xoap->xoa_appendonly = B_TRUE; + + XVA_SET_REQ(&xva, XAT_NODUMP); + if (ioctl_flags & FS_NODUMP_FL) + xoap->xoa_nodump = B_TRUE; + + crhold(cr); + error = -zfs_setattr(ip, (vattr_t *)&xva, 0, cr); + crfree(cr); + + return (error); +} + static long zpl_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) { switch (cmd) { - case ZFS_IOC_GETFLAGS: - case ZFS_IOC_SETFLAGS: - return (-EOPNOTSUPP); + case FS_IOC_GETFLAGS: + return (zpl_ioctl_getflags(filp, (void *)arg)); + case FS_IOC_SETFLAGS: + return (zpl_ioctl_setflags(filp, (void *)arg)); default: return (-ENOTTY); } -- 2.45.0