summaryrefslogtreecommitdiffstats
path: root/libarchive/archive_write_disk_windows.c
diff options
context:
space:
mode:
Diffstat (limited to 'libarchive/archive_write_disk_windows.c')
-rw-r--r--libarchive/archive_write_disk_windows.c377
1 files changed, 340 insertions, 37 deletions
diff --git a/libarchive/archive_write_disk_windows.c b/libarchive/archive_write_disk_windows.c
index 78eda4a..77e36c4 100644
--- a/libarchive/archive_write_disk_windows.c
+++ b/libarchive/archive_write_disk_windows.c
@@ -165,6 +165,8 @@ struct archive_write_disk {
struct archive_entry *entry; /* Entry being extracted. */
wchar_t *name; /* Name of entry, possibly edited. */
struct archive_wstring _name_data; /* backing store for 'name' */
+ wchar_t *tmpname; /* Temporary name */
+ struct archive_wstring _tmpname_data; /* backing store for 'tmpname' */
/* Tasks remaining for this object. */
int todo;
/* Tasks deferred until end-of-archive. */
@@ -205,6 +207,8 @@ struct archive_write_disk {
#define MINIMUM_DIR_MODE 0700
#define MAXIMUM_DIR_MODE 0775
+static int disk_unlink(const wchar_t *);
+static int disk_rmdir(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 *,
@@ -213,13 +217,17 @@ static int cleanup_pathname(struct archive_write_disk *);
static int create_dir(struct archive_write_disk *, wchar_t *);
static int create_parent_dir(struct archive_write_disk *, wchar_t *);
static int la_chmod(const wchar_t *, mode_t);
+static int la_mktemp(struct archive_write_disk *);
static int older(BY_HANDLE_FILE_INFORMATION *, struct archive_entry *);
static int permissive_name_w(struct archive_write_disk *);
static int restore_entry(struct archive_write_disk *);
static int set_acls(struct archive_write_disk *, HANDLE h,
const wchar_t *, struct archive_acl *);
static int set_xattrs(struct archive_write_disk *);
+static int clear_nochange_fflags(struct archive_write_disk *);
static int set_fflags(struct archive_write_disk *);
+static int set_fflags_platform(const wchar_t *, unsigned long,
+ unsigned long);
static int set_ownership(struct archive_write_disk *);
static int set_mode(struct archive_write_disk *, int mode);
static int set_times(struct archive_write_disk *, HANDLE, int,
@@ -474,6 +482,11 @@ permissive_name_w(struct archive_write_disk *a)
{
archive_wstrncpy(&(a->_name_data), wsp, l);
}
+ else if (l > 2 && wsp[0] == L'\\' && wsp[1] == L'\\' && wsp[2] != L'\\')
+ {
+ archive_wstrncpy(&(a->_name_data), L"\\\\?\\UNC\\", 8);
+ archive_wstrncat(&(a->_name_data), wsp+2, l-2);
+ }
else
{
archive_wstrncpy(&(a->_name_data), L"\\\\?\\", 4);
@@ -524,6 +537,28 @@ exit_chmode:
return (ret);
}
+static int
+la_mktemp(struct archive_write_disk *a)
+{
+ int fd;
+ mode_t mode;
+
+ archive_wstring_empty(&(a->_tmpname_data));
+ archive_wstrcpy(&(a->_tmpname_data), a->name);
+ archive_wstrcat(&(a->_tmpname_data), L".XXXXXX");
+ a->tmpname = a->_tmpname_data.s;
+
+ fd = __archive_mkstemp(a->tmpname);
+
+ mode = a->mode & 0777 & ~a->user_umask;
+ if (la_chmod(a->tmpname, mode) == -1) {
+ la_dosmaperr(GetLastError());
+ _close(fd);
+ return -1;
+ }
+ return (fd);
+}
+
static void *
la_GetFunctionKernel32(const char *name)
{
@@ -551,8 +586,10 @@ la_CreateHardLinkW(wchar_t *linkname, wchar_t *target)
set = 1;
f = la_GetFunctionKernel32("CreateHardLinkW");
}
- if (!f)
+ if (!f) {
+ errno = ENOTSUP;
return (0);
+ }
ret = (*f)(linkname, target, NULL);
if (!ret) {
/* Under windows 2000, it is necessary to remove
@@ -577,6 +614,103 @@ la_CreateHardLinkW(wchar_t *linkname, wchar_t *target)
return (ret);
}
+/*
+ * Create file or directory symolic link
+ *
+ * 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,
+ int linktype) {
+ static BOOLEAN (WINAPI *f)(LPCWSTR, LPCWSTR, DWORD);
+ static int set;
+ wchar_t *ttarget, *p;
+ int len;
+ DWORD attrs = 0;
+ DWORD flags = 0;
+ DWORD newflags = 0;
+ BOOL ret = 0;
+
+ if (!set) {
+ set = 1;
+ f = la_GetFunctionKernel32("CreateSymbolicLinkW");
+ }
+ if (!f)
+ return (0);
+
+ len = wcslen(target);
+ if (len == 0) {
+ errno = EINVAL;
+ return(0);
+ }
+ /*
+ * When writing path targets, we need to translate slashes
+ * to backslashes
+ */
+ ttarget = malloc((len + 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';
+
+ /*
+ * 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 ".." we assume it is a directory
+ * symlink. In all other cases we assume a file symlink.
+ */
+ 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'\\')))))) {
+#if defined(SYMBOLIC_LINK_FLAG_DIRECTORY)
+ flags |= SYMBOLIC_LINK_FLAG_DIRECTORY;
+#else
+ flags |= 0x1;
+#endif
+ }
+
+#if defined(SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE)
+ newflags = flags | SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE;
+#else
+ newflags = flags | 0x2;
+#endif
+
+ /*
+ * Windows won't overwrite existing links
+ */
+ attrs = GetFileAttributesW(linkname);
+ if (attrs != INVALID_FILE_ATTRIBUTES) {
+ if (attrs & FILE_ATTRIBUTE_DIRECTORY)
+ disk_rmdir(linkname);
+ else
+ disk_unlink(linkname);
+ }
+
+ ret = (*f)(linkname, ttarget, newflags);
+ /*
+ * Prior to Windows 10 calling CreateSymbolicLinkW() will fail
+ * if SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE is set
+ */
+ if (!ret) {
+ ret = (*f)(linkname, ttarget, flags);
+ }
+ free(ttarget);
+ return (ret);
+}
+
static int
la_ftruncate(HANDLE handle, int64_t length)
{
@@ -696,10 +830,8 @@ _archive_write_disk_header(struct archive *_a, struct archive_entry *entry)
a->pst = NULL;
a->current_fixup = NULL;
a->deferred = 0;
- if (a->entry) {
- archive_entry_free(a->entry);
- a->entry = NULL;
- }
+ archive_entry_free(a->entry);
+ a->entry = NULL;
a->entry = archive_entry_clone(entry);
a->fh = INVALID_HANDLE_VALUE;
a->fd_offset = 0;
@@ -860,9 +992,11 @@ _archive_write_disk_header(struct archive *_a, struct archive_entry *entry)
}
if (a->deferred & TODO_FFLAGS) {
+ unsigned long set, clear;
+
fe = current_fixup(a, archive_entry_pathname_w(entry));
- fe->fixup |= TODO_FFLAGS;
- /* TODO: Complete this.. defer fflags from below. */
+ archive_entry_fflags(entry, &set, &clear);
+ fe->fflags_set = set;
}
/*
@@ -1143,12 +1277,20 @@ _archive_write_disk_finish_entry(struct archive *_a)
if (a->fh != INVALID_HANDLE_VALUE) {
CloseHandle(a->fh);
a->fh = INVALID_HANDLE_VALUE;
+ if (a->tmpname) {
+ /* Windows does not support atomic rename */
+ disk_unlink(a->name);
+ if (_wrename(a->tmpname, a->name) != 0) {
+ archive_set_error(&a->archive, errno,
+ "rename failed");
+ ret = ARCHIVE_FATAL;
+ }
+ a->tmpname = NULL;
+ }
}
/* If there's an entry, we can release it now. */
- if (a->entry) {
- archive_entry_free(a->entry);
- a->entry = NULL;
- }
+ archive_entry_free(a->entry);
+ a->entry = NULL;
a->archive.state = ARCHIVE_STATE_HEADER;
return (ret);
}
@@ -1239,7 +1381,7 @@ archive_write_disk_new(void)
}
static int
-disk_unlink(wchar_t *path)
+disk_unlink(const wchar_t *path)
{
wchar_t *fullname;
int r;
@@ -1254,7 +1396,7 @@ disk_unlink(wchar_t *path)
}
static int
-disk_rmdir(wchar_t *path)
+disk_rmdir(const wchar_t *path)
{
wchar_t *fullname;
int r;
@@ -1285,6 +1427,8 @@ restore_entry(struct archive_write_disk *a)
* object is a dir, but that doesn't mean the old
* object isn't a dir.
*/
+ if (a->flags & ARCHIVE_EXTRACT_CLEAR_NOCHANGE_FFLAGS)
+ (void)clear_nochange_fflags(a);
if (disk_unlink(a->name) == 0) {
/* We removed it, reset cached stat. */
a->pst = NULL;
@@ -1359,28 +1503,45 @@ restore_entry(struct archive_write_disk *a)
en = create_filesystem_object(a);
} else if (en == EEXIST) {
mode_t st_mode;
+ mode_t lst_mode;
+ BY_HANDLE_FILE_INFORMATION lst;
/*
* We know something is in the way, but we don't know what;
* we need to find out before we go any further.
*/
int r = 0;
+ int dirlnk = 0;
+
/*
* The SECURE_SYMLINK logic has already removed a
* symlink to a dir if the client wants that. So
* follow the symlink if we're creating a dir.
- */
- if (S_ISDIR(a->mode))
- r = file_information(a, a->name, &a->st, &st_mode, 0);
- /*
* If it's not a dir (or it's a broken symlink),
* then don't follow it.
+ *
+ * Windows distinguishes file and directory symlinks.
+ * A file symlink may erroneously point to a directory
+ * and a directory symlink to a file. Windows does not follow
+ * such symlinks. We always need both source and target
+ * information.
*/
- if (r != 0 || !S_ISDIR(a->mode))
- r = file_information(a, a->name, &a->st, &st_mode, 1);
+ r = file_information(a, a->name, &lst, &lst_mode, 1);
if (r != 0) {
archive_set_error(&a->archive, errno,
"Can't stat existing object");
return (ARCHIVE_FAILED);
+ } else if (S_ISLNK(lst_mode)) {
+ if (lst.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
+ dirlnk = 1;
+ /* In case of a symlink we need target information */
+ r = file_information(a, a->name, &a->st, &st_mode, 0);
+ if (r != 0) {
+ a->st = lst;
+ st_mode = lst_mode;
+ }
+ } else {
+ a->st = lst;
+ st_mode = lst_mode;
}
/*
@@ -1404,17 +1565,50 @@ restore_entry(struct archive_write_disk *a)
}
if (!S_ISDIR(st_mode)) {
- /* A non-dir is in the way, unlink it. */
- if (disk_unlink(a->name) != 0) {
- archive_set_error(&a->archive, errno,
- "Can't unlink already-existing object");
- return (ARCHIVE_FAILED);
+ if (a->flags &
+ ARCHIVE_EXTRACT_CLEAR_NOCHANGE_FFLAGS) {
+ (void)clear_nochange_fflags(a);
+ }
+ if ((a->flags & ARCHIVE_EXTRACT_SAFE_WRITES) &&
+ S_ISREG(st_mode)) {
+ int fd = la_mktemp(a);
+
+ if (fd == -1)
+ return (ARCHIVE_FAILED);
+ a->fh = (HANDLE)_get_osfhandle(fd);
+ if (a->fh == INVALID_HANDLE_VALUE)
+ return (ARCHIVE_FAILED);
+
+ a->pst = NULL;
+ en = 0;
+ } else {
+ if (dirlnk) {
+ /* Edge case: dir symlink pointing
+ * to a file */
+ if (disk_rmdir(a->name) != 0) {
+ archive_set_error(&a->archive,
+ errno, "Can't unlink "
+ "directory symlink");
+ return (ARCHIVE_FAILED);
+ }
+ } else {
+ if (disk_unlink(a->name) != 0) {
+ /* A non-dir is in the way,
+ * unlink it. */
+ archive_set_error(&a->archive,
+ errno, "Can't unlink "
+ "already-existing object");
+ return (ARCHIVE_FAILED);
+ }
+ }
+ a->pst = NULL;
+ /* Try again. */
+ en = create_filesystem_object(a);
}
- a->pst = NULL;
- /* Try again. */
- en = create_filesystem_object(a);
} else if (!S_ISDIR(a->mode)) {
/* A dir is in the way of a non-dir, rmdir it. */
+ if (a->flags & ARCHIVE_EXTRACT_CLEAR_NOCHANGE_FFLAGS)
+ (void)clear_nochange_fflags(a);
if (disk_rmdir(a->name) != 0) {
archive_set_error(&a->archive, errno,
"Can't remove already-existing dir");
@@ -1462,6 +1656,7 @@ create_filesystem_object(struct archive_write_disk *a)
wchar_t *fullname;
mode_t final_mode, mode;
int r;
+ DWORD attrs = 0;
/* We identify hard/symlinks according to the link names. */
/* Since link(2) and symlink(2) don't handle modes, we're done here. */
@@ -1475,6 +1670,20 @@ create_filesystem_object(struct archive_write_disk *a)
errno = EINVAL;
r = -1;
} else {
+ /*
+ * Unlinking and linking here is really not atomic,
+ * but doing it right, would require us to construct
+ * an mktemplink() function, and then use _wrename().
+ */
+ if (a->flags & ARCHIVE_EXTRACT_SAFE_WRITES) {
+ attrs = GetFileAttributesW(namefull);
+ if (attrs != INVALID_FILE_ATTRIBUTES) {
+ if (attrs & FILE_ATTRIBUTE_DIRECTORY)
+ disk_rmdir(namefull);
+ else
+ disk_unlink(namefull);
+ }
+ }
r = la_CreateHardLinkW(namefull, linkfull);
if (r == 0) {
la_dosmaperr(GetLastError());
@@ -1511,10 +1720,31 @@ create_filesystem_object(struct archive_write_disk *a)
}
linkname = archive_entry_symlink_w(a->entry);
if (linkname != NULL) {
+ /*
+ * Unlinking and linking here is really not atomic,
+ * but doing it right, would require us to construct
+ * an mktemplink() function, and then use _wrename().
+ */
+ attrs = GetFileAttributesW(a->name);
+ if (attrs != INVALID_FILE_ATTRIBUTES) {
+ if (attrs & FILE_ATTRIBUTE_DIRECTORY)
+ disk_rmdir(a->name);
+ else
+ disk_unlink(a->name);
+ }
#if HAVE_SYMLINK
return symlink(linkname, a->name) ? errno : 0;
#else
- return (EPERM);
+ errno = 0;
+ r = la_CreateSymbolicLinkW((const wchar_t *)a->name, linkname,
+ archive_entry_symlink_type(a->entry));
+ if (r == 0) {
+ if (errno == 0)
+ la_dosmaperr(GetLastError());
+ r = errno;
+ } else
+ r = 0;
+ return (r);
#endif
}
@@ -1538,6 +1768,7 @@ create_filesystem_object(struct archive_write_disk *a)
/* POSIX requires that we fall through here. */
/* FALLTHROUGH */
case AE_IFREG:
+ a->tmpname = NULL;
fullname = a->name;
/* O_WRONLY | O_CREAT | O_EXCL */
a->fh = CreateFileW(fullname, GENERIC_WRITE, 0, NULL,
@@ -1667,6 +1898,8 @@ _archive_write_disk_close(struct archive *_a)
la_chmod(p->name, p->mode);
if (p->fixup & TODO_ACLS)
set_acls(a, INVALID_HANDLE_VALUE, p->name, &p->acl);
+ if (p->fixup & TODO_FFLAGS)
+ set_fflags_platform(p->name, p->fflags_set, 0);
next = p->next;
archive_acl_clear(&p->acl);
free(p->name);
@@ -1690,9 +1923,9 @@ _archive_write_disk_free(struct archive *_a)
ret = _archive_write_disk_close(&a->archive);
archive_write_disk_set_group_lookup(&a->archive, NULL, NULL, NULL);
archive_write_disk_set_user_lookup(&a->archive, NULL, NULL, NULL);
- if (a->entry)
- archive_entry_free(a->entry);
+ archive_entry_free(a->entry);
archive_wstring_free(&a->_name_data);
+ archive_wstring_free(&a->_tmpname_data);
archive_string_free(&a->archive.error_string);
archive_wstring_free(&a->path_safe);
a->archive.magic = 0;
@@ -1784,6 +2017,7 @@ new_fixup(struct archive_write_disk *a, const wchar_t *pathname)
a->fixup_list = fe;
fe->fixup = 0;
fe->name = _wcsdup(pathname);
+ fe->fflags_set = 0;
return (fe);
}
@@ -1798,7 +2032,6 @@ current_fixup(struct archive_write_disk *a, const wchar_t *pathname)
return (a->current_fixup);
}
-/* TODO: Make this work. */
/*
* TODO: The deep-directory support bypasses this; disable deep directory
* support if we're doing symlink checks.
@@ -1808,7 +2041,6 @@ current_fixup(struct archive_write_disk *a, const wchar_t *pathname)
* scan the path and both can be optimized by comparing against other
* recent paths.
*/
-/* TODO: Extend this to support symlinks on Windows Vista and later. */
static int
check_symlinks(struct archive_write_disk *a)
{
@@ -1827,6 +2059,9 @@ check_symlinks(struct archive_write_disk *a)
p = a->path_safe.s;
while ((*pn != '\0') && (*p == *pn))
++p, ++pn;
+ /* Skip leading backslashes */
+ while (*pn == '\\')
+ ++pn;
c = pn[0];
/* Keep going until we've checked the entire name. */
while (pn[0] != '\0' && (pn[0] != '\\' || pn[1] != '\0')) {
@@ -1844,11 +2079,21 @@ check_symlinks(struct archive_write_disk *a)
} else if (S_ISLNK(st_mode)) {
if (c == '\0') {
/*
- * Last element is symlink; remove it
- * so we can overwrite it with the
+ * Last element is a file or directory symlink.
+ * Remove it so we can overwrite it with the
* item being extracted.
*/
- if (disk_unlink(a->name)) {
+ if (a->flags &
+ ARCHIVE_EXTRACT_CLEAR_NOCHANGE_FFLAGS) {
+ (void)clear_nochange_fflags(a);
+ }
+ if (st.dwFileAttributes &
+ FILE_ATTRIBUTE_DIRECTORY) {
+ r = disk_rmdir(a->name);
+ } else {
+ r = disk_unlink(a->name);
+ }
+ if (r) {
archive_set_error(&a->archive, errno,
"Could not remove symlink %ls",
a->name);
@@ -1872,7 +2117,17 @@ check_symlinks(struct archive_write_disk *a)
return (0);
} else if (a->flags & ARCHIVE_EXTRACT_UNLINK) {
/* User asked us to remove problems. */
- if (disk_unlink(a->name) != 0) {
+ if (a->flags &
+ ARCHIVE_EXTRACT_CLEAR_NOCHANGE_FFLAGS) {
+ (void)clear_nochange_fflags(a);
+ }
+ if (st.dwFileAttributes &
+ FILE_ATTRIBUTE_DIRECTORY) {
+ r = disk_rmdir(a->name);
+ } else {
+ r = disk_unlink(a->name);
+ }
+ if (r != 0) {
archive_set_error(&a->archive, 0,
"Cannot remove intervening "
"symlink %ls", a->name);
@@ -1888,6 +2143,8 @@ check_symlinks(struct archive_write_disk *a)
return (ARCHIVE_FAILED);
}
}
+ pn[0] = c;
+ pn++;
}
pn[0] = c;
/* We've checked and/or cleaned the whole path, so remember it. */
@@ -2438,10 +2695,56 @@ set_mode(struct archive_write_disk *a, int mode)
return (r);
}
+static int set_fflags_platform(const wchar_t *name, unsigned long fflags_set,
+ unsigned long fflags_clear)
+{
+ DWORD oldflags, newflags;
+ wchar_t *fullname;
+
+ const DWORD settable_flags =
+ FILE_ATTRIBUTE_ARCHIVE |
+ FILE_ATTRIBUTE_HIDDEN |
+ FILE_ATTRIBUTE_NORMAL |
+ FILE_ATTRIBUTE_NOT_CONTENT_INDEXED |
+ FILE_ATTRIBUTE_OFFLINE |
+ FILE_ATTRIBUTE_READONLY |
+ FILE_ATTRIBUTE_SYSTEM |
+ FILE_ATTRIBUTE_TEMPORARY;
+
+ oldflags = GetFileAttributesW(name);
+ if (oldflags == (DWORD)-1 &&
+ GetLastError() == ERROR_INVALID_NAME) {
+ fullname = __la_win_permissive_name_w(name);
+ oldflags = GetFileAttributesW(fullname);
+ }
+ if (oldflags == (DWORD)-1) {
+ la_dosmaperr(GetLastError());
+ return (ARCHIVE_WARN);
+ }
+ newflags = ((oldflags & ~fflags_clear) | fflags_set) & settable_flags;
+ if(SetFileAttributesW(name, newflags) == 0)
+ return (ARCHIVE_WARN);
+ return (ARCHIVE_OK);
+}
+
+static int
+clear_nochange_fflags(struct archive_write_disk *a)
+{
+ return (set_fflags_platform(a->name, 0, FILE_ATTRIBUTE_READONLY));
+}
+
static int
set_fflags(struct archive_write_disk *a)
{
- (void)a; /* UNUSED */
+ unsigned long set, clear;
+
+ if (a->todo & TODO_FFLAGS) {
+ archive_entry_fflags(a->entry, &set, &clear);
+ if (set == 0 && clear == 0)
+ return (ARCHIVE_OK);
+ return (set_fflags_platform(a->name, set, clear));
+
+ }
return (ARCHIVE_OK);
}