From 927b1829e714d177de9d38c2da7fcdc13be44ed0 Mon Sep 17 00:00:00 2001 From: Eric Biggers Date: Tue, 29 Apr 2014 11:22:06 -0500 Subject: [PATCH] More logical behavior when canonicalizing WIM paths In addition to translating path separators and stripping trailing slashes, also collapse consecutive path separators, and retain the leading slash to emphasize that the paths are rooted. Add WIMLIB_WIM_ROOT_PATH and WIMLIB_IS_WIM_ROOT_PATH macros to wimlib.h. --- NEWS | 3 ++ include/wimlib.h | 56 ++++++++++++++++++---------------- programs/imagex.c | 40 +++++++------------------ src/add_image.c | 2 +- src/extract.c | 3 +- src/paths.c | 75 ++++++++++++++++++++++++++++++---------------- src/update_image.c | 35 +++++++++++++--------- 7 files changed, 117 insertions(+), 97 deletions(-) diff --git a/NEWS b/NEWS index d69f6286..727f8ab5 100644 --- a/NEWS +++ b/NEWS @@ -30,6 +30,9 @@ Version 1.6.3-BETA: - Removed deprecated functions: some (de)compression functions, wimlib_extract_files(), and wimlib_print_metadata(). + - WIM paths passed to progress functions now have a leading + slash. + Version 1.6.2: Case-insensitive comparisons of strings (e.g. filenames) containing UTF-16 codepoints above 32767 are now done correctly. diff --git a/include/wimlib.h b/include/wimlib.h index b54682aa..fc6526ae 100644 --- a/include/wimlib.h +++ b/include/wimlib.h @@ -392,6 +392,15 @@ typedef char wimlib_tchar; # define WIMLIB_WIM_PATH_SEPARATOR_STRING "/" #endif +/** Use this to specify the root directory of the WIM image. */ +#define WIMLIB_WIM_ROOT_PATH WIMLIB_WIM_PATH_SEPARATOR_STRING + +/** Use this to test if the specified path refers to the root directory of the + * WIM image. */ +#define WIMLIB_IS_WIM_ROOT_PATH(path) \ + ((path)[0] == WIMLIB_WIM_PATH_SEPARATOR && \ + (path)[1] == 0) + #ifdef __GNUC__ # define _wimlib_deprecated __attribute__((deprecated)) #else @@ -702,10 +711,7 @@ union wimlib_progress_info { union { /** Target path in the WIM image. Only valid on * messages ::WIMLIB_PROGRESS_MSG_SCAN_BEGIN and - * ::WIMLIB_PROGRESS_MSG_SCAN_END. If capturing a full - * image, this will be the empty string; otherwise it - * will name the place in the WIM image at which the - * directory tree is being added. */ + * ::WIMLIB_PROGRESS_MSG_SCAN_END. */ const wimlib_tchar *wim_target_path; /** For ::WIMLIB_PROGRESS_MSG_SCAN_DENTRY and a status @@ -823,8 +829,8 @@ union wimlib_progress_info { * data stream, or a reparse data buffer. */ uint64_t num_streams; - /** This will be the empty string. */ - const wimlib_tchar *extract_root_wim_source_path; + /** Reserved. */ + const wimlib_tchar *reserved_2; /** Currently only used for * ::WIMLIB_PROGRESS_MSG_EXTRACT_SPWM_PART_BEGIN. */ @@ -949,9 +955,8 @@ struct wimlib_capture_source { * filesystem to be included in the WIM image. */ wimlib_tchar *fs_source_path; - /** Destination path in the WIM image. Leading and trailing slashes are - * ignored. The empty string or @c NULL means the root directory of the - * WIM image. */ + /** Destination path in the WIM image. Use WIMLIB_WIM_ROOT_PATH to + * specify the root directory of the WIM image. */ wimlib_tchar *wim_target_path; /** Reserved; set to 0. */ @@ -1802,12 +1807,11 @@ enum wimlib_update_op { /** Data for a ::WIMLIB_UPDATE_OP_ADD operation. */ struct wimlib_add_command { - /** Filesystem path to the file or directory tree to - * add. */ + /** Filesystem path to the file or directory tree to add. */ wimlib_tchar *fs_source_path; - /** Path, specified from the root of the WIM image, at - * which to add the file or directory tree within the - * WIM image. */ + + /** Destination path in the WIM image. Use WIMLIB_WIM_ROOT_PATH to + * specify the root directory of the WIM image. */ wimlib_tchar *wim_target_path; /** Path to capture configuration file to use, or @c NULL for default. @@ -1820,25 +1824,27 @@ struct wimlib_add_command { /** Data for a ::WIMLIB_UPDATE_OP_DELETE operation. */ struct wimlib_delete_command { - /** Path, specified from the root of the WIM image, for - * the file or directory tree within the WIM image to be - * deleted. */ + + /** Path, specified from the root of the WIM image, for the file or + * directory tree within the WIM image to be deleted. */ wimlib_tchar *wim_path; - /** Bitwise OR of WIMLIB_DELETE_FLAG_* flags. */ + + /** Bitwise OR of WIMLIB_DELETE_FLAG_* flags. */ int delete_flags; }; /** Data for a ::WIMLIB_UPDATE_OP_RENAME operation. */ struct wimlib_rename_command { - /** Path, specified from the root of the WIM image, for - * the source file or directory tree within the WIM - * image. */ + + /** Path, specified from the root of the WIM image, for the source file + * or directory tree within the WIM image. */ wimlib_tchar *wim_source_path; - /** Path, specified from the root of the WIM image, for - * the destination file or directory tree within the WIM - * image. */ + + /** Path, specified from the root of the WIM image, for the destination + * file or directory tree within the WIM image. */ wimlib_tchar *wim_target_path; - /** Reserved; set to 0. */ + + /** Reserved; set to 0. */ int rename_flags; }; diff --git a/programs/imagex.c b/programs/imagex.c index 644a2bab..32f24c38 100644 --- a/programs/imagex.c +++ b/programs/imagex.c @@ -48,14 +48,10 @@ #ifdef __WIN32__ # include "imagex-win32.h" -# define OS_PREFERRED_PATH_SEPARATOR L'\\' -# define OS_PREFERRED_PATH_SEPARATOR_STRING L"\\" # define print_security_descriptor win32_print_security_descriptor #else /* __WIN32__ */ # include # include -# define OS_PREFERRED_PATH_SEPARATOR '/' -# define OS_PREFERRED_PATH_SEPARATOR_STRING "/" # define print_security_descriptor default_print_security_descriptor static inline void set_fd_to_binary_mode(int fd) { @@ -1067,12 +1063,11 @@ imagex_progress_func(enum wimlib_progress_msg msg, break; case WIMLIB_PROGRESS_MSG_SCAN_BEGIN: imagex_printf(T("Scanning \"%"TS"\""), info->scan.source); - if (*info->scan.wim_target_path) { - imagex_printf(T(" (loading as WIM path: " - "\""WIMLIB_WIM_PATH_SEPARATOR_STRING"%"TS"\")...\n"), - info->scan.wim_target_path); - } else { + if (WIMLIB_IS_WIM_ROOT_PATH(info->scan.wim_target_path)) { imagex_printf(T("\n")); + } else { + imagex_printf(T(" (loading as WIM path: \"%"TS"\")...\n"), + info->scan.wim_target_path); } memset(&last_scan_progress, 0, sizeof(last_scan_progress)); break; @@ -1141,17 +1136,6 @@ imagex_progress_func(enum wimlib_progress_msg msg, T("NTFS volume") : T("directory")), info->extract.target); break; - case WIMLIB_PROGRESS_MSG_EXTRACT_TREE_BEGIN: - if (info->extract.extract_root_wim_source_path[0]) { - imagex_printf(T("Extracting \"%"TS"\" from image %d " - "(\"%"TS"\") in \"%"TS"\" to \"%"TS"\"\n"), - info->extract.extract_root_wim_source_path, - info->extract.image, - info->extract.image_name, - info->extract.wimfile_name, - info->extract.target); - } - break; case WIMLIB_PROGRESS_MSG_EXTRACT_STREAMS: percent_done = TO_PERCENT(info->extract.completed_bytes, info->extract.total_bytes); @@ -1174,8 +1158,7 @@ imagex_progress_func(enum wimlib_progress_msg msg, } break; case WIMLIB_PROGRESS_MSG_APPLY_TIMESTAMPS: - if (info->extract.extract_root_wim_source_path[0] == T('\0')) - imagex_printf(T("Setting timestamps on all extracted files...\n")); + imagex_printf(T("Setting timestamps on all extracted files...\n")); break; case WIMLIB_PROGRESS_MSG_EXTRACT_IMAGE_END: if (info->extract.extract_flags & WIMLIB_EXTRACT_FLAG_NTFS) { @@ -1208,14 +1191,11 @@ imagex_progress_func(enum wimlib_progress_msg msg, case WIMLIB_PROGRESS_MSG_UPDATE_END_COMMAND: switch (info->update.command->op) { case WIMLIB_UPDATE_OP_DELETE: - imagex_printf(T("Deleted WIM path " - "\""WIMLIB_WIM_PATH_SEPARATOR_STRING "%"TS"\"\n"), + imagex_printf(T("Deleted WIM path \"%"TS"\"\n"), info->update.command->delete_.wim_path); break; case WIMLIB_UPDATE_OP_RENAME: - imagex_printf(T("Renamed WIM path " - "\""WIMLIB_WIM_PATH_SEPARATOR_STRING "%"TS"\" => " - "\""WIMLIB_WIM_PATH_SEPARATOR_STRING "%"TS"\"\n"), + imagex_printf(T("Renamed WIM path \"%"TS"\" => \"%"TS"\"\n"), info->update.command->rename.wim_source_path, info->update.command->rename.wim_target_path); break; @@ -1952,7 +1932,7 @@ imagex_capture_or_append(int argc, tchar **argv, int cmd) /* Set up capture source in non-source-list mode. */ capture_sources = alloca(sizeof(struct wimlib_capture_source)); capture_sources[0].fs_source_path = source; - capture_sources[0].wim_target_path = NULL; + capture_sources[0].wim_target_path = WIMLIB_WIM_ROOT_PATH; capture_sources[0].reserved = 0; num_sources = 1; capture_sources_malloced = false; @@ -2479,7 +2459,7 @@ imagex_dir(int argc, tchar **argv, int cmd) WIMStruct *wim = NULL; int image; int ret; - const tchar *path = T(""); + const tchar *path = WIMLIB_WIM_ROOT_PATH; int c; struct print_dentry_options options = { .detailed = false, @@ -2836,7 +2816,7 @@ imagex_extract(int argc, tchar **argv, int cmd) STRING_SET(refglobs); - tchar *root_path = T(""); + tchar *root_path = WIMLIB_WIM_ROOT_PATH; for_opt(c, extract_options) { switch (c) { diff --git a/src/add_image.c b/src/add_image.c index c8aeaa68..7c3a6cb2 100644 --- a/src/add_image.c +++ b/src/add_image.c @@ -218,7 +218,7 @@ wimlib_add_image(WIMStruct *wim, * */ const struct wimlib_capture_source capture_src = { .fs_source_path = (tchar*)source, - .wim_target_path = T(""), + .wim_target_path = WIMLIB_WIM_ROOT_PATH, .reserved = 0, }; return wimlib_add_image_multisource(wim, &capture_src, 1, name, diff --git a/src/extract.c b/src/extract.c index c6a3b561..1b7eb6bc 100644 --- a/src/extract.c +++ b/src/extract.c @@ -2435,7 +2435,6 @@ extract_trees(WIMStruct *wim, struct wim_dentry **trees, size_t num_trees, ctx.progress.extract.target = target; } - ctx.progress.extract.extract_root_wim_source_path = T(""); ctx.target_dentry = wim_root_dentry(wim); /* Note: ctx.target_dentry represents the dentry that gets extracted to * @target. There may be none, in which case it gets set to the image @@ -2925,7 +2924,7 @@ extract_single_image(WIMStruct *wim, int image, const tchar *target, int extract_flags, wimlib_progress_func_t progress_func) { - const tchar *path = T(""); + const tchar *path = WIMLIB_WIM_ROOT_PATH; extract_flags |= WIMLIB_EXTRACT_FLAG_IMAGEMODE; return do_wimlib_extract_paths(wim, image, target, &path, 1, extract_flags, progress_func); diff --git a/src/paths.c b/src/paths.c index 68942f20..675b9f27 100644 --- a/src/paths.c +++ b/src/paths.c @@ -87,38 +87,63 @@ canonicalize_fs_path(const tchar *fs_path) * canonicalize_wim_path() - Given a user-provided path to a file within a WIM * image, translate it into a "canonical" path. * - * To do this, translate all supported path separators (is_any_path_separator()) - * into the WIM_PATH_SEPARATOR, and strip any leading and trailing slashes. The - * returned string is allocated. Note that there still may be consecutive path - * separators within the string. Furthermore, the string may be empty, which - * indicates the root dentry of the WIM image. + * - Translate both types of slash into a consistent type (WIM_PATH_SEPARATOR). + * - Collapse path separators. + * - Add leading slash if missing. + * - Strip trailing slashes. + * + * Examples (with WIM_PATH_SEPARATOR == '/'): + * + * => / [ either NULL or empty string ] + * / => / + * \ => / + * hello => /hello + * \hello => /hello + * \hello => /hello + * /hello/ => /hello + * \hello/ => /hello + * /hello//1 => /hello/1 + * \\hello\\1\\ => /hello/1 */ tchar * canonicalize_wim_path(const tchar *wim_path) { - tchar *canonical_path; - tchar *p; - - if (wim_path == NULL) { - wim_path = T(""); - } else { - /* Strip leading path separators. */ - while (is_any_path_separator(*wim_path)) - wim_path++; - } + const tchar *in; + tchar *out; + tchar *result; - canonical_path = TSTRDUP(wim_path); - if (canonical_path == NULL) + in = wim_path; + if (!in) + in = T(""); + + result = MALLOC((1 + tstrlen(in) + 1) * sizeof(result[0])); + if (!result) return NULL; - /* Translate all path separators to WIM_PATH_SEPARATOR. */ - for (p = canonical_path; *p; p++) - if (is_any_path_separator(*p)) - *p = WIM_PATH_SEPARATOR; + out = result; + + /* Add leading slash if missing */ + if (!is_any_path_separator(*in)) + *out++ = WIM_PATH_SEPARATOR; + + while (*in) { + if (is_any_path_separator(*in)) { + /* Collapse multiple path separators into one */ + *out++ = WIM_PATH_SEPARATOR; + do { + in++; + } while (is_any_path_separator(*in)); + } else { + /* Copy non-path-separator character */ + *out++ = *in++; + } + } + + /* Remove trailing slash if existent */ + if (*(out - 1) == WIM_PATH_SEPARATOR && (out - 1) != result) + --out; - /* Strip trailing path separators. */ - while (p > canonical_path && *--p == WIM_PATH_SEPARATOR) - *p = T('\0'); + *out = T('\0'); - return canonical_path; + return result; } diff --git a/src/update_image.c b/src/update_image.c index 5dc32fc4..37d6593c 100644 --- a/src/update_image.c +++ b/src/update_image.c @@ -453,21 +453,33 @@ rollback_update(struct update_command_journal *j) free_update_command_journal(j); } +/* + * Set the name of @branch for placing it at @target in the WIM image. This + * assumes that @target is in "canonical form", as produced by + * canonicalize_wim_path(). + * + * Note: for the root target this produces the empty name. + */ static int set_branch_name(struct wim_dentry *branch, const utf16lechar *target) { const utf16lechar *p; + /* Find end of string. (We can assume it contains at least one + * character, the leading slash.) */ + wimlib_assert(target[0] == cpu_to_le16(WIM_PATH_SEPARATOR)); p = target; - while (*p) + do { p++; + } while (*p); - /* No trailing slashes allowed */ - wimlib_assert(p == target || *(p - 1) != cpu_to_le16(WIM_PATH_SEPARATOR)); - - while (p > target && *(p - 1) != cpu_to_le16(WIM_PATH_SEPARATOR)) + while (*(p - 1) != cpu_to_le16(WIM_PATH_SEPARATOR)) p--; + + /* We're assuming no trailing slashes. */ + wimlib_assert(*p || p == &target[1]); + return dentry_set_name_utf16le(branch, p); } @@ -840,7 +852,7 @@ execute_add_command(struct update_command_journal *j, config.prefix = fs_source_path; config.prefix_num_tchars = tstrlen(fs_source_path); - if (wim_target_path[0] == T('\0')) + if (WIMLIB_IS_WIM_ROOT_PATH(wim_target_path)) params.add_flags |= WIMLIB_ADD_FLAG_ROOT; ret = (*capture_tree)(&branch, fs_source_path, ¶ms); if (ret) @@ -849,7 +861,7 @@ execute_add_command(struct update_command_journal *j, if (progress_func) progress_func(WIMLIB_PROGRESS_MSG_SCAN_END, ¶ms.progress); - if (wim_target_path[0] == T('\0') && + if (WIMLIB_IS_WIM_ROOT_PATH(wim_target_path) && branch && !dentry_is_directory(branch)) { ERROR("\"%"TS"\" is not a directory!", fs_source_path); @@ -864,7 +876,7 @@ execute_add_command(struct update_command_journal *j, goto out_cleanup_after_capture; if (config_file && (add_flags & WIMLIB_ADD_FLAG_WIMBOOT) && - wim_target_path[0] == T('\0')) + WIMLIB_IS_WIM_ROOT_PATH(wim_target_path)) { params.add_flags = 0; params.progress_func = NULL; @@ -1243,12 +1255,7 @@ check_add_command(struct wimlib_update_command *cmd, WIMLIB_ADD_FLAG_NO_REPLACE)) return WIMLIB_ERR_INVALID_PARAM; - /* Are we adding the entire image or not? An empty wim_target_path - * indicates that the tree we're adding is to be placed in the root of - * the image. We consider this to be capturing the entire image, - * although it could potentially be an overlay on an existing root as - * well. */ - bool is_entire_image = cmd->add.wim_target_path[0] == T('\0'); + bool is_entire_image = WIMLIB_IS_WIM_ROOT_PATH(cmd->add.wim_target_path); #ifdef __WIN32__ /* Check for flags not supported on Windows */ -- 2.43.0