* 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'. */
};
/*
- * Reparse tags documented at
- * http://msdn.microsoft.com/en-us/library/dd541667(v=prot.10).aspx
+ * The available reparse tags are documented at
+ * http://msdn.microsoft.com/en-us/library/dd541667(v=prot.10).aspx.
+ * Here we only define the ones of interest to us.
*/
-#define WIM_IO_REPARSE_TAG_RESERVED_ZERO 0x00000000
-#define WIM_IO_REPARSE_TAG_RESERVED_ONE 0x00000001
#define WIM_IO_REPARSE_TAG_MOUNT_POINT 0xA0000003
-#define WIM_IO_REPARSE_TAG_HSM 0xC0000004
-#define WIM_IO_REPARSE_TAG_HSM2 0x80000006
-#define WIM_IO_REPARSE_TAG_DRIVER_EXTENDER 0x80000005
-#define WIM_IO_REPARSE_TAG_SIS 0x80000007
-#define WIM_IO_REPARSE_TAG_DFS 0x8000000A
-#define WIM_IO_REPARSE_TAG_DFSR 0x80000012
-#define WIM_IO_REPARSE_TAG_FILTER_MANAGER 0x8000000B
#define WIM_IO_REPARSE_TAG_SYMLINK 0xA000000C
+#define WIM_IO_REPARSE_TAG_WOF 0x80000017
+
+/* Flags for the rp_flags field. Currently the only known flag is NOT_FIXED,
+ * which indicates that the target of the absolute symbolic link or junction was
+ * not changed when it was stored. */
+#define WIM_RP_FLAG_NOT_FIXED 0x0001
+/* Windows file attribute flags */
#define FILE_ATTRIBUTE_READONLY 0x00000001
#define FILE_ATTRIBUTE_HIDDEN 0x00000002
#define FILE_ATTRIBUTE_SYSTEM 0x00000004
#ifndef _WIMLIB_REPARSE_H
#define _WIMLIB_REPARSE_H
-#include <sys/types.h>
-
+#include "wimlib/inode.h" /* for reparse tag definitions */
#include "wimlib/types.h"
-struct wim_inode;
-struct blob_table;
struct blob_descriptor;
+struct blob_table;
-#define REPARSE_POINT_MAX_SIZE (16 * 1024)
+/* Windows enforces this limit on the size of a reparse point buffer. */
+#define REPARSE_POINT_MAX_SIZE 16384
-/* On-disk format of reparse point buffer */
+/*
+ * On-disk format of a reparse point buffer. See:
+ * https://msdn.microsoft.com/en-us/library/dd541671.aspx
+ *
+ * Note: we are not using _packed_attribute for this structure, so only cast to
+ * this if properly aligned!
+ */
struct reparse_buffer_disk {
le32 rptag;
le16 rpdatalen;
le16 substitute_name_nbytes;
le16 print_name_offset;
le16 print_name_nbytes;
- le32 rpflags;
- u8 data[REPARSE_POINT_MAX_SIZE - 20];
- } _packed_attribute symlink;
- struct {
- le16 substitute_name_offset;
- le16 substitute_name_nbytes;
- le16 print_name_offset;
- le16 print_name_nbytes;
- u8 data[REPARSE_POINT_MAX_SIZE - 16];
- } _packed_attribute junction;
+ union {
+ struct {
+ u8 data[REPARSE_POINT_MAX_SIZE - 16];
+ } junction;
+
+ struct {
+ le32 flags;
+ #define SYMBOLIC_LINK_RELATIVE 0x00000001
+ u8 data[REPARSE_POINT_MAX_SIZE - 20];
+ } symlink;
+ };
+ } link;
};
-} _packed_attribute;
+};
#define REPARSE_DATA_OFFSET (offsetof(struct reparse_buffer_disk, rpdata))
#define REPARSE_DATA_MAX_SIZE (REPARSE_POINT_MAX_SIZE - REPARSE_DATA_OFFSET)
+static inline void
+check_reparse_buffer_disk(void)
+{
+ BUILD_BUG_ON(offsetof(struct reparse_buffer_disk, rpdata) != 8);
+ BUILD_BUG_ON(offsetof(struct reparse_buffer_disk, link.junction.data) != 16);
+ BUILD_BUG_ON(offsetof(struct reparse_buffer_disk, link.symlink.data) != 20);
+ BUILD_BUG_ON(sizeof(struct reparse_buffer_disk) != REPARSE_POINT_MAX_SIZE);
+}
-/* Structured format for symbolic link, junction point, or mount point reparse
- * data. */
-struct reparse_data {
- /* Reparse point tag (see WIM_IO_REPARSE_TAG_* values) */
- u32 rptag;
-
- /* Length of reparse data, not including the 8-byte header (ReparseTag,
- * ReparseDataLength, ReparseReserved) */
- u16 rpdatalen;
+/* Wrapper around a symbolic link or junction reparse point
+ * (WIM_IO_REPARSE_TAG_SYMLINK or WIM_IO_REPARSE_TAG_MOUNT_POINT) */
+struct link_reparse_point {
- /* ReparseReserved */
+ u32 rptag;
u16 rpreserved;
- /* Flags (only for WIM_IO_REPARSE_TAG_SYMLINK reparse points).
- * SYMBOLIC_LINK_RELATIVE means this is a relative symbolic link;
- * otherwise should be set to 0. */
-#define SYMBOLIC_LINK_RELATIVE 0x00000001
- u32 rpflags;
+ /* Flags, valid for symbolic links only */
+ u32 symlink_flags;
- /* Pointer to the substitute name of the link (UTF-16LE). */
+ /* Pointers to the substitute name and print name of the link,
+ * potentially not null terminated */
utf16lechar *substitute_name;
-
- /* Pointer to the print name of the link (UTF-16LE). */
utf16lechar *print_name;
- /* Number of bytes of the substitute name, not including null terminator
- * if present */
- u16 substitute_name_nbytes;
-
- /* Number of bytes of the print name, not including null terminator if
- * present */
- u16 print_name_nbytes;
+ /* Lengths of the substitute and print names in bytes, not including
+ * their null terminators if present */
+ size_t substitute_name_nbytes;
+ size_t print_name_nbytes;
};
+static inline bool
+link_is_relative_symlink(const struct link_reparse_point *link)
+{
+ return link->rptag == WIM_IO_REPARSE_TAG_SYMLINK &&
+ (link->symlink_flags & SYMBOLIC_LINK_RELATIVE);
+}
+
+extern void
+complete_reparse_point(struct reparse_buffer_disk *rpbuf,
+ const struct wim_inode *inode, u16 blob_size);
+
extern int
-parse_reparse_data(const u8 * restrict rpbuf, u16 rpbuflen,
- struct reparse_data * restrict rpdata);
+parse_link_reparse_point(const struct reparse_buffer_disk *rpbuf, u16 rpbuflen,
+ struct link_reparse_point *link);
extern int
-make_reparse_buffer(const struct reparse_data * restrict rpdata,
- u8 * restrict rpbuf,
- u16 * restrict rpbuflen_ret);
+make_link_reparse_point(const struct link_reparse_point *link,
+ struct reparse_buffer_disk *rpbuf, u16 *rpbuflen_ret);
#ifndef __WIN32__
-ssize_t
-wim_inode_readlink(const struct wim_inode * restrict inode, char * restrict buf,
- size_t buf_len, const struct blob_descriptor *blob);
+extern int
+wim_inode_readlink(const struct wim_inode *inode, char *buf, size_t bufsize,
+ const struct blob_descriptor *blob,
+ const char *altroot, size_t altroot_len);
extern int
wim_inode_set_symlink(struct wim_inode *inode, const char *target,
*/
u8 default_hash[SHA1_HASH_SIZE];
- /* The format of the following data is not yet completely known and they
- * do not correspond to Microsoft's documentation.
+ /* Unknown field (maybe accidental padding) */
+ le32 unknown_0x54;
+
+ /*
+ * The following 8-byte union contains either information about the
+ * reparse point (for files with FILE_ATTRIBUTE_REPARSE_POINT set), or
+ * the "hard link group ID" (for other files).
+ *
+ * The reparse point information contains ReparseTag and ReparseReserved
+ * from the header of the reparse point buffer. It also contains a flag
+ * that indicates whether a reparse point fixup (for the target of an
+ * absolute symbolic link or junction) was done or not.
*
- * If this directory entry is for a reparse point (has
- * FILE_ATTRIBUTE_REPARSE_POINT set in the 'attributes' field), then the
- * version of the following fields containing the reparse tag is valid.
- * Furthermore, the field notated as not_rpfixed, as far as I can tell,
- * is supposed to be set to 1 if reparse point fixups (a.k.a. fixing the
- * targets of absolute symbolic links) were *not* done, and otherwise 0.
+ * The "hard link group ID" is like an inode number; all dentries for
+ * the same inode share the same value. See inode_fixup.c for more
+ * information.
*
- * If this directory entry is not for a reparse point, then the version
- * of the following fields containing the hard_link_group_id is valid.
- * All MS says about this field is that "If this file is part of a hard
- * link set, all the directory entries in the set will share the same
- * value in this field.". However, more specifically I have observed
- * the following:
- * - If the file is part of a hard link set of size 1, then the
- * hard_link_group_id should be set to either 0, which is treated
- * specially as indicating "not hardlinked", or any unique value.
- * - The specific nonzero values used to identity hard link sets do
- * not matter, as long as they are unique.
- * - However, due to bugs in Microsoft's software, it is actually NOT
- * guaranteed that directory entries that share the same hard link
- * group ID are actually hard linked to each either. See
- * inode_fixup.c for the code that handles this.
+ * Note that this union creates the limitation that reparse point files
+ * cannot have multiple names (hard links).
*/
union {
struct {
- le32 rp_unknown_1;
le32 reparse_tag;
- le16 rp_unknown_2;
- le16 not_rpfixed;
+ le16 rp_reserved;
+ le16 rp_flags;
} _packed_attribute reparse;
struct {
- le32 rp_unknown_1;
le64 hard_link_group_id;
} _packed_attribute nonreparse;
};
inode->i_creation_time = le64_to_cpu(disk_dentry->creation_time);
inode->i_last_access_time = le64_to_cpu(disk_dentry->last_access_time);
inode->i_last_write_time = le64_to_cpu(disk_dentry->last_write_time);
+ inode->i_unknown_0x54 = le32_to_cpu(disk_dentry->unknown_0x54);
- /* I don't know what's going on here. It seems like M$ screwed up the
- * reparse points, then put the fields in the same place and didn't
- * document it. So we have some fields we read for reparse points, and
- * some fields in the same place for non-reparse-points. */
if (inode->i_attributes & FILE_ATTRIBUTE_REPARSE_POINT) {
- inode->i_rp_unknown_1 = le32_to_cpu(disk_dentry->reparse.rp_unknown_1);
inode->i_reparse_tag = le32_to_cpu(disk_dentry->reparse.reparse_tag);
- inode->i_rp_unknown_2 = le16_to_cpu(disk_dentry->reparse.rp_unknown_2);
- inode->i_not_rpfixed = le16_to_cpu(disk_dentry->reparse.not_rpfixed);
+ inode->i_rp_reserved = le16_to_cpu(disk_dentry->reparse.rp_reserved);
+ inode->i_rp_flags = le16_to_cpu(disk_dentry->reparse.rp_flags);
/* Leave inode->i_ino at 0. Note: this means that WIM cannot
* represent multiple hard links to a reparse point file. */
} else {
- inode->i_rp_unknown_1 = le32_to_cpu(disk_dentry->nonreparse.rp_unknown_1);
inode->i_ino = le64_to_cpu(disk_dentry->nonreparse.hard_link_group_id);
}
disk_dentry->creation_time = cpu_to_le64(inode->i_creation_time);
disk_dentry->last_access_time = cpu_to_le64(inode->i_last_access_time);
disk_dentry->last_write_time = cpu_to_le64(inode->i_last_write_time);
+ disk_dentry->unknown_0x54 = cpu_to_le32(inode->i_unknown_0x54);
if (inode->i_attributes & FILE_ATTRIBUTE_REPARSE_POINT) {
- disk_dentry->reparse.rp_unknown_1 = cpu_to_le32(inode->i_rp_unknown_1);
disk_dentry->reparse.reparse_tag = cpu_to_le32(inode->i_reparse_tag);
- disk_dentry->reparse.rp_unknown_2 = cpu_to_le16(inode->i_rp_unknown_2);
- disk_dentry->reparse.not_rpfixed = cpu_to_le16(inode->i_not_rpfixed);
+ disk_dentry->reparse.rp_reserved = cpu_to_le16(inode->i_rp_reserved);
+ disk_dentry->reparse.rp_flags = cpu_to_le16(inode->i_rp_flags);
} else {
- disk_dentry->nonreparse.rp_unknown_1 = cpu_to_le32(inode->i_rp_unknown_1);
disk_dentry->nonreparse.hard_link_group_id =
cpu_to_le64((inode->i_nlink == 1) ? 0 : inode->i_ino);
}
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) {
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;
* The path at which to create the first link to the new file. If a file
* already exists at this path, -EEXIST is returned.
* @mode
- * The UNIX mode for the new file. This is only honored if
+ * The UNIX mode for the new file. This is only fully honored if
* WIMLIB_MOUNT_FLAG_UNIX_DATA was passed to wimlib_mount_image().
* @rdev
* The device ID for the new file, encoding the major and minor device
* numbers. This is only honored if WIMLIB_MOUNT_FLAG_UNIX_DATA was passed
* to wimlib_mount_image().
- * @attributes
- * Windows file attributes to use for the new file.
* @dentry_ret
* On success, a pointer to the new dentry is returned here. Its d_inode
* member will point to the new inode that was created for it and added to
*/
static int
create_file(struct fuse_context *fuse_ctx, const char *path,
- mode_t mode, dev_t rdev, u32 attributes,
- struct wim_dentry **dentry_ret)
+ mode_t mode, dev_t rdev, struct wim_dentry **dentry_ret)
{
struct wimfs_context *wimfs_ctx = WIMFS_CTX(fuse_ctx);
struct wim_dentry *parent;
const char *basename;
- struct wim_dentry *new_dentry;
- struct wim_inode *new_inode;
+ struct wim_dentry *dentry;
+ struct wim_inode *inode;
parent = get_parent_dentry(wimfs_ctx->wim, path, WIMLIB_CASE_SENSITIVE);
if (!parent)
if (get_dentry_child_with_name(parent, basename, WIMLIB_CASE_SENSITIVE))
return -EEXIST;
- if (new_dentry_with_new_inode(basename, true, &new_dentry))
+ if (new_dentry_with_new_inode(basename, true, &dentry))
return -ENOMEM;
- new_inode = new_dentry->d_inode;
+ inode = dentry->d_inode;
+
+ inode->i_ino = wimfs_ctx->next_ino++;
- new_inode->i_ino = wimfs_ctx->next_ino++;
- new_inode->i_attributes = attributes;
+ /* Note: we still use FILE_ATTRIBUTE_NORMAL for device nodes, named
+ * pipes, and sockets. The real mode is in the UNIX metadata. */
+ if (S_ISDIR(mode))
+ inode->i_attributes = FILE_ATTRIBUTE_DIRECTORY;
+ else
+ inode->i_attributes = FILE_ATTRIBUTE_NORMAL;
if (wimfs_ctx->mount_flags & WIMLIB_MOUNT_FLAG_UNIX_DATA) {
struct wimlib_unix_data unix_data;
unix_data.gid = fuse_ctx->gid;
unix_data.mode = fuse_mask_mode(mode, fuse_ctx);
unix_data.rdev = rdev;
- if (!inode_set_unix_data(new_inode, &unix_data, UNIX_DATA_ALL))
+ if (!inode_set_unix_data(inode, &unix_data, UNIX_DATA_ALL))
{
- free_dentry(new_dentry);
+ free_dentry(dentry);
return -ENOMEM;
}
}
- hlist_add_head(&new_inode->i_hlist,
+ hlist_add_head(&inode->i_hlist,
&wim_get_current_image_metadata(wimfs_ctx->wim)->inode_list);
- dentry_add_child(parent, new_dentry);
+ dentry_add_child(parent, dentry);
- *dentry_ret = new_dentry;
+ *dentry_ret = dentry;
return 0;
}
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);
!(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);
}
static int
-wimfs_readlink(const char *path, char *buf, size_t buf_len)
+wimfs_readlink(const char *path, char *buf, size_t bufsize)
{
- WIMStruct *wim = wimfs_get_WIMStruct();
+ struct wimfs_context *ctx = wimfs_get_context();
const struct wim_inode *inode;
- ssize_t ret;
+ int ret;
- inode = wim_pathname_to_inode(wim, path);
+ inode = wim_pathname_to_inode(ctx->wim, path);
if (!inode)
return -errno;
- if (!inode_is_symlink(inode))
- return -EINVAL;
- if (buf_len == 0)
+ if (bufsize <= 0)
return -EINVAL;
- ret = wim_inode_readlink(inode, buf, buf_len - 1, NULL);
- if (ret >= 0) {
- buf[ret] = '\0';
- ret = 0;
- } else if (ret == -ENAMETOOLONG) {
- buf[buf_len - 1] = '\0';
- }
- return ret;
+ ret = wim_inode_readlink(inode, buf, bufsize - 1, NULL,
+ ctx->mountpoint_abspath,
+ ctx->mountpoint_abspath_nchars);
+ if (ret < 0)
+ return ret;
+ buf[ret] = '\0';
+ return 0;
}
/* We use this for both release() and releasedir(), since in both cases we
struct wim_dentry *dentry;
int ret;
- ret = create_file(fuse_ctx, from, S_IFLNK | 0777, 0,
- FILE_ATTRIBUTE_REPARSE_POINT, &dentry);
+ ret = create_file(fuse_ctx, from, S_IFLNK | 0777, 0, &dentry);
if (ret)
return ret;
- dentry->d_inode->i_reparse_tag = WIM_IO_REPARSE_TAG_SYMLINK;
ret = wim_inode_set_symlink(dentry->d_inode, to,
wimfs_ctx->wim->blob_table);
if (ret) {
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;
/* 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);
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++;
for (u32 i = 0; i < ctx->num_reparse_inodes; i++) {
struct wim_inode *inode = ctx->wim_reparse_inodes[i];
- ctx->rpbuf.rptag = cpu_to_le32(inode->i_reparse_tag);
+ complete_reparse_point(&ctx->rpbuf, inode, blob->size);
if (ntfs_set_ntfs_reparse_data(ctx->ntfs_reparse_inodes[i],
(const char *)&ctx->rpbuf,
- blob->size + REPARSE_DATA_OFFSET,
+ REPARSE_DATA_OFFSET + blob->size,
0))
{
ERROR_WITH_ERRNO("Failed to set reparse "
* 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;
/*
- * 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
#include <errno.h>
#include "wimlib/alloca.h"
-#include "wimlib/assert.h"
#include "wimlib/blob_table.h"
-#include "wimlib/compiler.h"
#include "wimlib/endianness.h"
#include "wimlib/encoding.h"
#include "wimlib/error.h"
#include "wimlib/reparse.h"
#include "wimlib/resource.h"
-/*
- * Read the data from a symbolic link, junction, or mount point reparse point
- * buffer into a `struct reparse_data'.
- *
- * See http://msdn.microsoft.com/en-us/library/cc232006(v=prot.10).aspx for a
- * description of the format of the reparse point buffers.
- */
+/* Reconstruct the header of a reparse point buffer. This is necessary because
+ * only reparse data is stored in WIM files. The reparse tag is instead stored
+ * in the on-disk WIM dentry, and the reparse data length is equal to the size
+ * of the blob in which the reparse data was stored. */
+void
+complete_reparse_point(struct reparse_buffer_disk *rpbuf,
+ const struct wim_inode *inode, u16 blob_size)
+{
+ rpbuf->rptag = cpu_to_le32(inode->i_reparse_tag);
+ rpbuf->rpdatalen = cpu_to_le16(blob_size);
+ rpbuf->rpreserved = cpu_to_le16(inode->i_rp_reserved);
+}
+
+/* Parse the buffer for a symbolic link or junction reparse point and fill in a
+ * 'struct link_reparse_point'. */
int
-parse_reparse_data(const u8 * restrict rpbuf, u16 rpbuflen,
- struct reparse_data * restrict rpdata)
+parse_link_reparse_point(const struct reparse_buffer_disk *rpbuf, u16 rpbuflen,
+ struct link_reparse_point *link)
{
u16 substitute_name_offset;
u16 print_name_offset;
- const struct reparse_buffer_disk *rpbuf_disk =
- (const struct reparse_buffer_disk*)rpbuf;
const u8 *data;
- memset(rpdata, 0, sizeof(*rpdata));
- if (rpbuflen < 16)
- goto out_invalid;
- rpdata->rptag = le32_to_cpu(rpbuf_disk->rptag);
- wimlib_assert(rpdata->rptag == WIM_IO_REPARSE_TAG_SYMLINK ||
- rpdata->rptag == WIM_IO_REPARSE_TAG_MOUNT_POINT);
- rpdata->rpdatalen = le16_to_cpu(rpbuf_disk->rpdatalen);
- rpdata->rpreserved = le16_to_cpu(rpbuf_disk->rpreserved);
- substitute_name_offset = le16_to_cpu(rpbuf_disk->symlink.substitute_name_offset);
- rpdata->substitute_name_nbytes = le16_to_cpu(rpbuf_disk->symlink.substitute_name_nbytes);
- print_name_offset = le16_to_cpu(rpbuf_disk->symlink.print_name_offset);
- rpdata->print_name_nbytes = le16_to_cpu(rpbuf_disk->symlink.print_name_nbytes);
-
- if ((substitute_name_offset & 1) | (print_name_offset & 1) |
- (rpdata->substitute_name_nbytes & 1) | (rpdata->print_name_nbytes & 1))
- {
- /* Names would be unaligned... */
- goto out_invalid;
- }
+ link->rptag = le32_to_cpu(rpbuf->rptag);
- if (rpdata->rptag == WIM_IO_REPARSE_TAG_SYMLINK) {
- if (rpbuflen < 20)
- goto out_invalid;
- rpdata->rpflags = le32_to_cpu(rpbuf_disk->symlink.rpflags);
- data = rpbuf_disk->symlink.data;
+ /* Not a symbolic link or junction? */
+ if (link->rptag != WIM_IO_REPARSE_TAG_SYMLINK &&
+ link->rptag != WIM_IO_REPARSE_TAG_MOUNT_POINT)
+ return WIMLIB_ERR_INVALID_REPARSE_DATA;
+
+ /* Is the buffer too small to be a symlink or a junction? */
+ if (rpbuflen < offsetof(struct reparse_buffer_disk, link.junction.data))
+ return WIMLIB_ERR_INVALID_REPARSE_DATA;
+
+ link->rpreserved = le16_to_cpu(rpbuf->rpreserved);
+ link->substitute_name_nbytes = le16_to_cpu(rpbuf->link.substitute_name_nbytes);
+ substitute_name_offset = le16_to_cpu(rpbuf->link.substitute_name_offset);
+ link->print_name_nbytes = le16_to_cpu(rpbuf->link.print_name_nbytes);
+ print_name_offset = le16_to_cpu(rpbuf->link.print_name_offset);
+
+ /* The names must be properly sized and aligned. */
+ if ((substitute_name_offset | print_name_offset |
+ link->substitute_name_nbytes | link->print_name_nbytes) & 1)
+ return WIMLIB_ERR_INVALID_REPARSE_DATA;
+
+ if (link->rptag == WIM_IO_REPARSE_TAG_SYMLINK) {
+ if (rpbuflen < offsetof(struct reparse_buffer_disk, link.symlink.data))
+ return WIMLIB_ERR_INVALID_REPARSE_DATA;
+ link->symlink_flags = le32_to_cpu(rpbuf->link.symlink.flags);
+ data = rpbuf->link.symlink.data;
} else {
- data = rpbuf_disk->junction.data;
+ data = rpbuf->link.junction.data;
}
- if ((size_t)substitute_name_offset + rpdata->substitute_name_nbytes +
- (data - rpbuf) > rpbuflen)
- goto out_invalid;
- if ((size_t)print_name_offset + rpdata->print_name_nbytes +
- (data - rpbuf) > rpbuflen)
- goto out_invalid;
- rpdata->substitute_name = (utf16lechar*)&data[substitute_name_offset];
- rpdata->print_name = (utf16lechar*)&data[print_name_offset];
+
+ /* Verify that the names don't overflow the buffer. */
+ if ((data - (const u8 *)rpbuf) + substitute_name_offset +
+ link->substitute_name_nbytes > rpbuflen)
+ return WIMLIB_ERR_INVALID_REPARSE_DATA;
+
+ if ((data - (const u8 *)rpbuf) + print_name_offset +
+ link->print_name_nbytes > rpbuflen)
+ return WIMLIB_ERR_INVALID_REPARSE_DATA;
+
+ /* Save the name pointers. */
+ link->substitute_name = (utf16lechar *)&data[substitute_name_offset];
+ link->print_name = (utf16lechar *)&data[print_name_offset];
return 0;
-out_invalid:
- ERROR("Invalid reparse data");
- return WIMLIB_ERR_INVALID_REPARSE_DATA;
}
-/*
- * Create a reparse point data buffer.
- *
- * @rpdata: Structure that contains the data we need.
- *
- * @rpbuf: Buffer into which to write the reparse point data buffer. Must be
- * at least REPARSE_POINT_MAX_SIZE bytes long.
- */
+/* Translate a 'struct link_reparse_point' into a reparse point buffer. */
int
-make_reparse_buffer(const struct reparse_data * restrict rpdata,
- u8 * restrict rpbuf,
- u16 * restrict rpbuflen_ret)
+make_link_reparse_point(const struct link_reparse_point *link,
+ struct reparse_buffer_disk *rpbuf, u16 *rpbuflen_ret)
{
- struct reparse_buffer_disk *rpbuf_disk =
- (struct reparse_buffer_disk*)rpbuf;
u8 *data;
- if (rpdata->rptag == WIM_IO_REPARSE_TAG_SYMLINK)
- data = rpbuf_disk->symlink.data;
- else
- data = rpbuf_disk->junction.data;
+ if (link->rptag == WIM_IO_REPARSE_TAG_SYMLINK)
+ data = rpbuf->link.symlink.data;
+ else if (link->rptag == WIM_IO_REPARSE_TAG_MOUNT_POINT)
+ data = rpbuf->link.junction.data;
+ else /* Callers should forbid this case, but check anyway. */
+ return WIMLIB_ERR_INVALID_REPARSE_DATA;
- if ((data - rpbuf) + rpdata->substitute_name_nbytes +
- rpdata->print_name_nbytes +
+ /* Check if the names are too long to fit in a reparse point. */
+ if ((data - (u8 *)rpbuf) + link->substitute_name_nbytes +
+ link->print_name_nbytes +
2 * sizeof(utf16lechar) > REPARSE_POINT_MAX_SIZE)
- {
- ERROR("Reparse data is too long!");
return WIMLIB_ERR_INVALID_REPARSE_DATA;
- }
- rpbuf_disk->rptag = cpu_to_le32(rpdata->rptag);
- rpbuf_disk->rpreserved = cpu_to_le16(rpdata->rpreserved);
- rpbuf_disk->symlink.substitute_name_offset = cpu_to_le16(0);
- rpbuf_disk->symlink.substitute_name_nbytes = cpu_to_le16(rpdata->substitute_name_nbytes);
- rpbuf_disk->symlink.print_name_offset = cpu_to_le16(rpdata->substitute_name_nbytes + 2);
- rpbuf_disk->symlink.print_name_nbytes = cpu_to_le16(rpdata->print_name_nbytes);
-
- if (rpdata->rptag == WIM_IO_REPARSE_TAG_SYMLINK)
- rpbuf_disk->symlink.rpflags = cpu_to_le32(rpdata->rpflags);
-
- /* We null-terminate the substitute and print names, although this may
- * not be strictly necessary. Note that the byte counts should not
- * include the null terminators. */
- data = mempcpy(data, rpdata->substitute_name, rpdata->substitute_name_nbytes);
- *(utf16lechar*)data = cpu_to_le16(0);
- data += 2;
- data = mempcpy(data, rpdata->print_name, rpdata->print_name_nbytes);
- *(utf16lechar*)data = cpu_to_le16(0);
- data += 2;
- rpbuf_disk->rpdatalen = cpu_to_le16(data - rpbuf - REPARSE_DATA_OFFSET);
- *rpbuflen_ret = data - rpbuf;
+ rpbuf->rptag = cpu_to_le32(link->rptag);
+ rpbuf->rpreserved = cpu_to_le16(link->rpreserved);
+ rpbuf->link.substitute_name_offset = cpu_to_le16(0);
+ rpbuf->link.substitute_name_nbytes = cpu_to_le16(link->substitute_name_nbytes);
+ rpbuf->link.print_name_offset = cpu_to_le16(link->substitute_name_nbytes +
+ sizeof(utf16lechar));
+ rpbuf->link.print_name_nbytes = cpu_to_le16(link->print_name_nbytes);
+
+ if (link->rptag == WIM_IO_REPARSE_TAG_SYMLINK)
+ rpbuf->link.symlink.flags = cpu_to_le32(link->symlink_flags);
+
+ /* We null-terminate the substitute and print names, although this isn't
+ * strictly necessary. Note that the nbytes fields do not include the
+ * null terminators. */
+ data = mempcpy(data, link->substitute_name, link->substitute_name_nbytes);
+ *(utf16lechar *)data = cpu_to_le16(0);
+ data += sizeof(utf16lechar);
+ data = mempcpy(data, link->print_name, link->print_name_nbytes);
+ *(utf16lechar *)data = cpu_to_le16(0);
+ data += sizeof(utf16lechar);
+ rpbuf->rpdatalen = cpu_to_le16(data - rpbuf->rpdata);
+
+ *rpbuflen_ret = data - (u8 *)rpbuf;
return 0;
}
-/* UNIX version of getting and setting the data in reparse points */
+/* UNIX symlink <=> Windows reparse point translation */
#ifndef __WIN32__
-/*
- * Read the reparse data from a WIM inode that is a reparse point.
- *
- * @rpbuf points to a buffer at least REPARSE_POINT_MAX_SIZE bytes into which
- * the reparse point data buffer will be reconstructed.
- *
- * Note: in the WIM format, the first 8 bytes of the reparse point data buffer
- * are omitted, presumably because we already know the reparse tag from the
- * dentry, and we already know the reparse tag length from the blob length.
- * However, we reconstruct the first 8 bytes in the buffer returned by this
- * function.
- */
+/* Retrieve the inode's reparse point buffer into @rpbuf and @rpbuflen_ret.
+ * This gets the reparse data from @blob if specified, otherwise from the
+ * inode's reparse point stream. The inode's streams must be resolved. */
static int
-wim_inode_get_reparse_data(const struct wim_inode * restrict inode,
- u8 * restrict rpbuf,
- u16 * restrict rpbuflen_ret,
- const struct blob_descriptor *blob_override)
+wim_inode_get_reparse_point(const struct wim_inode *inode,
+ struct reparse_buffer_disk *rpbuf,
+ u16 *rpbuflen_ret,
+ const struct blob_descriptor *blob)
{
- const struct blob_descriptor *blob;
int ret;
- struct reparse_buffer_disk *rpbuf_disk;
- u16 rpdatalen;
-
- wimlib_assert(inode->i_attributes & FILE_ATTRIBUTE_REPARSE_POINT);
+ u16 blob_size = 0;
- if (blob_override) {
- blob = blob_override;
- } else {
- struct wim_inode_stream *strm;
+ if (!blob) {
+ const struct wim_inode_stream *strm;
strm = inode_get_unnamed_stream(inode, STREAM_TYPE_REPARSE_POINT);
if (strm)
blob = stream_blob_resolved(strm);
- else
- blob = NULL;
- if (!blob) {
- ERROR("Reparse point has no reparse data!");
- return WIMLIB_ERR_INVALID_REPARSE_DATA;
- }
}
- if (blob->size > REPARSE_DATA_MAX_SIZE) {
- ERROR("Reparse data is too long!");
- return WIMLIB_ERR_INVALID_REPARSE_DATA;
+ if (blob) {
+ if (blob->size > REPARSE_DATA_MAX_SIZE)
+ return WIMLIB_ERR_INVALID_REPARSE_DATA;
+ blob_size = blob->size;
+ ret = read_blob_into_buf(blob, rpbuf->rpdata);
+ if (ret)
+ return ret;
}
- rpdatalen = blob->size;
-
- /* Read the reparse data from blob */
- ret = read_blob_into_buf(blob, rpbuf + REPARSE_DATA_OFFSET);
- if (ret)
- return ret;
-
- /* Reconstruct the first 8 bytes of the reparse point buffer */
- rpbuf_disk = (struct reparse_buffer_disk*)rpbuf;
-
- /* ReparseTag */
- rpbuf_disk->rptag = cpu_to_le32(inode->i_reparse_tag);
- /* ReparseDataLength */
- rpbuf_disk->rpdatalen = cpu_to_le16(rpdatalen);
+ complete_reparse_point(rpbuf, inode, blob_size);
- /* ReparseReserved
- * XXX this could be one of the unknown fields in the WIM dentry. */
- rpbuf_disk->rpreserved = cpu_to_le16(0);
-
- *rpbuflen_ret = rpdatalen + REPARSE_DATA_OFFSET;
+ *rpbuflen_ret = REPARSE_DATA_OFFSET + blob_size;
return 0;
}
-static const utf16lechar volume_junction_prefix[11] = {
- cpu_to_le16('\\'),
- cpu_to_le16('?'),
- cpu_to_le16('?'),
- cpu_to_le16('\\'),
- cpu_to_le16('V'),
- cpu_to_le16('o'),
- cpu_to_le16('l'),
- cpu_to_le16('u'),
- cpu_to_le16('m'),
- cpu_to_le16('e'),
- cpu_to_le16('{'),
-};
-
-enum {
- SUBST_NAME_IS_RELATIVE_LINK = -1,
- SUBST_NAME_IS_VOLUME_JUNCTION = -2,
- SUBST_NAME_IS_UNKNOWN = -3,
-};
-
-/* Parse the "substitute name" (link target) from a symbolic link or junction
- * reparse point.
- *
- * Return value is:
- *
- * Non-negative integer:
- * The name is an absolute symbolic link in one of several formats,
- * and the return value is the number of UTF-16LE characters that need to
- * be advanced to reach a simple "absolute" path starting with a backslash
- * (i.e. skip over \??\ and/or drive letter)
- * Negative integer:
- * SUBST_NAME_IS_VOLUME_JUNCTION:
- * The name is a volume junction.
- * SUBST_NAME_IS_RELATIVE_LINK:
- * The name is a relative symbolic link.
- * SUBST_NAME_IS_UNKNOWN:
- * The name does not appear to be a valid symbolic link, junction,
- * or mount point.
- */
-static int
-parse_substitute_name(const utf16lechar *substitute_name,
- u16 substitute_name_nbytes, u32 rptag)
+static void
+copy(char **buf_p, size_t *bufsize_p, const char *src, size_t src_size)
{
- u16 substitute_name_nchars = substitute_name_nbytes / 2;
-
- if (substitute_name_nchars >= 7 &&
- substitute_name[0] == cpu_to_le16('\\') &&
- substitute_name[1] == cpu_to_le16('?') &&
- substitute_name[2] == cpu_to_le16('?') &&
- substitute_name[3] == cpu_to_le16('\\') &&
- substitute_name[4] != cpu_to_le16('\0') &&
- substitute_name[5] == cpu_to_le16(':') &&
- substitute_name[6] == cpu_to_le16('\\'))
- {
- /* "Full" symlink or junction (\??\x:\ prefixed path) */
- return 6;
- } else if (rptag == WIM_IO_REPARSE_TAG_MOUNT_POINT &&
- substitute_name_nchars >= 12 &&
- memcmp(substitute_name, volume_junction_prefix,
- sizeof(volume_junction_prefix)) == 0 &&
- substitute_name[substitute_name_nchars - 1] == cpu_to_le16('\\'))
- {
- /* Volume junction. Can't really do anything with it. */
- return SUBST_NAME_IS_VOLUME_JUNCTION;
- } else if (rptag == WIM_IO_REPARSE_TAG_SYMLINK &&
- substitute_name_nchars >= 3 &&
- substitute_name[0] != cpu_to_le16('\0') &&
- substitute_name[1] == cpu_to_le16(':') &&
- substitute_name[2] == cpu_to_le16('\\'))
- {
- /* "Absolute" symlink, with drive letter */
- return 2;
- } else if (rptag == WIM_IO_REPARSE_TAG_SYMLINK &&
- substitute_name_nchars >= 1)
- {
- if (substitute_name[0] == cpu_to_le16('\\'))
- /* "Absolute" symlink, without drive letter */
- return 0;
- else
- /* "Relative" symlink, without drive letter */
- return SUBST_NAME_IS_RELATIVE_LINK;
- } else {
- return SUBST_NAME_IS_UNKNOWN;
- }
+ size_t n = min(*bufsize_p, src_size);
+ memcpy(*buf_p, src, n);
+ *buf_p += n;
+ *bufsize_p -= n;
}
/*
- * Get the UNIX-style symlink target from the WIM inode for a reparse point.
- * Specifically, this translates the target from UTF-16 to the current multibyte
- * encoding, strips the drive prefix if present, and swaps backslashes and
- * forward slashes.
+ * Get a UNIX-style symlink target from the WIM inode for a reparse point.
*
* @inode
- * The inode to read the symlink from. It must be a reparse point with
- * tag WIM_IO_REPARSE_TAG_SYMLINK (a real symlink) or
- * WIM_IO_REPARSE_TAG_MOUNT_POINT (a mount point or junction point).
- *
+ * The inode from which to read the symlink. If not a symbolic link or
+ * junction reparse point, then -EINVAL will be returned.
* @buf
* Buffer into which to place the link target.
- *
* @bufsize
* Available space in @buf, in bytes.
- *
- * @blob_override
+ * @blob
* If not NULL, the blob from which to read the reparse data. Otherwise,
* the reparse data will be read from the reparse point stream of @inode.
+ * @altroot
+ * If @altroot_len != 0 and the link is an absolute link that was stored as
+ * "fixed", then prepend this path to the link target.
+ * @altroot_len
+ * Length of the @altroot string or 0.
*
- * If the entire symbolic link target was placed in the buffer, returns the
- * number of bytes written. The resulting string is not null-terminated. If
- * the symbolic link target was too large to be placed in the buffer, the first
- * @bufsize bytes of it are placed in the buffer and
- * -ENAMETOOLONG is returned. Otherwise, a negative errno value indicating
- * another error is returned.
+ * Similar to POSIX readlink(), this function writes as much of the symlink
+ * target as possible (up to @bufsize bytes) to @buf with no null terminator and
+ * returns the number of bytes written or a negative errno value on error. Note
+ * that the target is truncated and @bufsize is returned in the overflow case.
*/
-ssize_t
-wim_inode_readlink(const struct wim_inode * restrict inode,
- char * restrict buf, size_t bufsize,
- const struct blob_descriptor *blob_override)
+int
+wim_inode_readlink(const struct wim_inode *inode, char *buf, size_t bufsize,
+ const struct blob_descriptor *blob,
+ const char *altroot, size_t altroot_len)
{
- int ret;
- struct reparse_buffer_disk rpbuf_disk _aligned_attribute(8);
- struct reparse_data rpdata;
- char *link_target;
- char *translated_target;
- size_t link_target_len;
+ struct reparse_buffer_disk rpbuf;
u16 rpbuflen;
+ struct link_reparse_point link;
+ char *target_buffer;
+ char *target;
+ size_t target_len;
+ char *buf_ptr;
+ bool rpfix_ok = false;
+
+ /* Not a symbolic link or junction? */
+ if (!inode_is_symlink(inode))
+ return -EINVAL;
- wimlib_assert(inode_is_symlink(inode));
+ /* Retrieve the native Windows "substitute name". */
- if (wim_inode_get_reparse_data(inode, (u8*)&rpbuf_disk, &rpbuflen,
- blob_override))
+ if (wim_inode_get_reparse_point(inode, &rpbuf, &rpbuflen, blob))
return -EIO;
- if (parse_reparse_data((const u8*)&rpbuf_disk, rpbuflen, &rpdata))
+ if (parse_link_reparse_point(&rpbuf, rpbuflen, &link))
return -EINVAL;
- ret = utf16le_to_tstr(rpdata.substitute_name,
- rpdata.substitute_name_nbytes,
- &link_target, &link_target_len);
- if (ret)
+ /* Translate the substitute name to the current multibyte encoding. */
+ if (utf16le_to_tstr(link.substitute_name, link.substitute_name_nbytes,
+ &target_buffer, &target_len))
return -errno;
+ target = target_buffer;
+
+ /*
+ * The substitute name is a native Windows NT path. There are two cases:
+ *
+ * 1. The reparse point is a symlink (rptag=WIM_IO_REPARSE_TAG_SYMLINK)
+ * and SYMBOLIC_LINK_RELATIVE is set. Windows resolves the path
+ * relative to the directory containing the reparse point file. In
+ * this case, we just translate the path separators.
+ * 2. Otherwise, Windows resolves the path from the root of the Windows
+ * NT kernel object namespace. In this case, we attempt to strip the
+ * device name, in addition to translating the path separators; e.g.
+ * "\??\C:\Users\Public" is translated to "/Users/Public".
+ *
+ * Also in case (2) the link target may have been stored as "fixed",
+ * meaning that with the device portion stripped off it is effectively
+ * "relative to the root of the WIM image". If this is the case, and if
+ * the caller provided an alternate root directory, then rewrite the
+ * link to be relative to that directory.
+ */
+ if (!link_is_relative_symlink(&link)) {
+ static const char *const nt_root_dirs[] = {
+ "\\??\\", "\\DosDevices\\", "\\Device\\",
+ };
+ for (size_t i = 0; i < ARRAY_LEN(nt_root_dirs); i++) {
+ size_t len = strlen(nt_root_dirs[i]);
+ if (!strncmp(target, nt_root_dirs[i], len)) {
+ char *p = target + len;
+ while (*p == '\\')
+ p++;
+ while (*p && *p != '\\')
+ p++;
+ target_len -= (p - target);
+ target = p;
+ break;
+ }
+ }
- translated_target = link_target;
- ret = parse_substitute_name(rpdata.substitute_name,
- rpdata.substitute_name_nbytes,
- rpdata.rptag);
- switch (ret) {
- case SUBST_NAME_IS_RELATIVE_LINK:
- goto out_translate_slashes;
- case SUBST_NAME_IS_VOLUME_JUNCTION:
- goto out_have_link;
- case SUBST_NAME_IS_UNKNOWN:
- ERROR("Can't understand reparse point "
- "substitute name \"%s\"", link_target);
- ret = -EIO;
- goto out_free_link_target;
- default:
- translated_target += ret;
- link_target_len -= ret;
- break;
+ if (!(inode->i_rp_flags & WIM_RP_FLAG_NOT_FIXED))
+ rpfix_ok = true;
}
-out_translate_slashes:
- for (size_t i = 0; i < link_target_len; i++) {
- if (translated_target[i] == '\\')
- translated_target[i] = '/';
- else if (translated_target[i] == '/')
- translated_target[i] = '\\';
+ /* Translate backslashes (Windows NT path separator) to forward slashes
+ * (UNIX path separator). In addition, translate forwards slashes to
+ * backslashes; this enables lossless handling of UNIX symbolic link
+ * targets that contain the backslash character. */
+ for (char *p = target; *p; p++) {
+ if (*p == '\\')
+ *p = '/';
+ else if (*p == '/')
+ *p = '\\';
}
-out_have_link:
- if (link_target_len > bufsize) {
- link_target_len = bufsize;
- ret = -ENAMETOOLONG;
- } else {
- ret = link_target_len;
+
+ /* Copy as much of the link target as possible to the output buffer and
+ * return the number of bytes copied. */
+ buf_ptr = buf;
+ if (rpfix_ok && altroot_len != 0) {
+ copy(&buf_ptr, &bufsize, altroot, altroot_len);
+ } else if (target_len == 0) {
+ /* An absolute link target that was made relative to the same
+ * directory pointed to will end up empty if the original target
+ * did not have a trailing slash. Here, we are reading this
+ * adjusted link target without prefixing it. This usually
+ * doesn't happen, but if it does then we need to change it to
+ * "/" so that it is a valid target. */
+ target = "/";
+ target_len = 1;
}
- memcpy(buf, translated_target, link_target_len);
-out_free_link_target:
- FREE(link_target);
- return ret;
+ copy(&buf_ptr, &bufsize, target, target_len);
+ FREE(target_buffer);
+ return buf_ptr - buf;
}
/* Given a UNIX-style symbolic link target, create a Windows-style reparse point
* buffer and assign it to the specified inode. */
int
-wim_inode_set_symlink(struct wim_inode *inode, const char *target,
+wim_inode_set_symlink(struct wim_inode *inode, const char *_target,
struct blob_table *blob_table)
{
- struct reparse_buffer_disk rpbuf_disk _aligned_attribute(8);
- struct reparse_data rpdata;
- static const char abs_subst_name_prefix[12] = "\\\0?\0?\0\\\0C\0:\0";
- static const char abs_print_name_prefix[4] = "C\0:\0";
- utf16lechar *name_utf16le;
- size_t name_utf16le_nbytes;
int ret;
+ utf16lechar *target;
+ size_t target_nbytes;
+ struct link_reparse_point link;
+ struct reparse_buffer_disk rpbuf;
u16 rpbuflen;
- DEBUG("Creating reparse point data buffer for UNIX "
- "symlink target \"%s\"", target);
- memset(&rpdata, 0, sizeof(rpdata));
- ret = tstr_to_utf16le(target, strlen(target),
- &name_utf16le, &name_utf16le_nbytes);
+ /* Translate the link target to UTF-16LE. */
+ ret = tstr_to_utf16le(_target, strlen(_target), &target, &target_nbytes);
if (ret)
- goto out;
+ return ret;
- for (size_t i = 0; i < name_utf16le_nbytes / 2; i++) {
- if (name_utf16le[i] == cpu_to_le16('/'))
- name_utf16le[i] = cpu_to_le16('\\');
- else if (name_utf16le[i] == cpu_to_le16('\\'))
- name_utf16le[i] = cpu_to_le16('/');
+ /* Translate forward slashes (UNIX path separator) to backslashes
+ * (Windows NT path separator). In addition, translate backslashes to
+ * forward slashes; this enables lossless handling of UNIX symbolic link
+ * targets that contain the backslash character. */
+ for (utf16lechar *p = target; *p; p++) {
+ if (*p == cpu_to_le16('/'))
+ *p = cpu_to_le16('\\');
+ else if (*p == cpu_to_le16('\\'))
+ *p = cpu_to_le16('/');
}
- /* Compatability notes:
- *
- * On UNIX, an absolute symbolic link begins with '/'; everything else
- * is a relative symbolic link. (Quite simple compared to the various
- * ways to provide Windows paths.)
- *
- * To change a UNIX relative symbolic link to Windows format, we need to
- * translate it to UTF-16LE, swap forward slashes and backslashes, and
- * set 'rpflags' to SYMBOLIC_LINK_RELATIVE.
- *
- * For UNIX absolute symbolic links, we must set the @flags field to 0.
- * Then, there are multiple options as to actually represent the
- * absolute link targets:
- *
- * (1) An absolute path beginning with one backslash character. similar
- * to UNIX-style, just with a different path separator. Print name same
- * as substitute name.
- *
- * (2) Absolute path beginning with drive letter followed by a
- * backslash. Print name same as substitute name.
- *
- * (3) Absolute path beginning with drive letter followed by a
- * backslash; substitute name prefixed with \??\, otherwise same as
- * print name.
- *
- * We choose option (3) here, and we just assume C: for the drive
- * letter. The reasoning for this is:
- *
- * (1) Microsoft imagex.exe has a bug where it does not attempt to do
- * reparse point fixups for these links, even though they are valid
- * absolute links. (Note: in this case prefixing the substitute name
- * with \??\ does not work; it just makes the data unable to be restored
- * at all.)
- * (2) Microsoft imagex.exe will fail when doing reparse point fixups
- * for these. It apparently contains a bug that causes it to create an
- * invalid reparse point, which then cannot be restored.
- * (3) This is the only option I tested for which reparse point fixups
- * worked properly in Microsoft imagex.exe.
- *
- * So option (3) it is.
- */
-
- rpdata.rptag = inode->i_reparse_tag;
- if (target[0] == '/') {
- rpdata.substitute_name_nbytes = name_utf16le_nbytes +
- sizeof(abs_subst_name_prefix);
- rpdata.print_name_nbytes = name_utf16le_nbytes +
- sizeof(abs_print_name_prefix);
- rpdata.substitute_name = alloca(rpdata.substitute_name_nbytes);
- rpdata.print_name = alloca(rpdata.print_name_nbytes);
- memcpy(rpdata.substitute_name, abs_subst_name_prefix,
- sizeof(abs_subst_name_prefix));
- memcpy(rpdata.print_name, abs_print_name_prefix,
- sizeof(abs_print_name_prefix));
- memcpy((void*)rpdata.substitute_name + sizeof(abs_subst_name_prefix),
- name_utf16le, name_utf16le_nbytes);
- memcpy((void*)rpdata.print_name + sizeof(abs_print_name_prefix),
- name_utf16le, name_utf16le_nbytes);
+ link.rptag = WIM_IO_REPARSE_TAG_SYMLINK;
+ link.rpreserved = 0;
+
+ /* Note: an absolute link that was rewritten to be relative to another
+ * directory is assumed to either be empty or to have a leading slash.
+ * See unix_relativize_link_target(). */
+ if (*target == cpu_to_le16('\\') || !*target) {
+ /*
+ * UNIX link target was absolute. In this case we represent the
+ * link as a symlink reparse point with SYMBOLIC_LINK_RELATIVE
+ * cleared. For this to work we need to assign it a path that
+ * can be resolved from the root of the Windows NT kernel object
+ * namespace. We do this by using "\??\C:" as a dummy prefix.
+ *
+ * Note that we could instead represent UNIX absolute links by
+ * setting SYMBOLIC_LINK_RELATIVE and then leaving the path
+ * backslash-prefixed like "\Users\Public". On Windows this is
+ * valid and denotes a path relative to the root of the
+ * filesystem on which the reparse point resides. The problem
+ * with this is that neither WIMGAPI nor wimlib (on Windows)
+ * will do "reparse point fixups" when extracting such links
+ * (modifying the link target to point into the actual
+ * extraction directory). So for the greatest cross-platform
+ * consistency, we have to use the fake C: drive approach.
+ */
+ static const utf16lechar prefix[6] = {
+ cpu_to_le16('\\'),
+ cpu_to_le16('?'),
+ cpu_to_le16('?'),
+ cpu_to_le16('\\'),
+ cpu_to_le16('C'),
+ cpu_to_le16(':'),
+ };
+
+ /* Do not show \??\ in print name */
+ const size_t num_unprintable_chars = 4;
+
+ link.symlink_flags = 0;
+ link.substitute_name_nbytes = sizeof(prefix) + target_nbytes;
+ link.substitute_name = alloca(link.substitute_name_nbytes);
+ memcpy(link.substitute_name, prefix, sizeof(prefix));
+ memcpy(link.substitute_name + ARRAY_LEN(prefix), target, target_nbytes);
+ link.print_name_nbytes = link.substitute_name_nbytes -
+ (num_unprintable_chars * sizeof(utf16lechar));
+ link.print_name = link.substitute_name + num_unprintable_chars;
} else {
- rpdata.substitute_name_nbytes = name_utf16le_nbytes;
- rpdata.print_name_nbytes = name_utf16le_nbytes;
- rpdata.substitute_name = name_utf16le;
- rpdata.print_name = name_utf16le;
- rpdata.rpflags = SYMBOLIC_LINK_RELATIVE;
+ /* UNIX link target was relative. In this case we represent the
+ * link as a symlink reparse point with SYMBOLIC_LINK_RELATIVE
+ * set. This causes Windows to interpret the link relative to
+ * the directory containing the reparse point file. */
+ link.symlink_flags = SYMBOLIC_LINK_RELATIVE;
+ link.substitute_name_nbytes = target_nbytes;
+ link.substitute_name = target;
+ link.print_name_nbytes = target_nbytes;
+ link.print_name = target;
}
- ret = make_reparse_buffer(&rpdata, (u8*)&rpbuf_disk, &rpbuflen);
+ /* Generate the reparse buffer. */
+ ret = make_link_reparse_point(&link, &rpbuf, &rpbuflen);
if (ret)
- goto out_free_name;
+ goto out_free_target;
+ /* Save the reparse data with the inode. */
ret = WIMLIB_ERR_NOMEM;
if (!inode_add_stream_with_data(inode,
STREAM_TYPE_REPARSE_POINT,
NO_STREAM_NAME,
- (u8*)&rpbuf_disk + REPARSE_DATA_OFFSET,
+ rpbuf.rpdata,
rpbuflen - REPARSE_DATA_OFFSET,
blob_table))
- goto out_free_name;
+ goto out_free_target;
+
+ /* The inode is now a reparse point. */
+ inode->i_reparse_tag = link.rptag;
+ inode->i_attributes &= ~FILE_ATTRIBUTE_NORMAL;
+ inode->i_attributes |= FILE_ATTRIBUTE_REPARSE_POINT;
ret = 0;
-out_free_name:
- FREE(name_utf16le);
-out:
+out_free_target:
+ FREE(target);
return ret;
}
static int
unix_create_symlink(const struct wim_inode *inode, const char *path,
- const u8 *rpdata, u16 rpdatalen, bool rpfix,
- const char *apply_dir, size_t apply_dir_nchars)
+ size_t rpdatalen, struct unix_apply_ctx *ctx)
{
- char link_target[REPARSE_DATA_MAX_SIZE];
- int ret;
+ char target[REPARSE_POINT_MAX_SIZE];
struct blob_descriptor blob_override;
+ int ret;
blob_set_is_located_in_attached_buffer(&blob_override,
- (void *)rpdata, rpdatalen);
+ ctx->reparse_data, rpdatalen);
- ret = wim_inode_readlink(inode, link_target,
- sizeof(link_target) - 1, &blob_override);
- if (ret < 0) {
+ ret = wim_inode_readlink(inode, target, sizeof(target) - 1,
+ &blob_override,
+ ctx->target_abspath,
+ ctx->target_abspath_nchars);
+ if (unlikely(ret < 0)) {
errno = -ret;
return WIMLIB_ERR_READLINK;
}
+ target[ret] = '\0';
- link_target[ret] = 0;
-
- if (rpfix && link_target[0] == '/') {
-
- /* "Fix" the absolute symbolic link by prepending the absolute
- * path to the target directory. */
-
- if (sizeof(link_target) - (ret + 1) < apply_dir_nchars) {
- errno = ENAMETOOLONG;
- return WIMLIB_ERR_REPARSE_POINT_FIXUP_FAILED;
- }
- memmove(link_target + apply_dir_nchars, link_target,
- ret + 1);
- memcpy(link_target, apply_dir, apply_dir_nchars);
- }
retry_symlink:
- if (symlink(link_target, path)) {
+ if (symlink(target, path)) {
if (errno == EEXIST && !unlink(path))
goto retry_symlink;
return WIMLIB_ERR_LINK;
/* We finally have the symlink data, so we can create
* the symlink. */
const char *path;
- bool rpfix;
-
- rpfix = (ctx->common.extract_flags &
- WIMLIB_EXTRACT_FLAG_RPFIX) &&
- !inode->i_not_rpfixed;
path = unix_build_inode_extraction_path(inode, ctx);
- ret = unix_create_symlink(inode, path,
- ctx->reparse_data,
- blob->size,
- rpfix,
- ctx->target_abspath,
- ctx->target_abspath_nchars);
+ ret = unix_create_symlink(inode, path, blob->size, ctx);
if (ret) {
ERROR_WITH_ERRNO("Can't create symbolic link "
"\"%s\"", path);
return ret;
}
-/* Given an absolute symbolic link target @dest (UNIX-style, beginning
- * with '/'), determine whether it points into the directory specified by
- * @ino and @dev. If so, return the target modified to be "absolute"
- * relative to this directory. Otherwise, return NULL. */
+/*
+ * Given an absolute symbolic link target (UNIX-style, beginning with '/'),
+ * determine whether it points into the directory identified by @ino and @dev.
+ * If yes, return the suffix of @target which is relative to this directory, but
+ * retaining leading slashes. If no, return @target.
+ *
+ * Here are some examples, assuming that the @ino/@dev directory is "/home/e":
+ *
+ * Original target New target
+ * --------------- ----------
+ * /home/e/test /test
+ * /home/e/test/ /test/
+ * //home//e//test// //test//
+ * /home/e (empty string)
+ * /home/e/ /
+ * /usr/lib /usr/lib (external link)
+ *
+ * Because of the possibility of other links into the @ino/@dev directory and/or
+ * multiple path separators, we can't simply do a string comparison; instead we
+ * need to stat() each ancestor directory.
+ *
+ * If the link points directly to the @ino/@dev directory with no trailing
+ * slashes, then the new target will be an empty string. This is not a valid
+ * UNIX symlink target, but we store this in the archive anyway since the target
+ * is intended to be de-relativized when the link is extracted.
+ */
static char *
-unix_fixup_abslink(char *dest, u64 ino, u64 dev)
+unix_relativize_link_target(char *target, u64 ino, u64 dev)
{
- char *p = dest;
+ char *p = target;
do {
char save;
struct stat stbuf;
int ret;
- /* Skip non-slashes. */
- while (*p && *p != '/')
+ /* Skip slashes (guaranteed to be at least one here) */
+ do {
p++;
+ } while (*p == '/');
+
+ /* End of string? */
+ if (!*p)
+ break;
- /* Skip slashes. */
- while (*p && *p == '/')
+ /* Skip non-slashes (guaranteed to be at least one here) */
+ do {
p++;
+ } while (*p && *p != '/');
- /* Get inode and device for this prefix. */
+ /* Get the inode and device numbers for this prefix. */
save = *p;
*p = '\0';
- ret = stat(dest, &stbuf);
+ ret = stat(target, &stbuf);
*p = save;
if (ret) {
if (stbuf.st_ino == ino && stbuf.st_dev == dev) {
/* Link points inside directory tree being captured.
* Return abbreviated path. */
- *--p = '/';
- while (p > dest && *(p - 1) == '/')
- p--;
return p;
}
} while (*p);
/* Link does not point inside directory tree being captured. */
- return NULL;
+ return target;
}
static int
unix_scan_symlink(const char *full_path, int dirfd, const char *relpath,
struct wim_inode *inode, struct capture_params *params)
{
- char deref_name_buf[4096];
- ssize_t deref_name_len;
- char *dest;
+ char orig_target[REPARSE_POINT_MAX_SIZE];
+ char *target = orig_target;
int ret;
- inode->i_attributes = FILE_ATTRIBUTE_REPARSE_POINT;
- inode->i_reparse_tag = WIM_IO_REPARSE_TAG_SYMLINK;
-
- /* The idea here is to call readlink() to get the UNIX target of the
- * symbolic link, then turn the target into a reparse point data buffer
- * that contains a relative or absolute symbolic link. */
- deref_name_len = my_readlinkat(full_path, dirfd, relpath,
- deref_name_buf, sizeof(deref_name_buf) - 1);
- if (deref_name_len < 0) {
+ /* Read the UNIX symbolic link target. */
+ ret = my_readlinkat(full_path, dirfd, relpath, target,
+ sizeof(orig_target));
+ if (unlikely(ret < 0)) {
ERROR_WITH_ERRNO("\"%s\": Can't read target of symbolic link",
full_path);
return WIMLIB_ERR_READLINK;
}
+ if (unlikely(ret >= sizeof(orig_target))) {
+ ERROR("\"%s\": target of symbolic link is too long", full_path);
+ return WIMLIB_ERR_READLINK;
+ }
+ target[ret] = '\0';
- dest = deref_name_buf;
-
- dest[deref_name_len] = '\0';
-
- if ((params->add_flags & WIMLIB_ADD_FLAG_RPFIX) &&
- dest[0] == '/')
- {
- char *fixed_dest;
+ /* If the link is absolute and reparse point fixups are enabled, then
+ * change it to be "absolute" relative to the tree being captured. */
+ if (target[0] == '/' && (params->add_flags & WIMLIB_ADD_FLAG_RPFIX)) {
+ int status = WIMLIB_SCAN_DENTRY_NOT_FIXED_SYMLINK;
- /* RPFIX (reparse point fixup) mode: Change target of absolute
- * symbolic link to be "absolute" relative to the tree being
- * captured. */
- fixed_dest = unix_fixup_abslink(dest,
- params->capture_root_ino,
- params->capture_root_dev);
params->progress.scan.cur_path = full_path;
- params->progress.scan.symlink_target = deref_name_buf;
- if (fixed_dest) {
- /* Link points inside the tree being captured, so it was
- * fixed. */
- inode->i_not_rpfixed = 0;
- dest = fixed_dest;
- ret = do_capture_progress(params,
- WIMLIB_SCAN_DENTRY_FIXED_SYMLINK,
- NULL);
- } else {
- /* Link points outside the tree being captured, so it
- * was not fixed. */
- ret = do_capture_progress(params,
- WIMLIB_SCAN_DENTRY_NOT_FIXED_SYMLINK,
- NULL);
+ params->progress.scan.symlink_target = target;
+
+ target = unix_relativize_link_target(target,
+ params->capture_root_ino,
+ params->capture_root_dev);
+ if (target != orig_target) {
+ /* Link target was fixed. */
+ inode->i_rp_flags &= ~WIM_RP_FLAG_NOT_FIXED;
+ status = WIMLIB_SCAN_DENTRY_FIXED_SYMLINK;
}
+ ret = do_capture_progress(params, status, NULL);
if (ret)
return ret;
}
- ret = wim_inode_set_symlink(inode, dest, params->blob_table);
+
+ /* Translate the UNIX symlink target into a Windows reparse point. */
+ ret = wim_inode_set_symlink(inode, target, params->blob_table);
if (ret)
return ret;
- /* Unfortunately, Windows seems to have the concept of "file" symbolic
- * links as being different from "directory" symbolic links... so
- * FILE_ATTRIBUTE_DIRECTORY needs to be set on the symbolic link if the
- * *target* of the symbolic link is a directory. */
+ /* On Windows, a reparse point can be set on both directory and
+ * non-directory files. Usually, a link that is intended to point to a
+ * (non-)directory is stored as a reparse point on a (non-)directory
+ * file. Replicate this behavior by examining the target file. */
struct stat stbuf;
if (my_fstatat(full_path, dirfd, relpath, &stbuf, 0) == 0 &&
S_ISDIR(stbuf.st_mode))
#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"
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;
return 0;
}
-/* Set the reparse data @rpbuf of length @rpbuflen on the extracted file
+/* Set the reparse point @rpbuf of length @rpbuflen on the extracted file
* corresponding to the WIM dentry @dentry. */
static int
-do_set_reparse_data(const struct wim_dentry *dentry,
- const void *rpbuf, u16 rpbuflen,
- struct win32_apply_ctx *ctx)
+do_set_reparse_point(const struct wim_dentry *dentry,
+ const struct reparse_buffer_disk *rpbuf, u16 rpbuflen,
+ struct win32_apply_ctx *ctx)
{
NTSTATUS status;
HANDLE h;
L"\\DosDevices\\",
L"\\Device\\",
};
- size_t first_dir_len = 0;
const wchar_t * const end = path + path_nchars;
for (size_t i = 0; i < ARRAY_LEN(dirs); i++) {
size_t len = wcslen(dirs[i]);
- if (len <= (end - path) && !wcsnicmp(path, dirs[i], len)) {
- first_dir_len = len;
- break;
+ if (len <= (end - path) && !wmemcmp(path, dirs[i], len)) {
+ path += len;
+ while (path != end && *path == L'\\')
+ path++;
+ return path;
}
}
- if (first_dir_len == 0)
- return path;
- path += first_dir_len;
- while (path != end && *path == L'\\')
- path++;
return path;
}
-/* Given a Windows NT namespace path, such as \??\e:\Windows\System32, return a
- * pointer to the suffix of the path that is device-relative, such as
- * Windows\System32.
+/*
+ * Given a Windows NT namespace path, such as \??\e:\Windows\System32, return a
+ * pointer to the suffix of the path that is device-relative but possibly with
+ * leading slashes, such as \Windows\System32.
*
* The path has an explicit length and is not necessarily null terminated.
- *
- * If the path just something like \??\e: then the returned pointer will point
- * just past the colon. In this case the length of the result will be 0
- * characters. */
+ */
static const wchar_t *
get_device_relative_path(const wchar_t *path, size_t path_nchars)
{
if (path == orig_path)
return orig_path;
- path = wmemchr(path, L'\\', (end - path));
- if (!path)
- return end;
- do {
+ while (path != end && *path != L'\\')
path++;
- } while (path != end && *path == L'\\');
+
return path;
}
/*
- * Given a reparse point buffer for a symbolic link or junction, adjust its
- * contents so that the target of the link is consistent with the new location
- * of the files.
+ * Given a reparse point buffer for an inode for which the absolute link target
+ * was relativized when it was archived, de-relative the link target to be
+ * consistent with the actual extraction location.
*/
static void
-try_rpfix(u8 *rpbuf, u16 *rpbuflen_p, struct win32_apply_ctx *ctx)
+try_rpfix(struct reparse_buffer_disk *rpbuf, u16 *rpbuflen_p,
+ struct win32_apply_ctx *ctx)
{
- struct reparse_data rpdata;
+ struct link_reparse_point link;
size_t orig_subst_name_nchars;
const wchar_t *relpath;
size_t relpath_nchars;
const wchar_t *fixed_print_name;
size_t fixed_print_name_nchars;
- if (parse_reparse_data(rpbuf, *rpbuflen_p, &rpdata)) {
- /* Do nothing if the reparse data is invalid. */
+ /* Do nothing if the reparse data is invalid. */
+ if (parse_link_reparse_point(rpbuf, *rpbuflen_p, &link))
return;
- }
- if (rpdata.rptag == WIM_IO_REPARSE_TAG_SYMLINK &&
- (rpdata.rpflags & SYMBOLIC_LINK_RELATIVE))
- {
- /* Do nothing if it's a relative symbolic link. */
+ /* Do nothing if the reparse point is a relative symbolic link. */
+ if (link_is_relative_symlink(&link))
return;
- }
/* Build the new substitute name from the NT namespace path to the
* target directory, then a path separator, then the "device relative"
* part of the old substitute name. */
- orig_subst_name_nchars = rpdata.substitute_name_nbytes / sizeof(wchar_t);
+ orig_subst_name_nchars = link.substitute_name_nbytes / sizeof(wchar_t);
- relpath = get_device_relative_path(rpdata.substitute_name,
+ relpath = get_device_relative_path(link.substitute_name,
orig_subst_name_nchars);
relpath_nchars = orig_subst_name_nchars -
- (relpath - rpdata.substitute_name);
+ (relpath - link.substitute_name);
target_ntpath_nchars = ctx->target_ntpath.Length / sizeof(wchar_t);
- fixed_subst_name_nchars = target_ntpath_nchars;
- if (relpath_nchars)
- fixed_subst_name_nchars += 1 + relpath_nchars;
+ fixed_subst_name_nchars = target_ntpath_nchars + relpath_nchars;
+
wchar_t fixed_subst_name[fixed_subst_name_nchars];
- wmemcpy(fixed_subst_name, ctx->target_ntpath.Buffer,
- target_ntpath_nchars);
- if (relpath_nchars) {
- fixed_subst_name[target_ntpath_nchars] = L'\\';
- wmemcpy(&fixed_subst_name[target_ntpath_nchars + 1],
- relpath, relpath_nchars);
- }
+ wmemcpy(fixed_subst_name, ctx->target_ntpath.Buffer, target_ntpath_nchars);
+ wmemcpy(&fixed_subst_name[target_ntpath_nchars], relpath, relpath_nchars);
/* Doesn't need to be null-terminated. */
/* Print name should be Win32, but not all NT names can even be
fixed_print_name_nchars = fixed_subst_name_nchars - (fixed_print_name -
fixed_subst_name);
- rpdata.substitute_name = fixed_subst_name;
- rpdata.substitute_name_nbytes = fixed_subst_name_nchars * sizeof(wchar_t);
- rpdata.print_name = (wchar_t *)fixed_print_name;
- rpdata.print_name_nbytes = fixed_print_name_nchars * sizeof(wchar_t);
- make_reparse_buffer(&rpdata, rpbuf, rpbuflen_p);
+ link.substitute_name = fixed_subst_name;
+ link.substitute_name_nbytes = fixed_subst_name_nchars * sizeof(wchar_t);
+ link.print_name = (wchar_t *)fixed_print_name;
+ link.print_name_nbytes = fixed_print_name_nchars * sizeof(wchar_t);
+ make_link_reparse_point(&link, rpbuf, rpbuflen_p);
}
-/* Sets reparse data on the specified file. This handles "fixing" the targets
- * of absolute symbolic links and junctions if WIMLIB_EXTRACT_FLAG_RPFIX was
- * specified. */
+/* Sets the reparse point on the specified file. This handles "fixing" the
+ * targets of absolute symbolic links and junctions if WIMLIB_EXTRACT_FLAG_RPFIX
+ * was specified. */
static int
-set_reparse_data(const struct wim_dentry *dentry,
- const void *_rpbuf, u16 rpbuflen, struct win32_apply_ctx *ctx)
+set_reparse_point(const struct wim_dentry *dentry,
+ const struct reparse_buffer_disk *rpbuf, u16 rpbuflen,
+ struct win32_apply_ctx *ctx)
{
- const struct wim_inode *inode = dentry->d_inode;
- const void *rpbuf = _rpbuf;
-
if ((ctx->common.extract_flags & WIMLIB_EXTRACT_FLAG_RPFIX)
- && !inode->i_not_rpfixed
- && (inode->i_reparse_tag == WIM_IO_REPARSE_TAG_SYMLINK ||
- inode->i_reparse_tag == WIM_IO_REPARSE_TAG_MOUNT_POINT))
+ && !(dentry->d_inode->i_rp_flags & WIM_RP_FLAG_NOT_FIXED))
{
- memcpy(&ctx->rpfixbuf, _rpbuf, rpbuflen);
- try_rpfix((u8 *)&ctx->rpfixbuf, &rpbuflen, ctx);
+ memcpy(&ctx->rpfixbuf, rpbuf, rpbuflen);
+ try_rpfix(&ctx->rpfixbuf, &rpbuflen, ctx);
rpbuf = &ctx->rpfixbuf;
}
- return do_set_reparse_data(dentry, rpbuf, rpbuflen, ctx);
+ return do_set_reparse_point(dentry, rpbuf, rpbuflen, ctx);
}
ret = WIMLIB_ERR_INVALID_REPARSE_DATA;
return check_apply_error(dentry, ctx, ret);
}
- /* In the WIM format, reparse point streams are just the reparse
- * data and omit the header. But we can reconstruct the header.
- */
+ /* Reparse data */
memcpy(ctx->rpbuf.rpdata, ctx->data_buffer, blob->size);
- ctx->rpbuf.rpdatalen = blob->size;
- ctx->rpbuf.rpreserved = 0;
+
list_for_each_entry(dentry, &ctx->reparse_dentries, tmp_list) {
- ctx->rpbuf.rptag = dentry->d_inode->i_reparse_tag;
- ret = set_reparse_data(dentry, &ctx->rpbuf,
- blob->size + REPARSE_DATA_OFFSET,
- ctx);
+
+ /* Reparse point header */
+ complete_reparse_point(&ctx->rpbuf, dentry->d_inode,
+ blob->size);
+
+ ret = set_reparse_point(dentry, &ctx->rpbuf,
+ REPARSE_DATA_OFFSET + blob->size,
+ ctx);
ret = check_apply_error(dentry, ctx, ret);
if (ret)
return ret;
}
/* 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)
}
/*
- * Given an (expected) NT namespace symbolic link or junction target @target of
- * length @target_nbytes, determine if a prefix of the target points to a file
- * identified by @capture_root_ino and @capture_root_dev.
- *
- * If yes, return a pointer to the portion of the link following this prefix.
- *
- * If no, return NULL.
- *
- * If the link target does not appear to be a valid NT namespace path, return
- * @target itself.
+ * This is the Windows equivalent of unix_relativize_link_target(); see there
+ * for general details. This version works with an "absolute" Windows link
+ * target, specified from the root of the Windows kernel object namespace. Note
+ * that we have to open directories with a trailing slash when present because
+ * \??\E: opens the E: device itself and not the filesystem root directory.
*/
static const wchar_t *
-winnt_get_root_relative_target(const wchar_t *target, size_t target_nbytes,
- u64 capture_root_ino, u64 capture_root_dev)
+winnt_relativize_link_target(const wchar_t *target, size_t target_nbytes,
+ u64 ino, u64 dev)
{
UNICODE_STRING name;
OBJECT_ATTRIBUTES attr;
name.Buffer = (wchar_t *)p;
name.Length = 0;
- if (file_has_ino_and_dev(h, capture_root_ino,
- capture_root_dev))
+ if (file_has_ino_and_dev(h, ino, dev))
goto out_close_root_dir;
}
} while (p != target_end);
- p = NULL;
+ p = target;
out_close_root_dir:
if (attr.RootDirectory)
(*func_NtClose)(attr.RootDirectory);
+ while (p > target && *(p - 1) == L'\\')
+ p--;
return p;
}
static int
winnt_rpfix_progress(struct capture_params *params, const wchar_t *path,
- const struct reparse_data *rpdata, int scan_status)
+ const struct link_reparse_point *link, int scan_status)
{
- size_t print_name_nchars = rpdata->print_name_nbytes / sizeof(wchar_t);
+ size_t print_name_nchars = link->print_name_nbytes / sizeof(wchar_t);
wchar_t print_name0[print_name_nchars + 1];
- wmemcpy(print_name0, rpdata->print_name, print_name_nchars);
+ wmemcpy(print_name0, link->print_name, print_name_nchars);
print_name0[print_name_nchars] = L'\0';
params->progress.scan.cur_path = printable_path(path);
}
static int
-winnt_try_rpfix(u8 *rpbuf, u16 *rpbuflen_p,
- u64 capture_root_ino, u64 capture_root_dev,
+winnt_try_rpfix(struct reparse_buffer_disk *rpbuf, u16 *rpbuflen_p,
const wchar_t *path, struct capture_params *params)
{
- struct reparse_data rpdata;
+ struct link_reparse_point link;
const wchar_t *rel_target;
int ret;
- if (parse_reparse_data(rpbuf, *rpbuflen_p, &rpdata)) {
- /* Couldn't even understand the reparse data. Don't try the
- * fixup. */
- return RP_NOT_FIXED;
+ if (parse_link_reparse_point(rpbuf, *rpbuflen_p, &link)) {
+ /* Couldn't understand the reparse data; don't do the fixup. */
+ return 0;
}
/*
* - \??\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 will cut off the prefix before this part (which is the path to the
* directory being captured) and add a dummy prefix. Since the process
- * will need to be reversed when applying the image, it shouldn't matter
+ * will need to be reversed when applying the image, it doesn't matter
* what exactly the prefix is, as long as it looks like an absolute
- * path.
- */
+ * path. */
- {
- size_t rel_target_nbytes =
- rpdata.substitute_name_nbytes - ((const u8 *)rel_target -
- (const u8 *)rpdata.substitute_name);
- size_t rel_target_nchars = rel_target_nbytes / sizeof(wchar_t);
+ static const wchar_t prefix[6] = L"\\??\\X:";
+ static const size_t num_unprintable_chars = 4;
- wchar_t tmp[rel_target_nchars + 7];
+ size_t rel_target_nbytes =
+ link.substitute_name_nbytes - ((const u8 *)rel_target -
+ (const u8 *)link.substitute_name);
- wmemcpy(tmp, L"\\??\\X:\\", 7);
- wmemcpy(tmp + 7, rel_target, rel_target_nchars);
+ wchar_t tmp[(sizeof(prefix) + rel_target_nbytes) / sizeof(wchar_t)];
- rpdata.substitute_name = tmp;
- rpdata.substitute_name_nbytes = rel_target_nbytes + (7 * sizeof(wchar_t));
- rpdata.print_name = tmp + 4;
- rpdata.print_name_nbytes = rel_target_nbytes + (3 * sizeof(wchar_t));
+ memcpy(tmp, prefix, sizeof(prefix));
+ memcpy(tmp + ARRAY_LEN(prefix), rel_target, rel_target_nbytes);
- if (make_reparse_buffer(&rpdata, rpbuf, rpbuflen_p))
- return RP_NOT_FIXED;
- }
- ret = winnt_rpfix_progress(params, path, &rpdata,
+ link.substitute_name = tmp;
+ link.substitute_name_nbytes = sizeof(tmp);
+
+ link.print_name = link.substitute_name + num_unprintable_chars;
+ link.print_name_nbytes = link.substitute_name_nbytes -
+ (num_unprintable_chars * sizeof(wchar_t));
+
+ if (make_link_reparse_point(&link, rpbuf, rpbuflen_p))
+ return 0;
+
+ ret = winnt_rpfix_progress(params, path, &link,
WIMLIB_SCAN_DENTRY_FIXED_SYMLINK);
if (ret)
return ret;
}
/*
- * 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.
*
* On success, the length of the reparse point buffer in bytes is written
* to this location.
*
- * On success, returns a negative `enum rp_status' value.
+ * On success, returns 0 or RP_FIXED.
* On failure, returns a positive error code.
*/
static int
-winnt_get_reparse_data(HANDLE h, const wchar_t *path,
- struct capture_params *params,
- u8 *rpbuf, u16 *rpbuflen_ret)
+winnt_get_reparse_point(HANDLE h, const wchar_t *path,
+ struct capture_params *params,
+ struct reparse_buffer_disk *rpbuf, u16 *rpbuflen_ret)
{
DWORD bytes_returned;
- u32 reparse_tag;
- int ret;
- u16 rpbuflen;
if (!DeviceIoControl(h, FSCTL_GET_REPARSE_POINT,
NULL, 0, rpbuf, REPARSE_POINT_MAX_SIZE,
&bytes_returned, NULL))
{
- win32_error(GetLastError(), L"\"%ls\": Can't get reparse data",
+ win32_error(GetLastError(), L"\"%ls\": Can't get reparse point",
printable_path(path));
return WIMLIB_ERR_READ;
}
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
WARNING("Ignoring reparse data of encrypted file \"%ls\"",
printable_path(full_path));
} else {
- u8 rpbuf[REPARSE_POINT_MAX_SIZE] _aligned_attribute(8);
+ struct reparse_buffer_disk rpbuf;
u16 rpbuflen;
- ret = winnt_get_reparse_data(h, full_path, params,
- rpbuf, &rpbuflen);
- switch (ret) {
- case RP_FIXED:
- inode->i_not_rpfixed = 0;
- break;
- case RP_NOT_FIXED:
- inode->i_not_rpfixed = 1;
- break;
- default:
+ ret = winnt_get_reparse_point(h, full_path, params,
+ &rpbuf, &rpbuflen);
+ if (ret == RP_FIXED)
+ inode->i_rp_flags &= ~WIM_RP_FLAG_NOT_FIXED;
+ else if (ret)
goto out;
- }
- inode->i_reparse_tag = le32_to_cpu(*(le32*)rpbuf);
+ inode->i_reparse_tag = le32_to_cpu(rpbuf.rptag);
if (!inode_add_stream_with_data(inode,
STREAM_TYPE_REPARSE_POINT,
NO_STREAM_NAME,
- rpbuf + REPARSE_DATA_OFFSET,
+ rpbuf.rpdata,
rpbuflen - REPARSE_DATA_OFFSET,
params->blob_table))
{