From: Martin Matuska Date: Wed, 27 Mar 2019 15:22:41 +0000 (+0100) Subject: Add support for directory symlinks on Windows X-Git-Tag: v3.4.0~95 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=bc8efdef3e1030976617b8710ff0f71762d078d3;p=thirdparty%2Flibarchive.git Add support for directory symlinks on Windows Symlinks with the targets ".", ".." or with an ending slash in the target are treated as directory symlinks on Windows. --- diff --git a/libarchive/archive_read_disk_windows.c b/libarchive/archive_read_disk_windows.c index 57774b15d..964de749e 100644 --- a/libarchive/archive_read_disk_windows.c +++ b/libarchive/archive_read_disk_windows.c @@ -299,7 +299,7 @@ static int close_and_restore_time(HANDLE, struct tree *, struct restore_time *); static int setup_sparse_from_disk(struct archive_read_disk *, struct archive_entry *, HANDLE); -static int la_linkname_from_handle(HANDLE, wchar_t **); +static int la_linkname_from_handle(HANDLE, wchar_t **, int); static int la_linkname_from_pathw(const wchar_t *, wchar_t **); static void entry_symlink_from_pathw(struct archive_entry *, const wchar_t *path); @@ -337,7 +337,7 @@ typedef struct _REPARSE_DATA_BUFFER { * outbuf is allocated in the function */ static int -la_linkname_from_handle(HANDLE h, wchar_t **linkname) +la_linkname_from_handle(HANDLE h, wchar_t **linkname, int isdir) { DWORD inbytes; REPARSE_DATA_BUFFER *buf; @@ -369,7 +369,8 @@ la_linkname_from_handle(HANDLE h, wchar_t **linkname) return (-1); } - tbuf = malloc(len + sizeof(wchar_t)); + /* We need an extra character here to append directory slash */ + tbuf = malloc(len + 2 * sizeof(wchar_t)); if (tbuf == NULL) { free(indata); return (-1); @@ -391,6 +392,17 @@ la_linkname_from_handle(HANDLE h, wchar_t **linkname) *tbuf = L'/'; tbuf++; } + + /* + * Directory symlinks need special treatment + */ + if (isdir && wcscmp(*linkname, L".") != 0 && + wcscmp(*linkname, L"..") != 0) { + if (*(tbuf - 1) != L'/') { + *tbuf = L'/'; + *(tbuf + 1) = L'\0'; + } + } return (0); } @@ -398,17 +410,24 @@ static int la_linkname_from_pathw(const wchar_t *path, wchar_t **outbuf) { HANDLE h; + DWORD attrs; DWORD flag = FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT; int ret; + attrs = GetFileAttributesW(path); + if (attrs == INVALID_FILE_ATTRIBUTES || + (!(attrs & FILE_ATTRIBUTE_REPARSE_POINT))) + return (-1); + h = CreateFileW(path, 0, FILE_SHARE_READ, NULL, OPEN_EXISTING, flag, NULL); if (h == INVALID_HANDLE_VALUE) { la_dosmaperr(GetLastError()); return (-1); } - ret = la_linkname_from_handle(h, outbuf); + ret = la_linkname_from_handle(h, outbuf, + attrs & FILE_ATTRIBUTE_DIRECTORY); CloseHandle(h); return (ret); } diff --git a/libarchive/archive_write_disk_windows.c b/libarchive/archive_write_disk_windows.c index 27a2c28f4..1155c3135 100644 --- a/libarchive/archive_write_disk_windows.c +++ b/libarchive/archive_write_disk_windows.c @@ -206,6 +206,7 @@ struct archive_write_disk { #define MAXIMUM_DIR_MODE 0775 static int disk_unlink(const wchar_t *); +static int disk_rmdir(const wchar_t *); static int check_symlinks(struct archive_write_disk *); static int create_filesystem_object(struct archive_write_disk *); static struct fixup_entry *current_fixup(struct archive_write_disk *, @@ -594,7 +595,9 @@ la_CreateSymbolicLinkW(const wchar_t *linkname, const wchar_t *target) { static BOOLEAN (WINAPI *f)(LPCWSTR, LPCWSTR, DWORD); static int set; wchar_t *ttarget, *p; + DWORD attrs = 0; DWORD flags = 0; + DWORD newflags = 0; BOOL ret = 0; if (!set) { @@ -625,22 +628,49 @@ la_CreateSymbolicLinkW(const wchar_t *linkname, const wchar_t *target) { *p = L'\0'; /* - * Windows won't overwrite existing links + * If the target equals ".", ".." or ends with a backslash, it always + * points to a directory. In this case we can safely set the directory + * flag. All other symlinks are created as file symlinks. */ - disk_unlink(linkname); + if (wcscmp(ttarget, L".") == 0 || wcscmp(ttarget, L"..") == 0 || + *(p - 1) == L'\\') { +#if defined(SYMBOLIC_LINK_FLAG_DIRECTORY) + flags |= SYMBOLIC_LINK_FLAG_DIRECTORY; +#else + flags |= 0x1; +#endif + /* Now we remove trailing backslashes, if any */ + p--; + while(*p == L'\\') { + *p = L'\0'; + p--; + } + } #if defined(SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE) - flags |= SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE; + newflags = flags | SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE; #else - flags |= 0x2; + newflags = flags | 0x2; #endif - ret = (*f)(linkname, ttarget, flags); + + /* + * Windows won't overwrite existing links + */ + attrs = GetFileAttributesW(linkname); + if (attrs != INVALID_FILE_ATTRIBUTES) { + if (attrs & FILE_ATTRIBUTE_DIRECTORY) + disk_rmdir(linkname); + else + disk_unlink(linkname); + } + + ret = (*f)(linkname, ttarget, newflags); /* * Prior to Windows 10 calling CreateSymbolicLinkW() will fail * if SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE is set */ - if (!ret && flags != 0) { - ret = (*f)(linkname, ttarget, 0); + if (!ret) { + ret = (*f)(linkname, ttarget, flags); } free(ttarget); return (ret); @@ -1319,7 +1349,7 @@ disk_unlink(const wchar_t *path) } static int -disk_rmdir(wchar_t *path) +disk_rmdir(const wchar_t *path) { wchar_t *fullname; int r; @@ -1918,7 +1948,13 @@ check_symlinks(struct archive_write_disk *a) * so we can overwrite it with the * item being extracted. */ - if (disk_unlink(a->name)) { + if (st.dwFileAttributes & + FILE_ATTRIBUTE_DIRECTORY) { + r = disk_rmdir(a->name); + } else { + r = disk_unlink(a->name); + } + if (r) { archive_set_error(&a->archive, errno, "Could not remove symlink %ls", a->name); diff --git a/libarchive/test/test_read_disk_directory_traversals.c b/libarchive/test/test_read_disk_directory_traversals.c index 705b3d989..2990b5088 100644 --- a/libarchive/test/test_read_disk_directory_traversals.c +++ b/libarchive/test/test_read_disk_directory_traversals.c @@ -570,7 +570,7 @@ test_symlink_hybrid(void) assertMakeDir("h", 0755); assertChdir("h"); assertMakeDir("d1", 0755); - assertMakeSymlink("ld1", "d1"); + assertMakeSymlink("ld1", "d1/"); assertMakeFile("d1/file1", 0644, "d1/file1"); assertMakeFile("d1/file2", 0644, "d1/file2"); assertMakeSymlink("d1/link1", "file1"); @@ -727,7 +727,7 @@ test_symlink_logical(void) assertMakeDir("l", 0755); assertChdir("l"); assertMakeDir("d1", 0755); - assertMakeSymlink("ld1", "d1"); + assertMakeSymlink("ld1", "d1/"); assertMakeFile("d1/file1", 0644, "d1/file1"); assertMakeFile("d1/file2", 0644, "d1/file2"); assertMakeSymlink("d1/link1", "file1"); @@ -961,8 +961,8 @@ test_symlink_logical_loop(void) assertMakeDir("d1/d2/d3", 0755); assertMakeDir("d2", 0755); assertMakeFile("d2/file1", 0644, "d2/file1"); - assertMakeSymlink("d1/d2/ld1", "../../d1"); - assertMakeSymlink("d1/d2/ld2", "../../d2"); + assertMakeSymlink("d1/d2/ld1", "../../d1/"); + assertMakeSymlink("d1/d2/ld2", "../../d2/"); assertChdir(".."); assert((ae = archive_entry_new()) != NULL); diff --git a/test_utils/test_main.c b/test_utils/test_main.c index d14e70fca..6f4dd3a1a 100644 --- a/test_utils/test_main.c +++ b/test_utils/test_main.c @@ -214,6 +214,7 @@ static int my_CreateSymbolicLinkA(const char *linkname, const char *target, int flags) { static BOOLEAN (WINAPI *f)(LPCSTR, LPCSTR, DWORD); + DWORD attrs; static int set; int ret, tmpflags; char *tgt, *p; @@ -243,14 +244,40 @@ my_CreateSymbolicLinkA(const char *linkname, const char *target, int flags) *p = '\0'; /* - * Windows can't overwrite existing links + * If the target equals ".", ".." or ends with a slash, it always + * points to a directory. In this case we can set the directory flag. */ - _unlink(linkname); + if (strcmp(tgt, ".") == 0 || strcmp(tgt, "..") == 0 || + *(p - 1) == '\\') { +#if defined(SYMBOLIC_LINK_FLAG_DIRECTORY) + flags |= SYMBOLIC_LINK_FLAG_DIRECTORY; +#else + flags |= 0x1; +#endif + /* Now we remove trailing backslashes */ + p--; + while(*p == '\\') { + *p = '\0'; + p--; + } + } + #if defined(SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE) tmpflags = flags | SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE; #else tmpflags = flags | 0x2; #endif + /* + * Windows won't overwrite existing links + */ + attrs = GetFileAttributesA(linkname); + if (attrs != INVALID_FILE_ATTRIBUTES) { + if (attrs & FILE_ATTRIBUTE_DIRECTORY) + RemoveDirectoryA(linkname); + else + DeleteFileA(linkname); + } + ret = (*f)(linkname, tgt, tmpflags); /* * Prior to Windows 10 the SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE