From: Eric Biggers Date: Sun, 27 Dec 2015 15:53:27 +0000 (-0600) Subject: Add experimental support for Windows VSS X-Git-Tag: v1.9.0~37 X-Git-Url: https://wimlib.net/git/?p=wimlib;a=commitdiff_plain;h=8b676e7d340fb8197824745eb387e1d3154e6f60;hp=a4123fea556d6c362318212127e25846981ea190 Add experimental support for Windows VSS --- diff --git a/Makefile.am b/Makefile.am index 8ee8e8bc..5f0cb6a8 100644 --- a/Makefile.am +++ b/Makefile.am @@ -159,14 +159,16 @@ libwim_la_SOURCES += src/ntfs-3g_apply.c \ endif if WINDOWS_NATIVE_BUILD -libwim_la_SOURCES += src/win32_common.c \ +libwim_la_SOURCES += src/wimboot.c \ + src/win32_common.c \ src/win32_apply.c \ src/win32_capture.c \ src/win32_replacements.c \ - src/wimboot.c \ - include/wimlib/win32_common.h \ - include/wimlib/win32.h \ + src/win32_vss.c \ include/wimlib/wimboot.h \ + include/wimlib/win32.h \ + include/wimlib/win32_common.h \ + include/wimlib/win32_vss.h \ include/wimlib/wof.h else libwim_la_SOURCES += src/unix_apply.c \ diff --git a/NEWS b/NEWS index c07c11d2..0dce6565 100644 --- a/NEWS +++ b/NEWS @@ -3,6 +3,10 @@ Version 1.8.4-BETA: descriptor of the target directory of an extraction even when the '--no-acls' option was specified. + Added experimental support for Windows VSS (Volume Shadow Copy Service). + The new '--snapshot' argument to 'wimcapture' makes wimlib automatically + create and use a temporary VSS snapshot when capturing a WIM image. + Added a new '--image-property' option to 'wimcapture', 'wimappend', and 'wiminfo'. This option lets you assign values to elements in a WIM file's XML document by name. diff --git a/doc/man1/wimlib-imagex-capture.1 b/doc/man1/wimlib-imagex-capture.1 index a48db594..05197006 100644 --- a/doc/man1/wimlib-imagex-capture.1 +++ b/doc/man1/wimlib-imagex-capture.1 @@ -576,6 +576,16 @@ however, this may still be overridden through the \fB--config\fR parameter. .TP \fB--unsafe-compact\fR See the documentation for this option in \fBwimlib-imagex-optimize\fR (1). +.TP +\fB--snapshot\fR +EXPERIMENTAL: create a temporary filesystem snapshot of the source directory and +capture the files from it. Currently, this option is only supported on Windows, +where it uses the Volume Shadow Copy Service (VSS). Using this option, you can +create a consistent backup of the system volume of a running Windows system +without running into problems with locked files. For the VSS snapshot to be +successfully created, \fBwimlib-imagex\fR must be run as an Administrator, and +it cannot be run in WoW64 mode (i.e. if Windows is 64-bit, then +\fBwimlib-imagex\fR must be 64-bit as well). .SH NOTES \fBwimlib-imagex append\fR does not support appending an image to a split WIM. .PP diff --git a/include/wimlib.h b/include/wimlib.h index 6a6b0e8a..4390e208 100644 --- a/include/wimlib.h +++ b/include/wimlib.h @@ -1747,6 +1747,18 @@ typedef int (*wimlib_iterate_lookup_table_callback_t)(const struct wimlib_resour */ #define WIMLIB_ADD_FLAG_TEST_FILE_EXCLUSION 0x00004000 +/** + * EXPERIMENTAL, since wimlib v1.8.4: create a temporary filesystem snapshot of + * the source directory and add the files from it. Currently, this option is + * only supported on Windows, where it uses the Volume Shadow Copy Service + * (VSS). Using this option, you can create a consistent backup of the system + * volume of a running Windows system without running into problems with locked + * files. For the VSS snapshot to be successfully created, your application + * must be run as an Administrator, and it cannot be run in WoW64 mode (i.e. if + * Windows is 64-bit, then your application must be 64-bit as well). + */ +#define WIMLIB_ADD_FLAG_SNAPSHOT 0x00008000 + /* Note: the WIMLIB_ADD_IMAGE_FLAG names are retained for source compatibility. * Use the WIMLIB_ADD_FLAG names in new code. */ #define WIMLIB_ADD_IMAGE_FLAG_NTFS WIMLIB_ADD_FLAG_NTFS @@ -2505,6 +2517,7 @@ enum wimlib_error_code { WIMLIB_ERR_IMAGE_HAS_MULTIPLE_REFERENCES = 86, WIMLIB_ERR_DUPLICATE_EXPORTED_IMAGE = 87, WIMLIB_ERR_CONCURRENT_MODIFICATION_DETECTED = 88, + WIMLIB_ERR_SNAPSHOT_FAILURE = 89, }; diff --git a/include/wimlib/blob_table.h b/include/wimlib/blob_table.h index 09996600..4e70e574 100644 --- a/include/wimlib/blob_table.h +++ b/include/wimlib/blob_table.h @@ -41,16 +41,10 @@ enum blob_location { #endif #ifdef __WIN32__ - /* Windows only: the blob's data is available as the contents of the - * data stream named by @file_on_disk. @file_on_disk is an NT namespace - * path that may be longer than the Win32-level MAX_PATH. Furthermore, - * the stream may require "backup semantics" to access. */ - BLOB_IN_WINNT_FILE_ON_DISK, - - /* Windows only: the blob's data is available as the raw encrypted data - * of the external file named by @file_on_disk. @file_on_disk is a - * Win32 namespace path. */ - BLOB_WIN32_ENCRYPTED, + /* Windows only: the blob's data is available in the file (or named data + * stream) specified by @windows_file. The data might be only properly + * accessible through the Windows API. */ + BLOB_IN_WINDOWS_FILE, #endif }; @@ -175,14 +169,13 @@ struct blob_descriptor { union { /* BLOB_IN_FILE_ON_DISK - * BLOB_IN_WINNT_FILE_ON_DISK - * BLOB_WIN32_ENCRYPTED */ + * BLOB_IN_WINDOWS_FILE */ struct { - tchar *file_on_disk; + union { + tchar *file_on_disk; + struct windows_file *windows_file; + }; struct wim_inode *file_inode; - #ifdef __WIN32__ - u64 sort_key; - #endif }; /* BLOB_IN_ATTACHED_BUFFER */ @@ -392,12 +385,26 @@ blob_is_in_file(const struct blob_descriptor *blob) { return blob->blob_location == BLOB_IN_FILE_ON_DISK #ifdef __WIN32__ - || blob->blob_location == BLOB_IN_WINNT_FILE_ON_DISK - || blob->blob_location == BLOB_WIN32_ENCRYPTED + || blob->blob_location == BLOB_IN_WINDOWS_FILE #endif ; } +#ifdef __WIN32__ +extern const wchar_t * +get_windows_file_path(const struct windows_file *file); +#endif + +static inline const tchar * +blob_file_path(const struct blob_descriptor *blob) +{ +#ifdef __WIN32__ + if (blob->blob_location == BLOB_IN_WINDOWS_FILE) + return get_windows_file_path(blob->windows_file); +#endif + return blob->file_on_disk; +} + extern struct blob_descriptor * new_blob_from_data_buffer(const void *buffer, size_t size, struct blob_table *blob_table); diff --git a/include/wimlib/win32.h b/include/wimlib/win32.h index 96d2ebd2..46d48a80 100644 --- a/include/wimlib/win32.h +++ b/include/wimlib/win32.h @@ -1,3 +1,7 @@ +/* + * win32.h - Windows-specific declarations needed by non-Windows-specific files. + */ + #ifndef _WIMLIB_WIN32_H #define _WIMLIB_WIN32_H @@ -7,15 +11,21 @@ struct blob_descriptor; struct read_blob_callbacks; +struct windows_file; + +extern struct windows_file * +clone_windows_file(const struct windows_file *file); + +extern void +free_windows_file(struct windows_file *file); extern int -read_winnt_stream_prefix(const struct blob_descriptor *blob, u64 size, - const struct read_blob_callbacks *cbs); +cmp_windows_files(const struct windows_file *file1, + const struct windows_file *file2); extern int -read_win32_encrypted_file_prefix(const struct blob_descriptor *blob, - u64 size, - const struct read_blob_callbacks *cbs); +read_windows_file_prefix(const struct blob_descriptor *blob, u64 size, + const struct read_blob_callbacks *cbs); extern int win32_global_init(int init_flags); diff --git a/include/wimlib/win32_common.h b/include/wimlib/win32_common.h index 0e2db438..d3649ace 100644 --- a/include/wimlib/win32_common.h +++ b/include/wimlib/win32_common.h @@ -1,3 +1,8 @@ +/* + * win32_common.h - common header for Windows-specific files. This always + * should be included first. + */ + #ifndef _WIMLIB_WIN32_COMMON_H #define _WIMLIB_WIN32_COMMON_H diff --git a/include/wimlib/win32_vss.h b/include/wimlib/win32_vss.h new file mode 100644 index 00000000..6e9c1977 --- /dev/null +++ b/include/wimlib/win32_vss.h @@ -0,0 +1,45 @@ +/* + * win32_vss.h - Declarations for managing VSS snapshots. This header should + * only be included by Windows-specific files. + */ + +#ifndef _WIMLIB_WIN32_VSS_H +#define _WIMLIB_WIN32_VSS_H + +#include "wimlib/win32_common.h" + +/* A reference counter for a VSS snapshot. This is embedded in another data + * structure only visible to win32_vss.c. */ +struct vss_snapshot { + size_t refcnt; +}; + +extern void +vss_delete_snapshot(struct vss_snapshot *snapshot); + +/* Acquire a reference to the specified VSS snapshot. */ +static inline struct vss_snapshot * +vss_get_snapshot(struct vss_snapshot *snapshot) +{ + if (snapshot) + snapshot->refcnt++; + return snapshot; +} + +/* Release a reference to the specified VSS snapshot. When the last reference + * is released, the snapshot is deleted. */ +static inline void +vss_put_snapshot(struct vss_snapshot *snapshot) +{ + if (snapshot && --snapshot->refcnt == 0) + vss_delete_snapshot(snapshot); +} + +extern int +vss_create_snapshot(const wchar_t *source, UNICODE_STRING *vss_path_ret, + struct vss_snapshot **snapshot_ret); + +extern void +vss_global_cleanup(void); + +#endif /* _WIMLIB_WIN32_VSS_H */ diff --git a/programs/imagex.c b/programs/imagex.c index 656bb36a..632adfbb 100644 --- a/programs/imagex.c +++ b/programs/imagex.c @@ -184,6 +184,7 @@ enum { IMAGEX_REF_OPTION, IMAGEX_RESUME_OPTION, IMAGEX_RPFIX_OPTION, + IMAGEX_SNAPSHOT_OPTION, IMAGEX_SOFT_OPTION, IMAGEX_SOLID_CHUNK_SIZE_OPTION, IMAGEX_SOLID_COMPRESS_OPTION, @@ -258,6 +259,7 @@ static const struct option capture_or_append_options[] = { {T("delta-from"), required_argument, NULL, IMAGEX_DELTA_FROM_OPTION}, {T("wimboot"), no_argument, NULL, IMAGEX_WIMBOOT_OPTION}, {T("unsafe-compact"), no_argument, NULL, IMAGEX_UNSAFE_COMPACT_OPTION}, + {T("snapshot"), no_argument, NULL, IMAGEX_SNAPSHOT_OPTION}, {NULL, 0, NULL, 0}, }; @@ -2022,6 +2024,9 @@ imagex_capture_or_append(int argc, tchar **argv, int cmd) } write_flags |= WIMLIB_WRITE_FLAG_UNSAFE_COMPACT; break; + case IMAGEX_SNAPSHOT_OPTION: + add_flags |= WIMLIB_ADD_FLAG_SNAPSHOT; + break; default: goto out_usage; } diff --git a/src/blob_table.c b/src/blob_table.c index e1fb6ada..5d464a97 100644 --- a/src/blob_table.c +++ b/src/blob_table.c @@ -44,6 +44,7 @@ #include "wimlib/resource.h" #include "wimlib/unaligned.h" #include "wimlib/util.h" +#include "wimlib/win32.h" #include "wimlib/write.h" /* A hash table mapping SHA-1 message digests to blob descriptors */ @@ -129,10 +130,6 @@ clone_blob_descriptor(const struct blob_descriptor *old) break; case BLOB_IN_FILE_ON_DISK: -#ifdef __WIN32__ - case BLOB_IN_WINNT_FILE_ON_DISK: - case BLOB_WIN32_ENCRYPTED: -#endif #ifdef WITH_FUSE case BLOB_IN_STAGING_FILE: STATIC_ASSERT((void*)&old->file_on_disk == @@ -142,6 +139,11 @@ clone_blob_descriptor(const struct blob_descriptor *old) if (new->file_on_disk == NULL) goto out_free; break; +#ifdef __WIN32__ + case BLOB_IN_WINDOWS_FILE: + new->windows_file = clone_windows_file(old->windows_file); + break; +#endif case BLOB_IN_ATTACHED_BUFFER: new->attached_buffer = memdup(old->attached_buffer, old->size); if (new->attached_buffer == NULL) @@ -179,10 +181,6 @@ blob_release_location(struct blob_descriptor *blob) break; } case BLOB_IN_FILE_ON_DISK: -#ifdef __WIN32__ - case BLOB_IN_WINNT_FILE_ON_DISK: - case BLOB_WIN32_ENCRYPTED: -#endif #ifdef WITH_FUSE case BLOB_IN_STAGING_FILE: STATIC_ASSERT((void*)&blob->file_on_disk == @@ -193,10 +191,14 @@ blob_release_location(struct blob_descriptor *blob) (void*)&blob->attached_buffer); FREE(blob->file_on_disk); break; +#ifdef __WIN32__ + case BLOB_IN_WINDOWS_FILE: + free_windows_file(blob->windows_file); + break; +#endif #ifdef WITH_NTFS_3G case BLOB_IN_NTFS_VOLUME: - if (blob->ntfs_loc) - free_ntfs_location(blob->ntfs_loc); + free_ntfs_location(blob->ntfs_loc); break; #endif } @@ -463,18 +465,14 @@ cmp_blobs_by_sequential_order(const void *p1, const void *p2) case BLOB_IN_FILE_ON_DISK: #ifdef WITH_FUSE case BLOB_IN_STAGING_FILE: -#endif -#ifdef __WIN32__ - case BLOB_IN_WINNT_FILE_ON_DISK: - case BLOB_WIN32_ENCRYPTED: - /* Windows: compare by starting LCN (logical cluster number) */ - v = cmp_u64(blob1->sort_key, blob2->sort_key); - if (v) - return v; #endif /* Compare files by path: just a heuristic that will place files * in the same directory next to each other. */ return tstrcmp(blob1->file_on_disk, blob2->file_on_disk); +#ifdef __WIN32__ + case BLOB_IN_WINDOWS_FILE: + return cmp_windows_files(blob1->windows_file, blob2->windows_file); +#endif #ifdef WITH_NTFS_3G case BLOB_IN_NTFS_VOLUME: return cmp_ntfs_locations(blob1->ntfs_loc, blob2->ntfs_loc); diff --git a/src/error.c b/src/error.c index 5afaec1c..e0816b52 100644 --- a/src/error.c +++ b/src/error.c @@ -344,6 +344,8 @@ static const tchar * const error_strings[] = { = T("The destination WIM already contains one of the source images"), [WIMLIB_ERR_CONCURRENT_MODIFICATION_DETECTED] = T("A file being added to a WIM image was concurrently modified"), + [WIMLIB_ERR_SNAPSHOT_FAILURE] + = T("Unable to create a filesystem snapshot"), }; WIMLIBAPI const tchar * diff --git a/src/resource.c b/src/resource.c index 04e924bb..c3e6927c 100644 --- a/src/resource.c +++ b/src/resource.c @@ -739,8 +739,7 @@ read_blob_prefix(const struct blob_descriptor *blob, u64 size, [BLOB_IN_NTFS_VOLUME] = read_ntfs_attribute_prefix, #endif #ifdef __WIN32__ - [BLOB_IN_WINNT_FILE_ON_DISK] = read_winnt_stream_prefix, - [BLOB_WIN32_ENCRYPTED] = read_win32_encrypted_file_prefix, + [BLOB_IN_WINDOWS_FILE] = read_windows_file_prefix, #endif }; wimlib_assert(blob->blob_location < ARRAY_LEN(handlers) @@ -959,7 +958,7 @@ report_sha1_mismatch_error(const struct blob_descriptor *blob, " Path: \"%"TS"\"\n" " Expected SHA-1: %"TS"\n" " Actual SHA-1: %"TS"\n", - blob->file_on_disk, expected_hashstr, actual_hashstr); + blob_file_path(blob), expected_hashstr, actual_hashstr); return WIMLIB_ERR_CONCURRENT_MODIFICATION_DETECTED; } else if (blob->blob_location == BLOB_IN_WIM) { const struct wim_resource_descriptor *rdesc = blob->rdesc; diff --git a/src/solid.c b/src/solid.c index 59971d46..2aecf5c9 100644 --- a/src/solid.c +++ b/src/solid.c @@ -205,7 +205,7 @@ sort_blob_list_for_solid_compression(struct list_head *blob_list) break; case BLOB_IN_FILE_ON_DISK: #ifdef __WIN32__ - case BLOB_IN_WINNT_FILE_ON_DISK: + case BLOB_IN_WINDOWS_FILE: #endif blob_set_solid_sort_name_from_inode(blob, blob->file_inode); break; diff --git a/src/update_image.c b/src/update_image.c index 15458a12..c76ae971 100644 --- a/src/update_image.c +++ b/src/update_image.c @@ -1197,7 +1197,8 @@ check_add_command(struct wimlib_update_command *cmd, WIMLIB_ADD_FLAG_WINCONFIG | WIMLIB_ADD_FLAG_WIMBOOT | WIMLIB_ADD_FLAG_NO_REPLACE | - WIMLIB_ADD_FLAG_TEST_FILE_EXCLUSION)) + WIMLIB_ADD_FLAG_TEST_FILE_EXCLUSION | + WIMLIB_ADD_FLAG_SNAPSHOT)) return WIMLIB_ERR_INVALID_PARAM; bool is_entire_image = WIMLIB_IS_WIM_ROOT_PATH(cmd->add.wim_target_path); @@ -1211,7 +1212,7 @@ check_add_command(struct wimlib_update_command *cmd, #endif #ifdef __WIN32__ - /* Check for flags not supported on Windows */ + /* Check for flags not supported on Windows. */ if (add_flags & WIMLIB_ADD_FLAG_UNIX_DATA) { ERROR("Capturing UNIX-specific data is not supported on Windows"); return WIMLIB_ERR_UNSUPPORTED; @@ -1220,6 +1221,15 @@ check_add_command(struct wimlib_update_command *cmd, ERROR("Dereferencing symbolic links is not supported on Windows"); return WIMLIB_ERR_UNSUPPORTED; } +#else + /* Check for flags only supported on Windows. */ + + /* Currently, SNAPSHOT means Windows VSS. In the future, it perhaps + * could be implemented for other types of snapshots, such as btrfs. */ + if (add_flags & WIMLIB_ADD_FLAG_SNAPSHOT) { + ERROR("Snapshot mode is only supported on Windows (VSS)"); + return WIMLIB_ERR_UNSUPPORTED; + } #endif /* VERBOSE implies EXCLUDE_VERBOSE */ diff --git a/src/win32_capture.c b/src/win32_capture.c index 8dbdd36f..70d7fd7e 100644 --- a/src/win32_capture.c +++ b/src/win32_capture.c @@ -38,6 +38,7 @@ #include "wimlib/error.h" #include "wimlib/paths.h" #include "wimlib/reparse.h" +#include "wimlib/win32_vss.h" #include "wimlib/wof.h" struct winnt_scan_ctx { @@ -48,6 +49,9 @@ struct winnt_scan_ctx { /* True if WOF is definitely not attached to the volume being scanned; * false if it may be */ bool wof_not_attached; + + /* A reference to the VSS snapshot being used, or NULL if none */ + struct vss_snapshot *snapshot; }; static inline const wchar_t * @@ -57,6 +61,135 @@ printable_path(const wchar_t *full_path) return full_path + 4; } +/* Description of where data is located on a Windows filesystem */ +struct windows_file { + + /* Is the data the raw encrypted data of an EFS-encrypted file? */ + u64 is_encrypted : 1; + + /* The file's LCN (logical cluster number) for sorting, or 0 if unknown. + */ + u64 sort_key : 63; + + /* A reference to the VSS snapshot containing the file, or NULL if none. + */ + struct vss_snapshot *snapshot; + + /* The path to the file. If 'is_encrypted=0' this is an NT namespace + * path; if 'is_encrypted=1' this is a Win32 namespace path. */ + wchar_t path[]; +}; + +/* Allocate a 'struct windows_file' to describe the location of a data stream. + */ +static struct windows_file * +alloc_windows_file(bool is_encrypted, struct vss_snapshot *snapshot, + const wchar_t *path, size_t path_nchars, + const wchar_t *stream_name, size_t stream_name_nchars) +{ + struct windows_file *file; + wchar_t *p; + + file = MALLOC(sizeof(struct windows_file) + + (path_nchars + (stream_name_nchars ? 1 : 0) + + stream_name_nchars + 1) * sizeof(wchar_t)); + if (!file) + return NULL; + + file->is_encrypted = is_encrypted; + file->sort_key = 0; + file->snapshot = vss_get_snapshot(snapshot); + p = wmempcpy(file->path, path, path_nchars); + if (stream_name_nchars) { + /* Named data stream */ + *p++ = L':'; + p = wmempcpy(p, stream_name, stream_name_nchars); + } + *p = L'\0'; + return file; +} + +/* Add a stream, located on a Windows filesystem, to the specified WIM inode. + */ +static int +add_stream(struct wim_inode *inode, bool is_encrypted, + struct vss_snapshot *snapshot, u64 size, + const wchar_t *path, size_t path_nchars, + int stream_type, const utf16lechar *stream_name, size_t stream_name_nchars, + struct list_head *unhashed_blobs) +{ + struct blob_descriptor *blob = NULL; + struct wim_inode_stream *strm; + + /* If the stream is nonempty, create a blob descriptor for it. */ + if (size) { + blob = new_blob_descriptor(); + if (!blob) + goto err_nomem; + + blob->windows_file = alloc_windows_file(is_encrypted, snapshot, + path, path_nchars, + stream_name, + stream_name_nchars); + if (!blob->windows_file) + goto err_nomem; + + blob->blob_location = BLOB_IN_WINDOWS_FILE; + blob->file_inode = inode; + blob->size = size; + } + + strm = inode_add_stream(inode, stream_type, stream_name, blob); + if (!strm) + goto err_nomem; + + prepare_unhashed_blob(blob, inode, strm->stream_id, unhashed_blobs); + return 0; + +err_nomem: + free_blob_descriptor(blob); + return WIMLIB_ERR_NOMEM; +} + +struct windows_file * +clone_windows_file(const struct windows_file *file) +{ + struct windows_file *new; + + new = memdup(file, sizeof(struct windows_file) + + (wcslen(file->path) + 1) * sizeof(wchar_t)); + if (new) + vss_get_snapshot(new->snapshot); + return new; +} + +void +free_windows_file(struct windows_file *file) +{ + vss_put_snapshot(file->snapshot); + FREE(file); +} + +int +cmp_windows_files(const struct windows_file *file1, + const struct windows_file *file2) +{ + /* Compare by starting LCN (logical cluster number) */ + int v = cmp_u64(file1->sort_key, file2->sort_key); + if (v) + return v; + + /* Compare files by path: just a heuristic that will place files + * in the same directory next to each other. */ + return wcscmp(file1->path, file2->path); +} + +const wchar_t * +get_windows_file_path(const struct windows_file *file) +{ + return file->path; +} + /* * If cur_dir is not NULL, open an existing file relative to the already-open * directory cur_dir. @@ -108,22 +241,16 @@ retry: return status; } -/* Read the first @size bytes from the file, or named data stream of a file, - * described by @blob. */ -int -read_winnt_stream_prefix(const struct blob_descriptor *blob, u64 size, +static int +read_winnt_stream_prefix(const wchar_t *path, u64 size, const struct read_blob_callbacks *cbs) { - const wchar_t *path; HANDLE h; NTSTATUS status; u8 buf[BUFFER_SIZE]; u64 bytes_remaining; int ret; - /* This is an NT namespace path. */ - path = blob->file_on_disk; - status = winnt_openat(NULL, path, wcslen(path), FILE_READ_DATA | SYNCHRONIZE, &h); if (!NT_SUCCESS(status)) { @@ -194,9 +321,8 @@ win32_encrypted_export_cb(unsigned char *data, void *_ctx, unsigned long len) return ERROR_SUCCESS; } -int -read_win32_encrypted_file_prefix(const struct blob_descriptor *blob, - u64 size, +static int +read_win32_encrypted_file_prefix(const wchar_t *path, bool is_dir, u64 size, const struct read_blob_callbacks *cbs) { struct win32_encrypted_read_ctx export_ctx; @@ -205,18 +331,18 @@ read_win32_encrypted_file_prefix(const struct blob_descriptor *blob, int ret; DWORD flags = 0; - if (blob->file_inode->i_attributes & FILE_ATTRIBUTE_DIRECTORY) + if (is_dir) flags |= CREATE_FOR_DIR; export_ctx.cbs = cbs; export_ctx.wimlib_err_code = 0; export_ctx.bytes_remaining = size; - err = OpenEncryptedFileRaw(blob->file_on_disk, flags, &file_ctx); + err = OpenEncryptedFileRaw(path, flags, &file_ctx); if (err != ERROR_SUCCESS) { win32_error(err, L"Failed to open encrypted file \"%ls\" for raw read", - printable_path(blob->file_on_disk)); + printable_path(path)); return WIMLIB_ERR_OPEN; } err = ReadEncryptedFileRaw(win32_encrypted_export_cb, @@ -226,14 +352,14 @@ read_win32_encrypted_file_prefix(const struct blob_descriptor *blob, if (ret == 0) { win32_error(err, L"Failed to read encrypted file \"%ls\"", - printable_path(blob->file_on_disk)); + printable_path(path)); ret = WIMLIB_ERR_READ; } } else if (export_ctx.bytes_remaining != 0) { ERROR("Only could read %"PRIu64" of %"PRIu64" bytes from " "encrypted file \"%ls\"", size - export_ctx.bytes_remaining, size, - printable_path(blob->file_on_disk)); + printable_path(path)); ret = WIMLIB_ERR_READ; } else { ret = 0; @@ -242,6 +368,22 @@ read_win32_encrypted_file_prefix(const struct blob_descriptor *blob, return ret; } +/* Read the first @size bytes from the file, or named data stream of a file, + * described by @blob. */ +int +read_windows_file_prefix(const struct blob_descriptor *blob, u64 size, + const struct read_blob_callbacks *cbs) +{ + const struct windows_file *file = blob->windows_file; + + if (unlikely(file->is_encrypted)) { + bool is_dir = (blob->file_inode->i_attributes & FILE_ATTRIBUTE_DIRECTORY); + return read_win32_encrypted_file_prefix(file->path, is_dir, size, cbs); + } + + return read_winnt_stream_prefix(file->path, size, cbs); +} + /* * Load the short name of a file into a WIM dentry. */ @@ -806,49 +948,32 @@ win32_get_encrypted_file_size(const wchar_t *path, bool is_dir, u64 *size_ret) } static int -winnt_scan_efsrpc_raw_data(struct wim_inode *inode, const wchar_t *nt_path, - struct list_head *unhashed_blobs) +winnt_scan_efsrpc_raw_data(struct wim_inode *inode, + wchar_t *path, size_t path_nchars, + struct list_head *unhashed_blobs, + struct vss_snapshot *snapshot) { - struct blob_descriptor *blob; - struct wim_inode_stream *strm; + const bool is_dir = (inode->i_attributes & FILE_ATTRIBUTE_DIRECTORY); + u64 size; int ret; - blob = new_blob_descriptor(); - if (!blob) - goto err_nomem; - - blob->file_on_disk = WCSDUP(nt_path); - if (!blob->file_on_disk) - goto err_nomem; - blob->blob_location = BLOB_WIN32_ENCRYPTED; - /* OpenEncryptedFileRaw() expects a Win32 name. */ - wimlib_assert(!wmemcmp(blob->file_on_disk, L"\\??\\", 4)); - blob->file_on_disk[1] = L'\\'; + wimlib_assert(!wmemcmp(path, L"\\??\\", 4)); + path[1] = L'\\'; - blob->file_inode = inode; - - ret = win32_get_encrypted_file_size(blob->file_on_disk, - (inode->i_attributes & FILE_ATTRIBUTE_DIRECTORY), - &blob->size); + ret = win32_get_encrypted_file_size(path, is_dir, &size); if (ret) - goto err; + goto out; /* Empty EFSRPC data does not make sense */ - wimlib_assert(blob->size != 0); - - strm = inode_add_stream(inode, STREAM_TYPE_EFSRPC_RAW_DATA, - NO_STREAM_NAME, blob); - if (!strm) - goto err_nomem; - - prepare_unhashed_blob(blob, inode, strm->stream_id, unhashed_blobs); - return 0; + wimlib_assert(size != 0); -err_nomem: - ret = WIMLIB_ERR_NOMEM; -err: - free_blob_descriptor(blob); + ret = add_stream(inode, true, snapshot, size, + path, path_nchars, + STREAM_TYPE_EFSRPC_RAW_DATA, NO_STREAM_NAME, 0, + unhashed_blobs); +out: + path[1] = L'?'; return ret; } @@ -885,43 +1010,15 @@ get_data_stream_name(wchar_t *raw_stream_name, size_t raw_stream_name_nchars, return true; } -/* Build the path to the data stream. For unnamed streams, this is simply the - * path to the file. For named streams, this is the path to the file, followed - * by a colon, followed by the stream name. */ -static wchar_t * -build_data_stream_path(const wchar_t *path, size_t path_nchars, - const wchar_t *stream_name, size_t stream_name_nchars) -{ - size_t stream_path_nchars; - wchar_t *stream_path; - wchar_t *p; - - stream_path_nchars = path_nchars; - if (stream_name_nchars) - stream_path_nchars += 1 + stream_name_nchars; - - stream_path = MALLOC((stream_path_nchars + 1) * sizeof(wchar_t)); - if (stream_path) { - p = wmempcpy(stream_path, path, path_nchars); - if (stream_name_nchars) { - *p++ = L':'; - p = wmempcpy(p, stream_name, stream_name_nchars); - } - *p++ = L'\0'; - } - return stream_path; -} - static int winnt_scan_data_stream(const wchar_t *path, size_t path_nchars, wchar_t *raw_stream_name, size_t raw_stream_name_nchars, u64 stream_size, - struct wim_inode *inode, struct list_head *unhashed_blobs) + struct wim_inode *inode, struct list_head *unhashed_blobs, + struct vss_snapshot *snapshot) { wchar_t *stream_name; size_t stream_name_nchars; - struct blob_descriptor *blob; - struct wim_inode_stream *strm; /* Given the raw stream name (which is something like * :streamname:$DATA), extract just the stream name part (streamname). @@ -932,34 +1029,10 @@ winnt_scan_data_stream(const wchar_t *path, size_t path_nchars, stream_name[stream_name_nchars] = L'\0'; - /* If the stream is non-empty, set up a blob descriptor for it. */ - if (stream_size != 0) { - blob = new_blob_descriptor(); - if (!blob) - goto err_nomem; - blob->file_on_disk = build_data_stream_path(path, - path_nchars, - stream_name, - stream_name_nchars); - if (!blob->file_on_disk) - goto err_nomem; - blob->blob_location = BLOB_IN_WINNT_FILE_ON_DISK; - blob->size = stream_size; - blob->file_inode = inode; - } else { - blob = NULL; - } - - strm = inode_add_stream(inode, STREAM_TYPE_DATA, stream_name, blob); - if (!strm) - goto err_nomem; - - prepare_unhashed_blob(blob, inode, strm->stream_id, unhashed_blobs); - return 0; - -err_nomem: - free_blob_descriptor(blob); - return WIMLIB_ERR_NOMEM; + return add_stream(inode, false, snapshot, stream_size, + path, path_nchars, + STREAM_TYPE_DATA, stream_name, stream_name_nchars, + unhashed_blobs); } /* @@ -979,7 +1052,8 @@ err_nomem: static noinline_for_stack int winnt_scan_data_streams(HANDLE h, const wchar_t *path, size_t path_nchars, struct wim_inode *inode, struct list_head *unhashed_blobs, - u64 file_size, u32 vol_flags) + u64 file_size, u32 vol_flags, + struct vss_snapshot *snapshot) { int ret; u8 _buf[4096] _aligned_attribute(8); @@ -1047,7 +1121,8 @@ winnt_scan_data_streams(HANDLE h, const wchar_t *path, size_t path_nchars, info->StreamName, info->StreamNameLength / 2, info->StreamSize.QuadPart, - inode, unhashed_blobs); + inode, unhashed_blobs, + snapshot); if (ret) goto out_free_buf; @@ -1075,7 +1150,8 @@ unnamed_only: { wchar_t stream_name[] = L"::$DATA"; ret = winnt_scan_data_stream(path, path_nchars, stream_name, 7, - file_size, inode, unhashed_blobs); + file_size, inode, unhashed_blobs, + snapshot); } out_free_buf: /* Free buffer if allocated on heap. */ @@ -1109,9 +1185,8 @@ set_sort_key(struct wim_inode *inode, u64 sort_key) for (unsigned i = 0; i < inode->i_num_streams; i++) { struct wim_inode_stream *strm = &inode->i_streams[i]; struct blob_descriptor *blob = stream_blob_resolved(strm); - if (blob && (blob->blob_location == BLOB_IN_WINNT_FILE_ON_DISK || - blob->blob_location == BLOB_WIN32_ENCRYPTED)) - blob->sort_key = sort_key; + if (blob && blob->blob_location == BLOB_IN_WINDOWS_FILE) + blob->windows_file->sort_key = sort_key; } } @@ -1517,8 +1592,11 @@ retry_open: * needed. */ (*func_NtClose)(h); h = NULL; - ret = winnt_scan_efsrpc_raw_data(inode, full_path, - params->unhashed_blobs); + ret = winnt_scan_efsrpc_raw_data(inode, + full_path, + full_path_nchars, + params->unhashed_blobs, + ctx->snapshot); if (ret) goto out; } else { @@ -1538,7 +1616,8 @@ retry_open: inode, params->unhashed_blobs, file_info.end_of_file, - ctx->vol_flags); + ctx->vol_flags, + ctx->snapshot); if (ret) goto out; } @@ -1632,11 +1711,11 @@ win32_build_dentry_tree(struct wim_dentry **root_ret, const wchar_t *root_disk_path, struct capture_params *params) { - wchar_t *path; - int ret; + wchar_t *path = NULL; + struct winnt_scan_ctx ctx = {}; UNICODE_STRING ntpath; - struct winnt_scan_ctx ctx; size_t ntpath_nchars; + int ret; /* WARNING: There is no check for overflow later when this buffer is * being used! But it's as long as the maximum path length understood @@ -1645,9 +1724,13 @@ win32_build_dentry_tree(struct wim_dentry **root_ret, if (!path) return WIMLIB_ERR_NOMEM; - ret = win32_path_to_nt_path(root_disk_path, &ntpath); + if (params->add_flags & WIMLIB_ADD_FLAG_SNAPSHOT) + ret = vss_create_snapshot(root_disk_path, &ntpath, &ctx.snapshot); + else + ret = win32_path_to_nt_path(root_disk_path, &ntpath); + if (ret) - goto out_free_path; + goto out; if (ntpath.Length < 4 * sizeof(wchar_t) || ntpath.Length > WINDOWS_NT_MAX_PATH * sizeof(wchar_t) || @@ -1667,14 +1750,13 @@ win32_build_dentry_tree(struct wim_dentry **root_ret, } HeapFree(GetProcessHeap(), 0, ntpath.Buffer); if (ret) - goto out_free_path; - - memset(&ctx, 0, sizeof(ctx)); + goto out; ret = winnt_build_dentry_tree_recursive(root_ret, NULL, path, ntpath_nchars, L"", 0, params, &ctx); -out_free_path: +out: + vss_put_snapshot(ctx.snapshot); FREE(path); if (ret == 0) winnt_do_scan_warnings(root_disk_path, &ctx); diff --git a/src/win32_common.c b/src/win32_common.c index b25871a3..b8ac2197 100644 --- a/src/win32_common.c +++ b/src/win32_common.c @@ -29,6 +29,7 @@ #include "wimlib/error.h" #include "wimlib/util.h" +#include "wimlib/win32_vss.h" static bool win32_modify_privilege(const wchar_t *privilege, bool enable) @@ -316,6 +317,8 @@ out_drop_privs: void win32_global_cleanup(void) { + vss_global_cleanup(); + if (acquired_privileges) win32_release_capture_and_apply_privileges(); diff --git a/src/win32_vss.c b/src/win32_vss.c new file mode 100644 index 00000000..6e5d3ebc --- /dev/null +++ b/src/win32_vss.c @@ -0,0 +1,507 @@ +/* + * win32_vss.c - Experimental Windows-specific code for creating VSS (Volume + * Shadow Copy Service) snapshots. + */ + +/* + * Copyright (C) 2015 Eric Biggers + * + * This file is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation; either version 3 of the License, or (at your option) any + * later version. + * + * This file 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 Lesser General Public License for more + * details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this file; if not, see http://www.gnu.org/licenses/. + */ + +#ifdef __WIN32__ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "wimlib/win32_common.h" + +#include +#include + +#include "wimlib/error.h" +#include "wimlib/util.h" +#include "wimlib/win32_vss.h" + +/*----------------------------------------------------------------------------* + * VSS API declarations * + *----------------------------------------------------------------------------*/ + +typedef GUID VSS_ID; +typedef LONGLONG VSS_TIMESTAMP; +typedef WCHAR *VSS_PWSZ; + +typedef enum { + VSS_BT_UNDEFINED = 0, + VSS_BT_FULL = 1, + VSS_BT_INCREMENTAL = 2, + VSS_BT_DIFFERENTIAL = 3, + VSS_BT_LOG = 4, + VSS_BT_COPY = 5, + VSS_BT_OTHER = 6, +} VSS_BACKUP_TYPE; + +typedef enum { + VSS_SS_UNKNOWN = 0x00, + VSS_SS_PREPARING = 0x01, + VSS_SS_PROCESSING_PREPARE = 0x02, + VSS_SS_PREPARED = 0x03, + VSS_SS_PROCESSING_PRECOMMIT = 0x04, + VSS_SS_PRECOMMITTED = 0x05, + VSS_SS_PROCESSING_COMMIT = 0x06, + VSS_SS_COMMITTED = 0x07, + VSS_SS_PROCESSING_POSTCOMMIT = 0x08, + VSS_SS_PROCESSING_PREFINALCOMMIT = 0x09, + VSS_SS_PREFINALCOMMITTED = 0x0a, + VSS_SS_PROCESSING_POSTFINALCOMMIT = 0x0b, + VSS_SS_CREATED = 0x0c, + VSS_SS_ABORTED = 0x0d, + VSS_SS_DELETED = 0x0e, + VSS_SS_POSTCOMMITTED = 0x0f, + VSS_SS_COUNT = 0x10, +} VSS_SNAPSHOT_STATE; + +typedef enum { + VSS_VOLSNAP_ATTR_PERSISTENT = 0x00000001, + VSS_VOLSNAP_ATTR_NO_AUTORECOVERY = 0x00000002, + VSS_VOLSNAP_ATTR_CLIENT_ACCESSIBLE = 0x00000004, + VSS_VOLSNAP_ATTR_NO_AUTO_RELEASE = 0x00000008, + VSS_VOLSNAP_ATTR_NO_WRITERS = 0x00000010, + VSS_VOLSNAP_ATTR_TRANSPORTABLE = 0x00000020, + VSS_VOLSNAP_ATTR_NOT_SURFACED = 0x00000040, + VSS_VOLSNAP_ATTR_NOT_TRANSACTED = 0x00000080, + VSS_VOLSNAP_ATTR_HARDWARE_ASSISTED = 0x00010000, + VSS_VOLSNAP_ATTR_DIFFERENTIAL = 0x00020000, + VSS_VOLSNAP_ATTR_PLEX = 0x00040000, + VSS_VOLSNAP_ATTR_IMPORTED = 0x00080000, + VSS_VOLSNAP_ATTR_EXPOSED_LOCALLY = 0x00100000, + VSS_VOLSNAP_ATTR_EXPOSED_REMOTELY = 0x00200000, + VSS_VOLSNAP_ATTR_AUTORECOVER = 0x00400000, + VSS_VOLSNAP_ATTR_ROLLBACK_RECOVERY = 0x00800000, + VSS_VOLSNAP_ATTR_DELAYED_POSTSNAPSHOT = 0x01000000, + VSS_VOLSNAP_ATTR_TXF_RECOVERY = 0x02000000, +} VSS_VOLUME_SNAPSHOT_ATTRIBUTES; + +typedef enum { + VSS_CTX_BACKUP = 0, + VSS_CTX_FILE_SHARE_BACKUP = VSS_VOLSNAP_ATTR_NO_WRITERS, + VSS_CTX_NAS_ROLLBACK = ( ( VSS_VOLSNAP_ATTR_PERSISTENT | VSS_VOLSNAP_ATTR_NO_AUTO_RELEASE ) | VSS_VOLSNAP_ATTR_NO_WRITERS ), + VSS_CTX_APP_ROLLBACK = ( VSS_VOLSNAP_ATTR_PERSISTENT | VSS_VOLSNAP_ATTR_NO_AUTO_RELEASE ), + VSS_CTX_CLIENT_ACCESSIBLE = ( ( ( VSS_VOLSNAP_ATTR_PERSISTENT | VSS_VOLSNAP_ATTR_CLIENT_ACCESSIBLE ) | VSS_VOLSNAP_ATTR_NO_AUTO_RELEASE ) | VSS_VOLSNAP_ATTR_NO_WRITERS ), + VSS_CTX_CLIENT_ACCESSIBLE_WRITERS = ( ( VSS_VOLSNAP_ATTR_PERSISTENT | VSS_VOLSNAP_ATTR_CLIENT_ACCESSIBLE ) | VSS_VOLSNAP_ATTR_NO_AUTO_RELEASE ), + VSS_CTX_ALL = 0xffffffff, +} VSS_SNAPSHOT_CONTEXT; + +typedef struct { + VSS_ID m_SnapshotId; + VSS_ID m_SnapshotSetId; + LONG m_lSnapshotsCount; + VSS_PWSZ m_pwszSnapshotDeviceObject; + VSS_PWSZ m_pwszOriginalVolumeName; + VSS_PWSZ m_pwszOriginatingMachine; + VSS_PWSZ m_pwszServiceMachine; + VSS_PWSZ m_pwszExposedName; + VSS_PWSZ m_pwszExposedPath; + VSS_ID m_ProviderId; + LONG m_lSnapshotAttributes; + VSS_TIMESTAMP m_tsCreationTimestamp; + VSS_SNAPSHOT_STATE m_eStatus; +} VSS_SNAPSHOT_PROP; + +typedef struct IVssAsyncVTable IVssAsyncVTable; + +typedef struct { + IVssAsyncVTable *vtable; +} IVssAsync; + +struct IVssAsyncVTable { + void *QueryInterface; + void *AddRef; + ULONG (WINAPI *Release)(IVssAsync *this); + void *Cancel; + HRESULT (WINAPI *Wait)(IVssAsync *this, DWORD dwMilliseconds); + void *QueryStatus; +}; + +typedef struct IVssBackupComponentsVTable IVssBackupComponentsVTable; + +typedef struct { + IVssBackupComponentsVTable *vtable; +} IVssBackupComponents; + +struct IVssBackupComponentsVTable { + void *QueryInterface; + void *AddRef; + ULONG (WINAPI *Release)(IVssBackupComponents *this); + void *GetWriterComponentsCount; + void *GetWriterComponents; + HRESULT (WINAPI *InitializeForBackup)(IVssBackupComponents *this, + BSTR bstrXML); + HRESULT (WINAPI *SetBackupState)(IVssBackupComponents *this, + BOOLEAN bSelectComponents, + BOOLEAN bBackupBootableSystemState, + VSS_BACKUP_TYPE backupType, + BOOLEAN bPartialFileSupport); + void *InitializeForRestore; + void *SetRestoreState; + HRESULT (WINAPI *GatherWriterMetadata)(IVssBackupComponents *this, + IVssAsync **ppAsync); + void *GetWriterMetadataCount; + void *GetWriterMetadata; + void *FreeWriterMetadata; + void *AddComponent; + HRESULT (WINAPI *PrepareForBackup)(IVssBackupComponents *this, + IVssAsync **ppAsync); + void *AbortBackup; + void *GatherWriterStatus; + void *GetWriterStatusCount; + void *FreeWriterStatus; + void *GetWriterStatus; + void *SetBackupSucceeded; + void *SetBackupOptions; + void *SetSelectedForRestore; + void *SetRestoreOptions; + void *SetAdditionalRestores; + void *SetPreviousBackupStamp; + void *SaveAsXML; + void *BackupComplete; + void *AddAlternativeLocationMapping; + void *AddRestoreSubcomponent; + void *SetFileRestoreStatus; + void *AddNewTarget; + void *SetRangesFilePath; + void *PreRestore; + void *PostRestore; + HRESULT (WINAPI *SetContext)(IVssBackupComponents *this, + LONG lContext); + HRESULT (WINAPI *StartSnapshotSet)(IVssBackupComponents *this, + VSS_ID *pSnapshotSetId); + HRESULT (WINAPI *AddToSnapshotSet)(IVssBackupComponents *this, + VSS_PWSZ pwszVolumeName, + VSS_ID ProviderId, + VSS_ID *pidSnapshot); + HRESULT (WINAPI *DoSnapshotSet)(IVssBackupComponents *this, + IVssAsync **ppAsync); + void *DeleteSnapshots; + void *ImportSnapshots; + void *BreakSnapshotSet; + HRESULT (WINAPI *GetSnapshotProperties)(IVssBackupComponents *this, + VSS_ID SnapshotId, + VSS_SNAPSHOT_PROP *pprop); + void *Query; + void *IsVolumeSupported; + void *DisableWriterClasses; + void *EnableWriterClasses; + void *DisableWriterInstances; + void *ExposeSnapshot; + void *RevertToSnapshot; + void *QueryRevertStatus; +}; + +/*----------------------------------------------------------------------------* + * VSS API initialization * + *----------------------------------------------------------------------------*/ + +static bool vss_initialized; +static pthread_mutex_t vss_initialization_mutex = PTHREAD_MUTEX_INITIALIZER; + +/* vssapi.dll */ +static HANDLE hVssapi; +static HRESULT (WINAPI *func_CreateVssBackupComponentsInternal)(IVssBackupComponents **ppBackup); +static void (WINAPI *func_VssFreeSnapshotPropertiesInternal)(VSS_SNAPSHOT_PROP *pProp); + +/* ole32.dll */ +static HANDLE hOle32; +static void (WINAPI *func_CoInitialize)(LPVOID *pvReserved); +static void (WINAPI *func_CoUninitialize)(void); + +static bool +vss_global_init_impl(void) +{ + hVssapi = LoadLibrary(L"vssapi.dll"); + if (!hVssapi) { + ERROR("vssapi.dll not found"); + goto err; + } + + func_CreateVssBackupComponentsInternal = + (void *)GetProcAddress(hVssapi, "CreateVssBackupComponentsInternal"); + if (!func_CreateVssBackupComponentsInternal) { + ERROR("CreateVssBackupComponentsInternal() not found in vssapi.dll"); + goto err_vssapi; + } + + func_VssFreeSnapshotPropertiesInternal = + (void *)GetProcAddress(hVssapi, "VssFreeSnapshotPropertiesInternal"); + if (!func_VssFreeSnapshotPropertiesInternal) { + ERROR("VssFreeSnapshotPropertiesInternal() not found in vssapi.dll"); + goto err_vssapi; + } + + hOle32 = LoadLibrary(L"ole32.dll"); + if (!hOle32) { + ERROR("ole32.dll not found"); + goto err_vssapi; + } + + func_CoInitialize = (void *)GetProcAddress(hOle32, "CoInitialize"); + if (!func_CoInitialize) { + ERROR("CoInitialize() not found in ole32.dll"); + goto err_ole32; + } + + func_CoUninitialize = (void *)GetProcAddress(hOle32, "CoUninitialize"); + if (!func_CoUninitialize) { + ERROR("CoUninitialize() not found in ole32.dll"); + goto err_ole32; + } + + (*func_CoInitialize)(NULL); + return true; + +err_ole32: + FreeLibrary(hOle32); +err_vssapi: + FreeLibrary(hVssapi); +err: + return false; +} + +static bool +vss_global_init(void) +{ + if (vss_initialized) + return true; + + pthread_mutex_lock(&vss_initialization_mutex); + if (!vss_initialized) + vss_initialized = vss_global_init_impl(); + pthread_mutex_unlock(&vss_initialization_mutex); + + if (vss_initialized) + return true; + ERROR("The Volume Shadow Copy Service (VSS) API could not be " + "initialized. Probably it isn't supported on this computer."); + return false; +} + +void +vss_global_cleanup(void) +{ + if (!vss_initialized) + return; + + pthread_mutex_lock(&vss_initialization_mutex); + if (vss_initialized) { + (*func_CoUninitialize)(); + FreeLibrary(hOle32); + FreeLibrary(hVssapi); + vss_initialized = false; + } + pthread_mutex_unlock(&vss_initialization_mutex); +} + +/*----------------------------------------------------------------------------* + * VSS implementation * + *----------------------------------------------------------------------------*/ + +struct vss_snapshot_internal { + struct vss_snapshot base; + IVssBackupComponents *vss; + VSS_SNAPSHOT_PROP props; +}; + +/* Delete the specified VSS snapshot. */ +void +vss_delete_snapshot(struct vss_snapshot *snapshot) +{ + struct vss_snapshot_internal *internal; + + internal = container_of(snapshot, struct vss_snapshot_internal, base); + + if (internal->props.m_pwszSnapshotDeviceObject) + (*func_VssFreeSnapshotPropertiesInternal)(&internal->props); + if (internal->vss) + internal->vss->vtable->Release(internal->vss); + FREE(internal); +} + +static HRESULT +wait_and_release(IVssAsync *async) +{ + HRESULT res = async->vtable->Wait(async, INFINITE); + async->vtable->Release(async); + return res; +} + +static bool +request_vss_snapshot(IVssBackupComponents *vss, wchar_t *volume, + VSS_ID *snapshot_id) +{ + HRESULT res; + IVssAsync *async; + + res = vss->vtable->InitializeForBackup(vss, NULL); + if (FAILED(res)) { + ERROR("IVssBackupComponents.InitializeForBackup() error: %x", res); + return false; + } + + res = vss->vtable->SetContext(vss, VSS_CTX_BACKUP); + if (FAILED(res)) { + ERROR("IVssBackupComponents.SetContext() error: %x", res); + return false; + } + + res = vss->vtable->SetBackupState(vss, FALSE, TRUE, VSS_BT_COPY, FALSE); + if (FAILED(res)) { + ERROR("IVssBackupComponents.SetBackupState() error: %x", res); + return false; + } + + res = vss->vtable->StartSnapshotSet(vss, snapshot_id); + if (FAILED(res)) { + ERROR("IVssBackupComponents.StartSnapshotSet() error: %x", res); + return false; + } + + res = vss->vtable->AddToSnapshotSet(vss, volume, (GUID){}, snapshot_id); + if (FAILED(res)) { + ERROR("IVssBackupComponents.AddToSnapshotSet() error: %x", res); + return false; + } + + res = vss->vtable->PrepareForBackup(vss, &async); + if (FAILED(res)) { + ERROR("IVssBackupComponents.PrepareForBackup() error: %x", res); + return false; + } + res = wait_and_release(async); + if (FAILED(res)) { + ERROR("IVssAsync.Wait() error while preparing for backup: %x", res); + return false; + } + + res = vss->vtable->DoSnapshotSet(vss, &async); + if (FAILED(res)) { + ERROR("IVssBackupComponents.DoSnapshotSet() error: %x", res); + return false; + } + res = wait_and_release(async); + if (FAILED(res)) { + ERROR("IVssAsync.Wait() error while doing snapshot set: %x", res); + return false; + } + + return true; +} + +/* + * Create a VSS snapshot of the specified @volume. Return the NT namespace path + * to the snapshot root directory in @vss_path_ret and a handle to the snapshot + * in @snapshot_ret. + */ +int +vss_create_snapshot(const wchar_t *source, UNICODE_STRING *vss_path_ret, + struct vss_snapshot **snapshot_ret) +{ + wchar_t *source_abspath; + wchar_t volume[4]; + VSS_ID snapshot_id; + struct vss_snapshot_internal *snapshot = NULL; + IVssBackupComponents *vss; + HRESULT res; + int ret; + + source_abspath = realpath(source, NULL); + if (!source_abspath) { + ret = WIMLIB_ERR_NOMEM; + goto err; + } + + if (source_abspath[0] == L'\0' || source_abspath[1] != L':' || + source_abspath[2] != L'\\') { + ERROR("\"%ls\" (full path \"%ls\"): Path format not recognized", + source, source_abspath); + ret = WIMLIB_ERR_UNSUPPORTED; + goto err; + } + + wsprintf(volume, L"%lc:\\", source_abspath[0]); + + snapshot = CALLOC(1, sizeof(*snapshot)); + if (!snapshot) { + ret = WIMLIB_ERR_NOMEM; + goto err; + } + + if (!vss_global_init()) + goto vss_err; + + res = (*func_CreateVssBackupComponentsInternal)(&vss); + if (FAILED(res)) { + ERROR("CreateVssBackupComponents error: %x", res); + goto vss_err; + } + + snapshot->vss = vss; + + if (!request_vss_snapshot(vss, volume, &snapshot_id)) + goto vss_err; + + res = vss->vtable->GetSnapshotProperties(vss, snapshot_id, &snapshot->props); + if (FAILED(res)) { + ERROR("IVssBackupComponents.GetSnapshotProperties() error: %x", res); + goto vss_err; + } + + if (wcsncmp(snapshot->props.m_pwszSnapshotDeviceObject, L"\\\\?\\", 4)) { + ERROR("Unexpected volume shadow device path: %ls", + snapshot->props.m_pwszSnapshotDeviceObject); + goto vss_err; + } + + vss_path_ret->MaximumLength = sizeof(wchar_t) * + (wcslen(snapshot->props.m_pwszSnapshotDeviceObject) + + 1 + wcslen(&source_abspath[3]) + 1); + vss_path_ret->Length = vss_path_ret->MaximumLength - sizeof(wchar_t); + vss_path_ret->Buffer = HeapAlloc(GetProcessHeap(), 0, + vss_path_ret->MaximumLength); + if (!vss_path_ret->Buffer) { + ret = WIMLIB_ERR_NOMEM; + goto err; + } + + wsprintf(vss_path_ret->Buffer, L"\\??\\%ls\\%ls", + &snapshot->props.m_pwszSnapshotDeviceObject[4], + &source_abspath[3]); + *snapshot_ret = &snapshot->base; + snapshot->base.refcnt = 1; + ret = 0; + goto out; + +vss_err: + ret = WIMLIB_ERR_SNAPSHOT_FAILURE; + ERROR("A problem occurred while creating a VSS snapshot of \"%ls\".\n" + " Aborting the operation.", volume); +err: + if (snapshot) + vss_delete_snapshot(&snapshot->base); +out: + FREE(source_abspath); + return ret; +} + +#endif /* __WIN32__ */ diff --git a/src/write.c b/src/write.c index 7f13630c..9f48d3f1 100644 --- a/src/write.c +++ b/src/write.c @@ -639,6 +639,7 @@ do_done_with_blob(struct blob_descriptor *blob, { int ret; struct wim_inode *inode; + const tchar *path; tchar *cookie1; tchar *cookie2; @@ -652,10 +653,12 @@ do_done_with_blob(struct blob_descriptor *blob, if (--inode->i_num_remaining_streams > 0) return 0; - cookie1 = progress_get_streamless_path(blob->file_on_disk); - cookie2 = progress_get_win32_path(blob->file_on_disk); + path = blob_file_path(blob); - ret = done_with_file(blob->file_on_disk, progfunc, progctx); + cookie1 = progress_get_streamless_path(path); + cookie2 = progress_get_win32_path(path); + + ret = done_with_file(path, progfunc, progctx); progress_put_win32_path(cookie2); progress_put_streamless_path(cookie1);