diff options
Diffstat (limited to 'libarchive/archive_write_disk_windows.c')
-rw-r--r-- | libarchive/archive_write_disk_windows.c | 377 |
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); } |