// SPDX-License-Identifier: GPL-2.0-or-later/* * Processing of reparse points * * Part of this file is based on code from the NTFS-3G. * * Copyright (c) 2008-2021 Jean-Pierre Andre * Copyright (c) 2025 LG Electronics Co., Ltd. */ #include "ntfs.h"#include "layout.h"#include "attrib.h"#include "inode.h"#include "dir.h"#include "volume.h"#include "mft.h"#include "index.h"#include "lcnalloc.h"#include "reparse.h" struct wsl_link_reparse_data { __le32 type; 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) return false; return name_off + len <= size;} /* * Windows-native reparse payloads store pathnames as UTF-16 strings with '\\' * separators. Convert the on-disk UTF-16 target into the mount's NLS and * normalize path separators. */static int ntfs_reparse_target_to_nls(struct ntfs_volume *vol, const __le16 *uname, u16 ulen, char **target){ int err, i; *target = NULL; ulen >>= 1; if (!ulen) return -EINVAL; if (!uname[ulen - 1]) ulen--; err = ntfs_ucstonls(vol, uname, ulen, (unsigned char **)target, 0); if (err < 0) { ntfs_attr_name_free((unsigned char **)target); return err; } for (i = 0; i < err; i++) { if ((*target)[i] == '\\') (*target)[i] = '/'; } return 0;} /* Index entry in $Extend/$Reparse */struct reparse_index { struct index_entry_header header; struct reparse_index_key key; __le32 filling;}; __le16 reparse_index_name[] = {cpu_to_le16('$'), cpu_to_le16('R'), 0}; /* * Check if the reparse point attribute buffer is valid. * Returns true if valid, false otherwise. */static bool valid_reparse_buffer(struct ntfs_inode *ni, const struct reparse_point *reparse_attr, size_t size, size_t payload_min_len){ size_t expected; if (!ni || !reparse_attr) return false; /* Minimum size must cover reparse_point header */ if (size < sizeof(struct reparse_point)) return false; /* The payload must contain the fixed fields for the current tag. */ if (payload_min_len && le16_to_cpu(reparse_attr->reparse_data_length) < payload_min_len) return false; /* Reserved zero tag is invalid */ if (reparse_attr->reparse_tag == IO_REPARSE_TAG_RESERVED_ZERO) return false; /* Calculate expected total size */ expected = sizeof(struct reparse_point) + le16_to_cpu(reparse_attr->reparse_data_length); /* Add GUID size for non-Microsoft tags */ if (!(reparse_attr->reparse_tag & IO_REPARSE_TAG_IS_MICROSOFT)) expected += sizeof(struct guid); /* Buffer must exactly match the expected size */ return expected == size;} /* * Do some sanity checks on reparse data * * Microsoft reparse points have an 8-byte header whereas * non-Microsoft reparse points have a 24-byte header. In each case, * 'reparse_data_length' must equal the number of non-header bytes. * * If the reparse data looks like a junction point or symbolic * link, more checks can be done. */static bool valid_reparse_data(struct ntfs_inode *ni, const struct reparse_point *reparse_attr, size_t size){ if (size < sizeof(*reparse_attr)) return false; switch (reparse_attr->reparse_tag) { case IO_REPARSE_TAG_MOUNT_POINT: { struct mount_point_reparse_data *data; size_t data_offs; if (!valid_reparse_buffer(ni, reparse_attr, size, sizeof(*data))) return false; data = (struct mount_point_reparse_data *)reparse_attr->reparse_data; data_offs = offsetof(struct reparse_point, reparse_data) + offsetof(struct mount_point_reparse_data, path_buffer); if (!reparse_name_is_valid(size, data_offs + le16_to_cpu(data->substitute_name_offset), le16_to_cpu(data->substitute_name_length)) || !reparse_name_is_valid(size, data_offs + le16_to_cpu(data->print_name_offset), le16_to_cpu(data->print_name_length))) return false; break; } case IO_REPARSE_TAG_SYMLINK: { struct symlink_reparse_data *data; size_t data_offs; if (!valid_reparse_buffer(ni, reparse_attr, size, sizeof(*data))) return false; data = (struct symlink_reparse_data *)reparse_attr->reparse_data; data_offs = offsetof(struct reparse_point, reparse_data) + offsetof(struct symlink_reparse_data, path_buffer); if (!reparse_name_is_valid(size, data_offs + le16_to_cpu(data->substitute_name_offset), le16_to_cpu(data->substitute_name_length)) || !reparse_name_is_valid(size, data_offs + le16_to_cpu(data->print_name_offset), le16_to_cpu(data->print_name_length))) return false; break; } case IO_REPARSE_TAG_LX_SYMLINK: { struct wsl_link_reparse_data *data; if (!valid_reparse_buffer(ni, reparse_attr, size, sizeof(*data))) return false; data = (struct wsl_link_reparse_data *)reparse_attr->reparse_data; if (le16_to_cpu(reparse_attr->reparse_data_length) <= sizeof(data->type) || data->type != cpu_to_le32(2)) return false; break; } case IO_REPARSE_TAG_AF_UNIX: case IO_REPARSE_TAG_LX_FIFO: case IO_REPARSE_TAG_LX_CHR: case IO_REPARSE_TAG_LX_BLK: if (!valid_reparse_buffer(ni, reparse_attr, size, 0)) return false; if (le16_to_cpu(reparse_attr->reparse_data_length) || !(ni->flags & FILE_ATTRIBUTE_RECALL_ON_OPEN)) return false; break; case IO_REPARSE_TAG_WOF: { if (!valid_reparse_buffer(ni, reparse_attr, size, sizeof(struct wof_reparse_data))) return false; break; } default: if (!valid_reparse_buffer(ni, reparse_attr, size, 0)) return false; break; } return true;} static unsigned int ntfs_reparse_tag_mode(__le32 reparse_tag){ unsigned int mode = 0; switch (reparse_tag) { case IO_REPARSE_TAG_MOUNT_POINT: case IO_REPARSE_TAG_SYMLINK: case IO_REPARSE_TAG_LX_SYMLINK: mode = S_IFLNK; break; case IO_REPARSE_TAG_AF_UNIX: mode = S_IFSOCK; break; case IO_REPARSE_TAG_LX_FIFO: mode = S_IFIFO; break; case IO_REPARSE_TAG_LX_CHR: mode = S_IFCHR; break; case IO_REPARSE_TAG_LX_BLK: mode = S_IFBLK; } return mode;} /* * Parse reparse point data and initialize its in-memory representation. */int ntfs_parse_reparse(struct ntfs_inode *ni, unsigned int *mode){ s64 attr_size = 0; int err = -EINVAL; unsigned int lth; struct reparse_point *reparse_attr; 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)) { 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)); 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; 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; ni->itype.compressed.block_size_bits = 0; ni->itype.compressed.block_size = 0; if (wof_data->version == WOF_CURRENT_VERSION && wof_data->provider == WOF_PROVIDER_FILE && wof_data->provider_version == WOF_PROVIDER_CURRENT_VERSION) { 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; } } if (ni->itype.compressed.block_size_bits) 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; } out: kvfree(reparse_attr); return err;} unsigned int ntfs_reparse_tag_dt_types(struct ntfs_volume *vol, unsigned long mref){ s64 attr_size = 0; struct reparse_point *reparse_attr; unsigned int dt_type = DT_UNKNOWN; struct inode *vi; vi = ntfs_iget(vol->sb, mref); if (IS_ERR(vi)) return DT_UNKNOWN; reparse_attr = (struct reparse_point *)ntfs_attr_readall(NTFS_I(vi), AT_REPARSE_POINT, NULL, 0, &attr_size); if (IS_ERR(reparse_attr)) reparse_attr = NULL; if (reparse_attr && attr_size >= sizeof(*reparse_attr)) { switch (reparse_attr->reparse_tag) { case IO_REPARSE_TAG_MOUNT_POINT: case IO_REPARSE_TAG_SYMLINK: case IO_REPARSE_TAG_LX_SYMLINK: dt_type = DT_LNK; break; case IO_REPARSE_TAG_AF_UNIX: dt_type = DT_SOCK; break; case IO_REPARSE_TAG_LX_FIFO: dt_type = DT_FIFO; break; case IO_REPARSE_TAG_LX_CHR: dt_type = DT_CHR; break; case IO_REPARSE_TAG_LX_BLK: dt_type = DT_BLK; } } kvfree(reparse_attr); iput(vi); return dt_type;} static bool ntfs_is_drive_letter(const char *target){ return ((target[0] >= 'A' && target[0] <= 'Z') || (target[0] >= 'a' && target[0] <= 'z')) && target[1] == ':';} /* * ntfs_translate_symlink_path * * @dentry: dentry of the symlink/junction being resolved * @target: NUL-terminated NLS target string with '\\' already normalized to '/' * @translated: out parameter, set to a newly kmalloc'd relative path on success * * Windows junctions (IO_REPARSE_TAG_MOUNT_POINT) and non-relative symlinks * (IO_REPARSE_TAG_SYMLINK without SYMLINK_FLAG_RELATIVE) store substitute * names such as "/??/C:/foo", "//?/C:/foo", "/foo", or "C:/foo". Linux * cannot continue pathname lookup from those syntaxes, so rewrite them as a * path relative to the symlink's containing directory on this NTFS volume, * anchored at the volume root via "../". * * Note: bind-mounted subtrees of the volume may resolve to unexpected * locations because the computed "../" depth is relative to the NTFS volume * root, not the bind-mounted subtree root. * * Return: 0 on success with *translated set to a newly allocated string the * caller must kfree(); negative errno on failure. */int ntfs_translate_symlink_path(struct dentry *dentry, const char *target, char **translated){ char *buf, *link_path, *out, *p; const char *path, *tail; unsigned int up_levels = 0; size_t tail_len, out_len; int err; if (!dentry || !target || !translated) return -EINVAL; path = target; /* reject UNC path. */ if (path[0] == '/' && path[1] == '/' && !(path[2] == '?' && path[3] == '/')) return -EOPNOTSUPP; /* target starts with "/??/" or "//?/"? */ if ((path[0] == '/' && path[1] == '?' && path[2] == '?' && path[3] == '/') || (path[0] == '/' && path[1] == '/' && path[2] == '?' && path[3] == '/')) path += 4; /* target must start with a drive character or '/'. */ if (ntfs_is_drive_letter(path)) { if (path[2] && path[2] != '/') return -EOPNOTSUPP; tail = path + 2; if (*tail == '/') tail++; } else if (*path == '/') {Lines 1–500 of 1034. Load more
// SPDX-License-Identifier: GPL-2.0-or-later /* * Processing of reparse points * * Part of this file is based on code from the NTFS-3G. * * Copyright (c) 2008-2021 Jean-Pierre Andre * Copyright (c) 2025 LG Electronics Co., Ltd. */ #include "ntfs.h" #include "layout.h" #include "attrib.h" #include "inode.h" #include "dir.h" #include "volume.h" #include "mft.h" #include "index.h" #include "lcnalloc.h" #include "reparse.h" struct wsl_link_reparse_data { __le32 type; 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) return false; return name_off + len <= size; } /* * Windows-native reparse payloads store pathnames as UTF-16 strings with '\\' * separators. Convert the on-disk UTF-16 target into the mount's NLS and * normalize path separators. */ static int ntfs_reparse_target_to_nls(struct ntfs_volume *vol, const __le16 *uname, u16 ulen, char **target) { int err, i; *target = NULL; ulen >>= 1; if (!ulen) return -EINVAL; if (!uname[ulen - 1]) ulen--; err = ntfs_ucstonls(vol, uname, ulen, (unsigned char **)target, 0); if (err < 0) { ntfs_attr_name_free((unsigned char **)target); return err; } for (i = 0; i < err; i++) { if ((*target)[i] == '\\') (*target)[i] = '/'; } return 0; } /* Index entry in $Extend/$Reparse */ struct reparse_index { struct index_entry_header header; struct reparse_index_key key; __le32 filling; }; __le16 reparse_index_name[] = {cpu_to_le16('$'), cpu_to_le16('R'), 0}; /* * Check if the reparse point attribute buffer is valid. * Returns true if valid, false otherwise. */ static bool valid_reparse_buffer(struct ntfs_inode *ni, const struct reparse_point *reparse_attr, size_t size, size_t payload_min_len) { size_t expected; if (!ni || !reparse_attr) return false; /* Minimum size must cover reparse_point header */ if (size < sizeof(struct reparse_point)) return false; /* The payload must contain the fixed fields for the current tag. */ if (payload_min_len && le16_to_cpu(reparse_attr->reparse_data_length) < payload_min_len) return false; /* Reserved zero tag is invalid */ if (reparse_attr->reparse_tag == IO_REPARSE_TAG_RESERVED_ZERO) return false; /* Calculate expected total size */ expected = sizeof(struct reparse_point) + le16_to_cpu(reparse_attr->reparse_data_length); /* Add GUID size for non-Microsoft tags */ if (!(reparse_attr->reparse_tag & IO_REPARSE_TAG_IS_MICROSOFT)) expected += sizeof(struct guid); /* Buffer must exactly match the expected size */ return expected == size; } /* * Do some sanity checks on reparse data * * Microsoft reparse points have an 8-byte header whereas * non-Microsoft reparse points have a 24-byte header. In each case, * 'reparse_data_length' must equal the number of non-header bytes. * * If the reparse data looks like a junction point or symbolic * link, more checks can be done. */ static bool valid_reparse_data(struct ntfs_inode *ni, const struct reparse_point *reparse_attr, size_t size) { if (size < sizeof(*reparse_attr)) return false; switch (reparse_attr->reparse_tag) { case IO_REPARSE_TAG_MOUNT_POINT: { struct mount_point_reparse_data *data; size_t data_offs; if (!valid_reparse_buffer(ni, reparse_attr, size, sizeof(*data))) return false; data = (struct mount_point_reparse_data *)reparse_attr->reparse_data; data_offs = offsetof(struct reparse_point, reparse_data) + offsetof(struct mount_point_reparse_data, path_buffer); if (!reparse_name_is_valid(size, data_offs + le16_to_cpu(data->substitute_name_offset), le16_to_cpu(data->substitute_name_length)) || !reparse_name_is_valid(size, data_offs + le16_to_cpu(data->print_name_offset), le16_to_cpu(data->print_name_length))) return false; break; } case IO_REPARSE_TAG_SYMLINK: { struct symlink_reparse_data *data; size_t data_offs; if (!valid_reparse_buffer(ni, reparse_attr, size, sizeof(*data))) return false; data = (struct symlink_reparse_data *)reparse_attr->reparse_data; data_offs = offsetof(struct reparse_point, reparse_data) + offsetof(struct symlink_reparse_data, path_buffer); if (!reparse_name_is_valid(size, data_offs + le16_to_cpu(data->substitute_name_offset), le16_to_cpu(data->substitute_name_length)) || !reparse_name_is_valid(size, data_offs + le16_to_cpu(data->print_name_offset), le16_to_cpu(data->print_name_length))) return false; break; } case IO_REPARSE_TAG_LX_SYMLINK: { struct wsl_link_reparse_data *data; if (!valid_reparse_buffer(ni, reparse_attr, size, sizeof(*data))) return false; data = (struct wsl_link_reparse_data *)reparse_attr->reparse_data; if (le16_to_cpu(reparse_attr->reparse_data_length) <= sizeof(data->type) || data->type != cpu_to_le32(2)) return false; break; } case IO_REPARSE_TAG_AF_UNIX: case IO_REPARSE_TAG_LX_FIFO: case IO_REPARSE_TAG_LX_CHR: case IO_REPARSE_TAG_LX_BLK: if (!valid_reparse_buffer(ni, reparse_attr, size, 0)) return false; if (le16_to_cpu(reparse_attr->reparse_data_length) || !(ni->flags & FILE_ATTRIBUTE_RECALL_ON_OPEN)) return false; break; case IO_REPARSE_TAG_WOF: { if (!valid_reparse_buffer(ni, reparse_attr, size, sizeof(struct wof_reparse_data))) return false; break; } default: if (!valid_reparse_buffer(ni, reparse_attr, size, 0)) return false; break; } return true; } static unsigned int ntfs_reparse_tag_mode(__le32 reparse_tag) { unsigned int mode = 0; switch (reparse_tag) { case IO_REPARSE_TAG_MOUNT_POINT: case IO_REPARSE_TAG_SYMLINK: case IO_REPARSE_TAG_LX_SYMLINK: mode = S_IFLNK; break; case IO_REPARSE_TAG_AF_UNIX: mode = S_IFSOCK; break; case IO_REPARSE_TAG_LX_FIFO: mode = S_IFIFO; break; case IO_REPARSE_TAG_LX_CHR: mode = S_IFCHR; break; case IO_REPARSE_TAG_LX_BLK: mode = S_IFBLK; } return mode; } /* * Parse reparse point data and initialize its in-memory representation. */ int ntfs_parse_reparse(struct ntfs_inode *ni, unsigned int *mode) { s64 attr_size = 0; int err = -EINVAL; unsigned int lth; struct reparse_point *reparse_attr; 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)) { 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)); 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; 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; ni->itype.compressed.block_size_bits = 0; ni->itype.compressed.block_size = 0; if (wof_data->version == WOF_CURRENT_VERSION && wof_data->provider == WOF_PROVIDER_FILE && wof_data->provider_version == WOF_PROVIDER_CURRENT_VERSION) { 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; } } if (ni->itype.compressed.block_size_bits) 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; } out: kvfree(reparse_attr); return err; } unsigned int ntfs_reparse_tag_dt_types(struct ntfs_volume *vol, unsigned long mref) { s64 attr_size = 0; struct reparse_point *reparse_attr; unsigned int dt_type = DT_UNKNOWN; struct inode *vi; vi = ntfs_iget(vol->sb, mref); if (IS_ERR(vi)) return DT_UNKNOWN; reparse_attr = (struct reparse_point *)ntfs_attr_readall(NTFS_I(vi), AT_REPARSE_POINT, NULL, 0, &attr_size); if (IS_ERR(reparse_attr)) reparse_attr = NULL; if (reparse_attr && attr_size >= sizeof(*reparse_attr)) { switch (reparse_attr->reparse_tag) { case IO_REPARSE_TAG_MOUNT_POINT: case IO_REPARSE_TAG_SYMLINK: case IO_REPARSE_TAG_LX_SYMLINK: dt_type = DT_LNK; break; case IO_REPARSE_TAG_AF_UNIX: dt_type = DT_SOCK; break; case IO_REPARSE_TAG_LX_FIFO: dt_type = DT_FIFO; break; case IO_REPARSE_TAG_LX_CHR: dt_type = DT_CHR; break; case IO_REPARSE_TAG_LX_BLK: dt_type = DT_BLK; } } kvfree(reparse_attr); iput(vi); return dt_type; } static bool ntfs_is_drive_letter(const char *target) { return ((target[0] >= 'A' && target[0] <= 'Z') || (target[0] >= 'a' && target[0] <= 'z')) && target[1] == ':'; } /* * ntfs_translate_symlink_path * * @dentry: dentry of the symlink/junction being resolved * @target: NUL-terminated NLS target string with '\\' already normalized to '/' * @translated: out parameter, set to a newly kmalloc'd relative path on success * * Windows junctions (IO_REPARSE_TAG_MOUNT_POINT) and non-relative symlinks * (IO_REPARSE_TAG_SYMLINK without SYMLINK_FLAG_RELATIVE) store substitute * names such as "/??/C:/foo", "//?/C:/foo", "/foo", or "C:/foo". Linux * cannot continue pathname lookup from those syntaxes, so rewrite them as a * path relative to the symlink's containing directory on this NTFS volume, * anchored at the volume root via "../". * * Note: bind-mounted subtrees of the volume may resolve to unexpected * locations because the computed "../" depth is relative to the NTFS volume * root, not the bind-mounted subtree root. * * Return: 0 on success with *translated set to a newly allocated string the * caller must kfree(); negative errno on failure. */ int ntfs_translate_symlink_path(struct dentry *dentry, const char *target, char **translated) { char *buf, *link_path, *out, *p; const char *path, *tail; unsigned int up_levels = 0; size_t tail_len, out_len; int err; if (!dentry || !target || !translated) return -EINVAL; path = target; /* reject UNC path. */ if (path[0] == '/' && path[1] == '/' && !(path[2] == '?' && path[3] == '/')) return -EOPNOTSUPP; /* target starts with "/??/" or "//?/"? */ if ((path[0] == '/' && path[1] == '?' && path[2] == '?' && path[3] == '/') || (path[0] == '/' && path[1] == '/' && path[2] == '?' && path[3] == '/')) path += 4; /* target must start with a drive character or '/'. */ if (ntfs_is_drive_letter(path)) { if (path[2] && path[2] != '/') return -EOPNOTSUPP; tail = path + 2; if (*tail == '/') tail++; } else if (*path == '/') {