]> git.ipfire.org Git - thirdparty/libarchive.git/commitdiff
Windows symlinks: new functions and extended tar header
authorMartin Matuska <martin@matuska.org>
Sun, 14 Apr 2019 23:50:29 +0000 (01:50 +0200)
committerMartin Matuska <martin@matuska.org>
Mon, 15 Apr 2019 21:36:06 +0000 (23:36 +0200)
New functions:
archive_entry_symlink_type()
archive_entry_set_symlink_type()

Suppoted value constants:
AE_SYMLINK_TYPE_UNDEFINED
AE_SYMLINK_TYPE_FILE
AE_SYMLINK_TYPE_DIRECTORY

New extended tar header:
LIBARCHIVE.symlinktype

The function archive_entry_symlink_type() retrieves and the function
archive_entry_set_symlink_type() sets the symbolic link type of an archive
entry. The information about the symbolic link type is required to properly
restore symbolic links on Microsoft Windows. If the symlink type is set
to AE_SYMLINK_TYPE_FILE or AE_SYMLINK_TYPE_DIRECTORY and a tar archive
is written, an extended tar header LIBARCHIVE.symlinktype is stored with
the value "file" or "dir". When reading symbolic links on Windows, the
link type is automatically stored in the archive_entry structure.

On unix systems, the symlink type has no effect when reading or writing
symbolic links.

12 files changed:
Makefile.am
libarchive/CMakeLists.txt
libarchive/archive_entry.c
libarchive/archive_entry.h
libarchive/archive_entry_misc.3 [new file with mode: 0644]
libarchive/archive_entry_private.h
libarchive/archive_read_disk_windows.c
libarchive/archive_read_support_format_tar.c
libarchive/archive_write_disk_windows.c
libarchive/archive_write_set_format_pax.c
libarchive/test/test_write_disk_symlink.c
test_utils/test_main.c

index 5cded0e8eec37debd1ad4274bf824a00e12f97ce..639936a64082998cb53417fca15982ac760ee3ef 100644 (file)
@@ -288,6 +288,7 @@ libarchive_man_MANS= \
        libarchive/archive_entry.3 \
        libarchive/archive_entry_acl.3 \
        libarchive/archive_entry_linkify.3 \
+       libarchive/archive_entry_misc.3 \
        libarchive/archive_entry_paths.3 \
        libarchive/archive_entry_perms.3 \
        libarchive/archive_entry_stat.3 \
index 2bc69593d038917d6a9b15a8a33dc5e332081e64..ec775bb49939e67523f95bfdff7ebcd6739488fd 100644 (file)
@@ -170,6 +170,7 @@ SET(libarchive_MANS
   archive_entry.3
   archive_entry_acl.3
   archive_entry_linkify.3
+  archive_entry_misc.3
   archive_entry_paths.3
   archive_entry_perms.3
   archive_entry_stat.3
index 34cd581155b278599eb4cb7fc98346f382e6ba15..d9d54b870e8fe5a4594bf7014c8f31238411f8c4 100644 (file)
@@ -168,6 +168,7 @@ archive_entry_clear(struct archive_entry *entry)
        archive_entry_xattr_clear(entry);
        archive_entry_sparse_clear(entry);
        free(entry->stat);
+       entry->ae_symlink_type = AE_SYMLINK_TYPE_UNDEFINED;
        memset(entry, 0, sizeof(*entry));
        return entry;
 }
@@ -202,6 +203,9 @@ archive_entry_clone(struct archive_entry *entry)
        entry2->ae_set = entry->ae_set;
        archive_mstring_copy(&entry2->ae_uname, &entry->ae_uname);
 
+       /* Copy symlink type */
+       entry2->ae_symlink_type = entry->ae_symlink_type;
+
        /* Copy encryption status */
        entry2->encryption = entry->encryption;
        
@@ -253,6 +257,7 @@ archive_entry_new2(struct archive *a)
        if (entry == NULL)
                return (NULL);
        entry->archive = a;
+       entry->ae_symlink_type = AE_SYMLINK_TYPE_UNDEFINED;
        return (entry);
 }
 
@@ -675,6 +680,12 @@ archive_entry_symlink(struct archive_entry *entry)
        return (NULL);
 }
 
+int
+archive_entry_symlink_type(struct archive_entry *entry)
+{
+       return (entry->ae_symlink_type);
+}
+
 const char *
 archive_entry_symlink_utf8(struct archive_entry *entry)
 {
@@ -1245,6 +1256,12 @@ archive_entry_set_symlink(struct archive_entry *entry, const char *linkname)
                entry->ae_set &= ~AE_SET_SYMLINK;
 }
 
+void
+archive_entry_set_symlink_type(struct archive_entry *entry, int type)
+{
+       entry->ae_symlink_type = type;
+}
+
 void
 archive_entry_set_symlink_utf8(struct archive_entry *entry, const char *linkname)
 {
index 47653f37e5a0f9cc5ff937de44105a525dbb267f..f8a7e532f2b9da924e98c7d901b9f030d261e477 100644 (file)
@@ -190,6 +190,13 @@ struct archive_entry;
 #define AE_IFDIR       ((__LA_MODE_T)0040000)
 #define AE_IFIFO       ((__LA_MODE_T)0010000)
 
+/*
+ * Symlink types
+ */
+#define AE_SYMLINK_TYPE_UNDEFINED      0
+#define AE_SYMLINK_TYPE_FILE           1
+#define AE_SYMLINK_TYPE_DIRECTORY      2
+
 /*
  * Basic object manipulation
  */
@@ -275,6 +282,7 @@ __LA_DECL int                archive_entry_size_is_set(struct archive_entry *);
 __LA_DECL const char   *archive_entry_strmode(struct archive_entry *);
 __LA_DECL const char   *archive_entry_symlink(struct archive_entry *);
 __LA_DECL const char   *archive_entry_symlink_utf8(struct archive_entry *);
+__LA_DECL int           archive_entry_symlink_type(struct archive_entry *);
 __LA_DECL const wchar_t        *archive_entry_symlink_w(struct archive_entry *);
 __LA_DECL la_int64_t    archive_entry_uid(struct archive_entry *);
 __LA_DECL const char   *archive_entry_uname(struct archive_entry *);
@@ -350,6 +358,7 @@ __LA_DECL void      archive_entry_unset_size(struct archive_entry *);
 __LA_DECL void archive_entry_copy_sourcepath(struct archive_entry *, const char *);
 __LA_DECL void archive_entry_copy_sourcepath_w(struct archive_entry *, const wchar_t *);
 __LA_DECL void archive_entry_set_symlink(struct archive_entry *, const char *);
+__LA_DECL void archive_entry_set_symlink_type(struct archive_entry *, int);
 __LA_DECL void archive_entry_set_symlink_utf8(struct archive_entry *, const char *);
 __LA_DECL void archive_entry_copy_symlink(struct archive_entry *, const char *);
 __LA_DECL void archive_entry_copy_symlink_w(struct archive_entry *, const wchar_t *);
@@ -692,7 +701,6 @@ __LA_DECL void archive_entry_linkify(struct archive_entry_linkresolver *,
     struct archive_entry **, struct archive_entry **);
 __LA_DECL struct archive_entry *archive_entry_partial_links(
     struct archive_entry_linkresolver *res, unsigned int *links);
-
 #ifdef __cplusplus
 }
 #endif
diff --git a/libarchive/archive_entry_misc.3 b/libarchive/archive_entry_misc.3
new file mode 100644 (file)
index 0000000..9b1e3ea
--- /dev/null
@@ -0,0 +1,62 @@
+.\" Copyright (c) 2019 Martin Matuska
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\"    notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\"    notice, this list of conditions and the following disclaimer in the
+.\"    documentation and/or other materials provided with the distribution.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.Dd April 15, 2019
+.Dt ARCHIVE_ENTRY_MISC 3
+.Os
+.Sh NAME
+.Nm archive_entry_symlink_type ,
+.Nm archive_entry_set_symlink_type
+.Nd miscellaneous functions for manipulating properties of archive_entry.
+.Sh LIBRARY
+Streaming Archive Library (libarchive, -larchive)
+.Sh SYNOPSIS
+.In archive_entry.h
+.Ft int
+.Fn archive_entry_symlink_type "struct archive_entry *a"
+.Ft void
+.Fn archive_entry_set_symlink_type "struct archive_entry *a" "int"
+.Sh DESCRIPTION
+The function
+.Fn archive_entry_symlink_type
+returns and the function
+.Fn archive_entry_set_symlink_type
+sets the type of the symbolic link stored in an archive entry. These functions
+have special meaning on operating systems that support multiple symbolic link
+types (e.g. Microsoft Windows).
+.Pp
+Supported values are:
+.Bl -tag -width "AE_SYMLINK_TYPE_DIRECTORY" -compact
+.It AE_SYMLINK_TYPE_UNDEFINED
+Symbolic link target type is not defined (default on unix systems)
+.It AE_SYMLINK_TYPE_FILE
+Symbolic link points to a file
+.It AE_SYMLINK_TYPE_DIRECTORY
+Symbolic link points to a directory
+.El
+.Sh SEE ALSO
+.Xr archive_entry 3 ,
+.Xr archive_entry_paths 3 ,
+.Xr archive_entry_stat 3 ,
+.Xr libarchive 3
index c69233e68bd3336a195872ee137f4539ab79081e..3d569bbfc6b5492497eea7326174205cb8966f46 100644 (file)
@@ -176,6 +176,9 @@ struct archive_entry {
 
        /* Miscellaneous. */
        char             strmode[12];
+
+       /* Symlink type support */
+       int ae_symlink_type;
 };
 
 #endif /* ARCHIVE_ENTRY_PRIVATE_H_INCLUDED */
