From: Eric Biggers Date: Sat, 30 May 2015 20:48:18 +0000 (-0500) Subject: Reparse point updates X-Git-Tag: v1.8.2~104 X-Git-Url: https://wimlib.net/git/?p=wimlib;a=commitdiff_plain;h=5538002965b9a7f08aef62c7b03194aa40bb0751 Reparse point updates - Rename 'struct reparse_data' => 'struct link_reparse_point' - Rename parse_reparse_data() => parse_link_reparse_data() - Rename make_reparse_data() => make_link_reparse_point() - Rename rp_unknown_1 => unknown_0x54 and move out of reparse point union - Rename rp_unknown_2 => rp_reserved and store/restore - Rename not_rpfixed => rp_flags and use 0x0001 as rpfix flag - Use 'struct reparse_buffer_disk' in more places and not raw byte array - Add new helper function: complete_reparse_point() - Eliminate need for various assertions in reparse.c - Improve handling of NT namespace paths in wim_inode_readlink() - More closely follow POSIX readlink semantics in wim_inode_readlink() - Set reparse tag and file attributes in wim_inode_readlink() - Handle reparse point fixup in wim_inode_readlink() and support fixing up link targets in mounted WIM image - Handle slashes between root and root-relative portion consistently between UNIX and Windows, and allow lossless handling of trailing slashes - Use 0 as RP_NOT_FIXED in win32_apply.c - Improve comments --- diff --git a/include/wimlib/inode.h b/include/wimlib/inode.h index 8455e667..08f414d0 100644 --- a/include/wimlib/inode.h +++ b/include/wimlib/inode.h @@ -163,21 +163,16 @@ struct wim_inode { * after checking for -1 (or equivalently < 0). */ s32 i_security_id; - /* Identity of a reparse point. See - * http://msdn.microsoft.com/en-us/library/windows/desktop/aa365503(v=vs.85).aspx - * for what a reparse point is. */ - u32 i_reparse_tag; - - /* Unused/unknown fields that we just read into memory so we can - * re-write them unchanged. */ - u32 i_rp_unknown_1; - u16 i_rp_unknown_2; + /* Unknown field that we only read into memory so we can re-write it + * unchanged. Probably it's actually just padding... */ + u32 i_unknown_0x54; - /* Corresponds to not_rpfixed in `struct wim_dentry_on_disk': Set to 0 - * if reparse point fixups have been done. Otherwise set to 1. Note: - * this actually may reflect the SYMBOLIC_LINK_RELATIVE flag. - */ - u16 i_not_rpfixed; + /* The following fields correspond to 'reparse_tag', 'rp_reserved', and + * 'rp_flags' in `struct wim_dentry_on_disk'. They are only meaningful + * for reparse point files. */ + u32 i_reparse_tag; + u16 i_rp_reserved; + u16 i_rp_flags; /* Inode number; corresponds to hard_link_group_id in the `struct * wim_dentry_on_disk'. */ @@ -240,21 +235,20 @@ struct wim_inode { }; /* - * Reparse tags documented at - * http://msdn.microsoft.com/en-us/library/dd541667(v=prot.10).aspx + * The available reparse tags are documented at + * http://msdn.microsoft.com/en-us/library/dd541667(v=prot.10).aspx. + * Here we only define the ones of interest to us. */ -#define WIM_IO_REPARSE_TAG_RESERVED_ZERO 0x00000000 -#define WIM_IO_REPARSE_TAG_RESERVED_ONE 0x00000001 #define WIM_IO_REPARSE_TAG_MOUNT_POINT 0xA0000003 -#define WIM_IO_REPARSE_TAG_HSM 0xC0000004 -#define WIM_IO_REPARSE_TAG_HSM2 0x80000006 -#define WIM_IO_REPARSE_TAG_DRIVER_EXTENDER 0x80000005 -#define WIM_IO_REPARSE_TAG_SIS 0x80000007 -#define WIM_IO_REPARSE_TAG_DFS 0x8000000A -#define WIM_IO_REPARSE_TAG_DFSR 0x80000012 -#define WIM_IO_REPARSE_TAG_FILTER_MANAGER 0x8000000B #define WIM_IO_REPARSE_TAG_SYMLINK 0xA000000C +#define WIM_IO_REPARSE_TAG_WOF 0x80000017 + +/* Flags for the rp_flags field. Currently the only known flag is NOT_FIXED, + * which indicates that the target of the absolute symbolic link or junction was + * not changed when it was stored. */ +#define WIM_RP_FLAG_NOT_FIXED 0x0001 +/* Windows file attribute flags */ #define FILE_ATTRIBUTE_READONLY 0x00000001 #define FILE_ATTRIBUTE_HIDDEN 0x00000002 #define FILE_ATTRIBUTE_SYSTEM 0x00000004 diff --git a/include/wimlib/reparse.h b/include/wimlib/reparse.h index b931835b..235c1680 100644 --- a/include/wimlib/reparse.h +++ b/include/wimlib/reparse.h @@ -1,17 +1,22 @@ #ifndef _WIMLIB_REPARSE_H #define _WIMLIB_REPARSE_H -#include - +#include "wimlib/inode.h" /* for reparse tag definitions */ #include "wimlib/types.h" -struct wim_inode; -struct blob_table; struct blob_descriptor; +struct blob_table; -#define REPARSE_POINT_MAX_SIZE (16 * 1024) +/* Windows enforces this limit on the size of a reparse point buffer. */ +#define REPARSE_POINT_MAX_SIZE 16384 -/* On-disk format of reparse point buffer */ +/* + * On-disk format of a reparse point buffer. See: + * https://msdn.microsoft.com/en-us/library/dd541671.aspx + * + * Note: we are not using _packed_attribute for this structure, so only cast to + * this if properly aligned! + */ struct reparse_buffer_disk { le32 rptag; le16 rpdatalen; @@ -24,72 +29,80 @@ struct reparse_buffer_disk { le16 substitute_name_nbytes; le16 print_name_offset; le16 print_name_nbytes; - le32 rpflags; - u8 data[REPARSE_POINT_MAX_SIZE - 20]; - } _packed_attribute symlink; - struct { - le16 substitute_name_offset; - le16 substitute_name_nbytes; - le16 print_name_offset; - le16 print_name_nbytes; - u8 data[REPARSE_POINT_MAX_SIZE - 16]; - } _packed_attribute junction; + union { + struct { + u8 data[REPARSE_POINT_MAX_SIZE - 16]; + } junction; + + struct { + le32 flags; + #define SYMBOLIC_LINK_RELATIVE 0x00000001 + u8 data[REPARSE_POINT_MAX_SIZE - 20]; + } symlink; + }; + } link; }; -} _packed_attribute; +}; #define REPARSE_DATA_OFFSET (offsetof(struct reparse_buffer_disk, rpdata)) #define REPARSE_DATA_MAX_SIZE (REPARSE_POINT_MAX_SIZE - REPARSE_DATA_OFFSET) +static inline void +check_reparse_buffer_disk(void) +{ + BUILD_BUG_ON(offsetof(struct reparse_buffer_disk, rpdata) != 8); + BUILD_BUG_ON(offsetof(struct reparse_buffer_disk, link.junction.data) != 16); + BUILD_BUG_ON(offsetof(struct reparse_buffer_disk, link.symlink.data) != 20); + BUILD_BUG_ON(sizeof(struct reparse_buffer_disk) != REPARSE_POINT_MAX_SIZE); +} -/* Structured format for symbolic link, junction point, or mount point reparse - * data. */ -struct reparse_data { - /* Reparse point tag (see WIM_IO_REPARSE_TAG_* values) */ - u32 rptag; - - /* Length of reparse data, not including the 8-byte header (ReparseTag, - * ReparseDataLength, ReparseReserved) */ - u16 rpdatalen; +/* Wrapper around a symbolic link or junction reparse point + * (WIM_IO_REPARSE_TAG_SYMLINK or WIM_IO_REPARSE_TAG_MOUNT_POINT) */ +struct link_reparse_point { - /* ReparseReserved */ + u32 rptag; u16 rpreserved; - /* Flags (only for WIM_IO_REPARSE_TAG_SYMLINK reparse points). - * SYMBOLIC_LINK_RELATIVE means this is a relative symbolic link; - * otherwise should be set to 0. */ -#define SYMBOLIC_LINK_RELATIVE 0x00000001 - u32 rpflags; + /* Flags, valid for symbolic links only */ + u32 symlink_flags; - /* Pointer to the substitute name of the link (UTF-16LE). */ + /* Pointers to the substitute name and print name of the link, + * potentially not null terminated */ utf16lechar *substitute_name; - - /* Pointer to the print name of the link (UTF-16LE). */ utf16lechar *print_name; - /* Number of bytes of the substitute name, not including null terminator - * if present */ - u16 substitute_name_nbytes; - - /* Number of bytes of the print name, not including null terminator if - * present */ - u16 print_name_nbytes; + /* Lengths of the substitute and print names in bytes, not including + * their null terminators if present */ + size_t substitute_name_nbytes; + size_t print_name_nbytes; }; +static inline bool +link_is_relative_symlink(const struct link_reparse_point *link) +{ + return link->rptag == WIM_IO_REPARSE_TAG_SYMLINK && + (link->symlink_flags & SYMBOLIC_LINK_RELATIVE); +} + +extern void +complete_reparse_point(struct reparse_buffer_disk *rpbuf, + const struct wim_inode *inode, u16 blob_size); + extern int -parse_reparse_data(const u8 * restrict rpbuf, u16 rpbuflen, - struct reparse_data * restrict rpdata); +parse_link_reparse_point(const struct reparse_buffer_disk *rpbuf, u16 rpbuflen, + struct link_reparse_point *link); extern int -make_reparse_buffer(const struct reparse_data * restrict rpdata, - u8 * restrict rpbuf, - u16 * restrict rpbuflen_ret); +make_link_reparse_point(const struct link_reparse_point *link, + struct reparse_buffer_disk *rpbuf, u16 *rpbuflen_ret); #ifndef __WIN32__ -ssize_t -wim_inode_readlink(const struct wim_inode * restrict inode, char * restrict buf, - size_t buf_len, const struct blob_descriptor *blob); +extern int +wim_inode_readlink(const struct wim_inode *inode, char *buf, size_t bufsize, + const struct blob_descriptor *blob, + const char *altroot, size_t altroot_len); extern int wim_inode_set_symlink(struct wim_inode *inode, const char *target, diff --git a/src/dentry.c b/src/dentry.c index a19e02c8..7ba43580 100644 --- a/src/dentry.c +++ b/src/dentry.c @@ -129,41 +129,33 @@ struct wim_dentry_on_disk { */ u8 default_hash[SHA1_HASH_SIZE]; - /* The format of the following data is not yet completely known and they - * do not correspond to Microsoft's documentation. + /* Unknown field (maybe accidental padding) */ + le32 unknown_0x54; + + /* + * The following 8-byte union contains either information about the + * reparse point (for files with FILE_ATTRIBUTE_REPARSE_POINT set), or + * the "hard link group ID" (for other files). + * + * The reparse point information contains ReparseTag and ReparseReserved + * from the header of the reparse point buffer. It also contains a flag + * that indicates whether a reparse point fixup (for the target of an + * absolute symbolic link or junction) was done or not. * - * If this directory entry is for a reparse point (has - * FILE_ATTRIBUTE_REPARSE_POINT set in the 'attributes' field), then the - * version of the following fields containing the reparse tag is valid. - * Furthermore, the field notated as not_rpfixed, as far as I can tell, - * is supposed to be set to 1 if reparse point fixups (a.k.a. fixing the - * targets of absolute symbolic links) were *not* done, and otherwise 0. + * The "hard link group ID" is like an inode number; all dentries for + * the same inode share the same value. See inode_fixup.c for more + * information. * - * If this directory entry is not for a reparse point, then the version - * of the following fields containing the hard_link_group_id is valid. - * All MS says about this field is that "If this file is part of a hard - * link set, all the directory entries in the set will share the same - * value in this field.". However, more specifically I have observed - * the following: - * - If the file is part of a hard link set of size 1, then the - * hard_link_group_id should be set to either 0, which is treated - * specially as indicating "not hardlinked", or any unique value. - * - The specific nonzero values used to identity hard link sets do - * not matter, as long as they are unique. - * - However, due to bugs in Microsoft's software, it is actually NOT - * guaranteed that directory entries that share the same hard link - * group ID are actually hard linked to each either. See - * inode_fixup.c for the code that handles this. + * Note that this union creates the limitation that reparse point files + * cannot have multiple names (hard links). */ union { struct { - le32 rp_unknown_1; le32 reparse_tag; - le16 rp_unknown_2; - le16 not_rpfixed; + le16 rp_reserved; + le16 rp_flags; } _packed_attribute reparse; struct { - le32 rp_unknown_1; le64 hard_link_group_id; } _packed_attribute nonreparse; }; @@ -1449,20 +1441,15 @@ read_dentry(const u8 * restrict buf, size_t buf_len, inode->i_creation_time = le64_to_cpu(disk_dentry->creation_time); inode->i_last_access_time = le64_to_cpu(disk_dentry->last_access_time); inode->i_last_write_time = le64_to_cpu(disk_dentry->last_write_time); + inode->i_unknown_0x54 = le32_to_cpu(disk_dentry->unknown_0x54); - /* I don't know what's going on here. It seems like M$ screwed up the - * reparse points, then put the fields in the same place and didn't - * document it. So we have some fields we read for reparse points, and - * some fields in the same place for non-reparse-points. */ if (inode->i_attributes & FILE_ATTRIBUTE_REPARSE_POINT) { - inode->i_rp_unknown_1 = le32_to_cpu(disk_dentry->reparse.rp_unknown_1); inode->i_reparse_tag = le32_to_cpu(disk_dentry->reparse.reparse_tag); - inode->i_rp_unknown_2 = le16_to_cpu(disk_dentry->reparse.rp_unknown_2); - inode->i_not_rpfixed = le16_to_cpu(disk_dentry->reparse.not_rpfixed); + inode->i_rp_reserved = le16_to_cpu(disk_dentry->reparse.rp_reserved); + inode->i_rp_flags = le16_to_cpu(disk_dentry->reparse.rp_flags); /* Leave inode->i_ino at 0. Note: this means that WIM cannot * represent multiple hard links to a reparse point file. */ } else { - inode->i_rp_unknown_1 = le32_to_cpu(disk_dentry->nonreparse.rp_unknown_1); inode->i_ino = le64_to_cpu(disk_dentry->nonreparse.hard_link_group_id); } @@ -1769,13 +1756,12 @@ write_dentry(const struct wim_dentry * restrict dentry, u8 * restrict p) disk_dentry->creation_time = cpu_to_le64(inode->i_creation_time); disk_dentry->last_access_time = cpu_to_le64(inode->i_last_access_time); disk_dentry->last_write_time = cpu_to_le64(inode->i_last_write_time); + disk_dentry->unknown_0x54 = cpu_to_le32(inode->i_unknown_0x54); if (inode->i_attributes & FILE_ATTRIBUTE_REPARSE_POINT) { - disk_dentry->reparse.rp_unknown_1 = cpu_to_le32(inode->i_rp_unknown_1); disk_dentry->reparse.reparse_tag = cpu_to_le32(inode->i_reparse_tag); - disk_dentry->reparse.rp_unknown_2 = cpu_to_le16(inode->i_rp_unknown_2); - disk_dentry->reparse.not_rpfixed = cpu_to_le16(inode->i_not_rpfixed); + disk_dentry->reparse.rp_reserved = cpu_to_le16(inode->i_rp_reserved); + disk_dentry->reparse.rp_flags = cpu_to_le16(inode->i_rp_flags); } else { - disk_dentry->nonreparse.rp_unknown_1 = cpu_to_le32(inode->i_rp_unknown_1); disk_dentry->nonreparse.hard_link_group_id = cpu_to_le64((inode->i_nlink == 1) ? 0 : inode->i_ino); } diff --git a/src/inode.c b/src/inode.c index 690eb22f..e43fd2d0 100644 --- a/src/inode.c +++ b/src/inode.c @@ -56,7 +56,7 @@ new_inode(struct wim_dentry *dentry, bool set_timestamps) inode->i_security_id = -1; /*inode->i_nlink = 0;*/ - inode->i_not_rpfixed = 1; + inode->i_rp_flags = WIM_RP_FLAG_NOT_FIXED; INIT_LIST_HEAD(&inode->i_dentry); inode->i_streams = inode->i_embedded_streams; if (set_timestamps) { diff --git a/src/mount_image.c b/src/mount_image.c index d9aa23cb..eeacf94d 100644 --- a/src/mount_image.c +++ b/src/mount_image.c @@ -145,6 +145,11 @@ struct wimfs_context { uid_t owner_uid; gid_t owner_gid; + /* Absolute path to the mountpoint directory (may be needed for absolute + * symbolic link fixups) */ + char *mountpoint_abspath; + size_t mountpoint_abspath_nchars; + /* Information about the staging directory for a read-write mount. */ int parent_dir_fd; int staging_dir_fd; @@ -443,14 +448,12 @@ wim_pathname_to_stream(const struct wimfs_context *ctx, * The path at which to create the first link to the new file. If a file * already exists at this path, -EEXIST is returned. * @mode - * The UNIX mode for the new file. This is only honored if + * The UNIX mode for the new file. This is only fully honored if * WIMLIB_MOUNT_FLAG_UNIX_DATA was passed to wimlib_mount_image(). * @rdev * The device ID for the new file, encoding the major and minor device * numbers. This is only honored if WIMLIB_MOUNT_FLAG_UNIX_DATA was passed * to wimlib_mount_image(). - * @attributes - * Windows file attributes to use for the new file. * @dentry_ret * On success, a pointer to the new dentry is returned here. Its d_inode * member will point to the new inode that was created for it and added to @@ -460,14 +463,13 @@ wim_pathname_to_stream(const struct wimfs_context *ctx, */ static int create_file(struct fuse_context *fuse_ctx, const char *path, - mode_t mode, dev_t rdev, u32 attributes, - struct wim_dentry **dentry_ret) + mode_t mode, dev_t rdev, struct wim_dentry **dentry_ret) { struct wimfs_context *wimfs_ctx = WIMFS_CTX(fuse_ctx); struct wim_dentry *parent; const char *basename; - struct wim_dentry *new_dentry; - struct wim_inode *new_inode; + struct wim_dentry *dentry; + struct wim_inode *inode; parent = get_parent_dentry(wimfs_ctx->wim, path, WIMLIB_CASE_SENSITIVE); if (!parent) @@ -481,13 +483,19 @@ create_file(struct fuse_context *fuse_ctx, const char *path, if (get_dentry_child_with_name(parent, basename, WIMLIB_CASE_SENSITIVE)) return -EEXIST; - if (new_dentry_with_new_inode(basename, true, &new_dentry)) + if (new_dentry_with_new_inode(basename, true, &dentry)) return -ENOMEM; - new_inode = new_dentry->d_inode; + inode = dentry->d_inode; + + inode->i_ino = wimfs_ctx->next_ino++; - new_inode->i_ino = wimfs_ctx->next_ino++; - new_inode->i_attributes = attributes; + /* Note: we still use FILE_ATTRIBUTE_NORMAL for device nodes, named + * pipes, and sockets. The real mode is in the UNIX metadata. */ + if (S_ISDIR(mode)) + inode->i_attributes = FILE_ATTRIBUTE_DIRECTORY; + else + inode->i_attributes = FILE_ATTRIBUTE_NORMAL; if (wimfs_ctx->mount_flags & WIMLIB_MOUNT_FLAG_UNIX_DATA) { struct wimlib_unix_data unix_data; @@ -496,19 +504,19 @@ create_file(struct fuse_context *fuse_ctx, const char *path, unix_data.gid = fuse_ctx->gid; unix_data.mode = fuse_mask_mode(mode, fuse_ctx); unix_data.rdev = rdev; - if (!inode_set_unix_data(new_inode, &unix_data, UNIX_DATA_ALL)) + if (!inode_set_unix_data(inode, &unix_data, UNIX_DATA_ALL)) { - free_dentry(new_dentry); + free_dentry(dentry); return -ENOMEM; } } - hlist_add_head(&new_inode->i_hlist, + hlist_add_head(&inode->i_hlist, &wim_get_current_image_metadata(wimfs_ctx->wim)->inode_list); - dentry_add_child(parent, new_dentry); + dentry_add_child(parent, dentry); - *dentry_ret = new_dentry; + *dentry_ret = dentry; return 0; } @@ -1491,8 +1499,7 @@ wimfs_mkdir(const char *path, mode_t mode) int ret; /* Note: according to fuse.h, mode may not include S_IFDIR */ - ret = create_file(fuse_get_context(), path, mode | S_IFDIR, 0, - FILE_ATTRIBUTE_DIRECTORY, &dentry); + ret = create_file(fuse_get_context(), path, mode | S_IFDIR, 0, &dentry); if (ret) return ret; touch_parent(dentry); @@ -1554,11 +1561,7 @@ wimfs_mknod(const char *path, mode_t mode, dev_t rdev) !(wimfs_ctx->mount_flags & WIMLIB_MOUNT_FLAG_UNIX_DATA)) return -EPERM; - /* Note: we still use FILE_ATTRIBUTE_NORMAL for device nodes, - * named pipes, and sockets. The real mode is in the UNIX - * metadata. */ - ret = create_file(fuse_ctx, path, mode, rdev, - FILE_ATTRIBUTE_NORMAL, &dentry); + ret = create_file(fuse_ctx, path, mode, rdev, &dentry); if (ret) return ret; touch_parent(dentry); @@ -1728,27 +1731,24 @@ wimfs_readdir(const char *path, void *buf, fuse_fill_dir_t filler, } static int -wimfs_readlink(const char *path, char *buf, size_t buf_len) +wimfs_readlink(const char *path, char *buf, size_t bufsize) { - WIMStruct *wim = wimfs_get_WIMStruct(); + struct wimfs_context *ctx = wimfs_get_context(); const struct wim_inode *inode; - ssize_t ret; + int ret; - inode = wim_pathname_to_inode(wim, path); + inode = wim_pathname_to_inode(ctx->wim, path); if (!inode) return -errno; - if (!inode_is_symlink(inode)) - return -EINVAL; - if (buf_len == 0) + if (bufsize <= 0) return -EINVAL; - ret = wim_inode_readlink(inode, buf, buf_len - 1, NULL); - if (ret >= 0) { - buf[ret] = '\0'; - ret = 0; - } else if (ret == -ENAMETOOLONG) { - buf[buf_len - 1] = '\0'; - } - return ret; + ret = wim_inode_readlink(inode, buf, bufsize - 1, NULL, + ctx->mountpoint_abspath, + ctx->mountpoint_abspath_nchars); + if (ret < 0) + return ret; + buf[ret] = '\0'; + return 0; } /* We use this for both release() and releasedir(), since in both cases we @@ -1907,11 +1907,9 @@ wimfs_symlink(const char *to, const char *from) struct wim_dentry *dentry; int ret; - ret = create_file(fuse_ctx, from, S_IFLNK | 0777, 0, - FILE_ATTRIBUTE_REPARSE_POINT, &dentry); + ret = create_file(fuse_ctx, from, S_IFLNK | 0777, 0, &dentry); if (ret) return ret; - dentry->d_inode->i_reparse_tag = WIM_IO_REPARSE_TAG_SYMLINK; ret = wim_inode_set_symlink(dentry->d_inode, to, wimfs_ctx->wim->blob_table); if (ret) { @@ -2217,6 +2215,11 @@ wimlib_mount_image(WIMStruct *wim, int image, const char *dir, if (mount_flags & WIMLIB_MOUNT_FLAG_READWRITE) imd->modified = 1; + /* Save the absolute path to the mountpoint directory. */ + ctx.mountpoint_abspath = realpath(dir, NULL); + if (ctx.mountpoint_abspath) + ctx.mountpoint_abspath_nchars = strlen(ctx.mountpoint_abspath); + /* Build the FUSE command line. */ fuse_argc = 0; @@ -2302,6 +2305,7 @@ wimlib_mount_image(WIMStruct *wim, int image, const char *dir, /* Cleanup and return. */ if (ret) ret = WIMLIB_ERR_FUSE; + FREE(ctx.mountpoint_abspath); release_extra_refcnts(&ctx); if (mount_flags & WIMLIB_MOUNT_FLAG_READWRITE) delete_staging_dir(&ctx); diff --git a/src/ntfs-3g_apply.c b/src/ntfs-3g_apply.c index 3a028128..f0386b12 100644 --- a/src/ntfs-3g_apply.c +++ b/src/ntfs-3g_apply.c @@ -707,8 +707,6 @@ ntfs_3g_begin_extract_blob_instance(struct blob_descriptor *blob, return WIMLIB_ERR_INVALID_REPARSE_DATA; } ctx->reparse_ptr = ctx->rpbuf.rpdata; - ctx->rpbuf.rpdatalen = cpu_to_le16(blob->size); - ctx->rpbuf.rpreserved = cpu_to_le16(0); ctx->ntfs_reparse_inodes[ctx->num_reparse_inodes] = ni; ctx->wim_reparse_inodes[ctx->num_reparse_inodes] = inode; ctx->num_reparse_inodes++; @@ -862,11 +860,11 @@ ntfs_3g_end_extract_blob(struct blob_descriptor *blob, int status, void *_ctx) for (u32 i = 0; i < ctx->num_reparse_inodes; i++) { struct wim_inode *inode = ctx->wim_reparse_inodes[i]; - ctx->rpbuf.rptag = cpu_to_le32(inode->i_reparse_tag); + complete_reparse_point(&ctx->rpbuf, inode, blob->size); if (ntfs_set_ntfs_reparse_data(ctx->ntfs_reparse_inodes[i], (const char *)&ctx->rpbuf, - blob->size + REPARSE_DATA_OFFSET, + REPARSE_DATA_OFFSET + blob->size, 0)) { ERROR_WITH_ERRNO("Failed to set reparse " diff --git a/src/ntfs-3g_capture.c b/src/ntfs-3g_capture.c index 69949783..7940e0ea 100644 --- a/src/ntfs-3g_capture.c +++ b/src/ntfs-3g_capture.c @@ -740,7 +740,7 @@ build_dentry_tree_ntfs_recursive(struct wim_dentry **root_ret, * only allow capturing an entire volume. */ if (params->add_flags & WIMLIB_ADD_FLAG_RPFIX && inode_is_symlink(inode)) - inode->i_not_rpfixed = 0; + inode->i_rp_flags &= ~WIM_RP_FLAG_NOT_FIXED; if (!(params->add_flags & WIMLIB_ADD_FLAG_NO_ACLS)) { struct SECURITY_CONTEXT sec_ctx; diff --git a/src/reparse.c b/src/reparse.c index 620684a7..284398ac 100644 --- a/src/reparse.c +++ b/src/reparse.c @@ -1,9 +1,9 @@ /* - * reparse.c - Handle reparse data. + * reparse.c - Reparse point handling */ /* - * Copyright (C) 2012, 2013 Eric Biggers + * Copyright (C) 2012, 2013, 2015 Eric Biggers * * This file is free software; you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by the Free @@ -26,9 +26,7 @@ #include #include "wimlib/alloca.h" -#include "wimlib/assert.h" #include "wimlib/blob_table.h" -#include "wimlib/compiler.h" #include "wimlib/endianness.h" #include "wimlib/encoding.h" #include "wimlib/error.h" @@ -36,487 +34,405 @@ #include "wimlib/reparse.h" #include "wimlib/resource.h" -/* - * Read the data from a symbolic link, junction, or mount point reparse point - * buffer into a `struct reparse_data'. - * - * See http://msdn.microsoft.com/en-us/library/cc232006(v=prot.10).aspx for a - * description of the format of the reparse point buffers. - */ +/* Reconstruct the header of a reparse point buffer. This is necessary because + * only reparse data is stored in WIM files. The reparse tag is instead stored + * in the on-disk WIM dentry, and the reparse data length is equal to the size + * of the blob in which the reparse data was stored. */ +void +complete_reparse_point(struct reparse_buffer_disk *rpbuf, + const struct wim_inode *inode, u16 blob_size) +{ + rpbuf->rptag = cpu_to_le32(inode->i_reparse_tag); + rpbuf->rpdatalen = cpu_to_le16(blob_size); + rpbuf->rpreserved = cpu_to_le16(inode->i_rp_reserved); +} + +/* Parse the buffer for a symbolic link or junction reparse point and fill in a + * 'struct link_reparse_point'. */ int -parse_reparse_data(const u8 * restrict rpbuf, u16 rpbuflen, - struct reparse_data * restrict rpdata) +parse_link_reparse_point(const struct reparse_buffer_disk *rpbuf, u16 rpbuflen, + struct link_reparse_point *link) { u16 substitute_name_offset; u16 print_name_offset; - const struct reparse_buffer_disk *rpbuf_disk = - (const struct reparse_buffer_disk*)rpbuf; const u8 *data; - memset(rpdata, 0, sizeof(*rpdata)); - if (rpbuflen < 16) - goto out_invalid; - rpdata->rptag = le32_to_cpu(rpbuf_disk->rptag); - wimlib_assert(rpdata->rptag == WIM_IO_REPARSE_TAG_SYMLINK || - rpdata->rptag == WIM_IO_REPARSE_TAG_MOUNT_POINT); - rpdata->rpdatalen = le16_to_cpu(rpbuf_disk->rpdatalen); - rpdata->rpreserved = le16_to_cpu(rpbuf_disk->rpreserved); - substitute_name_offset = le16_to_cpu(rpbuf_disk->symlink.substitute_name_offset); - rpdata->substitute_name_nbytes = le16_to_cpu(rpbuf_disk->symlink.substitute_name_nbytes); - print_name_offset = le16_to_cpu(rpbuf_disk->symlink.print_name_offset); - rpdata->print_name_nbytes = le16_to_cpu(rpbuf_disk->symlink.print_name_nbytes); - - if ((substitute_name_offset & 1) | (print_name_offset & 1) | - (rpdata->substitute_name_nbytes & 1) | (rpdata->print_name_nbytes & 1)) - { - /* Names would be unaligned... */ - goto out_invalid; - } + link->rptag = le32_to_cpu(rpbuf->rptag); - if (rpdata->rptag == WIM_IO_REPARSE_TAG_SYMLINK) { - if (rpbuflen < 20) - goto out_invalid; - rpdata->rpflags = le32_to_cpu(rpbuf_disk->symlink.rpflags); - data = rpbuf_disk->symlink.data; + /* Not a symbolic link or junction? */ + if (link->rptag != WIM_IO_REPARSE_TAG_SYMLINK && + link->rptag != WIM_IO_REPARSE_TAG_MOUNT_POINT) + return WIMLIB_ERR_INVALID_REPARSE_DATA; + + /* Is the buffer too small to be a symlink or a junction? */ + if (rpbuflen < offsetof(struct reparse_buffer_disk, link.junction.data)) + return WIMLIB_ERR_INVALID_REPARSE_DATA; + + link->rpreserved = le16_to_cpu(rpbuf->rpreserved); + link->substitute_name_nbytes = le16_to_cpu(rpbuf->link.substitute_name_nbytes); + substitute_name_offset = le16_to_cpu(rpbuf->link.substitute_name_offset); + link->print_name_nbytes = le16_to_cpu(rpbuf->link.print_name_nbytes); + print_name_offset = le16_to_cpu(rpbuf->link.print_name_offset); + + /* The names must be properly sized and aligned. */ + if ((substitute_name_offset | print_name_offset | + link->substitute_name_nbytes | link->print_name_nbytes) & 1) + return WIMLIB_ERR_INVALID_REPARSE_DATA; + + if (link->rptag == WIM_IO_REPARSE_TAG_SYMLINK) { + if (rpbuflen < offsetof(struct reparse_buffer_disk, link.symlink.data)) + return WIMLIB_ERR_INVALID_REPARSE_DATA; + link->symlink_flags = le32_to_cpu(rpbuf->link.symlink.flags); + data = rpbuf->link.symlink.data; } else { - data = rpbuf_disk->junction.data; + data = rpbuf->link.junction.data; } - if ((size_t)substitute_name_offset + rpdata->substitute_name_nbytes + - (data - rpbuf) > rpbuflen) - goto out_invalid; - if ((size_t)print_name_offset + rpdata->print_name_nbytes + - (data - rpbuf) > rpbuflen) - goto out_invalid; - rpdata->substitute_name = (utf16lechar*)&data[substitute_name_offset]; - rpdata->print_name = (utf16lechar*)&data[print_name_offset]; + + /* Verify that the names don't overflow the buffer. */ + if ((data - (const u8 *)rpbuf) + substitute_name_offset + + link->substitute_name_nbytes > rpbuflen) + return WIMLIB_ERR_INVALID_REPARSE_DATA; + + if ((data - (const u8 *)rpbuf) + print_name_offset + + link->print_name_nbytes > rpbuflen) + return WIMLIB_ERR_INVALID_REPARSE_DATA; + + /* Save the name pointers. */ + link->substitute_name = (utf16lechar *)&data[substitute_name_offset]; + link->print_name = (utf16lechar *)&data[print_name_offset]; return 0; -out_invalid: - ERROR("Invalid reparse data"); - return WIMLIB_ERR_INVALID_REPARSE_DATA; } -/* - * Create a reparse point data buffer. - * - * @rpdata: Structure that contains the data we need. - * - * @rpbuf: Buffer into which to write the reparse point data buffer. Must be - * at least REPARSE_POINT_MAX_SIZE bytes long. - */ +/* Translate a 'struct link_reparse_point' into a reparse point buffer. */ int -make_reparse_buffer(const struct reparse_data * restrict rpdata, - u8 * restrict rpbuf, - u16 * restrict rpbuflen_ret) +make_link_reparse_point(const struct link_reparse_point *link, + struct reparse_buffer_disk *rpbuf, u16 *rpbuflen_ret) { - struct reparse_buffer_disk *rpbuf_disk = - (struct reparse_buffer_disk*)rpbuf; u8 *data; - if (rpdata->rptag == WIM_IO_REPARSE_TAG_SYMLINK) - data = rpbuf_disk->symlink.data; - else - data = rpbuf_disk->junction.data; + if (link->rptag == WIM_IO_REPARSE_TAG_SYMLINK) + data = rpbuf->link.symlink.data; + else if (link->rptag == WIM_IO_REPARSE_TAG_MOUNT_POINT) + data = rpbuf->link.junction.data; + else /* Callers should forbid this case, but check anyway. */ + return WIMLIB_ERR_INVALID_REPARSE_DATA; - if ((data - rpbuf) + rpdata->substitute_name_nbytes + - rpdata->print_name_nbytes + + /* Check if the names are too long to fit in a reparse point. */ + if ((data - (u8 *)rpbuf) + link->substitute_name_nbytes + + link->print_name_nbytes + 2 * sizeof(utf16lechar) > REPARSE_POINT_MAX_SIZE) - { - ERROR("Reparse data is too long!"); return WIMLIB_ERR_INVALID_REPARSE_DATA; - } - rpbuf_disk->rptag = cpu_to_le32(rpdata->rptag); - rpbuf_disk->rpreserved = cpu_to_le16(rpdata->rpreserved); - rpbuf_disk->symlink.substitute_name_offset = cpu_to_le16(0); - rpbuf_disk->symlink.substitute_name_nbytes = cpu_to_le16(rpdata->substitute_name_nbytes); - rpbuf_disk->symlink.print_name_offset = cpu_to_le16(rpdata->substitute_name_nbytes + 2); - rpbuf_disk->symlink.print_name_nbytes = cpu_to_le16(rpdata->print_name_nbytes); - - if (rpdata->rptag == WIM_IO_REPARSE_TAG_SYMLINK) - rpbuf_disk->symlink.rpflags = cpu_to_le32(rpdata->rpflags); - - /* We null-terminate the substitute and print names, although this may - * not be strictly necessary. Note that the byte counts should not - * include the null terminators. */ - data = mempcpy(data, rpdata->substitute_name, rpdata->substitute_name_nbytes); - *(utf16lechar*)data = cpu_to_le16(0); - data += 2; - data = mempcpy(data, rpdata->print_name, rpdata->print_name_nbytes); - *(utf16lechar*)data = cpu_to_le16(0); - data += 2; - rpbuf_disk->rpdatalen = cpu_to_le16(data - rpbuf - REPARSE_DATA_OFFSET); - *rpbuflen_ret = data - rpbuf; + rpbuf->rptag = cpu_to_le32(link->rptag); + rpbuf->rpreserved = cpu_to_le16(link->rpreserved); + rpbuf->link.substitute_name_offset = cpu_to_le16(0); + rpbuf->link.substitute_name_nbytes = cpu_to_le16(link->substitute_name_nbytes); + rpbuf->link.print_name_offset = cpu_to_le16(link->substitute_name_nbytes + + sizeof(utf16lechar)); + rpbuf->link.print_name_nbytes = cpu_to_le16(link->print_name_nbytes); + + if (link->rptag == WIM_IO_REPARSE_TAG_SYMLINK) + rpbuf->link.symlink.flags = cpu_to_le32(link->symlink_flags); + + /* We null-terminate the substitute and print names, although this isn't + * strictly necessary. Note that the nbytes fields do not include the + * null terminators. */ + data = mempcpy(data, link->substitute_name, link->substitute_name_nbytes); + *(utf16lechar *)data = cpu_to_le16(0); + data += sizeof(utf16lechar); + data = mempcpy(data, link->print_name, link->print_name_nbytes); + *(utf16lechar *)data = cpu_to_le16(0); + data += sizeof(utf16lechar); + rpbuf->rpdatalen = cpu_to_le16(data - rpbuf->rpdata); + + *rpbuflen_ret = data - (u8 *)rpbuf; return 0; } -/* UNIX version of getting and setting the data in reparse points */ +/* UNIX symlink <=> Windows reparse point translation */ #ifndef __WIN32__ -/* - * Read the reparse data from a WIM inode that is a reparse point. - * - * @rpbuf points to a buffer at least REPARSE_POINT_MAX_SIZE bytes into which - * the reparse point data buffer will be reconstructed. - * - * Note: in the WIM format, the first 8 bytes of the reparse point data buffer - * are omitted, presumably because we already know the reparse tag from the - * dentry, and we already know the reparse tag length from the blob length. - * However, we reconstruct the first 8 bytes in the buffer returned by this - * function. - */ +/* Retrieve the inode's reparse point buffer into @rpbuf and @rpbuflen_ret. + * This gets the reparse data from @blob if specified, otherwise from the + * inode's reparse point stream. The inode's streams must be resolved. */ static int -wim_inode_get_reparse_data(const struct wim_inode * restrict inode, - u8 * restrict rpbuf, - u16 * restrict rpbuflen_ret, - const struct blob_descriptor *blob_override) +wim_inode_get_reparse_point(const struct wim_inode *inode, + struct reparse_buffer_disk *rpbuf, + u16 *rpbuflen_ret, + const struct blob_descriptor *blob) { - const struct blob_descriptor *blob; int ret; - struct reparse_buffer_disk *rpbuf_disk; - u16 rpdatalen; - - wimlib_assert(inode->i_attributes & FILE_ATTRIBUTE_REPARSE_POINT); + u16 blob_size = 0; - if (blob_override) { - blob = blob_override; - } else { - struct wim_inode_stream *strm; + if (!blob) { + const struct wim_inode_stream *strm; strm = inode_get_unnamed_stream(inode, STREAM_TYPE_REPARSE_POINT); if (strm) blob = stream_blob_resolved(strm); - else - blob = NULL; - if (!blob) { - ERROR("Reparse point has no reparse data!"); - return WIMLIB_ERR_INVALID_REPARSE_DATA; - } } - if (blob->size > REPARSE_DATA_MAX_SIZE) { - ERROR("Reparse data is too long!"); - return WIMLIB_ERR_INVALID_REPARSE_DATA; + if (blob) { + if (blob->size > REPARSE_DATA_MAX_SIZE) + return WIMLIB_ERR_INVALID_REPARSE_DATA; + blob_size = blob->size; + ret = read_blob_into_buf(blob, rpbuf->rpdata); + if (ret) + return ret; } - rpdatalen = blob->size; - - /* Read the reparse data from blob */ - ret = read_blob_into_buf(blob, rpbuf + REPARSE_DATA_OFFSET); - if (ret) - return ret; - - /* Reconstruct the first 8 bytes of the reparse point buffer */ - rpbuf_disk = (struct reparse_buffer_disk*)rpbuf; - - /* ReparseTag */ - rpbuf_disk->rptag = cpu_to_le32(inode->i_reparse_tag); - /* ReparseDataLength */ - rpbuf_disk->rpdatalen = cpu_to_le16(rpdatalen); + complete_reparse_point(rpbuf, inode, blob_size); - /* ReparseReserved - * XXX this could be one of the unknown fields in the WIM dentry. */ - rpbuf_disk->rpreserved = cpu_to_le16(0); - - *rpbuflen_ret = rpdatalen + REPARSE_DATA_OFFSET; + *rpbuflen_ret = REPARSE_DATA_OFFSET + blob_size; return 0; } -static const utf16lechar volume_junction_prefix[11] = { - cpu_to_le16('\\'), - cpu_to_le16('?'), - cpu_to_le16('?'), - cpu_to_le16('\\'), - cpu_to_le16('V'), - cpu_to_le16('o'), - cpu_to_le16('l'), - cpu_to_le16('u'), - cpu_to_le16('m'), - cpu_to_le16('e'), - cpu_to_le16('{'), -}; - -enum { - SUBST_NAME_IS_RELATIVE_LINK = -1, - SUBST_NAME_IS_VOLUME_JUNCTION = -2, - SUBST_NAME_IS_UNKNOWN = -3, -}; - -/* Parse the "substitute name" (link target) from a symbolic link or junction - * reparse point. - * - * Return value is: - * - * Non-negative integer: - * The name is an absolute symbolic link in one of several formats, - * and the return value is the number of UTF-16LE characters that need to - * be advanced to reach a simple "absolute" path starting with a backslash - * (i.e. skip over \??\ and/or drive letter) - * Negative integer: - * SUBST_NAME_IS_VOLUME_JUNCTION: - * The name is a volume junction. - * SUBST_NAME_IS_RELATIVE_LINK: - * The name is a relative symbolic link. - * SUBST_NAME_IS_UNKNOWN: - * The name does not appear to be a valid symbolic link, junction, - * or mount point. - */ -static int -parse_substitute_name(const utf16lechar *substitute_name, - u16 substitute_name_nbytes, u32 rptag) +static void +copy(char **buf_p, size_t *bufsize_p, const char *src, size_t src_size) { - u16 substitute_name_nchars = substitute_name_nbytes / 2; - - if (substitute_name_nchars >= 7 && - substitute_name[0] == cpu_to_le16('\\') && - substitute_name[1] == cpu_to_le16('?') && - substitute_name[2] == cpu_to_le16('?') && - substitute_name[3] == cpu_to_le16('\\') && - substitute_name[4] != cpu_to_le16('\0') && - substitute_name[5] == cpu_to_le16(':') && - substitute_name[6] == cpu_to_le16('\\')) - { - /* "Full" symlink or junction (\??\x:\ prefixed path) */ - return 6; - } else if (rptag == WIM_IO_REPARSE_TAG_MOUNT_POINT && - substitute_name_nchars >= 12 && - memcmp(substitute_name, volume_junction_prefix, - sizeof(volume_junction_prefix)) == 0 && - substitute_name[substitute_name_nchars - 1] == cpu_to_le16('\\')) - { - /* Volume junction. Can't really do anything with it. */ - return SUBST_NAME_IS_VOLUME_JUNCTION; - } else if (rptag == WIM_IO_REPARSE_TAG_SYMLINK && - substitute_name_nchars >= 3 && - substitute_name[0] != cpu_to_le16('\0') && - substitute_name[1] == cpu_to_le16(':') && - substitute_name[2] == cpu_to_le16('\\')) - { - /* "Absolute" symlink, with drive letter */ - return 2; - } else if (rptag == WIM_IO_REPARSE_TAG_SYMLINK && - substitute_name_nchars >= 1) - { - if (substitute_name[0] == cpu_to_le16('\\')) - /* "Absolute" symlink, without drive letter */ - return 0; - else - /* "Relative" symlink, without drive letter */ - return SUBST_NAME_IS_RELATIVE_LINK; - } else { - return SUBST_NAME_IS_UNKNOWN; - } + size_t n = min(*bufsize_p, src_size); + memcpy(*buf_p, src, n); + *buf_p += n; + *bufsize_p -= n; } /* - * Get the UNIX-style symlink target from the WIM inode for a reparse point. - * Specifically, this translates the target from UTF-16 to the current multibyte - * encoding, strips the drive prefix if present, and swaps backslashes and - * forward slashes. + * Get a UNIX-style symlink target from the WIM inode for a reparse point. * * @inode - * The inode to read the symlink from. It must be a reparse point with - * tag WIM_IO_REPARSE_TAG_SYMLINK (a real symlink) or - * WIM_IO_REPARSE_TAG_MOUNT_POINT (a mount point or junction point). - * + * The inode from which to read the symlink. If not a symbolic link or + * junction reparse point, then -EINVAL will be returned. * @buf * Buffer into which to place the link target. - * * @bufsize * Available space in @buf, in bytes. - * - * @blob_override + * @blob * If not NULL, the blob from which to read the reparse data. Otherwise, * the reparse data will be read from the reparse point stream of @inode. + * @altroot + * If @altroot_len != 0 and the link is an absolute link that was stored as + * "fixed", then prepend this path to the link target. + * @altroot_len + * Length of the @altroot string or 0. * - * If the entire symbolic link target was placed in the buffer, returns the - * number of bytes written. The resulting string is not null-terminated. If - * the symbolic link target was too large to be placed in the buffer, the first - * @bufsize bytes of it are placed in the buffer and - * -ENAMETOOLONG is returned. Otherwise, a negative errno value indicating - * another error is returned. + * Similar to POSIX readlink(), this function writes as much of the symlink + * target as possible (up to @bufsize bytes) to @buf with no null terminator and + * returns the number of bytes written or a negative errno value on error. Note + * that the target is truncated and @bufsize is returned in the overflow case. */ -ssize_t -wim_inode_readlink(const struct wim_inode * restrict inode, - char * restrict buf, size_t bufsize, - const struct blob_descriptor *blob_override) +int +wim_inode_readlink(const struct wim_inode *inode, char *buf, size_t bufsize, + const struct blob_descriptor *blob, + const char *altroot, size_t altroot_len) { - int ret; - struct reparse_buffer_disk rpbuf_disk _aligned_attribute(8); - struct reparse_data rpdata; - char *link_target; - char *translated_target; - size_t link_target_len; + struct reparse_buffer_disk rpbuf; u16 rpbuflen; + struct link_reparse_point link; + char *target_buffer; + char *target; + size_t target_len; + char *buf_ptr; + bool rpfix_ok = false; + + /* Not a symbolic link or junction? */ + if (!inode_is_symlink(inode)) + return -EINVAL; - wimlib_assert(inode_is_symlink(inode)); + /* Retrieve the native Windows "substitute name". */ - if (wim_inode_get_reparse_data(inode, (u8*)&rpbuf_disk, &rpbuflen, - blob_override)) + if (wim_inode_get_reparse_point(inode, &rpbuf, &rpbuflen, blob)) return -EIO; - if (parse_reparse_data((const u8*)&rpbuf_disk, rpbuflen, &rpdata)) + if (parse_link_reparse_point(&rpbuf, rpbuflen, &link)) return -EINVAL; - ret = utf16le_to_tstr(rpdata.substitute_name, - rpdata.substitute_name_nbytes, - &link_target, &link_target_len); - if (ret) + /* Translate the substitute name to the current multibyte encoding. */ + if (utf16le_to_tstr(link.substitute_name, link.substitute_name_nbytes, + &target_buffer, &target_len)) return -errno; + target = target_buffer; + + /* + * The substitute name is a native Windows NT path. There are two cases: + * + * 1. The reparse point is a symlink (rptag=WIM_IO_REPARSE_TAG_SYMLINK) + * and SYMBOLIC_LINK_RELATIVE is set. Windows resolves the path + * relative to the directory containing the reparse point file. In + * this case, we just translate the path separators. + * 2. Otherwise, Windows resolves the path from the root of the Windows + * NT kernel object namespace. In this case, we attempt to strip the + * device name, in addition to translating the path separators; e.g. + * "\??\C:\Users\Public" is translated to "/Users/Public". + * + * Also in case (2) the link target may have been stored as "fixed", + * meaning that with the device portion stripped off it is effectively + * "relative to the root of the WIM image". If this is the case, and if + * the caller provided an alternate root directory, then rewrite the + * link to be relative to that directory. + */ + if (!link_is_relative_symlink(&link)) { + static const char *const nt_root_dirs[] = { + "\\??\\", "\\DosDevices\\", "\\Device\\", + }; + for (size_t i = 0; i < ARRAY_LEN(nt_root_dirs); i++) { + size_t len = strlen(nt_root_dirs[i]); + if (!strncmp(target, nt_root_dirs[i], len)) { + char *p = target + len; + while (*p == '\\') + p++; + while (*p && *p != '\\') + p++; + target_len -= (p - target); + target = p; + break; + } + } - translated_target = link_target; - ret = parse_substitute_name(rpdata.substitute_name, - rpdata.substitute_name_nbytes, - rpdata.rptag); - switch (ret) { - case SUBST_NAME_IS_RELATIVE_LINK: - goto out_translate_slashes; - case SUBST_NAME_IS_VOLUME_JUNCTION: - goto out_have_link; - case SUBST_NAME_IS_UNKNOWN: - ERROR("Can't understand reparse point " - "substitute name \"%s\"", link_target); - ret = -EIO; - goto out_free_link_target; - default: - translated_target += ret; - link_target_len -= ret; - break; + if (!(inode->i_rp_flags & WIM_RP_FLAG_NOT_FIXED)) + rpfix_ok = true; } -out_translate_slashes: - for (size_t i = 0; i < link_target_len; i++) { - if (translated_target[i] == '\\') - translated_target[i] = '/'; - else if (translated_target[i] == '/') - translated_target[i] = '\\'; + /* Translate backslashes (Windows NT path separator) to forward slashes + * (UNIX path separator). In addition, translate forwards slashes to + * backslashes; this enables lossless handling of UNIX symbolic link + * targets that contain the backslash character. */ + for (char *p = target; *p; p++) { + if (*p == '\\') + *p = '/'; + else if (*p == '/') + *p = '\\'; } -out_have_link: - if (link_target_len > bufsize) { - link_target_len = bufsize; - ret = -ENAMETOOLONG; - } else { - ret = link_target_len; + + /* Copy as much of the link target as possible to the output buffer and + * return the number of bytes copied. */ + buf_ptr = buf; + if (rpfix_ok && altroot_len != 0) { + copy(&buf_ptr, &bufsize, altroot, altroot_len); + } else if (target_len == 0) { + /* An absolute link target that was made relative to the same + * directory pointed to will end up empty if the original target + * did not have a trailing slash. Here, we are reading this + * adjusted link target without prefixing it. This usually + * doesn't happen, but if it does then we need to change it to + * "/" so that it is a valid target. */ + target = "/"; + target_len = 1; } - memcpy(buf, translated_target, link_target_len); -out_free_link_target: - FREE(link_target); - return ret; + copy(&buf_ptr, &bufsize, target, target_len); + FREE(target_buffer); + return buf_ptr - buf; } /* Given a UNIX-style symbolic link target, create a Windows-style reparse point * buffer and assign it to the specified inode. */ int -wim_inode_set_symlink(struct wim_inode *inode, const char *target, +wim_inode_set_symlink(struct wim_inode *inode, const char *_target, struct blob_table *blob_table) { - struct reparse_buffer_disk rpbuf_disk _aligned_attribute(8); - struct reparse_data rpdata; - static const char abs_subst_name_prefix[12] = "\\\0?\0?\0\\\0C\0:\0"; - static const char abs_print_name_prefix[4] = "C\0:\0"; - utf16lechar *name_utf16le; - size_t name_utf16le_nbytes; int ret; + utf16lechar *target; + size_t target_nbytes; + struct link_reparse_point link; + struct reparse_buffer_disk rpbuf; u16 rpbuflen; - DEBUG("Creating reparse point data buffer for UNIX " - "symlink target \"%s\"", target); - memset(&rpdata, 0, sizeof(rpdata)); - ret = tstr_to_utf16le(target, strlen(target), - &name_utf16le, &name_utf16le_nbytes); + /* Translate the link target to UTF-16LE. */ + ret = tstr_to_utf16le(_target, strlen(_target), &target, &target_nbytes); if (ret) - goto out; + return ret; - for (size_t i = 0; i < name_utf16le_nbytes / 2; i++) { - if (name_utf16le[i] == cpu_to_le16('/')) - name_utf16le[i] = cpu_to_le16('\\'); - else if (name_utf16le[i] == cpu_to_le16('\\')) - name_utf16le[i] = cpu_to_le16('/'); + /* Translate forward slashes (UNIX path separator) to backslashes + * (Windows NT path separator). In addition, translate backslashes to + * forward slashes; this enables lossless handling of UNIX symbolic link + * targets that contain the backslash character. */ + for (utf16lechar *p = target; *p; p++) { + if (*p == cpu_to_le16('/')) + *p = cpu_to_le16('\\'); + else if (*p == cpu_to_le16('\\')) + *p = cpu_to_le16('/'); } - /* Compatability notes: - * - * On UNIX, an absolute symbolic link begins with '/'; everything else - * is a relative symbolic link. (Quite simple compared to the various - * ways to provide Windows paths.) - * - * To change a UNIX relative symbolic link to Windows format, we need to - * translate it to UTF-16LE, swap forward slashes and backslashes, and - * set 'rpflags' to SYMBOLIC_LINK_RELATIVE. - * - * For UNIX absolute symbolic links, we must set the @flags field to 0. - * Then, there are multiple options as to actually represent the - * absolute link targets: - * - * (1) An absolute path beginning with one backslash character. similar - * to UNIX-style, just with a different path separator. Print name same - * as substitute name. - * - * (2) Absolute path beginning with drive letter followed by a - * backslash. Print name same as substitute name. - * - * (3) Absolute path beginning with drive letter followed by a - * backslash; substitute name prefixed with \??\, otherwise same as - * print name. - * - * We choose option (3) here, and we just assume C: for the drive - * letter. The reasoning for this is: - * - * (1) Microsoft imagex.exe has a bug where it does not attempt to do - * reparse point fixups for these links, even though they are valid - * absolute links. (Note: in this case prefixing the substitute name - * with \??\ does not work; it just makes the data unable to be restored - * at all.) - * (2) Microsoft imagex.exe will fail when doing reparse point fixups - * for these. It apparently contains a bug that causes it to create an - * invalid reparse point, which then cannot be restored. - * (3) This is the only option I tested for which reparse point fixups - * worked properly in Microsoft imagex.exe. - * - * So option (3) it is. - */ - - rpdata.rptag = inode->i_reparse_tag; - if (target[0] == '/') { - rpdata.substitute_name_nbytes = name_utf16le_nbytes + - sizeof(abs_subst_name_prefix); - rpdata.print_name_nbytes = name_utf16le_nbytes + - sizeof(abs_print_name_prefix); - rpdata.substitute_name = alloca(rpdata.substitute_name_nbytes); - rpdata.print_name = alloca(rpdata.print_name_nbytes); - memcpy(rpdata.substitute_name, abs_subst_name_prefix, - sizeof(abs_subst_name_prefix)); - memcpy(rpdata.print_name, abs_print_name_prefix, - sizeof(abs_print_name_prefix)); - memcpy((void*)rpdata.substitute_name + sizeof(abs_subst_name_prefix), - name_utf16le, name_utf16le_nbytes); - memcpy((void*)rpdata.print_name + sizeof(abs_print_name_prefix), - name_utf16le, name_utf16le_nbytes); + link.rptag = WIM_IO_REPARSE_TAG_SYMLINK; + link.rpreserved = 0; + + /* Note: an absolute link that was rewritten to be relative to another + * directory is assumed to either be empty or to have a leading slash. + * See unix_relativize_link_target(). */ + if (*target == cpu_to_le16('\\') || !*target) { + /* + * UNIX link target was absolute. In this case we represent the + * link as a symlink reparse point with SYMBOLIC_LINK_RELATIVE + * cleared. For this to work we need to assign it a path that + * can be resolved from the root of the Windows NT kernel object + * namespace. We do this by using "\??\C:" as a dummy prefix. + * + * Note that we could instead represent UNIX absolute links by + * setting SYMBOLIC_LINK_RELATIVE and then leaving the path + * backslash-prefixed like "\Users\Public". On Windows this is + * valid and denotes a path relative to the root of the + * filesystem on which the reparse point resides. The problem + * with this is that neither WIMGAPI nor wimlib (on Windows) + * will do "reparse point fixups" when extracting such links + * (modifying the link target to point into the actual + * extraction directory). So for the greatest cross-platform + * consistency, we have to use the fake C: drive approach. + */ + static const utf16lechar prefix[6] = { + cpu_to_le16('\\'), + cpu_to_le16('?'), + cpu_to_le16('?'), + cpu_to_le16('\\'), + cpu_to_le16('C'), + cpu_to_le16(':'), + }; + + /* Do not show \??\ in print name */ + const size_t num_unprintable_chars = 4; + + link.symlink_flags = 0; + link.substitute_name_nbytes = sizeof(prefix) + target_nbytes; + link.substitute_name = alloca(link.substitute_name_nbytes); + memcpy(link.substitute_name, prefix, sizeof(prefix)); + memcpy(link.substitute_name + ARRAY_LEN(prefix), target, target_nbytes); + link.print_name_nbytes = link.substitute_name_nbytes - + (num_unprintable_chars * sizeof(utf16lechar)); + link.print_name = link.substitute_name + num_unprintable_chars; } else { - rpdata.substitute_name_nbytes = name_utf16le_nbytes; - rpdata.print_name_nbytes = name_utf16le_nbytes; - rpdata.substitute_name = name_utf16le; - rpdata.print_name = name_utf16le; - rpdata.rpflags = SYMBOLIC_LINK_RELATIVE; + /* UNIX link target was relative. In this case we represent the + * link as a symlink reparse point with SYMBOLIC_LINK_RELATIVE + * set. This causes Windows to interpret the link relative to + * the directory containing the reparse point file. */ + link.symlink_flags = SYMBOLIC_LINK_RELATIVE; + link.substitute_name_nbytes = target_nbytes; + link.substitute_name = target; + link.print_name_nbytes = target_nbytes; + link.print_name = target; } - ret = make_reparse_buffer(&rpdata, (u8*)&rpbuf_disk, &rpbuflen); + /* Generate the reparse buffer. */ + ret = make_link_reparse_point(&link, &rpbuf, &rpbuflen); if (ret) - goto out_free_name; + goto out_free_target; + /* Save the reparse data with the inode. */ ret = WIMLIB_ERR_NOMEM; if (!inode_add_stream_with_data(inode, STREAM_TYPE_REPARSE_POINT, NO_STREAM_NAME, - (u8*)&rpbuf_disk + REPARSE_DATA_OFFSET, + rpbuf.rpdata, rpbuflen - REPARSE_DATA_OFFSET, blob_table)) - goto out_free_name; + goto out_free_target; + + /* The inode is now a reparse point. */ + inode->i_reparse_tag = link.rptag; + inode->i_attributes &= ~FILE_ATTRIBUTE_NORMAL; + inode->i_attributes |= FILE_ATTRIBUTE_REPARSE_POINT; ret = 0; -out_free_name: - FREE(name_utf16le); -out: +out_free_target: + FREE(target); return ret; } diff --git a/src/unix_apply.c b/src/unix_apply.c index dd253629..dc614913 100644 --- a/src/unix_apply.c +++ b/src/unix_apply.c @@ -496,40 +496,27 @@ unix_count_dentries(const struct list_head *dentry_list, static int unix_create_symlink(const struct wim_inode *inode, const char *path, - const u8 *rpdata, u16 rpdatalen, bool rpfix, - const char *apply_dir, size_t apply_dir_nchars) + size_t rpdatalen, struct unix_apply_ctx *ctx) { - char link_target[REPARSE_DATA_MAX_SIZE]; - int ret; + char target[REPARSE_POINT_MAX_SIZE]; struct blob_descriptor blob_override; + int ret; blob_set_is_located_in_attached_buffer(&blob_override, - (void *)rpdata, rpdatalen); + ctx->reparse_data, rpdatalen); - ret = wim_inode_readlink(inode, link_target, - sizeof(link_target) - 1, &blob_override); - if (ret < 0) { + ret = wim_inode_readlink(inode, target, sizeof(target) - 1, + &blob_override, + ctx->target_abspath, + ctx->target_abspath_nchars); + if (unlikely(ret < 0)) { errno = -ret; return WIMLIB_ERR_READLINK; } + target[ret] = '\0'; - link_target[ret] = 0; - - if (rpfix && link_target[0] == '/') { - - /* "Fix" the absolute symbolic link by prepending the absolute - * path to the target directory. */ - - if (sizeof(link_target) - (ret + 1) < apply_dir_nchars) { - errno = ENAMETOOLONG; - return WIMLIB_ERR_REPARSE_POINT_FIXUP_FAILED; - } - memmove(link_target + apply_dir_nchars, link_target, - ret + 1); - memcpy(link_target, apply_dir, apply_dir_nchars); - } retry_symlink: - if (symlink(link_target, path)) { + if (symlink(target, path)) { if (errno == EEXIST && !unlink(path)) goto retry_symlink; return WIMLIB_ERR_LINK; @@ -655,19 +642,9 @@ unix_end_extract_blob(struct blob_descriptor *blob, int status, void *_ctx) /* We finally have the symlink data, so we can create * the symlink. */ const char *path; - bool rpfix; - - rpfix = (ctx->common.extract_flags & - WIMLIB_EXTRACT_FLAG_RPFIX) && - !inode->i_not_rpfixed; path = unix_build_inode_extraction_path(inode, ctx); - ret = unix_create_symlink(inode, path, - ctx->reparse_data, - blob->size, - rpfix, - ctx->target_abspath, - ctx->target_abspath_nchars); + ret = unix_create_symlink(inode, path, blob->size, ctx); if (ret) { ERROR_WITH_ERRNO("Can't create symbolic link " "\"%s\"", path); diff --git a/src/unix_capture.c b/src/unix_capture.c index 08fd31ef..4e7c036d 100644 --- a/src/unix_capture.c +++ b/src/unix_capture.c @@ -204,32 +204,60 @@ unix_scan_directory(struct wim_dentry *dir_dentry, return ret; } -/* Given an absolute symbolic link target @dest (UNIX-style, beginning - * with '/'), determine whether it points into the directory specified by - * @ino and @dev. If so, return the target modified to be "absolute" - * relative to this directory. Otherwise, return NULL. */ +/* + * Given an absolute symbolic link target (UNIX-style, beginning with '/'), + * determine whether it points into the directory identified by @ino and @dev. + * If yes, return the suffix of @target which is relative to this directory, but + * retaining leading slashes. If no, return @target. + * + * Here are some examples, assuming that the @ino/@dev directory is "/home/e": + * + * Original target New target + * --------------- ---------- + * /home/e/test /test + * /home/e/test/ /test/ + * //home//e//test// //test// + * /home/e (empty string) + * /home/e/ / + * /usr/lib /usr/lib (external link) + * + * Because of the possibility of other links into the @ino/@dev directory and/or + * multiple path separators, we can't simply do a string comparison; instead we + * need to stat() each ancestor directory. + * + * If the link points directly to the @ino/@dev directory with no trailing + * slashes, then the new target will be an empty string. This is not a valid + * UNIX symlink target, but we store this in the archive anyway since the target + * is intended to be de-relativized when the link is extracted. + */ static char * -unix_fixup_abslink(char *dest, u64 ino, u64 dev) +unix_relativize_link_target(char *target, u64 ino, u64 dev) { - char *p = dest; + char *p = target; do { char save; struct stat stbuf; int ret; - /* Skip non-slashes. */ - while (*p && *p != '/') + /* Skip slashes (guaranteed to be at least one here) */ + do { p++; + } while (*p == '/'); + + /* End of string? */ + if (!*p) + break; - /* Skip slashes. */ - while (*p && *p == '/') + /* Skip non-slashes (guaranteed to be at least one here) */ + do { p++; + } while (*p && *p != '/'); - /* Get inode and device for this prefix. */ + /* Get the inode and device numbers for this prefix. */ save = *p; *p = '\0'; - ret = stat(dest, &stbuf); + ret = stat(target, &stbuf); *p = save; if (ret) { @@ -241,83 +269,66 @@ unix_fixup_abslink(char *dest, u64 ino, u64 dev) if (stbuf.st_ino == ino && stbuf.st_dev == dev) { /* Link points inside directory tree being captured. * Return abbreviated path. */ - *--p = '/'; - while (p > dest && *(p - 1) == '/') - p--; return p; } } while (*p); /* Link does not point inside directory tree being captured. */ - return NULL; + return target; } static int unix_scan_symlink(const char *full_path, int dirfd, const char *relpath, struct wim_inode *inode, struct capture_params *params) { - char deref_name_buf[4096]; - ssize_t deref_name_len; - char *dest; + char orig_target[REPARSE_POINT_MAX_SIZE]; + char *target = orig_target; int ret; - inode->i_attributes = FILE_ATTRIBUTE_REPARSE_POINT; - inode->i_reparse_tag = WIM_IO_REPARSE_TAG_SYMLINK; - - /* The idea here is to call readlink() to get the UNIX target of the - * symbolic link, then turn the target into a reparse point data buffer - * that contains a relative or absolute symbolic link. */ - deref_name_len = my_readlinkat(full_path, dirfd, relpath, - deref_name_buf, sizeof(deref_name_buf) - 1); - if (deref_name_len < 0) { + /* Read the UNIX symbolic link target. */ + ret = my_readlinkat(full_path, dirfd, relpath, target, + sizeof(orig_target)); + if (unlikely(ret < 0)) { ERROR_WITH_ERRNO("\"%s\": Can't read target of symbolic link", full_path); return WIMLIB_ERR_READLINK; } + if (unlikely(ret >= sizeof(orig_target))) { + ERROR("\"%s\": target of symbolic link is too long", full_path); + return WIMLIB_ERR_READLINK; + } + target[ret] = '\0'; - dest = deref_name_buf; - - dest[deref_name_len] = '\0'; - - if ((params->add_flags & WIMLIB_ADD_FLAG_RPFIX) && - dest[0] == '/') - { - char *fixed_dest; + /* If the link is absolute and reparse point fixups are enabled, then + * change it to be "absolute" relative to the tree being captured. */ + if (target[0] == '/' && (params->add_flags & WIMLIB_ADD_FLAG_RPFIX)) { + int status = WIMLIB_SCAN_DENTRY_NOT_FIXED_SYMLINK; - /* RPFIX (reparse point fixup) mode: Change target of absolute - * symbolic link to be "absolute" relative to the tree being - * captured. */ - fixed_dest = unix_fixup_abslink(dest, - params->capture_root_ino, - params->capture_root_dev); params->progress.scan.cur_path = full_path; - params->progress.scan.symlink_target = deref_name_buf; - if (fixed_dest) { - /* Link points inside the tree being captured, so it was - * fixed. */ - inode->i_not_rpfixed = 0; - dest = fixed_dest; - ret = do_capture_progress(params, - WIMLIB_SCAN_DENTRY_FIXED_SYMLINK, - NULL); - } else { - /* Link points outside the tree being captured, so it - * was not fixed. */ - ret = do_capture_progress(params, - WIMLIB_SCAN_DENTRY_NOT_FIXED_SYMLINK, - NULL); + params->progress.scan.symlink_target = target; + + target = unix_relativize_link_target(target, + params->capture_root_ino, + params->capture_root_dev); + if (target != orig_target) { + /* Link target was fixed. */ + inode->i_rp_flags &= ~WIM_RP_FLAG_NOT_FIXED; + status = WIMLIB_SCAN_DENTRY_FIXED_SYMLINK; } + ret = do_capture_progress(params, status, NULL); if (ret) return ret; } - ret = wim_inode_set_symlink(inode, dest, params->blob_table); + + /* Translate the UNIX symlink target into a Windows reparse point. */ + ret = wim_inode_set_symlink(inode, target, params->blob_table); if (ret) return ret; - /* Unfortunately, Windows seems to have the concept of "file" symbolic - * links as being different from "directory" symbolic links... so - * FILE_ATTRIBUTE_DIRECTORY needs to be set on the symbolic link if the - * *target* of the symbolic link is a directory. */ + /* On Windows, a reparse point can be set on both directory and + * non-directory files. Usually, a link that is intended to point to a + * (non-)directory is stored as a reparse point on a (non-)directory + * file. Replicate this behavior by examining the target file. */ struct stat stbuf; if (my_fstatat(full_path, dirfd, relpath, &stbuf, 0) == 0 && S_ISDIR(stbuf.st_mode)) diff --git a/src/wimboot.c b/src/wimboot.c index 5e5ae7bc..67798da3 100644 --- a/src/wimboot.c +++ b/src/wimboot.c @@ -37,6 +37,7 @@ #include "wimlib/assert.h" #include "wimlib/blob_table.h" +#include "wimlib/inode.h" #include "wimlib/error.h" #include "wimlib/util.h" #include "wimlib/wimboot.h" @@ -1138,7 +1139,7 @@ wimboot_set_pointer(HANDLE h, sizeof(struct wof_external_info) + sizeof(struct wim_provider_rpdata)); - in.hdr.rptag = WIMLIB_REPARSE_TAG_WOF; + in.hdr.rptag = WIM_IO_REPARSE_TAG_WOF; in.hdr.rpdatalen = sizeof(in) - sizeof(in.hdr); in.hdr.rpreserved = 0; diff --git a/src/win32_apply.c b/src/win32_apply.c index 9d627dbe..7bacac9d 100644 --- a/src/win32_apply.c +++ b/src/win32_apply.c @@ -1896,12 +1896,12 @@ begin_extract_blob_instance(const struct blob_descriptor *blob, return 0; } -/* Set the reparse data @rpbuf of length @rpbuflen on the extracted file +/* Set the reparse point @rpbuf of length @rpbuflen on the extracted file * corresponding to the WIM dentry @dentry. */ static int -do_set_reparse_data(const struct wim_dentry *dentry, - const void *rpbuf, u16 rpbuflen, - struct win32_apply_ctx *ctx) +do_set_reparse_point(const struct wim_dentry *dentry, + const struct reparse_buffer_disk *rpbuf, u16 rpbuflen, + struct win32_apply_ctx *ctx) { NTSTATUS status; HANDLE h; @@ -1955,33 +1955,27 @@ skip_nt_toplevel_component(const wchar_t *path, size_t path_nchars) L"\\DosDevices\\", L"\\Device\\", }; - size_t first_dir_len = 0; const wchar_t * const end = path + path_nchars; for (size_t i = 0; i < ARRAY_LEN(dirs); i++) { size_t len = wcslen(dirs[i]); - if (len <= (end - path) && !wcsnicmp(path, dirs[i], len)) { - first_dir_len = len; - break; + if (len <= (end - path) && !wmemcmp(path, dirs[i], len)) { + path += len; + while (path != end && *path == L'\\') + path++; + return path; } } - if (first_dir_len == 0) - return path; - path += first_dir_len; - while (path != end && *path == L'\\') - path++; return path; } -/* Given a Windows NT namespace path, such as \??\e:\Windows\System32, return a - * pointer to the suffix of the path that is device-relative, such as - * Windows\System32. +/* + * Given a Windows NT namespace path, such as \??\e:\Windows\System32, return a + * pointer to the suffix of the path that is device-relative but possibly with + * leading slashes, such as \Windows\System32. * * The path has an explicit length and is not necessarily null terminated. - * - * If the path just something like \??\e: then the returned pointer will point - * just past the colon. In this case the length of the result will be 0 - * characters. */ + */ static const wchar_t * get_device_relative_path(const wchar_t *path, size_t path_nchars) { @@ -1992,24 +1986,22 @@ get_device_relative_path(const wchar_t *path, size_t path_nchars) if (path == orig_path) return orig_path; - path = wmemchr(path, L'\\', (end - path)); - if (!path) - return end; - do { + while (path != end && *path != L'\\') path++; - } while (path != end && *path == L'\\'); + return path; } /* - * Given a reparse point buffer for a symbolic link or junction, adjust its - * contents so that the target of the link is consistent with the new location - * of the files. + * Given a reparse point buffer for an inode for which the absolute link target + * was relativized when it was archived, de-relative the link target to be + * consistent with the actual extraction location. */ static void -try_rpfix(u8 *rpbuf, u16 *rpbuflen_p, struct win32_apply_ctx *ctx) +try_rpfix(struct reparse_buffer_disk *rpbuf, u16 *rpbuflen_p, + struct win32_apply_ctx *ctx) { - struct reparse_data rpdata; + struct link_reparse_point link; size_t orig_subst_name_nchars; const wchar_t *relpath; size_t relpath_nchars; @@ -2018,43 +2010,33 @@ try_rpfix(u8 *rpbuf, u16 *rpbuflen_p, struct win32_apply_ctx *ctx) const wchar_t *fixed_print_name; size_t fixed_print_name_nchars; - if (parse_reparse_data(rpbuf, *rpbuflen_p, &rpdata)) { - /* Do nothing if the reparse data is invalid. */ + /* Do nothing if the reparse data is invalid. */ + if (parse_link_reparse_point(rpbuf, *rpbuflen_p, &link)) return; - } - if (rpdata.rptag == WIM_IO_REPARSE_TAG_SYMLINK && - (rpdata.rpflags & SYMBOLIC_LINK_RELATIVE)) - { - /* Do nothing if it's a relative symbolic link. */ + /* Do nothing if the reparse point is a relative symbolic link. */ + if (link_is_relative_symlink(&link)) return; - } /* Build the new substitute name from the NT namespace path to the * target directory, then a path separator, then the "device relative" * part of the old substitute name. */ - orig_subst_name_nchars = rpdata.substitute_name_nbytes / sizeof(wchar_t); + orig_subst_name_nchars = link.substitute_name_nbytes / sizeof(wchar_t); - relpath = get_device_relative_path(rpdata.substitute_name, + relpath = get_device_relative_path(link.substitute_name, orig_subst_name_nchars); relpath_nchars = orig_subst_name_nchars - - (relpath - rpdata.substitute_name); + (relpath - link.substitute_name); target_ntpath_nchars = ctx->target_ntpath.Length / sizeof(wchar_t); - fixed_subst_name_nchars = target_ntpath_nchars; - if (relpath_nchars) - fixed_subst_name_nchars += 1 + relpath_nchars; + fixed_subst_name_nchars = target_ntpath_nchars + relpath_nchars; + wchar_t fixed_subst_name[fixed_subst_name_nchars]; - wmemcpy(fixed_subst_name, ctx->target_ntpath.Buffer, - target_ntpath_nchars); - if (relpath_nchars) { - fixed_subst_name[target_ntpath_nchars] = L'\\'; - wmemcpy(&fixed_subst_name[target_ntpath_nchars + 1], - relpath, relpath_nchars); - } + wmemcpy(fixed_subst_name, ctx->target_ntpath.Buffer, target_ntpath_nchars); + wmemcpy(&fixed_subst_name[target_ntpath_nchars], relpath, relpath_nchars); /* Doesn't need to be null-terminated. */ /* Print name should be Win32, but not all NT names can even be @@ -2066,33 +2048,29 @@ try_rpfix(u8 *rpbuf, u16 *rpbuflen_p, struct win32_apply_ctx *ctx) fixed_print_name_nchars = fixed_subst_name_nchars - (fixed_print_name - fixed_subst_name); - rpdata.substitute_name = fixed_subst_name; - rpdata.substitute_name_nbytes = fixed_subst_name_nchars * sizeof(wchar_t); - rpdata.print_name = (wchar_t *)fixed_print_name; - rpdata.print_name_nbytes = fixed_print_name_nchars * sizeof(wchar_t); - make_reparse_buffer(&rpdata, rpbuf, rpbuflen_p); + link.substitute_name = fixed_subst_name; + link.substitute_name_nbytes = fixed_subst_name_nchars * sizeof(wchar_t); + link.print_name = (wchar_t *)fixed_print_name; + link.print_name_nbytes = fixed_print_name_nchars * sizeof(wchar_t); + make_link_reparse_point(&link, rpbuf, rpbuflen_p); } -/* Sets reparse data on the specified file. This handles "fixing" the targets - * of absolute symbolic links and junctions if WIMLIB_EXTRACT_FLAG_RPFIX was - * specified. */ +/* Sets the reparse point on the specified file. This handles "fixing" the + * targets of absolute symbolic links and junctions if WIMLIB_EXTRACT_FLAG_RPFIX + * was specified. */ static int -set_reparse_data(const struct wim_dentry *dentry, - const void *_rpbuf, u16 rpbuflen, struct win32_apply_ctx *ctx) +set_reparse_point(const struct wim_dentry *dentry, + const struct reparse_buffer_disk *rpbuf, u16 rpbuflen, + struct win32_apply_ctx *ctx) { - const struct wim_inode *inode = dentry->d_inode; - const void *rpbuf = _rpbuf; - if ((ctx->common.extract_flags & WIMLIB_EXTRACT_FLAG_RPFIX) - && !inode->i_not_rpfixed - && (inode->i_reparse_tag == WIM_IO_REPARSE_TAG_SYMLINK || - inode->i_reparse_tag == WIM_IO_REPARSE_TAG_MOUNT_POINT)) + && !(dentry->d_inode->i_rp_flags & WIM_RP_FLAG_NOT_FIXED)) { - memcpy(&ctx->rpfixbuf, _rpbuf, rpbuflen); - try_rpfix((u8 *)&ctx->rpfixbuf, &rpbuflen, ctx); + memcpy(&ctx->rpfixbuf, rpbuf, rpbuflen); + try_rpfix(&ctx->rpfixbuf, &rpbuflen, ctx); rpbuf = &ctx->rpfixbuf; } - return do_set_reparse_data(dentry, rpbuf, rpbuflen, ctx); + return do_set_reparse_point(dentry, rpbuf, rpbuflen, ctx); } @@ -2285,17 +2263,18 @@ end_extract_blob(struct blob_descriptor *blob, int status, void *_ctx) ret = WIMLIB_ERR_INVALID_REPARSE_DATA; return check_apply_error(dentry, ctx, ret); } - /* In the WIM format, reparse point streams are just the reparse - * data and omit the header. But we can reconstruct the header. - */ + /* Reparse data */ memcpy(ctx->rpbuf.rpdata, ctx->data_buffer, blob->size); - ctx->rpbuf.rpdatalen = blob->size; - ctx->rpbuf.rpreserved = 0; + list_for_each_entry(dentry, &ctx->reparse_dentries, tmp_list) { - ctx->rpbuf.rptag = dentry->d_inode->i_reparse_tag; - ret = set_reparse_data(dentry, &ctx->rpbuf, - blob->size + REPARSE_DATA_OFFSET, - ctx); + + /* Reparse point header */ + complete_reparse_point(&ctx->rpbuf, dentry->d_inode, + blob->size); + + ret = set_reparse_point(dentry, &ctx->rpbuf, + REPARSE_DATA_OFFSET + blob->size, + ctx); ret = check_apply_error(dentry, ctx, ret); if (ret) return ret; diff --git a/src/win32_capture.c b/src/win32_capture.c index 9d8bbbcb..8f964bfa 100644 --- a/src/win32_capture.c +++ b/src/win32_capture.c @@ -471,13 +471,7 @@ out_free_buf: } /* Reparse point fixup status code */ -enum rp_status { - /* Reparse point will be captured literally (no fixup) */ - RP_NOT_FIXED = -1, - - /* Reparse point will be captured with fixup */ - RP_FIXED = -2, -}; +#define RP_FIXED (-1) static bool file_has_ino_and_dev(HANDLE h, u64 ino, u64 dev) @@ -511,20 +505,15 @@ file_has_ino_and_dev(HANDLE h, u64 ino, u64 dev) } /* - * Given an (expected) NT namespace symbolic link or junction target @target of - * length @target_nbytes, determine if a prefix of the target points to a file - * identified by @capture_root_ino and @capture_root_dev. - * - * If yes, return a pointer to the portion of the link following this prefix. - * - * If no, return NULL. - * - * If the link target does not appear to be a valid NT namespace path, return - * @target itself. + * This is the Windows equivalent of unix_relativize_link_target(); see there + * for general details. This version works with an "absolute" Windows link + * target, specified from the root of the Windows kernel object namespace. Note + * that we have to open directories with a trailing slash when present because + * \??\E: opens the E: device itself and not the filesystem root directory. */ static const wchar_t * -winnt_get_root_relative_target(const wchar_t *target, size_t target_nbytes, - u64 capture_root_ino, u64 capture_root_dev) +winnt_relativize_link_target(const wchar_t *target, size_t target_nbytes, + u64 ino, u64 dev) { UNICODE_STRING name; OBJECT_ATTRIBUTES attr; @@ -590,28 +579,29 @@ winnt_get_root_relative_target(const wchar_t *target, size_t target_nbytes, name.Buffer = (wchar_t *)p; name.Length = 0; - if (file_has_ino_and_dev(h, capture_root_ino, - capture_root_dev)) + if (file_has_ino_and_dev(h, ino, dev)) goto out_close_root_dir; } } while (p != target_end); - p = NULL; + p = target; out_close_root_dir: if (attr.RootDirectory) (*func_NtClose)(attr.RootDirectory); + while (p > target && *(p - 1) == L'\\') + p--; return p; } static int winnt_rpfix_progress(struct capture_params *params, const wchar_t *path, - const struct reparse_data *rpdata, int scan_status) + const struct link_reparse_point *link, int scan_status) { - size_t print_name_nchars = rpdata->print_name_nbytes / sizeof(wchar_t); + size_t print_name_nchars = link->print_name_nbytes / sizeof(wchar_t); wchar_t print_name0[print_name_nchars + 1]; - wmemcpy(print_name0, rpdata->print_name, print_name_nchars); + wmemcpy(print_name0, link->print_name, print_name_nchars); print_name0[print_name_nchars] = L'\0'; params->progress.scan.cur_path = printable_path(path); @@ -620,18 +610,16 @@ winnt_rpfix_progress(struct capture_params *params, const wchar_t *path, } static int -winnt_try_rpfix(u8 *rpbuf, u16 *rpbuflen_p, - u64 capture_root_ino, u64 capture_root_dev, +winnt_try_rpfix(struct reparse_buffer_disk *rpbuf, u16 *rpbuflen_p, const wchar_t *path, struct capture_params *params) { - struct reparse_data rpdata; + struct link_reparse_point link; const wchar_t *rel_target; int ret; - if (parse_reparse_data(rpbuf, *rpbuflen_p, &rpdata)) { - /* Couldn't even understand the reparse data. Don't try the - * fixup. */ - return RP_NOT_FIXED; + if (parse_link_reparse_point(rpbuf, *rpbuflen_p, &link)) { + /* Couldn't understand the reparse data; don't do the fixup. */ + return 0; } /* @@ -656,28 +644,19 @@ winnt_try_rpfix(u8 *rpbuf, u16 *rpbuflen_p, * - \??\Volume{c47cb07c-946e-4155-b8f7-052e9cec7628}\Users\Public * - \DosDevices\Volume{c47cb07c-946e-4155-b8f7-052e9cec7628}\Users\Public */ - if (rpdata.rptag == WIM_IO_REPARSE_TAG_SYMLINK && - (rpdata.rpflags & SYMBOLIC_LINK_RELATIVE)) - return RP_NOT_FIXED; - - rel_target = winnt_get_root_relative_target(rpdata.substitute_name, - rpdata.substitute_name_nbytes, - capture_root_ino, - capture_root_dev); - if (!rel_target) { - /* Target points outside of the tree being captured. Don't - * adjust it. */ - ret = winnt_rpfix_progress(params, path, &rpdata, - WIMLIB_SCAN_DENTRY_NOT_FIXED_SYMLINK); - if (ret) - return ret; - return RP_NOT_FIXED; - } + if (link_is_relative_symlink(&link)) + return 0; + + rel_target = winnt_relativize_link_target(link.substitute_name, + link.substitute_name_nbytes, + params->capture_root_ino, + params->capture_root_dev); - if (rel_target == rpdata.substitute_name) { - /* Weird target --- keep the reparse point and don't mess with - * it. */ - return RP_NOT_FIXED; + if (rel_target == link.substitute_name) { + /* Target points outside of the tree being captured or had an + * unrecognized path format. Don't adjust it. */ + return winnt_rpfix_progress(params, path, &link, + WIMLIB_SCAN_DENTRY_NOT_FIXED_SYMLINK); } /* We have an absolute target pointing within the directory being @@ -686,31 +665,33 @@ winnt_try_rpfix(u8 *rpbuf, u16 *rpbuflen_p, * * We will cut off the prefix before this part (which is the path to the * directory being captured) and add a dummy prefix. Since the process - * will need to be reversed when applying the image, it shouldn't matter + * will need to be reversed when applying the image, it doesn't matter * what exactly the prefix is, as long as it looks like an absolute - * path. - */ + * path. */ - { - size_t rel_target_nbytes = - rpdata.substitute_name_nbytes - ((const u8 *)rel_target - - (const u8 *)rpdata.substitute_name); - size_t rel_target_nchars = rel_target_nbytes / sizeof(wchar_t); + static const wchar_t prefix[6] = L"\\??\\X:"; + static const size_t num_unprintable_chars = 4; - wchar_t tmp[rel_target_nchars + 7]; + size_t rel_target_nbytes = + link.substitute_name_nbytes - ((const u8 *)rel_target - + (const u8 *)link.substitute_name); - wmemcpy(tmp, L"\\??\\X:\\", 7); - wmemcpy(tmp + 7, rel_target, rel_target_nchars); + wchar_t tmp[(sizeof(prefix) + rel_target_nbytes) / sizeof(wchar_t)]; - rpdata.substitute_name = tmp; - rpdata.substitute_name_nbytes = rel_target_nbytes + (7 * sizeof(wchar_t)); - rpdata.print_name = tmp + 4; - rpdata.print_name_nbytes = rel_target_nbytes + (3 * sizeof(wchar_t)); + memcpy(tmp, prefix, sizeof(prefix)); + memcpy(tmp + ARRAY_LEN(prefix), rel_target, rel_target_nbytes); - if (make_reparse_buffer(&rpdata, rpbuf, rpbuflen_p)) - return RP_NOT_FIXED; - } - ret = winnt_rpfix_progress(params, path, &rpdata, + link.substitute_name = tmp; + link.substitute_name_nbytes = sizeof(tmp); + + link.print_name = link.substitute_name + num_unprintable_chars; + link.print_name_nbytes = link.substitute_name_nbytes - + (num_unprintable_chars * sizeof(wchar_t)); + + if (make_link_reparse_point(&link, rpbuf, rpbuflen_p)) + return 0; + + ret = winnt_rpfix_progress(params, path, &link, WIMLIB_SCAN_DENTRY_FIXED_SYMLINK); if (ret) return ret; @@ -718,7 +699,7 @@ winnt_try_rpfix(u8 *rpbuf, u16 *rpbuflen_p, } /* - * Loads the reparse point data from a reparse point into memory, optionally + * Loads the reparse point buffer from a reparse point into memory, optionally * fixing the targets of absolute symbolic links and junction points to be * relative to the root of capture. * @@ -736,24 +717,21 @@ winnt_try_rpfix(u8 *rpbuf, u16 *rpbuflen_p, * On success, the length of the reparse point buffer in bytes is written * to this location. * - * On success, returns a negative `enum rp_status' value. + * On success, returns 0 or RP_FIXED. * On failure, returns a positive error code. */ static int -winnt_get_reparse_data(HANDLE h, const wchar_t *path, - struct capture_params *params, - u8 *rpbuf, u16 *rpbuflen_ret) +winnt_get_reparse_point(HANDLE h, const wchar_t *path, + struct capture_params *params, + struct reparse_buffer_disk *rpbuf, u16 *rpbuflen_ret) { DWORD bytes_returned; - u32 reparse_tag; - int ret; - u16 rpbuflen; if (!DeviceIoControl(h, FSCTL_GET_REPARSE_POINT, NULL, 0, rpbuf, REPARSE_POINT_MAX_SIZE, &bytes_returned, NULL)) { - win32_error(GetLastError(), L"\"%ls\": Can't get reparse data", + win32_error(GetLastError(), L"\"%ls\": Can't get reparse point", printable_path(path)); return WIMLIB_ERR_READ; } @@ -764,20 +742,10 @@ winnt_get_reparse_data(HANDLE h, const wchar_t *path, return WIMLIB_ERR_INVALID_REPARSE_DATA; } - rpbuflen = bytes_returned; - reparse_tag = le32_to_cpu(*(le32*)rpbuf); - ret = RP_NOT_FIXED; - if (params->add_flags & WIMLIB_ADD_FLAG_RPFIX && - (reparse_tag == WIM_IO_REPARSE_TAG_SYMLINK || - reparse_tag == WIM_IO_REPARSE_TAG_MOUNT_POINT)) - { - ret = winnt_try_rpfix(rpbuf, &rpbuflen, - params->capture_root_ino, - params->capture_root_dev, - path, params); - } - *rpbuflen_ret = rpbuflen; - return ret; + *rpbuflen_ret = bytes_returned; + if (params->add_flags & WIMLIB_ADD_FLAG_RPFIX) + return winnt_try_rpfix(rpbuf, rpbuflen_ret, path, params); + return 0; } static DWORD WINAPI @@ -1345,26 +1313,20 @@ retry_open: WARNING("Ignoring reparse data of encrypted file \"%ls\"", printable_path(full_path)); } else { - u8 rpbuf[REPARSE_POINT_MAX_SIZE] _aligned_attribute(8); + struct reparse_buffer_disk rpbuf; u16 rpbuflen; - ret = winnt_get_reparse_data(h, full_path, params, - rpbuf, &rpbuflen); - switch (ret) { - case RP_FIXED: - inode->i_not_rpfixed = 0; - break; - case RP_NOT_FIXED: - inode->i_not_rpfixed = 1; - break; - default: + ret = winnt_get_reparse_point(h, full_path, params, + &rpbuf, &rpbuflen); + if (ret == RP_FIXED) + inode->i_rp_flags &= ~WIM_RP_FLAG_NOT_FIXED; + else if (ret) goto out; - } - inode->i_reparse_tag = le32_to_cpu(*(le32*)rpbuf); + inode->i_reparse_tag = le32_to_cpu(rpbuf.rptag); if (!inode_add_stream_with_data(inode, STREAM_TYPE_REPARSE_POINT, NO_STREAM_NAME, - rpbuf + REPARSE_DATA_OFFSET, + rpbuf.rpdata, rpbuflen - REPARSE_DATA_OFFSET, params->blob_table)) {