diff options
author | Martin Matuska <martin@matuska.org> | 2019-03-26 17:56:30 +0100 |
---|---|---|
committer | Martin Matuska <martin@matuska.org> | 2019-03-26 17:56:30 +0100 |
commit | abdc6ae6638f8531f944e244ea59fd1724cb5a4f (patch) | |
tree | 0073fd36fc51f143f0012267d9daa5459224f4c3 | |
parent | 3c079320b23ddf5ef38c443569c25898ad79ddb9 (diff) | |
download | libarchive-winsymlink.tar.gz |
Add basic read and write support for symbolic links on Windowswinsymlink
Fixes #1030
-rw-r--r-- | libarchive/archive_read_disk_windows.c | 167 | ||||
-rw-r--r-- | libarchive/archive_write_disk_windows.c | 69 | ||||
-rw-r--r-- | test_utils/test_main.c | 139 |
3 files changed, 363 insertions, 12 deletions
diff --git a/libarchive/archive_read_disk_windows.c b/libarchive/archive_read_disk_windows.c index d82048de..1479a980 100644 --- a/libarchive/archive_read_disk_windows.c +++ b/libarchive/archive_read_disk_windows.c @@ -299,8 +299,146 @@ 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_handle(struct archive_entry *, HANDLE); +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) + 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_handle(struct archive_entry *entry, HANDLE h) +{ + wchar_t *linkname; + int ret; + + ret = la_linkname_from_handle(h, &linkname); + if (ret == 0) + archive_entry_copy_symlink_w(entry, linkname); + free(linkname); + + return; +} + +static void +entry_symlink_from_pathw(struct archive_entry *entry, const wchar_t *path) +{ + wchar_t *linkname; + 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 +1976,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; @@ -1935,6 +2074,26 @@ tree_current_lstat(struct tree *t) static int tree_current_is_dir(struct tree *t) { + /* + * TODO: make this work with symlinks + */ + /* + HANDLE h; + BY_HANDLE_FILE_INFORMATION bhfi; + const DWORD flag = FILE_FLAG_BACKUP_SEMANTICS; + int r; + + if (tree_current_is_physical_link(t)) { + h = CreateFileW(tree_current_path(t), 0, FILE_SHARE_READ, + NULL, OPEN_EXISTING, flag, NULL); + if (h == INVALID_HANDLE_VALUE) + return (0); + r = GetFileInformationByHandle(h, &bhfi); + CloseHandle(h); + if (r == 0) + return (0); + return (bhfi.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY); + } else */ if (t->findData) return (t->findData->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY); @@ -2135,10 +2294,14 @@ archive_read_disk_entry_from_file(struct archive *_a, return (ARCHIVE_FAILED); } entry_copy_bhfi(entry, path, &findData, &bhfi); + if (flag & FILE_FLAG_OPEN_REPARSE_POINT) + entry_symlink_from_handle(entry, h); } 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; } diff --git a/libarchive/archive_write_disk_windows.c b/libarchive/archive_write_disk_windows.c index 975555f2..c7bb1082 100644 --- a/libarchive/archive_write_disk_windows.c +++ b/libarchive/archive_write_disk_windows.c @@ -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 *, @@ -583,6 +584,64 @@ la_CreateHardLinkW(wchar_t *linkname, wchar_t *target) } 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; + + wprintf(L"%s\n", target); + 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) { LARGE_INTEGER distance; @@ -1240,7 +1299,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 +1574,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 } diff --git a/test_utils/test_main.c b/test_utils/test_main.c index defdd344..c49262ab 100644 --- a/test_utils/test_main.c +++ b/test_utils/test_main.c @@ -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,51 @@ 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); + + return (ret); } static int @@ -1606,13 +1672,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; |