index 964de749e8d1c5ecc98b7fb7cad890ded18e1d79..ff79cc05521d1d60367f59e72021a9aa25cf0150 100644 (file)
@@ -299,8 +299,8 @@ 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 **, int);
-static int     la_linkname_from_pathw(const wchar_t *, wchar_t **);
+static int     la_linkname_from_handle(HANDLE, wchar_t **, int *);
+static int     la_linkname_from_pathw(const wchar_t *, wchar_t **, int *);
 static void    entry_symlink_from_pathw(struct archive_entry *,
                    const wchar_t *path);
 
@@ -337,15 +337,22 @@ typedef struct _REPARSE_DATA_BUFFER {
  * outbuf is allocated in the function
  */
 static int
-la_linkname_from_handle(HANDLE h, wchar_t **linkname, int isdir)
+la_linkname_from_handle(HANDLE h, wchar_t **linkname, int *linktype)
 {
        DWORD inbytes;
        REPARSE_DATA_BUFFER *buf;
+       BY_HANDLE_FILE_INFORMATION st;
        size_t len;
        BOOL ret;
        BYTE *indata;
        wchar_t *tbuf;
 
+       ret = GetFileInformationByHandle(h, &st);
+       if (ret == 0 ||
+           (st.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) == 0) {
+               return (-1);
+       }
+
        indata = malloc(MAXIMUM_REPARSE_DATA_BUFFER_SIZE);
        ret = DeviceIoControl(h, FSCTL_GET_REPARSE_POINT, NULL, 0, indata,
            1024, &inbytes, NULL);
@@ -369,8 +376,7 @@ la_linkname_from_handle(HANDLE h, wchar_t **linkname, int isdir)
                return (-1);
        }
 
-       /* We need an extra character here to append directory slash */
-       tbuf = malloc(len + 2 * sizeof(wchar_t));
+       tbuf = malloc(len + 1 * sizeof(wchar_t));
        if (tbuf == NULL) {
                free(indata);
                return (-1);
@@ -393,42 +399,35 @@ la_linkname_from_handle(HANDLE h, wchar_t **linkname, int isdir)
                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';
-               }
-       }
+       if ((st.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0)
+               *linktype = AE_SYMLINK_TYPE_FILE;
+       else
+               *linktype = AE_SYMLINK_TYPE_DIRECTORY;
+
        return (0);
 }
 
+/*
+ * Returns AE_SYMLINK_TYPE_FILE, AE_SYMLINK_TYPE_DIRECTORY or -1 on error
+ */
 static int
-la_linkname_from_pathw(const wchar_t *path, wchar_t **outbuf)
+la_linkname_from_pathw(const wchar_t *path, wchar_t **outbuf, int *linktype)
 {
        HANDLE h;
-       DWORD attrs;
-       DWORD flag = FILE_FLAG_BACKUP_SEMANTICS |
+       const 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,
-           attrs & FILE_ATTRIBUTE_DIRECTORY);
+
+       ret = la_linkname_from_handle(h, outbuf, linktype);
        CloseHandle(h);
+
        return (ret);
 }
 
@@ -436,11 +435,15 @@ static void
 entry_symlink_from_pathw(struct archive_entry *entry, const wchar_t *path)
 {
        wchar_t *linkname = NULL;
-       int ret;
+       int ret, linktype;
 
-       ret = la_linkname_from_pathw(path, &linkname);
-       if (ret == 0)
+       ret = la_linkname_from_pathw(path, &linkname, &linktype);
+       if (ret != 0)
+               return;
+       if (linktype >= 0) {
                archive_entry_copy_symlink_w(entry, linkname);
+               archive_entry_set_symlink_type(entry, linktype);
+       }
        free(linkname);
 
        return;
index 60800bb812e55da56d55452a8f3a49ad867a213e..81c03c885939dfb0cd344ff4dae3a647e2909185 100644 (file)
@@ -1942,6 +1942,15 @@ pax_attribute(struct archive_read *a, struct tar *tar,
                        pax_time(value, &s, &n);
                        archive_entry_set_birthtime(entry, s, n);
                }
+               if (strcmp(key, "LIBARCHIVE.symlinktype") == 0) {
+                       if (strcmp(value, "file") == 0) {
+                               archive_entry_set_symlink_type(entry,
+                                   AE_SYMLINK_TYPE_FILE);
+                       } else if (strcmp(value, "dir") == 0) {
+                               archive_entry_set_symlink_type(entry,
+                                   AE_SYMLINK_TYPE_DIRECTORY);
+                       }
+               }
                if (memcmp(key, "LIBARCHIVE.xattr.", 17) == 0)
                        pax_attribute_xattr(entry, key, value);
                break;
index b29389c1b9556380fe9aff5a6c32fe0efd374833..e3e65ba18a2b1eb87483b5f4c2657aae520a390a 100644 (file)
@@ -587,13 +587,14 @@ la_CreateHardLinkW(wchar_t *linkname, wchar_t *target)
 }
 
 /*
- * Create symolic link
+ * Create file or directory symolic link
  *
- * Always creates a file symbolic link.
- * Directory symbolic links are currently not implemented.
+ * If linktype is AE_SYMLINK_TYPE_UNDEFINED (or unknown), guess linktype from
+ * the link target
  */
 static int
