From 5218b1d7c83cf9e98ed6276e099844ae0d80abc2 Mon Sep 17 00:00:00 2001 From: Eric Biggers Date: Sun, 12 May 2013 01:49:24 -0500 Subject: [PATCH] Refactor Win32 capture/apply, UNIX apply --- Makefile.am | 9 +- src/add_image.c | 2 +- src/extract_image.c | 509 +------ src/header.c | 2 +- src/unix_apply.c | 470 +++++++ src/update_image.c | 6 +- src/wimlib_internal.h | 12 + src/win32.c | 3080 ----------------------------------------- src/win32_apply.c | 1244 +++++++++++++++++ src/win32_capture.c | 1176 ++++++++++++++++ src/win32_common.c | 643 +++++++++ src/win32_common.h | 47 + 12 files changed, 3629 insertions(+), 3571 deletions(-) create mode 100644 src/unix_apply.c delete mode 100644 src/win32.c create mode 100644 src/win32_apply.c create mode 100644 src/win32_capture.c create mode 100644 src/win32_common.c create mode 100644 src/win32_common.h diff --git a/Makefile.am b/Makefile.am index d75d065b..ab3f5e3c 100644 --- a/Makefile.am +++ b/Makefile.am @@ -107,9 +107,14 @@ imagex_SOURCES += programs/imagex-win32.c \ programs/wgetopt.c \ programs/wgetopt.h -libwim_la_SOURCES += src/win32.c +libwim_la_SOURCES += src/win32_common.c \ + src/win32_apply.c \ + src/win32_capture.c \ + src/win32.h \ + src/win32_common.h else -libwim_la_SOURCES += src/unix_capture.c +libwim_la_SOURCES += src/unix_apply.c \ + src/unix_capture.c endif install-exec-hook: diff --git a/src/add_image.c b/src/add_image.c index 263c2e75..fcb4c472 100644 --- a/src/add_image.c +++ b/src/add_image.c @@ -177,7 +177,7 @@ wimlib_add_image_multisource(WIMStruct *wim, } /* Delegate the work to wimlib_update_image(). */ - ret = wimlib_update_image(wim, wim->hdr.image_count, add_cmds, + ret = wimlib_update_image(wim, wim->hdr.image_count, add_cmds, num_sources, 0, progress_func); FREE(add_cmds); if (ret) diff --git a/src/extract_image.c b/src/extract_image.c index 2092cf71..7a46fe8f 100644 --- a/src/extract_image.c +++ b/src/extract_image.c @@ -25,502 +25,24 @@ #include "config.h" -#include +#include +#include +#include +#include #ifdef __WIN32__ # include "win32.h" -#else -# ifdef HAVE_UTIME_H -# include -# endif -# include "timestamp.h" -# include #endif -#include -#include -#include -#include -#include -#include - +#include "wimlib_internal.h" #include "dentry.h" #include "lookup_table.h" -#include "wimlib_internal.h" #include "xml.h" #ifdef WITH_NTFS_3G # include #endif -#ifdef HAVE_ALLOCA_H -# include -#endif - - -#ifndef __WIN32__ - -/* Returns the number of components of @path. */ -static unsigned -get_num_path_components(const char *path) -{ - unsigned num_components = 0; - while (*path) { - while (*path == '/') - path++; - if (*path) - num_components++; - while (*path && *path != '/') - path++; - } - return num_components; -} - -static const char * -path_next_part(const char *path) -{ - while (*path && *path != '/') - path++; - while (*path && *path == '/') - path++; - return path; -} - -static int -extract_regular_file_linked(struct wim_dentry *dentry, - const char *output_path, - struct apply_args *args, - struct wim_lookup_table_entry *lte) -{ - /* This mode overrides the normal hard-link extraction and - * instead either symlinks or hardlinks *all* identical files in - * the WIM, even if they are in a different image (in the case - * of a multi-image extraction) */ - - if (args->extract_flags & WIMLIB_EXTRACT_FLAG_HARDLINK) { - if (link(lte->extracted_file, output_path) != 0) { - ERROR_WITH_ERRNO("Failed to hard link " - "`%s' to `%s'", - output_path, lte->extracted_file); - return WIMLIB_ERR_LINK; - } - } else { - int num_path_components; - int num_output_dir_path_components; - size_t extracted_file_len; - char *p; - const char *p2; - size_t i; - - num_path_components = get_num_path_components(dentry->_full_path) - 1; - num_output_dir_path_components = get_num_path_components(args->target); - - if (args->extract_flags & WIMLIB_EXTRACT_FLAG_MULTI_IMAGE) { - num_path_components++; - num_output_dir_path_components--; - } - extracted_file_len = strlen(lte->extracted_file); - - char buf[extracted_file_len + 3 * num_path_components + 1]; - p = &buf[0]; - - for (i = 0; i < num_path_components; i++) { - *p++ = '.'; - *p++ = '.'; - *p++ = '/'; - } - p2 = lte->extracted_file; - while (*p2 == '/') - p2++; - while (num_output_dir_path_components > 0) { - p2 = path_next_part(p2); - num_output_dir_path_components--; - } - strcpy(p, p2); - if (symlink(buf, output_path) != 0) { - ERROR_WITH_ERRNO("Failed to symlink `%s' to `%s'", - buf, lte->extracted_file); - return WIMLIB_ERR_LINK; - } - } - return 0; -} - -static int -symlink_apply_unix_data(const char *link, - const struct wimlib_unix_data *unix_data) -{ - if (lchown(link, unix_data->uid, unix_data->gid)) { - if (errno == EPERM) { - /* Ignore */ - WARNING_WITH_ERRNO("failed to set symlink UNIX " - "owner/group on \"%s\"", link); - } else { - ERROR_WITH_ERRNO("failed to set symlink UNIX " - "owner/group on \"%s\"", link); - return WIMLIB_ERR_INVALID_DENTRY; - } - } - return 0; -} - -static int -fd_apply_unix_data(int fd, const char *path, - const struct wimlib_unix_data *unix_data, - int extract_flags) -{ - if (extract_flags & WIMLIB_EXTRACT_FLAG_NO_ACLS) - return 0; - - if (fchown(fd, unix_data->uid, unix_data->gid)) { - if (errno == EPERM && - !(extract_flags & WIMLIB_EXTRACT_FLAG_STRICT_ACLS)) - { - WARNING_WITH_ERRNO("failed to set file UNIX " - "owner/group on \"%s\"", path); - } else { - ERROR_WITH_ERRNO("failed to set file UNIX " - "owner/group on \"%s\"", path); - return (errno == EPERM) ? WIMLIB_ERR_INSUFFICIENT_PRIVILEGES_TO_EXTRACT : - WIMLIB_ERR_WRITE; - } - } - - if (fchmod(fd, unix_data->mode)) { - if (errno == EPERM && - !(extract_flags & WIMLIB_EXTRACT_FLAG_STRICT_ACLS)) - { - WARNING_WITH_ERRNO("failed to set UNIX file mode " - "on \"%s\"", path); - } else { - ERROR_WITH_ERRNO("failed to set UNIX file mode " - "on \"%s\"", path); - return (errno == EPERM) ? WIMLIB_ERR_INSUFFICIENT_PRIVILEGES_TO_EXTRACT : - WIMLIB_ERR_WRITE; - } - } - return 0; -} - -static int -dir_apply_unix_data(const char *dir, const struct wimlib_unix_data *unix_data, - int extract_flags) -{ - int dfd = open(dir, O_RDONLY); - int ret; - if (dfd >= 0) { - ret = fd_apply_unix_data(dfd, dir, unix_data, extract_flags); - if (close(dfd) && ret == 0) { - ERROR_WITH_ERRNO("can't close directory `%s'", dir); - ret = WIMLIB_ERR_WRITE; - } - } else { - ERROR_WITH_ERRNO("can't open directory `%s'", dir); - ret = WIMLIB_ERR_OPENDIR; - } - return ret; -} - -static int -extract_regular_file_unlinked(struct wim_dentry *dentry, - struct apply_args *args, - const char *output_path, - struct wim_lookup_table_entry *lte) -{ - /* Normal mode of extraction. Regular files and hard links are - * extracted in the way that they appear in the WIM. */ - - int out_fd; - int ret; - struct wim_inode *inode = dentry->d_inode; - - if (!((args->extract_flags & WIMLIB_EXTRACT_FLAG_MULTI_IMAGE) - && (args->extract_flags & (WIMLIB_EXTRACT_FLAG_SYMLINK | - WIMLIB_EXTRACT_FLAG_HARDLINK)))) - { - /* If the dentry is part of a hard link set of at least 2 - * dentries and one of the other dentries has already been - * extracted, make a hard link to the file corresponding to this - * already-extracted directory. Otherwise, extract the file and - * set the inode->i_extracted_file field so that other dentries - * in the hard link group can link to it. */ - if (inode->i_nlink > 1) { - if (inode->i_extracted_file) { - DEBUG("Extracting hard link `%s' => `%s'", - output_path, inode->i_extracted_file); - if (link(inode->i_extracted_file, output_path) != 0) { - ERROR_WITH_ERRNO("Failed to hard link " - "`%s' to `%s'", - output_path, - inode->i_extracted_file); - return WIMLIB_ERR_LINK; - } - return 0; - } - FREE(inode->i_extracted_file); - inode->i_extracted_file = STRDUP(output_path); - if (!inode->i_extracted_file) { - ERROR("Failed to allocate memory for filename"); - return WIMLIB_ERR_NOMEM; - } - } - } - - /* Extract the contents of the file to @output_path. */ - - out_fd = open(output_path, O_WRONLY | O_CREAT | O_TRUNC, 0644); - if (out_fd == -1) { - ERROR_WITH_ERRNO("Failed to open the file `%s' for writing", - output_path); - return WIMLIB_ERR_OPEN; - } - - if (!lte) { - /* Empty file with no lookup table entry */ - DEBUG("Empty file `%s'.", output_path); - ret = 0; - goto out_extract_unix_data; - } - - ret = extract_wim_resource_to_fd(lte, out_fd, wim_resource_size(lte)); - if (ret) { - ERROR("Failed to extract resource to `%s'", output_path); - goto out; - } - -out_extract_unix_data: - if (args->extract_flags & WIMLIB_EXTRACT_FLAG_UNIX_DATA) { - struct wimlib_unix_data unix_data; - ret = inode_get_unix_data(inode, &unix_data, NULL); - if (ret > 0) - ; - else if (ret < 0) - ret = 0; - else - ret = fd_apply_unix_data(out_fd, output_path, &unix_data, - args->extract_flags); - if (ret) - goto out; - } - if (lte) - args->progress.extract.completed_bytes += wim_resource_size(lte); -out: - if (close(out_fd) != 0) { - ERROR_WITH_ERRNO("Failed to close file `%s'", output_path); - if (ret == 0) - ret = WIMLIB_ERR_WRITE; - } - return ret; -} - -static int -extract_regular_file(struct wim_dentry *dentry, - struct apply_args *args, - const char *output_path) -{ - struct wim_lookup_table_entry *lte; - const struct wim_inode *inode = dentry->d_inode; - - lte = inode_unnamed_lte_resolved(inode); - - if (lte && (args->extract_flags & (WIMLIB_EXTRACT_FLAG_SYMLINK | - WIMLIB_EXTRACT_FLAG_HARDLINK))) - { - if (lte->extracted_file) { - return extract_regular_file_linked(dentry, output_path, args, lte); - } else { - lte->extracted_file = STRDUP(output_path); - if (!lte->extracted_file) - return WIMLIB_ERR_NOMEM; - } - } - return extract_regular_file_unlinked(dentry, args, output_path, lte); -} - -static int -extract_symlink(struct wim_dentry *dentry, - struct apply_args *args, - const char *output_path) -{ - char target[4096 + args->target_realpath_len]; - char *fixed_target; - const struct wim_inode *inode = dentry->d_inode; - - ssize_t ret = wim_inode_readlink(inode, - target + args->target_realpath_len, - sizeof(target) - args->target_realpath_len - 1); - struct wim_lookup_table_entry *lte; - - if (ret <= 0) { - ERROR("Could not read the symbolic link from dentry `%s'", - dentry->_full_path); - return WIMLIB_ERR_INVALID_DENTRY; - } - target[args->target_realpath_len + ret] = '\0'; - if (target[args->target_realpath_len] == '/' && - args->extract_flags & WIMLIB_EXTRACT_FLAG_RPFIX) - { - /* Fix absolute symbolic link target to point into the actual - * extraction destination */ - memcpy(target, args->target_realpath, - args->target_realpath_len); - fixed_target = target; - } else { - /* Keep same link target */ - 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, fixed_target); - return WIMLIB_ERR_LINK; - } - if (args->extract_flags & WIMLIB_EXTRACT_FLAG_UNIX_DATA) { - struct wimlib_unix_data unix_data; - ret = inode_get_unix_data(inode, &unix_data, NULL); - if (ret > 0) - ; - else if (ret < 0) - ret = 0; - else - ret = symlink_apply_unix_data(output_path, &unix_data); - if (ret) - return ret; - } - lte = inode_unnamed_lte_resolved(inode); - wimlib_assert(lte != NULL); - args->progress.extract.completed_bytes += wim_resource_size(lte); - return 0; -} - -#endif /* !__WIN32__ */ - -static int -extract_directory(struct wim_dentry *dentry, - const tchar *output_path, bool is_root, - int extract_flags) -{ - int ret; - struct stat stbuf; - - ret = tstat(output_path, &stbuf); - if (ret == 0) { - if (S_ISDIR(stbuf.st_mode)) { - /*if (!is_root)*/ - /*WARNING("`%s' already exists", output_path);*/ - goto dir_exists; - } else { - ERROR("`%"TS"' is not a directory", output_path); - return WIMLIB_ERR_MKDIR; - } - } else { - if (errno != ENOENT) { - ERROR_WITH_ERRNO("Failed to stat `%"TS"'", output_path); - return WIMLIB_ERR_STAT; - } - } - - if (tmkdir(output_path, S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH)) - { - ERROR_WITH_ERRNO("Cannot create directory `%"TS"'", output_path); - return WIMLIB_ERR_MKDIR; - } -dir_exists: - ret = 0; -#ifndef __WIN32__ - if (extract_flags & WIMLIB_EXTRACT_FLAG_UNIX_DATA) { - struct wimlib_unix_data unix_data; - ret = inode_get_unix_data(dentry->d_inode, &unix_data, NULL); - if (ret > 0) - ; - else if (ret < 0) - ret = 0; - else - ret = dir_apply_unix_data(output_path, &unix_data, - extract_flags); - } -#endif - return ret; -} - -#ifndef __WIN32__ -static int -unix_do_apply_dentry(const char *output_path, size_t output_path_len, - struct wim_dentry *dentry, struct apply_args *args) -{ - const struct wim_inode *inode = dentry->d_inode; - - if (inode_is_symlink(inode)) - return extract_symlink(dentry, args, output_path); - else if (inode_is_directory(inode)) - return extract_directory((args->extract_flags & - WIMLIB_EXTRACT_FLAG_UNIX_DATA) ? dentry : NULL, - output_path, false, args->extract_flags); - else - return extract_regular_file(dentry, args, output_path); -} - -static int -unix_do_apply_dentry_timestamps(const char *output_path, - size_t output_path_len, - struct wim_dentry *dentry, - struct apply_args *args) -{ - int ret; - const struct wim_inode *inode = dentry->d_inode; - -#ifdef HAVE_UTIMENSAT - /* Convert the WIM timestamps, which are accurate to 100 nanoseconds, - * into `struct timespec's for passing to utimensat(), which is accurate - * to 1 nanosecond. */ - - struct timespec ts[2]; - ts[0] = wim_timestamp_to_timespec(inode->i_last_access_time); - ts[1] = wim_timestamp_to_timespec(inode->i_last_write_time); - ret = utimensat(AT_FDCWD, output_path, ts, AT_SYMLINK_NOFOLLOW); - if (ret) - ret = errno; -#else - ret = ENOSYS; -#endif - - if (ret == ENOSYS) { - /* utimensat() not implemented or not available */ - #ifdef HAVE_LUTIMES - /* Convert the WIM timestamps, which are accurate to 100 - * nanoseconds, into `struct timeval's for passing to lutimes(), - * which is accurate to 1 microsecond. */ - struct timeval tv[2]; - tv[0] = wim_timestamp_to_timeval(inode->i_last_access_time); - tv[1] = wim_timestamp_to_timeval(inode->i_last_write_time); - ret = lutimes(output_path, tv); - if (ret) - ret = errno; - #endif - } - - if (ret == ENOSYS) { - /* utimensat() and lutimes() both not implemented or not - * available */ - #ifdef HAVE_UTIME - /* Convert the WIM timestamps, which are accurate to 100 - * nanoseconds, into a `struct utimbuf's for passing to - * utime(), which is accurate to 1 second. */ - struct utimbuf buf; - buf.actime = wim_timestamp_to_unix(inode->i_last_access_time); - buf.modtime = wim_timestamp_to_unix(inode->i_last_write_time); - ret = utime(output_path, &buf); - #endif - } - if (ret && args->num_utime_warnings < 10) { - WARNING_WITH_ERRNO("Failed to set timestamp on file `%s'", - output_path); - args->num_utime_warnings++; - } - return 0; -} -#endif /* !__WIN32__ */ - static int do_apply_op(struct wim_dentry *dentry, struct apply_args *args, int (*apply_dentry_func)(const tchar *, size_t, @@ -1392,10 +914,25 @@ extract_all_images(WIMStruct *wim, int ret; int image; const tchar *image_name; + struct stat stbuf; - ret = extract_directory(NULL, target, true, 0); - if (ret) - return ret; + if (tstat(target, &stbuf)) { + if (errno == ENOENT) + { + if (tmkdir(target, S_IRWXU | S_IRGRP | S_IXGRP | + S_IROTH | S_IXOTH)) + { + ERROR_WITH_ERRNO("Failed to create directory \"%"TS"\"", target); + return WIMLIB_ERR_MKDIR; + } + } else { + ERROR_WITH_ERRNO("Failed to stat \"%"TS"\"", target); + return WIMLIB_ERR_STAT; + } + } else if (!S_ISDIR(stbuf.st_mode)) { + ERROR("\"%"TS"\" is not a directory", target); + return WIMLIB_ERR_NOTDIR; + } tmemcpy(buf, target, output_path_len); buf[output_path_len] = T('/'); diff --git a/src/header.c b/src/header.c index ef0e5783..6017cbd9 100644 --- a/src/header.c +++ b/src/header.c @@ -147,7 +147,7 @@ read_header(const tchar *filename, int in_fd, * * @hdr: A pointer to a struct wim_header structure that describes the header. * @out_fd: The file descriptor to the WIM file, opened for writing. - * + * * Returns zero on success, nonzero on failure. */ int diff --git a/src/unix_apply.c b/src/unix_apply.c new file mode 100644 index 00000000..97c1c83b --- /dev/null +++ b/src/unix_apply.c @@ -0,0 +1,470 @@ +#include "config.h" + +#ifdef HAVE_UTIME_H +# include +#endif +#include +#include +#include +#include +#include +#include +#include + +#include "timestamp.h" +#include "wimlib_internal.h" +#include "lookup_table.h" + +/* Returns the number of components of @path. */ +static unsigned +get_num_path_components(const char *path) +{ + unsigned num_components = 0; + while (*path) { + while (*path == '/') + path++; + if (*path) + num_components++; + while (*path && *path != '/') + path++; + } + return num_components; +} + +static const char * +path_next_part(const char *path) +{ + while (*path && *path != '/') + path++; + while (*path && *path == '/') + path++; + return path; +} + +static int +extract_regular_file_linked(struct wim_dentry *dentry, + const char *output_path, + struct apply_args *args, + struct wim_lookup_table_entry *lte) +{ + /* This mode overrides the normal hard-link extraction and + * instead either symlinks or hardlinks *all* identical files in + * the WIM, even if they are in a different image (in the case + * of a multi-image extraction) */ + + if (args->extract_flags & WIMLIB_EXTRACT_FLAG_HARDLINK) { + if (link(lte->extracted_file, output_path) != 0) { + ERROR_WITH_ERRNO("Failed to hard link " + "`%s' to `%s'", + output_path, lte->extracted_file); + return WIMLIB_ERR_LINK; + } + } else { + int num_path_components; + int num_output_dir_path_components; + size_t extracted_file_len; + char *p; + const char *p2; + size_t i; + + num_path_components = get_num_path_components(dentry->_full_path) - 1; + num_output_dir_path_components = get_num_path_components(args->target); + + if (args->extract_flags & WIMLIB_EXTRACT_FLAG_MULTI_IMAGE) { + num_path_components++; + num_output_dir_path_components--; + } + extracted_file_len = strlen(lte->extracted_file); + + char buf[extracted_file_len + 3 * num_path_components + 1]; + p = &buf[0]; + + for (i = 0; i < num_path_components; i++) { + *p++ = '.'; + *p++ = '.'; + *p++ = '/'; + } + p2 = lte->extracted_file; + while (*p2 == '/') + p2++; + while (num_output_dir_path_components > 0) { + p2 = path_next_part(p2); + num_output_dir_path_components--; + } + strcpy(p, p2); + if (symlink(buf, output_path) != 0) { + ERROR_WITH_ERRNO("Failed to symlink `%s' to `%s'", + buf, lte->extracted_file); + return WIMLIB_ERR_LINK; + } + } + return 0; +} + +static int +symlink_apply_unix_data(const char *link, + const struct wimlib_unix_data *unix_data) +{ + if (lchown(link, unix_data->uid, unix_data->gid)) { + if (errno == EPERM) { + /* Ignore */ + WARNING_WITH_ERRNO("failed to set symlink UNIX " + "owner/group on \"%s\"", link); + } else { + ERROR_WITH_ERRNO("failed to set symlink UNIX " + "owner/group on \"%s\"", link); + return WIMLIB_ERR_INVALID_DENTRY; + } + } + return 0; +} + +static int +fd_apply_unix_data(int fd, const char *path, + const struct wimlib_unix_data *unix_data, + int extract_flags) +{ + if (extract_flags & WIMLIB_EXTRACT_FLAG_NO_ACLS) + return 0; + + if (fchown(fd, unix_data->uid, unix_data->gid)) { + if (errno == EPERM && + !(extract_flags & WIMLIB_EXTRACT_FLAG_STRICT_ACLS)) + { + WARNING_WITH_ERRNO("failed to set file UNIX " + "owner/group on \"%s\"", path); + } else { + ERROR_WITH_ERRNO("failed to set file UNIX " + "owner/group on \"%s\"", path); + return (errno == EPERM) ? WIMLIB_ERR_INSUFFICIENT_PRIVILEGES_TO_EXTRACT : + WIMLIB_ERR_WRITE; + } + } + + if (fchmod(fd, unix_data->mode)) { + if (errno == EPERM && + !(extract_flags & WIMLIB_EXTRACT_FLAG_STRICT_ACLS)) + { + WARNING_WITH_ERRNO("failed to set UNIX file mode " + "on \"%s\"", path); + } else { + ERROR_WITH_ERRNO("failed to set UNIX file mode " + "on \"%s\"", path); + return (errno == EPERM) ? WIMLIB_ERR_INSUFFICIENT_PRIVILEGES_TO_EXTRACT : + WIMLIB_ERR_WRITE; + } + } + return 0; +} + +static int +dir_apply_unix_data(const char *dir, const struct wimlib_unix_data *unix_data, + int extract_flags) +{ + int dfd = open(dir, O_RDONLY); + int ret; + if (dfd >= 0) { + ret = fd_apply_unix_data(dfd, dir, unix_data, extract_flags); + if (close(dfd) && ret == 0) { + ERROR_WITH_ERRNO("can't close directory `%s'", dir); + ret = WIMLIB_ERR_WRITE; + } + } else { + ERROR_WITH_ERRNO("can't open directory `%s'", dir); + ret = WIMLIB_ERR_OPENDIR; + } + return ret; +} + +static int +extract_regular_file_unlinked(struct wim_dentry *dentry, + struct apply_args *args, + const char *output_path, + struct wim_lookup_table_entry *lte) +{ + /* Normal mode of extraction. Regular files and hard links are + * extracted in the way that they appear in the WIM. */ + + int out_fd; + int ret; + struct wim_inode *inode = dentry->d_inode; + + if (!((args->extract_flags & WIMLIB_EXTRACT_FLAG_MULTI_IMAGE) + && (args->extract_flags & (WIMLIB_EXTRACT_FLAG_SYMLINK | + WIMLIB_EXTRACT_FLAG_HARDLINK)))) + { + /* If the dentry is part of a hard link set of at least 2 + * dentries and one of the other dentries has already been + * extracted, make a hard link to the file corresponding to this + * already-extracted directory. Otherwise, extract the file and + * set the inode->i_extracted_file field so that other dentries + * in the hard link group can link to it. */ + if (inode->i_nlink > 1) { + if (inode->i_extracted_file) { + DEBUG("Extracting hard link `%s' => `%s'", + output_path, inode->i_extracted_file); + if (link(inode->i_extracted_file, output_path) != 0) { + ERROR_WITH_ERRNO("Failed to hard link " + "`%s' to `%s'", + output_path, + inode->i_extracted_file); + return WIMLIB_ERR_LINK; + } + return 0; + } + FREE(inode->i_extracted_file); + inode->i_extracted_file = STRDUP(output_path); + if (!inode->i_extracted_file) { + ERROR("Failed to allocate memory for filename"); + return WIMLIB_ERR_NOMEM; + } + } + } + + /* Extract the contents of the file to @output_path. */ + + out_fd = open(output_path, O_WRONLY | O_CREAT | O_TRUNC, 0644); + if (out_fd == -1) { + ERROR_WITH_ERRNO("Failed to open the file `%s' for writing", + output_path); + return WIMLIB_ERR_OPEN; + } + + if (!lte) { + /* Empty file with no lookup table entry */ + DEBUG("Empty file `%s'.", output_path); + ret = 0; + goto out_extract_unix_data; + } + + ret = extract_wim_resource_to_fd(lte, out_fd, wim_resource_size(lte)); + if (ret) { + ERROR("Failed to extract resource to `%s'", output_path); + goto out; + } + +out_extract_unix_data: + if (args->extract_flags & WIMLIB_EXTRACT_FLAG_UNIX_DATA) { + struct wimlib_unix_data unix_data; + ret = inode_get_unix_data(inode, &unix_data, NULL); + if (ret > 0) + ; + else if (ret < 0) + ret = 0; + else + ret = fd_apply_unix_data(out_fd, output_path, &unix_data, + args->extract_flags); + if (ret) + goto out; + } + if (lte) + args->progress.extract.completed_bytes += wim_resource_size(lte); +out: + if (close(out_fd) != 0) { + ERROR_WITH_ERRNO("Failed to close file `%s'", output_path); + if (ret == 0) + ret = WIMLIB_ERR_WRITE; + } + return ret; +} + +static int +extract_regular_file(struct wim_dentry *dentry, + struct apply_args *args, + const char *output_path) +{ + struct wim_lookup_table_entry *lte; + const struct wim_inode *inode = dentry->d_inode; + + lte = inode_unnamed_lte_resolved(inode); + + if (lte && (args->extract_flags & (WIMLIB_EXTRACT_FLAG_SYMLINK | + WIMLIB_EXTRACT_FLAG_HARDLINK))) + { + if (lte->extracted_file) { + return extract_regular_file_linked(dentry, output_path, args, lte); + } else { + lte->extracted_file = STRDUP(output_path); + if (!lte->extracted_file) + return WIMLIB_ERR_NOMEM; + } + } + return extract_regular_file_unlinked(dentry, args, output_path, lte); +} + +static int +extract_symlink(struct wim_dentry *dentry, + struct apply_args *args, + const char *output_path) +{ + char target[4096 + args->target_realpath_len]; + char *fixed_target; + const struct wim_inode *inode = dentry->d_inode; + + ssize_t ret = wim_inode_readlink(inode, + target + args->target_realpath_len, + sizeof(target) - args->target_realpath_len - 1); + struct wim_lookup_table_entry *lte; + + if (ret <= 0) { + ERROR("Could not read the symbolic link from dentry `%s'", + dentry->_full_path); + return WIMLIB_ERR_INVALID_DENTRY; + } + target[args->target_realpath_len + ret] = '\0'; + if (target[args->target_realpath_len] == '/' && + args->extract_flags & WIMLIB_EXTRACT_FLAG_RPFIX) + { + /* Fix absolute symbolic link target to point into the actual + * extraction destination */ + memcpy(target, args->target_realpath, + args->target_realpath_len); + fixed_target = target; + } else { + /* Keep same link target */ + 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, fixed_target); + return WIMLIB_ERR_LINK; + } + if (args->extract_flags & WIMLIB_EXTRACT_FLAG_UNIX_DATA) { + struct wimlib_unix_data unix_data; + ret = inode_get_unix_data(inode, &unix_data, NULL); + if (ret > 0) + ; + else if (ret < 0) + ret = 0; + else + ret = symlink_apply_unix_data(output_path, &unix_data); + if (ret) + return ret; + } + lte = inode_unnamed_lte_resolved(inode); + wimlib_assert(lte != NULL); + args->progress.extract.completed_bytes += wim_resource_size(lte); + return 0; +} + +static int +extract_directory(struct wim_dentry *dentry, const tchar *output_path, + int extract_flags) +{ + int ret; + struct stat stbuf; + + ret = tstat(output_path, &stbuf); + if (ret == 0) { + if (S_ISDIR(stbuf.st_mode)) { + /*if (!is_root)*/ + /*WARNING("`%s' already exists", output_path);*/ + goto dir_exists; + } else { + ERROR("`%"TS"' is not a directory", output_path); + return WIMLIB_ERR_MKDIR; + } + } else { + if (errno != ENOENT) { + ERROR_WITH_ERRNO("Failed to stat `%"TS"'", output_path); + return WIMLIB_ERR_STAT; + } + } + + if (tmkdir(output_path, S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH)) + { + ERROR_WITH_ERRNO("Cannot create directory `%"TS"'", output_path); + return WIMLIB_ERR_MKDIR; + } +dir_exists: + ret = 0; +#ifndef __WIN32__ + if (extract_flags & WIMLIB_EXTRACT_FLAG_UNIX_DATA) { + struct wimlib_unix_data unix_data; + ret = inode_get_unix_data(dentry->d_inode, &unix_data, NULL); + if (ret > 0) + ; + else if (ret < 0) + ret = 0; + else + ret = dir_apply_unix_data(output_path, &unix_data, + extract_flags); + } +#endif + return ret; +} + +int +unix_do_apply_dentry(const char *output_path, size_t output_path_len, + struct wim_dentry *dentry, struct apply_args *args) +{ + const struct wim_inode *inode = dentry->d_inode; + + if (inode_is_symlink(inode)) + return extract_symlink(dentry, args, output_path); + else if (inode_is_directory(inode)) + return extract_directory(dentry, output_path, args->extract_flags); + else + return extract_regular_file(dentry, args, output_path); +} + +int +unix_do_apply_dentry_timestamps(const char *output_path, + size_t output_path_len, + struct wim_dentry *dentry, + struct apply_args *args) +{ + int ret; + const struct wim_inode *inode = dentry->d_inode; + +#ifdef HAVE_UTIMENSAT + /* Convert the WIM timestamps, which are accurate to 100 nanoseconds, + * into `struct timespec's for passing to utimensat(), which is accurate + * to 1 nanosecond. */ + + struct timespec ts[2]; + ts[0] = wim_timestamp_to_timespec(inode->i_last_access_time); + ts[1] = wim_timestamp_to_timespec(inode->i_last_write_time); + ret = utimensat(AT_FDCWD, output_path, ts, AT_SYMLINK_NOFOLLOW); + if (ret) + ret = errno; +#else + ret = ENOSYS; +#endif + + if (ret == ENOSYS) { + /* utimensat() not implemented or not available */ + #ifdef HAVE_LUTIMES + /* Convert the WIM timestamps, which are accurate to 100 + * nanoseconds, into `struct timeval's for passing to lutimes(), + * which is accurate to 1 microsecond. */ + struct timeval tv[2]; + tv[0] = wim_timestamp_to_timeval(inode->i_last_access_time); + tv[1] = wim_timestamp_to_timeval(inode->i_last_write_time); + ret = lutimes(output_path, tv); + if (ret) + ret = errno; + #endif + } + + if (ret == ENOSYS) { + /* utimensat() and lutimes() both not implemented or not + * available */ + #ifdef HAVE_UTIME + /* Convert the WIM timestamps, which are accurate to 100 + * nanoseconds, into a `struct utimbuf's for passing to + * utime(), which is accurate to 1 second. */ + struct utimbuf buf; + buf.actime = wim_timestamp_to_unix(inode->i_last_access_time); + buf.modtime = wim_timestamp_to_unix(inode->i_last_write_time); + ret = utime(output_path, &buf); + #endif + } + if (ret && args->num_utime_warnings < 10) { + WARNING_WITH_ERRNO("Failed to set timestamp on file `%s'", + output_path); + args->num_utime_warnings++; + } + return 0; +} diff --git a/src/update_image.c b/src/update_image.c index 788cd9e5..ecfef1ff 100644 --- a/src/update_image.c +++ b/src/update_image.c @@ -28,6 +28,10 @@ #include "xml.h" #include +#ifdef __WIN32__ +# include "win32.h" +#endif + /* Overlays @branch onto @target, both of which must be directories. */ static int do_overlay(struct wim_dentry *target, struct wim_dentry *branch) @@ -324,7 +328,7 @@ execute_delete_command(WIMStruct *wim, return 0; } -/* +/* * Rename a file or directory in the WIM. * * This is also called from wimfs_rename() in the FUSE mount code. diff --git a/src/wimlib_internal.h b/src/wimlib_internal.h index bde81152..9e55f371 100644 --- a/src/wimlib_internal.h +++ b/src/wimlib_internal.h @@ -743,6 +743,18 @@ write_security_data(const struct wim_security_data *sd, u8 *p); extern void free_security_data(struct wim_security_data *sd); +/* unix_apply.c */ +#ifndef __WIN32__ +extern int +unix_do_apply_dentry(const char *output_path, size_t output_path_len, + struct wim_dentry *dentry, struct apply_args *args); +extern int +unix_do_apply_dentry_timestamps(const char *output_path, + size_t output_path_len, + struct wim_dentry *dentry, + struct apply_args *args); +#endif + /* unix_capture.c */ #ifndef __WIN32__ extern int diff --git a/src/win32.c b/src/win32.c deleted file mode 100644 index 6a767472..00000000 --- a/src/win32.c +++ /dev/null @@ -1,3080 +0,0 @@ -/* - * win32.c - * - * Most of the library code specific to native Windows builds is in here. - */ - -/* - * Copyright (C) 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/. - */ - -#ifdef __WIN32__ - -#include "config.h" -#include -#include -#include -#include /* for PathMatchSpecW() */ -#include /* for SetSecurityInfo() */ -#ifdef ERROR /* windows.h defines this */ -# undef ERROR -#endif - -#include "win32.h" -#include "dentry.h" -#include "lookup_table.h" -#include "security.h" -#include "endianness.h" -#include "buffer_io.h" -#include - -#include - -#define MAX_GET_SD_ACCESS_DENIED_WARNINGS 1 -#define MAX_GET_SACL_PRIV_NOTHELD_WARNINGS 1 -#define MAX_CREATE_HARD_LINK_WARNINGS 5 -#define MAX_CREATE_SOFT_LINK_WARNINGS 5 -struct win32_capture_state { - unsigned long num_get_sd_access_denied; - unsigned long num_get_sacl_priv_notheld; -}; - -#define MAX_SET_SD_ACCESS_DENIED_WARNINGS 1 -#define MAX_SET_SACL_PRIV_NOTHELD_WARNINGS 1 - -#ifdef ENABLE_ERROR_MESSAGES -static void -win32_error(u32 err_code) -{ - wchar_t *buffer; - DWORD nchars; - nchars = FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM | - FORMAT_MESSAGE_ALLOCATE_BUFFER, - NULL, err_code, 0, - (wchar_t*)&buffer, 0, NULL); - if (nchars == 0) { - ERROR("Error printing error message! " - "Computer will self-destruct in 3 seconds."); - } else { - ERROR("Win32 error: %ls", buffer); - LocalFree(buffer); - } -} -#else /* ENABLE_ERROR_MESSAGES */ -# define win32_error(err_code) -#endif /* !ENABLE_ERROR_MESSAGES */ - -static int -win32_error_to_errno(DWORD err_code) -{ - /* This mapping is that used in Cygwin. - * Some of these choices are arbitrary. */ - switch (err_code) { - case ERROR_ACCESS_DENIED: - return EACCES; - case ERROR_ACTIVE_CONNECTIONS: - return EAGAIN; - case ERROR_ALREADY_EXISTS: - return EEXIST; - case ERROR_BAD_DEVICE: - return ENODEV; - case ERROR_BAD_EXE_FORMAT: - return ENOEXEC; - case ERROR_BAD_NETPATH: - return ENOENT; - case ERROR_BAD_NET_NAME: - return ENOENT; - case ERROR_BAD_NET_RESP: - return ENOSYS; - case ERROR_BAD_PATHNAME: - return ENOENT; - case ERROR_BAD_PIPE: - return EINVAL; - case ERROR_BAD_UNIT: - return ENODEV; - case ERROR_BAD_USERNAME: - return EINVAL; - case ERROR_BEGINNING_OF_MEDIA: - return EIO; - case ERROR_BROKEN_PIPE: - return EPIPE; - case ERROR_BUSY: - return EBUSY; - case ERROR_BUS_RESET: - return EIO; - case ERROR_CALL_NOT_IMPLEMENTED: - return ENOSYS; - case ERROR_CANNOT_MAKE: - return EPERM; - case ERROR_CHILD_NOT_COMPLETE: - return EBUSY; - case ERROR_COMMITMENT_LIMIT: - return EAGAIN; - case ERROR_CRC: - return EIO; - case ERROR_DEVICE_DOOR_OPEN: - return EIO; - case ERROR_DEVICE_IN_USE: - return EAGAIN; - case ERROR_DEVICE_REQUIRES_CLEANING: - return EIO; - case ERROR_DIRECTORY: - return ENOTDIR; - case ERROR_DIR_NOT_EMPTY: - return ENOTEMPTY; - case ERROR_DISK_CORRUPT: - return EIO; - case ERROR_DISK_FULL: - return ENOSPC; -#ifdef ENOTUNIQ - case ERROR_DUP_NAME: - return ENOTUNIQ; -#endif - case ERROR_EAS_DIDNT_FIT: - return ENOSPC; - case ERROR_EAS_NOT_SUPPORTED: - return ENOTSUP; - case ERROR_EA_LIST_INCONSISTENT: - return EINVAL; - case ERROR_EA_TABLE_FULL: - return ENOSPC; - case ERROR_END_OF_MEDIA: - return ENOSPC; - case ERROR_EOM_OVERFLOW: - return EIO; - case ERROR_EXE_MACHINE_TYPE_MISMATCH: - return ENOEXEC; - case ERROR_EXE_MARKED_INVALID: - return ENOEXEC; - case ERROR_FILEMARK_DETECTED: - return EIO; - case ERROR_FILENAME_EXCED_RANGE: - return ENAMETOOLONG; - case ERROR_FILE_CORRUPT: - return EEXIST; - case ERROR_FILE_EXISTS: - return EEXIST; - case ERROR_FILE_INVALID: - return ENXIO; - case ERROR_FILE_NOT_FOUND: - return ENOENT; - case ERROR_HANDLE_DISK_FULL: - return ENOSPC; -#ifdef ENODATA - case ERROR_HANDLE_EOF: - return ENODATA; -#endif - case ERROR_INVALID_ADDRESS: - return EINVAL; - case ERROR_INVALID_AT_INTERRUPT_TIME: - return EINTR; - case ERROR_INVALID_BLOCK_LENGTH: - return EIO; - case ERROR_INVALID_DATA: - return EINVAL; - case ERROR_INVALID_DRIVE: - return ENODEV; - case ERROR_INVALID_EA_NAME: - return EINVAL; - case ERROR_INVALID_EXE_SIGNATURE: - return ENOEXEC; -#ifdef EBADRQC - case ERROR_INVALID_FUNCTION: - return EBADRQC; -#endif - case ERROR_INVALID_HANDLE: - return EBADF; - case ERROR_INVALID_NAME: - return ENOENT; - case ERROR_INVALID_PARAMETER: - return EINVAL; - case ERROR_INVALID_SIGNAL_NUMBER: - return EINVAL; - case ERROR_IOPL_NOT_ENABLED: - return ENOEXEC; - case ERROR_IO_DEVICE: - return EIO; - case ERROR_IO_INCOMPLETE: - return EAGAIN; - case ERROR_IO_PENDING: - return EAGAIN; - case ERROR_LOCK_VIOLATION: - return EBUSY; - case ERROR_MAX_THRDS_REACHED: - return EAGAIN; - case ERROR_META_EXPANSION_TOO_LONG: - return EINVAL; - case ERROR_MOD_NOT_FOUND: - return ENOENT; -#ifdef EMSGSIZE - case ERROR_MORE_DATA: - return EMSGSIZE; -#endif - case ERROR_NEGATIVE_SEEK: - return EINVAL; - case ERROR_NETNAME_DELETED: - return ENOENT; - case ERROR_NOACCESS: - return EFAULT; - case ERROR_NONE_MAPPED: - return EINVAL; - case ERROR_NONPAGED_SYSTEM_RESOURCES: - return EAGAIN; -#ifdef ENOLINK - case ERROR_NOT_CONNECTED: - return ENOLINK; -#endif - case ERROR_NOT_ENOUGH_MEMORY: - return ENOMEM; - case ERROR_NOT_OWNER: - return EPERM; -#ifdef ENOMEDIUM - case ERROR_NOT_READY: - return ENOMEDIUM; -#endif - case ERROR_NOT_SAME_DEVICE: - return EXDEV; - case ERROR_NOT_SUPPORTED: - return ENOSYS; - case ERROR_NO_DATA: - return EPIPE; - case ERROR_NO_DATA_DETECTED: - return EIO; -#ifdef ENOMEDIUM - case ERROR_NO_MEDIA_IN_DRIVE: - return ENOMEDIUM; -#endif -#ifdef ENMFILE - case ERROR_NO_MORE_FILES: - return ENMFILE; -#endif -#ifdef ENMFILE - case ERROR_NO_MORE_ITEMS: - return ENMFILE; -#endif - case ERROR_NO_MORE_SEARCH_HANDLES: - return ENFILE; - case ERROR_NO_PROC_SLOTS: - return EAGAIN; - case ERROR_NO_SIGNAL_SENT: - return EIO; - case ERROR_NO_SYSTEM_RESOURCES: - return EFBIG; - case ERROR_NO_TOKEN: - return EINVAL; - case ERROR_OPEN_FAILED: - return EIO; - case ERROR_OPEN_FILES: - return EAGAIN; - case ERROR_OUTOFMEMORY: - return ENOMEM; - case ERROR_PAGED_SYSTEM_RESOURCES: - return EAGAIN; - case ERROR_PAGEFILE_QUOTA: - return EAGAIN; - case ERROR_PATH_NOT_FOUND: - return ENOENT; - case ERROR_PIPE_BUSY: - return EBUSY; - case ERROR_PIPE_CONNECTED: - return EBUSY; -#ifdef ECOMM - case ERROR_PIPE_LISTENING: - return ECOMM; - case ERROR_PIPE_NOT_CONNECTED: - return ECOMM; -#endif - case ERROR_POSSIBLE_DEADLOCK: - return EDEADLOCK; - case ERROR_PRIVILEGE_NOT_HELD: - return EPERM; - case ERROR_PROCESS_ABORTED: - return EFAULT; - case ERROR_PROC_NOT_FOUND: - return ESRCH; -#ifdef ENONET - case ERROR_REM_NOT_LIST: - return ENONET; -#endif - case ERROR_SECTOR_NOT_FOUND: - return EINVAL; - case ERROR_SEEK: - return EINVAL; - case ERROR_SETMARK_DETECTED: - return EIO; - case ERROR_SHARING_BUFFER_EXCEEDED: - return ENOLCK; - case ERROR_SHARING_VIOLATION: - return EBUSY; - case ERROR_SIGNAL_PENDING: - return EBUSY; - case ERROR_SIGNAL_REFUSED: - return EIO; -#ifdef ELIBBAD - case ERROR_SXS_CANT_GEN_ACTCTX: - return ELIBBAD; -#endif - case ERROR_THREAD_1_INACTIVE: - return EINVAL; - case ERROR_TOO_MANY_LINKS: - return EMLINK; - case ERROR_TOO_MANY_OPEN_FILES: - return EMFILE; - case ERROR_WAIT_NO_CHILDREN: - return ECHILD; - case ERROR_WORKING_SET_QUOTA: - return EAGAIN; - case ERROR_WRITE_PROTECT: - return EROFS; - default: - return -1; - } -} - -static void -set_errno_from_GetLastError() -{ - errno = win32_error_to_errno(GetLastError()); -} - -/* Pointers to functions that are not available on all targetted versions of - * Windows (XP and later). NOTE: The WINAPI annotations seem to be important; I - * assume it specifies a certain calling convention. */ - -/* Vista and later */ -static HANDLE (WINAPI *win32func_FindFirstStreamW)(LPCWSTR lpFileName, - STREAM_INFO_LEVELS InfoLevel, - LPVOID lpFindStreamData, - DWORD dwFlags) = NULL; - -/* Vista and later */ -static BOOL (WINAPI *win32func_FindNextStreamW)(HANDLE hFindStream, - LPVOID lpFindStreamData) = NULL; - -static HMODULE hKernel32 = NULL; - -/* Try to dynamically load some functions */ -void -win32_global_init() -{ - DWORD err; - - if (hKernel32 == NULL) { - DEBUG("Loading Kernel32.dll"); - hKernel32 = LoadLibraryW(L"Kernel32.dll"); - if (hKernel32 == NULL) { - err = GetLastError(); - WARNING("Can't load Kernel32.dll"); - win32_error(err); - return; - } - } - - DEBUG("Looking for FindFirstStreamW"); - win32func_FindFirstStreamW = (void*)GetProcAddress(hKernel32, "FindFirstStreamW"); - if (!win32func_FindFirstStreamW) { - WARNING("Could not find function FindFirstStreamW() in Kernel32.dll!"); - WARNING("Capturing alternate data streams will not be supported."); - return; - } - - DEBUG("Looking for FindNextStreamW"); - win32func_FindNextStreamW = (void*)GetProcAddress(hKernel32, "FindNextStreamW"); - if (!win32func_FindNextStreamW) { - WARNING("Could not find function FindNextStreamW() in Kernel32.dll!"); - WARNING("Capturing alternate data streams will not be supported."); - win32func_FindFirstStreamW = NULL; - } -} - -void -win32_global_cleanup() -{ - if (hKernel32 != NULL) { - DEBUG("Closing Kernel32.dll"); - FreeLibrary(hKernel32); - hKernel32 = NULL; - } -} - -static const wchar_t *capture_access_denied_msg = -L" If you are not running this program as the administrator, you may\n" - " need to do so, so that all data and metadata can be backed up.\n" - " Otherwise, there may be no way to access the desired data or\n" - " metadata without taking ownership of the file or directory.\n" - ; - -static const wchar_t *apply_access_denied_msg = -L"If you are not running this program as the administrator, you may\n" - " need to do so, so that all data and metadata can be extracted\n" - " exactly as the origignal copy. However, if you do not care that\n" - " the security descriptors are extracted correctly, you could run\n" - " `wimlib-imagex apply' with the --no-acls flag instead.\n" - ; - -static HANDLE -win32_open_existing_file(const wchar_t *path, DWORD dwDesiredAccess) -{ - return CreateFileW(path, - dwDesiredAccess, - FILE_SHARE_READ, - NULL, /* lpSecurityAttributes */ - OPEN_EXISTING, - FILE_FLAG_BACKUP_SEMANTICS | - FILE_FLAG_OPEN_REPARSE_POINT, - NULL /* hTemplateFile */); -} - -HANDLE -win32_open_file_data_only(const wchar_t *path) -{ - return win32_open_existing_file(path, FILE_READ_DATA); -} - -int -read_win32_file_prefix(const struct wim_lookup_table_entry *lte, - u64 size, - consume_data_callback_t cb, - void *ctx_or_buf, - int _ignored_flags) -{ - int ret = 0; - void *out_buf; - DWORD err; - u64 bytes_remaining; - - HANDLE hFile = win32_open_file_data_only(lte->file_on_disk); - if (hFile == INVALID_HANDLE_VALUE) { - err = GetLastError(); - ERROR("Failed to open \"%ls\"", lte->file_on_disk); - win32_error(err); - return WIMLIB_ERR_OPEN; - } - - if (cb) - out_buf = alloca(WIM_CHUNK_SIZE); - else - out_buf = ctx_or_buf; - - bytes_remaining = size; - while (bytes_remaining) { - DWORD bytesToRead, bytesRead; - - bytesToRead = min(WIM_CHUNK_SIZE, bytes_remaining); - if (!ReadFile(hFile, out_buf, bytesToRead, &bytesRead, NULL) || - bytesRead != bytesToRead) - { - err = GetLastError(); - ERROR("Failed to read data from \"%ls\"", lte->file_on_disk); - win32_error(err); - ret = WIMLIB_ERR_READ; - break; - } - bytes_remaining -= bytesRead; - if (cb) { - ret = (*cb)(out_buf, bytesRead, ctx_or_buf); - if (ret) - break; - } else { - out_buf += bytesRead; - } - } - CloseHandle(hFile); - return ret; -} - -struct win32_encrypted_read_ctx { - consume_data_callback_t read_prefix_cb; - void *read_prefix_ctx_or_buf; - int wimlib_err_code; - void *buf; - size_t buf_filled; - u64 bytes_remaining; -}; - -static DWORD WINAPI -win32_encrypted_export_cb(unsigned char *_data, void *_ctx, unsigned long len) -{ - const void *data = _data; - struct win32_encrypted_read_ctx *ctx = _ctx; - int ret; - - DEBUG("len = %lu", len); - if (ctx->read_prefix_cb) { - /* The length of the buffer passed to the ReadEncryptedFileRaw() - * export callback is undocumented, so we assume it may be of - * arbitrary size. */ - size_t bytes_to_buffer = min(ctx->bytes_remaining - ctx->buf_filled, - len); - while (bytes_to_buffer) { - size_t bytes_to_copy_to_buf = - min(bytes_to_buffer, WIM_CHUNK_SIZE - ctx->buf_filled); - - memcpy(ctx->buf + ctx->buf_filled, data, - bytes_to_copy_to_buf); - ctx->buf_filled += bytes_to_copy_to_buf; - data += bytes_to_copy_to_buf; - bytes_to_buffer -= bytes_to_copy_to_buf; - - if (ctx->buf_filled == WIM_CHUNK_SIZE || - ctx->buf_filled == ctx->bytes_remaining) - { - ret = (*ctx->read_prefix_cb)(ctx->buf, - ctx->buf_filled, - ctx->read_prefix_ctx_or_buf); - if (ret) { - ctx->wimlib_err_code = ret; - /* Shouldn't matter what error code is returned - * here, as long as it isn't ERROR_SUCCESS. */ - return ERROR_READ_FAULT; - } - ctx->bytes_remaining -= ctx->buf_filled; - ctx->buf_filled = 0; - } - } - } else { - size_t len_to_copy = min(len, ctx->bytes_remaining); - memcpy(ctx->read_prefix_ctx_or_buf, data, len_to_copy); - ctx->bytes_remaining -= len_to_copy; - ctx->read_prefix_ctx_or_buf += len_to_copy; - } - return ERROR_SUCCESS; -} - -int -read_win32_encrypted_file_prefix(const struct wim_lookup_table_entry *lte, - u64 size, - consume_data_callback_t cb, - void *ctx_or_buf, - int _ignored_flags) -{ - struct win32_encrypted_read_ctx export_ctx; - DWORD err; - void *file_ctx; - int ret; - - DEBUG("Reading %"PRIu64" bytes from encryted file \"%ls\"", - size, lte->file_on_disk); - - export_ctx.read_prefix_cb = cb; - export_ctx.read_prefix_ctx_or_buf = ctx_or_buf; - export_ctx.wimlib_err_code = 0; - if (cb) { - export_ctx.buf = MALLOC(WIM_CHUNK_SIZE); - if (!export_ctx.buf) - return WIMLIB_ERR_NOMEM; - } else { - export_ctx.buf = NULL; - } - export_ctx.buf_filled = 0; - export_ctx.bytes_remaining = size; - - err = OpenEncryptedFileRawW(lte->file_on_disk, 0, &file_ctx); - if (err != ERROR_SUCCESS) { - ERROR("Failed to open encrypted file \"%ls\" for raw read", - lte->file_on_disk); - win32_error(err); - ret = WIMLIB_ERR_OPEN; - goto out_free_buf; - } - err = ReadEncryptedFileRaw(win32_encrypted_export_cb, - &export_ctx, file_ctx); - if (err != ERROR_SUCCESS) { - ERROR("Failed to read encrypted file \"%ls\"", - lte->file_on_disk); - win32_error(err); - ret = export_ctx.wimlib_err_code; - if (ret == 0) - ret = WIMLIB_ERR_READ; - } else if (export_ctx.bytes_remaining != 0) { - ERROR("Only could read %"PRIu64" of %"PRIu64" bytes from " - "encryted file \"%ls\"", - size - export_ctx.bytes_remaining, size, - lte->file_on_disk); - ret = WIMLIB_ERR_READ; - } else { - ret = 0; - } - CloseEncryptedFileRaw(file_ctx); -out_free_buf: - FREE(export_ctx.buf); - return ret; -} - -/* Given a path, which may not yet exist, get a set of flags that describe the - * features of the volume the path is on. */ -static int -win32_get_vol_flags(const wchar_t *path, unsigned *vol_flags_ret) -{ - wchar_t *volume; - BOOL bret; - DWORD vol_flags; - - if (path[0] != L'\0' && path[0] != L'\\' && - path[0] != L'/' && path[1] == L':') - { - /* Path starts with a drive letter; use it. */ - volume = alloca(4 * sizeof(wchar_t)); - volume[0] = path[0]; - volume[1] = path[1]; - volume[2] = L'\\'; - volume[3] = L'\0'; - } else { - /* Path does not start with a drive letter; use the volume of - * the current working directory. */ - volume = NULL; - } - bret = GetVolumeInformationW(volume, /* lpRootPathName */ - NULL, /* lpVolumeNameBuffer */ - 0, /* nVolumeNameSize */ - NULL, /* lpVolumeSerialNumber */ - NULL, /* lpMaximumComponentLength */ - &vol_flags, /* lpFileSystemFlags */ - NULL, /* lpFileSystemNameBuffer */ - 0); /* nFileSystemNameSize */ - if (!bret) { - DWORD err = GetLastError(); - WARNING("Failed to get volume information for path \"%ls\"", path); - win32_error(err); - vol_flags = 0xffffffff; - } - - DEBUG("using vol_flags = %x", vol_flags); - *vol_flags_ret = vol_flags; - return 0; -} - - -static u64 -FILETIME_to_u64(const FILETIME *ft) -{ - return ((u64)ft->dwHighDateTime << 32) | (u64)ft->dwLowDateTime; -} - -static int -win32_get_short_name(struct wim_dentry *dentry, const wchar_t *path) -{ - WIN32_FIND_DATAW dat; - HANDLE hFind; - int ret = 0; - - /* If we can't read the short filename for some reason, we just ignore - * the error and assume the file has no short name. I don't think this - * should be an issue, since the short names are essentially obsolete - * anyway. */ - hFind = FindFirstFileW(path, &dat); - if (hFind != INVALID_HANDLE_VALUE) { - if (dat.cAlternateFileName[0] != L'\0') { - DEBUG("\"%ls\": short name \"%ls\"", path, dat.cAlternateFileName); - size_t short_name_nbytes = wcslen(dat.cAlternateFileName) * - sizeof(wchar_t); - size_t n = short_name_nbytes + sizeof(wchar_t); - dentry->short_name = MALLOC(n); - if (dentry->short_name) { - memcpy(dentry->short_name, dat.cAlternateFileName, n); - dentry->short_name_nbytes = short_name_nbytes; - } else { - ret = WIMLIB_ERR_NOMEM; - } - } - FindClose(hFind); - } - return ret; -} - -static int -win32_get_security_descriptor(struct wim_dentry *dentry, - struct sd_set *sd_set, - const wchar_t *path, - struct win32_capture_state *state, - int add_flags) -{ - SECURITY_INFORMATION requestedInformation; - DWORD lenNeeded = 0; - BOOL status; - DWORD err; - unsigned long n; - - requestedInformation = DACL_SECURITY_INFORMATION | - SACL_SECURITY_INFORMATION | - OWNER_SECURITY_INFORMATION | - GROUP_SECURITY_INFORMATION; -again: - /* Request length of security descriptor */ - status = GetFileSecurityW(path, requestedInformation, - NULL, 0, &lenNeeded); - err = GetLastError(); - if (!status && err == ERROR_INSUFFICIENT_BUFFER) { - DWORD len = lenNeeded; - char buf[len]; - if (GetFileSecurityW(path, requestedInformation, - (PSECURITY_DESCRIPTOR)buf, len, &lenNeeded)) - { - int security_id = sd_set_add_sd(sd_set, buf, len); - if (security_id < 0) - return WIMLIB_ERR_NOMEM; - else { - dentry->d_inode->i_security_id = security_id; - return 0; - } - } else { - err = GetLastError(); - } - } - - if (add_flags & WIMLIB_ADD_FLAG_STRICT_ACLS) - goto fail; - - switch (err) { - case ERROR_PRIVILEGE_NOT_HELD: - if (requestedInformation & SACL_SECURITY_INFORMATION) { - n = state->num_get_sacl_priv_notheld++; - requestedInformation &= ~SACL_SECURITY_INFORMATION; - if (n < MAX_GET_SACL_PRIV_NOTHELD_WARNINGS) { - WARNING( -"We don't have enough privileges to read the full security\n" -" descriptor of \"%ls\"!\n" -" Re-trying with SACL omitted.\n", path); - } else if (n == MAX_GET_SACL_PRIV_NOTHELD_WARNINGS) { - WARNING( -"Suppressing further privileges not held error messages when reading\n" -" security descriptors."); - } - goto again; - } - /* Fall through */ - case ERROR_ACCESS_DENIED: - n = state->num_get_sd_access_denied++; - if (n < MAX_GET_SD_ACCESS_DENIED_WARNINGS) { - WARNING("Failed to read security descriptor of \"%ls\": " - "Access denied!\n%ls", path, capture_access_denied_msg); - } else if (n == MAX_GET_SD_ACCESS_DENIED_WARNINGS) { - WARNING("Suppressing further access denied errors messages i" - "when reading security descriptors"); - } - return 0; - default: -fail: - ERROR("Failed to read security descriptor of \"%ls\"", path); - win32_error(err); - return WIMLIB_ERR_READ; - } -} - -static int -win32_build_dentry_tree_recursive(struct wim_dentry **root_ret, - wchar_t *path, - size_t path_num_chars, - struct add_image_params *params, - struct win32_capture_state *state, - unsigned vol_flags); - -/* Reads the directory entries of directory using a Win32 API and recursively - * calls win32_build_dentry_tree() on them. */ -static int -win32_recurse_directory(struct wim_dentry *root, - wchar_t *dir_path, - size_t dir_path_num_chars, - struct add_image_params *params, - struct win32_capture_state *state, - unsigned vol_flags) -{ - WIN32_FIND_DATAW dat; - HANDLE hFind; - DWORD err; - int ret; - - DEBUG("Recurse to directory \"%ls\"", dir_path); - - /* Begin reading the directory by calling FindFirstFileW. Unlike UNIX - * opendir(), FindFirstFileW has file globbing built into it. But this - * isn't what we actually want, so just add a dummy glob to get all - * entries. */ - dir_path[dir_path_num_chars] = L'/'; - dir_path[dir_path_num_chars + 1] = L'*'; - dir_path[dir_path_num_chars + 2] = L'\0'; - hFind = FindFirstFileW(dir_path, &dat); - dir_path[dir_path_num_chars] = L'\0'; - - if (hFind == INVALID_HANDLE_VALUE) { - err = GetLastError(); - if (err == ERROR_FILE_NOT_FOUND) { - return 0; - } else { - ERROR("Failed to read directory \"%ls\"", dir_path); - win32_error(err); - return WIMLIB_ERR_READ; - } - } - ret = 0; - do { - /* Skip . and .. entries */ - if (dat.cFileName[0] == L'.' && - (dat.cFileName[1] == L'\0' || - (dat.cFileName[1] == L'.' && - dat.cFileName[2] == L'\0'))) - continue; - size_t filename_len = wcslen(dat.cFileName); - - dir_path[dir_path_num_chars] = L'/'; - wmemcpy(dir_path + dir_path_num_chars + 1, - dat.cFileName, - filename_len + 1); - - struct wim_dentry *child; - size_t path_len = dir_path_num_chars + 1 + filename_len; - ret = win32_build_dentry_tree_recursive(&child, - dir_path, - path_len, - params, - state, - vol_flags); - dir_path[dir_path_num_chars] = L'\0'; - if (ret) - goto out_find_close; - if (child) - dentry_add_child(root, child); - } while (FindNextFileW(hFind, &dat)); - err = GetLastError(); - if (err != ERROR_NO_MORE_FILES) { - ERROR("Failed to read directory \"%ls\"", dir_path); - win32_error(err); - if (ret == 0) - ret = WIMLIB_ERR_READ; - } -out_find_close: - FindClose(hFind); - return ret; -} - -int -win32_get_file_and_vol_ids(const wchar_t *path, u64 *ino_ret, u64 *dev_ret) -{ - HANDLE hFile; - DWORD err; - BY_HANDLE_FILE_INFORMATION file_info; - int ret; - - hFile = win32_open_existing_file(path, FILE_READ_ATTRIBUTES); - if (hFile == INVALID_HANDLE_VALUE) { - err = GetLastError(); - if (err != ERROR_FILE_NOT_FOUND) { - WARNING("Failed to open \"%ls\" to get file " - "and volume IDs", path); - win32_error(err); - } - return WIMLIB_ERR_OPEN; - } - - if (!GetFileInformationByHandle(hFile, &file_info)) { - err = GetLastError(); - ERROR("Failed to get file information for \"%ls\"", path); - win32_error(err); - ret = WIMLIB_ERR_STAT; - } else { - *ino_ret = ((u64)file_info.nFileIndexHigh << 32) | - (u64)file_info.nFileIndexLow; - *dev_ret = file_info.dwVolumeSerialNumber; - ret = 0; - } - CloseHandle(hFile); - return ret; -} - -/* Reparse point fixup status code */ -enum rp_status { - /* Reparse point corresponded to an absolute symbolic link or junction - * point that pointed outside the directory tree being captured, and - * therefore was excluded. */ - RP_EXCLUDED = 0x0, - - /* Reparse point was not fixed as it was either a relative symbolic - * link, a mount point, or something else we could not understand. */ - RP_NOT_FIXED = 0x1, - - /* Reparse point corresponded to an absolute symbolic link or junction - * point that pointed inside the directory tree being captured, where - * the target was specified by a "full" \??\ prefixed path, and - * therefore was fixed to be relative to the root of the directory tree - * being captured. */ - RP_FIXED_FULLPATH = 0x2, - - /* Same as RP_FIXED_FULLPATH, except the absolute link target did not - * have the \??\ prefix. It may have begun with a drive letter though. - * */ - RP_FIXED_ABSPATH = 0x4, - - /* Either RP_FIXED_FULLPATH or RP_FIXED_ABSPATH. */ - RP_FIXED = RP_FIXED_FULLPATH | RP_FIXED_ABSPATH, -}; - -/* Given the "substitute name" target of a Windows reparse point, try doing a - * fixup where we change it to be absolute relative to the root of the directory - * tree being captured. - * - * Note that this is only executed when WIMLIB_ADD_FLAG_RPFIX has been - * set. - * - * @capture_root_ino and @capture_root_dev indicate the inode number and device - * of the root of the directory tree being captured. They are meant to identify - * this directory (as an alternative to its actual path, which could potentially - * be reached via multiple destinations due to other symbolic links). This may - * not work properly on FAT, which doesn't seem to supply proper inode numbers - * or file IDs. However, FAT doesn't support reparse points so this function - * wouldn't even be called anyway. - */ -static enum rp_status -win32_capture_maybe_rpfix_target(wchar_t *target, u16 *target_nbytes_p, - u64 capture_root_ino, u64 capture_root_dev, - u32 rptag) -{ - u16 target_nchars = *target_nbytes_p / 2; - size_t stripped_chars; - wchar_t *orig_target; - int ret; - - ret = parse_substitute_name(target, *target_nbytes_p, rptag); - if (ret < 0) - return RP_NOT_FIXED; - stripped_chars = ret; - if (stripped_chars) - stripped_chars -= 2; - target[target_nchars] = L'\0'; - orig_target = target; - target = capture_fixup_absolute_symlink(target + stripped_chars, - capture_root_ino, capture_root_dev); - if (!target) - return RP_EXCLUDED; - target_nchars = wcslen(target); - wmemmove(orig_target + stripped_chars, target, target_nchars + 1); - *target_nbytes_p = (target_nchars + stripped_chars) * sizeof(wchar_t); - DEBUG("Fixed reparse point (new target: \"%ls\")", orig_target); - if (stripped_chars) - return RP_FIXED_FULLPATH; - else - return RP_FIXED_ABSPATH; -} - -/* Returns: `enum rp_status' value on success; negative WIMLIB_ERR_* value on - * failure. */ -static int -win32_capture_try_rpfix(u8 *rpbuf, u16 *rpbuflen_p, - u64 capture_root_ino, u64 capture_root_dev, - const wchar_t *path) -{ - struct reparse_data rpdata; - DWORD rpbuflen; - int ret; - enum rp_status rp_status; - - rpbuflen = *rpbuflen_p; - ret = parse_reparse_data(rpbuf, rpbuflen, &rpdata); - if (ret) - return -ret; - - rp_status = win32_capture_maybe_rpfix_target(rpdata.substitute_name, - &rpdata.substitute_name_nbytes, - capture_root_ino, - capture_root_dev, - le32_to_cpu(*(u32*)rpbuf)); - if (rp_status & RP_FIXED) { - wimlib_assert(rpdata.substitute_name_nbytes % 2 == 0); - utf16lechar substitute_name_copy[rpdata.substitute_name_nbytes / 2]; - wmemcpy(substitute_name_copy, rpdata.substitute_name, - rpdata.substitute_name_nbytes / 2); - rpdata.substitute_name = substitute_name_copy; - rpdata.print_name = substitute_name_copy; - rpdata.print_name_nbytes = rpdata.substitute_name_nbytes; - if (rp_status == RP_FIXED_FULLPATH) { - /* "full path", meaning \??\ prefixed. We should not - * include this prefix in the print name, as it is - * apparently meant for the filesystem driver only. */ - rpdata.print_name += 4; - rpdata.print_name_nbytes -= 8; - } - ret = make_reparse_buffer(&rpdata, rpbuf); - if (ret == 0) - ret = rp_status; - else - ret = -ret; - } else { - if (rp_status == RP_EXCLUDED) { - size_t print_name_nchars = rpdata.print_name_nbytes / 2; - wchar_t print_name0[print_name_nchars + 1]; - print_name0[print_name_nchars] = L'\0'; - wmemcpy(print_name0, rpdata.print_name, print_name_nchars); - WARNING("Ignoring %ls pointing out of capture directory:\n" - " \"%ls\" -> \"%ls\"\n" - " (Use --norpfix to capture all symbolic links " - "and junction points as-is)", - (rpdata.rptag == WIM_IO_REPARSE_TAG_SYMLINK) ? - L"absolute symbolic link" : L"junction point", - path, print_name0); - } - ret = rp_status; - } - return ret; -} - -/* - * Loads the reparse point data 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. - * - * @hFile: Open handle to the reparse point. - * @path: Path to the reparse point. Used for error messages only. - * @params: Additional parameters, including whether to do reparse point fixups - * or not. - * @rpbuf: Buffer of length at least REPARSE_POINT_MAX_SIZE bytes into which - * the reparse point buffer will be loaded. - * @rpbuflen_ret: On success, the length of the reparse point buffer in bytes - * is written to this location. - * - * Returns: - * On success, returns an `enum rp_status' value that indicates if and/or - * how the reparse point fixup was done. - * - * On failure, returns a negative value that is a negated WIMLIB_ERR_* - * code. - */ -static int -win32_get_reparse_data(HANDLE hFile, const wchar_t *path, - struct add_image_params *params, - u8 *rpbuf, u16 *rpbuflen_ret) -{ - DWORD bytesReturned; - u32 reparse_tag; - int ret; - u16 rpbuflen; - - DEBUG("Loading reparse data from \"%ls\"", path); - if (!DeviceIoControl(hFile, FSCTL_GET_REPARSE_POINT, - NULL, /* "Not used with this operation; set to NULL" */ - 0, /* "Not used with this operation; set to 0" */ - rpbuf, /* "A pointer to a buffer that - receives the reparse point data */ - REPARSE_POINT_MAX_SIZE, /* "The size of the output - buffer, in bytes */ - &bytesReturned, - NULL)) - { - DWORD err = GetLastError(); - ERROR("Failed to get reparse data of \"%ls\"", path); - win32_error(err); - return -WIMLIB_ERR_READ; - } - if (bytesReturned < 8 || bytesReturned > REPARSE_POINT_MAX_SIZE) { - ERROR("Reparse data on \"%ls\" is invalid", path); - return -WIMLIB_ERR_INVALID_REPARSE_DATA; - } - - rpbuflen = bytesReturned; - reparse_tag = le32_to_cpu(*(u32*)rpbuf); - if (params->add_flags & WIMLIB_ADD_FLAG_RPFIX && - (reparse_tag == WIM_IO_REPARSE_TAG_SYMLINK || - reparse_tag == WIM_IO_REPARSE_TAG_MOUNT_POINT)) - { - /* Try doing reparse point fixup */ - ret = win32_capture_try_rpfix(rpbuf, - &rpbuflen, - params->capture_root_ino, - params->capture_root_dev, - path); - } else { - ret = RP_NOT_FIXED; - } - *rpbuflen_ret = rpbuflen; - return ret; -} - -static DWORD WINAPI -win32_tally_encrypted_size_cb(unsigned char *_data, void *_ctx, - unsigned long len) -{ - *(u64*)_ctx += len; - return ERROR_SUCCESS; -} - -static int -win32_get_encrypted_file_size(const wchar_t *path, u64 *size_ret) -{ - DWORD err; - void *file_ctx; - int ret; - - *size_ret = 0; - err = OpenEncryptedFileRawW(path, 0, &file_ctx); - if (err != ERROR_SUCCESS) { - ERROR("Failed to open encrypted file \"%ls\" for raw read", path); - win32_error(err); - return WIMLIB_ERR_OPEN; - } - err = ReadEncryptedFileRaw(win32_tally_encrypted_size_cb, - size_ret, file_ctx); - if (err != ERROR_SUCCESS) { - ERROR("Failed to read raw encrypted data from \"%ls\"", path); - win32_error(err); - ret = WIMLIB_ERR_READ; - } else { - ret = 0; - } - CloseEncryptedFileRaw(file_ctx); - return ret; -} - -/* Scans an unnamed or named stream of a Win32 file (not a reparse point - * stream); calculates its SHA1 message digest and either creates a `struct - * wim_lookup_table_entry' in memory for it, or uses an existing 'struct - * wim_lookup_table_entry' for an identical stream. - * - * @path: Path to the file (UTF-16LE). - * - * @path_num_chars: Number of 2-byte characters in @path. - * - * @inode: WIM inode to save the stream into. - * - * @lookup_table: Stream lookup table for the WIM. - * - * @dat: A `WIN32_FIND_STREAM_DATA' structure that specifies the - * stream name. - * - * Returns 0 on success; nonzero on failure. - */ -static int -win32_capture_stream(const wchar_t *path, - size_t path_num_chars, - struct wim_inode *inode, - struct wim_lookup_table *lookup_table, - WIN32_FIND_STREAM_DATA *dat) -{ - struct wim_ads_entry *ads_entry; - struct wim_lookup_table_entry *lte; - int ret; - wchar_t *stream_name, *colon; - size_t stream_name_nchars; - bool is_named_stream; - wchar_t *spath; - size_t spath_nchars; - size_t spath_buf_nbytes; - const wchar_t *relpath_prefix; - const wchar_t *colonchar; - - DEBUG("Capture \"%ls\" stream \"%ls\"", path, dat->cStreamName); - - /* The stream name should be returned as :NAME:TYPE */ - stream_name = dat->cStreamName; - if (*stream_name != L':') - goto out_invalid_stream_name; - stream_name += 1; - colon = wcschr(stream_name, L':'); - if (colon == NULL) - goto out_invalid_stream_name; - - if (wcscmp(colon + 1, L"$DATA")) { - /* Not a DATA stream */ - ret = 0; - goto out; - } - - *colon = '\0'; - - stream_name_nchars = colon - stream_name; - is_named_stream = (stream_name_nchars != 0); - - if (is_named_stream) { - /* Allocate an ADS entry for the named stream. */ - ads_entry = inode_add_ads_utf16le(inode, stream_name, - stream_name_nchars * sizeof(wchar_t)); - if (!ads_entry) { - ret = WIMLIB_ERR_NOMEM; - goto out; - } - } - - /* If zero length stream, no lookup table entry needed. */ - if ((u64)dat->StreamSize.QuadPart == 0) { - ret = 0; - goto out; - } - - /* Create a UTF-16LE string @spath that gives the filename, then a - * colon, then the stream name. Or, if it's an unnamed stream, just the - * filename. It is MALLOC()'ed so that it can be saved in the - * wim_lookup_table_entry if needed. - * - * As yet another special case, relative paths need to be changed to - * begin with an explicit "./" so that, for example, a file t:ads, where - * :ads is the part we added, is not interpreted as a file on the t: - * drive. */ - spath_nchars = path_num_chars; - relpath_prefix = L""; - colonchar = L""; - if (is_named_stream) { - spath_nchars += 1 + stream_name_nchars; - colonchar = L":"; - if (path_num_chars == 1 && - path[0] != L'/' && - path[0] != L'\\') - { - spath_nchars += 2; - relpath_prefix = L"./"; - } - } - - spath_buf_nbytes = (spath_nchars + 1) * sizeof(wchar_t); - spath = MALLOC(spath_buf_nbytes); - - swprintf(spath, L"%ls%ls%ls%ls", - relpath_prefix, path, colonchar, stream_name); - - /* Make a new wim_lookup_table_entry */ - lte = new_lookup_table_entry(); - if (!lte) { - ret = WIMLIB_ERR_NOMEM; - goto out_free_spath; - } - lte->file_on_disk = spath; - spath = NULL; - if (inode->i_attributes & FILE_ATTRIBUTE_ENCRYPTED && !is_named_stream) { - u64 encrypted_size; - lte->resource_location = RESOURCE_WIN32_ENCRYPTED; - ret = win32_get_encrypted_file_size(path, &encrypted_size); - if (ret) - goto out_free_spath; - lte->resource_entry.original_size = encrypted_size; - } else { - lte->resource_location = RESOURCE_WIN32; - lte->resource_entry.original_size = (u64)dat->StreamSize.QuadPart; - } - - u32 stream_id; - if (is_named_stream) { - stream_id = ads_entry->stream_id; - ads_entry->lte = lte; - } else { - stream_id = 0; - inode->i_lte = lte; - } - lookup_table_insert_unhashed(lookup_table, lte, inode, stream_id); - ret = 0; -out_free_spath: - FREE(spath); -out: - return ret; -out_invalid_stream_name: - ERROR("Invalid stream name: \"%ls:%ls\"", path, dat->cStreamName); - ret = WIMLIB_ERR_READ; - goto out; -} - -/* Scans a Win32 file for unnamed and named data streams (not reparse point - * streams). - * - * @path: Path to the file (UTF-16LE). - * - * @path_num_chars: Number of 2-byte characters in @path. - * - * @inode: WIM inode to save the stream into. - * - * @lookup_table: Stream lookup table for the WIM. - * - * @file_size: Size of unnamed data stream. (Used only if alternate - * data streams API appears to be unavailable.) - * - * @vol_flags: Flags that specify features of the volume being - * captured. - * - * Returns 0 on success; nonzero on failure. - */ -static int -win32_capture_streams(const wchar_t *path, - size_t path_num_chars, - struct wim_inode *inode, - struct wim_lookup_table *lookup_table, - u64 file_size, - unsigned vol_flags) -{ - WIN32_FIND_STREAM_DATA dat; - int ret; - HANDLE hFind; - DWORD err; - - DEBUG("Capturing streams from \"%ls\"", path); - - if (win32func_FindFirstStreamW == NULL || - !(vol_flags & FILE_NAMED_STREAMS)) - goto unnamed_only; - - hFind = win32func_FindFirstStreamW(path, FindStreamInfoStandard, &dat, 0); - if (hFind == INVALID_HANDLE_VALUE) { - err = GetLastError(); - if (err == ERROR_CALL_NOT_IMPLEMENTED) - goto unnamed_only; - - /* Seems legal for this to return ERROR_HANDLE_EOF on reparse - * points and directories */ - if ((inode->i_attributes & - (FILE_ATTRIBUTE_REPARSE_POINT | FILE_ATTRIBUTE_DIRECTORY)) - && err == ERROR_HANDLE_EOF) - { - DEBUG("ERROR_HANDLE_EOF (ok)"); - return 0; - } else { - if (err == ERROR_ACCESS_DENIED) { - WARNING("Failed to look up data streams " - "of \"%ls\": Access denied!\n%ls", - path, capture_access_denied_msg); - return 0; - } else { - ERROR("Failed to look up data streams " - "of \"%ls\"", path); - win32_error(err); - return WIMLIB_ERR_READ; - } - } - } - do { - ret = win32_capture_stream(path, - path_num_chars, - inode, lookup_table, - &dat); - if (ret) - goto out_find_close; - } while (win32func_FindNextStreamW(hFind, &dat)); - err = GetLastError(); - if (err != ERROR_HANDLE_EOF) { - ERROR("Win32 API: Error reading data streams from \"%ls\"", path); - win32_error(err); - ret = WIMLIB_ERR_READ; - } -out_find_close: - FindClose(hFind); - return ret; -unnamed_only: - /* FindFirstStreamW() API is not available, or the volume does not - * support named streams. Only capture the unnamed data stream. */ - DEBUG("Only capturing unnamed data stream"); - if (inode->i_attributes & - (FILE_ATTRIBUTE_REPARSE_POINT | FILE_ATTRIBUTE_DIRECTORY)) - { - ret = 0; - } else { - /* Just create our own WIN32_FIND_STREAM_DATA for an unnamed - * stream to reduce the code to a call to the - * already-implemented win32_capture_stream() */ - wcscpy(dat.cStreamName, L"::$DATA"); - dat.StreamSize.QuadPart = file_size; - ret = win32_capture_stream(path, - path_num_chars, - inode, lookup_table, - &dat); - } - return ret; -} - -static int -win32_build_dentry_tree_recursive(struct wim_dentry **root_ret, - wchar_t *path, - size_t path_num_chars, - struct add_image_params *params, - struct win32_capture_state *state, - unsigned vol_flags) -{ - struct wim_dentry *root = NULL; - struct wim_inode *inode; - DWORD err; - u64 file_size; - int ret; - u8 *rpbuf; - u16 rpbuflen; - u16 not_rpfixed; - - if (exclude_path(path, path_num_chars, params->config, true)) { - if (params->add_flags & WIMLIB_ADD_FLAG_ROOT) { - ERROR("Cannot exclude the root directory from capture"); - ret = WIMLIB_ERR_INVALID_CAPTURE_CONFIG; - goto out; - } - if ((params->add_flags & WIMLIB_ADD_FLAG_EXCLUDE_VERBOSE) - && params->progress_func) - { - union wimlib_progress_info info; - info.scan.cur_path = path; - info.scan.excluded = true; - params->progress_func(WIMLIB_PROGRESS_MSG_SCAN_DENTRY, &info); - } - ret = 0; - goto out; - } - - if ((params->add_flags & WIMLIB_ADD_FLAG_VERBOSE) - && params->progress_func) - { - union wimlib_progress_info info; - info.scan.cur_path = path; - info.scan.excluded = false; - params->progress_func(WIMLIB_PROGRESS_MSG_SCAN_DENTRY, &info); - } - - HANDLE hFile = win32_open_existing_file(path, - FILE_READ_DATA | FILE_READ_ATTRIBUTES); - if (hFile == INVALID_HANDLE_VALUE) { - err = GetLastError(); - ERROR("Win32 API: Failed to open \"%ls\"", path); - win32_error(err); - ret = WIMLIB_ERR_OPEN; - goto out; - } - - BY_HANDLE_FILE_INFORMATION file_info; - if (!GetFileInformationByHandle(hFile, &file_info)) { - err = GetLastError(); - ERROR("Win32 API: Failed to get file information for \"%ls\"", - path); - win32_error(err); - ret = WIMLIB_ERR_STAT; - goto out_close_handle; - } - - if (file_info.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) { - rpbuf = alloca(REPARSE_POINT_MAX_SIZE); - ret = win32_get_reparse_data(hFile, path, params, - rpbuf, &rpbuflen); - if (ret < 0) { - /* WIMLIB_ERR_* (inverted) */ - ret = -ret; - goto out_close_handle; - } else if (ret & RP_FIXED) { - not_rpfixed = 0; - } else if (ret == RP_EXCLUDED) { - ret = 0; - goto out_close_handle; - } else { - not_rpfixed = 1; - } - } - - /* Create a WIM dentry with an associated inode, which may be shared. - * - * However, we need to explicitly check for directories and files with - * only 1 link and refuse to hard link them. This is because Windows - * has a bug where it can return duplicate File IDs for files and - * directories on the FAT filesystem. */ - ret = inode_table_new_dentry(params->inode_table, - path_basename_with_len(path, path_num_chars), - ((u64)file_info.nFileIndexHigh << 32) | - (u64)file_info.nFileIndexLow, - file_info.dwVolumeSerialNumber, - (file_info.nNumberOfLinks <= 1 || - (file_info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)), - &root); - if (ret) - goto out_close_handle; - - ret = win32_get_short_name(root, path); - if (ret) - goto out_close_handle; - - inode = root->d_inode; - - if (inode->i_nlink > 1) /* Shared inode; nothing more to do */ - goto out_close_handle; - - inode->i_attributes = file_info.dwFileAttributes; - inode->i_creation_time = FILETIME_to_u64(&file_info.ftCreationTime); - inode->i_last_write_time = FILETIME_to_u64(&file_info.ftLastWriteTime); - inode->i_last_access_time = FILETIME_to_u64(&file_info.ftLastAccessTime); - inode->i_resolved = 1; - - params->add_flags &= ~WIMLIB_ADD_FLAG_ROOT; - - if (!(params->add_flags & WIMLIB_ADD_FLAG_NO_ACLS) - && (vol_flags & FILE_PERSISTENT_ACLS)) - { - ret = win32_get_security_descriptor(root, params->sd_set, - path, state, - params->add_flags); - if (ret) - goto out_close_handle; - } - - file_size = ((u64)file_info.nFileSizeHigh << 32) | - (u64)file_info.nFileSizeLow; - - CloseHandle(hFile); - - /* Capture the unnamed data stream (only should be present for regular - * files) and any alternate data streams. */ - ret = win32_capture_streams(path, - path_num_chars, - inode, - params->lookup_table, - file_size, - vol_flags); - if (ret) - goto out; - - if (inode->i_attributes & FILE_ATTRIBUTE_REPARSE_POINT) { - /* Reparse point: set the reparse data (which we read already) - * */ - inode->i_not_rpfixed = not_rpfixed; - inode->i_reparse_tag = le32_to_cpu(*(u32*)rpbuf); - ret = inode_set_unnamed_stream(inode, rpbuf + 8, rpbuflen - 8, - params->lookup_table); - } else if (inode->i_attributes & FILE_ATTRIBUTE_DIRECTORY) { - /* Directory (not a reparse point) --- recurse to children */ - ret = win32_recurse_directory(root, - path, - path_num_chars, - params, - state, - vol_flags); - } - goto out; -out_close_handle: - CloseHandle(hFile); -out: - if (ret == 0) - *root_ret = root; - else - free_dentry_tree(root, params->lookup_table); - return ret; -} - -static void -win32_do_capture_warnings(const struct win32_capture_state *state, - int add_flags) -{ - if (state->num_get_sacl_priv_notheld == 0 && - state->num_get_sd_access_denied == 0) - return; - - WARNING(""); - WARNING("Built dentry tree successfully, but with the following problem(s):"); - if (state->num_get_sacl_priv_notheld != 0) { - WARNING("Could not capture SACL (System Access Control List)\n" - " on %lu files or directories.", - state->num_get_sacl_priv_notheld); - } - if (state->num_get_sd_access_denied != 0) { - WARNING("Could not capture security descriptor at all\n" - " on %lu files or directories.", - state->num_get_sd_access_denied); - } - WARNING( - "Try running the program as the Administrator to make sure all the\n" -" desired metadata has been captured exactly. However, if you\n" -" do not care about capturing security descriptors correctly, then\n" -" nothing more needs to be done%ls\n", - (add_flags & WIMLIB_ADD_FLAG_NO_ACLS) ? L"." : - L", although you might consider\n" -" passing the --no-acls flag to `wimlib-imagex capture' or\n" -" `wimlib-imagex append' to explicitly capture no security\n" -" descriptors.\n"); -} - -/* Win32 version of capturing a directory tree */ -int -win32_build_dentry_tree(struct wim_dentry **root_ret, - const wchar_t *root_disk_path, - struct add_image_params *params) -{ - size_t path_nchars; - wchar_t *path; - int ret; - struct win32_capture_state state; - unsigned vol_flags; - - - path_nchars = wcslen(root_disk_path); - if (path_nchars > 32767) - return WIMLIB_ERR_INVALID_PARAM; - - if (GetFileAttributesW(root_disk_path) == INVALID_FILE_ATTRIBUTES && - GetLastError() == ERROR_FILE_NOT_FOUND) - { - ERROR("Capture directory \"%ls\" does not exist!", - root_disk_path); - return WIMLIB_ERR_OPENDIR; - } - - ret = win32_get_file_and_vol_ids(root_disk_path, - ¶ms->capture_root_ino, - ¶ms->capture_root_dev); - if (ret) - return ret; - - win32_get_vol_flags(root_disk_path, &vol_flags); - - /* There is no check for overflow later when this buffer is being used! - * But the max path length on NTFS is 32767 characters, and paths need - * to be written specially to even go past 260 characters, so we should - * be okay with 32770 characters. */ - path = MALLOC(32770 * sizeof(wchar_t)); - if (!path) - return WIMLIB_ERR_NOMEM; - - wmemcpy(path, root_disk_path, path_nchars + 1); - - memset(&state, 0, sizeof(state)); - ret = win32_build_dentry_tree_recursive(root_ret, path, - path_nchars, params, - &state, vol_flags); - FREE(path); - if (ret == 0) - win32_do_capture_warnings(&state, params->add_flags); - return ret; -} - -static int -win32_extract_try_rpfix(u8 *rpbuf, - const wchar_t *extract_root_realpath, - unsigned extract_root_realpath_nchars) -{ - struct reparse_data rpdata; - wchar_t *target; - size_t target_nchars; - size_t stripped_nchars; - wchar_t *stripped_target; - wchar_t stripped_target_nchars; - int ret; - - utf16lechar *new_target; - utf16lechar *new_print_name; - size_t new_target_nchars; - size_t new_print_name_nchars; - utf16lechar *p; - - ret = parse_reparse_data(rpbuf, 8 + le16_to_cpu(*(u16*)(rpbuf + 4)), - &rpdata); - if (ret) - return ret; - - if (extract_root_realpath[0] == L'\0' || - extract_root_realpath[1] != L':' || - extract_root_realpath[2] != L'\\') - { - ERROR("Can't understand full path format \"%ls\". " - "Try turning reparse point fixups off...", - extract_root_realpath); - return WIMLIB_ERR_REPARSE_POINT_FIXUP_FAILED; - } - - ret = parse_substitute_name(rpdata.substitute_name, - rpdata.substitute_name_nbytes, - rpdata.rptag); - if (ret < 0) - return 0; - stripped_nchars = ret; - target = rpdata.substitute_name; - target_nchars = rpdata.substitute_name_nbytes / sizeof(utf16lechar); - stripped_target = target + 6; - stripped_target_nchars = target_nchars - stripped_nchars; - - new_target = alloca((6 + extract_root_realpath_nchars + - stripped_target_nchars) * sizeof(utf16lechar)); - - p = new_target; - if (stripped_nchars == 6) { - /* Include \??\ prefix if it was present before */ - wmemcpy(p, L"\\??\\", 4); - p += 4; - } - - /* Print name excludes the \??\ if present. */ - new_print_name = p; - if (stripped_nchars != 0) { - /* Get drive letter from real path to extract root, if a drive - * letter was present before. */ - *p++ = extract_root_realpath[0]; - *p++ = extract_root_realpath[1]; - } - /* Copy the rest of the extract root */ - wmemcpy(p, extract_root_realpath + 2, extract_root_realpath_nchars - 2); - p += extract_root_realpath_nchars - 2; - - /* Append the stripped target */ - wmemcpy(p, stripped_target, stripped_target_nchars); - p += stripped_target_nchars; - new_target_nchars = p - new_target; - new_print_name_nchars = p - new_print_name; - - if (new_target_nchars * sizeof(utf16lechar) >= REPARSE_POINT_MAX_SIZE || - new_print_name_nchars * sizeof(utf16lechar) >= REPARSE_POINT_MAX_SIZE) - { - ERROR("Path names too long to do reparse point fixup!"); - return WIMLIB_ERR_REPARSE_POINT_FIXUP_FAILED; - } - rpdata.substitute_name = new_target; - rpdata.substitute_name_nbytes = new_target_nchars * sizeof(utf16lechar); - rpdata.print_name = new_print_name; - rpdata.print_name_nbytes = new_print_name_nchars * sizeof(utf16lechar); - return make_reparse_buffer(&rpdata, rpbuf); -} - -/* Wrapper around the FSCTL_SET_REPARSE_POINT ioctl to set the reparse data on - * an extracted reparse point. */ -static int -win32_set_reparse_data(HANDLE h, - const struct wim_inode *inode, - const struct wim_lookup_table_entry *lte, - const wchar_t *path, - struct apply_args *args) -{ - int ret; - u8 rpbuf[REPARSE_POINT_MAX_SIZE]; - DWORD bytesReturned; - - DEBUG("Setting reparse data on \"%ls\"", path); - - ret = wim_inode_get_reparse_data(inode, rpbuf); - if (ret) - return ret; - - if (args->extract_flags & WIMLIB_EXTRACT_FLAG_RPFIX && - (inode->i_reparse_tag == WIM_IO_REPARSE_TAG_SYMLINK || - inode->i_reparse_tag == WIM_IO_REPARSE_TAG_MOUNT_POINT) && - !inode->i_not_rpfixed) - { - ret = win32_extract_try_rpfix(rpbuf, - args->target_realpath, - args->target_realpath_len); - if (ret) - return WIMLIB_ERR_REPARSE_POINT_FIXUP_FAILED; - } - - /* Set the reparse data on the open file using the - * FSCTL_SET_REPARSE_POINT ioctl. - * - * There are contradictions in Microsoft's documentation for this: - * - * "If hDevice was opened without specifying FILE_FLAG_OVERLAPPED, - * lpOverlapped is ignored." - * - * --- So setting lpOverlapped to NULL is okay since it's ignored. - * - * "If lpOverlapped is NULL, lpBytesReturned cannot be NULL. Even when an - * operation returns no output data and lpOutBuffer is NULL, - * DeviceIoControl makes use of lpBytesReturned. After such an - * operation, the value of lpBytesReturned is meaningless." - * - * --- So lpOverlapped not really ignored, as it affects another - * parameter. This is the actual behavior: lpBytesReturned must be - * specified, even though lpBytesReturned is documented as: - * - * "Not used with this operation; set to NULL." - */ - if (!DeviceIoControl(h, FSCTL_SET_REPARSE_POINT, rpbuf, - 8 + le16_to_cpu(*(u16*)(rpbuf + 4)), - NULL, 0, - &bytesReturned /* lpBytesReturned */, - NULL /* lpOverlapped */)) - { - DWORD err = GetLastError(); - if (err == ERROR_ACCESS_DENIED || err == ERROR_PRIVILEGE_NOT_HELD) - { - args->num_soft_links_failed++; - if (args->num_soft_links_failed <= MAX_CREATE_SOFT_LINK_WARNINGS) { - WARNING("Can't set reparse data on \"%ls\": Access denied!\n" - " You may be trying to extract a symbolic " - "link without the\n" - " SeCreateSymbolicLink privilege, which by " - "default non-Administrator\n" - " accounts do not have.", path); - } - if (args->num_hard_links_failed == MAX_CREATE_HARD_LINK_WARNINGS) { - WARNING("Suppressing further warnings regarding failure to extract\n" - " reparse points due to insufficient privileges..."); - } - } else { - ERROR("Failed to set reparse data on \"%ls\"", path); - win32_error(err); - if (inode->i_reparse_tag == WIM_IO_REPARSE_TAG_SYMLINK || - inode->i_reparse_tag == WIM_IO_REPARSE_TAG_MOUNT_POINT) - return WIMLIB_ERR_LINK; - else - return WIMLIB_ERR_WRITE; - } - } - return 0; -} - -/* Wrapper around the FSCTL_SET_COMPRESSION ioctl to change the - * FILE_ATTRIBUTE_COMPRESSED flag of a file or directory. */ -static int -win32_set_compression_state(HANDLE hFile, USHORT format, const wchar_t *path) -{ - DWORD bytesReturned; - if (!DeviceIoControl(hFile, FSCTL_SET_COMPRESSION, - &format, sizeof(USHORT), - NULL, 0, - &bytesReturned, NULL)) - { - /* Could be a warning only, but we only call this if the volume - * supports compression. So I'm calling this an error. */ - DWORD err = GetLastError(); - ERROR("Failed to set compression flag on \"%ls\"", path); - win32_error(err); - if (err == ERROR_ACCESS_DENIED || err == ERROR_PRIVILEGE_NOT_HELD) - return WIMLIB_ERR_INSUFFICIENT_PRIVILEGES_TO_EXTRACT; - else - return WIMLIB_ERR_WRITE; - } - return 0; -} - -/* Wrapper around FSCTL_SET_SPARSE ioctl to set a file as sparse. */ -static int -win32_set_sparse(HANDLE hFile, const wchar_t *path) -{ - DWORD bytesReturned; - if (!DeviceIoControl(hFile, FSCTL_SET_SPARSE, - NULL, 0, - NULL, 0, - &bytesReturned, NULL)) - { - /* Could be a warning only, but we only call this if the volume - * supports sparse files. So I'm calling this an error. */ - DWORD err = GetLastError(); - WARNING("Failed to set sparse flag on \"%ls\"", path); - win32_error(err); - if (err == ERROR_ACCESS_DENIED || err == ERROR_PRIVILEGE_NOT_HELD) - return WIMLIB_ERR_INSUFFICIENT_PRIVILEGES_TO_EXTRACT; - else - return WIMLIB_ERR_WRITE; - } - return 0; -} - -/* - * Sets the security descriptor on an extracted file. - */ -static int -win32_set_security_data(const struct wim_inode *inode, - HANDLE hFile, - const wchar_t *path, - struct apply_args *args) -{ - PSECURITY_DESCRIPTOR descriptor; - unsigned long n; - DWORD err; - const struct wim_security_data *sd; - - SECURITY_INFORMATION securityInformation = 0; - - void *owner = NULL; - void *group = NULL; - ACL *dacl = NULL; - ACL *sacl = NULL; - - BOOL owner_defaulted; - BOOL group_defaulted; - BOOL dacl_present; - BOOL dacl_defaulted; - BOOL sacl_present; - BOOL sacl_defaulted; - - sd = wim_const_security_data(args->w); - descriptor = sd->descriptors[inode->i_security_id]; - - GetSecurityDescriptorOwner(descriptor, &owner, &owner_defaulted); - if (owner) - securityInformation |= OWNER_SECURITY_INFORMATION; - - GetSecurityDescriptorGroup(descriptor, &group, &group_defaulted); - if (group) - securityInformation |= GROUP_SECURITY_INFORMATION; - - GetSecurityDescriptorDacl(descriptor, &dacl_present, - &dacl, &dacl_defaulted); - if (dacl) - securityInformation |= DACL_SECURITY_INFORMATION; - - GetSecurityDescriptorSacl(descriptor, &sacl_present, - &sacl, &sacl_defaulted); - if (sacl) - securityInformation |= SACL_SECURITY_INFORMATION; - -again: - if (securityInformation == 0) - return 0; - if (SetSecurityInfo(hFile, SE_FILE_OBJECT, - securityInformation, owner, group, dacl, sacl)) - return 0; - err = GetLastError(); - if (args->extract_flags & WIMLIB_EXTRACT_FLAG_STRICT_ACLS) - goto fail; - switch (err) { - case ERROR_PRIVILEGE_NOT_HELD: - if (securityInformation & SACL_SECURITY_INFORMATION) { - n = args->num_set_sacl_priv_notheld++; - securityInformation &= ~SACL_SECURITY_INFORMATION; - sacl = NULL; - if (n < MAX_SET_SACL_PRIV_NOTHELD_WARNINGS) { - WARNING( -"We don't have enough privileges to set the full security\n" -" descriptor on \"%ls\"!\n", path); - if (args->num_set_sd_access_denied + - args->num_set_sacl_priv_notheld == 1) - { - WARNING("%ls", apply_access_denied_msg); - } - WARNING("Re-trying with SACL omitted.\n", path); - } else if (n == MAX_GET_SACL_PRIV_NOTHELD_WARNINGS) { - WARNING( -"Suppressing further 'privileges not held' error messages when setting\n" -" security descriptors."); - } - goto again; - } - /* Fall through */ - case ERROR_INVALID_OWNER: - case ERROR_ACCESS_DENIED: - n = args->num_set_sd_access_denied++; - if (n < MAX_SET_SD_ACCESS_DENIED_WARNINGS) { - WARNING("Failed to set security descriptor on \"%ls\": " - "Access denied!\n", path); - if (args->num_set_sd_access_denied + - args->num_set_sacl_priv_notheld == 1) - { - WARNING("%ls", apply_access_denied_msg); - } - } else if (n == MAX_SET_SD_ACCESS_DENIED_WARNINGS) { - WARNING( -"Suppressing further access denied error messages when setting\n" -" security descriptors"); - } - return 0; - default: -fail: - ERROR("Failed to set security descriptor on \"%ls\"", path); - win32_error(err); - if (err == ERROR_ACCESS_DENIED || err == ERROR_PRIVILEGE_NOT_HELD) - return WIMLIB_ERR_INSUFFICIENT_PRIVILEGES_TO_EXTRACT; - else - return WIMLIB_ERR_WRITE; - } -} - - -static int -win32_extract_chunk(const void *buf, size_t len, void *arg) -{ - HANDLE hStream = arg; - - DWORD nbytes_written; - wimlib_assert(len <= 0xffffffff); - - if (!WriteFile(hStream, buf, len, &nbytes_written, NULL) || - nbytes_written != len) - { - DWORD err = GetLastError(); - ERROR("WriteFile(): write error"); - win32_error(err); - return WIMLIB_ERR_WRITE; - } - return 0; -} - -static int -do_win32_extract_stream(HANDLE hStream, const struct wim_lookup_table_entry *lte) -{ - return extract_wim_resource(lte, wim_resource_size(lte), - win32_extract_chunk, hStream); -} - -struct win32_encrypted_extract_ctx { - const struct wim_lookup_table_entry *lte; - u64 offset; -}; - -static DWORD WINAPI -win32_encrypted_import_cb(unsigned char *data, void *_ctx, - unsigned long *len_p) -{ - struct win32_encrypted_extract_ctx *ctx = _ctx; - unsigned long len = *len_p; - const struct wim_lookup_table_entry *lte = ctx->lte; - - len = min(len, wim_resource_size(lte) - ctx->offset); - - if (read_partial_wim_resource_into_buf(lte, len, ctx->offset, data)) - return ERROR_READ_FAULT; - - ctx->offset += len; - *len_p = len; - return ERROR_SUCCESS; -} - -/* Create an encrypted file and extract the raw encrypted data to it. - * - * @path: Path to encrypted file to create. - * @lte: WIM lookup_table entry for the raw encrypted data. - * - * This is separate from do_win32_extract_stream() because the WIM is supposed - * to contain the *raw* encrypted data, which needs to be extracted ("imported") - * using the special APIs OpenEncryptedFileRawW(), WriteEncryptedFileRaw(), and - * CloseEncryptedFileRaw(). - * - * Returns 0 on success; nonzero on failure. - */ -static int -do_win32_extract_encrypted_stream(const wchar_t *path, - const struct wim_lookup_table_entry *lte) -{ - void *file_ctx; - int ret; - - DEBUG("Opening file \"%ls\" to extract raw encrypted data", path); - - ret = OpenEncryptedFileRawW(path, CREATE_FOR_IMPORT, &file_ctx); - if (ret) { - ERROR("Failed to open \"%ls\" to write raw encrypted data", path); - win32_error(ret); - return WIMLIB_ERR_OPEN; - } - - if (lte) { - struct win32_encrypted_extract_ctx ctx; - - ctx.lte = lte; - ctx.offset = 0; - ret = WriteEncryptedFileRaw(win32_encrypted_import_cb, &ctx, file_ctx); - if (ret == ERROR_SUCCESS) { - ret = 0; - } else { - ret = WIMLIB_ERR_WRITE; - ERROR("Failed to extract encrypted file \"%ls\"", path); - } - } - CloseEncryptedFileRaw(file_ctx); - return ret; -} - -static bool -path_is_root_of_drive(const wchar_t *path) -{ - if (!*path) - return false; - - if (*path != L'/' && *path != L'\\') { - if (*(path + 1) == L':') - path += 2; - else - return false; - } - while (*path == L'/' || *path == L'\\') - path++; - return (*path == L'\0'); -} - -static inline DWORD -win32_mask_attributes(DWORD i_attributes) -{ - return i_attributes & ~(FILE_ATTRIBUTE_SPARSE_FILE | - FILE_ATTRIBUTE_COMPRESSED | - FILE_ATTRIBUTE_REPARSE_POINT | - FILE_ATTRIBUTE_DIRECTORY | - FILE_ATTRIBUTE_ENCRYPTED | - FILE_FLAG_DELETE_ON_CLOSE | - FILE_FLAG_NO_BUFFERING | - FILE_FLAG_OPEN_NO_RECALL | - FILE_FLAG_OVERLAPPED | - FILE_FLAG_RANDOM_ACCESS | - /*FILE_FLAG_SESSION_AWARE |*/ - FILE_FLAG_SEQUENTIAL_SCAN | - FILE_FLAG_WRITE_THROUGH); -} - -static inline DWORD -win32_get_create_flags_and_attributes(DWORD i_attributes) -{ - /* - * Some attributes cannot be set by passing them to CreateFile(). In - * particular: - * - * FILE_ATTRIBUTE_DIRECTORY: - * CreateDirectory() must be called instead of CreateFile(). - * - * FILE_ATTRIBUTE_SPARSE_FILE: - * Needs an ioctl. - * See: win32_set_sparse(). - * - * FILE_ATTRIBUTE_COMPRESSED: - * Not clear from the documentation, but apparently this needs an - * ioctl as well. - * See: win32_set_compressed(). - * - * FILE_ATTRIBUTE_REPARSE_POINT: - * Needs an ioctl, with the reparse data specified. - * See: win32_set_reparse_data(). - * - * In addition, clear any file flags in the attributes that we don't - * want, but also specify FILE_FLAG_OPEN_REPARSE_POINT and - * FILE_FLAG_BACKUP_SEMANTICS as we are a backup application. - */ - return win32_mask_attributes(i_attributes) | - FILE_FLAG_OPEN_REPARSE_POINT | - FILE_FLAG_BACKUP_SEMANTICS; -} - -/* Set compression and/or sparse attributes on a stream, if supported by the - * volume. */ -static int -win32_set_special_stream_attributes(HANDLE hFile, const struct wim_inode *inode, - struct wim_lookup_table_entry *unnamed_stream_lte, - const wchar_t *path, unsigned vol_flags) -{ - int ret; - - if (inode->i_attributes & FILE_ATTRIBUTE_COMPRESSED) { - if (vol_flags & FILE_FILE_COMPRESSION) { - ret = win32_set_compression_state(hFile, - COMPRESSION_FORMAT_DEFAULT, - path); - if (ret) - return ret; - } else { - DEBUG("Cannot set compression attribute on \"%ls\": " - "volume does not support transparent compression", - path); - } - } - - if (inode->i_attributes & FILE_ATTRIBUTE_SPARSE_FILE) { - if (vol_flags & FILE_SUPPORTS_SPARSE_FILES) { - DEBUG("Setting sparse flag on \"%ls\"", path); - ret = win32_set_sparse(hFile, path); - if (ret) - return ret; - } else { - DEBUG("Cannot set sparse attribute on \"%ls\": " - "volume does not support sparse files", - path); - } - } - return 0; -} - -/* Pre-create directories; extract encrypted streams */ -static int -win32_begin_extract_unnamed_stream(const struct wim_inode *inode, - const struct wim_lookup_table_entry *lte, - const wchar_t *path, - DWORD *creationDisposition_ret, - unsigned int vol_flags) -{ - DWORD err; - int ret; - - /* Directories must be created with CreateDirectoryW(). Then the call - * to CreateFileW() will merely open the directory that was already - * created rather than creating a new file. */ - if (inode->i_attributes & FILE_ATTRIBUTE_DIRECTORY && - !path_is_root_of_drive(path)) { - if (!CreateDirectoryW(path, NULL)) { - err = GetLastError(); - if (err != ERROR_ALREADY_EXISTS) { - ERROR("Failed to create directory \"%ls\"", - path); - win32_error(err); - return WIMLIB_ERR_MKDIR; - } - } - DEBUG("Created directory \"%ls\"", path); - *creationDisposition_ret = OPEN_EXISTING; - } - if (inode->i_attributes & FILE_ATTRIBUTE_ENCRYPTED && - vol_flags & FILE_SUPPORTS_ENCRYPTION) - { - if (inode->i_attributes & FILE_ATTRIBUTE_DIRECTORY) { - unsigned remaining_sharing_violations = 100; - while (!EncryptFile(path)) { - if (remaining_sharing_violations && - err == ERROR_SHARING_VIOLATION) - { - WARNING("Couldn't encrypt directory \"%ls\" " - "due to sharing violation; re-trying " - "after 100 ms", path); - Sleep(100); - remaining_sharing_violations--; - } else { - err = GetLastError(); - ERROR("Failed to encrypt directory \"%ls\"", - path); - win32_error(err); - return WIMLIB_ERR_WRITE; - } - } - } else { - ret = do_win32_extract_encrypted_stream(path, lte); - if (ret) - return ret; - DEBUG("Extracted encrypted file \"%ls\"", path); - } - *creationDisposition_ret = OPEN_EXISTING; - } - - /* Set file attributes if we created the file. Otherwise, we haven't - * created the file set and we will set the attributes in the call to - * CreateFileW(). - * - * The FAT filesystem does not let you change the attributes of the root - * directory, so treat that as a special case and do not set attributes. - * */ - if (*creationDisposition_ret == OPEN_EXISTING && - !path_is_root_of_drive(path)) - { - if (!SetFileAttributesW(path, - win32_mask_attributes(inode->i_attributes))) - { - err = GetLastError(); - ERROR("Failed to set attributes on \"%ls\"", path); - win32_error(err); - return WIMLIB_ERR_WRITE; - } - } - return 0; -} - -/* Set security descriptor and extract stream data or reparse data (skip the - * unnamed data stream of encrypted files, which was already extracted). */ -static int -win32_finish_extract_stream(HANDLE h, const struct wim_dentry *dentry, - const struct wim_lookup_table_entry *lte, - const wchar_t *stream_path, - const wchar_t *stream_name_utf16, - struct apply_args *args) -{ - int ret = 0; - const struct wim_inode *inode = dentry->d_inode; - const wchar_t *short_name; - if (stream_name_utf16 == NULL) { - /* Unnamed stream. */ - - /* Set security descriptor, unless the extract_flags indicate - * not to or the volume does not supported it. Note that this - * is only done when the unnamed stream is being extracted, as - * security descriptors are per-file and not per-stream. */ - if (inode->i_security_id >= 0 && - !(args->extract_flags & WIMLIB_EXTRACT_FLAG_NO_ACLS) - && (args->vol_flags & FILE_PERSISTENT_ACLS)) - { - ret = win32_set_security_data(inode, h, stream_path, args); - if (ret) - return ret; - } - - /* Handle reparse points. The data for them needs to be set - * using a special ioctl. Note that the reparse point may have - * been created using CreateFileW() in the case of - * non-directories or CreateDirectoryW() in the case of - * directories; but the ioctl works either way. Also, it is - * only this step that actually sets the - * FILE_ATTRIBUTE_REPARSE_POINT, as it is not valid to set it - * using SetFileAttributesW() or CreateFileW(). - * - * If the volume does not support reparse points we simply - * ignore the reparse data. (N.B. the code currently doesn't - * actually reach this case because reparse points are skipped - * entirely on such volumes.) */ - if (inode->i_attributes & FILE_ATTRIBUTE_REPARSE_POINT) { - if (args->vol_flags & FILE_SUPPORTS_REPARSE_POINTS) { - ret = win32_set_reparse_data(h, inode, - lte, stream_path, - args); - if (ret) - return ret; - } else { - DEBUG("Cannot set reparse data on \"%ls\": volume " - "does not support reparse points", stream_path); - } - } else if (lte != NULL && - !(args->vol_flags & FILE_SUPPORTS_ENCRYPTION && - inode->i_attributes & FILE_ATTRIBUTE_ENCRYPTED)) - { - /* Extract the data of the unnamed stream, unless the - * lookup table entry is NULL (indicating an empty - * stream for which no data needs to be extracted), or - * the stream is encrypted and therefore was already - * extracted as a special case. */ - ret = do_win32_extract_stream(h, lte); - if (ret) - return ret; - } - - if (dentry_has_short_name(dentry)) - short_name = dentry->short_name; - else - short_name = L""; - /* Set short name */ - if (!SetFileShortNameW(h, short_name)) { - #if 0 - DWORD err = GetLastError(); - ERROR("Could not set short name on \"%ls\"", stream_path); - win32_error(err); - #endif - } - } else { - /* Extract the data for a named data stream. */ - if (lte != NULL) { - DEBUG("Extracting named data stream \"%ls\" (len = %"PRIu64")", - stream_path, wim_resource_size(lte)); - ret = do_win32_extract_stream(h, lte); - } - } - return ret; -} - -static int -win32_decrypt_file(HANDLE open_handle, const wchar_t *path) -{ - DWORD err; - /* We cannot call DecryptFileW() while there is an open handle to the - * file. So close it first. */ - if (!CloseHandle(open_handle)) { - err = GetLastError(); - ERROR("Failed to close handle for \"%ls\"", path); - win32_error(err); - return WIMLIB_ERR_WRITE; - } - if (!DecryptFileW(path, 0 /* reserved parameter; set to 0 */)) { - err = GetLastError(); - ERROR("Failed to decrypt file \"%ls\"", path); - win32_error(err); - return WIMLIB_ERR_WRITE; - } - return 0; -} - -/* - * Create and extract a stream to a file, or create a directory, using the - * Windows API. - * - * This handles reparse points, directories, alternate data streams, encrypted - * files, compressed files, etc. - * - * @dentry: WIM dentry for the file or directory being extracted. - * - * @path: Path to extract the file to. - * - * @stream_name_utf16: - * Name of the stream, or NULL if the stream is unnamed. This will - * be called with a NULL stream_name_utf16 before any non-NULL - * stream_name_utf16's. - * - * @lte: WIM lookup table entry for the stream. May be NULL to indicate - * a stream of length 0. - * - * @args: Additional apply context, including flags indicating supported - * volume features. - * - * Returns 0 on success; nonzero on failure. - */ -static int -win32_extract_stream(const struct wim_dentry *dentry, - const wchar_t *path, - const wchar_t *stream_name_utf16, - struct wim_lookup_table_entry *lte, - struct apply_args *args) -{ - wchar_t *stream_path; - HANDLE h; - int ret; - DWORD err; - DWORD creationDisposition = CREATE_ALWAYS; - DWORD requestedAccess; - BY_HANDLE_FILE_INFORMATION file_info; - unsigned remaining_sharing_violations = 1000; - const struct wim_inode *inode = dentry->d_inode; - - if (stream_name_utf16) { - /* Named stream. Create a buffer that contains the UTF-16LE - * string [./]path:stream_name_utf16. This is needed to - * create and open the stream using CreateFileW(). I'm not - * aware of any other APIs to do this. Note: the '$DATA' suffix - * seems to be unneeded. Additional note: a "./" prefix needs - * to be added when the path is not absolute to avoid ambiguity - * with drive letters. */ - size_t stream_path_nchars; - size_t path_nchars; - size_t stream_name_nchars; - const wchar_t *prefix; - - path_nchars = wcslen(path); - stream_name_nchars = wcslen(stream_name_utf16); - stream_path_nchars = path_nchars + 1 + stream_name_nchars; - if (path[0] != cpu_to_le16(L'\0') && - path[0] != cpu_to_le16(L'/') && - path[0] != cpu_to_le16(L'\\') && - path[1] != cpu_to_le16(L':')) - { - prefix = L"./"; - stream_path_nchars += 2; - } else { - prefix = L""; - } - stream_path = alloca((stream_path_nchars + 1) * sizeof(wchar_t)); - swprintf(stream_path, L"%ls%ls:%ls", - prefix, path, stream_name_utf16); - } else { - /* Unnamed stream; its path is just the path to the file itself. - * */ - stream_path = (wchar_t*)path; - - ret = win32_begin_extract_unnamed_stream(inode, lte, path, - &creationDisposition, - args->vol_flags); - if (ret) - goto fail; - } - - DEBUG("Opening \"%ls\"", stream_path); - /* DELETE access is needed for SetFileShortNameW(), for some reason. */ - requestedAccess = GENERIC_READ | GENERIC_WRITE | DELETE | - ACCESS_SYSTEM_SECURITY; -try_open_again: - /* Open the stream to be extracted. Depending on what we have set - * creationDisposition to, we may be creating this for the first time, - * or we may be opening on existing stream we already created using - * CreateDirectoryW() or OpenEncryptedFileRawW(). */ - h = CreateFileW(stream_path, - requestedAccess, - FILE_SHARE_READ, - NULL, - creationDisposition, - win32_get_create_flags_and_attributes(inode->i_attributes), - NULL); - if (h == INVALID_HANDLE_VALUE) { - err = GetLastError(); - if (err == ERROR_ACCESS_DENIED && - path_is_root_of_drive(stream_path)) - { - ret = 0; - goto out; - } - if ((err == ERROR_PRIVILEGE_NOT_HELD || - err == ERROR_ACCESS_DENIED) && - (requestedAccess & ACCESS_SYSTEM_SECURITY)) - { - /* Try opening the file again without privilege to - * modify SACL. */ - requestedAccess &= ~ACCESS_SYSTEM_SECURITY; - goto try_open_again; - } - if (err == ERROR_SHARING_VIOLATION) { - if (remaining_sharing_violations) { - --remaining_sharing_violations; - /* This can happen when restoring encrypted directories - * for some reason. Probably a bug in EncryptFile(). */ - WARNING("Couldn't open \"%ls\" due to sharing violation; " - "re-trying after 100ms", stream_path); - Sleep(100); - goto try_open_again; - } else { - ERROR("Too many sharing violations; giving up..."); - } - } else { - if (creationDisposition == OPEN_EXISTING) - ERROR("Failed to open \"%ls\"", stream_path); - else - ERROR("Failed to create \"%ls\"", stream_path); - win32_error(err); - } - ret = WIMLIB_ERR_OPEN; - goto fail; - } - - /* Check the attributes of the file we just opened, and remove - * encryption or compression if either was set by default but is not - * supposed to be set based on the WIM inode attributes. */ - if (!GetFileInformationByHandle(h, &file_info)) { - err = GetLastError(); - ERROR("Failed to get attributes of \"%ls\"", stream_path); - win32_error(err); - ret = WIMLIB_ERR_STAT; - goto fail_close_handle; - } - - /* Remove encryption? */ - if (file_info.dwFileAttributes & FILE_ATTRIBUTE_ENCRYPTED && - !(inode->i_attributes & FILE_ATTRIBUTE_ENCRYPTED)) - { - /* File defaulted to encrypted due to being in an encrypted - * directory, but is not actually supposed to be encrypted. - * - * This is a workaround, because I'm not aware of any way to - * directly (e.g. with CreateFileW()) create an unencrypted file - * in a directory with FILE_ATTRIBUTE_ENCRYPTED set. */ - ret = win32_decrypt_file(h, stream_path); - if (ret) - goto fail; /* win32_decrypt_file() closed the handle. */ - creationDisposition = OPEN_EXISTING; - goto try_open_again; - } - - /* Remove compression? */ - if (file_info.dwFileAttributes & FILE_ATTRIBUTE_COMPRESSED && - !(inode->i_attributes & FILE_ATTRIBUTE_COMPRESSED)) - { - /* Similar to the encrypted case, above, if the file defaulted - * to compressed due to being in an compressed directory, but is - * not actually supposed to be compressed, explicitly set the - * compression format to COMPRESSION_FORMAT_NONE. */ - ret = win32_set_compression_state(h, COMPRESSION_FORMAT_NONE, - stream_path); - if (ret) - goto fail_close_handle; - } - - /* Set compression and/or sparse attributes if needed */ - ret = win32_set_special_stream_attributes(h, inode, lte, path, - args->vol_flags); - - if (ret) - goto fail_close_handle; - - /* At this point we have at least created the needed stream with the - * appropriate attributes. We have yet to set the appropriate security - * descriptor and actually extract the stream data (other than for - * extracted files, which were already extracted). - * win32_finish_extract_stream() handles these additional steps. */ - ret = win32_finish_extract_stream(h, dentry, lte, stream_path, - stream_name_utf16, args); - if (ret) - goto fail_close_handle; - - /* Done extracting the stream. Close the handle and return. */ - DEBUG("Closing \"%ls\"", stream_path); - if (!CloseHandle(h)) { - err = GetLastError(); - ERROR("Failed to close \"%ls\"", stream_path); - win32_error(err); - ret = WIMLIB_ERR_WRITE; - goto fail; - } - ret = 0; - goto out; -fail_close_handle: - CloseHandle(h); -fail: - ERROR("Error extracting \"%ls\"", stream_path); -out: - return ret; -} - -/* - * Creates a file, directory, or reparse point and extracts all streams to it - * (unnamed data stream and/or reparse point stream, plus any alternate data - * streams). Handles sparse, compressed, and/or encrypted files. - * - * @dentry: WIM dentry for this file or directory. - * @path: UTF-16LE external path to extract the inode to. - * @args: Additional extraction context. - * - * Returns 0 on success; nonzero on failure. - */ -static int -win32_extract_streams(const struct wim_dentry *dentry, - const wchar_t *path, struct apply_args *args) -{ - struct wim_lookup_table_entry *unnamed_lte; - int ret; - const struct wim_inode *inode = dentry->d_inode; - - /* First extract the unnamed stream. */ - - unnamed_lte = inode_unnamed_lte_resolved(inode); - ret = win32_extract_stream(dentry, path, NULL, unnamed_lte, args); - if (ret) - goto out; - - /* Extract any named streams, if supported by the volume. */ - - if (!(args->vol_flags & FILE_NAMED_STREAMS)) - goto out; - for (u16 i = 0; i < inode->i_num_ads; i++) { - const struct wim_ads_entry *ads_entry = &inode->i_ads_entries[i]; - - /* Skip the unnamed stream if it's in the ADS entries (we - * already extracted it...) */ - if (ads_entry->stream_name_nbytes == 0) - continue; - - /* Skip special UNIX data entries (see documentation for - * WIMLIB_ADD_FLAG_UNIX_DATA) */ - if (ads_entry->stream_name_nbytes == WIMLIB_UNIX_DATA_TAG_UTF16LE_NBYTES - && !memcmp(ads_entry->stream_name, - WIMLIB_UNIX_DATA_TAG_UTF16LE, - WIMLIB_UNIX_DATA_TAG_UTF16LE_NBYTES)) - continue; - - /* Extract the named stream */ - ret = win32_extract_stream(dentry, - path, - ads_entry->stream_name, - ads_entry->lte, - args); - if (ret) - break; - } -out: - return ret; -} - -/* If not done already, load the supported feature flags for the volume onto - * which the image is being extracted, and warn the user about any missing - * features that could be important. */ -static int -win32_check_vol_flags(const wchar_t *output_path, struct apply_args *args) -{ - if (args->have_vol_flags) - return 0; - - win32_get_vol_flags(output_path, &args->vol_flags); - args->have_vol_flags = true; - /* Warn the user about data that may not be extracted. */ - if (!(args->vol_flags & FILE_SUPPORTS_SPARSE_FILES)) - WARNING("Volume does not support sparse files!\n" - " Sparse files will be extracted as non-sparse."); - if (!(args->vol_flags & FILE_SUPPORTS_REPARSE_POINTS)) - WARNING("Volume does not support reparse points!\n" - " Reparse point data will not be extracted."); - if (!(args->vol_flags & FILE_NAMED_STREAMS)) { - WARNING("Volume does not support named data streams!\n" - " Named data streams will not be extracted."); - } - if (!(args->vol_flags & FILE_SUPPORTS_ENCRYPTION)) { - WARNING("Volume does not support encryption!\n" - " Encrypted files will be extracted as raw data."); - } - if (!(args->vol_flags & FILE_FILE_COMPRESSION)) { - WARNING("Volume does not support transparent compression!\n" - " Compressed files will be extracted as non-compressed."); - } - if (!(args->vol_flags & FILE_PERSISTENT_ACLS)) { - if (args->extract_flags & WIMLIB_EXTRACT_FLAG_STRICT_ACLS) { - ERROR("Strict ACLs requested, but the volume does not " - "support ACLs!"); - return WIMLIB_ERR_VOLUME_LACKS_FEATURES; - } else { - WARNING("Volume does not support persistent ACLS!\n" - " File permissions will not be extracted."); - } - } - return 0; -} - -/* - * Try extracting a hard link. - * - * @output_path: Path to link to be extracted. - * - * @inode: WIM inode that the link is to; inode->i_extracted_file - * the path to a name of the file that has already been - * extracted (we use this to create the hard link). - * - * @args: Additional apply context, used here to keep track of - * the number of times creating a hard link failed due to - * ERROR_INVALID_FUNCTION. This error should indicate that hard - * links are not supported by the volume, and we would like to - * warn the user a few times, but not too many times. - * - * Returns 0 if the hard link was successfully extracted. Returns - * WIMLIB_ERR_LINK (> 0) if an error occurred, other than hard links possibly - * being unsupported by the volume. Returns a negative value if creating the - * hard link failed due to ERROR_INVALID_FUNCTION. - */ -static int -win32_try_hard_link(const wchar_t *output_path, const struct wim_inode *inode, - struct apply_args *args) -{ - DWORD err; - - /* There is a volume flag for this (FILE_SUPPORTS_HARD_LINKS), - * but it's only available on Windows 7 and later. So no use - * even checking it, really. Instead, CreateHardLinkW() will - * apparently return ERROR_INVALID_FUNCTION if the volume does - * not support hard links. */ - DEBUG("Creating hard link \"%ls => %ls\"", - output_path, inode->i_extracted_file); - if (CreateHardLinkW(output_path, inode->i_extracted_file, NULL)) - return 0; - - err = GetLastError(); - if (err != ERROR_INVALID_FUNCTION) { - ERROR("Can't create hard link \"%ls => %ls\"", - output_path, inode->i_extracted_file); - win32_error(err); - return WIMLIB_ERR_LINK; - } else { - args->num_hard_links_failed++; - if (args->num_hard_links_failed <= MAX_CREATE_HARD_LINK_WARNINGS) { - WARNING("Can't create hard link \"%ls => %ls\":\n" - " Volume does not support hard links!\n" - " Falling back to extracting a copy of the file.", - output_path, inode->i_extracted_file); - } - if (args->num_hard_links_failed == MAX_CREATE_HARD_LINK_WARNINGS) { - WARNING("Suppressing further hard linking warnings..."); - } - return -1; - } -} - -/* Extract a file, directory, reparse point, or hard link to an - * already-extracted file using the Win32 API */ -int -win32_do_apply_dentry(const wchar_t *output_path, - size_t output_path_num_chars, - struct wim_dentry *dentry, - struct apply_args *args) -{ - int ret; - struct wim_inode *inode = dentry->d_inode; - - ret = win32_check_vol_flags(output_path, args); - if (ret) - return ret; - if (inode->i_nlink > 1 && inode->i_extracted_file != NULL) { - /* Linked file, with another name already extracted. Create a - * hard link. */ - ret = win32_try_hard_link(output_path, inode, args); - if (ret >= 0) - return ret; - /* Negative return value from win32_try_hard_link() indicates - * that hard links are probably not supported by the volume. - * Fall back to extracting a copy of the file. */ - } - - /* If this is a reparse point and the volume does not support reparse - * points, just skip it completely. */ - if (inode->i_attributes & FILE_ATTRIBUTE_REPARSE_POINT && - !(args->vol_flags & FILE_SUPPORTS_REPARSE_POINTS)) - { - WARNING("Skipping extraction of reparse point \"%ls\":\n" - " Not supported by destination filesystem", - output_path); - } else { - /* Create the file, directory, or reparse point, and extract the - * data streams. */ - ret = win32_extract_streams(dentry, output_path, args); - if (ret) - return ret; - } - if (inode->i_extracted_file == NULL) { - const struct wim_lookup_table_entry *lte; - - /* Tally bytes extracted, including all alternate data streams, - * unless we extracted a hard link (or, at least extracted a - * name that was supposed to be a hard link) */ - for (unsigned i = 0; i <= inode->i_num_ads; i++) { - lte = inode_stream_lte_resolved(inode, i); - if (lte) - args->progress.extract.completed_bytes += - wim_resource_size(lte); - } - if (inode->i_nlink > 1) { - /* Save extracted path for a later call to - * CreateHardLinkW() if this inode has multiple links. - * */ - inode->i_extracted_file = WSTRDUP(output_path); - if (!inode->i_extracted_file) - return WIMLIB_ERR_NOMEM; - } - } - return 0; -} - -/* Set timestamps on an extracted file using the Win32 API */ -int -win32_do_apply_dentry_timestamps(const wchar_t *path, - size_t path_num_chars, - struct wim_dentry *dentry, - struct apply_args *args) -{ - DWORD err; - HANDLE h; - const struct wim_inode *inode = dentry->d_inode; - - if (inode->i_attributes & FILE_ATTRIBUTE_REPARSE_POINT && - !(args->vol_flags & FILE_SUPPORTS_REPARSE_POINTS)) - { - /* Skip reparse points not extracted */ - return 0; - } - - /* Windows doesn't let you change the timestamps of the root directory - * (at least on FAT, which is dumb but expected since FAT doesn't store - * any metadata about the root directory...) */ - if (path_is_root_of_drive(path)) - return 0; - - DEBUG("Opening \"%ls\" to set timestamps", path); - h = win32_open_existing_file(path, FILE_WRITE_ATTRIBUTES); - if (h == INVALID_HANDLE_VALUE) { - err = GetLastError(); - goto fail; - } - - FILETIME creationTime = {.dwLowDateTime = inode->i_creation_time & 0xffffffff, - .dwHighDateTime = inode->i_creation_time >> 32}; - FILETIME lastAccessTime = {.dwLowDateTime = inode->i_last_access_time & 0xffffffff, - .dwHighDateTime = inode->i_last_access_time >> 32}; - FILETIME lastWriteTime = {.dwLowDateTime = inode->i_last_write_time & 0xffffffff, - .dwHighDateTime = inode->i_last_write_time >> 32}; - - DEBUG("Calling SetFileTime() on \"%ls\"", path); - if (!SetFileTime(h, &creationTime, &lastAccessTime, &lastWriteTime)) { - err = GetLastError(); - CloseHandle(h); - goto fail; - } - DEBUG("Closing \"%ls\"", path); - if (!CloseHandle(h)) { - err = GetLastError(); - goto fail; - } - goto out; -fail: - /* Only warn if setting timestamps failed; still return 0. */ - WARNING("Can't set timestamps on \"%ls\"", path); - win32_error(err); -out: - return 0; -} - -/* Replacement for POSIX fsync() */ -int -fsync(int fd) -{ - HANDLE h; - - h = (HANDLE)_get_osfhandle(fd); - if (h == INVALID_HANDLE_VALUE) - goto err; - if (!FlushFileBuffers(h)) - goto err_set_errno; - return 0; -err_set_errno: - set_errno_from_GetLastError(); -err: - return -1; -} - -/* Use the Win32 API to get the number of processors */ -unsigned -win32_get_number_of_processors() -{ - SYSTEM_INFO sysinfo; - GetSystemInfo(&sysinfo); - return sysinfo.dwNumberOfProcessors; -} - -/* Replacement for POSIX-2008 realpath(). Warning: partial functionality only - * (resolved_path must be NULL). Also I highly doubt that GetFullPathName - * really does the right thing under all circumstances. */ -wchar_t * -realpath(const wchar_t *path, wchar_t *resolved_path) -{ - DWORD ret; - DWORD err; - wimlib_assert(resolved_path == NULL); - - ret = GetFullPathNameW(path, 0, NULL, NULL); - if (!ret) { - err = GetLastError(); - goto fail_win32; - } - - resolved_path = TMALLOC(ret); - if (!resolved_path) - goto out; - ret = GetFullPathNameW(path, ret, resolved_path, NULL); - if (!ret) { - err = GetLastError(); - free(resolved_path); - resolved_path = NULL; - goto fail_win32; - } - goto out; -fail_win32: - errno = win32_error_to_errno(err); -out: - return resolved_path; -} - -/* rename() on Windows fails if the destination file exists. And we need to - * make it work on wide characters. Fix it. */ -int -win32_rename_replacement(const wchar_t *oldpath, const wchar_t *newpath) -{ - if (MoveFileExW(oldpath, newpath, MOVEFILE_REPLACE_EXISTING)) { - return 0; - } else { - set_errno_from_GetLastError(); - return -1; - } -} - -/* Replacement for POSIX fnmatch() (partial functionality only) */ -int -fnmatch(const wchar_t *pattern, const wchar_t *string, int flags) -{ - if (PathMatchSpecW(string, pattern)) - return 0; - else - return FNM_NOMATCH; -} - -/* truncate() replacement */ -int -win32_truncate_replacement(const wchar_t *path, off_t size) -{ - DWORD err = NO_ERROR; - LARGE_INTEGER liOffset; - - HANDLE h = win32_open_existing_file(path, GENERIC_WRITE); - if (h == INVALID_HANDLE_VALUE) - goto fail; - - liOffset.QuadPart = size; - if (!SetFilePointerEx(h, liOffset, NULL, FILE_BEGIN)) - goto fail_close_handle; - - if (!SetEndOfFile(h)) - goto fail_close_handle; - CloseHandle(h); - return 0; - -fail_close_handle: - err = GetLastError(); - CloseHandle(h); -fail: - if (err == NO_ERROR) - err = GetLastError(); - errno = win32_error_to_errno(err); - return -1; -} - - -/* This really could be replaced with _wcserror_s, but this doesn't seem to - * actually be available in MSVCRT.DLL on Windows XP (perhaps it's statically - * linked in by Visual Studio...?). */ -extern int -win32_strerror_r_replacement(int errnum, wchar_t *buf, size_t buflen) -{ - static pthread_mutex_t strerror_lock = PTHREAD_MUTEX_INITIALIZER; - - pthread_mutex_lock(&strerror_lock); - mbstowcs(buf, strerror(errnum), buflen); - buf[buflen - 1] = '\0'; - pthread_mutex_unlock(&strerror_lock); - return 0; -} - -static int -do_pread_or_pwrite(int fd, void *buf, size_t count, off_t offset, - bool is_pwrite) -{ - HANDLE h; - LARGE_INTEGER orig_offset; - DWORD bytes_read_or_written; - LARGE_INTEGER relative_offset; - OVERLAPPED overlapped; - BOOL bret; - - wimlib_assert(count <= 0xffffffff); - - h = (HANDLE)_get_osfhandle(fd); - if (h == INVALID_HANDLE_VALUE) - goto err; - - /* Get original position */ - relative_offset.QuadPart = 0; - if (!SetFilePointerEx(h, relative_offset, &orig_offset, FILE_CURRENT)) - goto err_set_errno; - - memset(&overlapped, 0, sizeof(overlapped)); - overlapped.Offset = offset; - overlapped.OffsetHigh = offset >> 32; - - /* Do the read or write at the specified offset */ - if (is_pwrite) - bret = WriteFile(h, buf, count, &bytes_read_or_written, &overlapped); - else - bret = ReadFile(h, buf, count, &bytes_read_or_written, &overlapped); - if (!bret) - goto err_set_errno; - - /* Restore the original position */ - if (!SetFilePointerEx(h, orig_offset, NULL, FILE_BEGIN)) - goto err_set_errno; - - return bytes_read_or_written; -err_set_errno: - set_errno_from_GetLastError(); -err: - return -1; -} - -/* Dumb Windows implementation of pread(). It temporarily changes the file - * offset, so it is not safe to use with readers/writers on the same file - * descriptor. */ -extern ssize_t -win32_pread(int fd, void *buf, size_t count, off_t offset) -{ - return do_pread_or_pwrite(fd, buf, count, offset, false); -} - -/* Dumb Windows implementation of pwrite(). It temporarily changes the file - * offset, so it is not safe to use with readers/writers on the same file - * descriptor. */ -extern ssize_t -win32_pwrite(int fd, const void *buf, size_t count, off_t offset) -{ - return do_pread_or_pwrite(fd, (void*)buf, count, offset, true); -} - -/* Dumb Windows implementation of writev(). It writes the vectors one at a - * time. */ -extern ssize_t -win32_writev(int fd, const struct iovec *iov, int iovcnt) -{ - ssize_t total_bytes_written = 0; - - if (iovcnt <= 0) { - errno = EINVAL; - return -1; - } - for (int i = 0; i < iovcnt; i++) { - ssize_t bytes_written; - - bytes_written = write(fd, iov[i].iov_base, iov[i].iov_len); - if (bytes_written >= 0) - total_bytes_written += bytes_written; - if (bytes_written != iov[i].iov_len) { - if (total_bytes_written == 0) - total_bytes_written = -1; - break; - } - } - return total_bytes_written; -} - -#endif /* __WIN32__ */ diff --git a/src/win32_apply.c b/src/win32_apply.c new file mode 100644 index 00000000..90051f42 --- /dev/null +++ b/src/win32_apply.c @@ -0,0 +1,1244 @@ +#ifdef __WIN32__ + +#include /* for SetSecurityInfo() */ + +#include "win32_common.h" +#include "wimlib_internal.h" +#include "dentry.h" +#include "lookup_table.h" +#include "endianness.h" + +#define MAX_CREATE_HARD_LINK_WARNINGS 5 +#define MAX_CREATE_SOFT_LINK_WARNINGS 5 + +#define MAX_SET_SD_ACCESS_DENIED_WARNINGS 1 +#define MAX_SET_SACL_PRIV_NOTHELD_WARNINGS 1 + +static const wchar_t *apply_access_denied_msg = +L"If you are not running this program as the administrator, you may\n" + " need to do so, so that all data and metadata can be extracted\n" + " exactly as the origignal copy. However, if you do not care that\n" + " the security descriptors are extracted correctly, you could run\n" + " `wimlib-imagex apply' with the --no-acls flag instead.\n" + ; + + +static int +win32_extract_try_rpfix(u8 *rpbuf, + const wchar_t *extract_root_realpath, + unsigned extract_root_realpath_nchars) +{ + struct reparse_data rpdata; + wchar_t *target; + size_t target_nchars; + size_t stripped_nchars; + wchar_t *stripped_target; + wchar_t stripped_target_nchars; + int ret; + + utf16lechar *new_target; + utf16lechar *new_print_name; + size_t new_target_nchars; + size_t new_print_name_nchars; + utf16lechar *p; + + ret = parse_reparse_data(rpbuf, 8 + le16_to_cpu(*(u16*)(rpbuf + 4)), + &rpdata); + if (ret) + return ret; + + if (extract_root_realpath[0] == L'\0' || + extract_root_realpath[1] != L':' || + extract_root_realpath[2] != L'\\') + { + ERROR("Can't understand full path format \"%ls\". " + "Try turning reparse point fixups off...", + extract_root_realpath); + return WIMLIB_ERR_REPARSE_POINT_FIXUP_FAILED; + } + + ret = parse_substitute_name(rpdata.substitute_name, + rpdata.substitute_name_nbytes, + rpdata.rptag); + if (ret < 0) + return 0; + stripped_nchars = ret; + target = rpdata.substitute_name; + target_nchars = rpdata.substitute_name_nbytes / sizeof(utf16lechar); + stripped_target = target + 6; + stripped_target_nchars = target_nchars - stripped_nchars; + + new_target = alloca((6 + extract_root_realpath_nchars + + stripped_target_nchars) * sizeof(utf16lechar)); + + p = new_target; + if (stripped_nchars == 6) { + /* Include \??\ prefix if it was present before */ + wmemcpy(p, L"\\??\\", 4); + p += 4; + } + + /* Print name excludes the \??\ if present. */ + new_print_name = p; + if (stripped_nchars != 0) { + /* Get drive letter from real path to extract root, if a drive + * letter was present before. */ + *p++ = extract_root_realpath[0]; + *p++ = extract_root_realpath[1]; + } + /* Copy the rest of the extract root */ + wmemcpy(p, extract_root_realpath + 2, extract_root_realpath_nchars - 2); + p += extract_root_realpath_nchars - 2; + + /* Append the stripped target */ + wmemcpy(p, stripped_target, stripped_target_nchars); + p += stripped_target_nchars; + new_target_nchars = p - new_target; + new_print_name_nchars = p - new_print_name; + + if (new_target_nchars * sizeof(utf16lechar) >= REPARSE_POINT_MAX_SIZE || + new_print_name_nchars * sizeof(utf16lechar) >= REPARSE_POINT_MAX_SIZE) + { + ERROR("Path names too long to do reparse point fixup!"); + return WIMLIB_ERR_REPARSE_POINT_FIXUP_FAILED; + } + rpdata.substitute_name = new_target; + rpdata.substitute_name_nbytes = new_target_nchars * sizeof(utf16lechar); + rpdata.print_name = new_print_name; + rpdata.print_name_nbytes = new_print_name_nchars * sizeof(utf16lechar); + return make_reparse_buffer(&rpdata, rpbuf); +} + +/* Wrapper around the FSCTL_SET_REPARSE_POINT ioctl to set the reparse data on + * an extracted reparse point. */ +static int +win32_set_reparse_data(HANDLE h, + const struct wim_inode *inode, + const struct wim_lookup_table_entry *lte, + const wchar_t *path, + struct apply_args *args) +{ + int ret; + u8 rpbuf[REPARSE_POINT_MAX_SIZE]; + DWORD bytesReturned; + + DEBUG("Setting reparse data on \"%ls\"", path); + + ret = wim_inode_get_reparse_data(inode, rpbuf); + if (ret) + return ret; + + if (args->extract_flags & WIMLIB_EXTRACT_FLAG_RPFIX && + (inode->i_reparse_tag == WIM_IO_REPARSE_TAG_SYMLINK || + inode->i_reparse_tag == WIM_IO_REPARSE_TAG_MOUNT_POINT) && + !inode->i_not_rpfixed) + { + ret = win32_extract_try_rpfix(rpbuf, + args->target_realpath, + args->target_realpath_len); + if (ret) + return WIMLIB_ERR_REPARSE_POINT_FIXUP_FAILED; + } + + /* Set the reparse data on the open file using the + * FSCTL_SET_REPARSE_POINT ioctl. + * + * There are contradictions in Microsoft's documentation for this: + * + * "If hDevice was opened without specifying FILE_FLAG_OVERLAPPED, + * lpOverlapped is ignored." + * + * --- So setting lpOverlapped to NULL is okay since it's ignored. + * + * "If lpOverlapped is NULL, lpBytesReturned cannot be NULL. Even when an + * operation returns no output data and lpOutBuffer is NULL, + * DeviceIoControl makes use of lpBytesReturned. After such an + * operation, the value of lpBytesReturned is meaningless." + * + * --- So lpOverlapped not really ignored, as it affects another + * parameter. This is the actual behavior: lpBytesReturned must be + * specified, even though lpBytesReturned is documented as: + * + * "Not used with this operation; set to NULL." + */ + if (!DeviceIoControl(h, FSCTL_SET_REPARSE_POINT, rpbuf, + 8 + le16_to_cpu(*(u16*)(rpbuf + 4)), + NULL, 0, + &bytesReturned /* lpBytesReturned */, + NULL /* lpOverlapped */)) + { + DWORD err = GetLastError(); + if (err == ERROR_ACCESS_DENIED || err == ERROR_PRIVILEGE_NOT_HELD) + { + args->num_soft_links_failed++; + if (args->num_soft_links_failed <= MAX_CREATE_SOFT_LINK_WARNINGS) { + WARNING("Can't set reparse data on \"%ls\": Access denied!\n" + " You may be trying to extract a symbolic " + "link without the\n" + " SeCreateSymbolicLink privilege, which by " + "default non-Administrator\n" + " accounts do not have.", path); + } + if (args->num_hard_links_failed == MAX_CREATE_HARD_LINK_WARNINGS) { + WARNING("Suppressing further warnings regarding failure to extract\n" + " reparse points due to insufficient privileges..."); + } + } else { + ERROR("Failed to set reparse data on \"%ls\"", path); + win32_error(err); + if (inode->i_reparse_tag == WIM_IO_REPARSE_TAG_SYMLINK || + inode->i_reparse_tag == WIM_IO_REPARSE_TAG_MOUNT_POINT) + return WIMLIB_ERR_LINK; + else + return WIMLIB_ERR_WRITE; + } + } + return 0; +} + +/* Wrapper around the FSCTL_SET_COMPRESSION ioctl to change the + * FILE_ATTRIBUTE_COMPRESSED flag of a file or directory. */ +static int +win32_set_compression_state(HANDLE hFile, USHORT format, const wchar_t *path) +{ + DWORD bytesReturned; + if (!DeviceIoControl(hFile, FSCTL_SET_COMPRESSION, + &format, sizeof(USHORT), + NULL, 0, + &bytesReturned, NULL)) + { + /* Could be a warning only, but we only call this if the volume + * supports compression. So I'm calling this an error. */ + DWORD err = GetLastError(); + ERROR("Failed to set compression flag on \"%ls\"", path); + win32_error(err); + if (err == ERROR_ACCESS_DENIED || err == ERROR_PRIVILEGE_NOT_HELD) + return WIMLIB_ERR_INSUFFICIENT_PRIVILEGES_TO_EXTRACT; + else + return WIMLIB_ERR_WRITE; + } + return 0; +} + +/* Wrapper around FSCTL_SET_SPARSE ioctl to set a file as sparse. */ +static int +win32_set_sparse(HANDLE hFile, const wchar_t *path) +{ + DWORD bytesReturned; + if (!DeviceIoControl(hFile, FSCTL_SET_SPARSE, + NULL, 0, + NULL, 0, + &bytesReturned, NULL)) + { + /* Could be a warning only, but we only call this if the volume + * supports sparse files. So I'm calling this an error. */ + DWORD err = GetLastError(); + WARNING("Failed to set sparse flag on \"%ls\"", path); + win32_error(err); + if (err == ERROR_ACCESS_DENIED || err == ERROR_PRIVILEGE_NOT_HELD) + return WIMLIB_ERR_INSUFFICIENT_PRIVILEGES_TO_EXTRACT; + else + return WIMLIB_ERR_WRITE; + } + return 0; +} + +/* + * Sets the security descriptor on an extracted file. + */ +static int +win32_set_security_data(const struct wim_inode *inode, + HANDLE hFile, + const wchar_t *path, + struct apply_args *args) +{ + PSECURITY_DESCRIPTOR descriptor; + unsigned long n; + DWORD err; + const struct wim_security_data *sd; + + SECURITY_INFORMATION securityInformation = 0; + + void *owner = NULL; + void *group = NULL; + ACL *dacl = NULL; + ACL *sacl = NULL; + + BOOL owner_defaulted; + BOOL group_defaulted; + BOOL dacl_present; + BOOL dacl_defaulted; + BOOL sacl_present; + BOOL sacl_defaulted; + + sd = wim_const_security_data(args->w); + descriptor = sd->descriptors[inode->i_security_id]; + + GetSecurityDescriptorOwner(descriptor, &owner, &owner_defaulted); + if (owner) + securityInformation |= OWNER_SECURITY_INFORMATION; + + GetSecurityDescriptorGroup(descriptor, &group, &group_defaulted); + if (group) + securityInformation |= GROUP_SECURITY_INFORMATION; + + GetSecurityDescriptorDacl(descriptor, &dacl_present, + &dacl, &dacl_defaulted); + if (dacl) + securityInformation |= DACL_SECURITY_INFORMATION; + + GetSecurityDescriptorSacl(descriptor, &sacl_present, + &sacl, &sacl_defaulted); + if (sacl) + securityInformation |= SACL_SECURITY_INFORMATION; + +again: + if (securityInformation == 0) + return 0; + if (SetSecurityInfo(hFile, SE_FILE_OBJECT, + securityInformation, owner, group, dacl, sacl)) + return 0; + err = GetLastError(); + if (args->extract_flags & WIMLIB_EXTRACT_FLAG_STRICT_ACLS) + goto fail; + switch (err) { + case ERROR_PRIVILEGE_NOT_HELD: + if (securityInformation & SACL_SECURITY_INFORMATION) { + n = args->num_set_sacl_priv_notheld++; + securityInformation &= ~SACL_SECURITY_INFORMATION; + sacl = NULL; + if (n < MAX_SET_SACL_PRIV_NOTHELD_WARNINGS) { + WARNING( +"We don't have enough privileges to set the full security\n" +" descriptor on \"%ls\"!\n", path); + if (args->num_set_sd_access_denied + + args->num_set_sacl_priv_notheld == 1) + { + WARNING("%ls", apply_access_denied_msg); + } + WARNING("Re-trying with SACL omitted.\n", path); + } else if (n == MAX_SET_SACL_PRIV_NOTHELD_WARNINGS) { + WARNING( +"Suppressing further 'privileges not held' error messages when setting\n" +" security descriptors."); + } + goto again; + } + /* Fall through */ + case ERROR_INVALID_OWNER: + case ERROR_ACCESS_DENIED: + n = args->num_set_sd_access_denied++; + if (n < MAX_SET_SD_ACCESS_DENIED_WARNINGS) { + WARNING("Failed to set security descriptor on \"%ls\": " + "Access denied!\n", path); + if (args->num_set_sd_access_denied + + args->num_set_sacl_priv_notheld == 1) + { + WARNING("%ls", apply_access_denied_msg); + } + } else if (n == MAX_SET_SD_ACCESS_DENIED_WARNINGS) { + WARNING( +"Suppressing further access denied error messages when setting\n" +" security descriptors"); + } + return 0; + default: +fail: + ERROR("Failed to set security descriptor on \"%ls\"", path); + win32_error(err); + if (err == ERROR_ACCESS_DENIED || err == ERROR_PRIVILEGE_NOT_HELD) + return WIMLIB_ERR_INSUFFICIENT_PRIVILEGES_TO_EXTRACT; + else + return WIMLIB_ERR_WRITE; + } +} + + +static int +win32_extract_chunk(const void *buf, size_t len, void *arg) +{ + HANDLE hStream = arg; + + DWORD nbytes_written; + wimlib_assert(len <= 0xffffffff); + + if (!WriteFile(hStream, buf, len, &nbytes_written, NULL) || + nbytes_written != len) + { + DWORD err = GetLastError(); + ERROR("WriteFile(): write error"); + win32_error(err); + return WIMLIB_ERR_WRITE; + } + return 0; +} + +static int +do_win32_extract_stream(HANDLE hStream, const struct wim_lookup_table_entry *lte) +{ + return extract_wim_resource(lte, wim_resource_size(lte), + win32_extract_chunk, hStream); +} + +struct win32_encrypted_extract_ctx { + const struct wim_lookup_table_entry *lte; + u64 offset; +}; + +static DWORD WINAPI +win32_encrypted_import_cb(unsigned char *data, void *_ctx, + unsigned long *len_p) +{ + struct win32_encrypted_extract_ctx *ctx = _ctx; + unsigned long len = *len_p; + const struct wim_lookup_table_entry *lte = ctx->lte; + + len = min(len, wim_resource_size(lte) - ctx->offset); + + if (read_partial_wim_resource_into_buf(lte, len, ctx->offset, data)) + return ERROR_READ_FAULT; + + ctx->offset += len; + *len_p = len; + return ERROR_SUCCESS; +} + +/* Create an encrypted file and extract the raw encrypted data to it. + * + * @path: Path to encrypted file to create. + * @lte: WIM lookup_table entry for the raw encrypted data. + * + * This is separate from do_win32_extract_stream() because the WIM is supposed + * to contain the *raw* encrypted data, which needs to be extracted ("imported") + * using the special APIs OpenEncryptedFileRawW(), WriteEncryptedFileRaw(), and + * CloseEncryptedFileRaw(). + * + * Returns 0 on success; nonzero on failure. + */ +static int +do_win32_extract_encrypted_stream(const wchar_t *path, + const struct wim_lookup_table_entry *lte) +{ + void *file_ctx; + int ret; + + DEBUG("Opening file \"%ls\" to extract raw encrypted data", path); + + ret = OpenEncryptedFileRawW(path, CREATE_FOR_IMPORT, &file_ctx); + if (ret) { + ERROR("Failed to open \"%ls\" to write raw encrypted data", path); + win32_error(ret); + return WIMLIB_ERR_OPEN; + } + + if (lte) { + struct win32_encrypted_extract_ctx ctx; + + ctx.lte = lte; + ctx.offset = 0; + ret = WriteEncryptedFileRaw(win32_encrypted_import_cb, &ctx, file_ctx); + if (ret == ERROR_SUCCESS) { + ret = 0; + } else { + ret = WIMLIB_ERR_WRITE; + ERROR("Failed to extract encrypted file \"%ls\"", path); + } + } + CloseEncryptedFileRaw(file_ctx); + return ret; +} + +static bool +path_is_root_of_drive(const wchar_t *path) +{ + if (!*path) + return false; + + if (*path != L'/' && *path != L'\\') { + if (*(path + 1) == L':') + path += 2; + else + return false; + } + while (*path == L'/' || *path == L'\\') + path++; + return (*path == L'\0'); +} + +static inline DWORD +win32_mask_attributes(DWORD i_attributes) +{ + return i_attributes & ~(FILE_ATTRIBUTE_SPARSE_FILE | + FILE_ATTRIBUTE_COMPRESSED | + FILE_ATTRIBUTE_REPARSE_POINT | + FILE_ATTRIBUTE_DIRECTORY | + FILE_ATTRIBUTE_ENCRYPTED | + FILE_FLAG_DELETE_ON_CLOSE | + FILE_FLAG_NO_BUFFERING | + FILE_FLAG_OPEN_NO_RECALL | + FILE_FLAG_OVERLAPPED | + FILE_FLAG_RANDOM_ACCESS | + /*FILE_FLAG_SESSION_AWARE |*/ + FILE_FLAG_SEQUENTIAL_SCAN | + FILE_FLAG_WRITE_THROUGH); +} + +static inline DWORD +win32_get_create_flags_and_attributes(DWORD i_attributes) +{ + /* + * Some attributes cannot be set by passing them to CreateFile(). In + * particular: + * + * FILE_ATTRIBUTE_DIRECTORY: + * CreateDirectory() must be called instead of CreateFile(). + * + * FILE_ATTRIBUTE_SPARSE_FILE: + * Needs an ioctl. + * See: win32_set_sparse(). + * + * FILE_ATTRIBUTE_COMPRESSED: + * Not clear from the documentation, but apparently this needs an + * ioctl as well. + * See: win32_set_compressed(). + * + * FILE_ATTRIBUTE_REPARSE_POINT: + * Needs an ioctl, with the reparse data specified. + * See: win32_set_reparse_data(). + * + * In addition, clear any file flags in the attributes that we don't + * want, but also specify FILE_FLAG_OPEN_REPARSE_POINT and + * FILE_FLAG_BACKUP_SEMANTICS as we are a backup application. + */ + return win32_mask_attributes(i_attributes) | + FILE_FLAG_OPEN_REPARSE_POINT | + FILE_FLAG_BACKUP_SEMANTICS; +} + +/* Set compression and/or sparse attributes on a stream, if supported by the + * volume. */ +static int +win32_set_special_stream_attributes(HANDLE hFile, const struct wim_inode *inode, + struct wim_lookup_table_entry *unnamed_stream_lte, + const wchar_t *path, unsigned vol_flags) +{ + int ret; + + if (inode->i_attributes & FILE_ATTRIBUTE_COMPRESSED) { + if (vol_flags & FILE_FILE_COMPRESSION) { + ret = win32_set_compression_state(hFile, + COMPRESSION_FORMAT_DEFAULT, + path); + if (ret) + return ret; + } else { + DEBUG("Cannot set compression attribute on \"%ls\": " + "volume does not support transparent compression", + path); + } + } + + if (inode->i_attributes & FILE_ATTRIBUTE_SPARSE_FILE) { + if (vol_flags & FILE_SUPPORTS_SPARSE_FILES) { + DEBUG("Setting sparse flag on \"%ls\"", path); + ret = win32_set_sparse(hFile, path); + if (ret) + return ret; + } else { + DEBUG("Cannot set sparse attribute on \"%ls\": " + "volume does not support sparse files", + path); + } + } + return 0; +} + +/* Pre-create directories; extract encrypted streams */ +static int +win32_begin_extract_unnamed_stream(const struct wim_inode *inode, + const struct wim_lookup_table_entry *lte, + const wchar_t *path, + DWORD *creationDisposition_ret, + unsigned int vol_flags) +{ + DWORD err; + int ret; + + /* Directories must be created with CreateDirectoryW(). Then the call + * to CreateFileW() will merely open the directory that was already + * created rather than creating a new file. */ + if (inode->i_attributes & FILE_ATTRIBUTE_DIRECTORY && + !path_is_root_of_drive(path)) { + if (!CreateDirectoryW(path, NULL)) { + err = GetLastError(); + if (err != ERROR_ALREADY_EXISTS) { + ERROR("Failed to create directory \"%ls\"", + path); + win32_error(err); + return WIMLIB_ERR_MKDIR; + } + } + DEBUG("Created directory \"%ls\"", path); + *creationDisposition_ret = OPEN_EXISTING; + } + if (inode->i_attributes & FILE_ATTRIBUTE_ENCRYPTED && + vol_flags & FILE_SUPPORTS_ENCRYPTION) + { + if (inode->i_attributes & FILE_ATTRIBUTE_DIRECTORY) { + unsigned remaining_sharing_violations = 100; + while (!EncryptFile(path)) { + if (remaining_sharing_violations && + err == ERROR_SHARING_VIOLATION) + { + WARNING("Couldn't encrypt directory \"%ls\" " + "due to sharing violation; re-trying " + "after 100 ms", path); + Sleep(100); + remaining_sharing_violations--; + } else { + err = GetLastError(); + ERROR("Failed to encrypt directory \"%ls\"", + path); + win32_error(err); + return WIMLIB_ERR_WRITE; + } + } + } else { + ret = do_win32_extract_encrypted_stream(path, lte); + if (ret) + return ret; + DEBUG("Extracted encrypted file \"%ls\"", path); + } + *creationDisposition_ret = OPEN_EXISTING; + } + + /* Set file attributes if we created the file. Otherwise, we haven't + * created the file set and we will set the attributes in the call to + * CreateFileW(). + * + * The FAT filesystem does not let you change the attributes of the root + * directory, so treat that as a special case and do not set attributes. + * */ + if (*creationDisposition_ret == OPEN_EXISTING && + !path_is_root_of_drive(path)) + { + if (!SetFileAttributesW(path, + win32_mask_attributes(inode->i_attributes))) + { + err = GetLastError(); + ERROR("Failed to set attributes on \"%ls\"", path); + win32_error(err); + return WIMLIB_ERR_WRITE; + } + } + return 0; +} + +/* Set security descriptor and extract stream data or reparse data (skip the + * unnamed data stream of encrypted files, which was already extracted). */ +static int +win32_finish_extract_stream(HANDLE h, const struct wim_dentry *dentry, + const struct wim_lookup_table_entry *lte, + const wchar_t *stream_path, + const wchar_t *stream_name_utf16, + struct apply_args *args) +{ + int ret = 0; + const struct wim_inode *inode = dentry->d_inode; + const wchar_t *short_name; + if (stream_name_utf16 == NULL) { + /* Unnamed stream. */ + + /* Set security descriptor, unless the extract_flags indicate + * not to or the volume does not supported it. Note that this + * is only done when the unnamed stream is being extracted, as + * security descriptors are per-file and not per-stream. */ + if (inode->i_security_id >= 0 && + !(args->extract_flags & WIMLIB_EXTRACT_FLAG_NO_ACLS) + && (args->vol_flags & FILE_PERSISTENT_ACLS)) + { + ret = win32_set_security_data(inode, h, stream_path, args); + if (ret) + return ret; + } + + /* Handle reparse points. The data for them needs to be set + * using a special ioctl. Note that the reparse point may have + * been created using CreateFileW() in the case of + * non-directories or CreateDirectoryW() in the case of + * directories; but the ioctl works either way. Also, it is + * only this step that actually sets the + * FILE_ATTRIBUTE_REPARSE_POINT, as it is not valid to set it + * using SetFileAttributesW() or CreateFileW(). + * + * If the volume does not support reparse points we simply + * ignore the reparse data. (N.B. the code currently doesn't + * actually reach this case because reparse points are skipped + * entirely on such volumes.) */ + if (inode->i_attributes & FILE_ATTRIBUTE_REPARSE_POINT) { + if (args->vol_flags & FILE_SUPPORTS_REPARSE_POINTS) { + ret = win32_set_reparse_data(h, inode, + lte, stream_path, + args); + if (ret) + return ret; + } else { + DEBUG("Cannot set reparse data on \"%ls\": volume " + "does not support reparse points", stream_path); + } + } else if (lte != NULL && + !(args->vol_flags & FILE_SUPPORTS_ENCRYPTION && + inode->i_attributes & FILE_ATTRIBUTE_ENCRYPTED)) + { + /* Extract the data of the unnamed stream, unless the + * lookup table entry is NULL (indicating an empty + * stream for which no data needs to be extracted), or + * the stream is encrypted and therefore was already + * extracted as a special case. */ + ret = do_win32_extract_stream(h, lte); + if (ret) + return ret; + } + + if (dentry_has_short_name(dentry)) + short_name = dentry->short_name; + else + short_name = L""; + /* Set short name */ + if (!SetFileShortNameW(h, short_name)) { + #if 0 + DWORD err = GetLastError(); + ERROR("Could not set short name on \"%ls\"", stream_path); + win32_error(err); + #endif + } + } else { + /* Extract the data for a named data stream. */ + if (lte != NULL) { + DEBUG("Extracting named data stream \"%ls\" (len = %"PRIu64")", + stream_path, wim_resource_size(lte)); + ret = do_win32_extract_stream(h, lte); + } + } + return ret; +} + +static int +win32_decrypt_file(HANDLE open_handle, const wchar_t *path) +{ + DWORD err; + /* We cannot call DecryptFileW() while there is an open handle to the + * file. So close it first. */ + if (!CloseHandle(open_handle)) { + err = GetLastError(); + ERROR("Failed to close handle for \"%ls\"", path); + win32_error(err); + return WIMLIB_ERR_WRITE; + } + if (!DecryptFileW(path, 0 /* reserved parameter; set to 0 */)) { + err = GetLastError(); + ERROR("Failed to decrypt file \"%ls\"", path); + win32_error(err); + return WIMLIB_ERR_WRITE; + } + return 0; +} + +/* + * Create and extract a stream to a file, or create a directory, using the + * Windows API. + * + * This handles reparse points, directories, alternate data streams, encrypted + * files, compressed files, etc. + * + * @dentry: WIM dentry for the file or directory being extracted. + * + * @path: Path to extract the file to. + * + * @stream_name_utf16: + * Name of the stream, or NULL if the stream is unnamed. This will + * be called with a NULL stream_name_utf16 before any non-NULL + * stream_name_utf16's. + * + * @lte: WIM lookup table entry for the stream. May be NULL to indicate + * a stream of length 0. + * + * @args: Additional apply context, including flags indicating supported + * volume features. + * + * Returns 0 on success; nonzero on failure. + */ +static int +win32_extract_stream(const struct wim_dentry *dentry, + const wchar_t *path, + const wchar_t *stream_name_utf16, + struct wim_lookup_table_entry *lte, + struct apply_args *args) +{ + wchar_t *stream_path; + HANDLE h; + int ret; + DWORD err; + DWORD creationDisposition = CREATE_ALWAYS; + DWORD requestedAccess; + BY_HANDLE_FILE_INFORMATION file_info; + unsigned remaining_sharing_violations = 1000; + const struct wim_inode *inode = dentry->d_inode; + + if (stream_name_utf16) { + /* Named stream. Create a buffer that contains the UTF-16LE + * string [./]path:stream_name_utf16. This is needed to + * create and open the stream using CreateFileW(). I'm not + * aware of any other APIs to do this. Note: the '$DATA' suffix + * seems to be unneeded. Additional note: a "./" prefix needs + * to be added when the path is not absolute to avoid ambiguity + * with drive letters. */ + size_t stream_path_nchars; + size_t path_nchars; + size_t stream_name_nchars; + const wchar_t *prefix; + + path_nchars = wcslen(path); + stream_name_nchars = wcslen(stream_name_utf16); + stream_path_nchars = path_nchars + 1 + stream_name_nchars; + if (path[0] != cpu_to_le16(L'\0') && + path[0] != cpu_to_le16(L'/') && + path[0] != cpu_to_le16(L'\\') && + path[1] != cpu_to_le16(L':')) + { + prefix = L"./"; + stream_path_nchars += 2; + } else { + prefix = L""; + } + stream_path = alloca((stream_path_nchars + 1) * sizeof(wchar_t)); + swprintf(stream_path, L"%ls%ls:%ls", + prefix, path, stream_name_utf16); + } else { + /* Unnamed stream; its path is just the path to the file itself. + * */ + stream_path = (wchar_t*)path; + + ret = win32_begin_extract_unnamed_stream(inode, lte, path, + &creationDisposition, + args->vol_flags); + if (ret) + goto fail; + } + + DEBUG("Opening \"%ls\"", stream_path); + /* DELETE access is needed for SetFileShortNameW(), for some reason. */ + requestedAccess = GENERIC_READ | GENERIC_WRITE | DELETE | + ACCESS_SYSTEM_SECURITY; +try_open_again: + /* Open the stream to be extracted. Depending on what we have set + * creationDisposition to, we may be creating this for the first time, + * or we may be opening on existing stream we already created using + * CreateDirectoryW() or OpenEncryptedFileRawW(). */ + h = CreateFileW(stream_path, + requestedAccess, + FILE_SHARE_READ, + NULL, + creationDisposition, + win32_get_create_flags_and_attributes(inode->i_attributes), + NULL); + if (h == INVALID_HANDLE_VALUE) { + err = GetLastError(); + if (err == ERROR_ACCESS_DENIED && + path_is_root_of_drive(stream_path)) + { + ret = 0; + goto out; + } + if ((err == ERROR_PRIVILEGE_NOT_HELD || + err == ERROR_ACCESS_DENIED) && + (requestedAccess & ACCESS_SYSTEM_SECURITY)) + { + /* Try opening the file again without privilege to + * modify SACL. */ + requestedAccess &= ~ACCESS_SYSTEM_SECURITY; + goto try_open_again; + } + if (err == ERROR_SHARING_VIOLATION) { + if (remaining_sharing_violations) { + --remaining_sharing_violations; + /* This can happen when restoring encrypted directories + * for some reason. Probably a bug in EncryptFile(). */ + WARNING("Couldn't open \"%ls\" due to sharing violation; " + "re-trying after 100ms", stream_path); + Sleep(100); + goto try_open_again; + } else { + ERROR("Too many sharing violations; giving up..."); + } + } else { + if (creationDisposition == OPEN_EXISTING) + ERROR("Failed to open \"%ls\"", stream_path); + else + ERROR("Failed to create \"%ls\"", stream_path); + win32_error(err); + } + ret = WIMLIB_ERR_OPEN; + goto fail; + } + + /* Check the attributes of the file we just opened, and remove + * encryption or compression if either was set by default but is not + * supposed to be set based on the WIM inode attributes. */ + if (!GetFileInformationByHandle(h, &file_info)) { + err = GetLastError(); + ERROR("Failed to get attributes of \"%ls\"", stream_path); + win32_error(err); + ret = WIMLIB_ERR_STAT; + goto fail_close_handle; + } + + /* Remove encryption? */ + if (file_info.dwFileAttributes & FILE_ATTRIBUTE_ENCRYPTED && + !(inode->i_attributes & FILE_ATTRIBUTE_ENCRYPTED)) + { + /* File defaulted to encrypted due to being in an encrypted + * directory, but is not actually supposed to be encrypted. + * + * This is a workaround, because I'm not aware of any way to + * directly (e.g. with CreateFileW()) create an unencrypted file + * in a directory with FILE_ATTRIBUTE_ENCRYPTED set. */ + ret = win32_decrypt_file(h, stream_path); + if (ret) + goto fail; /* win32_decrypt_file() closed the handle. */ + creationDisposition = OPEN_EXISTING; + goto try_open_again; + } + + /* Remove compression? */ + if (file_info.dwFileAttributes & FILE_ATTRIBUTE_COMPRESSED && + !(inode->i_attributes & FILE_ATTRIBUTE_COMPRESSED)) + { + /* Similar to the encrypted case, above, if the file defaulted + * to compressed due to being in an compressed directory, but is + * not actually supposed to be compressed, explicitly set the + * compression format to COMPRESSION_FORMAT_NONE. */ + ret = win32_set_compression_state(h, COMPRESSION_FORMAT_NONE, + stream_path); + if (ret) + goto fail_close_handle; + } + + /* Set compression and/or sparse attributes if needed */ + ret = win32_set_special_stream_attributes(h, inode, lte, path, + args->vol_flags); + + if (ret) + goto fail_close_handle; + + /* At this point we have at least created the needed stream with the + * appropriate attributes. We have yet to set the appropriate security + * descriptor and actually extract the stream data (other than for + * extracted files, which were already extracted). + * win32_finish_extract_stream() handles these additional steps. */ + ret = win32_finish_extract_stream(h, dentry, lte, stream_path, + stream_name_utf16, args); + if (ret) + goto fail_close_handle; + + /* Done extracting the stream. Close the handle and return. */ + DEBUG("Closing \"%ls\"", stream_path); + if (!CloseHandle(h)) { + err = GetLastError(); + ERROR("Failed to close \"%ls\"", stream_path); + win32_error(err); + ret = WIMLIB_ERR_WRITE; + goto fail; + } + ret = 0; + goto out; +fail_close_handle: + CloseHandle(h); +fail: + ERROR("Error extracting \"%ls\"", stream_path); +out: + return ret; +} + +/* + * Creates a file, directory, or reparse point and extracts all streams to it + * (unnamed data stream and/or reparse point stream, plus any alternate data + * streams). Handles sparse, compressed, and/or encrypted files. + * + * @dentry: WIM dentry for this file or directory. + * @path: UTF-16LE external path to extract the inode to. + * @args: Additional extraction context. + * + * Returns 0 on success; nonzero on failure. + */ +static int +win32_extract_streams(const struct wim_dentry *dentry, + const wchar_t *path, struct apply_args *args) +{ + struct wim_lookup_table_entry *unnamed_lte; + int ret; + const struct wim_inode *inode = dentry->d_inode; + + /* First extract the unnamed stream. */ + + unnamed_lte = inode_unnamed_lte_resolved(inode); + ret = win32_extract_stream(dentry, path, NULL, unnamed_lte, args); + if (ret) + goto out; + + /* Extract any named streams, if supported by the volume. */ + + if (!(args->vol_flags & FILE_NAMED_STREAMS)) + goto out; + for (u16 i = 0; i < inode->i_num_ads; i++) { + const struct wim_ads_entry *ads_entry = &inode->i_ads_entries[i]; + + /* Skip the unnamed stream if it's in the ADS entries (we + * already extracted it...) */ + if (ads_entry->stream_name_nbytes == 0) + continue; + + /* Skip special UNIX data entries (see documentation for + * WIMLIB_ADD_FLAG_UNIX_DATA) */ + if (ads_entry->stream_name_nbytes == WIMLIB_UNIX_DATA_TAG_UTF16LE_NBYTES + && !memcmp(ads_entry->stream_name, + WIMLIB_UNIX_DATA_TAG_UTF16LE, + WIMLIB_UNIX_DATA_TAG_UTF16LE_NBYTES)) + continue; + + /* Extract the named stream */ + ret = win32_extract_stream(dentry, + path, + ads_entry->stream_name, + ads_entry->lte, + args); + if (ret) + break; + } +out: + return ret; +} + +/* If not done already, load the supported feature flags for the volume onto + * which the image is being extracted, and warn the user about any missing + * features that could be important. */ +static int +win32_check_vol_flags(const wchar_t *output_path, struct apply_args *args) +{ + if (args->have_vol_flags) + return 0; + + win32_get_vol_flags(output_path, &args->vol_flags); + args->have_vol_flags = true; + /* Warn the user about data that may not be extracted. */ + if (!(args->vol_flags & FILE_SUPPORTS_SPARSE_FILES)) + WARNING("Volume does not support sparse files!\n" + " Sparse files will be extracted as non-sparse."); + if (!(args->vol_flags & FILE_SUPPORTS_REPARSE_POINTS)) + WARNING("Volume does not support reparse points!\n" + " Reparse point data will not be extracted."); + if (!(args->vol_flags & FILE_NAMED_STREAMS)) { + WARNING("Volume does not support named data streams!\n" + " Named data streams will not be extracted."); + } + if (!(args->vol_flags & FILE_SUPPORTS_ENCRYPTION)) { + WARNING("Volume does not support encryption!\n" + " Encrypted files will be extracted as raw data."); + } + if (!(args->vol_flags & FILE_FILE_COMPRESSION)) { + WARNING("Volume does not support transparent compression!\n" + " Compressed files will be extracted as non-compressed."); + } + if (!(args->vol_flags & FILE_PERSISTENT_ACLS)) { + if (args->extract_flags & WIMLIB_EXTRACT_FLAG_STRICT_ACLS) { + ERROR("Strict ACLs requested, but the volume does not " + "support ACLs!"); + return WIMLIB_ERR_VOLUME_LACKS_FEATURES; + } else { + WARNING("Volume does not support persistent ACLS!\n" + " File permissions will not be extracted."); + } + } + return 0; +} + +/* + * Try extracting a hard link. + * + * @output_path: Path to link to be extracted. + * + * @inode: WIM inode that the link is to; inode->i_extracted_file + * the path to a name of the file that has already been + * extracted (we use this to create the hard link). + * + * @args: Additional apply context, used here to keep track of + * the number of times creating a hard link failed due to + * ERROR_INVALID_FUNCTION. This error should indicate that hard + * links are not supported by the volume, and we would like to + * warn the user a few times, but not too many times. + * + * Returns 0 if the hard link was successfully extracted. Returns + * WIMLIB_ERR_LINK (> 0) if an error occurred, other than hard links possibly + * being unsupported by the volume. Returns a negative value if creating the + * hard link failed due to ERROR_INVALID_FUNCTION. + */ +static int +win32_try_hard_link(const wchar_t *output_path, const struct wim_inode *inode, + struct apply_args *args) +{ + DWORD err; + + /* There is a volume flag for this (FILE_SUPPORTS_HARD_LINKS), + * but it's only available on Windows 7 and later. So no use + * even checking it, really. Instead, CreateHardLinkW() will + * apparently return ERROR_INVALID_FUNCTION if the volume does + * not support hard links. */ + DEBUG("Creating hard link \"%ls => %ls\"", + output_path, inode->i_extracted_file); + if (CreateHardLinkW(output_path, inode->i_extracted_file, NULL)) + return 0; + + err = GetLastError(); + if (err != ERROR_INVALID_FUNCTION) { + ERROR("Can't create hard link \"%ls => %ls\"", + output_path, inode->i_extracted_file); + win32_error(err); + return WIMLIB_ERR_LINK; + } else { + args->num_hard_links_failed++; + if (args->num_hard_links_failed <= MAX_CREATE_HARD_LINK_WARNINGS) { + WARNING("Can't create hard link \"%ls => %ls\":\n" + " Volume does not support hard links!\n" + " Falling back to extracting a copy of the file.", + output_path, inode->i_extracted_file); + } + if (args->num_hard_links_failed == MAX_CREATE_HARD_LINK_WARNINGS) { + WARNING("Suppressing further hard linking warnings..."); + } + return -1; + } +} + +/* Extract a file, directory, reparse point, or hard link to an + * already-extracted file using the Win32 API */ +int +win32_do_apply_dentry(const wchar_t *output_path, + size_t output_path_num_chars, + struct wim_dentry *dentry, + struct apply_args *args) +{ + int ret; + struct wim_inode *inode = dentry->d_inode; + + ret = win32_check_vol_flags(output_path, args); + if (ret) + return ret; + if (inode->i_nlink > 1 && inode->i_extracted_file != NULL) { + /* Linked file, with another name already extracted. Create a + * hard link. */ + ret = win32_try_hard_link(output_path, inode, args); + if (ret >= 0) + return ret; + /* Negative return value from win32_try_hard_link() indicates + * that hard links are probably not supported by the volume. + * Fall back to extracting a copy of the file. */ + } + + /* If this is a reparse point and the volume does not support reparse + * points, just skip it completely. */ + if (inode->i_attributes & FILE_ATTRIBUTE_REPARSE_POINT && + !(args->vol_flags & FILE_SUPPORTS_REPARSE_POINTS)) + { + WARNING("Skipping extraction of reparse point \"%ls\":\n" + " Not supported by destination filesystem", + output_path); + } else { + /* Create the file, directory, or reparse point, and extract the + * data streams. */ + ret = win32_extract_streams(dentry, output_path, args); + if (ret) + return ret; + } + if (inode->i_extracted_file == NULL) { + const struct wim_lookup_table_entry *lte; + + /* Tally bytes extracted, including all alternate data streams, + * unless we extracted a hard link (or, at least extracted a + * name that was supposed to be a hard link) */ + for (unsigned i = 0; i <= inode->i_num_ads; i++) { + lte = inode_stream_lte_resolved(inode, i); + if (lte) + args->progress.extract.completed_bytes += + wim_resource_size(lte); + } + if (inode->i_nlink > 1) { + /* Save extracted path for a later call to + * CreateHardLinkW() if this inode has multiple links. + * */ + inode->i_extracted_file = WSTRDUP(output_path); + if (!inode->i_extracted_file) + return WIMLIB_ERR_NOMEM; + } + } + return 0; +} + +/* Set timestamps on an extracted file using the Win32 API */ +int +win32_do_apply_dentry_timestamps(const wchar_t *path, + size_t path_num_chars, + struct wim_dentry *dentry, + struct apply_args *args) +{ + DWORD err; + HANDLE h; + const struct wim_inode *inode = dentry->d_inode; + + if (inode->i_attributes & FILE_ATTRIBUTE_REPARSE_POINT && + !(args->vol_flags & FILE_SUPPORTS_REPARSE_POINTS)) + { + /* Skip reparse points not extracted */ + return 0; + } + + /* Windows doesn't let you change the timestamps of the root directory + * (at least on FAT, which is dumb but expected since FAT doesn't store + * any metadata about the root directory...) */ + if (path_is_root_of_drive(path)) + return 0; + + DEBUG("Opening \"%ls\" to set timestamps", path); + h = win32_open_existing_file(path, FILE_WRITE_ATTRIBUTES); + if (h == INVALID_HANDLE_VALUE) { + err = GetLastError(); + goto fail; + } + + FILETIME creationTime = {.dwLowDateTime = inode->i_creation_time & 0xffffffff, + .dwHighDateTime = inode->i_creation_time >> 32}; + FILETIME lastAccessTime = {.dwLowDateTime = inode->i_last_access_time & 0xffffffff, + .dwHighDateTime = inode->i_last_access_time >> 32}; + FILETIME lastWriteTime = {.dwLowDateTime = inode->i_last_write_time & 0xffffffff, + .dwHighDateTime = inode->i_last_write_time >> 32}; + + DEBUG("Calling SetFileTime() on \"%ls\"", path); + if (!SetFileTime(h, &creationTime, &lastAccessTime, &lastWriteTime)) { + err = GetLastError(); + CloseHandle(h); + goto fail; + } + DEBUG("Closing \"%ls\"", path); + if (!CloseHandle(h)) { + err = GetLastError(); + goto fail; + } + goto out; +fail: + /* Only warn if setting timestamps failed; still return 0. */ + WARNING("Can't set timestamps on \"%ls\"", path); + win32_error(err); +out: + return 0; +} + +#endif /* __WIN32__ */ diff --git a/src/win32_capture.c b/src/win32_capture.c new file mode 100644 index 00000000..e3072494 --- /dev/null +++ b/src/win32_capture.c @@ -0,0 +1,1176 @@ +#ifdef __WIN32__ + +#include "win32_common.h" +#include "wimlib_internal.h" +#include "lookup_table.h" +#include "security.h" +#include "endianness.h" + +#define MAX_GET_SD_ACCESS_DENIED_WARNINGS 1 +#define MAX_GET_SACL_PRIV_NOTHELD_WARNINGS 1 +struct win32_capture_state { + unsigned long num_get_sd_access_denied; + unsigned long num_get_sacl_priv_notheld; +}; + + +static const wchar_t *capture_access_denied_msg = +L" If you are not running this program as the administrator, you may\n" + " need to do so, so that all data and metadata can be backed up.\n" + " Otherwise, there may be no way to access the desired data or\n" + " metadata without taking ownership of the file or directory.\n" + ; + +int +read_win32_file_prefix(const struct wim_lookup_table_entry *lte, + u64 size, + consume_data_callback_t cb, + void *ctx_or_buf, + int _ignored_flags) +{ + int ret = 0; + void *out_buf; + DWORD err; + u64 bytes_remaining; + + HANDLE hFile = win32_open_file_data_only(lte->file_on_disk); + if (hFile == INVALID_HANDLE_VALUE) { + err = GetLastError(); + ERROR("Failed to open \"%ls\"", lte->file_on_disk); + win32_error(err); + return WIMLIB_ERR_OPEN; + } + + if (cb) + out_buf = alloca(WIM_CHUNK_SIZE); + else + out_buf = ctx_or_buf; + + bytes_remaining = size; + while (bytes_remaining) { + DWORD bytesToRead, bytesRead; + + bytesToRead = min(WIM_CHUNK_SIZE, bytes_remaining); + if (!ReadFile(hFile, out_buf, bytesToRead, &bytesRead, NULL) || + bytesRead != bytesToRead) + { + err = GetLastError(); + ERROR("Failed to read data from \"%ls\"", lte->file_on_disk); + win32_error(err); + ret = WIMLIB_ERR_READ; + break; + } + bytes_remaining -= bytesRead; + if (cb) { + ret = (*cb)(out_buf, bytesRead, ctx_or_buf); + if (ret) + break; + } else { + out_buf += bytesRead; + } + } + CloseHandle(hFile); + return ret; +} + +struct win32_encrypted_read_ctx { + consume_data_callback_t read_prefix_cb; + void *read_prefix_ctx_or_buf; + int wimlib_err_code; + void *buf; + size_t buf_filled; + u64 bytes_remaining; +}; + +static DWORD WINAPI +win32_encrypted_export_cb(unsigned char *_data, void *_ctx, unsigned long len) +{ + const void *data = _data; + struct win32_encrypted_read_ctx *ctx = _ctx; + int ret; + + DEBUG("len = %lu", len); + if (ctx->read_prefix_cb) { + /* The length of the buffer passed to the ReadEncryptedFileRaw() + * export callback is undocumented, so we assume it may be of + * arbitrary size. */ + size_t bytes_to_buffer = min(ctx->bytes_remaining - ctx->buf_filled, + len); + while (bytes_to_buffer) { + size_t bytes_to_copy_to_buf = + min(bytes_to_buffer, WIM_CHUNK_SIZE - ctx->buf_filled); + + memcpy(ctx->buf + ctx->buf_filled, data, + bytes_to_copy_to_buf); + ctx->buf_filled += bytes_to_copy_to_buf; + data += bytes_to_copy_to_buf; + bytes_to_buffer -= bytes_to_copy_to_buf; + + if (ctx->buf_filled == WIM_CHUNK_SIZE || + ctx->buf_filled == ctx->bytes_remaining) + { + ret = (*ctx->read_prefix_cb)(ctx->buf, + ctx->buf_filled, + ctx->read_prefix_ctx_or_buf); + if (ret) { + ctx->wimlib_err_code = ret; + /* Shouldn't matter what error code is returned + * here, as long as it isn't ERROR_SUCCESS. */ + return ERROR_READ_FAULT; + } + ctx->bytes_remaining -= ctx->buf_filled; + ctx->buf_filled = 0; + } + } + } else { + size_t len_to_copy = min(len, ctx->bytes_remaining); + memcpy(ctx->read_prefix_ctx_or_buf, data, len_to_copy); + ctx->bytes_remaining -= len_to_copy; + ctx->read_prefix_ctx_or_buf += len_to_copy; + } + return ERROR_SUCCESS; +} + +int +read_win32_encrypted_file_prefix(const struct wim_lookup_table_entry *lte, + u64 size, + consume_data_callback_t cb, + void *ctx_or_buf, + int _ignored_flags) +{ + struct win32_encrypted_read_ctx export_ctx; + DWORD err; + void *file_ctx; + int ret; + + DEBUG("Reading %"PRIu64" bytes from encryted file \"%ls\"", + size, lte->file_on_disk); + + export_ctx.read_prefix_cb = cb; + export_ctx.read_prefix_ctx_or_buf = ctx_or_buf; + export_ctx.wimlib_err_code = 0; + if (cb) { + export_ctx.buf = MALLOC(WIM_CHUNK_SIZE); + if (!export_ctx.buf) + return WIMLIB_ERR_NOMEM; + } else { + export_ctx.buf = NULL; + } + export_ctx.buf_filled = 0; + export_ctx.bytes_remaining = size; + + err = OpenEncryptedFileRawW(lte->file_on_disk, 0, &file_ctx); + if (err != ERROR_SUCCESS) { + ERROR("Failed to open encrypted file \"%ls\" for raw read", + lte->file_on_disk); + win32_error(err); + ret = WIMLIB_ERR_OPEN; + goto out_free_buf; + } + err = ReadEncryptedFileRaw(win32_encrypted_export_cb, + &export_ctx, file_ctx); + if (err != ERROR_SUCCESS) { + ERROR("Failed to read encrypted file \"%ls\"", + lte->file_on_disk); + win32_error(err); + ret = export_ctx.wimlib_err_code; + if (ret == 0) + ret = WIMLIB_ERR_READ; + } else if (export_ctx.bytes_remaining != 0) { + ERROR("Only could read %"PRIu64" of %"PRIu64" bytes from " + "encryted file \"%ls\"", + size - export_ctx.bytes_remaining, size, + lte->file_on_disk); + ret = WIMLIB_ERR_READ; + } else { + ret = 0; + } + CloseEncryptedFileRaw(file_ctx); +out_free_buf: + FREE(export_ctx.buf); + return ret; +} + + +static u64 +FILETIME_to_u64(const FILETIME *ft) +{ + return ((u64)ft->dwHighDateTime << 32) | (u64)ft->dwLowDateTime; +} + +static int +win32_get_short_name(struct wim_dentry *dentry, const wchar_t *path) +{ + WIN32_FIND_DATAW dat; + HANDLE hFind; + int ret = 0; + + /* If we can't read the short filename for some reason, we just ignore + * the error and assume the file has no short name. I don't think this + * should be an issue, since the short names are essentially obsolete + * anyway. */ + hFind = FindFirstFileW(path, &dat); + if (hFind != INVALID_HANDLE_VALUE) { + if (dat.cAlternateFileName[0] != L'\0') { + DEBUG("\"%ls\": short name \"%ls\"", path, dat.cAlternateFileName); + size_t short_name_nbytes = wcslen(dat.cAlternateFileName) * + sizeof(wchar_t); + size_t n = short_name_nbytes + sizeof(wchar_t); + dentry->short_name = MALLOC(n); + if (dentry->short_name) { + memcpy(dentry->short_name, dat.cAlternateFileName, n); + dentry->short_name_nbytes = short_name_nbytes; + } else { + ret = WIMLIB_ERR_NOMEM; + } + } + FindClose(hFind); + } + return ret; +} + +static int +win32_get_security_descriptor(struct wim_dentry *dentry, + struct sd_set *sd_set, + const wchar_t *path, + struct win32_capture_state *state, + int add_flags) +{ + SECURITY_INFORMATION requestedInformation; + DWORD lenNeeded = 0; + BOOL status; + DWORD err; + unsigned long n; + + requestedInformation = DACL_SECURITY_INFORMATION | + SACL_SECURITY_INFORMATION | + OWNER_SECURITY_INFORMATION | + GROUP_SECURITY_INFORMATION; +again: + /* Request length of security descriptor */ + status = GetFileSecurityW(path, requestedInformation, + NULL, 0, &lenNeeded); + err = GetLastError(); + if (!status && err == ERROR_INSUFFICIENT_BUFFER) { + DWORD len = lenNeeded; + char buf[len]; + if (GetFileSecurityW(path, requestedInformation, + (PSECURITY_DESCRIPTOR)buf, len, &lenNeeded)) + { + int security_id = sd_set_add_sd(sd_set, buf, len); + if (security_id < 0) + return WIMLIB_ERR_NOMEM; + else { + dentry->d_inode->i_security_id = security_id; + return 0; + } + } else { + err = GetLastError(); + } + } + + if (add_flags & WIMLIB_ADD_FLAG_STRICT_ACLS) + goto fail; + + switch (err) { + case ERROR_PRIVILEGE_NOT_HELD: + if (requestedInformation & SACL_SECURITY_INFORMATION) { + n = state->num_get_sacl_priv_notheld++; + requestedInformation &= ~SACL_SECURITY_INFORMATION; + if (n < MAX_GET_SACL_PRIV_NOTHELD_WARNINGS) { + WARNING( +"We don't have enough privileges to read the full security\n" +" descriptor of \"%ls\"!\n" +" Re-trying with SACL omitted.\n", path); + } else if (n == MAX_GET_SACL_PRIV_NOTHELD_WARNINGS) { + WARNING( +"Suppressing further privileges not held error messages when reading\n" +" security descriptors."); + } + goto again; + } + /* Fall through */ + case ERROR_ACCESS_DENIED: + n = state->num_get_sd_access_denied++; + if (n < MAX_GET_SD_ACCESS_DENIED_WARNINGS) { + WARNING("Failed to read security descriptor of \"%ls\": " + "Access denied!\n%ls", path, capture_access_denied_msg); + } else if (n == MAX_GET_SD_ACCESS_DENIED_WARNINGS) { + WARNING("Suppressing further access denied errors messages i" + "when reading security descriptors"); + } + return 0; + default: +fail: + ERROR("Failed to read security descriptor of \"%ls\"", path); + win32_error(err); + return WIMLIB_ERR_READ; + } +} + +static int +win32_build_dentry_tree_recursive(struct wim_dentry **root_ret, + wchar_t *path, + size_t path_num_chars, + struct add_image_params *params, + struct win32_capture_state *state, + unsigned vol_flags); + +/* Reads the directory entries of directory using a Win32 API and recursively + * calls win32_build_dentry_tree() on them. */ +static int +win32_recurse_directory(struct wim_dentry *root, + wchar_t *dir_path, + size_t dir_path_num_chars, + struct add_image_params *params, + struct win32_capture_state *state, + unsigned vol_flags) +{ + WIN32_FIND_DATAW dat; + HANDLE hFind; + DWORD err; + int ret; + + DEBUG("Recurse to directory \"%ls\"", dir_path); + + /* Begin reading the directory by calling FindFirstFileW. Unlike UNIX + * opendir(), FindFirstFileW has file globbing built into it. But this + * isn't what we actually want, so just add a dummy glob to get all + * entries. */ + dir_path[dir_path_num_chars] = L'/'; + dir_path[dir_path_num_chars + 1] = L'*'; + dir_path[dir_path_num_chars + 2] = L'\0'; + hFind = FindFirstFileW(dir_path, &dat); + dir_path[dir_path_num_chars] = L'\0'; + + if (hFind == INVALID_HANDLE_VALUE) { + err = GetLastError(); + if (err == ERROR_FILE_NOT_FOUND) { + return 0; + } else { + ERROR("Failed to read directory \"%ls\"", dir_path); + win32_error(err); + return WIMLIB_ERR_READ; + } + } + ret = 0; + do { + /* Skip . and .. entries */ + if (dat.cFileName[0] == L'.' && + (dat.cFileName[1] == L'\0' || + (dat.cFileName[1] == L'.' && + dat.cFileName[2] == L'\0'))) + continue; + size_t filename_len = wcslen(dat.cFileName); + + dir_path[dir_path_num_chars] = L'/'; + wmemcpy(dir_path + dir_path_num_chars + 1, + dat.cFileName, + filename_len + 1); + + struct wim_dentry *child; + size_t path_len = dir_path_num_chars + 1 + filename_len; + ret = win32_build_dentry_tree_recursive(&child, + dir_path, + path_len, + params, + state, + vol_flags); + dir_path[dir_path_num_chars] = L'\0'; + if (ret) + goto out_find_close; + if (child) + dentry_add_child(root, child); + } while (FindNextFileW(hFind, &dat)); + err = GetLastError(); + if (err != ERROR_NO_MORE_FILES) { + ERROR("Failed to read directory \"%ls\"", dir_path); + win32_error(err); + if (ret == 0) + ret = WIMLIB_ERR_READ; + } +out_find_close: + FindClose(hFind); + return ret; +} + +int +win32_get_file_and_vol_ids(const wchar_t *path, u64 *ino_ret, u64 *dev_ret) +{ + HANDLE hFile; + DWORD err; + BY_HANDLE_FILE_INFORMATION file_info; + int ret; + + hFile = win32_open_existing_file(path, FILE_READ_ATTRIBUTES); + if (hFile == INVALID_HANDLE_VALUE) { + err = GetLastError(); + if (err != ERROR_FILE_NOT_FOUND) { + WARNING("Failed to open \"%ls\" to get file " + "and volume IDs", path); + win32_error(err); + } + return WIMLIB_ERR_OPEN; + } + + if (!GetFileInformationByHandle(hFile, &file_info)) { + err = GetLastError(); + ERROR("Failed to get file information for \"%ls\"", path); + win32_error(err); + ret = WIMLIB_ERR_STAT; + } else { + *ino_ret = ((u64)file_info.nFileIndexHigh << 32) | + (u64)file_info.nFileIndexLow; + *dev_ret = file_info.dwVolumeSerialNumber; + ret = 0; + } + CloseHandle(hFile); + return ret; +} + +/* Reparse point fixup status code */ +enum rp_status { + /* Reparse point corresponded to an absolute symbolic link or junction + * point that pointed outside the directory tree being captured, and + * therefore was excluded. */ + RP_EXCLUDED = 0x0, + + /* Reparse point was not fixed as it was either a relative symbolic + * link, a mount point, or something else we could not understand. */ + RP_NOT_FIXED = 0x1, + + /* Reparse point corresponded to an absolute symbolic link or junction + * point that pointed inside the directory tree being captured, where + * the target was specified by a "full" \??\ prefixed path, and + * therefore was fixed to be relative to the root of the directory tree + * being captured. */ + RP_FIXED_FULLPATH = 0x2, + + /* Same as RP_FIXED_FULLPATH, except the absolute link target did not + * have the \??\ prefix. It may have begun with a drive letter though. + * */ + RP_FIXED_ABSPATH = 0x4, + + /* Either RP_FIXED_FULLPATH or RP_FIXED_ABSPATH. */ + RP_FIXED = RP_FIXED_FULLPATH | RP_FIXED_ABSPATH, +}; + +/* Given the "substitute name" target of a Windows reparse point, try doing a + * fixup where we change it to be absolute relative to the root of the directory + * tree being captured. + * + * Note that this is only executed when WIMLIB_ADD_FLAG_RPFIX has been + * set. + * + * @capture_root_ino and @capture_root_dev indicate the inode number and device + * of the root of the directory tree being captured. They are meant to identify + * this directory (as an alternative to its actual path, which could potentially + * be reached via multiple destinations due to other symbolic links). This may + * not work properly on FAT, which doesn't seem to supply proper inode numbers + * or file IDs. However, FAT doesn't support reparse points so this function + * wouldn't even be called anyway. + */ +static enum rp_status +win32_capture_maybe_rpfix_target(wchar_t *target, u16 *target_nbytes_p, + u64 capture_root_ino, u64 capture_root_dev, + u32 rptag) +{ + u16 target_nchars = *target_nbytes_p / 2; + size_t stripped_chars; + wchar_t *orig_target; + int ret; + + ret = parse_substitute_name(target, *target_nbytes_p, rptag); + if (ret < 0) + return RP_NOT_FIXED; + stripped_chars = ret; + if (stripped_chars) + stripped_chars -= 2; + target[target_nchars] = L'\0'; + orig_target = target; + target = capture_fixup_absolute_symlink(target + stripped_chars, + capture_root_ino, capture_root_dev); + if (!target) + return RP_EXCLUDED; + target_nchars = wcslen(target); + wmemmove(orig_target + stripped_chars, target, target_nchars + 1); + *target_nbytes_p = (target_nchars + stripped_chars) * sizeof(wchar_t); + DEBUG("Fixed reparse point (new target: \"%ls\")", orig_target); + if (stripped_chars) + return RP_FIXED_FULLPATH; + else + return RP_FIXED_ABSPATH; +} + +/* Returns: `enum rp_status' value on success; negative WIMLIB_ERR_* value on + * failure. */ +static int +win32_capture_try_rpfix(u8 *rpbuf, u16 *rpbuflen_p, + u64 capture_root_ino, u64 capture_root_dev, + const wchar_t *path) +{ + struct reparse_data rpdata; + DWORD rpbuflen; + int ret; + enum rp_status rp_status; + + rpbuflen = *rpbuflen_p; + ret = parse_reparse_data(rpbuf, rpbuflen, &rpdata); + if (ret) + return -ret; + + rp_status = win32_capture_maybe_rpfix_target(rpdata.substitute_name, + &rpdata.substitute_name_nbytes, + capture_root_ino, + capture_root_dev, + le32_to_cpu(*(u32*)rpbuf)); + if (rp_status & RP_FIXED) { + wimlib_assert(rpdata.substitute_name_nbytes % 2 == 0); + utf16lechar substitute_name_copy[rpdata.substitute_name_nbytes / 2]; + wmemcpy(substitute_name_copy, rpdata.substitute_name, + rpdata.substitute_name_nbytes / 2); + rpdata.substitute_name = substitute_name_copy; + rpdata.print_name = substitute_name_copy; + rpdata.print_name_nbytes = rpdata.substitute_name_nbytes; + if (rp_status == RP_FIXED_FULLPATH) { + /* "full path", meaning \??\ prefixed. We should not + * include this prefix in the print name, as it is + * apparently meant for the filesystem driver only. */ + rpdata.print_name += 4; + rpdata.print_name_nbytes -= 8; + } + ret = make_reparse_buffer(&rpdata, rpbuf); + if (ret == 0) + ret = rp_status; + else + ret = -ret; + } else { + if (rp_status == RP_EXCLUDED) { + size_t print_name_nchars = rpdata.print_name_nbytes / 2; + wchar_t print_name0[print_name_nchars + 1]; + print_name0[print_name_nchars] = L'\0'; + wmemcpy(print_name0, rpdata.print_name, print_name_nchars); + WARNING("Ignoring %ls pointing out of capture directory:\n" + " \"%ls\" -> \"%ls\"\n" + " (Use --norpfix to capture all symbolic links " + "and junction points as-is)", + (rpdata.rptag == WIM_IO_REPARSE_TAG_SYMLINK) ? + L"absolute symbolic link" : L"junction point", + path, print_name0); + } + ret = rp_status; + } + return ret; +} + +/* + * Loads the reparse point data 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. + * + * @hFile: Open handle to the reparse point. + * @path: Path to the reparse point. Used for error messages only. + * @params: Additional parameters, including whether to do reparse point fixups + * or not. + * @rpbuf: Buffer of length at least REPARSE_POINT_MAX_SIZE bytes into which + * the reparse point buffer will be loaded. + * @rpbuflen_ret: On success, the length of the reparse point buffer in bytes + * is written to this location. + * + * Returns: + * On success, returns an `enum rp_status' value that indicates if and/or + * how the reparse point fixup was done. + * + * On failure, returns a negative value that is a negated WIMLIB_ERR_* + * code. + */ +static int +win32_get_reparse_data(HANDLE hFile, const wchar_t *path, + struct add_image_params *params, + u8 *rpbuf, u16 *rpbuflen_ret) +{ + DWORD bytesReturned; + u32 reparse_tag; + int ret; + u16 rpbuflen; + + DEBUG("Loading reparse data from \"%ls\"", path); + if (!DeviceIoControl(hFile, FSCTL_GET_REPARSE_POINT, + NULL, /* "Not used with this operation; set to NULL" */ + 0, /* "Not used with this operation; set to 0" */ + rpbuf, /* "A pointer to a buffer that + receives the reparse point data */ + REPARSE_POINT_MAX_SIZE, /* "The size of the output + buffer, in bytes */ + &bytesReturned, + NULL)) + { + DWORD err = GetLastError(); + ERROR("Failed to get reparse data of \"%ls\"", path); + win32_error(err); + return -WIMLIB_ERR_READ; + } + if (bytesReturned < 8 || bytesReturned > REPARSE_POINT_MAX_SIZE) { + ERROR("Reparse data on \"%ls\" is invalid", path); + return -WIMLIB_ERR_INVALID_REPARSE_DATA; + } + + rpbuflen = bytesReturned; + reparse_tag = le32_to_cpu(*(u32*)rpbuf); + if (params->add_flags & WIMLIB_ADD_FLAG_RPFIX && + (reparse_tag == WIM_IO_REPARSE_TAG_SYMLINK || + reparse_tag == WIM_IO_REPARSE_TAG_MOUNT_POINT)) + { + /* Try doing reparse point fixup */ + ret = win32_capture_try_rpfix(rpbuf, + &rpbuflen, + params->capture_root_ino, + params->capture_root_dev, + path); + } else { + ret = RP_NOT_FIXED; + } + *rpbuflen_ret = rpbuflen; + return ret; +} + +static DWORD WINAPI +win32_tally_encrypted_size_cb(unsigned char *_data, void *_ctx, + unsigned long len) +{ + *(u64*)_ctx += len; + return ERROR_SUCCESS; +} + +static int +win32_get_encrypted_file_size(const wchar_t *path, u64 *size_ret) +{ + DWORD err; + void *file_ctx; + int ret; + + *size_ret = 0; + err = OpenEncryptedFileRawW(path, 0, &file_ctx); + if (err != ERROR_SUCCESS) { + ERROR("Failed to open encrypted file \"%ls\" for raw read", path); + win32_error(err); + return WIMLIB_ERR_OPEN; + } + err = ReadEncryptedFileRaw(win32_tally_encrypted_size_cb, + size_ret, file_ctx); + if (err != ERROR_SUCCESS) { + ERROR("Failed to read raw encrypted data from \"%ls\"", path); + win32_error(err); + ret = WIMLIB_ERR_READ; + } else { + ret = 0; + } + CloseEncryptedFileRaw(file_ctx); + return ret; +} + +/* Scans an unnamed or named stream of a Win32 file (not a reparse point + * stream); calculates its SHA1 message digest and either creates a `struct + * wim_lookup_table_entry' in memory for it, or uses an existing 'struct + * wim_lookup_table_entry' for an identical stream. + * + * @path: Path to the file (UTF-16LE). + * + * @path_num_chars: Number of 2-byte characters in @path. + * + * @inode: WIM inode to save the stream into. + * + * @lookup_table: Stream lookup table for the WIM. + * + * @dat: A `WIN32_FIND_STREAM_DATA' structure that specifies the + * stream name. + * + * Returns 0 on success; nonzero on failure. + */ +static int +win32_capture_stream(const wchar_t *path, + size_t path_num_chars, + struct wim_inode *inode, + struct wim_lookup_table *lookup_table, + WIN32_FIND_STREAM_DATA *dat) +{ + struct wim_ads_entry *ads_entry; + struct wim_lookup_table_entry *lte; + int ret; + wchar_t *stream_name, *colon; + size_t stream_name_nchars; + bool is_named_stream; + wchar_t *spath; + size_t spath_nchars; + size_t spath_buf_nbytes; + const wchar_t *relpath_prefix; + const wchar_t *colonchar; + + DEBUG("Capture \"%ls\" stream \"%ls\"", path, dat->cStreamName); + + /* The stream name should be returned as :NAME:TYPE */ + stream_name = dat->cStreamName; + if (*stream_name != L':') + goto out_invalid_stream_name; + stream_name += 1; + colon = wcschr(stream_name, L':'); + if (colon == NULL) + goto out_invalid_stream_name; + + if (wcscmp(colon + 1, L"$DATA")) { + /* Not a DATA stream */ + ret = 0; + goto out; + } + + *colon = '\0'; + + stream_name_nchars = colon - stream_name; + is_named_stream = (stream_name_nchars != 0); + + if (is_named_stream) { + /* Allocate an ADS entry for the named stream. */ + ads_entry = inode_add_ads_utf16le(inode, stream_name, + stream_name_nchars * sizeof(wchar_t)); + if (!ads_entry) { + ret = WIMLIB_ERR_NOMEM; + goto out; + } + } + + /* If zero length stream, no lookup table entry needed. */ + if ((u64)dat->StreamSize.QuadPart == 0) { + ret = 0; + goto out; + } + + /* Create a UTF-16LE string @spath that gives the filename, then a + * colon, then the stream name. Or, if it's an unnamed stream, just the + * filename. It is MALLOC()'ed so that it can be saved in the + * wim_lookup_table_entry if needed. + * + * As yet another special case, relative paths need to be changed to + * begin with an explicit "./" so that, for example, a file t:ads, where + * :ads is the part we added, is not interpreted as a file on the t: + * drive. */ + spath_nchars = path_num_chars; + relpath_prefix = L""; + colonchar = L""; + if (is_named_stream) { + spath_nchars += 1 + stream_name_nchars; + colonchar = L":"; + if (path_num_chars == 1 && + path[0] != L'/' && + path[0] != L'\\') + { + spath_nchars += 2; + relpath_prefix = L"./"; + } + } + + spath_buf_nbytes = (spath_nchars + 1) * sizeof(wchar_t); + spath = MALLOC(spath_buf_nbytes); + + swprintf(spath, L"%ls%ls%ls%ls", + relpath_prefix, path, colonchar, stream_name); + + /* Make a new wim_lookup_table_entry */ + lte = new_lookup_table_entry(); + if (!lte) { + ret = WIMLIB_ERR_NOMEM; + goto out_free_spath; + } + lte->file_on_disk = spath; + spath = NULL; + if (inode->i_attributes & FILE_ATTRIBUTE_ENCRYPTED && !is_named_stream) { + u64 encrypted_size; + lte->resource_location = RESOURCE_WIN32_ENCRYPTED; + ret = win32_get_encrypted_file_size(path, &encrypted_size); + if (ret) + goto out_free_spath; + lte->resource_entry.original_size = encrypted_size; + } else { + lte->resource_location = RESOURCE_WIN32; + lte->resource_entry.original_size = (u64)dat->StreamSize.QuadPart; + } + + u32 stream_id; + if (is_named_stream) { + stream_id = ads_entry->stream_id; + ads_entry->lte = lte; + } else { + stream_id = 0; + inode->i_lte = lte; + } + lookup_table_insert_unhashed(lookup_table, lte, inode, stream_id); + ret = 0; +out_free_spath: + FREE(spath); +out: + return ret; +out_invalid_stream_name: + ERROR("Invalid stream name: \"%ls:%ls\"", path, dat->cStreamName); + ret = WIMLIB_ERR_READ; + goto out; +} + +/* Scans a Win32 file for unnamed and named data streams (not reparse point + * streams). + * + * @path: Path to the file (UTF-16LE). + * + * @path_num_chars: Number of 2-byte characters in @path. + * + * @inode: WIM inode to save the stream into. + * + * @lookup_table: Stream lookup table for the WIM. + * + * @file_size: Size of unnamed data stream. (Used only if alternate + * data streams API appears to be unavailable.) + * + * @vol_flags: Flags that specify features of the volume being + * captured. + * + * Returns 0 on success; nonzero on failure. + */ +static int +win32_capture_streams(const wchar_t *path, + size_t path_num_chars, + struct wim_inode *inode, + struct wim_lookup_table *lookup_table, + u64 file_size, + unsigned vol_flags) +{ + WIN32_FIND_STREAM_DATA dat; + int ret; + HANDLE hFind; + DWORD err; + + DEBUG("Capturing streams from \"%ls\"", path); + + if (win32func_FindFirstStreamW == NULL || + !(vol_flags & FILE_NAMED_STREAMS)) + goto unnamed_only; + + hFind = win32func_FindFirstStreamW(path, FindStreamInfoStandard, &dat, 0); + if (hFind == INVALID_HANDLE_VALUE) { + err = GetLastError(); + if (err == ERROR_CALL_NOT_IMPLEMENTED) + goto unnamed_only; + + /* Seems legal for this to return ERROR_HANDLE_EOF on reparse + * points and directories */ + if ((inode->i_attributes & + (FILE_ATTRIBUTE_REPARSE_POINT | FILE_ATTRIBUTE_DIRECTORY)) + && err == ERROR_HANDLE_EOF) + { + DEBUG("ERROR_HANDLE_EOF (ok)"); + return 0; + } else { + if (err == ERROR_ACCESS_DENIED) { + WARNING("Failed to look up data streams " + "of \"%ls\": Access denied!\n%ls", + path, capture_access_denied_msg); + return 0; + } else { + ERROR("Failed to look up data streams " + "of \"%ls\"", path); + win32_error(err); + return WIMLIB_ERR_READ; + } + } + } + do { + ret = win32_capture_stream(path, + path_num_chars, + inode, lookup_table, + &dat); + if (ret) + goto out_find_close; + } while (win32func_FindNextStreamW(hFind, &dat)); + err = GetLastError(); + if (err != ERROR_HANDLE_EOF) { + ERROR("Win32 API: Error reading data streams from \"%ls\"", path); + win32_error(err); + ret = WIMLIB_ERR_READ; + } +out_find_close: + FindClose(hFind); + return ret; +unnamed_only: + /* FindFirstStreamW() API is not available, or the volume does not + * support named streams. Only capture the unnamed data stream. */ + DEBUG("Only capturing unnamed data stream"); + if (inode->i_attributes & + (FILE_ATTRIBUTE_REPARSE_POINT | FILE_ATTRIBUTE_DIRECTORY)) + { + ret = 0; + } else { + /* Just create our own WIN32_FIND_STREAM_DATA for an unnamed + * stream to reduce the code to a call to the + * already-implemented win32_capture_stream() */ + wcscpy(dat.cStreamName, L"::$DATA"); + dat.StreamSize.QuadPart = file_size; + ret = win32_capture_stream(path, + path_num_chars, + inode, lookup_table, + &dat); + } + return ret; +} + +static int +win32_build_dentry_tree_recursive(struct wim_dentry **root_ret, + wchar_t *path, + size_t path_num_chars, + struct add_image_params *params, + struct win32_capture_state *state, + unsigned vol_flags) +{ + struct wim_dentry *root = NULL; + struct wim_inode *inode; + DWORD err; + u64 file_size; + int ret; + u8 *rpbuf; + u16 rpbuflen; + u16 not_rpfixed; + + if (exclude_path(path, path_num_chars, params->config, true)) { + if (params->add_flags & WIMLIB_ADD_FLAG_ROOT) { + ERROR("Cannot exclude the root directory from capture"); + ret = WIMLIB_ERR_INVALID_CAPTURE_CONFIG; + goto out; + } + if ((params->add_flags & WIMLIB_ADD_FLAG_EXCLUDE_VERBOSE) + && params->progress_func) + { + union wimlib_progress_info info; + info.scan.cur_path = path; + info.scan.excluded = true; + params->progress_func(WIMLIB_PROGRESS_MSG_SCAN_DENTRY, &info); + } + ret = 0; + goto out; + } + + if ((params->add_flags & WIMLIB_ADD_FLAG_VERBOSE) + && params->progress_func) + { + union wimlib_progress_info info; + info.scan.cur_path = path; + info.scan.excluded = false; + params->progress_func(WIMLIB_PROGRESS_MSG_SCAN_DENTRY, &info); + } + + HANDLE hFile = win32_open_existing_file(path, + FILE_READ_DATA | FILE_READ_ATTRIBUTES); + if (hFile == INVALID_HANDLE_VALUE) { + err = GetLastError(); + ERROR("Win32 API: Failed to open \"%ls\"", path); + win32_error(err); + ret = WIMLIB_ERR_OPEN; + goto out; + } + + BY_HANDLE_FILE_INFORMATION file_info; + if (!GetFileInformationByHandle(hFile, &file_info)) { + err = GetLastError(); + ERROR("Win32 API: Failed to get file information for \"%ls\"", + path); + win32_error(err); + ret = WIMLIB_ERR_STAT; + goto out_close_handle; + } + + if (file_info.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) { + rpbuf = alloca(REPARSE_POINT_MAX_SIZE); + ret = win32_get_reparse_data(hFile, path, params, + rpbuf, &rpbuflen); + if (ret < 0) { + /* WIMLIB_ERR_* (inverted) */ + ret = -ret; + goto out_close_handle; + } else if (ret & RP_FIXED) { + not_rpfixed = 0; + } else if (ret == RP_EXCLUDED) { + ret = 0; + goto out_close_handle; + } else { + not_rpfixed = 1; + } + } + + /* Create a WIM dentry with an associated inode, which may be shared. + * + * However, we need to explicitly check for directories and files with + * only 1 link and refuse to hard link them. This is because Windows + * has a bug where it can return duplicate File IDs for files and + * directories on the FAT filesystem. */ + ret = inode_table_new_dentry(params->inode_table, + path_basename_with_len(path, path_num_chars), + ((u64)file_info.nFileIndexHigh << 32) | + (u64)file_info.nFileIndexLow, + file_info.dwVolumeSerialNumber, + (file_info.nNumberOfLinks <= 1 || + (file_info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)), + &root); + if (ret) + goto out_close_handle; + + ret = win32_get_short_name(root, path); + if (ret) + goto out_close_handle; + + inode = root->d_inode; + + if (inode->i_nlink > 1) /* Shared inode; nothing more to do */ + goto out_close_handle; + + inode->i_attributes = file_info.dwFileAttributes; + inode->i_creation_time = FILETIME_to_u64(&file_info.ftCreationTime); + inode->i_last_write_time = FILETIME_to_u64(&file_info.ftLastWriteTime); + inode->i_last_access_time = FILETIME_to_u64(&file_info.ftLastAccessTime); + inode->i_resolved = 1; + + params->add_flags &= ~WIMLIB_ADD_FLAG_ROOT; + + if (!(params->add_flags & WIMLIB_ADD_FLAG_NO_ACLS) + && (vol_flags & FILE_PERSISTENT_ACLS)) + { + ret = win32_get_security_descriptor(root, params->sd_set, + path, state, + params->add_flags); + if (ret) + goto out_close_handle; + } + + file_size = ((u64)file_info.nFileSizeHigh << 32) | + (u64)file_info.nFileSizeLow; + + CloseHandle(hFile); + + /* Capture the unnamed data stream (only should be present for regular + * files) and any alternate data streams. */ + ret = win32_capture_streams(path, + path_num_chars, + inode, + params->lookup_table, + file_size, + vol_flags); + if (ret) + goto out; + + if (inode->i_attributes & FILE_ATTRIBUTE_REPARSE_POINT) { + /* Reparse point: set the reparse data (which we read already) + * */ + inode->i_not_rpfixed = not_rpfixed; + inode->i_reparse_tag = le32_to_cpu(*(u32*)rpbuf); + ret = inode_set_unnamed_stream(inode, rpbuf + 8, rpbuflen - 8, + params->lookup_table); + } else if (inode->i_attributes & FILE_ATTRIBUTE_DIRECTORY) { + /* Directory (not a reparse point) --- recurse to children */ + ret = win32_recurse_directory(root, + path, + path_num_chars, + params, + state, + vol_flags); + } + goto out; +out_close_handle: + CloseHandle(hFile); +out: + if (ret == 0) + *root_ret = root; + else + free_dentry_tree(root, params->lookup_table); + return ret; +} + +static void +win32_do_capture_warnings(const struct win32_capture_state *state, + int add_flags) +{ + if (state->num_get_sacl_priv_notheld == 0 && + state->num_get_sd_access_denied == 0) + return; + + WARNING(""); + WARNING("Built dentry tree successfully, but with the following problem(s):"); + if (state->num_get_sacl_priv_notheld != 0) { + WARNING("Could not capture SACL (System Access Control List)\n" + " on %lu files or directories.", + state->num_get_sacl_priv_notheld); + } + if (state->num_get_sd_access_denied != 0) { + WARNING("Could not capture security descriptor at all\n" + " on %lu files or directories.", + state->num_get_sd_access_denied); + } + WARNING( + "Try running the program as the Administrator to make sure all the\n" +" desired metadata has been captured exactly. However, if you\n" +" do not care about capturing security descriptors correctly, then\n" +" nothing more needs to be done%ls\n", + (add_flags & WIMLIB_ADD_FLAG_NO_ACLS) ? L"." : + L", although you might consider\n" +" passing the --no-acls flag to `wimlib-imagex capture' or\n" +" `wimlib-imagex append' to explicitly capture no security\n" +" descriptors.\n"); +} + +/* Win32 version of capturing a directory tree */ +int +win32_build_dentry_tree(struct wim_dentry **root_ret, + const wchar_t *root_disk_path, + struct add_image_params *params) +{ + size_t path_nchars; + wchar_t *path; + int ret; + struct win32_capture_state state; + unsigned vol_flags; + + + path_nchars = wcslen(root_disk_path); + if (path_nchars > 32767) + return WIMLIB_ERR_INVALID_PARAM; + + if (GetFileAttributesW(root_disk_path) == INVALID_FILE_ATTRIBUTES && + GetLastError() == ERROR_FILE_NOT_FOUND) + { + ERROR("Capture directory \"%ls\" does not exist!", + root_disk_path); + return WIMLIB_ERR_OPENDIR; + } + + ret = win32_get_file_and_vol_ids(root_disk_path, + ¶ms->capture_root_ino, + ¶ms->capture_root_dev); + if (ret) + return ret; + + win32_get_vol_flags(root_disk_path, &vol_flags); + + /* There is no check for overflow later when this buffer is being used! + * But the max path length on NTFS is 32767 characters, and paths need + * to be written specially to even go past 260 characters, so we should + * be okay with 32770 characters. */ + path = MALLOC(32770 * sizeof(wchar_t)); + if (!path) + return WIMLIB_ERR_NOMEM; + + wmemcpy(path, root_disk_path, path_nchars + 1); + + memset(&state, 0, sizeof(state)); + ret = win32_build_dentry_tree_recursive(root_ret, path, + path_nchars, params, + &state, vol_flags); + FREE(path); + if (ret == 0) + win32_do_capture_warnings(&state, params->add_flags); + return ret; +} + +#endif /* __WIN32__ */ diff --git a/src/win32_common.c b/src/win32_common.c new file mode 100644 index 00000000..0a7a6d55 --- /dev/null +++ b/src/win32_common.c @@ -0,0 +1,643 @@ +#ifdef __WIN32__ + +#include /* for PathMatchSpecW() */ +#include + +#include "win32_common.h" + +#ifdef ENABLE_ERROR_MESSAGES +void +win32_error(DWORD err_code) +{ + wchar_t *buffer; + DWORD nchars; + nchars = FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_ALLOCATE_BUFFER, + NULL, err_code, 0, + (wchar_t*)&buffer, 0, NULL); + if (nchars == 0) { + ERROR("Error printing error message! " + "Computer will self-destruct in 3 seconds."); + } else { + ERROR("Win32 error: %ls", buffer); + LocalFree(buffer); + } +} +#endif /* ENABLE_ERROR_MESSAGES */ + +int +win32_error_to_errno(DWORD err_code) +{ + /* This mapping is that used in Cygwin. + * Some of these choices are arbitrary. */ + switch (err_code) { + case ERROR_ACCESS_DENIED: + return EACCES; + case ERROR_ACTIVE_CONNECTIONS: + return EAGAIN; + case ERROR_ALREADY_EXISTS: + return EEXIST; + case ERROR_BAD_DEVICE: + return ENODEV; + case ERROR_BAD_EXE_FORMAT: + return ENOEXEC; + case ERROR_BAD_NETPATH: + return ENOENT; + case ERROR_BAD_NET_NAME: + return ENOENT; + case ERROR_BAD_NET_RESP: + return ENOSYS; + case ERROR_BAD_PATHNAME: + return ENOENT; + case ERROR_BAD_PIPE: + return EINVAL; + case ERROR_BAD_UNIT: + return ENODEV; + case ERROR_BAD_USERNAME: + return EINVAL; + case ERROR_BEGINNING_OF_MEDIA: + return EIO; + case ERROR_BROKEN_PIPE: + return EPIPE; + case ERROR_BUSY: + return EBUSY; + case ERROR_BUS_RESET: + return EIO; + case ERROR_CALL_NOT_IMPLEMENTED: + return ENOSYS; + case ERROR_CANNOT_MAKE: + return EPERM; + case ERROR_CHILD_NOT_COMPLETE: + return EBUSY; + case ERROR_COMMITMENT_LIMIT: + return EAGAIN; + case ERROR_CRC: + return EIO; + case ERROR_DEVICE_DOOR_OPEN: + return EIO; + case ERROR_DEVICE_IN_USE: + return EAGAIN; + case ERROR_DEVICE_REQUIRES_CLEANING: + return EIO; + case ERROR_DIRECTORY: + return ENOTDIR; + case ERROR_DIR_NOT_EMPTY: + return ENOTEMPTY; + case ERROR_DISK_CORRUPT: + return EIO; + case ERROR_DISK_FULL: + return ENOSPC; +#ifdef ENOTUNIQ + case ERROR_DUP_NAME: + return ENOTUNIQ; +#endif + case ERROR_EAS_DIDNT_FIT: + return ENOSPC; +#ifdef ENOTSUP + case ERROR_EAS_NOT_SUPPORTED: + return ENOTSUP; +#endif + case ERROR_EA_LIST_INCONSISTENT: + return EINVAL; + case ERROR_EA_TABLE_FULL: + return ENOSPC; + case ERROR_END_OF_MEDIA: + return ENOSPC; + case ERROR_EOM_OVERFLOW: + return EIO; + case ERROR_EXE_MACHINE_TYPE_MISMATCH: + return ENOEXEC; + case ERROR_EXE_MARKED_INVALID: + return ENOEXEC; + case ERROR_FILEMARK_DETECTED: + return EIO; + case ERROR_FILENAME_EXCED_RANGE: + return ENAMETOOLONG; + case ERROR_FILE_CORRUPT: + return EEXIST; + case ERROR_FILE_EXISTS: + return EEXIST; + case ERROR_FILE_INVALID: + return ENXIO; + case ERROR_FILE_NOT_FOUND: + return ENOENT; + case ERROR_HANDLE_DISK_FULL: + return ENOSPC; +#ifdef ENODATA + case ERROR_HANDLE_EOF: + return ENODATA; +#endif + case ERROR_INVALID_ADDRESS: + return EINVAL; + case ERROR_INVALID_AT_INTERRUPT_TIME: + return EINTR; + case ERROR_INVALID_BLOCK_LENGTH: + return EIO; + case ERROR_INVALID_DATA: + return EINVAL; + case ERROR_INVALID_DRIVE: + return ENODEV; + case ERROR_INVALID_EA_NAME: + return EINVAL; + case ERROR_INVALID_EXE_SIGNATURE: + return ENOEXEC; +#ifdef EBADRQC + case ERROR_INVALID_FUNCTION: + return EBADRQC; +#endif + case ERROR_INVALID_HANDLE: + return EBADF; + case ERROR_INVALID_NAME: + return ENOENT; + case ERROR_INVALID_PARAMETER: + return EINVAL; + case ERROR_INVALID_SIGNAL_NUMBER: + return EINVAL; + case ERROR_IOPL_NOT_ENABLED: + return ENOEXEC; + case ERROR_IO_DEVICE: + return EIO; + case ERROR_IO_INCOMPLETE: + return EAGAIN; + case ERROR_IO_PENDING: + return EAGAIN; + case ERROR_LOCK_VIOLATION: + return EBUSY; + case ERROR_MAX_THRDS_REACHED: + return EAGAIN; + case ERROR_META_EXPANSION_TOO_LONG: + return EINVAL; + case ERROR_MOD_NOT_FOUND: + return ENOENT; +#ifdef EMSGSIZE + case ERROR_MORE_DATA: + return EMSGSIZE; +#endif + case ERROR_NEGATIVE_SEEK: + return EINVAL; + case ERROR_NETNAME_DELETED: + return ENOENT; + case ERROR_NOACCESS: + return EFAULT; + case ERROR_NONE_MAPPED: + return EINVAL; + case ERROR_NONPAGED_SYSTEM_RESOURCES: + return EAGAIN; +#ifdef ENOLINK + case ERROR_NOT_CONNECTED: + return ENOLINK; +#endif + case ERROR_NOT_ENOUGH_MEMORY: + return ENOMEM; + case ERROR_NOT_OWNER: + return EPERM; +#ifdef ENOMEDIUM + case ERROR_NOT_READY: + return ENOMEDIUM; +#endif + case ERROR_NOT_SAME_DEVICE: + return EXDEV; + case ERROR_NOT_SUPPORTED: + return ENOSYS; + case ERROR_NO_DATA: + return EPIPE; + case ERROR_NO_DATA_DETECTED: + return EIO; +#ifdef ENOMEDIUM + case ERROR_NO_MEDIA_IN_DRIVE: + return ENOMEDIUM; +#endif +#ifdef ENMFILE + case ERROR_NO_MORE_FILES: + return ENMFILE; +#endif +#ifdef ENMFILE + case ERROR_NO_MORE_ITEMS: + return ENMFILE; +#endif + case ERROR_NO_MORE_SEARCH_HANDLES: + return ENFILE; + case ERROR_NO_PROC_SLOTS: + return EAGAIN; + case ERROR_NO_SIGNAL_SENT: + return EIO; + case ERROR_NO_SYSTEM_RESOURCES: + return EFBIG; + case ERROR_NO_TOKEN: + return EINVAL; + case ERROR_OPEN_FAILED: + return EIO; + case ERROR_OPEN_FILES: + return EAGAIN; + case ERROR_OUTOFMEMORY: + return ENOMEM; + case ERROR_PAGED_SYSTEM_RESOURCES: + return EAGAIN; + case ERROR_PAGEFILE_QUOTA: + return EAGAIN; + case ERROR_PATH_NOT_FOUND: + return ENOENT; + case ERROR_PIPE_BUSY: + return EBUSY; + case ERROR_PIPE_CONNECTED: + return EBUSY; +#ifdef ECOMM + case ERROR_PIPE_LISTENING: + return ECOMM; + case ERROR_PIPE_NOT_CONNECTED: + return ECOMM; +#endif + case ERROR_POSSIBLE_DEADLOCK: + return EDEADLOCK; + case ERROR_PRIVILEGE_NOT_HELD: + return EPERM; + case ERROR_PROCESS_ABORTED: + return EFAULT; + case ERROR_PROC_NOT_FOUND: + return ESRCH; +#ifdef ENONET + case ERROR_REM_NOT_LIST: + return ENONET; +#endif + case ERROR_SECTOR_NOT_FOUND: + return EINVAL; + case ERROR_SEEK: + return EINVAL; + case ERROR_SETMARK_DETECTED: + return EIO; + case ERROR_SHARING_BUFFER_EXCEEDED: + return ENOLCK; + case ERROR_SHARING_VIOLATION: + return EBUSY; + case ERROR_SIGNAL_PENDING: + return EBUSY; + case ERROR_SIGNAL_REFUSED: + return EIO; +#ifdef ELIBBAD + case ERROR_SXS_CANT_GEN_ACTCTX: + return ELIBBAD; +#endif + case ERROR_THREAD_1_INACTIVE: + return EINVAL; + case ERROR_TOO_MANY_LINKS: + return EMLINK; + case ERROR_TOO_MANY_OPEN_FILES: + return EMFILE; + case ERROR_WAIT_NO_CHILDREN: + return ECHILD; + case ERROR_WORKING_SET_QUOTA: + return EAGAIN; + case ERROR_WRITE_PROTECT: + return EROFS; + default: + return -1; + } +} + +void +set_errno_from_GetLastError() +{ + errno = win32_error_to_errno(GetLastError()); +} + +/* Replacement for POSIX fsync() */ +int +fsync(int fd) +{ + HANDLE h; + + h = (HANDLE)_get_osfhandle(fd); + if (h == INVALID_HANDLE_VALUE) + goto err; + if (!FlushFileBuffers(h)) + goto err_set_errno; + return 0; +err_set_errno: + set_errno_from_GetLastError(); +err: + return -1; +} + +/* Use the Win32 API to get the number of processors */ +unsigned +win32_get_number_of_processors() +{ + SYSTEM_INFO sysinfo; + GetSystemInfo(&sysinfo); + return sysinfo.dwNumberOfProcessors; +} + +/* Replacement for POSIX-2008 realpath(). Warning: partial functionality only + * (resolved_path must be NULL). Also I highly doubt that GetFullPathName + * really does the right thing under all circumstances. */ +wchar_t * +realpath(const wchar_t *path, wchar_t *resolved_path) +{ + DWORD ret; + DWORD err; + wimlib_assert(resolved_path == NULL); + + ret = GetFullPathNameW(path, 0, NULL, NULL); + if (!ret) { + err = GetLastError(); + goto fail_win32; + } + + resolved_path = TMALLOC(ret); + if (!resolved_path) + goto out; + ret = GetFullPathNameW(path, ret, resolved_path, NULL); + if (!ret) { + err = GetLastError(); + free(resolved_path); + resolved_path = NULL; + goto fail_win32; + } + goto out; +fail_win32: + errno = win32_error_to_errno(err); +out: + return resolved_path; +} + +/* rename() on Windows fails if the destination file exists. And we need to + * make it work on wide characters. Fix it. */ +int +win32_rename_replacement(const wchar_t *oldpath, const wchar_t *newpath) +{ + if (MoveFileExW(oldpath, newpath, MOVEFILE_REPLACE_EXISTING)) { + return 0; + } else { + set_errno_from_GetLastError(); + return -1; + } +} + +/* Replacement for POSIX fnmatch() (partial functionality only) */ +int +fnmatch(const wchar_t *pattern, const wchar_t *string, int flags) +{ + if (PathMatchSpecW(string, pattern)) + return 0; + else + return FNM_NOMATCH; +} + +/* truncate() replacement */ +int +win32_truncate_replacement(const wchar_t *path, off_t size) +{ + DWORD err = NO_ERROR; + LARGE_INTEGER liOffset; + + HANDLE h = win32_open_existing_file(path, GENERIC_WRITE); + if (h == INVALID_HANDLE_VALUE) + goto fail; + + liOffset.QuadPart = size; + if (!SetFilePointerEx(h, liOffset, NULL, FILE_BEGIN)) + goto fail_close_handle; + + if (!SetEndOfFile(h)) + goto fail_close_handle; + CloseHandle(h); + return 0; + +fail_close_handle: + err = GetLastError(); + CloseHandle(h); +fail: + if (err == NO_ERROR) + err = GetLastError(); + errno = win32_error_to_errno(err); + return -1; +} + + +/* This really could be replaced with _wcserror_s, but this doesn't seem to + * actually be available in MSVCRT.DLL on Windows XP (perhaps it's statically + * linked in by Visual Studio...?). */ +extern int +win32_strerror_r_replacement(int errnum, wchar_t *buf, size_t buflen) +{ + static pthread_mutex_t strerror_lock = PTHREAD_MUTEX_INITIALIZER; + + pthread_mutex_lock(&strerror_lock); + mbstowcs(buf, strerror(errnum), buflen); + buf[buflen - 1] = '\0'; + pthread_mutex_unlock(&strerror_lock); + return 0; +} + +static int +do_pread_or_pwrite(int fd, void *buf, size_t count, off_t offset, + bool is_pwrite) +{ + HANDLE h; + LARGE_INTEGER orig_offset; + DWORD bytes_read_or_written; + LARGE_INTEGER relative_offset; + OVERLAPPED overlapped; + BOOL bret; + + wimlib_assert(count <= 0xffffffff); + + h = (HANDLE)_get_osfhandle(fd); + if (h == INVALID_HANDLE_VALUE) + goto err; + + /* Get original position */ + relative_offset.QuadPart = 0; + if (!SetFilePointerEx(h, relative_offset, &orig_offset, FILE_CURRENT)) + goto err_set_errno; + + memset(&overlapped, 0, sizeof(overlapped)); + overlapped.Offset = offset; + overlapped.OffsetHigh = offset >> 32; + + /* Do the read or write at the specified offset */ + if (is_pwrite) + bret = WriteFile(h, buf, count, &bytes_read_or_written, &overlapped); + else + bret = ReadFile(h, buf, count, &bytes_read_or_written, &overlapped); + if (!bret) + goto err_set_errno; + + /* Restore the original position */ + if (!SetFilePointerEx(h, orig_offset, NULL, FILE_BEGIN)) + goto err_set_errno; + + return bytes_read_or_written; +err_set_errno: + set_errno_from_GetLastError(); +err: + return -1; +} + +/* Dumb Windows implementation of pread(). It temporarily changes the file + * offset, so it is not safe to use with readers/writers on the same file + * descriptor. */ +extern ssize_t +win32_pread(int fd, void *buf, size_t count, off_t offset) +{ + return do_pread_or_pwrite(fd, buf, count, offset, false); +} + +/* Dumb Windows implementation of pwrite(). It temporarily changes the file + * offset, so it is not safe to use with readers/writers on the same file + * descriptor. */ +extern ssize_t +win32_pwrite(int fd, const void *buf, size_t count, off_t offset) +{ + return do_pread_or_pwrite(fd, (void*)buf, count, offset, true); +} + +/* Dumb Windows implementation of writev(). It writes the vectors one at a + * time. */ +extern ssize_t +win32_writev(int fd, const struct iovec *iov, int iovcnt) +{ + ssize_t total_bytes_written = 0; + + if (iovcnt <= 0) { + errno = EINVAL; + return -1; + } + for (int i = 0; i < iovcnt; i++) { + ssize_t bytes_written; + + bytes_written = write(fd, iov[i].iov_base, iov[i].iov_len); + if (bytes_written >= 0) + total_bytes_written += bytes_written; + if (bytes_written != iov[i].iov_len) { + if (total_bytes_written == 0) + total_bytes_written = -1; + break; + } + } + return total_bytes_written; +} + +/* Given a path, which may not yet exist, get a set of flags that describe the + * features of the volume the path is on. */ +int +win32_get_vol_flags(const wchar_t *path, unsigned *vol_flags_ret) +{ + wchar_t *volume; + BOOL bret; + DWORD vol_flags; + + if (path[0] != L'\0' && path[0] != L'\\' && + path[0] != L'/' && path[1] == L':') + { + /* Path starts with a drive letter; use it. */ + volume = alloca(4 * sizeof(wchar_t)); + volume[0] = path[0]; + volume[1] = path[1]; + volume[2] = L'\\'; + volume[3] = L'\0'; + } else { + /* Path does not start with a drive letter; use the volume of + * the current working directory. */ + volume = NULL; + } + bret = GetVolumeInformationW(volume, /* lpRootPathName */ + NULL, /* lpVolumeNameBuffer */ + 0, /* nVolumeNameSize */ + NULL, /* lpVolumeSerialNumber */ + NULL, /* lpMaximumComponentLength */ + &vol_flags, /* lpFileSystemFlags */ + NULL, /* lpFileSystemNameBuffer */ + 0); /* nFileSystemNameSize */ + if (!bret) { + DWORD err = GetLastError(); + WARNING("Failed to get volume information for path \"%ls\"", path); + win32_error(err); + vol_flags = 0xffffffff; + } + + DEBUG("using vol_flags = %x", vol_flags); + *vol_flags_ret = vol_flags; + return 0; +} + +HANDLE +win32_open_existing_file(const wchar_t *path, DWORD dwDesiredAccess) +{ + return CreateFileW(path, + dwDesiredAccess, + FILE_SHARE_READ, + NULL, /* lpSecurityAttributes */ + OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS | + FILE_FLAG_OPEN_REPARSE_POINT, + NULL /* hTemplateFile */); +} + +HANDLE +win32_open_file_data_only(const wchar_t *path) +{ + return win32_open_existing_file(path, FILE_READ_DATA); +} + +/* Pointers to functions that are not available on all targetted versions of + * Windows (XP and later). NOTE: The WINAPI annotations seem to be important; I + * assume it specifies a certain calling convention. */ + +/* Vista and later */ +HANDLE (WINAPI *win32func_FindFirstStreamW)(LPCWSTR lpFileName, + STREAM_INFO_LEVELS InfoLevel, + LPVOID lpFindStreamData, + DWORD dwFlags) = NULL; + +/* Vista and later */ +BOOL (WINAPI *win32func_FindNextStreamW)(HANDLE hFindStream, + LPVOID lpFindStreamData) = NULL; + +static HMODULE hKernel32 = NULL; + +/* Try to dynamically load some functions */ +void +win32_global_init() +{ + DWORD err; + + if (hKernel32 == NULL) { + DEBUG("Loading Kernel32.dll"); + hKernel32 = LoadLibraryW(L"Kernel32.dll"); + if (hKernel32 == NULL) { + err = GetLastError(); + WARNING("Can't load Kernel32.dll"); + win32_error(err); + return; + } + } + + DEBUG("Looking for FindFirstStreamW"); + win32func_FindFirstStreamW = (void*)GetProcAddress(hKernel32, "FindFirstStreamW"); + if (!win32func_FindFirstStreamW) { + WARNING("Could not find function FindFirstStreamW() in Kernel32.dll!"); + WARNING("Capturing alternate data streams will not be supported."); + return; + } + + DEBUG("Looking for FindNextStreamW"); + win32func_FindNextStreamW = (void*)GetProcAddress(hKernel32, "FindNextStreamW"); + if (!win32func_FindNextStreamW) { + WARNING("Could not find function FindNextStreamW() in Kernel32.dll!"); + WARNING("Capturing alternate data streams will not be supported."); + win32func_FindFirstStreamW = NULL; + } +} + +void +win32_global_cleanup() +{ + if (hKernel32 != NULL) { + DEBUG("Closing Kernel32.dll"); + FreeLibrary(hKernel32); + hKernel32 = NULL; + } +} + +#endif /* __WIN32__ */ diff --git a/src/win32_common.h b/src/win32_common.h new file mode 100644 index 00000000..ffbc59a5 --- /dev/null +++ b/src/win32_common.h @@ -0,0 +1,47 @@ +#ifndef _WIMLIB_WIN32_COMMON_H +#define _WIMLIB_WIN32_COMMON_H + +#include +#ifdef ERROR +# undef ERROR +#endif + +#include "util.h" +#include "win32.h" + + +#ifdef ENABLE_ERROR_MESSAGES +extern void +win32_error(DWORD err_code); +#else +static inline void +win32_error(DWORD err_code) +{ +} +#endif + +extern void +set_errno_from_GetLastError(); + +extern int +win32_error_to_errno(DWORD err_code); + +extern int +win32_get_vol_flags(const wchar_t *path, unsigned *vol_flags_ret); + +extern HANDLE +win32_open_existing_file(const wchar_t *path, DWORD dwDesiredAccess); + +extern HANDLE +win32_open_file_data_only(const wchar_t *path); + +extern HANDLE (WINAPI *win32func_FindFirstStreamW)(LPCWSTR lpFileName, + STREAM_INFO_LEVELS InfoLevel, + LPVOID lpFindStreamData, + DWORD dwFlags); + +/* Vista and later */ +extern BOOL (WINAPI *win32func_FindNextStreamW)(HANDLE hFindStream, + LPVOID lpFindStreamData); + +#endif /* _WIMLIB_WIN32_COMMON_H */ -- 2.43.0