Win32 capture/apply: Simplify opening existing files
[wimlib] / src / win32_common.c
1 /*
2  * win32_common.c - Windows code common to applying and capturing images.
3  */
4
5 /*
6  * Copyright (C) 2013 Eric Biggers
7  *
8  * This file is part of wimlib, a library for working with WIM files.
9  *
10  * wimlib is free software; you can redistribute it and/or modify it under the
11  * terms of the GNU General Public License as published by the Free
12  * Software Foundation; either version 3 of the License, or (at your option)
13  * any later version.
14  *
15  * wimlib is distributed in the hope that it will be useful, but WITHOUT ANY
16  * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
17  * A PARTICULAR PURPOSE. See the GNU General Public License for more
18  * details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with wimlib; if not, see http://www.gnu.org/licenses/.
22  */
23
24 #ifdef __WIN32__
25
26 #ifdef HAVE_CONFIG_H
27 #  include "config.h"
28 #endif
29
30 #include <errno.h>
31
32 #ifdef WITH_NTDLL
33 #  include <winternl.h>
34 #endif
35
36 #include "wimlib/win32_common.h"
37 #include "wimlib/assert.h"
38 #include "wimlib/error.h"
39 #include "wimlib/util.h"
40
41 static int
42 win32_error_to_errno(DWORD err_code)
43 {
44         /* This mapping is that used in Cygwin.
45          * Some of these choices are arbitrary. */
46         switch (err_code) {
47         case ERROR_ACCESS_DENIED:
48                 return EACCES;
49         case ERROR_ACTIVE_CONNECTIONS:
50                 return EAGAIN;
51         case ERROR_ALREADY_EXISTS:
52                 return EEXIST;
53         case ERROR_BAD_DEVICE:
54                 return ENODEV;
55         case ERROR_BAD_EXE_FORMAT:
56                 return ENOEXEC;
57         case ERROR_BAD_NETPATH:
58                 return ENOENT;
59         case ERROR_BAD_NET_NAME:
60                 return ENOENT;
61         case ERROR_BAD_NET_RESP:
62                 return ENOSYS;
63         case ERROR_BAD_PATHNAME:
64                 return ENOENT;
65         case ERROR_BAD_PIPE:
66                 return EINVAL;
67         case ERROR_BAD_UNIT:
68                 return ENODEV;
69         case ERROR_BAD_USERNAME:
70                 return EINVAL;
71         case ERROR_BEGINNING_OF_MEDIA:
72                 return EIO;
73         case ERROR_BROKEN_PIPE:
74                 return EPIPE;
75         case ERROR_BUSY:
76                 return EBUSY;
77         case ERROR_BUS_RESET:
78                 return EIO;
79         case ERROR_CALL_NOT_IMPLEMENTED:
80                 return ENOSYS;
81         case ERROR_CANNOT_MAKE:
82                 return EPERM;
83         case ERROR_CHILD_NOT_COMPLETE:
84                 return EBUSY;
85         case ERROR_COMMITMENT_LIMIT:
86                 return EAGAIN;
87         case ERROR_CRC:
88                 return EIO;
89         case ERROR_DEVICE_DOOR_OPEN:
90                 return EIO;
91         case ERROR_DEVICE_IN_USE:
92                 return EAGAIN;
93         case ERROR_DEVICE_REQUIRES_CLEANING:
94                 return EIO;
95         case ERROR_DIRECTORY:
96                 return ENOTDIR;
97         case ERROR_DIR_NOT_EMPTY:
98                 return ENOTEMPTY;
99         case ERROR_DISK_CORRUPT:
100                 return EIO;
101         case ERROR_DISK_FULL:
102                 return ENOSPC;
103 #ifdef ENOTUNIQ
104         case ERROR_DUP_NAME:
105                 return ENOTUNIQ;
106 #endif
107         case ERROR_EAS_DIDNT_FIT:
108                 return ENOSPC;
109 #ifdef ENOTSUP
110         case ERROR_EAS_NOT_SUPPORTED:
111                 return ENOTSUP;
112 #endif
113         case ERROR_EA_LIST_INCONSISTENT:
114                 return EINVAL;
115         case ERROR_EA_TABLE_FULL:
116                 return ENOSPC;
117         case ERROR_END_OF_MEDIA:
118                 return ENOSPC;
119         case ERROR_EOM_OVERFLOW:
120                 return EIO;
121         case ERROR_EXE_MACHINE_TYPE_MISMATCH:
122                 return ENOEXEC;
123         case ERROR_EXE_MARKED_INVALID:
124                 return ENOEXEC;
125         case ERROR_FILEMARK_DETECTED:
126                 return EIO;
127         case ERROR_FILENAME_EXCED_RANGE:
128                 return ENAMETOOLONG;
129         case ERROR_FILE_CORRUPT:
130                 return EEXIST;
131         case ERROR_FILE_EXISTS:
132                 return EEXIST;
133         case ERROR_FILE_INVALID:
134                 return ENXIO;
135         case ERROR_FILE_NOT_FOUND:
136                 return ENOENT;
137         case ERROR_HANDLE_DISK_FULL:
138                 return ENOSPC;
139 #ifdef ENODATA
140         case ERROR_HANDLE_EOF:
141                 return ENODATA;
142 #endif
143         case ERROR_INVALID_ADDRESS:
144                 return EINVAL;
145         case ERROR_INVALID_AT_INTERRUPT_TIME:
146                 return EINTR;
147         case ERROR_INVALID_BLOCK_LENGTH:
148                 return EIO;
149         case ERROR_INVALID_DATA:
150                 return EINVAL;
151         case ERROR_INVALID_DRIVE:
152                 return ENODEV;
153         case ERROR_INVALID_EA_NAME:
154                 return EINVAL;
155         case ERROR_INVALID_EXE_SIGNATURE:
156                 return ENOEXEC;
157 #ifdef EBADRQC
158         case ERROR_INVALID_FUNCTION:
159                 return EBADRQC;
160 #endif
161         case ERROR_INVALID_HANDLE:
162                 return EBADF;
163         case ERROR_INVALID_NAME:
164                 return ENOENT;
165         case ERROR_INVALID_PARAMETER:
166                 return EINVAL;
167         case ERROR_INVALID_SIGNAL_NUMBER:
168                 return EINVAL;
169         case ERROR_IOPL_NOT_ENABLED:
170                 return ENOEXEC;
171         case ERROR_IO_DEVICE:
172                 return EIO;
173         case ERROR_IO_INCOMPLETE:
174                 return EAGAIN;
175         case ERROR_IO_PENDING:
176                 return EAGAIN;
177         case ERROR_LOCK_VIOLATION:
178                 return EBUSY;
179         case ERROR_MAX_THRDS_REACHED:
180                 return EAGAIN;
181         case ERROR_META_EXPANSION_TOO_LONG:
182                 return EINVAL;
183         case ERROR_MOD_NOT_FOUND:
184                 return ENOENT;
185 #ifdef EMSGSIZE
186         case ERROR_MORE_DATA:
187                 return EMSGSIZE;
188 #endif
189         case ERROR_NEGATIVE_SEEK:
190                 return EINVAL;
191         case ERROR_NETNAME_DELETED:
192                 return ENOENT;
193         case ERROR_NOACCESS:
194                 return EFAULT;
195         case ERROR_NONE_MAPPED:
196                 return EINVAL;
197         case ERROR_NONPAGED_SYSTEM_RESOURCES:
198                 return EAGAIN;
199 #ifdef ENOLINK
200         case ERROR_NOT_CONNECTED:
201                 return ENOLINK;
202 #endif
203         case ERROR_NOT_ENOUGH_MEMORY:
204                 return ENOMEM;
205         case ERROR_NOT_OWNER:
206                 return EPERM;
207 #ifdef ENOMEDIUM
208         case ERROR_NOT_READY:
209                 return ENOMEDIUM;
210 #endif
211         case ERROR_NOT_SAME_DEVICE:
212                 return EXDEV;
213         case ERROR_NOT_SUPPORTED:
214                 return ENOSYS;
215         case ERROR_NO_DATA:
216                 return EPIPE;
217         case ERROR_NO_DATA_DETECTED:
218                 return EIO;
219 #ifdef ENOMEDIUM
220         case ERROR_NO_MEDIA_IN_DRIVE:
221                 return ENOMEDIUM;
222 #endif
223 #ifdef ENMFILE
224         case ERROR_NO_MORE_FILES:
225                 return ENMFILE;
226 #endif
227 #ifdef ENMFILE
228         case ERROR_NO_MORE_ITEMS:
229                 return ENMFILE;
230 #endif
231         case ERROR_NO_MORE_SEARCH_HANDLES:
232                 return ENFILE;
233         case ERROR_NO_PROC_SLOTS:
234                 return EAGAIN;
235         case ERROR_NO_SIGNAL_SENT:
236                 return EIO;
237         case ERROR_NO_SYSTEM_RESOURCES:
238                 return EFBIG;
239         case ERROR_NO_TOKEN:
240                 return EINVAL;
241         case ERROR_OPEN_FAILED:
242                 return EIO;
243         case ERROR_OPEN_FILES:
244                 return EAGAIN;
245         case ERROR_OUTOFMEMORY:
246                 return ENOMEM;
247         case ERROR_PAGED_SYSTEM_RESOURCES:
248                 return EAGAIN;
249         case ERROR_PAGEFILE_QUOTA:
250                 return EAGAIN;
251         case ERROR_PATH_NOT_FOUND:
252                 return ENOENT;
253         case ERROR_PIPE_BUSY:
254                 return EBUSY;
255         case ERROR_PIPE_CONNECTED:
256                 return EBUSY;
257 #ifdef ECOMM
258         case ERROR_PIPE_LISTENING:
259                 return ECOMM;
260         case ERROR_PIPE_NOT_CONNECTED:
261                 return ECOMM;
262 #endif
263         case ERROR_POSSIBLE_DEADLOCK:
264                 return EDEADLOCK;
265         case ERROR_PRIVILEGE_NOT_HELD:
266                 return EPERM;
267         case ERROR_PROCESS_ABORTED:
268                 return EFAULT;
269         case ERROR_PROC_NOT_FOUND:
270                 return ESRCH;
271 #ifdef ENONET
272         case ERROR_REM_NOT_LIST:
273                 return ENONET;
274 #endif
275         case ERROR_SECTOR_NOT_FOUND:
276                 return EINVAL;
277         case ERROR_SEEK:
278                 return EINVAL;
279         case ERROR_SETMARK_DETECTED:
280                 return EIO;
281         case ERROR_SHARING_BUFFER_EXCEEDED:
282                 return ENOLCK;
283         case ERROR_SHARING_VIOLATION:
284                 return EBUSY;
285         case ERROR_SIGNAL_PENDING:
286                 return EBUSY;
287         case ERROR_SIGNAL_REFUSED:
288                 return EIO;
289 #ifdef ELIBBAD
290         case ERROR_SXS_CANT_GEN_ACTCTX:
291                 return ELIBBAD;
292 #endif
293         case ERROR_THREAD_1_INACTIVE:
294                 return EINVAL;
295         case ERROR_TOO_MANY_LINKS:
296                 return EMLINK;
297         case ERROR_TOO_MANY_OPEN_FILES:
298                 return EMFILE;
299         case ERROR_WAIT_NO_CHILDREN:
300                 return ECHILD;
301         case ERROR_WORKING_SET_QUOTA:
302                 return EAGAIN;
303         case ERROR_WRITE_PROTECT:
304                 return EROFS;
305         default:
306                 return -1;
307         }
308 }
309
310
311 void
312 set_errno_from_win32_error(DWORD err)
313 {
314         errno = win32_error_to_errno(err);
315 }
316
317 void
318 set_errno_from_GetLastError(void)
319 {
320         set_errno_from_win32_error(GetLastError());
321 }
322
323 #ifdef WITH_NTDLL
324 void
325 set_errno_from_nt_status(DWORD status)
326 {
327         set_errno_from_win32_error(RtlNtStatusToDosError(status));
328 }
329 #endif
330
331 /* Given a Windows-style path, return the number of characters of the prefix
332  * that specify the path to the root directory of a drive, or return 0 if the
333  * drive is relative (or at least on the current drive, in the case of
334  * absolute-but-not-really-absolute paths like \Windows\System32) */
335 static size_t
336 win32_path_drive_spec_len(const wchar_t *path)
337 {
338         size_t n = 0;
339
340         if (!wcsncmp(path, L"\\\\?\\", 4)) {
341                 /* \\?\-prefixed path.  Check for following drive letter and
342                  * path separator. */
343                 if (path[4] != L'\0' && path[5] == L':' &&
344                     is_any_path_separator(path[6]))
345                         n = 7;
346         } else {
347                 /* Not a \\?\-prefixed path.  Check for an initial drive letter
348                  * and path separator. */
349                 if (path[0] != L'\0' && path[1] == L':' &&
350                     is_any_path_separator(path[2]))
351                         n = 3;
352         }
353         /* Include any additional path separators.*/
354         if (n > 0)
355                 while (is_any_path_separator(path[n]))
356                         n++;
357         return n;
358 }
359
360 bool
361 win32_path_is_root_of_drive(const wchar_t *path)
362 {
363         size_t drive_spec_len;
364         wchar_t full_path[32768];
365         DWORD ret;
366
367         ret = GetFullPathName(path, ARRAY_LEN(full_path), full_path, NULL);
368         if (ret > 0 && ret < ARRAY_LEN(full_path))
369                 path = full_path;
370
371         /* Explicit drive letter and path separator? */
372         drive_spec_len = win32_path_drive_spec_len(path);
373         if (drive_spec_len > 0 && path[drive_spec_len] == L'\0')
374                 return true;
375
376         /* All path separators? */
377         for (const wchar_t *p = path; *p != L'\0'; p++)
378                 if (!is_any_path_separator(*p))
379                         return false;
380         return true;
381 }
382
383
384 /* Given a path, which may not yet exist, get a set of flags that describe the
385  * features of the volume the path is on. */
386 int
387 win32_get_vol_flags(const wchar_t *path, unsigned *vol_flags_ret,
388                     bool *supports_SetFileShortName_ret)
389 {
390         wchar_t *volume;
391         BOOL bret;
392         DWORD vol_flags;
393         size_t drive_spec_len;
394         wchar_t filesystem_name[MAX_PATH + 1];
395
396         if (supports_SetFileShortName_ret)
397                 *supports_SetFileShortName_ret = false;
398
399         drive_spec_len = win32_path_drive_spec_len(path);
400
401         if (drive_spec_len == 0)
402                 if (path[0] != L'\0' && path[1] == L':') /* Drive-relative path? */
403                         drive_spec_len = 2;
404
405         if (drive_spec_len == 0) {
406                 /* Path does not start with a drive letter; use the volume of
407                  * the current working directory. */
408                 volume = NULL;
409         } else {
410                 /* Path starts with a drive letter (or \\?\ followed by a drive
411                  * letter); use it. */
412                 volume = alloca((drive_spec_len + 2) * sizeof(wchar_t));
413                 wmemcpy(volume, path, drive_spec_len);
414                 /* Add trailing backslash in case this was a drive-relative
415                  * path. */
416                 volume[drive_spec_len] = L'\\';
417                 volume[drive_spec_len + 1] = L'\0';
418         }
419         bret = GetVolumeInformation(
420                         volume,                         /* lpRootPathName */
421                         NULL,                           /* lpVolumeNameBuffer */
422                         0,                              /* nVolumeNameSize */
423                         NULL,                           /* lpVolumeSerialNumber */
424                         NULL,                           /* lpMaximumComponentLength */
425                         &vol_flags,                     /* lpFileSystemFlags */
426                         filesystem_name,                /* lpFileSystemNameBuffer */
427                         ARRAY_LEN(filesystem_name));    /* nFileSystemNameSize */
428         if (!bret) {
429                 set_errno_from_GetLastError();
430                 WARNING_WITH_ERRNO("Failed to get volume information for "
431                                    "path \"%ls\"", path);
432                 vol_flags = 0xffffffff;
433                 goto out;
434         }
435
436         if (wcsstr(filesystem_name, L"NTFS")) {
437                 /* FILE_SUPPORTS_HARD_LINKS is only supported on Windows 7 and later.
438                  * Force it on anyway if filesystem is NTFS.  */
439                 vol_flags |= FILE_SUPPORTS_HARD_LINKS;
440
441                 if (supports_SetFileShortName_ret)
442                         *supports_SetFileShortName_ret = true;
443         }
444
445 out:
446         DEBUG("using vol_flags = %x", vol_flags);
447         *vol_flags_ret = vol_flags;
448         return 0;
449 }
450
451 static bool
452 win32_modify_privilege(const wchar_t *privilege, bool enable)
453 {
454         HANDLE hToken;
455         LUID luid;
456         TOKEN_PRIVILEGES newState;
457         bool ret = FALSE;
458
459         if (!OpenProcessToken(GetCurrentProcess(),
460                               TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY,
461                               &hToken))
462                 goto out;
463
464         if (!LookupPrivilegeValue(NULL, privilege, &luid))
465                 goto out_close_handle;
466
467         newState.PrivilegeCount = 1;
468         newState.Privileges[0].Luid = luid;
469         newState.Privileges[0].Attributes = (enable ? SE_PRIVILEGE_ENABLED : 0);
470         SetLastError(ERROR_SUCCESS);
471         ret = AdjustTokenPrivileges(hToken, FALSE, &newState, 0, NULL, NULL);
472         if (ret && GetLastError() == ERROR_NOT_ALL_ASSIGNED)
473                 ret = FALSE;
474 out_close_handle:
475         CloseHandle(hToken);
476 out:
477         return ret;
478 }
479
480 static bool
481 win32_modify_capture_privileges(bool enable)
482 {
483         return win32_modify_privilege(SE_BACKUP_NAME, enable)
484             && win32_modify_privilege(SE_SECURITY_NAME, enable);
485 }
486
487 static bool
488 win32_modify_apply_privileges(bool enable)
489 {
490         return win32_modify_privilege(SE_RESTORE_NAME, enable)
491             && win32_modify_privilege(SE_SECURITY_NAME, enable)
492             && win32_modify_privilege(SE_TAKE_OWNERSHIP_NAME, enable);
493 }
494
495 static void
496 win32_release_capture_and_apply_privileges(void)
497 {
498         win32_modify_capture_privileges(false);
499         win32_modify_apply_privileges(false);
500 }
501
502 HANDLE
503 win32_open_existing_file(const wchar_t *path, DWORD dwDesiredAccess)
504 {
505         return CreateFileW(path,
506                            dwDesiredAccess,
507                            FILE_SHARE_READ,
508                            NULL, /* lpSecurityAttributes */
509                            OPEN_EXISTING,
510                            FILE_FLAG_BACKUP_SEMANTICS |
511                                FILE_FLAG_OPEN_REPARSE_POINT,
512                            NULL /* hTemplateFile */);
513 }
514
515 #ifndef WITH_NTDLL
516 /* Pointers to functions that are not available on all targetted versions of
517  * Windows (XP and later).  NOTE: The WINAPI annotations seem to be important; I
518  * assume it specifies a certain calling convention. */
519
520 /* Vista and later */
521 HANDLE (WINAPI *win32func_FindFirstStreamW)(LPCWSTR lpFileName,
522                                             STREAM_INFO_LEVELS InfoLevel,
523                                             LPVOID lpFindStreamData,
524                                             DWORD dwFlags) = NULL;
525
526 /* Vista and later */
527 BOOL (WINAPI *win32func_FindNextStreamW)(HANDLE hFindStream,
528                                          LPVOID lpFindStreamData) = NULL;
529 #endif /* !WITH_NTDLL */
530
531 /* Vista and later */
532 BOOL (WINAPI *win32func_CreateSymbolicLinkW)(const wchar_t *lpSymlinkFileName,
533                                              const wchar_t *lpTargetFileName,
534                                              DWORD dwFlags) = NULL;
535
536 static OSVERSIONINFO windows_version_info = {
537         .dwOSVersionInfoSize = sizeof(OSVERSIONINFO),
538 };
539
540 static HMODULE hKernel32 = NULL;
541
542 static bool acquired_privileges = false;
543
544 bool
545 windows_version_is_at_least(unsigned major, unsigned minor)
546 {
547         return windows_version_info.dwMajorVersion > major ||
548                 (windows_version_info.dwMajorVersion == major &&
549                  windows_version_info.dwMinorVersion >= minor);
550 }
551
552 /* One-time initialization for Windows capture/apply code.  */
553 int
554 win32_global_init(int init_flags)
555 {
556         /* Try to acquire useful privileges.  */
557         if (!(init_flags & WIMLIB_INIT_FLAG_DONT_ACQUIRE_PRIVILEGES)) {
558                 if (!win32_modify_capture_privileges(true))
559                         if (init_flags & WIMLIB_INIT_FLAG_STRICT_CAPTURE_PRIVILEGES)
560                                 goto insufficient_privileges;
561                 if (!win32_modify_apply_privileges(true))
562                         if (init_flags & WIMLIB_INIT_FLAG_STRICT_APPLY_PRIVILEGES)
563                                 goto insufficient_privileges;
564                 acquired_privileges = true;
565         }
566
567         /* Get Windows version information.  */
568         GetVersionEx(&windows_version_info);
569
570         /* Try to dynamically load some functions.  */
571         if (hKernel32 == NULL)
572                 hKernel32 = LoadLibrary(L"Kernel32.dll");
573
574         if (hKernel32) {
575         #ifndef WITH_NTDLL
576                 win32func_FindFirstStreamW = (void*)GetProcAddress(hKernel32,
577                                                                    "FindFirstStreamW");
578                 if (win32func_FindFirstStreamW) {
579                         win32func_FindNextStreamW = (void*)GetProcAddress(hKernel32,
580                                                                           "FindNextStreamW");
581                         if (!win32func_FindNextStreamW)
582                                 win32func_FindFirstStreamW = NULL;
583                 }
584         #endif /* !WITH_NTDLL */
585                 win32func_CreateSymbolicLinkW = (void*)GetProcAddress(hKernel32,
586                                                                       "CreateSymbolicLinkW");
587         }
588         return 0;
589
590 insufficient_privileges:
591         win32_release_capture_and_apply_privileges();
592         return WIMLIB_ERR_INSUFFICIENT_PRIVILEGES;
593 }
594
595 void
596 win32_global_cleanup(void)
597 {
598         if (acquired_privileges)
599                 win32_release_capture_and_apply_privileges();
600         if (hKernel32 != NULL) {
601                 FreeLibrary(hKernel32);
602                 hKernel32 = NULL;
603         }
604 }
605
606 #endif /* __WIN32__ */