-la_CreateSymbolicLinkW(const wchar_t *linkname, const wchar_t *target) {
+la_CreateSymbolicLinkW(const wchar_t *linkname, const wchar_t *target,
+    int linktype) {
        static BOOLEAN (WINAPI *f)(LPCWSTR, LPCWSTR, DWORD);
        static int set;
        wchar_t *ttarget, *p;
@@ -636,14 +637,16 @@ la_CreateSymbolicLinkW(const wchar_t *linkname, const wchar_t *target) {
        *p = L'\0';
 
        /*
+        * In case of undefined symlink type we guess it from the target.
         * If the target equals ".", "..", ends with a backslash or a
-        * backslash followed by "." or ".." it always points to a directory.
-        * In this case we can safely set the directory flag.
-        * All other symlinks are created as file symlinks.
+        * backslash followed by "." or ".." we assume it is a directory
+        * symlink. In all other cases we assume a file symlink.
         */
-       if (*(p - 1) == L'\\' || (*(p - 1) == L'.' && (
+       if (linktype != AE_SYMLINK_TYPE_FILE && (
+               linktype == AE_SYMLINK_TYPE_DIRECTORY ||
+               *(p - 1) == L'\\' || (*(p - 1) == L'.' && (
            len == 1 || *(p - 2) == L'\\' || ( *(p - 2) == L'.' && (
-           len == 2 || *(p - 3) == L'\\'))))) {
+           len == 2 || *(p - 3) == L'\\')))))) {
 #if defined(SYMBOLIC_LINK_FLAG_DIRECTORY)
                flags |= SYMBOLIC_LINK_FLAG_DIRECTORY;
 #else
@@ -1638,7 +1641,8 @@ create_filesystem_object(struct archive_write_disk *a)
                return symlink(linkname, a->name) ? errno : 0;
 #else
                errno = 0;
-               r = la_CreateSymbolicLinkW((const wchar_t *)a->name, linkname);
+               r = la_CreateSymbolicLinkW((const wchar_t *)a->name, linkname,
+                   archive_entry_symlink_type(a->entry));
                if (r == 0) {
                        if (errno == 0)
                                la_dosmaperr(GetLastError());
index 5a4c45a11e022690e02dfeb15f4b2db6e0cf0b1f..cf2a1f959e341a384cb03d59bab70411537a944d 100644 (file)
@@ -1114,6 +1114,10 @@ archive_write_pax_header(struct archive_write *a,
        if (!need_extension && acl_types != 0)
                need_extension = 1;
 
+       /* If the symlink type is defined, we need an extension */
+       if (!need_extension && archive_entry_symlink_type(entry_main) > 0)
+               need_extension = 1;
+
        /*
         * Libarchive used to include these in extended headers for
         * restricted pax format, but that confused people who
@@ -1247,6 +1251,17 @@ archive_write_pax_header(struct archive_write *a,
                        archive_string_free(&entry_name);
                        return (ARCHIVE_FATAL);
                }
+
+               /* Store extended symlink information */
+               if (archive_entry_symlink_type(entry_main) ==
+                   AE_SYMLINK_TYPE_FILE) {
+                       add_pax_attr(&(pax->pax_header),
+                           "LIBARCHIVE.symlinktype", "file");
+               } else if (archive_entry_symlink_type(entry_main) ==
+                   AE_SYMLINK_TYPE_DIRECTORY) {
+                       add_pax_attr(&(pax->pax_header),
+                           "LIBARCHIVE.symlinktype", "dir");
+               }
        }
 
        /* Only regular files have data. */
index 43796a45d7030238820e2bbdf174caa07af177d0..aeadfee8a4e942adcf9fdfbfc29b59c602f82a0d 100644 (file)
@@ -208,6 +208,29 @@ DEFINE_TEST(test_write_disk_symlink)
                assertEqualIntA(ad, 0, archive_write_finish_entry(ad));
        archive_entry_free(ae);
 
+       /* Symbolic link: d1dir -> d1 */
+       assert((ae = archive_entry_new()) != NULL);
+       archive_entry_copy_pathname(ae, "d1dir");
+       archive_entry_set_mode(ae, AE_IFLNK | 0642);
+       archive_entry_set_symlink_type(ae, AE_SYMLINK_TYPE_DIRECTORY);
+       archive_entry_unset_size(ae);
+       archive_entry_copy_symlink(ae, "d1");
+       assertEqualIntA(ad, 0, r = archive_write_header(ad, ae));
+       if (r >= ARCHIVE_WARN)
+               assertEqualIntA(ad, 0, archive_write_finish_entry(ad));
+       archive_entry_free(ae);
+
+       /* Symbolic link: d1file -> d1 */
+       assert((ae = archive_entry_new()) != NULL);
+       archive_entry_copy_pathname(ae, "d1file");
+       archive_entry_set_mode(ae, AE_IFLNK | 0642);
+       archive_entry_set_symlink_type(ae, AE_SYMLINK_TYPE_FILE);
+       archive_entry_unset_size(ae);
+       archive_entry_copy_symlink(ae, "d1");
+       assertEqualIntA(ad, 0, r = archive_write_header(ad, ae));
+       if (r >= ARCHIVE_WARN)
+               assertEqualIntA(ad, 0, archive_write_finish_entry(ad));
+       archive_entry_free(ae);
 
        assertEqualInt(ARCHIVE_OK, archive_write_free(ad));
 
@@ -239,4 +262,8 @@ DEFINE_TEST(test_write_disk_symlink)
        assertIsSymlink("d1slash", "d1/", 1);
        assertIsSymlink("d1sldot", "d1/.", 1);
        assertIsSymlink("d1slddot", "d1/..", 1);
+
+       /* Test #5: symlink_type is set */
+       assertIsSymlink("d1dir", "d1", 1);
+       assertIsSymlink("d1file", "d1", 0);
 }
index d9f3b19ffe9dfc5d5acb1d57da376485b1a9a306..59c835ba81fa41efb684fccc364824e7973dced3 100644 (file)
@@ -1734,7 +1734,7 @@ is_symlink(const char *file, int line,
        char *s, *pn;
        int ret = 0;
        BYTE *indata;
-       static DWORD flag = FILE_FLAG_BACKUP_SEMANTICS |
+       const DWORD flag = FILE_FLAG_BACKUP_SEMANTICS |
            FILE_FLAG_OPEN_REPARSE_POINT;
 
        /* Replace slashes with backslashes in pathname */