]> git.ipfire.org Git - thirdparty/libarchive.git/commitdiff
Add support for directory symlinks on Windows
authorMartin Matuska <martin@matuska.org>
Wed, 27 Mar 2019 15:22:41 +0000 (16:22 +0100)
committerMartin Matuska <martin@matuska.org>
Wed, 27 Mar 2019 15:33:16 +0000 (16:33 +0100)
Symlinks with the targets ".", ".." or with an ending slash in the
target are treated as directory symlinks on Windows.

libarchive/archive_read_disk_windows.c
libarchive/archive_write_disk_windows.c
libarchive/test/test_read_disk_directory_traversals.c
test_utils/test_main.c

index 57774b15dbef90ea0449479ba96698dd668f4a3a..964de749e8d1c5ecc98b7fb7cad890ded18e1d79 100644 (file)
@@ -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);
 }
index 27a2c28f45f09245731f41f34174277b3c7fe25b..1155c3135c59e35197f4c11bb7b9df239ef49e1d 100644 (file)
@@ -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);
index 705b3d989cd5ced23404fc4e2fcefe7118a58e88..2990b50884321d4c44405cd7975f2c99b3b6f991 100644 (file)
@@ -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);
index d14e70fcaedc545c062692d53bbadbdc50d60752..6f4dd3a1a376706a240f4846aa77e6cbd5466fe0 100644 (file)
@@ -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