]> wimlib.net Git - wimlib/blobdiff - src/win32_capture.c
update_image.c: Ignore Windows 10 Recycle Bin directories.
[wimlib] / src / win32_capture.c
index 06a5f343eb70299cd5ec867735749fdd4a01514b..a93427ec46a1d60a02642b5fc721b3946a1c64a8 100644 (file)
 
 #include "wimlib/assert.h"
 #include "wimlib/blob_table.h"
-#include "wimlib/capture.h"
 #include "wimlib/dentry.h"
 #include "wimlib/encoding.h"
 #include "wimlib/endianness.h"
 #include "wimlib/error.h"
+#include "wimlib/object_id.h"
 #include "wimlib/paths.h"
 #include "wimlib/reparse.h"
+#include "wimlib/scan.h"
 #include "wimlib/win32_vss.h"
 #include "wimlib/wof.h"
 
 struct winnt_scan_ctx {
-       struct capture_params *params;
+       struct scan_params *params;
        bool is_ntfs;
        u32 vol_flags;
        unsigned long num_get_sd_access_denied;
@@ -314,7 +315,7 @@ windows_file_to_string(const struct windows_file *file, u8 *buf, size_t bufsize)
 
 static int
 read_winnt_stream_prefix(const struct windows_file *file,
-                        u64 size, const struct read_blob_callbacks *cbs)
+                        u64 size, const struct consume_chunk_callback *cb)
 {
        IO_STATUS_BLOCK iosb;
        UNICODE_STRING name = {
@@ -400,7 +401,7 @@ read_winnt_stream_prefix(const struct windows_file *file,
                bytes_read = iosb.Information;
 
                bytes_remaining -= bytes_read;
-               ret = call_consume_chunk(buf, bytes_read, cbs);
+               ret = consume_chunk(cb, buf, bytes_read);
                if (ret)
                        break;
        }
@@ -409,7 +410,7 @@ read_winnt_stream_prefix(const struct windows_file *file,
 }
 
 struct win32_encrypted_read_ctx {
-       const struct read_blob_callbacks *cbs;
+       const struct consume_chunk_callback *cb;
        int wimlib_err_code;
        u64 bytes_remaining;
 };
@@ -424,7 +425,7 @@ win32_encrypted_export_cb(unsigned char *data, void *_ctx, unsigned long len)
        if (bytes_to_consume == 0)
                return ERROR_SUCCESS;
 
-       ret = call_consume_chunk(data, bytes_to_consume, ctx->cbs);
+       ret = consume_chunk(ctx->cb, data, bytes_to_consume);
        if (ret) {
                ctx->wimlib_err_code = ret;
                /* It doesn't matter what error code is returned here, as long
@@ -437,7 +438,7 @@ win32_encrypted_export_cb(unsigned char *data, void *_ctx, unsigned long len)
 
 static int
 read_win32_encrypted_file_prefix(const wchar_t *path, bool is_dir, u64 size,
-                                const struct read_blob_callbacks *cbs)
+                                const struct consume_chunk_callback *cb)
 {
        struct win32_encrypted_read_ctx export_ctx;
        DWORD err;
@@ -448,7 +449,7 @@ read_win32_encrypted_file_prefix(const wchar_t *path, bool is_dir, u64 size,
        if (is_dir)
                flags |= CREATE_FOR_DIR;
 
-       export_ctx.cbs = cbs;
+       export_ctx.cb = cb;
        export_ctx.wimlib_err_code = 0;
        export_ctx.bytes_remaining = size;
 
@@ -486,16 +487,16 @@ read_win32_encrypted_file_prefix(const wchar_t *path, bool is_dir, u64 size,
  * described by @blob.  */
 int
 read_windows_file_prefix(const struct blob_descriptor *blob, u64 size,
-                        const struct read_blob_callbacks *cbs)
+                        const struct consume_chunk_callback *cb)
 {
        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_win32_encrypted_file_prefix(file->path, is_dir, size, cb);
        }
 
-       return read_winnt_stream_prefix(file, size, cbs);
+       return read_winnt_stream_prefix(file, size, cb);
 }
 
 /*
@@ -655,6 +656,46 @@ out:
        return 0;
 }
 
+/* Load a file's object ID into the corresponding WIM inode.  */
+static noinline_for_stack int
+winnt_load_object_id(HANDLE h, struct wim_inode *inode,
+                    const wchar_t *full_path, struct winnt_scan_ctx *ctx)
+{
+       FILE_OBJECTID_BUFFER buffer;
+       NTSTATUS status;
+       u32 len;
+
+       if (!(ctx->vol_flags & FILE_SUPPORTS_OBJECT_IDS))
+               return 0;
+
+       status = winnt_fsctl(h, FSCTL_GET_OBJECT_ID, NULL, 0,
+                            &buffer, sizeof(buffer), &len);
+
+       if (status == STATUS_OBJECTID_NOT_FOUND) /* No object ID  */
+               return 0;
+
+       if (status == STATUS_INVALID_DEVICE_REQUEST) {
+               /* The filesystem claimed to support object IDs, but we can't
+                * actually read them.  This happens with Samba.  */
+               ctx->vol_flags &= ~FILE_SUPPORTS_OBJECT_IDS;
+               return 0;
+       }
+
+       if (!NT_SUCCESS(status)) {
+               winnt_error(status, L"\"%ls\": Can't read object ID",
+                           printable_path(full_path));
+               return WIMLIB_ERR_STAT;
+       }
+
+       if (len == 0) /* No object ID (for directories)  */
+               return 0;
+
+       if (!inode_set_object_id(inode, &buffer, len))
+               return WIMLIB_ERR_NOMEM;
+
+       return 0;
+}
+
 static int
 winnt_build_dentry_tree_recursive(struct wim_dentry **root_ret,
                                  HANDLE cur_dir,
@@ -869,7 +910,7 @@ out_close_root_dir:
 }
 
 static int
-winnt_rpfix_progress(struct capture_params *params, const wchar_t *path,
+winnt_rpfix_progress(struct scan_params *params, const wchar_t *path,
                     const struct link_reparse_point *link, int scan_status)
 {
        size_t print_name_nchars = link->print_name_nbytes / sizeof(wchar_t);
@@ -880,12 +921,12 @@ winnt_rpfix_progress(struct capture_params *params, const wchar_t *path,
 
        params->progress.scan.cur_path = path;
        params->progress.scan.symlink_target = print_name0;
-       return do_capture_progress(params, scan_status, NULL);
+       return do_scan_progress(params, scan_status, NULL);
 }
 
 static int
 winnt_try_rpfix(struct reparse_buffer_disk *rpbuf, u16 *rpbuflen_p,
-               const wchar_t *path, struct capture_params *params)
+               const wchar_t *path, struct scan_params *params)
 {
        struct link_reparse_point link;
        const wchar_t *rel_target;
@@ -978,7 +1019,7 @@ winnt_try_rpfix(struct reparse_buffer_disk *rpbuf, u16 *rpbuflen_p,
  * capture root.  */
 static noinline_for_stack int
 winnt_load_reparse_data(HANDLE h, struct wim_inode *inode,
-                       const wchar_t *full_path, struct capture_params *params)
+                       const wchar_t *full_path, struct scan_params *params)
 {
        struct reparse_buffer_disk rpbuf;
        NTSTATUS status;
@@ -1009,6 +1050,21 @@ winnt_load_reparse_data(HANDLE h, struct wim_inode *inode,
                return WIMLIB_ERR_INVALID_REPARSE_DATA;
        }
 
+       if (le32_to_cpu(rpbuf.rptag) == WIM_IO_REPARSE_TAG_DEDUP) {
+               /*
+                * Windows treats Data Deduplication reparse points specially.
+                * Reads from the unnamed data stream actually return the
+                * redirected file contents, even with FILE_OPEN_REPARSE_POINT.
+                * Deduplicated files also cannot be properly restored without
+                * also restoring the "System Volume Information" directory,
+                * which wimlib excludes by default.  Therefore, the logical
+                * behavior for us seems to be to ignore the reparse point and
+                * treat the file as a normal file.
+                */
+               inode->i_attributes &= ~FILE_ATTRIBUTE_REPARSE_POINT;
+               return 0;
+       }
+
        if (params->add_flags & WIMLIB_ADD_FLAG_RPFIX) {
                ret = winnt_try_rpfix(&rpbuf, &rpbuflen, full_path, params);
                if (ret == RP_FIXED)
@@ -1320,7 +1376,7 @@ set_sort_key(struct wim_inode *inode, u64 sort_key)
 static inline bool
 should_try_to_use_wimboot_hash(const struct wim_inode *inode,
                               const struct winnt_scan_ctx *ctx,
-                              const struct capture_params *params)
+                              const struct scan_params *params)
 {
        /* Directories and encrypted files aren't valid for external backing. */
        if (inode->i_attributes & (FILE_ATTRIBUTE_DIRECTORY |
@@ -1673,6 +1729,11 @@ winnt_build_dentry_tree_recursive(struct wim_dentry **root_ret,
                        goto out;
        }
 
+       /* Get the file's object ID.  */
+       ret = winnt_load_object_id(h, inode, full_path, ctx);
+       if (ret)
+               goto out;
+
        /* If this is a reparse point, load the reparse data.  */
        if (unlikely(inode->i_attributes & FILE_ATTRIBUTE_REPARSE_POINT)) {
                ret = winnt_load_reparse_data(h, inode, full_path, ctx->params);
@@ -1756,16 +1817,16 @@ winnt_build_dentry_tree_recursive(struct wim_dentry **root_ret,
 out_progress:
        ctx->params->progress.scan.cur_path = full_path;
        if (likely(root))
-               ret = do_capture_progress(ctx->params, WIMLIB_SCAN_DENTRY_OK, inode);
+               ret = do_scan_progress(ctx->params, WIMLIB_SCAN_DENTRY_OK, inode);
        else
-               ret = do_capture_progress(ctx->params, WIMLIB_SCAN_DENTRY_EXCLUDED, NULL);
+               ret = do_scan_progress(ctx->params, WIMLIB_SCAN_DENTRY_EXCLUDED, NULL);
 out:
        if (likely(h))
                NtClose(h);
        if (unlikely(ret)) {
                free_dentry_tree(root, ctx->params->blob_table);
                root = NULL;
-               ret = report_capture_error(ctx->params, ret, full_path);
+               ret = report_scan_error(ctx->params, ret, full_path);
        }
        *root_ret = root;
        return ret;
@@ -1937,7 +1998,8 @@ struct ntfs_inode {
        u32 attributes;
        u32 security_id;
        u32 num_aliases;
-       u32 num_streams;
+       u32 num_streams : 31;
+       u32 have_object_id : 1;
        u32 first_stream_offset;
        struct ntfs_dentry *first_child;
        wchar_t short_name[13];
@@ -2108,6 +2170,13 @@ is_valid_stream_entry(const STREAM_LAYOUT_ENTRY *stream)
                         stream->StreamIdentifierLength / 2);
 }
 
+static bool
+is_object_id_stream(const STREAM_LAYOUT_ENTRY *stream)
+{
+       return stream->StreamIdentifierLength == 24 &&
+               !wmemcmp(stream->StreamIdentifier, L"::$OBJECT_ID", 12);
+}
+
 /*
  * If the specified STREAM_LAYOUT_ENTRY represents a DATA stream as opposed to
  * some other type of NTFS stream such as a STANDARD_INFORMATION stream, return
@@ -2144,10 +2213,12 @@ use_stream(const FILE_LAYOUT_ENTRY *file, const STREAM_LAYOUT_ENTRY *stream,
 
 /* Validate the STREAM_LAYOUT_ENTRYs of the specified file and compute the total
  * length in bytes of the ntfs_stream structures needed to hold the stream
- * information.  */
+ * information.  In addition, set *have_object_id_ret=true if the file has an
+ * object ID stream.  */
 static int
 validate_streams_and_compute_total_length(const FILE_LAYOUT_ENTRY *file,
-                                         size_t *total_length_ret)
+                                         size_t *total_length_ret,
+                                         bool *have_object_id_ret)
 {
        const STREAM_LAYOUT_ENTRY *stream =
                (const void *)file + file->FirstStreamOffset;
@@ -2171,6 +2242,8 @@ validate_streams_and_compute_total_length(const FILE_LAYOUT_ENTRY *file,
                if (use_stream(file, stream, &name, &name_nchars)) {
                        total += ALIGN(sizeof(struct ntfs_stream) +
                                       (name_nchars + 1) * sizeof(wchar_t), 8);
+               } else if (is_object_id_stream(stream)) {
+                       *have_object_id_ret = true;
                }
                if (stream->NextStreamOffset == 0)
                        break;
@@ -2274,6 +2347,7 @@ load_one_file(const FILE_LAYOUT_ENTRY *file, struct ntfs_inode_map *inode_map)
        size_t n;
        int ret;
        void *p;
+       bool have_object_id = false;
 
        inode_size = ALIGN(sizeof(struct ntfs_inode), 8);
 
@@ -2290,7 +2364,8 @@ load_one_file(const FILE_LAYOUT_ENTRY *file, struct ntfs_inode_map *inode_map)
        }
 
        if (file_has_streams(file)) {
-               ret = validate_streams_and_compute_total_length(file, &n);
+               ret = validate_streams_and_compute_total_length(file, &n,
+                                                               &have_object_id);
                if (ret)
                        return ret;
                inode_size += n;
@@ -2308,6 +2383,7 @@ load_one_file(const FILE_LAYOUT_ENTRY *file, struct ntfs_inode_map *inode_map)
        ni->last_write_time = info->BasicInformation.LastWriteTime;
        ni->last_access_time = info->BasicInformation.LastAccessTime;
        ni->security_id = info->SecurityId;
+       ni->have_object_id = have_object_id;
 
        p = FIRST_DENTRY(ni);
 
@@ -2344,7 +2420,7 @@ load_files_from_mft(const wchar_t *path, struct ntfs_inode_map *inode_map)
                         QUERY_FILE_LAYOUT_INCLUDE_STREAMS_WITH_NO_CLUSTERS_ALLOCATED,
                .FilterType = QUERY_FILE_LAYOUT_FILTER_TYPE_NONE,
        };
-       const size_t outsize = 32768;
+       size_t outsize = 32768;
        QUERY_FILE_LAYOUT_OUTPUT *out = NULL;
        int ret;
        NTSTATUS status;
@@ -2356,27 +2432,38 @@ load_files_from_mft(const wchar_t *path, struct ntfs_inode_map *inode_map)
                goto out;
        }
 
-       out = MALLOC(outsize);
-       if (!out) {
-               ret = WIMLIB_ERR_NOMEM;
-               goto out;
-       }
+       for (;;) {
+               /* Allocate a buffer for the output of the ioctl.  */
+               out = MALLOC(outsize);
+               if (!out) {
+                       ret = WIMLIB_ERR_NOMEM;
+                       goto out;
+               }
 
-       while (NT_SUCCESS(status = winnt_fsctl(h, FSCTL_QUERY_FILE_LAYOUT,
-                                              &in, sizeof(in),
-                                              out, outsize, NULL)))
-       {
-               const FILE_LAYOUT_ENTRY *file =
-                       (const void *)out + out->FirstFileOffset;
-               for (;;) {
-                       ret = load_one_file(file, inode_map);
-                       if (ret)
-                               goto out;
-                       if (file->NextFileOffset == 0)
-                               break;
-                       file = (const void *)file + file->NextFileOffset;
+               /* Execute FSCTL_QUERY_FILE_LAYOUT until it fails.  */
+               while (NT_SUCCESS(status = winnt_fsctl(h,
+                                                      FSCTL_QUERY_FILE_LAYOUT,
+                                                      &in, sizeof(in),
+                                                      out, outsize, NULL)))
+               {
+                       const FILE_LAYOUT_ENTRY *file =
+                               (const void *)out + out->FirstFileOffset;
+                       for (;;) {
+                               ret = load_one_file(file, inode_map);
+                               if (ret)
+                                       goto out;
+                               if (file->NextFileOffset == 0)
+                                       break;
+                               file = (const void *)file + file->NextFileOffset;
+                       }
+                       in.Flags &= ~QUERY_FILE_LAYOUT_RESTART;
                }
-               in.Flags &= ~QUERY_FILE_LAYOUT_RESTART;
+
+               /* Enlarge the buffer if needed.  */
+               if (status != STATUS_BUFFER_TOO_SMALL)
+                       break;
+               FREE(out);
+               outsize *= 2;
        }
 
        /* Normally, FSCTL_QUERY_FILE_LAYOUT fails with STATUS_END_OF_FILE after
@@ -2550,7 +2637,8 @@ generate_wim_structures_recursive(struct wim_dentry **root_ret,
         * filter driver (WOF) hides reparse points from regular filesystem APIs
         * but not from FSCTL_QUERY_FILE_LAYOUT.  */
        if (ni->attributes & (FILE_ATTRIBUTE_REPARSE_POINT |
-                             FILE_ATTRIBUTE_ENCRYPTED))
+                             FILE_ATTRIBUTE_ENCRYPTED) ||
+           ni->have_object_id)
        {
                ret = winnt_build_dentry_tree_recursive(&root,
                                                        NULL,
@@ -2717,9 +2805,9 @@ generate_wim_structures_recursive(struct wim_dentry **root_ret,
 out_progress:
        ctx->params->progress.scan.cur_path = path;
        if (likely(root))
-               ret = do_capture_progress(ctx->params, WIMLIB_SCAN_DENTRY_OK, inode);
+               ret = do_scan_progress(ctx->params, WIMLIB_SCAN_DENTRY_OK, inode);
        else
-               ret = do_capture_progress(ctx->params, WIMLIB_SCAN_DENTRY_EXCLUDED, NULL);
+               ret = do_scan_progress(ctx->params, WIMLIB_SCAN_DENTRY_EXCLUDED, NULL);
 out:
        if (--ni->num_aliases == 0) {
                /* Memory usage optimization: when we don't need the ntfs_inode
@@ -2789,7 +2877,7 @@ out:
 int
 win32_build_dentry_tree(struct wim_dentry **root_ret,
                        const wchar_t *root_disk_path,
-                       struct capture_params *params)
+                       struct scan_params *params)
 {
        wchar_t *path = NULL;
        struct winnt_scan_ctx ctx = { .params = params };