]> wimlib.net Git - wimlib/blobdiff - src/win32_capture.c
Generalized support for referencing resources in external WIMs
[wimlib] / src / win32_capture.c
index feb3feaa53795eeb30615504483c287fb51d9f87..14a6c134833e0b47860c60e75321560ea60258ba 100644 (file)
@@ -46,6 +46,18 @@ NtQuerySecurityObject(HANDLE handle,
                      PSECURITY_DESCRIPTOR SecurityDescriptor,
                      ULONG Length,
                      PULONG LengthNeeded);
+NTSTATUS WINAPI
+NtQueryDirectoryFile(HANDLE FileHandle,
+                    HANDLE Event,
+                    PIO_APC_ROUTINE ApcRoutine,
+                    PVOID ApcContext,
+                    PIO_STATUS_BLOCK IoStatusBlock,
+                    PVOID FileInformation,
+                    ULONG Length,
+                    FILE_INFORMATION_CLASS FileInformationClass,
+                    BOOLEAN ReturnSingleEntry,
+                    PUNICODE_STRING FileName,
+                    BOOLEAN RestartScan);
 #endif
 
 #define MAX_GET_SD_ACCESS_DENIED_WARNINGS 1
@@ -75,14 +87,13 @@ read_win32_file_prefix(const struct wim_lookup_table_entry *lte,
 {
        int ret = 0;
        void *out_buf;
-       DWORD err;
        u64 bytes_remaining;
 
-       HANDLE hFile = win32_open_file_data_only(lte->file_on_disk);
+       HANDLE hFile = win32_open_existing_file(lte->file_on_disk,
+                                               FILE_READ_DATA);
        if (hFile == INVALID_HANDLE_VALUE) {
-               err = GetLastError();
-               ERROR("Failed to open \"%ls\"", lte->file_on_disk);
-               win32_error(err);
+               set_errno_from_GetLastError();
+               ERROR_WITH_ERRNO("Failed to open \"%ls\"", lte->file_on_disk);
                return WIMLIB_ERR_OPEN;
        }
 
@@ -99,9 +110,9 @@ read_win32_file_prefix(const struct wim_lookup_table_entry *lte,
                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);
+                       set_errno_from_GetLastError();
+                       ERROR_WITH_ERRNO("Failed to read data from \"%ls\"",
+                                        lte->file_on_disk);
                        ret = WIMLIB_ERR_READ;
                        break;
                }
@@ -205,20 +216,20 @@ read_win32_encrypted_file_prefix(const struct wim_lookup_table_entry *lte,
        export_ctx.buf_filled = 0;
        export_ctx.bytes_remaining = size;
 
-       err = OpenEncryptedFileRawW(lte->file_on_disk, 0, &file_ctx);
+       err = OpenEncryptedFileRaw(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);
+               set_errno_from_win32_error(err);
+               ERROR_WITH_ERRNO("Failed to open encrypted file \"%ls\" "
+                                "for raw read", lte->file_on_disk);
                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);
+               set_errno_from_win32_error(err);
+               ERROR_WITH_ERRNO("Failed to read encrypted file \"%ls\"",
+                                lte->file_on_disk);
                ret = export_ctx.wimlib_err_code;
                if (ret == 0)
                        ret = WIMLIB_ERR_READ;
@@ -244,18 +255,44 @@ FILETIME_to_u64(const FILETIME *ft)
        return ((u64)ft->dwHighDateTime << 32) | (u64)ft->dwLowDateTime;
 }
 
+/* Load the short name of a file into a WIM dentry.
+ *
+ * If we can't read the short filename for some reason, we just ignore the error
+ * and assume the file has no short name.  This shouldn't be an issue, since the
+ * short names are essentially obsolete anyway.
+ */
 static int
-win32_get_short_name(struct wim_dentry *dentry, const wchar_t *path)
+win32_get_short_name(HANDLE hFile, const wchar_t *path, struct wim_dentry *dentry)
 {
+
+       /* It's not any harder to just make the NtQueryInformationFile() system
+        * call ourselves, and it saves a dumb call to FindFirstFile() which of
+        * course has to create its own handle.  */
+#ifdef WITH_NTDLL
+       NTSTATUS status;
+       IO_STATUS_BLOCK io_status;
+       u8 buf[128] _aligned_attribute(8);
+       const FILE_NAME_INFORMATION *info;
+
+       status = NtQueryInformationFile(hFile, &io_status, buf, sizeof(buf),
+                                       FileAlternateNameInformation);
+       info = (const FILE_NAME_INFORMATION*)buf;
+       if (status == STATUS_SUCCESS && info->FileNameLength != 0) {
+               dentry->short_name = MALLOC(info->FileNameLength + 2);
+               if (!dentry->short_name)
+                       return WIMLIB_ERR_NOMEM;
+               memcpy(dentry->short_name, info->FileName,
+                      info->FileNameLength);
+               dentry->short_name[info->FileNameLength / 2] = L'\0';
+               dentry->short_name_nbytes = info->FileNameLength;
+       }
+       return 0;
+#else
        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);
+       hFind = FindFirstFile(path, &dat);
        if (hFind != INVALID_HANDLE_VALUE) {
                if (dat.cAlternateFileName[0] != L'\0') {
                        DEBUG("\"%ls\": short name \"%ls\"", path, dat.cAlternateFileName);
@@ -273,13 +310,14 @@ win32_get_short_name(struct wim_dentry *dentry, const wchar_t *path)
                FindClose(hFind);
        }
        return ret;
+#endif
 }
 
 /*
  * win32_query_security_descriptor() - Query a file's security descriptor
  *
  * We need the file's security descriptor in SECURITY_DESCRIPTOR_RELATIVE
- * relative format, and we currently have a handle opened with as many relevant
+ * format, and we currently have a handle opened with as many relevant
  * permissions as possible.  At this point, on Windows there are a number of
  * options for reading a file's security descriptor:
  *
@@ -305,7 +343,7 @@ win32_get_short_name(struct wim_dentry *dentry, const wchar_t *path)
 static DWORD
 win32_query_security_descriptor(HANDLE hFile, const wchar_t *path,
                                SECURITY_INFORMATION requestedInformation,
-                               PSECURITY_DESCRIPTOR *buf,
+                               SECURITY_DESCRIPTOR *buf,
                                DWORD bufsize, DWORD *lengthNeeded)
 {
 #ifdef WITH_NTDLL
@@ -338,7 +376,7 @@ win32_get_security_descriptor(HANDLE hFile,
                              int add_flags)
 {
        SECURITY_INFORMATION requestedInformation;
-       u8 _buf[1];
+       u8 _buf[4096];
        u8 *buf;
        size_t bufsize;
        DWORD lenNeeded;
@@ -354,7 +392,7 @@ win32_get_security_descriptor(HANDLE hFile,
        for (;;) {
                err = win32_query_security_descriptor(hFile, path,
                                                      requestedInformation,
-                                                     (PSECURITY_DESCRIPTOR)buf,
+                                                     (SECURITY_DESCRIPTOR*)buf,
                                                      bufsize, &lenNeeded);
                switch (err) {
                case ERROR_SUCCESS:
@@ -383,7 +421,7 @@ win32_get_security_descriptor(HANDLE hFile,
                        goto out_free_buf;
                default:
                fail:
-                       errno = win32_error_to_errno(err);
+                       set_errno_from_win32_error(err);
                        ERROR("Failed to read security descriptor of \"%ls\"", path);
                        ret = WIMLIB_ERR_READ;
                        goto out_free_buf;
@@ -392,11 +430,10 @@ win32_get_security_descriptor(HANDLE hFile,
 
 have_descriptor:
        inode->i_security_id = sd_set_add_sd(sd_set, buf, lenNeeded);
-       if (inode->i_security_id < 0) {
+       if (inode->i_security_id < 0)
                ret = WIMLIB_ERR_NOMEM;
-               goto out_free_buf;
-       }
-       ret = 0;
+       else
+               ret = 0;
 out_free_buf:
        if (buf != _buf)
                FREE(buf);
@@ -411,23 +448,99 @@ win32_build_dentry_tree_recursive(struct wim_dentry **root_ret,
                                  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. */
+/* Reads the directory entries of directory and recursively calls
+ * win32_build_dentry_tree() on them.  */
 static int
