]> git.ipfire.org Git - thirdparty/libarchive.git/commitdiff
Add basic read and write support for symbolic links on Windows
authorMartin Matuska <martin@matuska.org>
Tue, 26 Mar 2019 16:56:30 +0000 (17:56 +0100)
committerMartin Matuska <martin@matuska.org>
Tue, 26 Mar 2019 20:39:37 +0000 (21:39 +0100)
TODO: proper handling of directory symlinks

Fixes #1030

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

index d82048de29950f7bf25469f8bbfbb1723d7fb631..57774b15dbef90ea0449479ba96698dd668f4a3a 100644 (file)
@@ -299,8 +299,133 @@ 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_pathw(const wchar_t *, wchar_t **);
+static void    entry_symlink_from_pathw(struct archive_entry *,
+                   const wchar_t *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;
+       } DUMMYUNIONNAME;
+} REPARSE_DATA_BUFFER, *PREPARSE_DATA_BUFFER;
 
+/*
+ * Reads the target of a symbolic link
+ *
+ * Returns 0 on success and -1 on failure
+ * outbuf is allocated in the function
+ */
+static int
+la_linkname_from_handle(HANDLE h, wchar_t **linkname)
+{
+       DWORD inbytes;
+       REPARSE_DATA_BUFFER *buf;
+       size_t len;
+       BOOL ret;
+       BYTE *indata;
+       wchar_t *tbuf;
+
+       indata = malloc(MAXIMUM_REPARSE_DATA_BUFFER_SIZE);
+       ret = DeviceIoControl(h, FSCTL_GET_REPARSE_POINT, NULL, 0, indata,
+           1024, &inbytes, NULL);
+       if (ret == 0) {
+               la_dosmaperr(GetLastError());
+               free(indata);
+               return (-1);
+       }
+
+       buf = (REPARSE_DATA_BUFFER *) indata;
+       if (buf->ReparseTag != IO_REPARSE_TAG_SYMLINK) {
+               free(indata);
+               /* File is not a symbolic link */
+               errno = EINVAL;
+               return (-1);
+       }
+
+       len = buf->SymbolicLinkReparseBuffer.SubstituteNameLength;
+       if (len <= 0) {
+               free(indata);
+               return (-1);
+       }
+
+       tbuf = malloc(len + sizeof(wchar_t));
+       if (tbuf == NULL) {
+               free(indata);
+               return (-1);
+       }
+
+       memcpy(tbuf, &((BYTE *)buf->SymbolicLinkReparseBuffer.PathBuffer)
+           [buf->SymbolicLinkReparseBuffer.SubstituteNameOffset], len);
+       free(indata);
+
+       tbuf[len / sizeof(wchar_t)] = L'\0';
+
+       *linkname = tbuf;
+
+       /*
+        * Translate backslashes to slashes for libarchive internal use
+        */
+       while(*tbuf != L'\0') {
+               if (*tbuf == L'\\')
+                       *tbuf = L'/';
+               tbuf++;
+       }
+       return (0);
+}
+
+static int
+la_linkname_from_pathw(const wchar_t *path, wchar_t **outbuf)
+{
+       HANDLE h;
+       DWORD flag = FILE_FLAG_BACKUP_SEMANTICS |
+           FILE_FLAG_OPEN_REPARSE_POINT;
+       int ret;
 
+       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);
+       CloseHandle(h);
+       return (ret);
+}
+
+static void
+entry_symlink_from_pathw(struct archive_entry *entry, const wchar_t *path)
+{
+       wchar_t *linkname = NULL;
+       int ret;
+
+       ret = la_linkname_from_pathw(path, &linkname);
+       if (ret == 0)
+               archive_entry_copy_symlink_w(entry, linkname);
+       free(linkname);
+
+       return;
+}
 
 static struct archive_vtable *
 archive_read_disk_vtable(void)
