X-Git-Url: https://wimlib.net/git/?p=wimlib;a=blobdiff_plain;f=src%2Fwin32.c;h=57f6fdb9e0a3bcb842391badc4df6ced18f9c4f1;hp=dfa92ffd01169c6b86a18ddd919f4be696be810e;hb=237947e198385359a0c618b664d73bf4db36c405;hpb=650997e4865a090b6856c7ca34b02f42994e8e29 diff --git a/src/win32.c b/src/win32.c index dfa92ffd..57f6fdb9 100644 --- a/src/win32.c +++ b/src/win32.c @@ -23,9 +23,7 @@ * along with wimlib; if not, see http://www.gnu.org/licenses/. */ -#ifndef __WIN32__ -# error "This file contains Windows code" -#endif +#ifdef __WIN32__ #include "config.h" #include @@ -44,7 +42,15 @@ #include +#define MAX_GET_SD_ACCESS_DENIED_WARNINGS 1 +#define MAX_GET_SACL_PRIV_NOTHELD_WARNINGS 1 +struct win32_capture_state { + unsigned long num_get_sd_access_denied; + unsigned long num_get_sacl_priv_notheld; +}; +#define MAX_SET_SD_ACCESS_DENIED_WARNINGS 1 +#define MAX_SET_SACL_PRIV_NOTHELD_WARNINGS 1 /* Pointers to functions that are not available on all targetted versions of * Windows (XP and later). NOTE: The WINAPI annotations seem to be important; I @@ -106,13 +112,21 @@ win32_global_cleanup() } } -static const wchar_t *access_denied_msg = +static const wchar_t *capture_access_denied_msg = L" If you are not running this program as the administrator, you may\n" " need to do so, so that all data and metadata can be backed up.\n" " Otherwise, there may be no way to access the desired data or\n" " metadata without taking ownership of the file or directory.\n" ; +static const wchar_t *apply_access_denied_msg = +L"If you are not running this program as the administrator, you may\n" + " need to do so, so that all data and metadata can be extracted\n" + " exactly as the origignal copy. However, if you do not care that\n" + " the security descriptors are extracted correctly, you could run\n" + " `wimlib-imagex apply' with the --no-acls flag instead.\n" + ; + #ifdef ENABLE_ERROR_MESSAGES void win32_error(u32 err_code) @@ -204,23 +218,31 @@ win32_get_short_name(struct wim_dentry *dentry, const wchar_t *path) memcpy(dentry->short_name, dat.cAlternateFileName, n); dentry->short_name_nbytes = short_name_nbytes; } + /* If we can't read the short filename for some reason, we just ignore + * the error and assume the file has no short name. I don't think this + * should be an issue, since the short names are essentially obsolete + * anyway. */ return 0; } static int win32_get_security_descriptor(struct wim_dentry *dentry, struct sd_set *sd_set, - const wchar_t *path) + const wchar_t *path, + struct win32_capture_state *state, + int add_image_flags) { SECURITY_INFORMATION requestedInformation; DWORD lenNeeded = 0; BOOL status; DWORD err; + unsigned long n; requestedInformation = DACL_SECURITY_INFORMATION | SACL_SECURITY_INFORMATION | OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION; +again: /* Request length of security descriptor */ status = GetFileSecurityW(path, requestedInformation, NULL, 0, &lenNeeded); @@ -243,11 +265,39 @@ win32_get_security_descriptor(struct wim_dentry *dentry, } } - if (err == ERROR_ACCESS_DENIED) { - WARNING("Failed to read security descriptor of \"%ls\": " - "Access denied!\n%ls", path, access_denied_msg); + if (add_image_flags & WIMLIB_ADD_IMAGE_FLAG_STRICT_ACLS) + goto fail; + + switch (err) { + case ERROR_PRIVILEGE_NOT_HELD: + if (requestedInformation & SACL_SECURITY_INFORMATION) { + n = state->num_get_sacl_priv_notheld++; + requestedInformation &= ~SACL_SECURITY_INFORMATION; + if (n < MAX_GET_SACL_PRIV_NOTHELD_WARNINGS) { + WARNING( +"We don't have enough privileges to read the full security\n" +" descriptor of \"%ls\"!\n" +" Re-trying with SACL omitted.\n", path); + } else if (n == MAX_GET_SACL_PRIV_NOTHELD_WARNINGS) { + WARNING( +"Suppressing further privileges not held error messages when reading\n" +" security descriptors."); + } + goto again; + } + /* Fall through */ + case ERROR_ACCESS_DENIED: + n = state->num_get_sd_access_denied++; + if (n < MAX_GET_SD_ACCESS_DENIED_WARNINGS) { + WARNING("Failed to read security descriptor of \"%ls\": " + "Access denied!\n%ls", path, capture_access_denied_msg); + } else if (n == MAX_GET_SD_ACCESS_DENIED_WARNINGS) { + WARNING("Suppressing further access denied errors messages i" + "when reading security descriptors"); + } return 0; - } else { + default: +fail: ERROR("Failed to read security descriptor of \"%ls\"", path); win32_error(err); return WIMLIB_ERR_READ; @@ -256,40 +306,43 @@ win32_get_security_descriptor(struct wim_dentry *dentry, static int win32_build_dentry_tree_recursive(struct wim_dentry **root_ret, - const wchar_t *path, - const size_t path_num_chars, + wchar_t *path, + size_t path_num_chars, struct wim_lookup_table *lookup_table, struct sd_set *sd_set, const struct capture_config *config, int add_image_flags, - wimlib_progress_func_t progress_func); + wimlib_progress_func_t progress_func, + struct win32_capture_state *state); /* Reads the directory entries of directory using a Win32 API and recursively * calls win32_build_dentry_tree() on them. */ static int win32_recurse_directory(struct wim_dentry *root, - const wchar_t *dir_path, - const size_t dir_path_num_chars, + wchar_t *dir_path, + size_t dir_path_num_chars, struct wim_lookup_table *lookup_table, struct sd_set *sd_set, const struct capture_config *config, int add_image_flags, - wimlib_progress_func_t progress_func) + wimlib_progress_func_t progress_func, + struct win32_capture_state *state) { WIN32_FIND_DATAW dat; HANDLE hFind; DWORD err; int ret; - { - /* Begin reading the directory by calling FindFirstFileW. - * Unlike UNIX opendir(), FindFirstFileW has file globbing built - * into it. But this isn't what we actually want, so just add a - * dummy glob to get all entries. */ - wchar_t pattern_buf[dir_path_num_chars + 3]; - swprintf(pattern_buf, L"%ls/*", dir_path); - hFind = FindFirstFileW(pattern_buf, &dat); - } + /* Begin reading the directory by calling FindFirstFileW. Unlike UNIX + * opendir(), FindFirstFileW has file globbing built into it. But this + * isn't what we actually want, so just add a dummy glob to get all + * entries. */ + dir_path[dir_path_num_chars] = L'/'; + dir_path[dir_path_num_chars + 1] = L'*'; + dir_path[dir_path_num_chars + 2] = L'\0'; + hFind = FindFirstFileW(dir_path, &dat); + dir_path[dir_path_num_chars] = L'\0'; + if (hFind == INVALID_HANDLE_VALUE) { err = GetLastError(); if (err == ERROR_FILE_NOT_FOUND) { @@ -303,29 +356,34 @@ win32_recurse_directory(struct wim_dentry *root, ret = 0; do { /* Skip . and .. entries */ - if (wcscmp(dat.cFileName, L".") && wcscmp(dat.cFileName, L"..")) - { - size_t filename_num_chars = wcslen(dat.cFileName); - size_t new_path_num_chars = dir_path_num_chars + 1 + - filename_num_chars; - wchar_t new_path[new_path_num_chars + 1]; - - swprintf(new_path, L"%ls/%ls", dir_path, dat.cFileName); - - struct wim_dentry *child; - ret = win32_build_dentry_tree_recursive(&child, - new_path, - new_path_num_chars, - lookup_table, - sd_set, - config, - add_image_flags, - progress_func); - if (ret) - goto out_find_close; - if (child) - dentry_add_child(root, child); - } + if (dat.cFileName[0] == L'.' && + (dat.cFileName[1] == L'\0' || + (dat.cFileName[1] == L'.' && + dat.cFileName[2] == L'\0'))) + continue; + size_t filename_len = wcslen(dat.cFileName); + + dir_path[dir_path_num_chars] = L'/'; + wmemcpy(dir_path + dir_path_num_chars + 1, + dat.cFileName, + filename_len + 1); + + struct wim_dentry *child; + size_t path_len = dir_path_num_chars + 1 + filename_len; + ret = win32_build_dentry_tree_recursive(&child, + dir_path, + path_len, + lookup_table, + sd_set, + config, + add_image_flags, + progress_func, + state); + dir_path[dir_path_num_chars] = L'\0'; + if (ret) + goto out_find_close; + if (child) + dentry_add_child(root, child); } while (FindNextFileW(hFind, &dat)); err = GetLastError(); if (err != ERROR_NO_MORE_FILES) { @@ -619,9 +677,10 @@ win32_capture_streams(const wchar_t *path, return 0; } else { if (err == ERROR_ACCESS_DENIED) { + /* XXX This maybe should be an error. */ WARNING("Failed to look up data streams " "of \"%ls\": Access denied!\n%ls", - path, access_denied_msg); + path, capture_access_denied_msg); return 0; } else { ERROR("Failed to look up data streams " @@ -649,11 +708,16 @@ out_find_close: FindClose(hFind); return ret; unnamed_only: + /* FindFirstStreamW() API is not available. Only capture the unnamed + * data stream. */ if (inode->i_attributes & (FILE_ATTRIBUTE_REPARSE_POINT | FILE_ATTRIBUTE_DIRECTORY)) { ret = 0; } else { + /* Just create our own WIN32_FIND_STREAM_DATA for an unnamed + * stream to reduce the code to a call to the + * already-implemented win32_capture_stream() */ wcscpy(dat.cStreamName, L"::$DATA"); dat.StreamSize.QuadPart = file_size; ret = win32_capture_stream(path, @@ -666,13 +730,14 @@ unnamed_only: static int win32_build_dentry_tree_recursive(struct wim_dentry **root_ret, - const wchar_t *path, - const size_t path_num_chars, + wchar_t *path, + size_t path_num_chars, struct wim_lookup_table *lookup_table, struct sd_set *sd_set, const struct capture_config *config, int add_image_flags, - wimlib_progress_func_t progress_func) + wimlib_progress_func_t progress_func, + struct win32_capture_state *state) { struct wim_dentry *root = NULL; struct wim_inode *inode; @@ -680,13 +745,13 @@ win32_build_dentry_tree_recursive(struct wim_dentry **root_ret, u64 file_size; int ret = 0; - if (exclude_path(path, config, true)) { + if (exclude_path(path, path_num_chars, config, true)) { if (add_image_flags & WIMLIB_ADD_IMAGE_FLAG_ROOT) { ERROR("Cannot exclude the root directory from capture"); ret = WIMLIB_ERR_INVALID_CAPTURE_CONFIG; goto out; } - if ((add_image_flags & WIMLIB_ADD_IMAGE_FLAG_VERBOSE) + if ((add_image_flags & WIMLIB_ADD_IMAGE_FLAG_EXCLUDE_VERBOSE) && progress_func) { union wimlib_progress_info info; @@ -751,7 +816,8 @@ win32_build_dentry_tree_recursive(struct wim_dentry **root_ret, goto out_close_handle; if (!(add_image_flags & WIMLIB_ADD_IMAGE_FLAG_NO_ACLS)) { - ret = win32_get_security_descriptor(root, sd_set, path); + ret = win32_get_security_descriptor(root, sd_set, path, state, + add_image_flags); if (ret) goto out_close_handle; } @@ -778,9 +844,12 @@ win32_build_dentry_tree_recursive(struct wim_dentry **root_ret, sd_set, config, add_image_flags, - progress_func); + progress_func, + state); } else if (inode->i_attributes & FILE_ATTRIBUTE_REPARSE_POINT) { - /* Reparse point: save the reparse tag and data */ + /* Reparse point: save the reparse tag and data. Alternate data + * streams are not captured, if it's even possible for a reparse + * point to have alternate data streams... */ ret = win32_capture_reparse_point(hFile, inode, lookup_table, @@ -804,6 +873,38 @@ out: return ret; } +static void +win32_do_capture_warnings(const struct win32_capture_state *state, + int add_image_flags) +{ + if (state->num_get_sacl_priv_notheld == 0 && + state->num_get_sd_access_denied == 0) + return; + + WARNING(""); + WARNING("Built dentry tree successfully, but with the following problem(s):"); + if (state->num_get_sacl_priv_notheld != 0) { + WARNING("Could not capture SACL (System Access Control List)\n" + " on %lu files or directories.", + state->num_get_sacl_priv_notheld); + } + if (state->num_get_sd_access_denied != 0) { + WARNING("Could not capture security descriptor at all\n" + " on %lu files or directories.", + state->num_get_sd_access_denied); + } + WARNING( + "Try running the program as the Administrator to make sure all the\n" +" desired metadata has been captured exactly. However, if you\n" +" do not care about capturing security descriptors correctly, then\n" +" nothing more needs to be done%ls\n", + (add_image_flags & WIMLIB_ADD_IMAGE_FLAG_NO_ACLS) ? L"." : + L", although you might consider\n" +" passing the --no-acls flag to `wimlib-imagex capture' or\n" +" `wimlib-imagex append' to explicitly capture no security\n" +" descriptors.\n"); +} + /* Win32 version of capturing a directory tree */ int win32_build_dentry_tree(struct wim_dentry **root_ret, @@ -818,6 +919,7 @@ win32_build_dentry_tree(struct wim_dentry **root_ret, size_t path_nchars; wchar_t *path; int ret; + struct win32_capture_state state; path_nchars = wcslen(root_disk_path); if (path_nchars > 32767) @@ -833,6 +935,7 @@ win32_build_dentry_tree(struct wim_dentry **root_ret, wmemcpy(path, root_disk_path, path_nchars + 1); + memset(&state, 0, sizeof(state)); ret = win32_build_dentry_tree_recursive(root_ret, path, path_nchars, @@ -840,21 +943,14 @@ win32_build_dentry_tree(struct wim_dentry **root_ret, sd_set, config, add_image_flags, - progress_func); + progress_func, + &state); FREE(path); + if (ret == 0) + win32_do_capture_warnings(&state, add_image_flags); return ret; } -/* 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; -} - static int win32_set_reparse_data(HANDLE h, u32 reparse_tag, @@ -922,6 +1018,78 @@ win32_set_reparse_data(HANDLE h, return 0; } +/* + * Sets the security descriptor on an extracted file. + */ +static int +win32_set_security_data(const struct wim_inode *inode, + const wchar_t *path, + struct apply_args *args) +{ + PSECURITY_DESCRIPTOR descriptor; + unsigned long n; + DWORD err; + + descriptor = wim_const_security_data(args->w)->descriptors[inode->i_security_id]; + + SECURITY_INFORMATION securityInformation = DACL_SECURITY_INFORMATION | + SACL_SECURITY_INFORMATION | + OWNER_SECURITY_INFORMATION | + GROUP_SECURITY_INFORMATION; +again: + if (SetFileSecurityW(path, securityInformation, descriptor)) + return 0; + err = GetLastError(); + if (args->extract_flags & WIMLIB_EXTRACT_FLAG_STRICT_ACLS) + goto fail; + switch (err) { + case ERROR_PRIVILEGE_NOT_HELD: + if (securityInformation & SACL_SECURITY_INFORMATION) { + n = args->num_set_sacl_priv_notheld++; + securityInformation &= ~SACL_SECURITY_INFORMATION; + if (n < MAX_SET_SACL_PRIV_NOTHELD_WARNINGS) { + WARNING( +"We don't have enough privileges to set the full security\n" +" descriptor on \"%ls\"!\n", path); + if (args->num_set_sd_access_denied + + args->num_set_sacl_priv_notheld == 1) + { + WARNING("%ls", apply_access_denied_msg); + } + WARNING("Re-trying with SACL omitted.\n", path); + } else if (n == MAX_GET_SACL_PRIV_NOTHELD_WARNINGS) { + WARNING( +"Suppressing further 'privileges not held' error messages when setting\n" +" security descriptors."); + } + goto again; + } + /* Fall through */ + case ERROR_INVALID_OWNER: + case ERROR_ACCESS_DENIED: + n = args->num_set_sd_access_denied++; + if (n < MAX_SET_SD_ACCESS_DENIED_WARNINGS) { + WARNING("Failed to set security descriptor on \"%ls\": " + "Access denied!\n", path); + if (args->num_set_sd_access_denied + + args->num_set_sacl_priv_notheld == 1) + { + WARNING("%ls", apply_access_denied_msg); + } + } else if (n == MAX_SET_SD_ACCESS_DENIED_WARNINGS) { + WARNING( +"Suppressing further access denied error messages when setting\n" +" security descriptors"); + } + return 0; + default: +fail: + ERROR("Failed to set security descriptor on \"%ls\"", path); + win32_error(err); + return WIMLIB_ERR_WRITE; + } +} + static int win32_extract_chunk(const void *buf, size_t len, u64 offset, void *arg) @@ -949,12 +1117,28 @@ do_win32_extract_stream(HANDLE hStream, struct wim_lookup_table_entry *lte) win32_extract_chunk, hStream); } +static bool +path_is_root_of_drive(const wchar_t *path) +{ + if (!*path) + return false; + + if (*path != L'/' && *path != L'\\') { + if (*(path + 1) == L':') + path += 2; + else + return false; + } + while (*path == L'/' || *path == L'\\') + path++; + return (*path == L'\0'); +} + static int win32_extract_stream(const struct wim_inode *inode, const wchar_t *path, const wchar_t *stream_name_utf16, - struct wim_lookup_table_entry *lte, - const struct wim_security_data *security_data) + struct wim_lookup_table_entry *lte) { wchar_t *stream_path; HANDLE h; @@ -962,17 +1146,6 @@ win32_extract_stream(const struct wim_inode *inode, DWORD err; DWORD creationDisposition = CREATE_ALWAYS; - SECURITY_ATTRIBUTES *secattr; - - if (security_data && inode->i_security_id != -1) { - secattr = alloca(sizeof(*secattr)); - secattr->nLength = sizeof(*secattr); - secattr->lpSecurityDescriptor = security_data->descriptors[inode->i_security_id]; - secattr->bInheritHandle = FALSE; - } else { - secattr = NULL; - } - if (stream_name_utf16) { /* Named stream. Create a buffer that contains the UTF-16LE * string [.\]@path:@stream_name_utf16. This is needed to @@ -1011,9 +1184,16 @@ win32_extract_stream(const struct wim_inode *inode, * the call to CreateFileW() will merely open the directory that * was already created rather than creating a new file. */ if (inode->i_attributes & FILE_ATTRIBUTE_DIRECTORY) { - if (!CreateDirectoryW(stream_path, secattr)) { + if (!CreateDirectoryW(stream_path, NULL)) { err = GetLastError(); - if (err != ERROR_ALREADY_EXISTS) { + switch (err) { + case ERROR_ALREADY_EXISTS: + break; + case ERROR_ACCESS_DENIED: + if (path_is_root_of_drive(path)) + break; + /* Fall through */ + default: ERROR("Failed to create directory \"%ls\"", stream_path); win32_error(err); @@ -1034,7 +1214,7 @@ win32_extract_stream(const struct wim_inode *inode, h = CreateFileW(stream_path, GENERIC_WRITE, 0, - secattr, + NULL, creationDisposition, FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS | @@ -1095,15 +1275,13 @@ out: */ static int win32_extract_streams(const struct wim_inode *inode, - const wchar_t *path, u64 *completed_bytes_p, - const struct wim_security_data *security_data) + const wchar_t *path, u64 *completed_bytes_p) { struct wim_lookup_table_entry *unnamed_lte; int ret; unnamed_lte = inode_unnamed_lte_resolved(inode); - ret = win32_extract_stream(inode, path, NULL, unnamed_lte, - security_data); + ret = win32_extract_stream(inode, path, NULL, unnamed_lte); if (ret) goto out; if (unnamed_lte) @@ -1121,8 +1299,7 @@ win32_extract_streams(const struct wim_inode *inode, ret = win32_extract_stream(inode, path, ads_entry->stream_name, - ads_entry->lte, - NULL); + ads_entry->lte); if (ret) break; if (ads_entry->lte) @@ -1135,10 +1312,11 @@ out: /* Extract a file, directory, reparse point, or hard link to an * already-extracted file using the Win32 API */ -int win32_do_apply_dentry(const wchar_t *output_path, - size_t output_path_num_chars, - struct wim_dentry *dentry, - struct apply_args *args) +int +win32_do_apply_dentry(const wchar_t *output_path, + size_t output_path_num_chars, + struct wim_dentry *dentry, + struct apply_args *args) { int ret; struct wim_inode *inode = dentry->d_inode; @@ -1149,28 +1327,29 @@ int win32_do_apply_dentry(const wchar_t *output_path, * hard link. */ DEBUG("Creating hard link \"%ls => %ls\"", output_path, inode->i_extracted_file); - if (CreateHardLinkW(output_path, inode->i_extracted_file, NULL)) { - ret = 0; - } else { + if (!CreateHardLinkW(output_path, inode->i_extracted_file, NULL)) { err = GetLastError(); ERROR("Can't create hard link \"%ls => %ls\"", output_path, inode->i_extracted_file); - ret = WIMLIB_ERR_LINK; win32_error(err); + return WIMLIB_ERR_LINK; } } else { /* Create the file, directory, or reparse point, and extract the * data streams. */ - const struct wim_security_data *security_data; - if (args->extract_flags & WIMLIB_EXTRACT_FLAG_NOACLS) - security_data = NULL; - else - security_data = wim_const_security_data(args->w); - ret = win32_extract_streams(inode, output_path, - &args->progress.extract.completed_bytes, - security_data); - if (ret == 0 && inode->i_nlink > 1) { + &args->progress.extract.completed_bytes); + if (ret) + return ret; + + if (inode->i_security_id >= 0 && + !(args->extract_flags & WIMLIB_EXTRACT_FLAG_NO_ACLS)) + { + ret = win32_set_security_data(inode, output_path, args); + if (ret) + return ret; + } + if (inode->i_nlink > 1) { /* Save extracted path for a later call to * CreateHardLinkW() if this inode has multiple links. * */ @@ -1179,7 +1358,7 @@ int win32_do_apply_dentry(const wchar_t *output_path, ret = WIMLIB_ERR_NOMEM; } } - return ret; + return 0; } /* Set timestamps on an extracted file using the Win32 API */ @@ -1231,16 +1410,21 @@ out: int fsync(int fd) { - HANDLE h = (HANDLE)_get_osfhandle(fd); + DWORD err; + HANDLE h; + + h = (HANDLE)_get_osfhandle(fd); if (h == INVALID_HANDLE_VALUE) { + err = GetLastError(); ERROR("Could not get Windows handle for file descriptor"); - win32_error(GetLastError()); + win32_error(err); errno = EBADF; return -1; } if (!FlushFileBuffers(h)) { + err = GetLastError(); ERROR("Could not flush file buffers to disk"); - win32_error(GetLastError()); + win32_error(err); errno = EIO; return -1; } @@ -1264,33 +1448,30 @@ realpath(const wchar_t *path, wchar_t *resolved_path) { DWORD ret; wimlib_assert(resolved_path == NULL); + DWORD err; ret = GetFullPathNameW(path, 0, NULL, NULL); - if (!ret) + if (!ret) { + err = GetLastError(); goto fail_win32; + } resolved_path = TMALLOC(ret); if (!resolved_path) - goto fail; + goto out; ret = GetFullPathNameW(path, ret, resolved_path, NULL); if (!ret) { + err = GetLastError(); free(resolved_path); + resolved_path = NULL; goto fail_win32; } - return resolved_path; + goto out; fail_win32: - win32_error(GetLastError()); -fail: - return NULL; -} - -char * -nl_langinfo(nl_item item) -{ - wimlib_assert(item == CODESET); - static char buf[64]; - strcpy(buf, "Unknown"); - return buf; + win32_error(err); + errno = -1; +out: + return resolved_path; } /* rename() on Windows fails if the destination file exists. And we need to @@ -1306,11 +1487,21 @@ win32_rename_replacement(const wchar_t *oldpath, const wchar_t *newpath) ERROR("MoveFileEx(): Can't rename \"%ls\" to \"%ls\"", oldpath, newpath); win32_error(err); - errno = 0; + errno = -1; return -1; } } +/* 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) @@ -1337,8 +1528,10 @@ fail_close_handle: fail: if (err == NO_ERROR) err = GetLastError(); - ERROR("Can't truncate %ls to %"PRIu64" bytes", path, size); + ERROR("Can't truncate \"%ls\" to %"PRIu64" bytes", path, size); win32_error(err); errno = -1; return -1; } + +#endif /* __WIN32__ */