mz_os: split file and link attribute getters (#797)

mz_os_get_file_attribs used lstat on POSIX and GetFileAttributesW
on Win32, both of which return the link's own attributes rather
than the target's. Callers that wanted to follow a symlink had no
way to ask for that, so mz_zip_writer_add_file produced followed
entries tagged ISLNK and readers that tried to recreate them as
symlinks.

Flip mz_os_get_file_attribs to stat on POSIX and to a CreateFile +
GetFileInformationByHandle path on Win32 when the entry is a
reparse point. Add mz_os_get_link_attribs preserving the original
lstat / GetFileAttributesW semantics. Update mz_zip_writer_add_file
to pick the right getter for each branch and to skip symlinks
cleanly when neither store_links nor follow_links is set.

Refs #797.
This commit is contained in:
Nathan Moin Vaziri 2026-04-26 18:21:36 -07:00
parent 3f23ed7a11
commit 6b9defc73c
5 changed files with 102 additions and 5 deletions

View file

@ -39,6 +39,7 @@ These functions provide support for handling common file system operations.
- [mz\_os\_close\_dir](#mz_os_close_dir)
- [mz\_os\_is\_dir](#mz_os_is_dir)
- [mz\_os\_is\_symlink](#mz_os_is_symlink)
- [mz\_os\_get\_link\_attribs](#mz_os_get_link_attribs)
- [mz\_os\_make\_symlink](#mz_os_make_symlink)
- [mz\_os\_read\_symlink](#mz_os_read_symlink)
- [mz\_os\_ms\_time](#mz_os_ms_time)
@ -621,7 +622,7 @@ if (mz_os_get_file_date(src_path, &modified_date, &accessed_date, &creation_date
### mz_os_get_file_attribs
Gets a file's attributes.
Gets a file's attributes, following symbolic links.
**Arguments**
|Type|Name|Description|
@ -839,6 +840,29 @@ else
printf("Path %s is not a symbolic link\n", path);
```
### mz_os_get_link_attribs
Gets a symbolic link's attributes.
**Arguments**
|Type|Name|Description|
|-|-|-|
|const char *|path|File path|
|uint32_t *|attributes|Pointer to store file attributes value|
**Return**
|Type|Description|
|-|-|
|int32_t|[MZ_ERROR](mz_error.md) code, MZ_OK if successful|
**Example**
```
const char *path = "c:\\test7.txt";
uint32_t attributes = 0;
if (mz_os_get_link_attribs(path, &attributes) == MZ_OK)
printf("Link %s attributes %08x\n", path, attributes);
```
### mz_os_make_symlink
Creates a symbolic link pointing to a target.

View file

@ -146,7 +146,7 @@ int32_t mz_os_set_file_date(const char *path, time_t modified_date, time_t acces
/* Sets a file's modified, access, and creation dates if supported */
int32_t mz_os_get_file_attribs(const char *path, uint32_t *attributes);
/* Gets a file's attributes */
/* Gets a file's attributes, following symbolic links */
int32_t mz_os_set_file_attribs(const char *path, uint32_t attributes);
/* Sets a file's attributes */
@ -177,6 +177,9 @@ int32_t mz_os_is_dir(const char *path);
int32_t mz_os_is_symlink(const char *path);
/* Checks to see if path is a symbolic link */
int32_t mz_os_get_link_attribs(const char *path, uint32_t *attributes);
/* Gets a symbolic link's attributes */
int32_t mz_os_make_symlink(const char *path, const char *target_path);
/* Creates a symbolic link pointing to a target */

View file

@ -255,7 +255,7 @@ int32_t mz_os_get_file_attribs(const char *path, uint32_t *attributes) {
int32_t err = MZ_OK;
memset(&path_stat, 0, sizeof(path_stat));
if (lstat(path, &path_stat) == -1)
if (stat(path, &path_stat) == -1)
err = MZ_INTERNAL_ERROR;
*attributes = path_stat.st_mode;
return err;
@ -333,6 +333,17 @@ int32_t mz_os_is_symlink(const char *path) {
return MZ_EXIST_ERROR;
}
int32_t mz_os_get_link_attribs(const char *path, uint32_t *attributes) {
struct stat path_stat;
int32_t err = MZ_OK;
memset(&path_stat, 0, sizeof(path_stat));
if (lstat(path, &path_stat) == -1)
err = MZ_INTERNAL_ERROR;
*attributes = path_stat.st_mode;
return err;
}
int32_t mz_os_make_symlink(const char *path, const char *target_path) {
#if !HAVE_SYMLINK
return MZ_SUPPORT_ERROR;

View file

@ -336,11 +336,39 @@ int32_t mz_os_get_file_attribs(const char *path, uint32_t *attributes) {
if (!path || !attributes)
return MZ_PARAM_ERROR;
path_wide = mz_os_unicode_string_create(path, MZ_ENCODING_UTF8);
if (!path_wide)
return MZ_PARAM_ERROR;
*attributes = GetFileAttributesW(path_wide);
/* If target is a reparse point, open with default flags to get attributes */
if (*attributes != INVALID_FILE_ATTRIBUTES && (*attributes & FILE_ATTRIBUTE_REPARSE_POINT)) {
HANDLE handle = INVALID_HANDLE_VALUE;
BY_HANDLE_FILE_INFORMATION info;
#if _WIN32_WINNT >= _WIN32_WINNT_WIN8
CREATEFILE2_EXTENDED_PARAMETERS extended_params;
memset(&extended_params, 0, sizeof(extended_params));
extended_params.dwSize = sizeof(extended_params);
extended_params.dwFileFlags = FILE_FLAG_BACKUP_SEMANTICS;
handle = CreateFile2(path_wide, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, OPEN_EXISTING,
&extended_params);
#else
handle = CreateFileW(path_wide, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS, NULL);
#endif
if (handle != INVALID_HANDLE_VALUE) {
if (GetFileInformationByHandle(handle, &info))
*attributes = info.dwFileAttributes;
CloseHandle(handle);
}
}
mz_os_unicode_string_delete(&path_wide);
if (*attributes == INVALID_FILE_ATTRIBUTES)
@ -512,6 +540,26 @@ int32_t mz_os_is_symlink(const char *path) {
return MZ_EXIST_ERROR;
}
int32_t mz_os_get_link_attribs(const char *path, uint32_t *attributes) {
wchar_t *path_wide = NULL;
int32_t err = MZ_OK;
if (!path || !attributes)
return MZ_PARAM_ERROR;
path_wide = mz_os_unicode_string_create(path, MZ_ENCODING_UTF8);
if (!path_wide)
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_make_symlink(const char *path, const char *target_path) {
typedef BOOLEAN(WINAPI * LPCREATESYMBOLICLINKW)(LPCWSTR, LPCWSTR, DWORD);
MEMORY_BASIC_INFORMATION mbi;

View file

@ -1628,6 +1628,7 @@ int32_t mz_zip_writer_add_file(void *handle, const char *path, const char *filen
uint32_t src_attrib = 0;
int32_t err = MZ_OK;
uint8_t src_sys = 0;
uint8_t is_symlink = 0;
void *stream = NULL;
char link_path[1024];
const char *filename = filename_in_zip;
@ -1643,6 +1644,12 @@ int32_t mz_zip_writer_add_file(void *handle, const char *path, const char *filen
return err;
}
if (mz_os_is_symlink(path) == MZ_OK)
is_symlink = 1;
if (is_symlink && !writer->store_links && !writer->follow_links)
return MZ_OK;
memset(&file_info, 0, sizeof(file_info));
/* The path name saved, should not include a leading slash. */
@ -1665,7 +1672,11 @@ int32_t mz_zip_writer_add_file(void *handle, const char *path, const char *filen
file_info.aes_version = MZ_AES_VERSION;
mz_os_get_file_date(path, &file_info.modified_date, &file_info.accessed_date, &file_info.creation_date);
mz_os_get_file_attribs(path, &src_attrib);
if (is_symlink && writer->store_links)
mz_os_get_link_attribs(path, &src_attrib);
else
mz_os_get_file_attribs(path, &src_attrib);
src_sys = MZ_HOST_SYSTEM(file_info.version_madeby);
@ -1678,7 +1689,7 @@ int32_t mz_zip_writer_add_file(void *handle, const char *path, const char *filen
file_info.external_fa = src_attrib;
}
if (writer->store_links && mz_os_is_symlink(path) == MZ_OK) {
if (is_symlink && writer->store_links) {
err = mz_os_read_symlink(path, link_path, sizeof(link_path));
if (err == MZ_OK)
file_info.linkname = link_path;