-win32_recurse_directory(struct wim_dentry *root,
+win32_recurse_directory(HANDLE hDir,
                        wchar_t *dir_path,
                        size_t dir_path_num_chars,
+                       struct wim_dentry *root,
                        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);
 
+       /* Using NtQueryDirectoryFile() we can re-use the same open handle,
+        * which we opened with FILE_FLAG_BACKUP_SEMANTICS (probably not the
+        * case for the FindFirstFile() API; it's not documented).  */
+#ifdef WITH_NTDLL
+       NTSTATUS status;
+       IO_STATUS_BLOCK io_status;
+       const size_t bufsize = 8192;
+       u8 *buf;
+       BOOL restartScan = TRUE;
+       const FILE_NAMES_INFORMATION *info;
+
+       buf = MALLOC(bufsize);
+       if (!buf)
+               return WIMLIB_ERR_NOMEM;
+       for (;;) {
+               status = NtQueryDirectoryFile(hDir, NULL, NULL, NULL,
+                                             &io_status, buf, bufsize,
+                                             FileNamesInformation,
+                                             FALSE, NULL, restartScan);
+               restartScan = FALSE;
+               if (status != STATUS_SUCCESS) {
+                       if (status == STATUS_NO_MORE_FILES ||
+                           status == STATUS_NO_MORE_ENTRIES ||
+                           status == STATUS_NO_MORE_MATCHES) {
+                               ret = 0;
+                       } else {
+                               set_errno_from_nt_status(status);
+                               ERROR_WITH_ERRNO("Failed to read directory "
+                                                "\"%ls\"", dir_path);
+                               ret = WIMLIB_ERR_READ;
+                       }
+                       goto out_free_buf;
+               }
+               wimlib_assert(io_status.Information != 0);
+               info = (const FILE_NAMES_INFORMATION*)buf;
+               for (;;) {
+                       if (!(info->FileNameLength == 2 && info->FileName[0] == L'.') &&
+                           !(info->FileNameLength == 4 && info->FileName[0] == L'.' &&
+                                                          info->FileName[1] == L'.'))
+                       {
+                               wchar_t *p;
+                               struct wim_dentry *child;
+
+                               p = dir_path + dir_path_num_chars;
+                               *p++ = L'\\';
+                               p = wmempcpy(p, info->FileName,
+                                            info->FileNameLength / 2);
+                               *p = '\0';
+
+                               ret = win32_build_dentry_tree_recursive(
+                                                               &child,
+                                                               dir_path,
+                                                               p - dir_path,
+                                                               params,
+                                                               state,
+                                                               vol_flags);
+
+                               dir_path[dir_path_num_chars] = L'\0';
+
+                               if (ret)
+                                       goto out_free_buf;
+                               if (child)
+                                       dentry_add_child(root, child);
+                       }
+                       if (info->NextEntryOffset == 0)
+                               break;
+                       info = (const FILE_NAMES_INFORMATION*)
+                                       ((const u8*)info + info->NextEntryOffset);
+               }
+       }
+out_free_buf:
+       FREE(buf);
+       return ret;
+#else
+       WIN32_FIND_DATAW dat;
+       HANDLE hFind;
+       DWORD err;
+
        /* 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
@@ -435,7 +548,7 @@ win32_recurse_directory(struct wim_dentry *root,
        dir_path[dir_path_num_chars] = OS_PREFERRED_PATH_SEPARATOR;
        dir_path[dir_path_num_chars + 1] = L'*';
        dir_path[dir_path_num_chars + 2] = L'\0';
-       hFind = FindFirstFileW(dir_path, &dat);
+       hFind = FindFirstFile(dir_path, &dat);
        dir_path[dir_path_num_chars] = L'\0';
 
        if (hFind == INVALID_HANDLE_VALUE) {
@@ -443,8 +556,9 @@ win32_recurse_directory(struct wim_dentry *root,
                if (err == ERROR_FILE_NOT_FOUND) {
                        return 0;
                } else {
-                       ERROR("Failed to read directory \"%ls\"", dir_path);
-                       win32_error(err);
+                       set_errno_from_win32_error(err);
+                       ERROR_WITH_ERRNO("Failed to read directory \"%ls\"",
+                                        dir_path);
                        return WIMLIB_ERR_READ;
                }
        }
@@ -476,17 +590,18 @@ win32_recurse_directory(struct wim_dentry *root,
                        goto out_find_close;
                if (child)
                        dentry_add_child(root, child);
-       } while (FindNextFileW(hFind, &dat));
+       } while (FindNextFile(hFind, &dat));
        err = GetLastError();
        if (err != ERROR_NO_MORE_FILES) {
-               ERROR("Failed to read directory \"%ls\"", dir_path);
-               win32_error(err);
+               set_errno_from_win32_error(err);
+               ERROR_WITH_ERRNO("Failed to read directory \"%ls\"", dir_path);
                if (ret == 0)
                        ret = WIMLIB_ERR_READ;
        }
 out_find_close:
        FindClose(hFind);
        return ret;
+#endif
 }
 
 /* Reparse point fixup status code */
@@ -664,9 +779,8 @@ win32_get_reparse_data(HANDLE hFile, const wchar_t *path,
                             &bytesReturned,
                             NULL))
        {
-               DWORD err = GetLastError();
-               ERROR("Failed to get reparse data of \"%ls\"", path);
-               win32_error(err);
+               set_errno_from_GetLastError();
+               ERROR_WITH_ERRNO("Failed to get reparse data of \"%ls\"", path);
                return -WIMLIB_ERR_READ;
        }
        if (bytesReturned < 8 || bytesReturned > REPARSE_POINT_MAX_SIZE) {
@@ -694,10 +808,10 @@ win32_get_reparse_data(HANDLE hFile, const wchar_t *path,
 }
 
 static DWORD WINAPI
-win32_tally_encrypted_size_cb(unsigned char *_data, void *_ctx,
+win32_tally_encrypted_size_cb(unsigned char *_data, void *_size_ret,
                              unsigned long len)
 {
-       *(u64*)_ctx += len;
+       *(u64*)_size_ret += len;
        return ERROR_SUCCESS;
 }
 
@@ -708,18 +822,20 @@ win32_get_encrypted_file_size(const wchar_t *path, u64 *size_ret)
        void *file_ctx;
        int ret;
 
-       *size_ret = 0;
-       err = OpenEncryptedFileRawW(path, 0, &file_ctx);
+       err = OpenEncryptedFileRaw(path, 0, &file_ctx);
        if (err != ERROR_SUCCESS) {
-               ERROR("Failed to open encrypted file \"%ls\" for raw read", path);
-               win32_error(err);
+               set_errno_from_win32_error(err);
+               ERROR_WITH_ERRNO("Failed to open encrypted file \"%ls\" "
+                                "for raw read", path);
                return WIMLIB_ERR_OPEN;
        }
+       *size_ret = 0;
        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);
+               set_errno_from_win32_error(err);
+               ERROR_WITH_ERRNO("Failed to read raw encrypted data from "
+                                "\"%ls\"", path);
                ret = WIMLIB_ERR_READ;
        } else {
                ret = 0;
@@ -829,7 +945,7 @@ win32_capture_stream(const wchar_t *path,
        spath_buf_nbytes = (spath_nchars + 1) * sizeof(wchar_t);
        spath = MALLOC(spath_buf_nbytes);
 
-       swprintf(spath, L"%ls%ls%ls%ls",
+       tsprintf(spath, L"%ls%ls%ls%ls",
                 relpath_prefix, path, colonchar, stream_name);
 
        /* Make a new wim_lookup_table_entry */
@@ -886,7 +1002,7 @@ out_invalid_stream_name:
  *   already present in Windows XP.
  */
 static int
-win32_capture_streams(HANDLE hFile,
+win32_capture_streams(HANDLE *hFile_p,
                      const wchar_t *path,
                      size_t path_num_chars,
                      struct wim_inode *inode,
@@ -923,7 +1039,7 @@ win32_capture_streams(HANDLE hFile,
 
        /* Get a buffer containing the stream information.  */
        for (;;) {
-               status = NtQueryInformationFile(hFile, &io_status, buf, bufsize,
+               status = NtQueryInformationFile(*hFile_p, &io_status, buf, bufsize,
                                                FileStreamInformation);
                if (status == STATUS_SUCCESS) {
                        break;
@@ -942,7 +1058,7 @@ win32_capture_streams(HANDLE hFile,
                        }
                        buf = newbuf;
                } else {
-                       errno = win32_error_to_errno(RtlNtStatusToDosError(status));
+                       set_errno_from_nt_status(status);
                        ERROR_WITH_ERRNO("Failed to read streams of %ls", path);
                        ret = WIMLIB_ERR_READ;
                        goto out_free_buf;
@@ -955,6 +1071,14 @@ win32_capture_streams(HANDLE hFile,
                goto out_free_buf;
        }
 
+       if (inode->i_attributes & FILE_ATTRIBUTE_ENCRYPTED) {
+               /* OpenEncryptedFileRaw() seems to fail with
+                * ERROR_SHARING_VIOLATION if there are any handles opened to
+                * the file.  */
+               CloseHandle(*hFile_p);
+               *hFile_p = INVALID_HANDLE_VALUE;
+       }
+
        /* Parse one or more stream information structures.  */
        info = (const FILE_STREAM_INFORMATION*)buf;
        for (;;) {
@@ -1006,9 +1130,9 @@ out_free_buf:
                                        path, capture_access_denied_msg);
                                return 0;
                        } else {
-                               ERROR("Failed to look up data streams "
-                                     "of \"%ls\"", path);
-                               win32_error(err);
+                               set_errno_from_win32_error(err);
+                               ERROR_WITH_ERRNO("Failed to look up data streams "
+                                                "of \"%ls\"", path);
                                return WIMLIB_ERR_READ;
                        }
                }
@@ -1023,8 +1147,9 @@ out_free_buf:
        } 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);
+               set_errno_from_win32_error(err);
+               ERROR_WITH_ERRNO("Error reading data streams from "
+                                "\"%ls\"", path);
                ret = WIMLIB_ERR_READ;
        }
 out_find_close:
@@ -1033,7 +1158,7 @@ out_find_close:
 #endif /* !WITH_NTDLL */
 
 unnamed_only:
-       /* FindFirstStreamW() API is not available, or the volume does not
+       /* FindFirstStream() 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_DIRECTORY |
@@ -1163,7 +1288,7 @@ again:
        if (ret)
                goto out_close_handle;
 
-       ret = win32_get_short_name(root, path);
+       ret = win32_get_short_name(hFile, path, root);
        if (ret)
                goto out_close_handle;
 
@@ -1196,7 +1321,7 @@ again:
 
        /* Capture the unnamed data stream (only should be present for regular
         * files) and any alternate data streams. */
-       ret = win32_capture_streams(hFile,
+       ret = win32_capture_streams(&hFile,
                                    path,
                                    path_num_chars,
                                    inode,
@@ -1206,8 +1331,6 @@ again:
        if (ret)
                goto out_close_handle;
 
-       CloseHandle(hFile);
-
        if (inode->i_attributes & FILE_ATTRIBUTE_REPARSE_POINT) {
                /* Reparse point: set the reparse data (which we read already)
                 * */
@@ -1217,14 +1340,27 @@ again:
                                               params->lookup_table);
        } else if (inode->i_attributes & FILE_ATTRIBUTE_DIRECTORY) {
                /* Directory (not a reparse point) --- recurse to children */
-               ret = win32_recurse_directory(root,
+
+               if (hFile == INVALID_HANDLE_VALUE) {
+                       /* Re-open handle that was closed to read raw encrypted
+                        * data.  */
+                       hFile = win32_open_existing_file(path, FILE_READ_DATA);
+                       if (hFile == INVALID_HANDLE_VALUE) {
+                               set_errno_from_GetLastError();
+                               ERROR_WITH_ERRNO("Failed to reopen \"%ls\"",
+                                                path);
+                               ret = WIMLIB_ERR_OPEN;
+                               goto out_close_handle;
+                       }
+               }
+               ret = win32_recurse_directory(hFile,
                                              path,
                                              path_num_chars,
+                                             root,
                                              params,
                                              state,
                                              vol_flags);
        }
-       goto out;
 out_close_handle:
        CloseHandle(hFile);
 out:
@@ -1286,19 +1422,13 @@ win32_build_dentry_tree(struct wim_dentry **root_ret,
        if (path_nchars > WINDOWS_NT_MAX_PATH)
                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,
                                         &params->capture_root_ino,
                                         &params->capture_root_dev);
-       if (ret)
+       if (ret) {
+               ERROR_WITH_ERRNO("Can't open %ls", root_disk_path);
                return ret;
+       }
 
        win32_get_vol_flags(root_disk_path, &vol_flags, NULL);