]> wimlib.net Git - wimlib/blobdiff - src/symlink.c
refactor reparse point code; Win32: working extract rpfix
[wimlib] / src / symlink.c
diff --git a/src/symlink.c b/src/symlink.c
deleted file mode 100644 (file)
index f005cd7..0000000
+++ /dev/null
@@ -1,433 +0,0 @@
-/*
- * symlink.c
- *
- * Code to read and set symbolic links in WIM files.
- */
-
-/*
- * Copyright (C) 2012, 2013 Eric Biggers
- *
- * This file is part of wimlib, a library for working with WIM files.
- *
- * wimlib is free software; you can redistribute it and/or modify it under the
- * terms of the GNU General Public License as published by the Free
- * Software Foundation; either version 3 of the License, or (at your option)
- * any later version.
- *
- * wimlib is distributed in the hope that it will be useful, but WITHOUT ANY
- * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- * A PARTICULAR PURPOSE. See the GNU General Public License for more
- * details.
- *
- * You should have received a copy of the GNU General Public License
- * along with wimlib; if not, see http://www.gnu.org/licenses/.
- */
-
-#include "dentry.h"
-#include "buffer_io.h"
-#include "lookup_table.h"
-#include "sha1.h"
-#include <errno.h>
-
-/* UNIX version of getting and setting the data in reparse points */
-#if !defined(__WIN32__)
-
-#include <sys/stat.h>
-
-#ifdef HAVE_ALLOCA_H
-#  include <alloca.h>
-#endif
-
-/*
- * Find the symlink target of a symbolic link or junction point in the WIM.
- *
- * See http://msdn.microsoft.com/en-us/library/cc232006(v=prot.10).aspx for a
- * description of the format of the so-called "reparse point data buffers".
- *
- * But, 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 lookup table
- * entry resource length.
- */
-static ssize_t
-get_symlink_name(const void *resource, size_t resource_len, char *buf,
-                size_t buf_len, u32 reparse_tag)
-{
-       const void *p = resource;
-       u16 substitute_name_offset;
-       u16 substitute_name_len;
-       u16 print_name_offset;
-       u16 print_name_len;
-       char *link_target;
-       char *translated_target;
-       size_t link_target_len;
-       ssize_t ret;
-       unsigned header_size;
-       bool translate_slashes;
-
-       if (resource_len < 12)
-               return -EIO;
-       p = get_u16(p, &substitute_name_offset);
-       p = get_u16(p, &substitute_name_len);
-       p = get_u16(p, &print_name_offset);
-       p = get_u16(p, &print_name_len);
-
-       wimlib_assert(reparse_tag == WIM_IO_REPARSE_TAG_SYMLINK ||
-                     reparse_tag == WIM_IO_REPARSE_TAG_MOUNT_POINT);
-
-       if (reparse_tag == WIM_IO_REPARSE_TAG_MOUNT_POINT)
-               header_size = 8;
-       else {
-               header_size = 12;
-               p += 4;
-       }
-       if (header_size +
-           substitute_name_offset + substitute_name_len > resource_len)
-               return -EIO;
-
-       ret = utf16le_to_tstr((const utf16lechar*)(p + substitute_name_offset),
-                             substitute_name_len,
-                             &link_target, &link_target_len);
-       if (ret)
-               return -errno;
-
-       DEBUG("Interpeting substitute name \"%s\" (ReparseTag=0x%x)",
-             link_target, reparse_tag);
-       translate_slashes = true;
-       translated_target = link_target;
-       if (link_target_len >= 7 &&
-           translated_target[0] == '\\' &&
-           translated_target[1] == '?' &&
-           translated_target[2] == '?' &&
-           translated_target[3] == '\\' &&
-           translated_target[4] != '\0' &&
-           translated_target[5] == ':' &&
-           translated_target[6] == '\\')
-       {
-               /* "Full" symlink or junction (\??\x:\ prefixed path) */
-               translated_target += 6;
-               link_target_len -= 6;
-       } else if (reparse_tag == WIM_IO_REPARSE_TAG_MOUNT_POINT &&
-                  link_target_len >= 12 &&
-                  memcmp(translated_target, "\\\\?\\Volume{", 11) == 0 &&
-                  translated_target[link_target_len - 1] == '\\')
-       {
-               /* Volume junction.  Can't really do anything with it. */
-               translate_slashes = false;
-       } else if (reparse_tag == WIM_IO_REPARSE_TAG_SYMLINK &&
-                  link_target_len >= 3 &&
-                  translated_target[0] != '\0' &&
-                  translated_target[1] == ':' &&
-                  translated_target[2] == '\\')
-       {
-               /* "Absolute" symlink, with drive letter */
-               translated_target += 2;
-               link_target_len -= 2;
-       } else if (reparse_tag == WIM_IO_REPARSE_TAG_SYMLINK &&
-                  link_target_len >= 1)
-       {
-               if (translated_target[0] == '\\')
-                       /* "Absolute" symlink, without drive letter */
-                       ;
-               else
-                       /* "Relative" symlink, without drive letter */
-                       ;
-       } else {
-               ERROR("Invalid reparse point substitute name: \"%s\"", translated_target);
-               ret = -EIO;
-               goto out;
-       }
-
-       if (translate_slashes)
-               for (size_t i = 0; i < link_target_len; i++)
-                       if (translated_target[i] == '\\')
-                               translated_target[i] = '/';
-
-       if (link_target_len > buf_len) {
-               link_target_len = buf_len;
-               ret = -ENAMETOOLONG;
-       } else {
-               ret = link_target_len;
-       }
-       memcpy(buf, translated_target, link_target_len);
-out:
-       FREE(link_target);
-       return ret;
-}
-
-#define SYMBOLIC_LINK_RELATIVE 0x00000001
-
-/* Given a UNIX symlink target, prepare the corresponding symbolic link reparse
- * data buffer. */
-static int
-make_symlink_reparse_data_buf(const char *symlink_target, void *rpdata,
-                             size_t *rplen_ret)
-{
-       int ret;
-       utf16lechar *name_utf16le;
-       size_t name_utf16le_nbytes;
-       size_t substitute_name_nbytes;
-       size_t print_name_nbytes;
-       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";
-       u32 flags;
-       size_t rplen;
-       void *p;
-
-       ret = tstr_to_utf16le(symlink_target, strlen(symlink_target),
-                             &name_utf16le, &name_utf16le_nbytes);
-       if (ret)
-               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('\\');
-
-       /* 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 only
-        * need to translate it to UTF-16LE and replace backslashes with forward
-        * slashes.  We do not make any attempt to handle filename character
-        * problems, such as a link target that itself contains backslashes on
-        * UNIX.  Then, for these relative links, we set the reparse header
-        * @flags field 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.
-        */
-
-       substitute_name_nbytes = name_utf16le_nbytes;
-       print_name_nbytes = name_utf16le_nbytes;
-       if (symlink_target[0] == '/') {
-               substitute_name_nbytes += sizeof(abs_subst_name_prefix);
-               print_name_nbytes += sizeof(abs_print_name_prefix);
-       }
-
-       rplen = 12 + substitute_name_nbytes + print_name_nbytes +
-                       2 * sizeof(utf16lechar);
-
-       if (rplen > REPARSE_POINT_MAX_SIZE) {
-               ERROR("Symlink \"%s\" is too long!", symlink_target);
-               return WIMLIB_ERR_LINK;
-       }
-
-       p = rpdata;
-
-       /* Substitute name offset */
-       p = put_u16(p, 0);
-
-       /* Substitute name length */
-       p = put_u16(p, substitute_name_nbytes);
-
-       /* Print name offset */
-       p = put_u16(p, substitute_name_nbytes + sizeof(utf16lechar));
-
-       /* Print name length */
-       p = put_u16(p, print_name_nbytes);
-
-       /* Flags */
-       flags = 0;
-       if (symlink_target[0] != '/')
-               flags |= SYMBOLIC_LINK_RELATIVE;
-       p = put_u32(p, flags);
-
-       /* Substitute name */
-       if (symlink_target[0] == '/')
-               p = put_bytes(p, sizeof(abs_subst_name_prefix), abs_subst_name_prefix);
-       p = put_bytes(p, name_utf16le_nbytes, name_utf16le);
-       p = put_u16(p, 0);
-
-       /* Print name */
-       if (symlink_target[0] == '/')
-               p = put_bytes(p, sizeof(abs_print_name_prefix), abs_print_name_prefix);
-       p = put_bytes(p, name_utf16le_nbytes, name_utf16le);
-       p = put_u16(p, 0);
-
-       *rplen_ret = rplen;
-       ret = 0;
-out_free_name_utf16le:
-       FREE(name_utf16le);
-       return ret;
-}
-
-/* Get the symlink target from a WIM inode.
- *
- * The inode may be either a "real" symlink (reparse tag
- * WIM_IO_REPARSE_TAG_SYMLINK), or it may be a junction point (reparse tag
- * WIM_IO_REPARSE_TAG_MOUNT_POINT).
- */
-ssize_t
-inode_readlink(const struct wim_inode *inode, char *buf, size_t buf_len,
-              const WIMStruct *w, bool threadsafe)
-{
-       const struct wim_lookup_table_entry *lte;
-       int ret;
-       u8 *res_buf;
-
-       wimlib_assert(inode_is_symlink(inode));
-
-       lte = inode_unnamed_lte(inode, w->lookup_table);
-       if (!lte)
-               return -EIO;
-
-       if (wim_resource_size(lte) > REPARSE_POINT_MAX_SIZE)
-               return -EIO;
-
-       res_buf = alloca(wim_resource_size(lte));
-       ret = read_full_resource_into_buf(lte, res_buf, threadsafe);
-       if (ret)
-               return -EIO;
-       return get_symlink_name(res_buf, wim_resource_size(lte),
-                               buf, buf_len, inode->i_reparse_tag);
-}
-
-/*
- * Sets @inode to be a symbolic link pointing to @target.
- *
- * A lookup table entry for the symbolic link data buffer is created and
- * inserted into @lookup_table, unless there is an existing lookup table entry
- * for the exact same data, in which its reference count is incremented.
- *
- * The lookup table entry is returned in @lte_ret.
- *
- * On failure @dentry and @lookup_table are not modified.
- */
-int
-inode_set_symlink(struct wim_inode *inode,
-                 const char *target,
-                 struct wim_lookup_table *lookup_table,
-                 struct wim_lookup_table_entry **lte_ret)
-
-{
-       int ret;
-
-       /* Buffer for reparse point data */
-       u8 rpdata[REPARSE_POINT_MAX_SIZE];
-
-       /* Actual length of the reparse point data (to be calculated by
-        * make_symlink_reparse_data_buf()) */
-       size_t rplen;
-
-       DEBUG("Creating reparse point data buffer "
-             "for UNIX symlink target \"%s\"", target);
-
-       ret = make_symlink_reparse_data_buf(target, rpdata, &rplen);
-       if (ret)
-               return ret;
-
-       ret = inode_set_unnamed_stream(inode, rpdata, rplen, lookup_table);
-       if (ret)
-               return ret;
-
-       if (lte_ret)
-               *lte_ret = inode->i_lte;
-       return 0;
-}
-
-static int
-unix_get_ino_and_dev(const char *path, u64 *ino_ret, u64 *dev_ret)
-{
-       struct stat stbuf;
-       if (stat(path, &stbuf)) {
-               WARNING_WITH_ERRNO("Failed to stat \"%s\"", path);
-               /* Treat as a link pointing outside the capture root (it
-                * most likely is). */
-               return WIMLIB_ERR_STAT;
-       } else {
-               *ino_ret = stbuf.st_ino;
-               *dev_ret = stbuf.st_dev;
-               return 0;
-       }
-}
-
-#endif /* !defined(__WIN32__) */
-
-#ifdef __WIN32__
-#  include "win32.h"
-#  define RP_PATH_SEPARATOR L'\\'
-#  define is_rp_path_separator(c) ((c) == L'\\' || (c) == L'/')
-#  define os_get_ino_and_dev win32_get_file_and_vol_ids
-#else
-#  define RP_PATH_SEPARATOR '/'
-#  define is_rp_path_separator(c) ((c) == '/')
-#  define os_get_ino_and_dev unix_get_ino_and_dev
-#endif
-
-/* Fix up absolute symbolic link targets--- mostly shared between UNIX and
- * Windows */
-tchar *
-fixup_symlink(tchar *dest, u64 capture_root_ino, u64 capture_root_dev)
-{
-       tchar *p = dest;
-
-       DEBUG("Fixing symlink or junction \"%"TS"\"", dest);
-       for (;;) {
-               tchar save;
-               int ret;
-               u64 ino;
-               u64 dev;
-
-               while (is_rp_path_separator(*p))
-                       p++;
-
-               save = *p;
-               *p = T('\0');
-               ret = os_get_ino_and_dev(dest, &ino, &dev);
-               *p = save;
-
-               if (ret) /* stat() failed before we got to the capture root---
-                           assume the link points outside it. */
-                       return NULL;
-
-               if (ino == capture_root_ino && dev == capture_root_dev) {
-                       /* Link points inside capture root.  Return abbreviated
-                        * path. */
-                       if (*p == T('\0'))
-                               *(p - 1) = RP_PATH_SEPARATOR;
-                       while (p - 1 >= dest && is_rp_path_separator(*(p - 1)))
-                               p--;
-                       return p;
-               }
-
-               if (*p == T('\0')) {
-                       /* Link points outside capture root. */
-                       return NULL;
-               }
-
-               do {
-                       p++;
-               } while (!is_rp_path_separator(*p) && *p != T('\0'));
-       }
-}