X-Git-Url: https://wimlib.net/git/?p=wimlib;a=blobdiff_plain;f=src%2Fwin32_common.c;h=bcd5c55037b8ab12edf0001eb8689be78511f833;hp=0a7a6d55c48b9c423df776a39a2b13d7919ba89b;hb=b072e7cbca1ccb874e22aa94e3efae37ce211939;hpb=5218b1d7c83cf9e98ed6276e099844ae0d80abc2 diff --git a/src/win32_common.c b/src/win32_common.c index 0a7a6d55..bcd5c550 100644 --- a/src/win32_common.c +++ b/src/win32_common.c @@ -1,31 +1,44 @@ +/* + * win32_common.c - Windows code common to applying and capturing images. + */ + +/* + * Copyright (C) 2013 Eric Biggers + * + * This file is part of wimlib, a library for working with WIM files. + * + * wimlib is free software; you can redistribute it and/or modify it under the + * terms of the GNU General Public License as published by the Free + * Software Foundation; either version 3 of the License, or (at your option) + * any later version. + * + * wimlib is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License + * along with wimlib; if not, see http://www.gnu.org/licenses/. + */ + #ifdef __WIN32__ -#include /* for PathMatchSpecW() */ +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + #include -#include "win32_common.h" +#ifdef WITH_NTDLL +# include +#endif -#ifdef ENABLE_ERROR_MESSAGES -void -win32_error(DWORD err_code) -{ - wchar_t *buffer; - DWORD nchars; - nchars = FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM | - FORMAT_MESSAGE_ALLOCATE_BUFFER, - NULL, err_code, 0, - (wchar_t*)&buffer, 0, NULL); - if (nchars == 0) { - ERROR("Error printing error message! " - "Computer will self-destruct in 3 seconds."); - } else { - ERROR("Win32 error: %ls", buffer); - LocalFree(buffer); - } -} -#endif /* ENABLE_ERROR_MESSAGES */ +#include "wimlib/win32_common.h" +#include "wimlib/assert.h" +#include "wimlib/error.h" +#include "wimlib/util.h" -int +static int win32_error_to_errno(DWORD err_code) { /* This mapping is that used in Cygwin. @@ -294,290 +307,209 @@ win32_error_to_errno(DWORD err_code) } } + void -set_errno_from_GetLastError() +set_errno_from_win32_error(DWORD err) { - errno = win32_error_to_errno(GetLastError()); + errno = win32_error_to_errno(err); } -/* Replacement for POSIX fsync() */ -int -fsync(int fd) +void +set_errno_from_GetLastError(void) { - HANDLE h; - - h = (HANDLE)_get_osfhandle(fd); - if (h == INVALID_HANDLE_VALUE) - goto err; - if (!FlushFileBuffers(h)) - goto err_set_errno; - return 0; -err_set_errno: - set_errno_from_GetLastError(); -err: - return -1; + set_errno_from_win32_error(GetLastError()); } -/* Use the Win32 API to get the number of processors */ -unsigned -win32_get_number_of_processors() +#ifdef WITH_NTDLL +void +set_errno_from_nt_status(NTSTATUS status) { - SYSTEM_INFO sysinfo; - GetSystemInfo(&sysinfo); - return sysinfo.dwNumberOfProcessors; + set_errno_from_win32_error((*func_RtlNtStatusToDosError)(status)); } +#endif -/* Replacement for POSIX-2008 realpath(). Warning: partial functionality only - * (resolved_path must be NULL). Also I highly doubt that GetFullPathName - * really does the right thing under all circumstances. */ -wchar_t * -realpath(const wchar_t *path, wchar_t *resolved_path) +/* Given a Windows-style path, return the number of characters of the prefix + * that specify the path to the root directory of a drive, or return 0 if the + * drive is relative (or at least on the current drive, in the case of + * absolute-but-not-really-absolute paths like \Windows\System32) */ +static size_t +win32_path_drive_spec_len(const wchar_t *path) { - DWORD ret; - DWORD err; - wimlib_assert(resolved_path == NULL); - - ret = GetFullPathNameW(path, 0, NULL, NULL); - if (!ret) { - err = GetLastError(); - goto fail_win32; - } - - resolved_path = TMALLOC(ret); - if (!resolved_path) - goto out; - ret = GetFullPathNameW(path, ret, resolved_path, NULL); - if (!ret) { - err = GetLastError(); - free(resolved_path); - resolved_path = NULL; - goto fail_win32; - } - goto out; -fail_win32: - errno = win32_error_to_errno(err); -out: - return resolved_path; -} - -/* rename() on Windows fails if the destination file exists. And we need to - * make it work on wide characters. Fix it. */ -int -win32_rename_replacement(const wchar_t *oldpath, const wchar_t *newpath) -{ - if (MoveFileExW(oldpath, newpath, MOVEFILE_REPLACE_EXISTING)) { - return 0; + size_t n = 0; + + if (!wcsncmp(path, L"\\\\?\\", 4)) { + /* \\?\-prefixed path. Check for following drive letter and + * path separator. */ + if (path[4] != L'\0' && path[5] == L':' && + is_any_path_separator(path[6])) + n = 7; } else { - set_errno_from_GetLastError(); - return -1; + /* Not a \\?\-prefixed path. Check for an initial drive letter + * and path separator. */ + if (path[0] != L'\0' && path[1] == L':' && + is_any_path_separator(path[2])) + n = 3; } + /* Include any additional path separators.*/ + if (n > 0) + while (is_any_path_separator(path[n])) + n++; + return n; } -/* Replacement for POSIX fnmatch() (partial functionality only) */ -int -fnmatch(const wchar_t *pattern, const wchar_t *string, int flags) -{ - if (PathMatchSpecW(string, pattern)) - return 0; - else - return FNM_NOMATCH; -} - -/* truncate() replacement */ -int -win32_truncate_replacement(const wchar_t *path, off_t size) +bool +win32_path_is_root_of_drive(const wchar_t *path) { - DWORD err = NO_ERROR; - LARGE_INTEGER liOffset; - - HANDLE h = win32_open_existing_file(path, GENERIC_WRITE); - if (h == INVALID_HANDLE_VALUE) - goto fail; - - liOffset.QuadPart = size; - if (!SetFilePointerEx(h, liOffset, NULL, FILE_BEGIN)) - goto fail_close_handle; - - if (!SetEndOfFile(h)) - goto fail_close_handle; - CloseHandle(h); - return 0; - -fail_close_handle: - err = GetLastError(); - CloseHandle(h); -fail: - if (err == NO_ERROR) - err = GetLastError(); - errno = win32_error_to_errno(err); - return -1; -} - - -/* This really could be replaced with _wcserror_s, but this doesn't seem to - * actually be available in MSVCRT.DLL on Windows XP (perhaps it's statically - * linked in by Visual Studio...?). */ -extern int -win32_strerror_r_replacement(int errnum, wchar_t *buf, size_t buflen) -{ - static pthread_mutex_t strerror_lock = PTHREAD_MUTEX_INITIALIZER; - - pthread_mutex_lock(&strerror_lock); - mbstowcs(buf, strerror(errnum), buflen); - buf[buflen - 1] = '\0'; - pthread_mutex_unlock(&strerror_lock); - return 0; -} - -static int -do_pread_or_pwrite(int fd, void *buf, size_t count, off_t offset, - bool is_pwrite) -{ - HANDLE h; - LARGE_INTEGER orig_offset; - DWORD bytes_read_or_written; - LARGE_INTEGER relative_offset; - OVERLAPPED overlapped; - BOOL bret; + size_t drive_spec_len; + wchar_t full_path[32768]; + DWORD ret; - wimlib_assert(count <= 0xffffffff); - - h = (HANDLE)_get_osfhandle(fd); - if (h == INVALID_HANDLE_VALUE) - goto err; - - /* Get original position */ - relative_offset.QuadPart = 0; - if (!SetFilePointerEx(h, relative_offset, &orig_offset, FILE_CURRENT)) - goto err_set_errno; - - memset(&overlapped, 0, sizeof(overlapped)); - overlapped.Offset = offset; - overlapped.OffsetHigh = offset >> 32; - - /* Do the read or write at the specified offset */ - if (is_pwrite) - bret = WriteFile(h, buf, count, &bytes_read_or_written, &overlapped); - else - bret = ReadFile(h, buf, count, &bytes_read_or_written, &overlapped); - if (!bret) - goto err_set_errno; - - /* Restore the original position */ - if (!SetFilePointerEx(h, orig_offset, NULL, FILE_BEGIN)) - goto err_set_errno; - - return bytes_read_or_written; -err_set_errno: - set_errno_from_GetLastError(); -err: - return -1; -} + ret = GetFullPathName(path, ARRAY_LEN(full_path), full_path, NULL); + if (ret > 0 && ret < ARRAY_LEN(full_path)) + path = full_path; -/* Dumb Windows implementation of pread(). It temporarily changes the file - * offset, so it is not safe to use with readers/writers on the same file - * descriptor. */ -extern ssize_t -win32_pread(int fd, void *buf, size_t count, off_t offset) -{ - return do_pread_or_pwrite(fd, buf, count, offset, false); -} + /* Explicit drive letter and path separator? */ + drive_spec_len = win32_path_drive_spec_len(path); + if (drive_spec_len > 0 && path[drive_spec_len] == L'\0') + return true; -/* Dumb Windows implementation of pwrite(). It temporarily changes the file - * offset, so it is not safe to use with readers/writers on the same file - * descriptor. */ -extern ssize_t -win32_pwrite(int fd, const void *buf, size_t count, off_t offset) -{ - return do_pread_or_pwrite(fd, (void*)buf, count, offset, true); + /* All path separators? */ + for (const wchar_t *p = path; *p != L'\0'; p++) + if (!is_any_path_separator(*p)) + return false; + return true; } -/* Dumb Windows implementation of writev(). It writes the vectors one at a - * time. */ -extern ssize_t -win32_writev(int fd, const struct iovec *iov, int iovcnt) -{ - ssize_t total_bytes_written = 0; - - if (iovcnt <= 0) { - errno = EINVAL; - return -1; - } - for (int i = 0; i < iovcnt; i++) { - ssize_t bytes_written; - - bytes_written = write(fd, iov[i].iov_base, iov[i].iov_len); - if (bytes_written >= 0) - total_bytes_written += bytes_written; - if (bytes_written != iov[i].iov_len) { - if (total_bytes_written == 0) - total_bytes_written = -1; - break; - } - } - return total_bytes_written; -} /* Given a path, which may not yet exist, get a set of flags that describe the * features of the volume the path is on. */ int -win32_get_vol_flags(const wchar_t *path, unsigned *vol_flags_ret) +win32_get_vol_flags(const wchar_t *path, unsigned *vol_flags_ret, + bool *supports_SetFileShortName_ret) { wchar_t *volume; BOOL bret; DWORD vol_flags; + size_t drive_spec_len; + wchar_t filesystem_name[MAX_PATH + 1]; - if (path[0] != L'\0' && path[0] != L'\\' && - path[0] != L'/' && path[1] == L':') - { - /* Path starts with a drive letter; use it. */ - volume = alloca(4 * sizeof(wchar_t)); - volume[0] = path[0]; - volume[1] = path[1]; - volume[2] = L'\\'; - volume[3] = L'\0'; - } else { + if (supports_SetFileShortName_ret) + *supports_SetFileShortName_ret = false; + + drive_spec_len = win32_path_drive_spec_len(path); + + if (drive_spec_len == 0) + if (path[0] != L'\0' && path[1] == L':') /* Drive-relative path? */ + drive_spec_len = 2; + + if (drive_spec_len == 0) { /* Path does not start with a drive letter; use the volume of * the current working directory. */ volume = NULL; + } else { + /* Path starts with a drive letter (or \\?\ followed by a drive + * letter); use it. */ + volume = alloca((drive_spec_len + 2) * sizeof(wchar_t)); + wmemcpy(volume, path, drive_spec_len); + /* Add trailing backslash in case this was a drive-relative + * path. */ + volume[drive_spec_len] = L'\\'; + volume[drive_spec_len + 1] = L'\0'; } - bret = GetVolumeInformationW(volume, /* lpRootPathName */ - NULL, /* lpVolumeNameBuffer */ - 0, /* nVolumeNameSize */ - NULL, /* lpVolumeSerialNumber */ - NULL, /* lpMaximumComponentLength */ - &vol_flags, /* lpFileSystemFlags */ - NULL, /* lpFileSystemNameBuffer */ - 0); /* nFileSystemNameSize */ + bret = GetVolumeInformation( + volume, /* lpRootPathName */ + NULL, /* lpVolumeNameBuffer */ + 0, /* nVolumeNameSize */ + NULL, /* lpVolumeSerialNumber */ + NULL, /* lpMaximumComponentLength */ + &vol_flags, /* lpFileSystemFlags */ + filesystem_name, /* lpFileSystemNameBuffer */ + ARRAY_LEN(filesystem_name)); /* nFileSystemNameSize */ if (!bret) { - DWORD err = GetLastError(); - WARNING("Failed to get volume information for path \"%ls\"", path); - win32_error(err); + set_errno_from_GetLastError(); + WARNING_WITH_ERRNO("Failed to get volume information for " + "path \"%ls\"", path); vol_flags = 0xffffffff; + goto out; } + if (wcsstr(filesystem_name, L"NTFS")) { + /* FILE_SUPPORTS_HARD_LINKS is only supported on Windows 7 and later. + * Force it on anyway if filesystem is NTFS. */ + vol_flags |= FILE_SUPPORTS_HARD_LINKS; + + if (supports_SetFileShortName_ret) + *supports_SetFileShortName_ret = true; + } + +out: DEBUG("using vol_flags = %x", vol_flags); *vol_flags_ret = vol_flags; return 0; } -HANDLE -win32_open_existing_file(const wchar_t *path, DWORD dwDesiredAccess) +static bool +win32_modify_privilege(const wchar_t *privilege, bool enable) +{ + HANDLE hToken; + LUID luid; + TOKEN_PRIVILEGES newState; + bool ret = FALSE; + + if (!OpenProcessToken(GetCurrentProcess(), + TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, + &hToken)) + goto out; + + if (!LookupPrivilegeValue(NULL, privilege, &luid)) + goto out_close_handle; + + newState.PrivilegeCount = 1; + newState.Privileges[0].Luid = luid; + newState.Privileges[0].Attributes = (enable ? SE_PRIVILEGE_ENABLED : 0); + SetLastError(ERROR_SUCCESS); + ret = AdjustTokenPrivileges(hToken, FALSE, &newState, 0, NULL, NULL); + if (ret && GetLastError() == ERROR_NOT_ALL_ASSIGNED) + ret = FALSE; +out_close_handle: + CloseHandle(hToken); +out: + return ret; +} + +static bool +win32_modify_capture_privileges(bool enable) +{ + return win32_modify_privilege(SE_BACKUP_NAME, enable) + && win32_modify_privilege(SE_SECURITY_NAME, enable); +} + +static bool +win32_modify_apply_privileges(bool enable) { - return CreateFileW(path, - dwDesiredAccess, - FILE_SHARE_READ, - NULL, /* lpSecurityAttributes */ - OPEN_EXISTING, - FILE_FLAG_BACKUP_SEMANTICS | - FILE_FLAG_OPEN_REPARSE_POINT, - NULL /* hTemplateFile */); + return win32_modify_privilege(SE_RESTORE_NAME, enable) + && win32_modify_privilege(SE_SECURITY_NAME, enable) + && win32_modify_privilege(SE_TAKE_OWNERSHIP_NAME, enable); +} + +static void +win32_release_capture_and_apply_privileges(void) +{ + win32_modify_capture_privileges(false); + win32_modify_apply_privileges(false); } HANDLE -win32_open_file_data_only(const wchar_t *path) +win32_open_existing_file(const wchar_t *path, DWORD dwDesiredAccess) { - return win32_open_existing_file(path, FILE_READ_DATA); + return CreateFile(path, + dwDesiredAccess, + FILE_SHARE_READ, + NULL, /* lpSecurityAttributes */ + OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS | + FILE_FLAG_OPEN_REPARSE_POINT, + NULL /* hTemplateFile */); } /* Pointers to functions that are not available on all targetted versions of @@ -594,50 +526,155 @@ HANDLE (WINAPI *win32func_FindFirstStreamW)(LPCWSTR lpFileName, BOOL (WINAPI *win32func_FindNextStreamW)(HANDLE hFindStream, LPVOID lpFindStreamData) = NULL; +/* Vista and later */ +BOOL (WINAPI *win32func_CreateSymbolicLinkW)(const wchar_t *lpSymlinkFileName, + const wchar_t *lpTargetFileName, + DWORD dwFlags) = NULL; + +#ifdef WITH_NTDLL + +DWORD (WINAPI *func_RtlNtStatusToDosError)(NTSTATUS status); + +NTSTATUS (WINAPI *func_NtQueryInformationFile)(HANDLE FileHandle, + PIO_STATUS_BLOCK IoStatusBlock, + PVOID FileInformation, + ULONG Length, + FILE_INFORMATION_CLASS FileInformationClass); + +NTSTATUS (WINAPI *func_NtQuerySecurityObject)(HANDLE handle, + SECURITY_INFORMATION SecurityInformation, + PSECURITY_DESCRIPTOR SecurityDescriptor, + ULONG Length, + PULONG LengthNeeded); + +NTSTATUS (WINAPI *func_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); + +NTSTATUS (WINAPI *func_NtSetSecurityObject)(HANDLE Handle, + SECURITY_INFORMATION SecurityInformation, + PSECURITY_DESCRIPTOR SecurityDescriptor); + +#endif /* WITH_NTDLL */ + +static OSVERSIONINFO windows_version_info = { + .dwOSVersionInfoSize = sizeof(OSVERSIONINFO), +}; + static HMODULE hKernel32 = NULL; -/* Try to dynamically load some functions */ -void -win32_global_init() +#ifdef WITH_NTDLL +static HMODULE hNtdll = NULL; +#endif + +static bool acquired_privileges = false; + +bool +windows_version_is_at_least(unsigned major, unsigned minor) { - DWORD err; - - if (hKernel32 == NULL) { - DEBUG("Loading Kernel32.dll"); - hKernel32 = LoadLibraryW(L"Kernel32.dll"); - if (hKernel32 == NULL) { - err = GetLastError(); - WARNING("Can't load Kernel32.dll"); - win32_error(err); - return; - } + return windows_version_info.dwMajorVersion > major || + (windows_version_info.dwMajorVersion == major && + windows_version_info.dwMinorVersion >= minor); +} + +/* One-time initialization for Windows capture/apply code. */ +int +win32_global_init(int init_flags) +{ + /* Try to acquire useful privileges. */ + if (!(init_flags & WIMLIB_INIT_FLAG_DONT_ACQUIRE_PRIVILEGES)) { + if (!win32_modify_capture_privileges(true)) + if (init_flags & WIMLIB_INIT_FLAG_STRICT_CAPTURE_PRIVILEGES) + goto insufficient_privileges; + if (!win32_modify_apply_privileges(true)) + if (init_flags & WIMLIB_INIT_FLAG_STRICT_APPLY_PRIVILEGES) + goto insufficient_privileges; + acquired_privileges = true; } - DEBUG("Looking for FindFirstStreamW"); - win32func_FindFirstStreamW = (void*)GetProcAddress(hKernel32, "FindFirstStreamW"); - if (!win32func_FindFirstStreamW) { - WARNING("Could not find function FindFirstStreamW() in Kernel32.dll!"); - WARNING("Capturing alternate data streams will not be supported."); - return; + /* Get Windows version information. */ + GetVersionEx(&windows_version_info); + + /* Try to dynamically load some functions. */ + if (hKernel32 == NULL) + hKernel32 = LoadLibrary(L"Kernel32.dll"); + + if (hKernel32) { + win32func_FindFirstStreamW = (void*)GetProcAddress(hKernel32, + "FindFirstStreamW"); + if (win32func_FindFirstStreamW) { + win32func_FindNextStreamW = (void*)GetProcAddress(hKernel32, + "FindNextStreamW"); + if (!win32func_FindNextStreamW) + win32func_FindFirstStreamW = NULL; + } + win32func_CreateSymbolicLinkW = (void*)GetProcAddress(hKernel32, + "CreateSymbolicLinkW"); } - DEBUG("Looking for FindNextStreamW"); - win32func_FindNextStreamW = (void*)GetProcAddress(hKernel32, "FindNextStreamW"); - if (!win32func_FindNextStreamW) { - WARNING("Could not find function FindNextStreamW() in Kernel32.dll!"); - WARNING("Capturing alternate data streams will not be supported."); - win32func_FindFirstStreamW = NULL; +#ifdef WITH_NTDLL + if (hNtdll == NULL) + hNtdll = LoadLibrary(L"ntdll.dll"); + + if (hNtdll) { + func_RtlNtStatusToDosError = + (void*)GetProcAddress(hNtdll, "RtlNtStatusToDosError"); + if (func_RtlNtStatusToDosError) { + + func_NtQuerySecurityObject = + (void*)GetProcAddress(hNtdll, "NtQuerySecurityObject"); + + func_NtQueryDirectoryFile = + (void*)GetProcAddress(hNtdll, "NtQueryDirectoryFile"); + + func_NtQueryInformationFile = + (void*)GetProcAddress(hNtdll, "NtQueryInformationFile"); + + func_NtSetSecurityObject = + (void*)GetProcAddress(hNtdll, "NtSetSecurityObject"); + } } + + DEBUG("FindFirstStreamW @ %p", win32func_FindFirstStreamW); + DEBUG("FindNextStreamW @ %p", win32func_FindNextStreamW); + DEBUG("CreateSymbolicLinkW @ %p", win32func_CreateSymbolicLinkW); + DEBUG("RtlNtStatusToDosError @ %p", func_RtlNtStatusToDosError); + DEBUG("NtQuerySecurityObject @ %p", func_NtQuerySecurityObject); + DEBUG("NtQueryDirectoryFile @ %p", func_NtQueryDirectoryFile); + DEBUG("NtQueryInformationFile @ %p", func_NtQueryInformationFile); + DEBUG("NtSetSecurityObject @ %p", func_NtSetSecurityObject); +#endif + + return 0; + +insufficient_privileges: + win32_release_capture_and_apply_privileges(); + return WIMLIB_ERR_INSUFFICIENT_PRIVILEGES; } void -win32_global_cleanup() +win32_global_cleanup(void) { + if (acquired_privileges) + win32_release_capture_and_apply_privileges(); if (hKernel32 != NULL) { - DEBUG("Closing Kernel32.dll"); FreeLibrary(hKernel32); hKernel32 = NULL; } +#ifdef WITH_NTDLL + if (hNtdll != NULL) { + FreeLibrary(hNtdll); + hNtdll = NULL; + } +#endif } #endif /* __WIN32__ */