summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMartin Matuska <martin@matuska.org>2019-03-26 17:56:30 +0100
committerMartin Matuska <martin@matuska.org>2019-03-26 17:56:30 +0100
commitabdc6ae6638f8531f944e244ea59fd1724cb5a4f (patch)
tree0073fd36fc51f143f0012267d9daa5459224f4c3
parent3c079320b23ddf5ef38c443569c25898ad79ddb9 (diff)
downloadlibarchive-winsymlink.tar.gz
Add basic read and write support for symbolic links on Windowswinsymlink
Fixes #1030
-rw-r--r--libarchive/archive_read_disk_windows.c167
-rw-r--r--libarchive/archive_write_disk_windows.c69
-rw-r--r--test_utils/test_main.c139
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;