diff --git a/include/linux/vfs_compat.h b/include/linux/vfs_compat.h index 4358cd288..d7d79bd26 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 2ab896fec..8cd0d8379 100644 --- a/module/zfs/zfs_znode.c +++ b/module/zfs/zfs_znode.c @@ -446,6 +446,25 @@ error: 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 3737bb519..2a7bcb9b0 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); }