/*
* Check if @flags are a supported and valid set of FS_*_FL flags and that
- * the old and new flags are not conflicting
+ * the flags are not conflicting
*/
-static int check_fsflags(unsigned int old_flags, unsigned int flags)
+static int check_fsflags(unsigned int flags)
{
if (flags & ~(FS_IMMUTABLE_FL | FS_APPEND_FL | \
FS_NOATIME_FL | FS_NODUMP_FL | \
FS_NOCOW_FL))
return -EOPNOTSUPP;
- /* COMPR and NOCOMP on new/old are valid */
+ /* Only one of COMPR and NOCOMP is valid at a time */
if ((flags & FS_NOCOMP_FL) && (flags & FS_COMPR_FL))
return -EINVAL;
- if ((flags & FS_COMPR_FL) && (flags & FS_NOCOW_FL))
- return -EINVAL;
-
- /* NOCOW and compression options are mutually exclusive */
- if ((old_flags & FS_NOCOW_FL) && (flags & (FS_COMPR_FL | FS_NOCOMP_FL)))
- return -EINVAL;
- if ((flags & FS_NOCOW_FL) && (old_flags & (FS_COMPR_FL | FS_NOCOMP_FL)))
- return -EINVAL;
-
return 0;
}
unsigned int fsflags, old_fsflags;
int ret;
const char *comp = NULL;
+ u32 old_inode_flags;
u32 inode_flags;
bool prop_set = false;
return -EOPNOTSUPP;
fsflags = btrfs_mask_fsflags_for_type(&inode->vfs_inode, fa->flags);
- old_fsflags = btrfs_inode_flags_to_fsflags(inode);
- ret = check_fsflags(old_fsflags, fsflags);
+ ret = check_fsflags(fsflags);
if (ret)
return ret;
} else {
inode_flags |= BTRFS_INODE_NODATACOW;
}
+ /*
+ * NODATACOW files will ignore COMPRESS, so forbid
+ * COMPRESS | NODATACOW.
+ *
+ * We do this check here, after we've decided whether
+ * to silently drop the NODATACOW bit for non-empty files.
+ *
+ * NODATACOW files will inherit flags from NODATACOW
+ * directories, so COMPRESS is forbidden for them too.
+ *
+ * Old kernels allowed the forbidden combination of flags
+ * to be set, and they may still be found on existing
+ * filesystems. If the forbidden combination of flags
+ * are already set, we will allow an application to set
+ * or clear unrelated flags (like IMMUTABLE or NOATIME)
+ * as long as the existing COMPRESS | NODATACOW flags
+ * are not changed.
+ */
+ if ((inode_flags & BTRFS_INODE_NODATACOW) &&
+ (fsflags & (FS_COMPR_FL | FS_NOCOMP_FL))) {
+ old_fsflags = btrfs_inode_flags_to_fsflags(inode);
+ if ((old_fsflags & (FS_NOCOW_FL | FS_COMPR_FL | FS_NOCOMP_FL)) !=
+ (fsflags & (FS_NOCOW_FL | FS_COMPR_FL | FS_NOCOMP_FL)))
+ return -EINVAL;
+ }
} else {
/* We can only change NODATACOW for zero-sized regular file. */
if (S_ISREG(inode->vfs_inode.i_mode) && (inode->vfs_inode.i_size == 0)) {
if (IS_ERR(trans))
return PTR_ERR(trans);
+ /*
+ * Commit the new flags to the inode before setting the compression
+ * property: property validation rejects compression on nodatacow
+ * inodes based on the inode's current flags, which would incorrectly
+ * reject a single ioctl that both clears NOCOW and sets COMPR.
+ * Restore the original flags if the property cannot be set.
+ */
+ old_inode_flags = inode->flags;
+ inode->flags = inode_flags;
+
if (comp) {
ret = btrfs_set_prop(trans, inode, "btrfs.compression",
comp, strlen(comp), 0);
- if (ret)
+ if (ret) {
+ inode->flags = old_inode_flags;
goto out_end_trans;
+ }
prop_set = true;
} else {
ret = btrfs_set_prop(trans, inode, "btrfs.compression", NULL, 0, 0);
prop_set = (ret == 0);
/* If ret == -ENODATA ignore and proceed to update inode item. */
- if (ret && ret != -ENODATA)
+ if (ret && ret != -ENODATA) {
+ inode->flags = old_inode_flags;
goto out_end_trans;
+ }
}
update_flags: