mirror of
https://github.com/torvalds/linux.git
synced 2026-11-07 06:17:02 +01:00
ntfs: rewrite EA stream before updating metadata
Updating an EA removes the old record and appends its replacement.
Build the complete $EA stream in memory and rewrite it from offset zero,
rather than committing a compacted stream followed by a separate append.
generic/642 shows that the append path can leave an invalid record layout
on disk, including when a new EA entry is added.
When removing an EA, write the compacted stream before updating
$EA_INFORMATION and restore the original pair if the metadata update
fails.
When the final EA entry is removed the $EA/$EA_INFORMATION pair is torn
down. If removing $EA_INFORMATION fails after $EA has already been
removed, the original $EA is restored so the two attributes stay
consistent.
Fixes: fc053f05ca ("ntfs: add reparse and ea operations")
Reviewed-by: Hyunchul Lee <hyc.lee@gmail.com>
Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
This commit is contained in:
parent
772e4c8f25
commit
41e1cf4ec1
65
fs/ntfs/ea.c
65
fs/ntfs/ea.c
|
|
@ -196,6 +196,9 @@ static int ntfs_set_ea(struct inode *inode, const char *name, size_t name_len,
|
|||
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;
|
||||
|
||||
|
|
@ -249,6 +252,14 @@ static int ntfs_set_ea(struct inode *inode, const char *name, size_t name_len,
|
|||
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 &&
|
||||
|
|
@ -281,20 +292,33 @@ static int ntfs_set_ea(struct inode *inode, const char *name, size_t name_len,
|
|||
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;
|
||||
}
|
||||
|
||||
err = ntfs_write_ea(ni, AT_EA_INFORMATION, (char *)p_ea_info, 0,
|
||||
sizeof(struct ea_information), false);
|
||||
if (err)
|
||||
goto out;
|
||||
|
||||
err = ntfs_write_ea(ni, AT_EA, ea_buf, 0, ea_info_qsize, true);
|
||||
if (err)
|
||||
goto out;
|
||||
|
||||
if ((flags & XATTR_REPLACE) && !val_size) {
|
||||
/* Remove xattr. */
|
||||
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;
|
||||
}
|
||||
} else {
|
||||
|
|
@ -309,21 +333,23 @@ static int ntfs_set_ea(struct inode *inode, const char *name, size_t name_len,
|
|||
goto out;
|
||||
}
|
||||
}
|
||||
kvfree(ea_buf);
|
||||
|
||||
alloc_new_ea:
|
||||
ea_buf = kzalloc(new_ea_size, GFP_NOFS);
|
||||
if (!ea_buf) {
|
||||
new_ea_buf = kvzalloc(ea_info_qsize + new_ea_size, GFP_NOFS);
|
||||
if (!new_ea_buf) {
|
||||
err = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
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.
|
||||
*/
|
||||
p_ea = (struct ea_attr *)ea_buf;
|
||||
memcpy(p_ea->ea_name, name, name_len);
|
||||
p_ea->ea_name_length = name_len;
|
||||
p_ea->ea_name[name_len] = 0;
|
||||
|
|
@ -344,13 +370,13 @@ static int ntfs_set_ea(struct inode *inode, const char *name, size_t name_len,
|
|||
* 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, (char *)p_ea,
|
||||
new_ea_size);
|
||||
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, (char *)p_ea, ea_info_qsize,
|
||||
new_ea_size, false);
|
||||
err = ntfs_write_ea(ni, AT_EA, ea_buf, 0,
|
||||
ea_info_qsize + new_ea_size, true);
|
||||
if (err)
|
||||
goto out;
|
||||
}
|
||||
|
|
@ -370,6 +396,7 @@ static int ntfs_set_ea(struct inode *inode, const char *name, size_t name_len,
|
|||
NInoClearHasEA(ni);
|
||||
|
||||
kvfree(ea_buf);
|
||||
kvfree(old_ea_buf);
|
||||
kvfree(p_ea_info);
|
||||
|
||||
return err;
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user