483518c99bab9c4c0609d9d39ae62f5c5fa5099f
[wimlib] / src / win32.c
1 /*
2  * win32.c
3  *
4  * All the code specific to native Windows builds is in here.
5  */
6
7 /*
8  * Copyright (C) 2013 Eric Biggers
9  *
10  * This file is part of wimlib, a library for working with WIM files.
11  *
12  * wimlib is free software; you can redistribute it and/or modify it under the
13  * terms of the GNU General Public License as published by the Free
14  * Software Foundation; either version 3 of the License, or (at your option)
15  * any later version.
16  *
17  * wimlib is distributed in the hope that it will be useful, but WITHOUT ANY
18  * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
19  * A PARTICULAR PURPOSE. See the GNU General Public License for more
20  * details.
21  *
22  * You should have received a copy of the GNU General Public License
23  * along with wimlib; if not, see http://www.gnu.org/licenses/.
24  */
25
26 #ifndef __WIN32__
27 #  error "This file contains Windows code"
28 #endif
29
30 #include "config.h"
31 #include <windows.h>
32 #include <ntdef.h>
33 #include <wchar.h>
34 #include <shlwapi.h>
35 #ifdef ERROR
36 #  undef ERROR
37 #endif
38
39
40 /* Microsoft's swprintf() violates the C standard and they require programmers
41  * to do this weird define to get the correct function.  */
42 #define swprintf _snwprintf
43
44 #include "win32.h"
45 #include "dentry.h"
46 #include "lookup_table.h"
47 #include "security.h"
48
49 #include <errno.h>
50
51 #ifdef ENABLE_ERROR_MESSAGES
52 void win32_error(u32 err_code)
53 {
54         char *buffer;
55         DWORD nchars;
56         nchars = FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER,
57                                 NULL, err_code, 0,
58                                 (char*)&buffer, 0, NULL);
59         if (nchars == 0) {
60                 ERROR("Error printing error message! "
61                       "Computer will self-destruct in 3 seconds.");
62         } else {
63                 ERROR("Win32 error: %s", buffer);
64                 LocalFree(buffer);
65         }
66 }
67 #else
68 #define win32_error(err_code)
69 #endif
70
71 void *win32_open_file_readonly(const void *path)
72 {
73         return CreateFileW((const wchar_t*)path,
74                            FILE_READ_DATA |
75                                FILE_READ_ATTRIBUTES |
76                                READ_CONTROL |
77                                ACCESS_SYSTEM_SECURITY,
78                            FILE_SHARE_READ,
79                            NULL, /* lpSecurityAttributes */
80                            OPEN_EXISTING,
81                            FILE_FLAG_BACKUP_SEMANTICS |
82                                FILE_FLAG_OPEN_REPARSE_POINT,
83                            NULL /* hTemplateFile */);
84 }
85
86 int win32_read_file(const char *filename,
87                     void *handle, u64 offset, size_t size, u8 *buf)
88 {
89         HANDLE h = handle;
90         DWORD err;
91         DWORD bytesRead;
92         LARGE_INTEGER liOffset = {.QuadPart = offset};
93
94         wimlib_assert(size <= 0xffffffff);
95
96         if (SetFilePointerEx(h, liOffset, NULL, FILE_BEGIN))
97                 if (ReadFile(h, buf, size, &bytesRead, NULL) && bytesRead == size)
98                         return 0;
99         err = GetLastError();
100         ERROR("Error reading \"%s\"", filename);
101         win32_error(err);
102         return WIMLIB_ERR_READ;
103 }
104
105 void win32_close_file(void *handle)
106 {
107         CloseHandle((HANDLE)handle);
108 }
109
110 static bool win32_modify_privilege(const char *privilege, bool enable)
111 {
112         HANDLE hToken;
113         LUID luid;
114         TOKEN_PRIVILEGES newState;
115         bool ret = false;
116
117         DEBUG("%s privilege %s",
118               enable ? "Enabling" : "Disabling", privilege);
119
120         if (!OpenProcessToken(GetCurrentProcess(),
121                               TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY,
122                               &hToken))
123         {
124                 DEBUG("OpenProcessToken() failed");
125                 goto out;
126         }
127
128         if (!LookupPrivilegeValue(NULL, privilege, &luid)) {
129                 DEBUG("LookupPrivilegeValue() failed");
130                 goto out;
131         }
132
133         newState.PrivilegeCount = 1;
134         newState.Privileges[0].Luid = luid;
135         newState.Privileges[0].Attributes = (enable ? SE_PRIVILEGE_ENABLED : 0);
136         ret = AdjustTokenPrivileges(hToken, FALSE, &newState, 0, NULL, NULL);
137         if (!ret)
138                 DEBUG("AdjustTokenPrivileges() failed");
139         CloseHandle(hToken);
140 out:
141         if (!ret) {
142                 DWORD err = GetLastError();
143                 win32_error(err);
144                 WARNING("Failed to %s privilege %s",
145                         enable ? "enable" : "disable", privilege);
146                 WARNING("The program will continue, but if permission issues are "
147                         "encountered, you may need to run this program as the administrator");
148         }
149         return ret;
150 }
151
152 static bool win32_acquire_privilege(const char *privilege)
153 {
154         return win32_modify_privilege(privilege, true);
155 }
156
157 static bool win32_release_privilege(const char *privilege)
158 {
159         return win32_modify_privilege(privilege, false);
160 }
161
162
163 void win32_acquire_capture_privileges()
164 {
165         win32_acquire_privilege(SE_BACKUP_NAME);
166         win32_acquire_privilege(SE_SECURITY_NAME);
167 }
168
169 void win32_release_capture_privileges()
170 {
171         win32_release_privilege(SE_BACKUP_NAME);
172         win32_release_privilege(SE_SECURITY_NAME);
173 }
174
175 void win32_acquire_restore_privileges()
176 {
177         win32_acquire_privilege(SE_RESTORE_NAME);
178         win32_acquire_privilege(SE_SECURITY_NAME);
179         win32_acquire_privilege(SE_TAKE_OWNERSHIP_NAME);
180 }
181
182 void win32_release_restore_privileges()
183 {
184         win32_release_privilege(SE_RESTORE_NAME);
185         win32_release_privilege(SE_SECURITY_NAME);
186         win32_release_privilege(SE_TAKE_OWNERSHIP_NAME);
187 }
188
189 static u64 FILETIME_to_u64(const FILETIME *ft)
190 {
191         return ((u64)ft->dwHighDateTime << 32) | (u64)ft->dwLowDateTime;
192 }
193
194
195 int win32_build_dentry_tree(struct wim_dentry **root_ret,
196                             const char *root_disk_path,
197                             struct wim_lookup_table *lookup_table,
198                             struct wim_security_data *sd,
199                             const struct capture_config *config,
200                             int add_image_flags,
201                             wimlib_progress_func_t progress_func,
202                             void *extra_arg);
203
204 static int win32_get_short_name(struct wim_dentry *dentry,
205                                 const wchar_t *path_utf16)
206 {
207         WIN32_FIND_DATAW dat;
208         if (FindFirstFileW(path_utf16, &dat) &&
209             dat.cAlternateFileName[0] != L'\0')
210         {
211                 size_t short_name_len = wcslen(dat.cAlternateFileName) * 2;
212                 size_t n = short_name_len + sizeof(wchar_t);
213                 dentry->short_name = MALLOC(n);
214                 if (!dentry->short_name)
215                         return WIMLIB_ERR_NOMEM;
216                 memcpy(dentry->short_name, dat.cAlternateFileName, n);
217                 dentry->short_name_len = short_name_len;
218         }
219         return 0;
220 }
221
222 static int win32_get_security_descriptor(struct wim_dentry *dentry,
223                                          struct sd_set *sd_set,
224                                          const wchar_t *path_utf16)
225 {
226         SECURITY_INFORMATION requestedInformation;
227         DWORD lenNeeded = 0;
228         BOOL status;
229         DWORD err;
230
231         requestedInformation = DACL_SECURITY_INFORMATION |
232                                SACL_SECURITY_INFORMATION |
233                                OWNER_SECURITY_INFORMATION |
234                                GROUP_SECURITY_INFORMATION;
235         /* Request length of security descriptor */
236         status = GetFileSecurityW(path_utf16, requestedInformation,
237                                   NULL, 0, &lenNeeded);
238         err = GetLastError();
239         if (!status && err == ERROR_INSUFFICIENT_BUFFER) {
240                 DWORD len = lenNeeded;
241                 char buf[len];
242                 if (GetFileSecurityW(path_utf16, requestedInformation,
243                                      (PSECURITY_DESCRIPTOR)buf, len, &lenNeeded))
244                 {
245                         int security_id = sd_set_add_sd(sd_set, buf, len);
246                         if (security_id < 0)
247                                 return WIMLIB_ERR_NOMEM;
248                         else {
249                                 dentry->d_inode->i_security_id = security_id;
250                                 return 0;
251                         }
252                 } else {
253                         err = GetLastError();
254                 }
255         }
256         ERROR("Win32 API: Failed to read security descriptor of \"%ls\"",
257               path_utf16);
258         win32_error(err);
259         return WIMLIB_ERR_READ;
260 }
261
262 /* Reads the directory entries of directory using a Win32 API and recursively
263  * calls build_dentry_tree() on them. */
264 static int win32_recurse_directory(struct wim_dentry *root,
265                                    const char *root_disk_path,
266                                    struct wim_lookup_table *lookup_table,
267                                    struct wim_security_data *sd,
268                                    const struct capture_config *config,
269                                    int add_image_flags,
270                                    wimlib_progress_func_t progress_func,
271                                    struct sd_set *sd_set,
272                                    const wchar_t *path_utf16,
273                                    size_t path_utf16_nchars)
274 {
275         WIN32_FIND_DATAW dat;
276         HANDLE hFind;
277         DWORD err;
278         int ret;
279
280         {
281                 /* Begin reading the directory by calling FindFirstFileW.
282                  * Unlike UNIX opendir(), FindFirstFileW has file globbing built
283                  * into it.  But this isn't what we actually want, so just add a
284                  * dummy glob to get all entries. */
285                 wchar_t pattern_buf[path_utf16_nchars + 3];
286                 memcpy(pattern_buf, path_utf16,
287                        path_utf16_nchars * sizeof(wchar_t));
288                 pattern_buf[path_utf16_nchars] = L'/';
289                 pattern_buf[path_utf16_nchars + 1] = L'*';
290                 pattern_buf[path_utf16_nchars + 2] = L'\0';
291                 hFind = FindFirstFileW(pattern_buf, &dat);
292         }
293         if (hFind == INVALID_HANDLE_VALUE) {
294                 err = GetLastError();
295                 if (err == ERROR_FILE_NOT_FOUND) {
296                         return 0;
297                 } else {
298                         ERROR("Win32 API: Failed to read directory \"%s\"",
299                               root_disk_path);
300                         win32_error(err);
301                         return WIMLIB_ERR_READ;
302                 }
303         }
304         ret = 0;
305         do {
306                 /* Skip . and .. entries */
307                 if (!(dat.cFileName[0] == L'.' &&
308                       (dat.cFileName[1] == L'\0' ||
309                        (dat.cFileName[1] == L'.' && dat.cFileName[2] == L'\0'))))
310                 {
311                         struct wim_dentry *child;
312
313                         char *utf8_name;
314                         size_t utf8_name_nbytes;
315                         ret = utf16_to_utf8((const char*)dat.cFileName,
316                                             wcslen(dat.cFileName) * sizeof(wchar_t),
317                                             &utf8_name,
318                                             &utf8_name_nbytes);
319                         if (ret)
320                                 goto out_find_close;
321
322                         char name[strlen(root_disk_path) + 1 + utf8_name_nbytes + 1];
323                         sprintf(name, "%s/%s", root_disk_path, utf8_name);
324                         FREE(utf8_name);
325                         ret = win32_build_dentry_tree(&child, name, lookup_table,
326                                                       sd, config, add_image_flags,
327                                                       progress_func, sd_set);
328                         if (ret)
329                                 goto out_find_close;
330                         if (child)
331                                 dentry_add_child(root, child);
332                 }
333         } while (FindNextFileW(hFind, &dat));
334         err = GetLastError();
335         if (err != ERROR_NO_MORE_FILES) {
336                 ERROR("Win32 API: Failed to read directory \"%s\"", root_disk_path);
337                 win32_error(err);
338                 if (ret == 0)
339                         ret = WIMLIB_ERR_READ;
340         }
341 out_find_close:
342         FindClose(hFind);
343         return ret;
344 }
345
346 /* Load a reparse point into a WIM inode.  It is just stored in memory.
347  *
348  * @hFile:  Open handle to a reparse point, with permission to read the reparse
349  *          data.
350  *
351  * @inode:  WIM inode for the reparse point.
352  *
353  * @lookup_table:  Stream lookup table for the WIM; an entry will be added to it
354  *                 for the reparse point unless an entry already exists for
355  *                 the exact same data stream.
356  *
357  * @path:  External path to the parse point (UTF-8).  Used for error messages
358  *         only.
359  *
360  * Returns 0 on success; nonzero on failure. */
361 static int win32_capture_reparse_point(HANDLE hFile,
362                                        struct wim_inode *inode,
363                                        struct wim_lookup_table *lookup_table,
364                                        const char *path)
365 {
366         /* "Reparse point data, including the tag and optional GUID,
367          * cannot exceed 16 kilobytes." - MSDN  */
368         char reparse_point_buf[16 * 1024];
369         DWORD bytesReturned;
370
371         if (!DeviceIoControl(hFile, FSCTL_GET_REPARSE_POINT,
372                              NULL, 0, reparse_point_buf,
373                              sizeof(reparse_point_buf), &bytesReturned, NULL))
374         {
375                 DWORD err = GetLastError();
376                 ERROR("Win32 API: Failed to get reparse data of \"%s\"", path);
377                 win32_error(err);
378                 return WIMLIB_ERR_READ;
379         }
380         if (bytesReturned < 8) {
381                 ERROR("Reparse data on \"%s\" is invalid", path);
382                 return WIMLIB_ERR_READ;
383         }
384         inode->i_reparse_tag = *(u32*)reparse_point_buf;
385         return inode_add_ads_with_data(inode, "",
386                                        (const u8*)reparse_point_buf + 8,
387                                        bytesReturned - 8, lookup_table);
388 }
389
390 /* Calculate the SHA1 message digest of a Win32 data stream, which may be either
391  * an unnamed or named data stream.
392  *
393  * @path:       Path to the file, with the stream noted at the end for named
394  *              streams.  UTF-16LE encoding.
395  *
396  * @hash:       On success, the SHA1 message digest of the stream is written to
397  *              this location.
398  *
399  * Returns 0 on success; nonzero on failure.
400  */
401 static int win32_sha1sum(const wchar_t *path, u8 hash[SHA1_HASH_SIZE])
402 {
403         HANDLE hFile;
404         SHA_CTX ctx;
405         u8 buf[32768];
406         DWORD bytesRead;
407         int ret;
408
409         hFile = win32_open_file_readonly(path);
410         if (hFile == INVALID_HANDLE_VALUE)
411                 return WIMLIB_ERR_OPEN;
412
413         sha1_init(&ctx);
414         for (;;) {
415                 if (!ReadFile(hFile, buf, sizeof(buf), &bytesRead, NULL)) {
416                         ret = WIMLIB_ERR_READ;
417                         goto out_close_handle;
418                 }
419                 if (bytesRead == 0)
420                         break;
421                 sha1_update(&ctx, buf, bytesRead);
422         }
423         ret = 0;
424         sha1_final(hash, &ctx);
425 out_close_handle:
426         CloseHandle(hFile);
427         return ret;
428 }
429
430 /* Scans an unnamed or named stream of a Win32 file (not a reparse point
431  * stream); calculates its SHA1 message digest and either creates a `struct
432  * wim_lookup_table_entry' in memory for it, or uses an existing 'struct
433  * wim_lookup_table_entry' for an identical stream.
434  *
435  * @path_utf16:         Path to the file (UTF-16LE).
436  *
437  * @path_utf16_nchars:  Number of 2-byte characters in @path_utf16.
438  *
439  * @inode:              WIM inode to save the stream into.
440  *
441  * @lookup_table:       Stream lookup table for the WIM.
442  *
443  * @dat:                A `WIN32_FIND_STREAM_DATA' structure that specifies the
444  *                      stream name.
445  *
446  * Returns 0 on success; nonzero on failure.
447  */
448 static int win32_capture_stream(const wchar_t *path_utf16,
449                                 size_t path_utf16_nchars,
450                                 struct wim_inode *inode,
451                                 struct wim_lookup_table *lookup_table,
452                                 WIN32_FIND_STREAM_DATA *dat)
453 {
454         struct wim_ads_entry *ads_entry;
455         u8 hash[SHA1_HASH_SIZE];
456         struct wim_lookup_table_entry *lte;
457         int ret;
458         wchar_t *p, *colon;
459         bool is_named_stream;
460         wchar_t *spath;
461         size_t spath_nchars;
462         DWORD err;
463
464         /* The stream name should be returned as :NAME:TYPE */
465         p = dat->cStreamName;
466         if (*p != L':')
467                 goto out_invalid_stream_name;
468         p += 1;
469         colon = wcschr(p, L':');
470         if (colon == NULL)
471                 goto out_invalid_stream_name;
472
473         if (wcscmp(colon + 1, L"$DATA")) {
474                 /* Not a DATA stream */
475                 ret = 0;
476                 goto out;
477         }
478
479         is_named_stream = (p != colon);
480         if (is_named_stream) {
481                 /* Allocate an ADS entry for the named stream. */
482                 char *utf8_stream_name;
483                 size_t utf8_stream_name_len;
484                 ret = utf16_to_utf8((const char *)p,
485                                     (colon - p) * sizeof(wchar_t),
486                                     &utf8_stream_name,
487                                     &utf8_stream_name_len);
488                 if (ret)
489                         goto out;
490                 ads_entry = inode_add_ads(inode, utf8_stream_name);
491                 FREE(utf8_stream_name);
492                 if (!ads_entry) {
493                         ret = WIMLIB_ERR_NOMEM;
494                         goto out;
495                 }
496         }
497
498         /* Create a UTF-16 string @spath that gives the filename, then a colon,
499          * then the stream name.  Or, if it's an unnamed stream, just the
500          * filename.  It is MALLOC()'ed so that it can be saved in the
501          * wim_lookup_table_entry if needed. */
502         *colon = '\0';
503         spath_nchars = path_utf16_nchars;
504         if (is_named_stream)
505                 spath_nchars += colon - p + 1;
506
507         spath = MALLOC((spath_nchars + 1) * sizeof(wchar_t));
508         memcpy(spath, path_utf16, path_utf16_nchars * sizeof(wchar_t));
509         if (is_named_stream) {
510                 spath[path_utf16_nchars] = L':';
511                 memcpy(&spath[path_utf16_nchars + 1], p, (colon - p) * sizeof(wchar_t));
512         }
513         spath[spath_nchars] = L'\0';
514
515         ret = win32_sha1sum(spath, hash);
516         if (ret) {
517                 err = GetLastError();
518                 ERROR("Win32 API: Failed to read \"%ls\" to calculate SHA1sum",
519                       path_utf16);
520                 win32_error(err);
521                 goto out_free_spath;
522         }
523
524         lte = __lookup_resource(lookup_table, hash);
525         if (lte) {
526                 /* Use existing wim_lookup_table_entry that has the same SHA1
527                  * message digest */
528                 lte->refcnt++;
529         } else {
530                 /* Make a new wim_lookup_table_entry */
531                 lte = new_lookup_table_entry();
532                 if (!lte) {
533                         ret = WIMLIB_ERR_NOMEM;
534                         goto out_free_spath;
535                 }
536                 lte->file_on_disk = (char*)spath;
537                 spath = NULL;
538                 lte->resource_location = RESOURCE_WIN32;
539                 lte->resource_entry.original_size = (uint64_t)dat->StreamSize.QuadPart;
540                 lte->resource_entry.size = (uint64_t)dat->StreamSize.QuadPart;
541                 copy_hash(lte->hash, hash);
542                 lookup_table_insert(lookup_table, lte);
543         }
544         if (is_named_stream)
545                 ads_entry->lte = lte;
546         else
547                 inode->i_lte = lte;
548 out_free_spath:
549         FREE(spath);
550 out:
551         return ret;
552 out_invalid_stream_name:
553         ERROR("Invalid stream name: \"%ls:%ls\"", path_utf16, dat->cStreamName);
554         ret = WIMLIB_ERR_READ;
555         goto out;
556 }
557
558 /* Scans a Win32 file for unnamed and named data streams (not reparse point
559  * streams).
560  *
561  * @path_utf16:         Path to the file (UTF-16LE).
562  *
563  * @path_utf16_nchars:  Number of 2-byte characters in @path_utf16.
564  *
565  * @inode:              WIM inode to save the stream into.
566  *
567  * @lookup_table:       Stream lookup table for the WIM.
568  *
569  * Returns 0 on success; nonzero on failure.
570  */
571 static int win32_capture_streams(const wchar_t *path_utf16,
572                                  size_t path_utf16_nchars,
573                                  struct wim_inode *inode,
574                                  struct wim_lookup_table *lookup_table)
575 {
576         WIN32_FIND_STREAM_DATA dat;
577         int ret;
578         HANDLE hFind;
579         DWORD err;
580
581         hFind = FindFirstStreamW(path_utf16, FindStreamInfoStandard, &dat, 0);
582         if (hFind == INVALID_HANDLE_VALUE) {
583                 err = GetLastError();
584
585                 /* Seems legal for this to return ERROR_HANDLE_EOF on reparse
586                  * points and directories */
587                 if ((inode->i_attributes &
588                     (FILE_ATTRIBUTE_REPARSE_POINT | FILE_ATTRIBUTE_DIRECTORY))
589                     && err == ERROR_HANDLE_EOF)
590                 {
591                         return 0;
592                 } else {
593                         ERROR("Win32 API: Failed to look up data streams of \"%ls\"",
594                               path_utf16);
595                         win32_error(err);
596                         return WIMLIB_ERR_READ;
597                 }
598         }
599         do {
600                 ret = win32_capture_stream(path_utf16,
601                                            path_utf16_nchars,
602                                            inode, lookup_table,
603                                            &dat);
604                 if (ret)
605                         goto out_find_close;
606         } while (FindNextStreamW(hFind, &dat));
607         err = GetLastError();
608         if (err != ERROR_HANDLE_EOF) {
609                 ERROR("Win32 API: Error reading data streams from \"%ls\"", path_utf16);
610                 win32_error(err);
611                 ret = WIMLIB_ERR_READ;
612         }
613 out_find_close:
614         FindClose(hFind);
615         return ret;
616 }
617
618 /* Win32 version of capturing a directory tree */
619 int win32_build_dentry_tree(struct wim_dentry **root_ret,
620                             const char *root_disk_path,
621                             struct wim_lookup_table *lookup_table,
622                             struct wim_security_data *sd,
623                             const struct capture_config *config,
624                             int add_image_flags,
625                             wimlib_progress_func_t progress_func,
626                             void *extra_arg)
627 {
628         struct wim_dentry *root = NULL;
629         int ret = 0;
630         struct wim_inode *inode;
631
632         wchar_t *path_utf16;
633         size_t path_utf16_nchars;
634         struct sd_set *sd_set;
635         DWORD err;
636
637         if (exclude_path(root_disk_path, config, true)) {
638                 if (add_image_flags & WIMLIB_ADD_IMAGE_FLAG_ROOT) {
639                         ERROR("Cannot exclude the root directory from capture");
640                         ret = WIMLIB_ERR_INVALID_CAPTURE_CONFIG;
641                         goto out;
642                 }
643                 if ((add_image_flags & WIMLIB_ADD_IMAGE_FLAG_VERBOSE)
644                     && progress_func)
645                 {
646                         union wimlib_progress_info info;
647                         info.scan.cur_path = root_disk_path;
648                         info.scan.excluded = true;
649                         progress_func(WIMLIB_PROGRESS_MSG_SCAN_DENTRY, &info);
650                 }
651                 goto out;
652         }
653
654         if ((add_image_flags & WIMLIB_ADD_IMAGE_FLAG_VERBOSE)
655             && progress_func)
656         {
657                 union wimlib_progress_info info;
658                 info.scan.cur_path = root_disk_path;
659                 info.scan.excluded = false;
660                 progress_func(WIMLIB_PROGRESS_MSG_SCAN_DENTRY, &info);
661         }
662
663         if (extra_arg == NULL) {
664                 sd_set = alloca(sizeof(struct sd_set));
665                 sd_set->rb_root.rb_node = NULL,
666                 sd_set->sd = sd;
667         } else {
668                 sd_set = extra_arg;
669         }
670
671         ret = utf8_to_utf16(root_disk_path, strlen(root_disk_path),
672                             (char**)&path_utf16, &path_utf16_nchars);
673         if (ret)
674                 goto out_destroy_sd_set;
675         path_utf16_nchars /= sizeof(wchar_t);
676
677         HANDLE hFile = win32_open_file_readonly(path_utf16);
678         if (hFile == INVALID_HANDLE_VALUE) {
679                 err = GetLastError();
680                 ERROR("Win32 API: Failed to open \"%s\"", root_disk_path);
681                 win32_error(err);
682                 ret = WIMLIB_ERR_OPEN;
683                 goto out_free_path_utf16;
684         }
685
686         BY_HANDLE_FILE_INFORMATION file_info;
687         if (!GetFileInformationByHandle(hFile, &file_info)) {
688                 err = GetLastError();
689                 ERROR("Win32 API: Failed to get file information for \"%s\"",
690                       root_disk_path);
691                 win32_error(err);
692                 ret = WIMLIB_ERR_STAT;
693                 goto out_close_handle;
694         }
695
696         /* Create a WIM dentry */
697         root = new_dentry_with_timeless_inode(path_basename(root_disk_path));
698         if (!root) {
699                 if (errno == EILSEQ)
700                         ret = WIMLIB_ERR_INVALID_UTF8_STRING;
701                 else if (errno == ENOMEM)
702                         ret = WIMLIB_ERR_NOMEM;
703                 else
704                         ret = WIMLIB_ERR_ICONV_NOT_AVAILABLE;
705                 goto out_close_handle;
706         }
707
708         /* Start preparing the associated WIM inode */
709         inode = root->d_inode;
710
711         inode->i_attributes = file_info.dwFileAttributes;
712         inode->i_creation_time = FILETIME_to_u64(&file_info.ftCreationTime);
713         inode->i_last_write_time = FILETIME_to_u64(&file_info.ftLastWriteTime);
714         inode->i_last_access_time = FILETIME_to_u64(&file_info.ftLastAccessTime);
715         inode->i_ino = ((u64)file_info.nFileIndexHigh << 32) |
716                         (u64)file_info.nFileIndexLow;
717
718         inode->i_resolved = 1;
719         add_image_flags &= ~(WIMLIB_ADD_IMAGE_FLAG_ROOT | WIMLIB_ADD_IMAGE_FLAG_SOURCE);
720
721         /* Get DOS name and security descriptor (if any). */
722         ret = win32_get_short_name(root, path_utf16);
723         if (ret)
724                 goto out_close_handle;
725         ret = win32_get_security_descriptor(root, sd_set, path_utf16);
726         if (ret)
727                 goto out_close_handle;
728
729         if (inode_is_directory(inode)) {
730                 /* Directory (not a reparse point) --- recurse to children */
731
732                 /* But first... directories may have alternate data streams that
733                  * need to be captured. */
734                 ret = win32_capture_streams(path_utf16,
735                                             path_utf16_nchars,
736                                             inode,
737                                             lookup_table);
738                 if (ret)
739                         goto out_close_handle;
740                 ret = win32_recurse_directory(root,
741                                               root_disk_path,
742                                               lookup_table,
743                                               sd,
744                                               config,
745                                               add_image_flags,
746                                               progress_func,
747                                               sd_set,
748                                               path_utf16,
749                                               path_utf16_nchars);
750         } else if (inode->i_attributes & FILE_ATTRIBUTE_REPARSE_POINT) {
751                 /* Reparse point: save the reparse tag and data */
752                 ret = win32_capture_reparse_point(hFile,
753                                                   inode,
754                                                   lookup_table,
755                                                   root_disk_path);
756         } else {
757                 /* Not a directory, not a reparse point; capture the default
758                  * file contents and any alternate data streams. */
759                 ret = win32_capture_streams(path_utf16,
760                                             path_utf16_nchars,
761                                             inode,
762                                             lookup_table);
763         }
764 out_close_handle:
765         CloseHandle(hFile);
766 out_free_path_utf16:
767         FREE(path_utf16);
768 out_destroy_sd_set:
769         if (extra_arg == NULL)
770                 destroy_sd_set(sd_set);
771 out:
772         if (ret == 0)
773                 *root_ret = root;
774         else
775                 free_dentry_tree(root, lookup_table);
776         return ret;
777 }
778
779 /* Replacement for POSIX fnmatch() (partial functionality only) */
780 extern int fnmatch(const char *pattern, const char *string, int flags)
781 {
782         if (PathMatchSpecA(string, pattern))
783                 return 0;
784         else
785                 return FNM_NOMATCH;
786 }
787
788 static int win32_set_reparse_data(HANDLE h,
789                                   u32 reparse_tag,
790                                   const struct wim_lookup_table_entry *lte,
791                                   const wchar_t *path)
792 {
793         int ret;
794         u8 *buf;
795         size_t len;
796
797         if (!lte) {
798                 WARNING("\"%ls\" is marked as a reparse point but had no reparse data",
799                         path);
800                 return 0;
801         }
802         len = wim_resource_size(lte);
803         if (len > 16 * 1024 - 8) {
804                 WARNING("\"%ls\": reparse data too long!", path);
805                 return 0;
806         }
807
808         /* The WIM stream omits the ReparseTag and ReparseDataLength fields, so
809          * leave 8 bytes of space for them at the beginning of the buffer, then
810          * set them manually. */
811         buf = alloca(len + 8);
812         ret = read_full_wim_resource(lte, buf + 8, 0);
813         if (ret)
814                 return ret;
815         *(u32*)(buf + 0) = reparse_tag;
816         *(u16*)(buf + 4) = len;
817         *(u16*)(buf + 6) = 0;
818
819         /* Set the reparse data on the open file using the
820          * FSCTL_SET_REPARSE_POINT ioctl.
821          *
822          * There are contradictions in Microsoft's documentation for this:
823          *
824          * "If hDevice was opened without specifying FILE_FLAG_OVERLAPPED,
825          * lpOverlapped is ignored."
826          *
827          * --- So setting lpOverlapped to NULL is okay since it's ignored.
828          *
829          * "If lpOverlapped is NULL, lpBytesReturned cannot be NULL. Even when an
830          * operation returns no output data and lpOutBuffer is NULL,
831          * DeviceIoControl makes use of lpBytesReturned. After such an
832          * operation, the value of lpBytesReturned is meaningless."
833          *
834          * --- So lpOverlapped not really ignored, as it affects another
835          *  parameter.  This is the actual behavior: lpBytesReturned must be
836          *  specified, even though lpBytesReturned is documented as:
837          *
838          *  "Not used with this operation; set to NULL."
839          */
840         DWORD bytesReturned;
841         if (!DeviceIoControl(h, FSCTL_SET_REPARSE_POINT, buf, len + 8,
842                              NULL, 0,
843                              &bytesReturned /* lpBytesReturned */,
844                              NULL /* lpOverlapped */))
845         {
846                 DWORD err = GetLastError();
847                 ERROR("Failed to set reparse data on \"%ls\"", path);
848                 win32_error(err);
849                 return WIMLIB_ERR_WRITE;
850         }
851         return 0;
852 }
853
854
855 static int win32_extract_chunk(const u8 *buf, size_t len, u64 offset, void *arg)
856 {
857         HANDLE hStream = arg;
858
859         DWORD nbytes_written;
860         wimlib_assert(len <= 0xffffffff);
861
862         if (!WriteFile(hStream, buf, len, &nbytes_written, NULL) ||
863             nbytes_written != len)
864         {
865                 DWORD err = GetLastError();
866                 ERROR("WriteFile(): write error");
867                 win32_error(err);
868                 return WIMLIB_ERR_WRITE;
869         }
870         return 0;
871 }
872
873 static int do_win32_extract_stream(HANDLE hStream, struct wim_lookup_table_entry *lte)
874 {
875         return extract_wim_resource(lte, wim_resource_size(lte),
876                                     win32_extract_chunk, hStream);
877 }
878
879 static int win32_extract_stream(const struct wim_inode *inode,
880                                 const wchar_t *path,
881                                 const wchar_t *stream_name_utf16,
882                                 struct wim_lookup_table_entry *lte)
883 {
884         wchar_t *stream_path;
885         HANDLE h;
886         int ret;
887         DWORD err;
888         DWORD creationDisposition = CREATE_ALWAYS;
889
890         if (stream_name_utf16) {
891                 /* Named stream.  Create a buffer that contains the UTF-16LE
892                  * string [./]@path:@stream_name_utf16.  This is needed to
893                  * create and open the stream using CreateFileW().  I'm not
894                  * aware of any other APIs to do this.  Note: the '$DATA' suffix
895                  * seems to be unneeded.  Additional note: a "./" prefix needs
896                  * to be added when the path is not absolute to avoid ambiguity
897                  * with drive letters. */
898                 size_t stream_path_nchars;
899                 size_t path_nchars;
900                 size_t stream_name_nchars;
901                 const wchar_t *prefix;
902
903                 path_nchars = wcslen(path);
904                 stream_name_nchars = wcslen(stream_name_utf16);
905                 stream_path_nchars = path_nchars + 1 + stream_name_nchars;
906                 if (path[0] != L'/' && path[0] != L'\\') {
907                         prefix = L"./";
908                         stream_path_nchars += 2;
909                 } else {
910                         prefix = L"";
911                 }
912                 stream_path = alloca((stream_path_nchars + 1) * sizeof(wchar_t));
913                 swprintf(stream_path, stream_path_nchars + 1, L"%ls%ls:%ls",
914                          prefix, path, stream_name_utf16);
915         } else {
916                 /* Unnamed stream; its path is just the path to the file itself.
917                  * */
918                 stream_path = (wchar_t*)path;
919
920                 /* Directories must be created with CreateDirectoryW().  Then
921                  * the call to CreateFileW() will merely open the directory that
922                  * was already created rather than creating a new file. */
923                 if (inode->i_attributes & FILE_ATTRIBUTE_DIRECTORY) {
924                         if (!CreateDirectoryW(stream_path, NULL)) {
925                                 err = GetLastError();
926                                 if (err != ERROR_ALREADY_EXISTS) {
927                                         ERROR("Failed to create directory \"%ls\"",
928                                               stream_path);
929                                         win32_error(err);
930                                         ret = WIMLIB_ERR_MKDIR;
931                                         goto fail;
932                                 }
933                         }
934                         DEBUG("Created directory \"%ls\"", stream_path);
935                         if (!(inode->i_attributes & FILE_ATTRIBUTE_REPARSE_POINT)) {
936                                 ret = 0;
937                                 goto out;
938                         }
939                         creationDisposition = OPEN_EXISTING;
940                 }
941         }
942
943         DEBUG("Opening \"%ls\"", stream_path);
944         h = CreateFileW(stream_path,
945                         GENERIC_WRITE | WRITE_OWNER | WRITE_DAC | ACCESS_SYSTEM_SECURITY,
946                         0,
947                         NULL,
948                         creationDisposition,
949                         FILE_FLAG_OPEN_REPARSE_POINT |
950                             FILE_FLAG_BACKUP_SEMANTICS |
951                             inode->i_attributes,
952                         NULL);
953         if (h == INVALID_HANDLE_VALUE) {
954                 err = GetLastError();
955                 ERROR("Failed to create \"%ls\"", stream_path);
956                 win32_error(err);
957                 ret = WIMLIB_ERR_OPEN;
958                 goto fail;
959         }
960
961         if (inode->i_attributes & FILE_ATTRIBUTE_REPARSE_POINT &&
962             stream_name_utf16 == NULL)
963         {
964                 DEBUG("Setting reparse data on \"%ls\"", path);
965                 ret = win32_set_reparse_data(h, inode->i_reparse_tag, lte, path);
966                 if (ret)
967                         goto fail_close_handle;
968         } else {
969                 if (lte) {
970                         DEBUG("Extracting \"%ls\" (len = %"PRIu64")",
971                               stream_path, wim_resource_size(lte));
972                         ret = do_win32_extract_stream(h, lte);
973                         if (ret)
974                                 goto fail_close_handle;
975                 }
976         }
977
978         DEBUG("Closing \"%ls\"", stream_path);
979         if (!CloseHandle(h)) {
980                 err = GetLastError();
981                 ERROR("Failed to close \"%ls\"", stream_path);
982                 win32_error(err);
983                 ret = WIMLIB_ERR_WRITE;
984                 goto fail;
985         }
986         ret = 0;
987         goto out;
988 fail_close_handle:
989         CloseHandle(h);
990 fail:
991         ERROR("Error extracting %ls", stream_path);
992 out:
993         return ret;
994 }
995
996 /*
997  * Creates a file, directory, or reparse point and extracts all streams to it
998  * (unnamed data stream and/or reparse point stream, plus any alternate data
999  * streams).  This in Win32-specific code.
1000  *
1001  * @inode:      WIM inode for this file or directory.
1002  * @path:       UTF-16LE external path to extract the inode to.
1003  *
1004  * Returns 0 on success; nonzero on failure.
1005  */
1006 static int win32_extract_streams(const struct wim_inode *inode,
1007                                  const wchar_t *path, u64 *completed_bytes_p)
1008 {
1009         struct wim_lookup_table_entry *unnamed_lte;
1010         int ret;
1011
1012         unnamed_lte = inode_unnamed_lte_resolved(inode);
1013         ret = win32_extract_stream(inode, path, NULL, unnamed_lte);
1014         if (ret)
1015                 goto out;
1016         if (unnamed_lte)
1017                 *completed_bytes_p += wim_resource_size(unnamed_lte);
1018         for (u16 i = 0; i < inode->i_num_ads; i++) {
1019                 const struct wim_ads_entry *ads_entry = &inode->i_ads_entries[i];
1020                 if (ads_entry->stream_name_len != 0) {
1021                         /* Skip special UNIX data entries (see documentation for
1022                          * WIMLIB_ADD_IMAGE_FLAG_UNIX_DATA) */
1023                         if (ads_entry->stream_name_len == WIMLIB_UNIX_DATA_TAG_LEN
1024                             && !memcmp(ads_entry->stream_name_utf8,
1025                                        WIMLIB_UNIX_DATA_TAG,
1026                                        WIMLIB_UNIX_DATA_TAG_LEN))
1027                                 continue;
1028                         ret = win32_extract_stream(inode,
1029                                                    path,
1030                                                    (const wchar_t*)ads_entry->stream_name,
1031                                                    ads_entry->lte);
1032                         if (ret)
1033                                 break;
1034                         if (ads_entry->lte)
1035                                 *completed_bytes_p += wim_resource_size(ads_entry->lte);
1036                 }
1037         }
1038 out:
1039         return ret;
1040 }
1041
1042 /*
1043  * Sets the security descriptor on an extracted file.  This is Win32-specific
1044  * code.
1045  *
1046  * @inode:      The WIM inode that was extracted and has a security descriptor.
1047  * @path:       UTF-16LE external path that the inode was extracted to.
1048  * @sd:         Security data for the WIM image.
1049  *
1050  * Returns 0 on success; nonzero on failure.
1051  */
1052 static int win32_set_security_data(const struct wim_inode *inode,
1053                                    const wchar_t *path,
1054                                    const struct wim_security_data *sd)
1055 {
1056         SECURITY_INFORMATION securityInformation = DACL_SECURITY_INFORMATION |
1057                                                    SACL_SECURITY_INFORMATION |
1058                                                    OWNER_SECURITY_INFORMATION |
1059                                                    GROUP_SECURITY_INFORMATION;
1060         if (!SetFileSecurityW(path, securityInformation,
1061                               (PSECURITY_DESCRIPTOR)sd->descriptors[inode->i_security_id]))
1062         {
1063                 DWORD err = GetLastError();
1064                 ERROR("Can't set security descriptor on \"%ls\"", path);
1065                 win32_error(err);
1066                 return WIMLIB_ERR_WRITE;
1067         }
1068         return 0;
1069 }
1070
1071 /* Extract a file, directory, reparse point, or hard link to an
1072  * already-extracted file using the Win32 API */
1073 int win32_do_apply_dentry(const char *output_path,
1074                           size_t output_path_len,
1075                           struct wim_dentry *dentry,
1076                           struct apply_args *args)
1077 {
1078         char *utf16_path;
1079         size_t utf16_path_len;
1080         DWORD err;
1081         int ret;
1082         struct wim_inode *inode = dentry->d_inode;
1083
1084         ret = utf8_to_utf16(output_path, output_path_len,
1085                             &utf16_path, &utf16_path_len);
1086         if (ret)
1087                 return ret;
1088
1089         if (inode->i_nlink > 1 && inode->i_extracted_file != NULL) {
1090                 /* Linked file, with another name already extracted.  Create a
1091                  * hard link. */
1092                 DEBUG("Creating hard link \"%ls => %ls\"",
1093                       (const wchar_t*)utf16_path,
1094                       (const wchar_t*)inode->i_extracted_file);
1095                 if (!CreateHardLinkW((const wchar_t*)utf16_path,
1096                                      (const wchar_t*)inode->i_extracted_file,
1097                                      NULL))
1098                 {
1099                         err = GetLastError();
1100                         ERROR("Can't create hard link \"%ls => %ls\"",
1101                               (const wchar_t*)utf16_path,
1102                               (const wchar_t*)inode->i_extracted_file);
1103                         ret = WIMLIB_ERR_LINK;
1104                         win32_error(err);
1105                 }
1106         } else {
1107                 /* Create the file, directory, or reparse point, and extract the
1108                  * data streams. */
1109                 ret = win32_extract_streams(inode, (const wchar_t*)utf16_path,
1110                                             &args->progress.extract.completed_bytes);
1111                 if (ret)
1112                         goto out_free_utf16_path;
1113
1114                 /* Set security descriptor if present */
1115                 if (inode->i_security_id != -1) {
1116                         DEBUG("Setting security descriptor %d on %s",
1117                               inode->i_security_id, output_path);
1118                         ret = win32_set_security_data(inode,
1119                                                       (const wchar_t*)utf16_path,
1120                                                       wim_const_security_data(args->w));
1121                         if (ret)
1122                                 goto out_free_utf16_path;
1123                 }
1124                 if (inode->i_nlink > 1) {
1125                         /* Save extracted path for a later call to
1126                          * CreateHardLinkW() if this inode has multiple links.
1127                          * */
1128                         inode->i_extracted_file = utf16_path;
1129                         goto out;
1130                 }
1131         }
1132 out_free_utf16_path:
1133         FREE(utf16_path);
1134 out:
1135         return ret;
1136 }
1137
1138 /* Set timestamps on an extracted file using the Win32 API */
1139 int win32_do_apply_dentry_timestamps(const char *output_path,
1140                                      size_t output_path_len,
1141                                      const struct wim_dentry *dentry,
1142                                      const struct apply_args *args)
1143 {
1144         /* Win32 */
1145         char *utf16_path;
1146         size_t utf16_path_len;
1147         DWORD err;
1148         HANDLE h;
1149         int ret;
1150         const struct wim_inode *inode = dentry->d_inode;
1151
1152         ret = utf8_to_utf16(output_path, output_path_len,
1153                             &utf16_path, &utf16_path_len);
1154         if (ret)
1155                 return ret;
1156
1157         DEBUG("Opening \"%s\" to set timestamps", output_path);
1158         h = CreateFileW((const wchar_t*)utf16_path,
1159                         GENERIC_WRITE | WRITE_OWNER | WRITE_DAC | ACCESS_SYSTEM_SECURITY,
1160                         FILE_SHARE_READ,
1161                         NULL,
1162                         OPEN_EXISTING,
1163                         FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT,
1164                         NULL);
1165
1166         if (h == INVALID_HANDLE_VALUE)
1167                 err = GetLastError();
1168         FREE(utf16_path);
1169         if (h == INVALID_HANDLE_VALUE)
1170                 goto fail;
1171
1172         FILETIME creationTime = {.dwLowDateTime = inode->i_creation_time & 0xffffffff,
1173                                  .dwHighDateTime = inode->i_creation_time >> 32};
1174         FILETIME lastAccessTime = {.dwLowDateTime = inode->i_last_access_time & 0xffffffff,
1175                                   .dwHighDateTime = inode->i_last_access_time >> 32};
1176         FILETIME lastWriteTime = {.dwLowDateTime = inode->i_last_write_time & 0xffffffff,
1177                                   .dwHighDateTime = inode->i_last_write_time >> 32};
1178
1179         DEBUG("Calling SetFileTime() on \"%s\"", output_path);
1180         if (!SetFileTime(h, &creationTime, &lastAccessTime, &lastWriteTime)) {
1181                 err = GetLastError();
1182                 CloseHandle(h);
1183                 goto fail;
1184         }
1185         DEBUG("Closing \"%s\"", output_path);
1186         if (!CloseHandle(h)) {
1187                 err = GetLastError();
1188                 goto fail;
1189         }
1190         goto out;
1191 fail:
1192         /* Only warn if setting timestamps failed. */
1193         WARNING("Can't set timestamps on \"%s\"", output_path);
1194         win32_error(err);
1195 out:
1196         return 0;
1197 }
1198
1199 /* Replacement for POSIX fsync() */
1200 int fsync(int fd)
1201 {
1202         HANDLE h = (HANDLE)_get_osfhandle(fd);
1203         if (h == INVALID_HANDLE_VALUE) {
1204                 errno = EBADF;
1205                 return -1;
1206         }
1207         if (!FlushFileBuffers(h)) {
1208                 errno = EIO;
1209                 return -1;
1210         }
1211         return 0;
1212 }
1213
1214 /* Use the Win32 API to get the number of processors */
1215 unsigned win32_get_number_of_processors()
1216 {
1217         SYSTEM_INFO sysinfo;
1218         GetSystemInfo(&sysinfo);
1219         return sysinfo.dwNumberOfProcessors;
1220 }
1221
1222 /* Replacement for POSIX-2008 realpath().  Warning: partial functionality only
1223  * (resolved_path must be NULL).   Also I highly doubt that GetFullPathName
1224  * really does the right thing under all circumstances. */
1225 char *realpath(const char *path, char *resolved_path)
1226 {
1227         DWORD ret;
1228         wimlib_assert(resolved_path == NULL);
1229
1230         ret = GetFullPathNameA(path, 0, NULL, NULL);
1231         if (!ret)
1232                 goto fail_win32;
1233
1234         resolved_path = MALLOC(ret + 1);
1235         if (!resolved_path)
1236                 goto fail;
1237         ret = GetFullPathNameA(path, ret, resolved_path, NULL);
1238         if (!ret) {
1239                 free(resolved_path);
1240                 goto fail_win32;
1241         }
1242         return resolved_path;
1243 fail_win32:
1244         win32_error(GetLastError());
1245 fail:
1246         return NULL;
1247 }