summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--fs/ntfs/ea.c67
1 files changed, 63 insertions, 4 deletions
diff --git a/fs/ntfs/ea.c b/fs/ntfs/ea.c
index a1f948c4d2ec..9f62056c1d6f 100644
--- a/fs/ntfs/ea.c
+++ b/fs/ntfs/ea.c
@@ -1053,8 +1053,9 @@ static noinline int ntfs_set_acl_ex(struct mnt_idmap *idmap,
{
const char *name;
size_t size, name_len;
- void *value;
- int err;
+ void *value, *old_value = NULL;
+ int err, old_len = -ENODATA;
+ bool acl_committed = false;
int flags;
umode_t mode;
@@ -1098,7 +1099,34 @@ static noinline int ntfs_set_acl_ex(struct mnt_idmap *idmap,
}
mutex_lock(&NTFS_I(inode)->mrec_lock);
+ if (!init_acl) {
+ /* Save the old xattr for rollback. */
+ old_len = ntfs_get_ea(inode, name, name_len, NULL, 0);
+ if (old_len >= 0) {
+ /* kmalloc(0) returns the non-NULL ZERO_SIZE_PTR. */
+ old_value = kmalloc(old_len, GFP_NOFS);
+ if (!old_value) {
+ err = -ENOMEM;
+ goto out_unlock;
+ }
+ err = ntfs_get_ea(inode, name, name_len,
+ old_value, old_len);
+ if (err != old_len) {
+ kfree(old_value);
+ old_value = NULL;
+ if (err >= 0)
+ err = -EIO;
+ goto out_unlock;
+ }
+ } else if (old_len != -ENODATA) {
+ err = old_len;
+ goto out_unlock;
+ }
+ }
err = ntfs_set_ea(inode, name, name_len, value, size, flags, NULL);
+ if (!err)
+ acl_committed = true;
+out_unlock:
mutex_unlock(&NTFS_I(inode)->mrec_lock);
if (err == -ENODATA && !size)
err = 0; /* Removing non existed xattr. */
@@ -1110,8 +1138,38 @@ static noinline int ntfs_set_acl_ex(struct mnt_idmap *idmap,
mutex_lock(&NTFS_I(inode)->mrec_lock);
err = ntfs_ea_set_wsl_inode(inode, 0, &ea_size, NTFS_EA_MODE);
if (err) {
- ntfs_set_ea(inode, name, name_len, NULL, 0,
- XATTR_REPLACE, NULL);
+ if (acl_committed) {
+ int undo_err;
+
+ if (old_len >= 0) {
+ /*
+ * A removed or zero-length xattr must
+ * be recreated without XATTR_REPLACE.
+ */
+ int undo_flags = acl && old_len ?
+ XATTR_REPLACE : 0;
+
+ undo_err = ntfs_set_ea(inode, name,
+ name_len, old_value,
+ old_len, undo_flags,
+ NULL);
+ } else {
+ undo_err = ntfs_set_ea(inode, name,
+ name_len, NULL, 0,
+ XATTR_REPLACE, NULL);
+ }
+ if (undo_err) {
+ /*
+ * Record a failed rollback so the next
+ * persistence point marks the volume
+ * dirty. Run chkdsk.
+ */
+ NVolSetErrors(NTFS_I(inode)->vol);
+ ntfs_error(inode->i_sb,
+ "Failed to undo the ACL xattr: %d",
+ undo_err);
+ }
+ }
mutex_unlock(&NTFS_I(inode)->mrec_lock);
inode->i_mode = old_mode;
goto out;
@@ -1124,6 +1182,7 @@ static noinline int ntfs_set_acl_ex(struct mnt_idmap *idmap,
}
out:
+ kfree(old_value);
kfree(value);
return err;