]> wimlib.net Git - wimlib/commitdiff
Reparse point updates
authorEric Biggers <ebiggers3@gmail.com>
Sat, 30 May 2015 20:48:18 +0000 (15:48 -0500)
committerEric Biggers <ebiggers3@gmail.com>
Fri, 5 Jun 2015 03:05:37 +0000 (22:05 -0500)
- 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

13 files changed:
include/wimlib/inode.h
include/wimlib/reparse.h
src/dentry.c
src/inode.c
src/mount_image.c
src/ntfs-3g_apply.c
src/ntfs-3g_capture.c
src/reparse.c
src/unix_apply.c
src/unix_capture.c
src/wimboot.c
src/win32_apply.c
src/win32_capture.c

index 8455e667b47044b2bc2d52ea3962d9b93d36a5d7..08f414d0d7ca6857b90dbffa0396fabfd59784a0 100644 (file)
@@ -163,21 +163,16 @@ struct wim_inode {
         * after checking for -1 (or equivalently < 0).  */
        s32 i_security_id;
 
         * 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'.  */
 
        /* 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_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_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
 #define FILE_ATTRIBUTE_READONLY            0x00000001
 #define FILE_ATTRIBUTE_HIDDEN              0x00000002
 #define FILE_ATTRIBUTE_SYSTEM              0x00000004
index b931835bd23af98e08d5268444fb130dd5edea3f..235c1680c17bca3a5bbd68e386864f05bd3d6f53 100644 (file)
@@ -1,17 +1,22 @@
 #ifndef _WIMLIB_REPARSE_H
 #define _WIMLIB_REPARSE_H
 
 #ifndef _WIMLIB_REPARSE_H
 #define _WIMLIB_REPARSE_H
 
-#include <sys/types.h>
-
+#include "wimlib/inode.h" /* for reparse tag definitions */
 #include "wimlib/types.h"
 
 #include "wimlib/types.h"
 
-struct wim_inode;
-struct blob_table;
 struct blob_descriptor;
 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;
 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;
                        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)
 
 
 #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;
 
        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;
        utf16lechar *substitute_name;
-
-       /* Pointer to the print name of the link (UTF-16LE). */
        utf16lechar *print_name;
 
        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
 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
 
 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__
 
 #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,
 
 extern int
 wim_inode_set_symlink(struct wim_inode *inode, const char *target,
index a19e02c8f3df54dde88ad1b563d491dd1c5f309f..7ba4358089f42eca3a8ab97088857de6f28d4198 100644 (file)
@@ -129,41 +129,33 @@ struct wim_dentry_on_disk {
         */
        u8 default_hash[SHA1_HASH_SIZE];
 
         */
        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 {
         */
        union {
                struct {
-                       le32 rp_unknown_1;
                        le32 reparse_tag;
                        le32 reparse_tag;
-                       le16 rp_unknown_2;
-                       le16 not_rpfixed;
+                       le16 rp_reserved;
+                       le16 rp_flags;
                } _packed_attribute reparse;
                struct {
                } _packed_attribute reparse;
                struct {
-                       le32 rp_unknown_1;
                        le64 hard_link_group_id;
                } _packed_attribute nonreparse;
        };
                        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_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) {
        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_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 {
                /* 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);
        }
 
                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->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) {
        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.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 {
        } 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);
        }
                disk_dentry->nonreparse.hard_link_group_id =
                        cpu_to_le64((inode->i_nlink == 1) ? 0 : inode->i_ino);
        }
index 690eb22f3abf657ef51b354e030e85fe00eec8ac..e43fd2d0d1b446f70a5b334f45ad05a76a2d1deb 100644 (file)
@@ -56,7 +56,7 @@ new_inode(struct wim_dentry *dentry, bool set_timestamps)
 
        inode->i_security_id = -1;
        /*inode->i_nlink = 0;*/
 
        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) {
        INIT_LIST_HEAD(&inode->i_dentry);
        inode->i_streams = inode->i_embedded_streams;
        if (set_timestamps) {
index d9aa23cb69fbdd28f272db5c500fd3dcc80c6448..eeacf94d138864ab9c0bae8975d1ff0e475302a3 100644 (file)
@@ -145,6 +145,11 @@ struct wimfs_context {
        uid_t owner_uid;
        gid_t owner_gid;
 
        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;
        /* 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 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().
  *     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
  * @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,
  */
 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 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)
 
        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 (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;
 
                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;
 
        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;
                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;
                }
        }
 
                        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);
 
                       &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;
 }
 
        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  */
        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);
        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;
 
                    !(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);
                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
 }
 
 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;
        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)
                return -errno;
-       if (!inode_is_symlink(inode))
-               return -EINVAL;
-       if (buf_len == 0)
+       if (bufsize <= 0)
                return -EINVAL;
                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
 }
 
 /* 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;
 
        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;
        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) {
        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;
 
        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;
        /* 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;
        /* 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);
        release_extra_refcnts(&ctx);
        if (mount_flags & WIMLIB_MOUNT_FLAG_READWRITE)
                delete_staging_dir(&ctx);
index 3a02812814a71b186212e62c73d5c98bb773bea5..f0386b1245a025c6138308f8e13cfb9676172684 100644 (file)
@@ -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;
                        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++;
                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];
 
        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,
 
                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 "
                                               0))
                {
                        ERROR_WITH_ERRNO("Failed to set reparse "
index 69949783e93c66a73f834040ee5bab1d740b95cf..7940e0eac0661b4946e6315e08c0b9ce7d6c6f6d 100644 (file)
@@ -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))
         * 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;
 
        if (!(params->add_flags & WIMLIB_ADD_FLAG_NO_ACLS)) {
                struct SECURITY_CONTEXT sec_ctx;
index 620684a7f52c7db9039f731c20666e6165519d08..284398ac0d672a610e82858b2af684801a3615c6 100644 (file)
@@ -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
  *
  * 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 <errno.h>
 
 #include "wimlib/alloca.h"
 #include <errno.h>
 
 #include "wimlib/alloca.h"
-#include "wimlib/assert.h"
 #include "wimlib/blob_table.h"
 #include "wimlib/blob_table.h"
-#include "wimlib/compiler.h"
 #include "wimlib/endianness.h"
 #include "wimlib/encoding.h"
 #include "wimlib/error.h"
 #include "wimlib/endianness.h"
 #include "wimlib/encoding.h"
 #include "wimlib/error.h"
 #include "wimlib/reparse.h"
 #include "wimlib/resource.h"
 
 #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
 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;
 {
        u16 substitute_name_offset;
        u16 print_name_offset;
-       const struct reparse_buffer_disk *rpbuf_disk =
-               (const struct reparse_buffer_disk*)rpbuf;
        const u8 *data;
 
        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 {
        } 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;
        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
 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;
 
        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)
            2 * sizeof(utf16lechar) > REPARSE_POINT_MAX_SIZE)
-       {
-               ERROR("Reparse data is too long!");
                return WIMLIB_ERR_INVALID_REPARSE_DATA;
                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;
 }
 
        return 0;
 }
 
-/* UNIX version of getting and setting the data in reparse points */
+/* UNIX symlink <=> Windows reparse point translation  */
 #ifndef __WIN32__
 
 #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
 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;
        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);
 
                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;
 }
 
        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
  *
  * @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.
  * @buf
  *     Buffer into which to place the link target.
- *
  * @bufsize
  *     Available space in @buf, in bytes.
  * @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.
  *     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;
        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;
 
                return -EIO;
 
-       if (parse_reparse_data((const u8*)&rpbuf_disk, rpbuflen, &rpdata))
+       if (parse_link_reparse_point(&rpbuf, rpbuflen, &link))
                return -EINVAL;
 
                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;
                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
 }
 
 /* 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 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;
        int ret;
+       utf16lechar *target;
+       size_t target_nbytes;
+       struct link_reparse_point link;
+       struct reparse_buffer_disk rpbuf;
        u16 rpbuflen;
 
        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)
        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 {
        } 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)
        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,
        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))
                                        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;
 
        ret = 0;
-out_free_name:
-       FREE(name_utf16le);
-out:
+out_free_target:
+       FREE(target);
        return ret;
 }
 
        return ret;
 }
 
index dd2536296790f3e84006c6cc2976914dfc5e1ad4..dc6149131318ed8783d211833e87faf4bc5cf961 100644 (file)
@@ -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,
 
 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;
        struct blob_descriptor blob_override;
+       int ret;
 
        blob_set_is_located_in_attached_buffer(&blob_override,
 
        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;
        }
                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:
 retry_symlink:
-       if (symlink(link_target, path)) {
+       if (symlink(target, path)) {
                if (errno == EEXIST && !unlink(path))
                        goto retry_symlink;
                return WIMLIB_ERR_LINK;
                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;
                        /* 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);
 
                        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);
                        if (ret) {
                                ERROR_WITH_ERRNO("Can't create symbolic link "
                                                 "\"%s\"", path);
index 08fd31ef64e1eec3bc66f38ebbf77f5a57b291ac..4e7c036d648f5acce814834aa36a6812aad333fe 100644 (file)
@@ -204,32 +204,60 @@ unix_scan_directory(struct wim_dentry *dir_dentry,
        return ret;
 }
 
        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 *
 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;
 
 
        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++;
                        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++;
                        p++;
+               } while (*p && *p != '/');
 
 
-               /* Get inode and device for this prefix.  */
+               /* Get the inode and device numbers for this prefix.  */
                save = *p;
                *p = '\0';
                save = *p;
                *p = '\0';
