summaryrefslogtreecommitdiff
path: root/libarchive
diff options
context:
space:
mode:
authorMartin Matuska <martin@matuska.org>2019-03-27 16:22:41 +0100
committerMartin Matuska <martin@matuska.org>2019-03-27 16:33:16 +0100
commitbc8efdef3e1030976617b8710ff0f71762d078d3 (patch)
tree24c9b7645dbe6e794105ba57118546c877a7e2ba /libarchive
parent5fe1dadb88c499e8dff71f2e9d9366cb99acda91 (diff)
downloadlibarchive-bc8efdef3e1030976617b8710ff0f71762d078d3.tar.gz
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.
Diffstat (limited to 'libarchive')
-rw-r--r--libarchive/archive_read_disk_windows.c27
-rw-r--r--libarchive/archive_write_disk_windows.c54
-rw-r--r--libarchive/test/test_read_disk_directory_traversals.c8
3 files changed, 72 insertions, 17 deletions
diff --git a/libarchive/archive_read_disk_windows.c b/libarchive/archive_read_disk_windows.c
index 57774b15..964de749 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 27a2c28f..1155c313 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 705b3d98..2990b508 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);