/* mz_os_win32.c -- System functions for Windows part of the minizip-ng project Copyright (C) 2010-2021 Nathan Moinvaziri https://github.com/zlib-ng/minizip-ng This program is distributed under the terms of the same license as zlib. See the accompanying LICENSE file for the full text of the license. */ #include "mz.h" #include "mz_os.h" #include "mz_strm_os.h" #include #include /***************************************************************************/ #if defined(WINAPI_FAMILY_PARTITION) && (!(defined(MZ_WINRT_API))) # if !WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) # define MZ_WINRT_API 1 # endif #endif #ifndef SYMBOLIC_LINK_FLAG_DIRECTORY # define SYMBOLIC_LINK_FLAG_DIRECTORY 0x1 #endif /***************************************************************************/ typedef struct DIR_int_s { void *find_handle; WIN32_FIND_DATAW find_data; struct dirent entry; uint8_t end; } DIR_int; /***************************************************************************/ wchar_t *mz_os_unicode_string_create(const char *string, int32_t encoding) { wchar_t *string_wide = NULL; uint32_t string_wide_size = 0; string_wide_size = MultiByteToWideChar(encoding, 0, string, -1, NULL, 0); if (string_wide_size == 0) return NULL; string_wide = (wchar_t *)MZ_ALLOC((string_wide_size + 1) * sizeof(wchar_t)); if (string_wide == NULL) return NULL; memset(string_wide, 0, sizeof(wchar_t) * (string_wide_size + 1)); MultiByteToWideChar(encoding, 0, string, -1, string_wide, string_wide_size); return string_wide; } void mz_os_unicode_string_delete(wchar_t **string) { if (string != NULL) { MZ_FREE(*string); *string = NULL; } } uint8_t *mz_os_utf8_string_create(const char *string, int32_t encoding) { wchar_t *string_wide = NULL; uint8_t *string_utf8 = NULL; uint32_t string_utf8_size = 0; string_wide = mz_os_unicode_string_create(string, encoding); if (string_wide) { string_utf8_size = WideCharToMultiByte(CP_UTF8, 0, string_wide, -1, NULL, 0, NULL, NULL); string_utf8 = (uint8_t *)MZ_ALLOC((string_utf8_size + 1) * sizeof(wchar_t)); if (string_utf8) { memset(string_utf8, 0, string_utf8_size + 1); WideCharToMultiByte(CP_UTF8, 0, string_wide, -1, (char *)string_utf8, string_utf8_size, NULL, NULL); } mz_os_unicode_string_delete(&string_wide); } return string_utf8; } uint8_t *mz_os_utf8_string_create_from_unicode(const wchar_t *string, int32_t encoding) { uint8_t *string_utf8 = NULL; uint32_t string_utf8_size = 0; MZ_UNUSED(encoding); string_utf8_size = WideCharToMultiByte(CP_UTF8, 0, string, -1, NULL, 0, NULL, NULL); string_utf8 = (uint8_t *)MZ_ALLOC((string_utf8_size + 1) * sizeof(wchar_t)); if (string_utf8) { memset(string_utf8, 0, string_utf8_size + 1); WideCharToMultiByte(CP_UTF8, 0, string, -1, (char *)string_utf8, string_utf8_size, NULL, NULL); } return string_utf8; } void mz_os_utf8_string_delete(uint8_t **string) { if (string != NULL) { MZ_FREE(*string); *string = NULL; } } /***************************************************************************/ int32_t mz_os_rand(uint8_t *buf, int32_t size) { unsigned __int64 pentium_tsc[1]; int32_t len = 0; for (len = 0; len < (int)size; len += 1) { if (len % 8 == 0) QueryPerformanceCounter((LARGE_INTEGER *)pentium_tsc); buf[len] = ((unsigned char*)pentium_tsc)[len % 8]; } return len; } int32_t mz_os_rename(const char *source_path, const char *target_path) { wchar_t *source_path_wide = NULL; wchar_t *target_path_wide = NULL; int32_t result = 0; int32_t err = MZ_OK; if (source_path == NULL || target_path == NULL) return MZ_PARAM_ERROR; source_path_wide = mz_os_unicode_string_create(source_path, MZ_ENCODING_UTF8); if (source_path_wide == NULL) { err = MZ_PARAM_ERROR; } else { target_path_wide = mz_os_unicode_string_create(target_path, MZ_ENCODING_UTF8); if (target_path_wide == NULL) err = MZ_PARAM_ERROR; } if (err == MZ_OK) { #ifdef MZ_WINRT_API result = MoveFileExW(source_path_wide, target_path_wide, MOVEFILE_WRITE_THROUGH); #else result = MoveFileW(source_path_wide, target_path_wide); #endif if (result == 0) err = MZ_EXIST_ERROR; } if (target_path_wide) mz_os_unicode_string_delete(&target_path_wide); if (source_path_wide) mz_os_unicode_string_delete(&source_path_wide); return err; } int32_t mz_os_unlink(const char *path) { wchar_t *path_wide = NULL; int32_t result = 0; if (path == NULL) return MZ_PARAM_ERROR; path_wide = mz_os_unicode_string_create(path, MZ_ENCODING_UTF8); if (path_wide == NULL) return MZ_PARAM_ERROR; if (mz_os_is_dir(path) == MZ_OK) result = RemoveDirectoryW(path_wide); else result = DeleteFileW(path_wide); mz_os_unicode_string_delete(&path_wide); if (result == 0) return MZ_EXIST_ERROR; return MZ_OK; } int32_t mz_os_file_exists(const char *path) { wchar_t *path_wide = NULL; DWORD attribs = 0; if (path == NULL) return MZ_PARAM_ERROR; path_wide = mz_os_unicode_string_create(path, MZ_ENCODING_UTF8); if (path_wide == NULL) return MZ_PARAM_ERROR; attribs = GetFileAttributesW(path_wide); mz_os_unicode_string_delete(&path_wide); if (attribs == 0xFFFFFFFF) return MZ_EXIST_ERROR; return MZ_OK; } int64_t mz_os_get_file_size(const char *path) { HANDLE handle = NULL; LARGE_INTEGER large_size; wchar_t *path_wide = NULL; if (path == NULL) return MZ_PARAM_ERROR; path_wide = mz_os_unicode_string_create(path, MZ_ENCODING_UTF8); if (path_wide == NULL) return MZ_PARAM_ERROR; #ifdef MZ_WINRT_API handle = CreateFile2(path_wide, GENERIC_READ, 0, OPEN_EXISTING, NULL); #else handle = CreateFileW(path_wide, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); #endif mz_os_unicode_string_delete(&path_wide); large_size.QuadPart = 0; if (handle != INVALID_HANDLE_VALUE) { GetFileSizeEx(handle, &large_size); CloseHandle(handle); } return large_size.QuadPart; } static void mz_os_file_to_unix_time(FILETIME file_time, time_t *unix_time) { uint64_t quad_file_time = 0; quad_file_time = file_time.dwLowDateTime; quad_file_time |= ((uint64_t)file_time.dwHighDateTime << 32); *unix_time = (time_t)((quad_file_time - 116444736000000000LL) / 10000000); } static void mz_os_unix_to_file_time(time_t unix_time, FILETIME *file_time) { uint64_t quad_file_time = 0; quad_file_time = ((uint64_t)unix_time * 10000000) + 116444736000000000LL; file_time->dwHighDateTime = (quad_file_time >> 32); file_time->dwLowDateTime = (uint32_t)(quad_file_time); } int32_t mz_os_get_file_date(const char *path, time_t *modified_date, time_t *accessed_date, time_t *creation_date) { WIN32_FIND_DATAW ff32; HANDLE handle = NULL; wchar_t *path_wide = NULL; int32_t err = MZ_INTERNAL_ERROR; if (path == NULL) return MZ_PARAM_ERROR; path_wide = mz_os_unicode_string_create(path, MZ_ENCODING_UTF8); if (path_wide == NULL) return MZ_PARAM_ERROR; handle = FindFirstFileW(path_wide, &ff32); MZ_FREE(path_wide); if (handle != INVALID_HANDLE_VALUE) { if (modified_date != NULL) mz_os_file_to_unix_time(ff32.ftLastWriteTime, modified_date); if (accessed_date != NULL) mz_os_file_to_unix_time(ff32.ftLastAccessTime, accessed_date); if (creation_date != NULL) mz_os_file_to_unix_time(ff32.ftCreationTime, creation_date); FindClose(handle); err = MZ_OK; } return err; } int32_t mz_os_set_file_date(const char *path, time_t modified_date, time_t accessed_date, time_t creation_date) { HANDLE handle = NULL; FILETIME ftm_creation, ftm_accessed, ftm_modified; wchar_t *path_wide = NULL; int32_t err = MZ_OK; if (path == NULL) return MZ_PARAM_ERROR; path_wide = mz_os_unicode_string_create(path, MZ_ENCODING_UTF8); if (path_wide == NULL) return MZ_PARAM_ERROR; #ifdef MZ_WINRT_API handle = CreateFile2(path_wide, GENERIC_READ | GENERIC_WRITE, 0, OPEN_EXISTING, NULL); #else handle = CreateFileW(path_wide, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL); #endif mz_os_unicode_string_delete(&path_wide); if (handle != INVALID_HANDLE_VALUE) { GetFileTime(handle, &ftm_creation, &ftm_accessed, &ftm_modified); if (modified_date != 0) mz_os_unix_to_file_time(modified_date, &ftm_modified); if (accessed_date != 0) mz_os_unix_to_file_time(accessed_date, &ftm_accessed); if (creation_date != 0) mz_os_unix_to_file_time(creation_date, &ftm_creation); if (SetFileTime(handle, &ftm_creation, &ftm_accessed, &ftm_modified) == 0) err = MZ_INTERNAL_ERROR; CloseHandle(handle); } return err; } int32_t mz_os_get_file_attribs(const char *path, uint32_t *attributes) { wchar_t *path_wide = NULL; int32_t err = MZ_OK; if (path == NULL || attributes == NULL) return MZ_PARAM_ERROR; path_wide = mz_os_unicode_string_create(path, MZ_ENCODING_UTF8); if (path_wide == NULL) return MZ_PARAM_ERROR; *attributes = GetFileAttributesW(path_wide); mz_os_unicode_string_delete(&path_wide); if (*attributes == INVALID_FILE_ATTRIBUTES) err = MZ_INTERNAL_ERROR; return err; } int32_t mz_os_set_file_attribs(const char *path, uint32_t attributes) { wchar_t *path_wide = NULL; int32_t err = MZ_OK; if (path == NULL) return MZ_PARAM_ERROR; path_wide = mz_os_unicode_string_create(path, MZ_ENCODING_UTF8); if (path_wide == NULL) return MZ_PARAM_ERROR; if (SetFileAttributesW(path_wide, attributes) == 0) err = MZ_INTERNAL_ERROR; mz_os_unicode_string_delete(&path_wide); return err; } int32_t mz_os_make_dir(const char *path) { wchar_t *path_wide = NULL; int32_t err = MZ_OK; if (path == NULL) return MZ_PARAM_ERROR; /* Don't try to create a drive letter */ if ((path[0] != 0) && (strlen(path) <= 3) && (path[1] == ':')) return mz_os_is_dir(path); path_wide = mz_os_unicode_string_create(path, MZ_ENCODING_UTF8); if (path_wide == NULL) return MZ_PARAM_ERROR; if (CreateDirectoryW(path_wide, NULL) == 0) { if (GetLastError() != ERROR_ALREADY_EXISTS) err = MZ_INTERNAL_ERROR; } mz_os_unicode_string_delete(&path_wide); return err; } DIR *mz_os_open_dir(const char *path) { WIN32_FIND_DATAW find_data; DIR_int *dir_int = NULL; wchar_t *path_wide = NULL; char fixed_path[320]; void *handle = NULL; if (path == NULL) return NULL; strncpy(fixed_path, path, sizeof(fixed_path) - 1); fixed_path[sizeof(fixed_path) - 1] = 0; mz_path_append_slash(fixed_path, sizeof(fixed_path), MZ_PATH_SLASH_PLATFORM); mz_path_combine(fixed_path, "*", sizeof(fixed_path)); path_wide = mz_os_unicode_string_create(fixed_path, MZ_ENCODING_UTF8); if (path_wide == NULL) return NULL; handle = FindFirstFileW(path_wide, &find_data); mz_os_unicode_string_delete(&path_wide); if (handle == INVALID_HANDLE_VALUE) return NULL; dir_int = (DIR_int *)MZ_ALLOC(sizeof(DIR_int)); if (dir_int == NULL) return NULL; dir_int->find_handle = handle; dir_int->end = 0; memcpy(&dir_int->find_data, &find_data, sizeof(dir_int->find_data)); return (DIR *)dir_int; } struct dirent* mz_os_read_dir(DIR *dir) { DIR_int *dir_int; if (dir == NULL) return NULL; dir_int = (DIR_int *)dir; if (dir_int->end) return NULL; WideCharToMultiByte(CP_UTF8, 0, dir_int->find_data.cFileName, -1, dir_int->entry.d_name, sizeof(dir_int->entry.d_name), NULL, NULL); if (FindNextFileW(dir_int->find_handle, &dir_int->find_data) == 0) { if (GetLastError() != ERROR_NO_MORE_FILES) return NULL; dir_int->end = 1; } return &dir_int->entry; } int32_t mz_os_close_dir(DIR *dir) { DIR_int *dir_int; if (dir == NULL) return MZ_PARAM_ERROR; dir_int = (DIR_int *)dir; if (dir_int->find_handle != INVALID_HANDLE_VALUE) FindClose(dir_int->find_handle); MZ_FREE(dir_int); return MZ_OK; } int32_t mz_os_is_dir(const char *path) { wchar_t *path_wide = NULL; uint32_t attribs = 0; if (path == NULL) return MZ_PARAM_ERROR; path_wide = mz_os_unicode_string_create(path, MZ_ENCODING_UTF8); if (path_wide == NULL) return MZ_PARAM_ERROR; attribs = GetFileAttributesW(path_wide); mz_os_unicode_string_delete(&path_wide); if (attribs != 0xFFFFFFFF) { if (attribs & FILE_ATTRIBUTE_DIRECTORY) return MZ_OK; } return MZ_EXIST_ERROR; } int32_t mz_os_is_symlink(const char *path) { wchar_t *path_wide = NULL; uint32_t attribs = 0; if (path == NULL) return MZ_PARAM_ERROR; path_wide = mz_os_unicode_string_create(path, MZ_ENCODING_UTF8); if (path_wide == NULL) return MZ_PARAM_ERROR; attribs = GetFileAttributesW(path_wide); mz_os_unicode_string_delete(&path_wide); if (attribs != 0xFFFFFFFF) { if (attribs & FILE_ATTRIBUTE_REPARSE_POINT) return MZ_OK; } return MZ_EXIST_ERROR; } int32_t mz_os_make_symlink(const char *path, const char *target_path) { typedef BOOLEAN (WINAPI *LPCREATESYMBOLICLINKW)(LPCWSTR, LPCWSTR, DWORD); LPCREATESYMBOLICLINKW create_symbolic_link_w = NULL; HMODULE kernel32_mod = NULL; wchar_t *path_wide = NULL; wchar_t *target_path_wide = NULL; int32_t err = MZ_OK; int32_t flags = 0; if (path == NULL) return MZ_PARAM_ERROR; #ifdef MZ_WINRT_API MEMORY_BASIC_INFORMATION mbi; memset(&mbi, 0, sizeof(mbi)); VirtualQuery(VirtualQuery, &mbi, sizeof(mbi)); kernel32_mod = (HMODULE)mbi.AllocationBase; #else kernel32_mod = GetModuleHandleW(L"kernel32.dll"); #endif if (kernel32_mod == NULL) return MZ_SUPPORT_ERROR; create_symbolic_link_w = (LPCREATESYMBOLICLINKW)GetProcAddress(kernel32_mod, "CreateSymbolicLinkW"); if (create_symbolic_link_w == NULL) { return MZ_SUPPORT_ERROR; } path_wide = mz_os_unicode_string_create(path, MZ_ENCODING_UTF8); if (path_wide == NULL) { return MZ_PARAM_ERROR; } target_path_wide = mz_os_unicode_string_create(target_path, MZ_ENCODING_UTF8); if (target_path_wide != NULL) { if (mz_path_has_slash(target_path) == MZ_OK) flags |= SYMBOLIC_LINK_FLAG_DIRECTORY; if (create_symbolic_link_w(path_wide, target_path_wide, flags) == FALSE) err = MZ_SYMLINK_ERROR; mz_os_unicode_string_delete(&target_path_wide); } else { err = MZ_PARAM_ERROR; } mz_os_unicode_string_delete(&path_wide); return err; } int32_t mz_os_read_symlink(const char *path, char *target_path, int32_t max_target_path) { typedef struct _REPARSE_DATA_BUFFER { ULONG ReparseTag; USHORT ReparseDataLength; USHORT Reserved; union { struct { USHORT SubstituteNameOffset; USHORT SubstituteNameLength; USHORT PrintNameOffset; USHORT PrintNameLength; ULONG Flags; WCHAR PathBuffer[1]; } SymbolicLinkReparseBuffer; struct { USHORT SubstituteNameOffset; USHORT SubstituteNameLength; USHORT PrintNameOffset; USHORT PrintNameLength; WCHAR PathBuffer[1]; } MountPointReparseBuffer; struct { UCHAR DataBuffer[1]; } GenericReparseBuffer; }; } REPARSE_DATA_BUFFER; REPARSE_DATA_BUFFER *reparse_data = NULL; DWORD length = 0; HANDLE handle = NULL; wchar_t *path_wide = NULL; wchar_t *target_path_wide = NULL; uint8_t buffer[MAXIMUM_REPARSE_DATA_BUFFER_SIZE]; int32_t target_path_len = 0; int32_t target_path_idx = 0; int32_t err = MZ_OK; uint8_t *target_path_utf8 = NULL; if (path == NULL) return MZ_PARAM_ERROR; path_wide = mz_os_unicode_string_create(path, MZ_ENCODING_UTF8); if (path_wide == NULL) return MZ_PARAM_ERROR; #ifdef MZ_WINRT_API CREATEFILE2_EXTENDED_PARAMETERS extended_params; memset(&extended_params, 0, sizeof(extended_params)); extended_params.dwSize = sizeof(extended_params); extended_params.dwFileAttributes = FILE_ATTRIBUTE_NORMAL; extended_params.dwFileFlags = FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT; extended_params.dwSecurityQosFlags = SECURITY_ANONYMOUS; extended_params.lpSecurityAttributes = NULL; extended_params.hTemplateFile = NULL; handle = CreateFile2(path_wide, FILE_READ_EA, FILE_SHARE_READ | FILE_SHARE_WRITE, OPEN_EXISTING, &extended_params); #else handle = CreateFileW(path_wide, FILE_READ_EA, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, NULL); #endif if (handle == INVALID_HANDLE_VALUE) { mz_os_unicode_string_delete(&path_wide); return MZ_OPEN_ERROR; } if (DeviceIoControl(handle, FSCTL_GET_REPARSE_POINT, NULL, 0, buffer, sizeof(buffer), &length, NULL) == TRUE) { reparse_data = (REPARSE_DATA_BUFFER *)buffer; if ((IsReparseTagMicrosoft(reparse_data->ReparseTag)) && (reparse_data->ReparseTag == IO_REPARSE_TAG_SYMLINK)) { target_path_len = max_target_path * sizeof(wchar_t); if (target_path_len > reparse_data->SymbolicLinkReparseBuffer.PrintNameLength) target_path_len = reparse_data->SymbolicLinkReparseBuffer.PrintNameLength; target_path_wide = (wchar_t *)MZ_ALLOC(target_path_len + sizeof(wchar_t)); if (target_path_wide) { target_path_idx = reparse_data->SymbolicLinkReparseBuffer.PrintNameOffset / sizeof(wchar_t); memcpy(target_path_wide, &reparse_data->SymbolicLinkReparseBuffer.PathBuffer[target_path_idx], target_path_len); target_path_wide[target_path_len / sizeof(wchar_t)] = 0; target_path_utf8 = mz_os_utf8_string_create_from_unicode(target_path_wide, MZ_ENCODING_UTF8); if (target_path_utf8) { strncpy(target_path, (const char *)target_path_utf8, max_target_path - 1); target_path[max_target_path - 1] = 0; /* Ensure directories have slash at the end so we can recreate them later */ if (mz_os_is_dir((const char *)target_path_utf8) == MZ_OK) mz_path_append_slash(target_path, max_target_path, MZ_PATH_SLASH_PLATFORM); mz_os_utf8_string_delete(&target_path_utf8); } else { err = MZ_MEM_ERROR; } MZ_FREE(target_path_wide); } else { err = MZ_MEM_ERROR; } } } else { err = MZ_INTERNAL_ERROR; } CloseHandle(handle); mz_os_unicode_string_delete(&path_wide); return err; } uint64_t mz_os_ms_time(void) { SYSTEMTIME system_time; FILETIME file_time; uint64_t quad_file_time = 0; GetSystemTime(&system_time); SystemTimeToFileTime(&system_time, &file_time); quad_file_time = file_time.dwLowDateTime; quad_file_time |= ((uint64_t)file_time.dwHighDateTime << 32); return quad_file_time / 10000 - 11644473600000LL; }