ntfs: parse REPARSE_TAG_WOF

Introduce parsing support for REPARSE_TAG_WOF reparse points. Rename
ntfs_make_symlink() to ntfs_parse_reparse() since it now handles both
symlinks and WOF reparse tags. Introduce NI_WofCompressed flag to
indicate files compressed via Windows System Compression (WOF), and
configure compressed block size accordingly (12 to 15 bits based on the
format).

Signed-off-by: Hyunchul Lee <hyc.lee@gmail.com>
Signed-off-by: Namjae Jeon <linkinjeon@kernel.org>
This commit is contained in:
Hyunchul Lee 2026-08-21 14:00:54 +09:00 committed by Namjae Jeon
parent 56cc42a7b3
commit 760c9d271d
4 changed files with 142 additions and 62 deletions

View File

@ -869,7 +869,9 @@ static int ntfs_read_locked_inode(struct inode *vi)
if (ni->flags & FILE_ATTR_REPARSE_POINT) {
unsigned int mode;
mode = ntfs_make_symlink(ni);
err = ntfs_parse_reparse(ni, &mode);
if (err)
goto unm_err_out;
if (mode)
vi->i_mode |= mode;
else {
@ -1214,7 +1216,8 @@ static int ntfs_read_locked_inode(struct inode *vi)
* sizes of all non-resident attributes present to give us the Linux
* correct size that should go into i_blocks (after division by 512).
*/
if (S_ISREG(vi->i_mode) && (NInoCompressed(ni) || NInoSparse(ni)))
if (S_ISREG(vi->i_mode) &&
(NInoCompressed(ni) || (NInoSparse(ni) && !NInoWofCompressed(ni))))
vi->i_blocks = ni->itype.compressed.size >> 9;
else
vi->i_blocks = ni->allocated_size >> 9;

View File

@ -189,6 +189,7 @@ enum {
NI_NonResident,
NI_IndexAllocPresent,
NI_Compressed,
NI_WofCompressed,
NI_Encrypted,
NI_Sparse,
NI_SparseDisabled,
@ -248,6 +249,7 @@ NINO_FNS(MstProtected)
NINO_FNS(NonResident)
NINO_FNS(IndexAllocPresent)
NINO_FNS(Compressed)
NINO_FNS(WofCompressed)
NINO_FNS(Encrypted)
NINO_FNS(Sparse)
NINO_FNS(SparseDisabled)

View File

@ -24,6 +24,25 @@ struct wsl_link_reparse_data {
char link[];
};
struct wof_reparse_data {
__le32 version;
__le32 provider;
__le32 provider_version;
__le32 compression_format;
} __packed;
#define WOF_CURRENT_VERSION cpu_to_le32(1)
#define WOF_PROVIDER_WIM cpu_to_le32(1)
#define WOF_PROVIDER_FILE cpu_to_le32(2)
#define WOF_PROVIDER_CURRENT_VERSION cpu_to_le32(1)
#define WOF_COMPRESSION_XPRESS4K cpu_to_le32(0)
#define WOF_COMPRESSION_LZX cpu_to_le32(1)
#define WOF_COMPRESSION_XPRESS8K cpu_to_le32(2)
#define WOF_COMPRESSION_XPRESS16K cpu_to_le32(3)
static bool reparse_name_is_valid(size_t size, size_t name_off, u16 len)
{
if ((name_off | len) & 1)
@ -203,6 +222,28 @@ static bool valid_reparse_data(struct ntfs_inode *ni,
!(ni->flags & FILE_ATTRIBUTE_RECALL_ON_OPEN))
return false;
break;
case IO_REPARSE_TAG_WOF: {
const struct wof_reparse_data *wof_data =
(const struct wof_reparse_data *)reparse_attr->reparse_data;
if (!valid_reparse_buffer(ni, reparse_attr, size,
sizeof(struct wof_reparse_data)))
return false;
if (le16_to_cpu(reparse_attr->reparse_data_length) <
sizeof(struct wof_reparse_data) ||
wof_data->version != WOF_CURRENT_VERSION ||
wof_data->provider != WOF_PROVIDER_FILE ||
wof_data->provider_version !=
WOF_PROVIDER_CURRENT_VERSION)
return false;
if (wof_data->compression_format != WOF_COMPRESSION_XPRESS4K &&
wof_data->compression_format != WOF_COMPRESSION_XPRESS8K &&
wof_data->compression_format != WOF_COMPRESSION_XPRESS16K &&
wof_data->compression_format != WOF_COMPRESSION_LZX)
return false;
break;
}
default:
if (!valid_reparse_buffer(ni, reparse_attr, size, 0))
return false;
@ -239,89 +280,123 @@ static unsigned int ntfs_reparse_tag_mode(__le32 reparse_tag)
}
/*
* Get the target for symbolic link
* Parse reparse point data and initialize its in-memory representation.
*/
unsigned int ntfs_make_symlink(struct ntfs_inode *ni)
int ntfs_parse_reparse(struct ntfs_inode *ni, unsigned int *mode)
{
s64 attr_size = 0;
int err;
int err = -EINVAL;
unsigned int lth;
struct reparse_point *reparse_attr;
unsigned int mode = 0;
kvfree(ni->target);
ni->target = NULL;
ni->reparse_tag = 0;
ni->reparse_flags = 0;
*mode = 0;
reparse_attr = ntfs_attr_readall(ni, AT_REPARSE_POINT, NULL, 0,
&attr_size);
if (IS_ERR(reparse_attr))
reparse_attr = NULL;
if (reparse_attr &&
valid_reparse_data(ni, reparse_attr, attr_size)) {
err = -EINVAL;
if (IS_ERR(reparse_attr)) {
err = PTR_ERR(reparse_attr);
ntfs_error(ni->vol->sb,
"Failed to read reparse point: %d.", err);
return err;
}
if (!valid_reparse_data(ni, reparse_attr, attr_size)) {
ntfs_error(ni->vol->sb, "Invalid reparse point.");
err = -EFSCORRUPTED;
goto out;
}
switch (reparse_attr->reparse_tag) {
case IO_REPARSE_TAG_MOUNT_POINT:
{
struct mount_point_reparse_data *data =
(struct mount_point_reparse_data *)reparse_attr->reparse_data;
const __le16 *name = (const __le16 *)((u8 *)data->path_buffer +
le16_to_cpu(data->substitute_name_offset));
switch (reparse_attr->reparse_tag) {
case IO_REPARSE_TAG_MOUNT_POINT:
{
struct mount_point_reparse_data *data =
(struct mount_point_reparse_data *)reparse_attr->reparse_data;
const __le16 *name = (const __le16 *)((u8 *)data->path_buffer +
le16_to_cpu(data->substitute_name_offset));
err = ntfs_reparse_target_to_nls(ni->vol,
name,
le16_to_cpu(data->substitute_name_length),
&ni->target);
break;
}
case IO_REPARSE_TAG_SYMLINK:
{
struct symlink_reparse_data *data =
(struct symlink_reparse_data *)reparse_attr->reparse_data;
const __le16 *name = (const __le16 *)((u8 *)data->path_buffer +
le16_to_cpu(data->substitute_name_offset));
err = ntfs_reparse_target_to_nls(ni->vol,
name,
le16_to_cpu(data->substitute_name_length),
&ni->target);
break;
}
case IO_REPARSE_TAG_SYMLINK:
{
struct symlink_reparse_data *data =
(struct symlink_reparse_data *)reparse_attr->reparse_data;
const __le16 *name = (const __le16 *)((u8 *)data->path_buffer +
le16_to_cpu(data->substitute_name_offset));
err = ntfs_reparse_target_to_nls(ni->vol,
name,
le16_to_cpu(data->substitute_name_length),
&ni->target);
if (!err)
ni->reparse_flags = data->flags;
break;
}
case IO_REPARSE_TAG_LX_SYMLINK:
{
struct wsl_link_reparse_data *wsl_link_data =
(struct wsl_link_reparse_data *)reparse_attr->reparse_data;
err = ntfs_reparse_target_to_nls(ni->vol,
name,
le16_to_cpu(data->substitute_name_length),
&ni->target);
if (!err)
ni->reparse_flags = data->flags;
break;
}
case IO_REPARSE_TAG_LX_SYMLINK:
{
struct wsl_link_reparse_data *wsl_link_data =
(struct wsl_link_reparse_data *)reparse_attr->reparse_data;
if (wsl_link_data->type == cpu_to_le32(2)) {
lth = le16_to_cpu(reparse_attr->reparse_data_length) -
sizeof(wsl_link_data->type);
ni->target = kvzalloc(lth + 1, GFP_NOFS);
if (ni->target) {
memcpy(ni->target, wsl_link_data->link, lth);
ni->target[lth] = 0;
err = 0;
}
if (wsl_link_data->type == cpu_to_le32(2)) {
lth = le16_to_cpu(reparse_attr->reparse_data_length) -
sizeof(wsl_link_data->type);
ni->target = kvzalloc(lth + 1, GFP_NOFS);
if (ni->target) {
memcpy(ni->target, wsl_link_data->link, lth);
ni->target[lth] = 0;
err = 0;
}
}
break;
}
case IO_REPARSE_TAG_WOF:
{
#ifdef CONFIG_NTFS_FS_WOF_COMPRESSION
const struct wof_reparse_data *wof_data =
(const struct wof_reparse_data *)reparse_attr->reparse_data;
switch (wof_data->compression_format) {
case WOF_COMPRESSION_XPRESS4K:
ni->itype.compressed.block_size_bits = 12;
break;
case WOF_COMPRESSION_XPRESS8K:
ni->itype.compressed.block_size_bits = 13;
break;
case WOF_COMPRESSION_XPRESS16K:
ni->itype.compressed.block_size_bits = 14;
break;
case WOF_COMPRESSION_LZX:
ni->itype.compressed.block_size_bits = 15;
break;
}
default:
err = 0;
}
ni->itype.compressed.block_size =
1 << ni->itype.compressed.block_size_bits;
#endif
NInoSetWofCompressed(ni);
VFS_I(ni)->i_mode &= ~0222;
err = 0;
break;
}
default:
err = 0;
}
if (!err) {
mode = ntfs_reparse_tag_mode(reparse_attr->reparse_tag);
ni->reparse_tag = reparse_attr->reparse_tag;
}
} else
ni->flags &= ~FILE_ATTR_REPARSE_POINT;
if (!err) {
*mode = ntfs_reparse_tag_mode(
reparse_attr->reparse_tag);
ni->reparse_tag = reparse_attr->reparse_tag;
}
out:
kvfree(reparse_attr);
return mode;
return err;
}
unsigned int ntfs_reparse_tag_dt_types(struct ntfs_volume *vol, unsigned long mref)

View File

@ -9,7 +9,7 @@
extern __le16 reparse_index_name[];
unsigned int ntfs_make_symlink(struct ntfs_inode *ni);
int ntfs_parse_reparse(struct ntfs_inode *ni, unsigned int *mode);
unsigned int ntfs_reparse_tag_dt_types(struct ntfs_volume *vol, unsigned long mref);
int ntfs_translate_symlink_path(struct dentry *dentry, const char *target,
char **translated);