@@ -1838,9 +1963,10 @@ entry_copy_bhfi(struct archive_entry *entry, const wchar_t *path,
                mode |= S_IWUSR | S_IWGRP | S_IWOTH;
        if ((bhfi->dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) &&
            findData != NULL &&
-           findData->dwReserved0 == IO_REPARSE_TAG_SYMLINK)
+           findData->dwReserved0 == IO_REPARSE_TAG_SYMLINK) {
                mode |= S_IFLNK;
-       else if (bhfi->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
+               entry_symlink_from_pathw(entry, path);
+       } else if (bhfi->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
                mode |= S_IFDIR | S_IXUSR | S_IXGRP | S_IXOTH;
        else {
                const wchar_t *p;
@@ -2139,6 +2265,8 @@ archive_read_disk_entry_from_file(struct archive *_a,
                fileAttributes = bhfi.dwFileAttributes;
        } else {
                archive_entry_copy_stat(entry, st);
+               if (st->st_mode & S_IFLNK)
+                       entry_symlink_from_pathw(entry, path);
                h = INVALID_HANDLE_VALUE;
        }
 
index 975555f2e732ec59bd873954126ad51d32814d25..27a2c28f45f09245731f41f34174277b3c7fe25b 100644 (file)
@@ -205,6 +205,7 @@ struct archive_write_disk {
 #define        MINIMUM_DIR_MODE 0700
 #define        MAXIMUM_DIR_MODE 0775
 
+static int     disk_unlink(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 *,
@@ -582,6 +583,69 @@ la_CreateHardLinkW(wchar_t *linkname, wchar_t *target)
        return (ret);
 }
 
+/*
+ * Create symolic link
+ *
+ * Always creates a file symbolic link.
+ * Directory symbolic links are currently not implemented.
+ */
+static int
+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 flags = 0;
+       BOOL ret = 0;
+
+       if (!set) {
+               set = 1;
+               f = la_GetFunctionKernel32("CreateSymbolicLinkW");
+       }
+       if (!f)
+               return (0);
+
+       /*
+        * When writing path targets, we need to translate slashes
+        * to backslashes
+        */
+       ttarget = malloc((wcslen(target) + 1) * sizeof(wchar_t));
+       if (ttarget == NULL)
+               return(0);
+
+       p = ttarget;
+
+       while(*target != L'\0') {
+               if (*target == L'/')
+                       *p = L'\\';
+               else
+                       *p = *target;
+               target++;
+               p++;
+       }
+       *p = L'\0';
+
+       /*
+        * Windows won't overwrite existing links
+        */
+       disk_unlink(linkname);
+
+#if defined(SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE)
+       flags |= SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE;
+#else
+       flags |= 0x2;
+#endif
+       ret = (*f)(linkname, ttarget, flags);
+       /*
+        * 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);
+       }
+       free(ttarget);
+       return (ret);
+}
+
 static int
 la_ftruncate(HANDLE handle, int64_t length)
 {
@@ -1240,7 +1304,7 @@ archive_write_disk_new(void)
 }
 
 static int
-disk_unlink(wchar_t *path)
+disk_unlink(const wchar_t *path)
 {
        wchar_t *fullname;
        int r;
@@ -1515,7 +1579,13 @@ create_filesystem_object(struct archive_write_disk *a)
 #if HAVE_SYMLINK
                return symlink(linkname, a->name) ? errno : 0;
 #else
-               return (EPERM);
+               r = la_CreateSymbolicLinkW((const wchar_t *)a->name, linkname);
+               if (r == 0) {
+                       la_dosmaperr(GetLastError());
+                       r = errno;
+               } else
+                       r = 0;
+               return (r);
 #endif
        }
 
index defdd34446c412e94f612a8f40e24dc6259644c4..d14e70fcaedc545c062692d53bbadbdc50d60752 100644 (file)
@@ -168,6 +168,32 @@ static int  my_CreateHardLinkA(const char *, const char *);
 static int      my_GetFileInformationByName(const char *,
                     BY_HANDLE_FILE_INFORMATION *);
 
+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;
+       } DUMMYUNIONNAME;
+} REPARSE_DATA_BUFFER, *PREPARSE_DATA_BUFFER;
+
 static void *
 GetFunctionKernel32(const char *name)
 {
@@ -189,11 +215,52 @@ my_CreateSymbolicLinkA(const char *linkname, const char *target, int flags)
 {
        static BOOLEAN (WINAPI *f)(LPCSTR, LPCSTR, DWORD);
        static int set;
+       int ret, tmpflags;
+       char *tgt, *p;
        if (!set) {
                set = 1;
                f = GetFunctionKernel32("CreateSymbolicLinkA");
        }
-       return f == NULL ? 0 : (*f)(linkname, target, flags);
+       if (f == NULL)
+               return (0);
+
+       tgt = malloc(strlen(target) + 1);
+       if (tgt == NULL)
+               return (0);
+
+       /*
+        * Translate slashes to backslashes
+        */
+       p = tgt;
+       while(*target != '\0') {
+               if (*target == '/')
+                       *p = '\\';
+               else
+                       *p = *target;
+               target++;
+               p++;
+       }
+       *p = '\0';
+
+       /*
+        * Windows can't overwrite existing links
+        */
+       _unlink(linkname);
+#if defined(SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE)
+       tmpflags = flags | SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE;
+#else
+       tmpflags = flags | 0x2;
+#endif
+       ret = (*f)(linkname, tgt, tmpflags);
+       /*
+        * Prior to Windows 10 the SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE
+        * is not undestood
+        */
+       if (!ret)
+               ret = (*f)(linkname, tgt, flags);
+
+       free(tgt);
+       return (ret);
 }
 
 static int
@@ -1606,13 +1673,70 @@ is_symlink(const char *file, int line,
     const char *pathname, const char *contents)
 {
 #if defined(_WIN32) && !defined(__CYGWIN__)
-       (void)pathname; /* UNUSED */
-       (void)contents; /* UNUSED */
-       assertion_count(file, line);
-       /* Windows sort-of has real symlinks, but they're only usable
-        * by privileged users and are crippled even then, so there's
-        * really not much point in bothering with this. */
-       return (0);
+       HANDLE h;
+       DWORD inbytes;
+       REPARSE_DATA_BUFFER *buf;
+       size_t len, len2;
+       wchar_t *linknamew, *contentsw;
+       int ret = 0;
+       BYTE *indata;
+       DWORD flag = FILE_FLAG_BACKUP_SEMANTICS |
+           FILE_FLAG_OPEN_REPARSE_POINT;
+
+       if (contents == NULL)
+               return (0);
+
+       h = CreateFileA(pathname, 0, FILE_SHARE_READ, NULL, OPEN_EXISTING,
+           flag, NULL);
+       if (h == INVALID_HANDLE_VALUE)
+               return (0);
+
+       indata = malloc(MAXIMUM_REPARSE_DATA_BUFFER_SIZE);
+       ret = DeviceIoControl(h, FSCTL_GET_REPARSE_POINT, NULL, 0, indata,
+           1024, &inbytes, NULL);
+       CloseHandle(h);
+       if (ret == 0) {
+               free(indata);
+               return (0);
+       }
+
+       buf = (REPARSE_DATA_BUFFER *) indata;
+       if (buf->ReparseTag != IO_REPARSE_TAG_SYMLINK) {
+               free(indata);
+               /* File is not a symbolic link */
+               errno = EINVAL;
+               return (0);
+       }
+
+       len = buf->SymbolicLinkReparseBuffer.SubstituteNameLength;
+
+       linknamew = malloc(len + sizeof(wchar_t));
+       if (linknamew == NULL) {
+               free(indata);
+               return (0);
+       }
+
+       memcpy(linknamew, &((BYTE *)buf->SymbolicLinkReparseBuffer.PathBuffer)
+           [buf->SymbolicLinkReparseBuffer.SubstituteNameOffset], len);
+       free(indata);
+
+       linknamew[len / sizeof(wchar_t)] = L'\0';
+
+       contentsw = malloc(len + sizeof(wchar_t));
+       if (contentsw == NULL) {
+               free(linknamew);
+               return (0);
+       }
+
+       len2 = mbsrtowcs(contentsw, &contents, (len + sizeof(wchar_t)
+           / sizeof(wchar_t)), NULL);
+
+       if (len2 > 0 && wcscmp(linknamew, contentsw) != 0)
+               ret = 1;
+
+       free(linknamew);
+       free(contentsw);
+       return (ret);
 #else
        char buff[300];
        struct stat st;