rpfix extract on UNIX
authorEric Biggers <ebiggers3@gmail.com>
Mon, 22 Apr 2013 02:43:23 +0000 (21:43 -0500)
committerEric Biggers <ebiggers3@gmail.com>
Mon, 22 Apr 2013 02:43:23 +0000 (21:43 -0500)
programs/imagex.c
src/extract_image.c
src/symlink.c
src/wimlib.h
src/wimlib_internal.h
tests/test-imagex-capture_and_apply

index afaf110..c1f4217 100644 (file)
@@ -96,7 +96,7 @@ T(
 IMAGEX_PROGNAME" apply WIMFILE [IMAGE_NUM | IMAGE_NAME | all]\n"
 "                    (DIRECTORY | NTFS_VOLUME) [--check] [--hardlink]\n"
 "                    [--symlink] [--verbose] [--ref=\"GLOB\"] [--unix-data]\n"
-"                    [--no-acls] [--strict-acls]\n"
+"                    [--no-acls] [--strict-acls] [--rpfix] [--norpfix]\n"
 ),
 [CAPTURE] =
 T(
@@ -202,6 +202,8 @@ static const struct option apply_options[] = {
        {T("noacls"),      no_argument,       NULL, IMAGEX_NO_ACLS_OPTION},
        {T("no-acls"),     no_argument,       NULL, IMAGEX_NO_ACLS_OPTION},
        {T("strict-acls"), no_argument,       NULL, IMAGEX_STRICT_ACLS_OPTION},
+       {T("rpfix"),       no_argument,       NULL, IMAGEX_RPFIX_OPTION},
+       {T("norpfix"),     no_argument,       NULL, IMAGEX_NORPFIX_OPTION},
        {NULL, 0, NULL, 0},
 };
 static const struct option capture_or_append_options[] = {
@@ -1184,6 +1186,12 @@ imagex_apply(int argc, tchar **argv)
                case IMAGEX_STRICT_ACLS_OPTION:
                        extract_flags |= WIMLIB_EXTRACT_FLAG_STRICT_ACLS;
                        break;
+               case IMAGEX_NORPFIX_OPTION:
+                       extract_flags |= WIMLIB_EXTRACT_FLAG_NORPFIX;
+                       break;
+               case IMAGEX_RPFIX_OPTION:
+                       extract_flags |= WIMLIB_EXTRACT_FLAG_RPFIX;
+                       break;
                default:
                        usage(APPLY);
                        return -1;
index fffdcfd..48a916a 100644 (file)
@@ -324,9 +324,13 @@ extract_symlink(struct wim_dentry *dentry,
                struct apply_args *args,
                const char *output_path)
 {
-       char target[4096];
-       ssize_t ret = inode_readlink(dentry->d_inode, target,
-                                    sizeof(target), args->w, false);
+       char target[4096 + args->target_realpath_len];
+       char *fixed_target;
+
+       ssize_t ret = inode_readlink(dentry->d_inode,
+                                    target + args->target_realpath_len,
+                                    sizeof(target) - args->target_realpath_len - 1,
+                                    args->w, false);
        struct wim_lookup_table_entry *lte;
 
        if (ret <= 0) {
@@ -334,10 +338,20 @@ extract_symlink(struct wim_dentry *dentry,
                      dentry->_full_path);
                return WIMLIB_ERR_INVALID_DENTRY;
        }
-       ret = symlink(target, output_path);
-       if (ret != 0) {
+       target[args->target_realpath_len + ret] = '\0';
+       if (target[args->target_realpath_len] == '/' &&
+           args->extract_flags & WIMLIB_EXTRACT_FLAG_RPFIX)
+       {
+               memcpy(target, args->target_realpath,
+                      args->target_realpath_len);
+               fixed_target = target;
+       } else {
+               fixed_target = target + args->target_realpath_len;
+       }
+       ret = symlink(fixed_target, output_path);
+       if (ret) {
                ERROR_WITH_ERRNO("Failed to symlink `%s' to `%s'",
-                                output_path, target);
+                                output_path, fixed_target);
                return WIMLIB_ERR_LINK;
        }
        lte = inode_unnamed_lte_resolved(dentry->d_inode);
@@ -351,7 +365,7 @@ extract_symlink(struct wim_dentry *dentry,
                        ret = 0;
                else
                        ret = symlink_apply_unix_data(output_path, &unix_data);
-               if (ret != 0)
+               if (ret)
                        return ret;
        }
        args->progress.extract.completed_bytes += wim_resource_size(lte);
@@ -782,6 +796,7 @@ extract_single_image(WIMStruct *w, int image,
 
        struct apply_args args;
        const struct apply_operations *ops;
+       tchar *target_realpath;
 
        memset(&args, 0, sizeof(args));
 
@@ -863,10 +878,17 @@ extract_single_image(WIMStruct *w, int image,
                              &args.progress);
        }
 
+       if (extract_flags & WIMLIB_EXTRACT_FLAG_RPFIX) {
+               args.target_realpath = realpath(target, NULL);
+               if (!args.target_realpath)
+                       return WIMLIB_ERR_NOMEM;
+               args.target_realpath_len = tstrlen(args.target_realpath);
+       }
+
        /* Extract non-empty files */
        ret = apply_stream_list(&stream_list, &args, ops, progress_func);
        if (ret)
-               goto out;
+               goto out_free_target_realpath;
 
        if (progress_func) {
                progress_func(WIMLIB_PROGRESS_MSG_APPLY_TIMESTAMPS,
@@ -877,12 +899,14 @@ extract_single_image(WIMStruct *w, int image,
        ret = for_dentry_in_tree_depth(wim_root_dentry(w),
                                       ops->apply_dentry_timestamps, &args);
        if (ret)
-               goto out;
+               goto out_free_target_realpath;
 
        if (progress_func) {
                progress_func(WIMLIB_PROGRESS_MSG_EXTRACT_IMAGE_END,
                              &args.progress);
        }
+out_free_target_realpath:
+       FREE(args.target_realpath);
 out:
 #ifdef WITH_NTFS_3G
        /* Unmount the NTFS volume */
@@ -1011,6 +1035,19 @@ wimlib_extract_image(WIMStruct *w,
 #endif
        }
 
+       if ((extract_flags & (WIMLIB_EXTRACT_FLAG_RPFIX |
+                             WIMLIB_EXTRACT_FLAG_RPFIX)) ==
+               (WIMLIB_EXTRACT_FLAG_RPFIX | WIMLIB_EXTRACT_FLAG_NORPFIX))
+       {
+               ERROR("Cannot specify RPFIX and NORPFIX flags at the same time!");
+               return WIMLIB_ERR_INVALID_PARAM;
+       }
+
+       if ((extract_flags & (WIMLIB_EXTRACT_FLAG_RPFIX |
+                             WIMLIB_EXTRACT_FLAG_NORPFIX)) == 0)
+               if (w->hdr.flags & WIM_HDR_FLAG_RP_FIX)
+                       extract_flags |= WIMLIB_EXTRACT_FLAG_RPFIX;
+
        ret = verify_swm_set(w, additional_swms, num_additional_swms);
        if (ret)
                return ret;
index 9eaf6b4..dc365e8 100644 (file)
@@ -57,9 +57,7 @@ get_symlink_name(const void *resource, size_t resource_len, char *buf,
        size_t link_target_len;
        ssize_t ret;
        unsigned header_size;
-       char *translated_target;
-       bool is_absolute;
-       u32 flags;
+       bool translate_slashes;
 
        if (resource_len < 12)
                return -EIO;
@@ -67,15 +65,13 @@ get_symlink_name(const void *resource, size_t resource_len, char *buf,
        p = get_u16(p, &substitute_name_len);
        p = get_u16(p, &print_name_offset);
        p = get_u16(p, &print_name_len);
-       get_u32(p, &flags);
 
        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) {
+       if (reparse_tag == WIM_IO_REPARSE_TAG_MOUNT_POINT)
                header_size = 8;
-       } else {
-               is_absolute = (flags & 1) ? false : true;
+       else {
                header_size = 12;
                p += 4;
        }
@@ -93,32 +89,57 @@ get_symlink_name(const void *resource, size_t resource_len, char *buf,
                goto out;
        }
 
-       translated_target = link_target;
-       if (reparse_tag == WIM_IO_REPARSE_TAG_MOUNT_POINT || is_absolute) {
-               if (link_target_len < 7
-                     || memcmp(translated_target, "\\??\\", 4) != 0
-                     || translated_target[4] == '\0'
-                     || translated_target[5] != ':'
-                     || translated_target[6] != '\\') {
-                       ret = -EIO;
-                       goto out;
-               }
-               translated_target += 4;
-               link_target_len -= 4;
-               /* There's a drive letter, so just leave the backslashes since
-                * it won't go anyhwere on UNIX anyway...
-                *
-                * XXX
-                * NTFS-3g tries to re-map these links to actually point to
-                * something, so maybe we could do something like that here
-                * XXX*/
+       DEBUG("Interpeting substitute name \"%s\" (ReparseTag=0x%x)",
+             link_target, reparse_tag);
+       translate_slashes = true;
+       if (link_target_len >= 7 &&
+           link_target[0] == '\\' &&
+           link_target[1] == '?' &&
+           link_target[2] == '?' &&
+           link_target[3] == '\\' &&
+           link_target[4] != '\0' &&
+           link_target[5] == ':' &&
+           link_target[6] == '\\')
+       {
+               /* "Full" symlink or junction (\??\x:\ prefixed path) */
+               link_target += 6;
+               link_target_len -= 6;
+       } else if (reparse_tag == WIM_IO_REPARSE_TAG_MOUNT_POINT &&
+                  link_target_len >= 12 &&
+                  memcmp(link_target, "\\\\?\\Volume{", 11) == 0 &&
+                  link_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 &&
+                  link_target[0] != '\0' &&
+                  link_target[1] == ':' &&
+                  link_target[2] == '/')
+       {
+               /* "Absolute" symlink, with drive letter */
+               link_target += 2;
+               link_target_len -= 2;
+       } else if (reparse_tag == WIM_IO_REPARSE_TAG_SYMLINK &&
+                  link_target_len >= 1)
+       {
+               if (link_target[0] == '/')
+                       /* "Absolute" symlink, without drive letter */
+                       ;
+               else
+                       /* "Relative" symlink, without drive letter */
+                       ;
        } else {
-               for (size_t i = 0; i < link_target_len; i++)
-                       if (translated_target[i] == '\\')
-                               translated_target[i] = '/';
+               ERROR("Invalid reparse point: \"%s\"", link_target);
+               ret = -EIO;
+               goto out;
        }
 
-       memcpy(buf, translated_target, link_target_len + 1);
+       if (translate_slashes)
+               for (size_t i = 0; i < link_target_len; i++)
+                       if (link_target[i] == '\\')
+                               link_target[i] = '/';
+       memcpy(buf, link_target, link_target_len + 1);
        ret = link_target_len;
 out:
        FREE(link_target);
@@ -187,7 +208,7 @@ inode_readlink(const struct wim_inode *inode, char *buf, size_t buf_len,
 
        u8 res_buf[wim_resource_size(lte)];
        ret = read_full_resource_into_buf(lte, res_buf, threadsafe);
-       if (ret != 0)
+       if (ret)
                return -EIO;
        return get_symlink_name(res_buf, wim_resource_size(lte), buf,
                                buf_len, inode->i_reparse_tag);
index 479fa24..e7f89fa 100644 (file)
@@ -697,10 +697,13 @@ struct wimlib_capture_config {
 /** Reparse-point fixups:  Modify absolute symbolic links (or junction points,
  * in the case of Windows) that point inside the directory being captured to
  * instead be absolute relative to the directory being captured, rather than the
- * current root.
+ * current root; also exclude absolute symbolic links that point outside the
+ * directory tree being captured.
  *
- * Without this flag, the default is to do the reparse-point fixups if
- * WIM_HDR_FLAG_RP_FIX is set in the WIM header. */
+ * Without this flag, the default is to do this only if WIM_HDR_FLAG_RP_FIX is
+ * set in the WIM header.  WIM_HDR_FLAG_RP_FIX is set if the first image in a
+ * WIM is captured with WIMLIB_ADD_IMAGE_FLAG_RPFIX enabled and currently cannot
+ * be changed. */
 #define WIMLIB_ADD_IMAGE_FLAG_RPFIX                    0x00000100
 
 /* Don't do reparse point fixups.  Without this flag, the default is to do
@@ -754,6 +757,16 @@ struct wimlib_capture_config {
  * not have permission to set the desired one. */
 #define WIMLIB_EXTRACT_FLAG_STRICT_ACLS                        0x00000080
 
+/* Extract equivalent to ::WIMLIB_ADD_IMAGE_FLAG_RPFIX; force reparse-point
+ * fixups on, so absolute symbolic links are junction points will be fixed to be
+ * absolute relative to the actual extraction root.  Done by default if
+ * WIM_HDR_FLAG_RP_FIX is set in the WIM header. */
+#define WIMLIB_EXTRACT_FLAG_RPFIX                      0x00000100
+
+/** Force reparse-point fixups on extraction off, regardless of the state of the
+ * WIM_HDR_FLAG_RP_FIX flag in the WIM header. */
+#define WIMLIB_EXTRACT_FLAG_NORPFIX                    0x00000200
+
 /******************************
  * WIMLIB_MOUNT_FLAG_*        *
  ******************************/
index 1078457..1d3edcd 100644 (file)
@@ -540,6 +540,8 @@ write_metadata_resource(WIMStruct *w);
 struct apply_args {
        WIMStruct *w;
        const tchar *target;
+       tchar *target_realpath;
+       unsigned target_realpath_len;
        int extract_flags;
        union wimlib_progress_info progress;
        wimlib_progress_func_t progress_func;
index d457751..dfa240f 100755 (executable)
@@ -142,6 +142,14 @@ if [[ `readlink out.dir/abslink` != "/file" ]] ||
    [[ `readlink out.dir/abslinkslashes` != "/file///" ]]; then
        error "imagex capture --rpfix did fix absolute link properly"
 fi
+rm -rf out.dir
+
+imagex apply test.wim out.dir
+if [[ $(get_inode_number $(readlink out.dir/absrootlink)) != \
+       $(get_inode_number out.dir) ]];
+then
+       error "imagex apply failed to apply fixed absolute symlinks"
+fi
 
 echo "**********************************************************"
 echo "          imagex capture/apply tests passed               "