-               ret = stat(dest, &stbuf);
+               ret = stat(target, &stbuf);
                *p = save;
 
                if (ret) {
                *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.  */
                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 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)
 {
 }
 
 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;
 
        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;
        }
                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.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;
        }
                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;
 
        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))
        struct stat stbuf;
        if (my_fstatat(full_path, dirfd, relpath, &stbuf, 0) == 0 &&
            S_ISDIR(stbuf.st_mode))
index 5e5ae7bc6731dc48bc5a0b31be896dc79adb562f..67798da3fd02f0deefc8591fd915f98509246a67 100644 (file)
@@ -37,6 +37,7 @@
 
 #include "wimlib/assert.h"
 #include "wimlib/blob_table.h"
 
 #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"
 #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));
 
                             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;
 
                in.hdr.rpdatalen = sizeof(in) - sizeof(in.hdr);
                in.hdr.rpreserved = 0;
 
index 9d627dbe274114853bad69faf45512d667f5bed0..7bacac9d50b42a742f92f39a6da386cf4fef7d1b 100644 (file)
@@ -1896,12 +1896,12 @@ begin_extract_blob_instance(const struct blob_descriptor *blob,
        return 0;
 }
 
        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
  * 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;
 {
        NTSTATUS status;
        HANDLE h;
@@ -1955,33 +1955,27 @@ skip_nt_toplevel_component(const wchar_t *path, size_t path_nchars)
                L"\\DosDevices\\",
                L"\\Device\\",
        };
                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]);
        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;
 }
 
        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.
  *
  * 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)
 {
 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;
 
        if (path == orig_path)
                return orig_path;
 
-       path = wmemchr(path, L'\\', (end - path));
-       if (!path)
-               return end;
-       do {
+       while (path != end && *path != L'\\')
                path++;
                path++;
-       } while (path != end && *path == L'\\');
+
        return path;
 }
 
 /*
        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
  */
 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;
        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;
 
        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;
                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;
                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.  */
 
 
        /* 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 -
                                           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);
 
 
        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];
 
        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
        /* 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);
 
        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
 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)
        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;
        }
                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);
                }
                        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);
                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) {
                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;
                        ret = check_apply_error(dentry, ctx, ret);
                        if (ret)
                                return ret;
index 9d8bbbcb15423925bf8b1d0be26b9410c34580b8..8f964bfa0fc31c1d6ca072795a776d02a7bfbfd3 100644 (file)
@@ -471,13 +471,7 @@ out_free_buf:
 }
 
 /* Reparse point fixup status code  */
 }
 
 /* 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)
 
 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 *
  */
 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;
 {
        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;
 
                        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);
 
                                goto out_close_root_dir;
                }
        } while (p != target_end);
 
-       p = NULL;
+       p = target;
 
 out_close_root_dir:
        if (attr.RootDirectory)
                (*func_NtClose)(attr.RootDirectory);
 
 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,
        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];
 
        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);
        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
 }
 
 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)
 {
                const wchar_t *path, struct capture_params *params)
 {
-       struct reparse_data rpdata;
+       struct link_reparse_point link;
        const wchar_t *rel_target;
        int ret;
 
        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
         */
         *     - \??\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
        }
 
        /* 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
         *
         * 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
         * 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;
                                   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.
  *
  * 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, 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
  * 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;
 {
        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))
        {
 
        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;
        }
                            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;
        }
 
                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
 }
 
 static DWORD WINAPI
@@ -1345,26 +1313,20 @@ retry_open:
                        WARNING("Ignoring reparse data of encrypted file \"%ls\"",
                                printable_path(full_path));
                } else {
                        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;
 
                        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;
                                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,
                        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))
                        {
                                                        rpbuflen - REPARSE_DATA_OFFSET,
                                                        params->blob_table))
                        {