From: Martin Matuska Date: Sun, 14 Apr 2019 23:50:29 +0000 (+0200) Subject: Windows symlinks: new functions and extended tar header X-Git-Tag: v3.4.0~76 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=b0ed582e68fe6e474d321934d29b0f229c0feb9f;p=thirdparty%2Flibarchive.git Windows symlinks: new functions and extended tar header 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. --- diff --git a/Makefile.am b/Makefile.am index 5cded0e8e..639936a64 100644 --- a/Makefile.am +++ b/Makefile.am @@ -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 \ diff --git a/libarchive/CMakeLists.txt b/libarchive/CMakeLists.txt index 2bc69593d..ec775bb49 100644 --- a/libarchive/CMakeLists.txt +++ b/libarchive/CMakeLists.txt @@ -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 diff --git a/libarchive/archive_entry.c b/libarchive/archive_entry.c index 34cd58115..d9d54b870 100644 --- a/libarchive/archive_entry.c +++ b/libarchive/archive_entry.c @@ -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) { diff --git a/libarchive/archive_entry.h b/libarchive/archive_entry.h index 47653f37e..f8a7e532f 100644 --- a/libarchive/archive_entry.h +++ b/libarchive/archive_entry.h @@ -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 index 000000000..9b1e3ea20 --- /dev/null +++ b/libarchive/archive_entry_misc.3 @@ -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 diff --git a/libarchive/archive_entry_private.h b/libarchive/archive_entry_private.h index c69233e68..3d569bbfc 100644 --- a/libarchive/archive_entry_private.h +++ b/libarchive/archive_entry_private.h @@ -176,6 +176,9 @@ struct archive_entry { /* Miscellaneous. */ char strmode[12]; + + /* Symlink type support */ + int ae_symlink_type; }; #endif /* ARCHIVE_ENTRY_PRIVATE_H_INCLUDED */ diff --git a/libarchive/archive_read_disk_windows.c b/libarchive/archive_read_disk_windows.c index 964de749e..ff79cc055 100644 --- a/libarchive/archive_read_disk_windows.c +++ b/libarchive/archive_read_disk_windows.c @@ -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; diff --git a/libarchive/archive_read_support_format_tar.c b/libarchive/archive_read_support_format_tar.c index 60800bb81..81c03c885 100644 --- a/libarchive/archive_read_support_format_tar.c +++ b/libarchive/archive_read_support_format_tar.c @@ -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; diff --git a/libarchive/archive_write_disk_windows.c b/libarchive/archive_write_disk_windows.c index b29389c1b..e3e65ba18 100644 --- a/libarchive/archive_write_disk_windows.c +++ b/libarchive/archive_write_disk_windows.c @@ -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()); diff --git a/libarchive/archive_write_set_format_pax.c b/libarchive/archive_write_set_format_pax.c index 5a4c45a11..cf2a1f959 100644 --- a/libarchive/archive_write_set_format_pax.c +++ b/libarchive/archive_write_set_format_pax.c @@ -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. */ diff --git a/libarchive/test/test_write_disk_symlink.c b/libarchive/test/test_write_disk_symlink.c index 43796a45d..aeadfee8a 100644 --- a/libarchive/test/test_write_disk_symlink.c +++ b/libarchive/test/test_write_disk_symlink.c @@ -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); } diff --git a/test_utils/test_main.c b/test_utils/test_main.c index d9f3b19ff..59c835ba8 100644 --- a/test_utils/test_main.c +++ b/test_utils/test_main.c @@ -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 */