summaryrefslogtreecommitdiff
path: root/fs/ntfs/ea.c
diff options
context:
space:
mode:
Diffstat (limited to 'fs/ntfs/ea.c')
-rw-r--r--fs/ntfs/ea.c567
1 files changed, 327 insertions, 240 deletions
diff --git a/fs/ntfs/ea.c b/fs/ntfs/ea.c
index ddc201e3e2aa..fa5c5c625751 100644
--- a/fs/ntfs/ea.c
+++ b/fs/ntfs/ea.c
@@ -32,12 +32,12 @@ static int ntfs_write_ea(struct ntfs_inode *ni, __le32 type, char *value, s64 ea
written = ntfs_inode_attr_pwrite(ea_vi, ea_off, ea_size, value, false);
if (written != ea_size)
- err = -EIO;
+ err = written < 0 ? written : -EIO;
else {
struct ntfs_inode *ea_ni = NTFS_I(ea_vi);
if (need_truncate && ea_ni->data_size > ea_off + ea_size)
- ntfs_attr_truncate(ea_ni, ea_off + ea_size);
+ err = ntfs_attr_truncate(ea_ni, ea_off + ea_size);
mark_mft_record_dirty(ni);
}
@@ -53,10 +53,13 @@ static int ntfs_ea_lookup(char *ea_buf, s64 ea_buf_size, const char *name,
loff_t offset, p_ea_size;
unsigned int next;
+ if (!ea_buf_size)
+ return -ENOENT;
+
offset = 0;
do {
if (ea_buf_size - offset < sizeof(struct ea_attr))
- break;
+ return -EUCLEAN;
p_ea = (const struct ea_attr *)&ea_buf[offset];
next = le32_to_cpu(p_ea->next_entry_offset);
@@ -64,31 +67,31 @@ static int ntfs_ea_lookup(char *ea_buf, s64 ea_buf_size, const char *name,
if (p_ea_size < sizeof(struct ea_attr) ||
offset + p_ea_size > ea_buf_size)
- break;
+ return -EUCLEAN;
if ((s64)p_ea->ea_name_length + 1 >
p_ea_size - offsetof(struct ea_attr, ea_name))
- break;
+ return -EUCLEAN;
actual_size = ALIGN(struct_size(p_ea, ea_name, 1 + p_ea->ea_name_length +
le16_to_cpu(p_ea->ea_value_length)), 4);
- if (actual_size > p_ea_size)
- break;
+ if (actual_size > p_ea_size ||
+ p_ea->ea_name[p_ea->ea_name_length])
+ return -EUCLEAN;
- if (p_ea->ea_name_length == name_len &&
+ if (name && p_ea->ea_name_length == name_len &&
!memcmp(p_ea->ea_name, name, name_len)) {
*ea_offset = offset;
- *ea_size = next ? next : actual_size;
+ *ea_size = p_ea_size;
if (ea_buf_size < *ea_offset + *ea_size)
- goto out;
+ return -EUCLEAN;
return 0;
}
offset += next;
} while (next > 0 && offset < ea_buf_size);
-out:
return -ENOENT;
}
@@ -126,7 +129,7 @@ static int ntfs_get_ea(struct inode *inode, const char *name, size_t name_len,
return PTR_ERR(p_ea_info);
if (ea_info_size != sizeof(struct ea_information)) {
kvfree(p_ea_info);
- return -EIO;
+ return -EUCLEAN;
}
ea_info_qlen = le32_to_cpu(p_ea_info->ea_query_length);
@@ -137,7 +140,7 @@ static int ntfs_get_ea(struct inode *inode, const char *name, size_t name_len,
return PTR_ERR(ea_buf);
if (ea_info_qlen > all_ea_size) {
- err = -EIO;
+ err = -EUCLEAN;
goto free_ea_buf;
}
@@ -162,7 +165,8 @@ static int ntfs_get_ea(struct inode *inode, const char *name, size_t name_len,
return ea_value_len;
}
- err = -ENODATA;
+ if (err == -ENOENT)
+ err = -ENODATA;
free_ea_buf:
kvfree(ea_buf);
return err;
@@ -177,247 +181,214 @@ static inline int ea_packed_size(const struct ea_attr *p_ea)
return 5 + p_ea->ea_name_length + le16_to_cpu(p_ea->ea_value_length);
}
+/* Unlike ntfs_attr_exist(), preserve lookup errors. */
+static int ntfs_ea_attr_lookup(struct ntfs_inode *ni, __le32 type)
+{
+ struct ntfs_attr_search_ctx *ctx;
+ struct mft_record *mrec;
+ int err;
+
+ mrec = map_mft_record(ni);
+ if (IS_ERR(mrec))
+ return PTR_ERR(mrec);
+ ctx = ntfs_attr_get_search_ctx(ni, mrec);
+ if (!ctx) {
+ err = -ENOMEM;
+ goto out;
+ }
+ err = ntfs_attr_lookup(type, AT_UNNAMED, 0, CASE_SENSITIVE,
+ 0, NULL, 0, ctx);
+ ntfs_attr_put_search_ctx(ctx);
+out:
+ unmap_mft_record(ni);
+ return err;
+}
+
+/* A NULL value means that the attribute should not exist. */
+static int ntfs_ea_update_attr(struct ntfs_inode *ni, __le32 type,
+ char *value, s64 size)
+{
+ int err = ntfs_ea_attr_lookup(ni, type);
+
+ if (err && err != -ENOENT)
+ return err;
+ if (!value)
+ return err == -ENOENT ? 0 :
+ ntfs_attr_remove(ni, type, AT_UNNAMED, 0, NULL);
+ if (err == -ENOENT)
+ return ntfs_attr_add(ni, type, AT_UNNAMED, 0, value, size);
+ return ntfs_write_ea(ni, type, value, 0, size, true);
+}
+
/*
- * Set a new EA, and set EA_INFORMATION accordingly
- *
- * This is roughly the same as ZwSetEaFile() on Windows, however
- * the "offset to next" of the last EA should not be cleared.
- *
- * Consistency of the new EA is first checked.
- *
- * EA_INFORMATION is set first, and it is restored to its former
- * state if setting EA fails.
+ * Prepare the new EA list before changing either NTFS attribute. Keep the
+ * original values until both updates succeed, so a failed update can be
+ * rolled back. This does not provide atomicity across a crash or power loss.
*/
static int ntfs_set_ea(struct inode *inode, const char *name, size_t name_len,
const void *value, size_t val_size, int flags,
__le16 *packed_ea_size)
{
struct ntfs_inode *ni = NTFS_I(inode);
- struct ea_information *p_ea_info = NULL;
- int ea_packed, err = 0;
- struct ea_attr *p_ea;
- u32 ea_info_qsize = 0;
- char *ea_buf = NULL;
- char *new_ea_buf;
- char *old_ea_buf = NULL;
- struct ea_information old_ea_info;
- size_t new_ea_size = ALIGN(struct_size(p_ea, ea_name, 1 + name_len + val_size), 4);
- s64 ea_off, ea_info_size, all_ea_size, ea_size;
-
- if (name_len > 255)
+ struct ea_information *old_info = NULL, info = {};
+ char *old_ea = NULL, *ea = NULL;
+ struct ea_attr *entry;
+ s64 old_size = 0, info_size, offset = 0, entry_size = 0;
+ u32 size = 0, new_size, next;
+ size_t add_size;
+ int err, info_err, ea_err, packed = 0, needed = 0;
+ bool exists, remove = !value;
+
+ if (name_len > U8_MAX)
return -ENAMETOOLONG;
+ if (val_size > U16_MAX)
+ return -E2BIG;
- if (ntfs_attr_exist(ni, AT_EA_INFORMATION, AT_UNNAMED, 0)) {
- p_ea_info = ntfs_attr_readall(ni, AT_EA_INFORMATION, NULL, 0,
- &ea_info_size);
- if (IS_ERR(p_ea_info)) {
- err = PTR_ERR(p_ea_info);
- p_ea_info = NULL;
- goto out;
- }
- if (ea_info_size != sizeof(struct ea_information)) {
- err = -EIO;
+ err = ntfs_ea_attr_lookup(ni, AT_EA_INFORMATION);
+ if (err && err != -ENOENT)
+ return err;
+ exists = !err;
+ err = ntfs_ea_attr_lookup(ni, AT_EA);
+ if (err && err != -ENOENT)
+ return err;
+ /* Do not silently discard an orphaned attribute. */
+ if (exists != !err)
+ return -EUCLEAN;
+
+ if (exists) {
+ old_info = ntfs_attr_readall(ni, AT_EA_INFORMATION, NULL, 0,
+ &info_size);
+ if (IS_ERR(old_info))
+ return PTR_ERR(old_info);
+ if (info_size != sizeof(*old_info)) {
+ err = -EUCLEAN;
goto out;
}
-
- ea_buf = ntfs_attr_readall(ni, AT_EA, NULL, 0, &all_ea_size);
- if (IS_ERR(ea_buf)) {
- err = PTR_ERR(ea_buf);
- ea_buf = NULL;
+ old_ea = ntfs_attr_readall(ni, AT_EA, NULL, 0, &old_size);
+ if (IS_ERR(old_ea)) {
+ err = PTR_ERR(old_ea);
+ old_ea = NULL;
goto out;
}
- if (!ea_buf) {
- ea_info_qsize = 0;
- kvfree(p_ea_info);
- goto create_ea_info;
- }
-
- ea_info_qsize = le32_to_cpu(p_ea_info->ea_query_length);
- } else {
-create_ea_info:
- p_ea_info = kzalloc_obj(struct ea_information, GFP_NOFS);
- if (!p_ea_info)
- return -ENOMEM;
-
- ea_info_qsize = 0;
- err = ntfs_attr_add(ni, AT_EA_INFORMATION, AT_UNNAMED, 0,
- (char *)p_ea_info, sizeof(struct ea_information));
- if (err)
+ size = le32_to_cpu(old_info->ea_query_length);
+ if (size > old_size) {
+ err = -EUCLEAN;
goto out;
-
- if (ntfs_attr_exist(ni, AT_EA, AT_UNNAMED, 0)) {
- err = ntfs_attr_remove(ni, AT_EA, AT_UNNAMED, 0);
- if (err)
- goto out;
}
-
- goto alloc_new_ea;
}
- if (ea_info_qsize > all_ea_size) {
- err = -EIO;
+ /* Validate even the entries following the one being replaced. */
+ err = ntfs_ea_lookup(old_ea, size, NULL, 0, &offset, &entry_size);
+ if (err != -ENOENT)
goto out;
- }
-
- err = ntfs_ea_lookup(ea_buf, ea_info_qsize, name, name_len, &ea_off,
- &ea_size);
- if (ea_info_qsize && !err) {
+ err = ntfs_ea_lookup(old_ea, size, name, name_len, &offset, &entry_size);
+ if (err && err != -ENOENT)
+ goto out;
+ if (!err) {
if (flags & XATTR_CREATE) {
err = -EEXIST;
goto out;
}
- if ((flags & XATTR_REPLACE) && !val_size) {
- old_ea_info = *p_ea_info;
- old_ea_buf = kvmemdup(ea_buf, all_ea_size, GFP_NOFS);
- if (!old_ea_buf) {
- err = -ENOMEM;
- goto out;
- }
- }
-
- /* Check the final $EA size before removing the old entry. */
- if (val_size &&
- ntfs_attr_size_bounds_check(ni->vol, AT_EA,
- ea_info_qsize - ea_size + new_ea_size)) {
- err = -EFBIG;
- goto out;
- }
-
- p_ea = (struct ea_attr *)(ea_buf + ea_off);
-
- if (val_size &&
- le16_to_cpu(p_ea->ea_value_length) == val_size &&
- !memcmp(p_ea->ea_name + p_ea->ea_name_length + 1, value,
- val_size))
- goto out;
-
- le16_add_cpu(&p_ea_info->ea_length, 0 - ea_packed_size(p_ea));
-
- if (p_ea->flags & NEED_EA)
- le16_add_cpu(&p_ea_info->need_ea_count, -1);
-
- memmove((char *)p_ea, (char *)p_ea + ea_size, ea_info_qsize - (ea_off + ea_size));
- ea_info_qsize -= ea_size;
- p_ea_info->ea_query_length = cpu_to_le32(ea_info_qsize);
-
- if ((flags & XATTR_REPLACE) && !val_size && !ea_info_qsize) {
- err = ntfs_attr_remove(ni, AT_EA, AT_UNNAMED, 0);
- if (err)
- goto out;
-
- err = ntfs_attr_remove(ni, AT_EA_INFORMATION, AT_UNNAMED, 0);
- if (err) {
- /* Restore the original $EA if $EA_INFORMATION removal failed. */
- ntfs_attr_add(ni, AT_EA, AT_UNNAMED, 0, old_ea_buf,
- all_ea_size);
- ea_info_qsize = le32_to_cpu(old_ea_info.ea_query_length);
- }
- goto out;
- }
-
- if ((flags & XATTR_REPLACE) && !val_size) {
- err = ntfs_write_ea(ni, AT_EA, ea_buf, 0, ea_info_qsize,
- true);
- if (err) {
- ntfs_write_ea(ni, AT_EA, old_ea_buf, 0,
- all_ea_size, false);
- goto out;
- }
-
- err = ntfs_write_ea(ni, AT_EA_INFORMATION, (char *)p_ea_info,
- 0, sizeof(struct ea_information), false);
- if (err) {
- ntfs_write_ea(ni, AT_EA, old_ea_buf, 0,
- all_ea_size, false);
- ntfs_write_ea(ni, AT_EA_INFORMATION,
- (char *)&old_ea_info, 0,
- sizeof(old_ea_info), false);
- }
- goto out;
+ entry = (struct ea_attr *)(old_ea + offset);
+ if (!remove && le16_to_cpu(entry->ea_value_length) == val_size &&
+ !memcmp(entry->ea_name + name_len + 1, value, val_size)) {
+ info = *old_info;
+ goto done;
}
} else {
- if (flags & XATTR_REPLACE) {
+ if (remove || (flags & XATTR_REPLACE)) {
err = -ENODATA;
goto out;
}
+ offset = size;
+ entry_size = 0;
+ }
- if (ntfs_attr_size_bounds_check(ni->vol, AT_EA,
- ea_info_qsize + new_ea_size)) {
- err = -EFBIG;
+ add_size = remove ? 0 : ALIGN(sizeof(*entry) + name_len + 1 + val_size, 4);
+ new_size = size - entry_size + add_size;
+ if (new_size && ntfs_attr_size_bounds_check(ni->vol, AT_EA, new_size)) {
+ err = -EFBIG;
+ goto out;
+ }
+ if (new_size) {
+ ea = kvzalloc(new_size, GFP_NOFS);
+ if (!ea) {
+ err = -ENOMEM;
goto out;
}
+ if (offset)
+ memcpy(ea, old_ea, offset);
+ if (size > offset + entry_size)
+ memcpy(ea + offset, old_ea + offset + entry_size,
+ size - offset - entry_size);
}
-alloc_new_ea:
- new_ea_buf = kvzalloc(ea_info_qsize + new_ea_size, GFP_NOFS);
- if (!new_ea_buf) {
- err = -ENOMEM;
- goto out;
+ size -= entry_size;
+
+ /* Link a zero-terminated old tail before appending the new entry. */
+ for (offset = 0; offset < size; offset += next) {
+ entry = (struct ea_attr *)(ea + offset);
+ next = le32_to_cpu(entry->next_entry_offset);
+ if (!next) {
+ next = size - offset;
+ entry->next_entry_offset = cpu_to_le32(next);
+ }
+ packed += ea_packed_size(entry);
+ if (entry->flags & NEED_EA)
+ needed++;
}
- if (ea_info_qsize)
- memcpy(new_ea_buf, ea_buf, ea_info_qsize);
- kvfree(ea_buf);
- ea_buf = new_ea_buf;
- p_ea = (struct ea_attr *)(ea_buf + ea_info_qsize);
-
- /*
- * EA and REPARSE_POINT compatibility not checked any more,
- * required by Windows 10, but having both may lead to
- * problems with earlier versions.
- */
- memcpy(p_ea->ea_name, name, name_len);
- p_ea->ea_name_length = name_len;
- p_ea->ea_name[name_len] = 0;
- memcpy(p_ea->ea_name + name_len + 1, value, val_size);
- p_ea->ea_value_length = cpu_to_le16(val_size);
- p_ea->next_entry_offset = cpu_to_le32(new_ea_size);
-
- ea_packed = le16_to_cpu(p_ea_info->ea_length) + ea_packed_size(p_ea);
- p_ea_info->ea_length = cpu_to_le16(ea_packed);
- p_ea_info->ea_query_length = cpu_to_le32(ea_info_qsize + new_ea_size);
-
- if (ea_packed > 0xffff) {
+ if (!remove) {
+ entry = (struct ea_attr *)(ea + size);
+ entry->next_entry_offset = cpu_to_le32(add_size);
+ entry->ea_name_length = name_len;
+ entry->ea_value_length = cpu_to_le16(val_size);
+ memcpy(entry->ea_name, name, name_len);
+ memcpy(entry->ea_name + name_len + 1, value, val_size);
+ packed += ea_packed_size(entry);
+ }
+ if (packed > U16_MAX) {
err = -EFBIG;
goto out;
}
-
- /*
- * no EA or EA_INFORMATION : add them
- */
- if (!ntfs_attr_exist(ni, AT_EA, AT_UNNAMED, 0)) {
- err = ntfs_attr_add(ni, AT_EA, AT_UNNAMED, 0, ea_buf,
- ea_info_qsize + new_ea_size);
- if (err)
- goto out;
- } else {
- err = ntfs_write_ea(ni, AT_EA, ea_buf, 0,
- ea_info_qsize + new_ea_size, true);
- if (err)
- goto out;
- }
-
- err = ntfs_write_ea(ni, AT_EA_INFORMATION, (char *)p_ea_info, 0,
- sizeof(struct ea_information), false);
- if (err)
+ info.ea_length = cpu_to_le16(packed);
+ info.need_ea_count = cpu_to_le16(needed);
+ info.ea_query_length = cpu_to_le32(new_size);
+
+ /* All preparation is complete; errors from here require rollback. */
+ err = ntfs_ea_update_attr(ni, AT_EA, ea, new_size);
+ if (!err)
+ err = ntfs_ea_update_attr(ni, AT_EA_INFORMATION,
+ new_size ? (char *)&info : NULL,
+ sizeof(info));
+ if (err) {
+ /* Try both restorations even if the first one fails. */
+ ea_err = ntfs_ea_update_attr(ni, AT_EA, old_ea, old_size);
+ info_err = ntfs_ea_update_attr(ni, AT_EA_INFORMATION,
+ (char *)old_info, sizeof(info));
+ if (ea_err || info_err) {
+ NVolSetErrors(ni->vol);
+ NVolSetShutdown(ni->vol);
+ ntfs_error(inode->i_sb,
+ "Failed to restore EAs of inode 0x%llx (%d, %d); shutting down.",
+ ni->mft_no, ea_err, info_err);
+ err = -EIO;
+ }
goto out;
-
- if (packed_ea_size)
- *packed_ea_size = p_ea_info->ea_length;
+ }
+ size = new_size;
mark_mft_record_dirty(ni);
+done:
+ if (packed_ea_size)
+ *packed_ea_size = info.ea_length;
+ if (size)
+ NInoSetHasEA(ni);
+ else
+ NInoClearHasEA(ni);
out:
- if (!err) {
- if (ea_info_qsize > 0)
- NInoSetHasEA(ni);
- else
- NInoClearHasEA(ni);
- }
-
- kvfree(ea_buf);
- kvfree(old_ea_buf);
- kvfree(p_ea_info);
-
+ kvfree(ea);
+ kvfree(old_ea);
+ kvfree(old_info);
return err;
}
-
/*
* Check for the presence of an EA "$LXDEV" (used by WSL)
* and return its value as a device address
@@ -510,6 +481,55 @@ int ntfs_ea_set_wsl_inode(struct inode *inode, dev_t rdev, __le16 *ea_size,
return err;
}
+int ntfs_ea_get_linuxflags(struct inode *inode)
+{
+ struct ntfs_inode *ni = NTFS_I(inode);
+ __le32 value;
+ int err;
+
+ err = ntfs_get_ea(inode, "$LINUXFLAGS", sizeof("$LINUXFLAGS") - 1,
+ &value, sizeof(value));
+ if (err == -ENODATA)
+ return 0;
+ if (err < 0)
+ return err == -ERANGE ? -EUCLEAN : err;
+ if (err != sizeof(value))
+ return -EUCLEAN;
+
+ ni->linuxflags = le32_to_cpu(value);
+ if (ni->linuxflags & NTFS_LINUXFLAGS_IMMUTABLE)
+ inode->i_flags |= S_IMMUTABLE;
+ if (ni->linuxflags & NTFS_LINUXFLAGS_APPEND)
+ inode->i_flags |= S_APPEND;
+ if (ni->linuxflags & NTFS_LINUXFLAGS_NOATIME)
+ inode->i_flags |= S_NOATIME;
+ return 0;
+}
+
+int ntfs_ea_set_linuxflags(struct inode *inode, u32 linuxflags)
+{
+ struct ntfs_inode *ni = NTFS_I(inode);
+ __le32 value;
+ int err;
+
+ if (linuxflags) {
+ value = cpu_to_le32(linuxflags);
+ err = ntfs_set_ea(inode, "$LINUXFLAGS", sizeof("$LINUXFLAGS") - 1,
+ &value, sizeof(value), 0, NULL);
+ } else if (NInoHasEA(ni)) {
+ err = ntfs_set_ea(inode, "$LINUXFLAGS", sizeof("$LINUXFLAGS") - 1,
+ NULL, 0, XATTR_REPLACE, NULL);
+ if (err == -ENODATA)
+ err = 0;
+ } else {
+ err = 0;
+ }
+
+ if (!err)
+ ni->linuxflags = linuxflags;
+ return err;
+}
+
ssize_t ntfs_listxattr(struct dentry *dentry, char *buffer, size_t size)
{
struct inode *inode = d_inode(dentry);
@@ -536,7 +556,7 @@ ssize_t ntfs_listxattr(struct dentry *dentry, char *buffer, size_t size)
goto out;
}
if (ea_info_size != sizeof(struct ea_information)) {
- err = -EIO;
+ err = -EUCLEAN;
goto out;
}
@@ -549,32 +569,26 @@ ssize_t ntfs_listxattr(struct dentry *dentry, char *buffer, size_t size)
goto out;
}
- if (ea_info_qsize > ea_buf_size || ea_info_qsize == 0)
+ if (ea_info_qsize > ea_buf_size) {
+ err = -EUCLEAN;
+ goto out;
+ }
+
+ /* Validate the entire chain, including size-only queries. */
+ err = ntfs_ea_lookup(ea_buf, ea_info_qsize, NULL, 0, &offset,
+ &ea_size);
+ if (err != -ENOENT)
+ goto out;
+ err = 0;
+ if (!ea_info_qsize)
goto out;
offset = 0;
do {
- if (ea_info_qsize - offset < sizeof(struct ea_attr)) {
- err = -EIO;
- goto out;
- }
-
p_ea = (const struct ea_attr *)&ea_buf[offset];
next = le32_to_cpu(p_ea->next_entry_offset);
ea_size = next ? next : (ea_info_qsize - offset);
- if (ea_size < sizeof(struct ea_attr) ||
- offset + ea_size > ea_info_qsize) {
- err = -EIO;
- goto out;
- }
-
- if ((int)p_ea->ea_name_length + 1 >
- ea_size - offsetof(struct ea_attr, ea_name)) {
- err = -EIO;
- goto out;
- }
-
if (buffer) {
if (ret + p_ea->ea_name_length + 1 > size) {
err = -ERANGE;
@@ -610,6 +624,14 @@ static int ntfs_getxattr(const struct xattr_handler *handler,
struct ntfs_inode *ni = NTFS_I(inode);
int err;
+ /*
+ * Stream permissions and privileges belong to the base inode. This
+ * also lets VFS killpriv helpers find the base security attributes.
+ */
+ if (ntfs_inode_is_named_stream(ni)) {
+ ni = ni->ext.base_ntfs_ino;
+ inode = VFS_I(ni);
+ }
if (NVolShutdown(ni->vol))
return -EIO;
@@ -716,8 +738,8 @@ static int ntfs_new_attr_flags(struct ntfs_inode *ni, __le32 fattr)
goto err_out;
ntfs_attr_reinit_search_ctx(ctx);
- err = ntfs_attr_lookup(ni->type, ni->name,
- ni->name_len, CASE_SENSITIVE,
+ err = ntfs_attr_lookup(ni->type, ni->name, ni->name_len,
+ CASE_SENSITIVE,
0, NULL, 0, ctx);
if (err) {
err = -EINVAL;
@@ -883,9 +905,15 @@ static int ntfs_setxattr(const struct xattr_handler *handler,
int err;
__le32 fattr;
+ if (ntfs_inode_is_named_stream(ni))
+ return -EOPNOTSUPP;
if (NVolShutdown(ni->vol))
return -EIO;
+ /* Only fileattr_set may change the flags and their cached state. */
+ if (!strcmp(name, "$LINUXFLAGS"))
+ return -EPERM;
+
if (ntfs_is_reserved_lxattr(name) && !capable(CAP_SYS_ADMIN))
return -EPERM;
@@ -1027,8 +1055,9 @@ static noinline int ntfs_set_acl_ex(const 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;
@@ -1072,7 +1101,34 @@ static noinline int ntfs_set_acl_ex(const 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. */
@@ -1084,8 +1140,38 @@ static noinline int ntfs_set_acl_ex(const 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;
@@ -1098,6 +1184,7 @@ static noinline int ntfs_set_acl_ex(const struct mnt_idmap *idmap,
}
out:
+ kfree(old_value);
kfree(value);
return err;