f5a5cfb411
schedule a chmod() fixup for directories. In particular, this fixes sgid handling on systems where the sgid bit is inherited from the parent directory (which means that the actual mode of the dir does not match the mode used in the mkdir() system call. It may be possible to tighten this condition a bit. In working through this, I also found a few other places where it looks like we can avoid a redundant syscall or two. I've commented those here but not yet tried to address them.
2252 lines
62 KiB
C
2252 lines
62 KiB
C
/*-
|
|
* Copyright (c) 2003-2007 Tim Kientzle
|
|
* 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
|
|
* in this position and unchanged.
|
|
* 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(S) ``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(S) 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.
|
|
*/
|
|
|
|
#include "archive_platform.h"
|
|
__FBSDID("$FreeBSD$");
|
|
|
|
#ifdef HAVE_SYS_TYPES_H
|
|
#include <sys/types.h>
|
|
#endif
|
|
#ifdef HAVE_SYS_ACL_H
|
|
#include <sys/acl.h>
|
|
#endif
|
|
#ifdef HAVE_ATTR_XATTR_H
|
|
#include <attr/xattr.h>
|
|
#endif
|
|
#ifdef HAVE_SYS_IOCTL_H
|
|
#include <sys/ioctl.h>
|
|
#endif
|
|
#ifdef HAVE_SYS_STAT_H
|
|
#include <sys/stat.h>
|
|
#endif
|
|
#ifdef HAVE_SYS_TIME_H
|
|
#include <sys/time.h>
|
|
#endif
|
|
#ifdef HAVE_SYS_UTIME_H
|
|
#include <sys/utime.h>
|
|
#endif
|
|
|
|
#ifdef HAVE_EXT2FS_EXT2_FS_H
|
|
#include <ext2fs/ext2_fs.h> /* for Linux file flags */
|
|
#endif
|
|
#ifdef HAVE_ERRNO_H
|
|
#include <errno.h>
|
|
#endif
|
|
#ifdef HAVE_FCNTL_H
|
|
#include <fcntl.h>
|
|
#endif
|
|
#ifdef HAVE_GRP_H
|
|
#include <grp.h>
|
|
#endif
|
|
#ifdef HAVE_LINUX_FS_H
|
|
#include <linux/fs.h> /* for Linux file flags */
|
|
#endif
|
|
#ifdef HAVE_LIMITS_H
|
|
#include <limits.h>
|
|
#endif
|
|
#ifdef HAVE_PWD_H
|
|
#include <pwd.h>
|
|
#endif
|
|
#include <stdio.h>
|
|
#ifdef HAVE_STDLIB_H
|
|
#include <stdlib.h>
|
|
#endif
|
|
#ifdef HAVE_STRING_H
|
|
#include <string.h>
|
|
#endif
|
|
#ifdef HAVE_UNISTD_H
|
|
#include <unistd.h>
|
|
#endif
|
|
#ifdef HAVE_UTIME_H
|
|
#include <utime.h>
|
|
#endif
|
|
|
|
#include "archive.h"
|
|
#include "archive_string.h"
|
|
#include "archive_entry.h"
|
|
#include "archive_private.h"
|
|
|
|
#ifndef O_BINARY
|
|
#define O_BINARY 0
|
|
#endif
|
|
|
|
struct fixup_entry {
|
|
struct fixup_entry *next;
|
|
mode_t mode;
|
|
int64_t mtime;
|
|
int64_t atime;
|
|
unsigned long mtime_nanos;
|
|
unsigned long atime_nanos;
|
|
unsigned long fflags_set;
|
|
int fixup; /* bitmask of what needs fixing */
|
|
char *name;
|
|
};
|
|
|
|
/*
|
|
* We use a bitmask to track which operations remain to be done for
|
|
* this file. In particular, this helps us avoid unnecessary
|
|
* operations when it's possible to take care of one step as a
|
|
* side-effect of another. For example, mkdir() can specify the mode
|
|
* for the newly-created object but symlink() cannot. This means we
|
|
* can skip chmod() if mkdir() succeeded, but we must explicitly
|
|
* chmod() if we're trying to create a directory that already exists
|
|
* (mkdir() failed) or if we're restoring a symlink. Similarly, we
|
|
* need to verify UID/GID before trying to restore SUID/SGID bits;
|
|
* that verification can occur explicitly through a stat() call or
|
|
* implicitly because of a successful chown() call.
|
|
*/
|
|
#define TODO_MODE_FORCE 0x40000000
|
|
#define TODO_MODE_BASE 0x20000000
|
|
#define TODO_SUID 0x10000000
|
|
#define TODO_SUID_CHECK 0x08000000
|
|
#define TODO_SGID 0x04000000
|
|
#define TODO_SGID_CHECK 0x02000000
|
|
#define TODO_MODE (TODO_MODE_BASE|TODO_SUID|TODO_SGID)
|
|
#define TODO_TIMES ARCHIVE_EXTRACT_TIME
|
|
#define TODO_OWNER ARCHIVE_EXTRACT_OWNER
|
|
#define TODO_FFLAGS ARCHIVE_EXTRACT_FFLAGS
|
|
#define TODO_ACLS ARCHIVE_EXTRACT_ACL
|
|
#define TODO_XATTR ARCHIVE_EXTRACT_XATTR
|
|
|
|
struct archive_write_disk {
|
|
struct archive archive;
|
|
|
|
mode_t user_umask;
|
|
struct fixup_entry *fixup_list;
|
|
struct fixup_entry *current_fixup;
|
|
uid_t user_uid;
|
|
dev_t skip_file_dev;
|
|
ino_t skip_file_ino;
|
|
|
|
gid_t (*lookup_gid)(void *private, const char *gname, gid_t gid);
|
|
void (*cleanup_gid)(void *private);
|
|
void *lookup_gid_data;
|
|
uid_t (*lookup_uid)(void *private, const char *gname, gid_t gid);
|
|
void (*cleanup_uid)(void *private);
|
|
void *lookup_uid_data;
|
|
|
|
/*
|
|
* Full path of last file to satisfy symlink checks.
|
|
*/
|
|
struct archive_string path_safe;
|
|
|
|
/*
|
|
* Cached stat data from disk for the current entry.
|
|
* If this is valid, pst points to st. Otherwise,
|
|
* pst is null.
|
|
*/
|
|
struct stat st;
|
|
struct stat *pst;
|
|
|
|
/* Information about the object being restored right now. */
|
|
struct archive_entry *entry; /* Entry being extracted. */
|
|
char *name; /* Name of entry, possibly edited. */
|
|
struct archive_string _name_data; /* backing store for 'name' */
|
|
/* Tasks remaining for this object. */
|
|
int todo;
|
|
/* Tasks deferred until end-of-archive. */
|
|
int deferred;
|
|
/* Options requested by the client. */
|
|
int flags;
|
|
/* Handle for the file we're restoring. */
|
|
int fd;
|
|
/* Current offset for writing data to the file. */
|
|
off_t offset;
|
|
/* Maximum size of file. */
|
|
off_t filesize;
|
|
/* Dir we were in before this restore; only for deep paths. */
|
|
int restore_pwd;
|
|
/* Mode we should use for this entry; affected by _PERM and umask. */
|
|
mode_t mode;
|
|
/* UID/GID to use in restoring this entry. */
|
|
uid_t uid;
|
|
gid_t gid;
|
|
/* Last offset written to disk. */
|
|
off_t last_offset;
|
|
};
|
|
|
|
/*
|
|
* Default mode for dirs created automatically (will be modified by umask).
|
|
* Note that POSIX specifies 0777 for implicity-created dirs, "modified
|
|
* by the process' file creation mask."
|
|
*/
|
|
#define DEFAULT_DIR_MODE 0777
|
|
/*
|
|
* Dir modes are restored in two steps: During the extraction, the permissions
|
|
* in the archive are modified to match the following limits. During
|
|
* the post-extract fixup pass, the permissions from the archive are
|
|
* applied.
|
|
*/
|
|
#define MINIMUM_DIR_MODE 0700
|
|
#define MAXIMUM_DIR_MODE 0775
|
|
|
|
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 *, const char *pathname);
|
|
#ifdef HAVE_FCHDIR
|
|
static void edit_deep_directories(struct archive_write_disk *ad);
|
|
#endif
|
|
static int cleanup_pathname(struct archive_write_disk *);
|
|
static int create_dir(struct archive_write_disk *, char *);
|
|
static int create_parent_dir(struct archive_write_disk *, char *);
|
|
static int older(struct stat *, struct archive_entry *);
|
|
static int restore_entry(struct archive_write_disk *);
|
|
#ifdef HAVE_POSIX_ACL
|
|
static int set_acl(struct archive_write_disk *, int fd, struct archive_entry *,
|
|
acl_type_t, int archive_entry_acl_type, const char *tn);
|
|
#endif
|
|
static int set_acls(struct archive_write_disk *);
|
|
static int set_xattrs(struct archive_write_disk *);
|
|
static int set_fflags(struct archive_write_disk *);
|
|
static int set_fflags_platform(struct archive_write_disk *, int fd,
|
|
const char *name, mode_t mode,
|
|
unsigned long fflags_set, unsigned long fflags_clear);
|
|
static int set_ownership(struct archive_write_disk *);
|
|
static int set_mode(struct archive_write_disk *, int mode);
|
|
static int set_time(struct archive_write_disk *);
|
|
static struct fixup_entry *sort_dir_list(struct fixup_entry *p);
|
|
static gid_t trivial_lookup_gid(void *, const char *, gid_t);
|
|
static uid_t trivial_lookup_uid(void *, const char *, uid_t);
|
|
|
|
|
|
static struct archive_vtable *archive_write_disk_vtable(void);
|
|
|
|
static int _archive_write_close(struct archive *);
|
|
static int _archive_write_finish(struct archive *);
|
|
static int _archive_write_header(struct archive *, struct archive_entry *);
|
|
static int _archive_write_finish_entry(struct archive *);
|
|
static ssize_t _archive_write_data(struct archive *, const void *, size_t);
|
|
static ssize_t _archive_write_data_block(struct archive *, const void *, size_t, off_t);
|
|
|
|
static int
|
|
_archive_write_disk_lazy_stat(struct archive_write_disk *a)
|
|
{
|
|
if (a->pst != NULL) {
|
|
/* Already have stat() data available. */
|
|
return (ARCHIVE_OK);
|
|
}
|
|
#ifdef HAVE_FSTAT
|
|
if (a->fd >= 0 && fstat(a->fd, &a->st) == 0) {
|
|
a->pst = &a->st;
|
|
return (ARCHIVE_OK);
|
|
}
|
|
#endif
|
|
/*
|
|
* XXX At this point, symlinks should not be hit, otherwise
|
|
* XXX a race occured. Do we want to check explicitly for that?
|
|
*/
|
|
if (lstat(a->name, &a->st) == 0) {
|
|
a->pst = &a->st;
|
|
return (ARCHIVE_OK);
|
|
}
|
|
archive_set_error(&a->archive, errno, "Couldn't stat file");
|
|
return (ARCHIVE_WARN);
|
|
}
|
|
|
|
static struct archive_vtable *
|
|
archive_write_disk_vtable(void)
|
|
{
|
|
static struct archive_vtable av;
|
|
static int inited = 0;
|
|
|
|
if (!inited) {
|
|
av.archive_write_close = _archive_write_close;
|
|
av.archive_write_finish = _archive_write_finish;
|
|
av.archive_write_header = _archive_write_header;
|
|
av.archive_write_finish_entry = _archive_write_finish_entry;
|
|
av.archive_write_data = _archive_write_data;
|
|
av.archive_write_data_block = _archive_write_data_block;
|
|
}
|
|
return (&av);
|
|
}
|
|
|
|
|
|
int
|
|
archive_write_disk_set_options(struct archive *_a, int flags)
|
|
{
|
|
struct archive_write_disk *a = (struct archive_write_disk *)_a;
|
|
|
|
a->flags = flags;
|
|
return (ARCHIVE_OK);
|
|
}
|
|
|
|
|
|
/*
|
|
* Extract this entry to disk.
|
|
*
|
|
* TODO: Validate hardlinks. According to the standards, we're
|
|
* supposed to check each extracted hardlink and squawk if it refers
|
|
* to a file that we didn't restore. I'm not entirely convinced this
|
|
* is a good idea, but more importantly: Is there any way to validate
|
|
* hardlinks without keeping a complete list of filenames from the
|
|
* entire archive?? Ugh.
|
|
*
|
|
*/
|
|
static int
|
|
_archive_write_header(struct archive *_a, struct archive_entry *entry)
|
|
{
|
|
struct archive_write_disk *a = (struct archive_write_disk *)_a;
|
|
struct fixup_entry *fe;
|
|
int ret, r;
|
|
|
|
__archive_check_magic(&a->archive, ARCHIVE_WRITE_DISK_MAGIC,
|
|
ARCHIVE_STATE_HEADER | ARCHIVE_STATE_DATA,
|
|
"archive_write_disk_header");
|
|
archive_clear_error(&a->archive);
|
|
if (a->archive.state & ARCHIVE_STATE_DATA) {
|
|
r = _archive_write_finish_entry(&a->archive);
|
|
if (r == ARCHIVE_FATAL)
|
|
return (r);
|
|
}
|
|
|
|
/* Set up for this particular entry. */
|
|
a->pst = NULL;
|
|
a->current_fixup = NULL;
|
|
a->deferred = 0;
|
|
if (a->entry) {
|
|
archive_entry_free(a->entry);
|
|
a->entry = NULL;
|
|
}
|
|
a->entry = archive_entry_clone(entry);
|
|
a->fd = -1;
|
|
a->last_offset = 0;
|
|
a->offset = 0;
|
|
a->uid = a->user_uid;
|
|
a->mode = archive_entry_mode(a->entry);
|
|
a->filesize = archive_entry_size(a->entry);
|
|
archive_strcpy(&(a->_name_data), archive_entry_pathname(a->entry));
|
|
a->name = a->_name_data.s;
|
|
archive_clear_error(&a->archive);
|
|
|
|
/*
|
|
* Clean up the requested path. This is necessary for correct
|
|
* dir restores; the dir restore logic otherwise gets messed
|
|
* up by nonsense like "dir/.".
|
|
*/
|
|
ret = cleanup_pathname(a);
|
|
if (ret != ARCHIVE_OK)
|
|
return (ret);
|
|
|
|
/*
|
|
* Set the umask to zero so we get predictable mode settings.
|
|
* This gets done on every call to _write_header in case the
|
|
* user edits their umask during the extraction for some
|
|
* reason. This will be reset before we return. Note that we
|
|
* don't need to do this in _finish_entry, as the chmod(), etc,
|
|
* system calls don't obey umask.
|
|
*/
|
|
a->user_umask = umask(0);
|
|
/* From here on, early exit requires "goto done" to clean up. */
|
|
|
|
/* Figure out what we need to do for this entry. */
|
|
a->todo = TODO_MODE_BASE;
|
|
if (a->flags & ARCHIVE_EXTRACT_PERM) {
|
|
a->todo |= TODO_MODE_FORCE; /* Be pushy about permissions. */
|
|
/*
|
|
* SGID requires an extra "check" step because we
|
|
* cannot easily predict the GID that the system will
|
|
* assign. (Different systems assign GIDs to files
|
|
* based on a variety of criteria, including process
|
|
* credentials and the gid of the enclosing
|
|
* directory.) We can only restore the SGID bit if
|
|
* the file has the right GID, and we only know the
|
|
* GID if we either set it (see set_ownership) or if
|
|
* we've actually called stat() on the file after it
|
|
* was restored. Since there are several places at
|
|
* which we might verify the GID, we need a TODO bit
|
|
* to keep track.
|
|
*/
|
|
if (a->mode & S_ISGID)
|
|
a->todo |= TODO_SGID | TODO_SGID_CHECK;
|
|
/*
|
|
* Verifying the SUID is simpler, but can still be
|
|
* done in multiple ways, hence the separate "check" bit.
|
|
*/
|
|
if (a->mode & S_ISUID)
|
|
a->todo |= TODO_SUID | TODO_SUID_CHECK;
|
|
} else {
|
|
/*
|
|
* User didn't request full permissions, so don't
|
|
* restore SUID, SGID bits and obey umask.
|
|
*/
|
|
a->mode &= ~S_ISUID;
|
|
a->mode &= ~S_ISGID;
|
|
a->mode &= ~S_ISVTX;
|
|
a->mode &= ~a->user_umask;
|
|
}
|
|
if (a->flags & ARCHIVE_EXTRACT_OWNER)
|
|
a->todo |= TODO_OWNER;
|
|
if (a->flags & ARCHIVE_EXTRACT_TIME)
|
|
a->todo |= TODO_TIMES;
|
|
if (a->flags & ARCHIVE_EXTRACT_ACL)
|
|
a->todo |= TODO_ACLS;
|
|
if (a->flags & ARCHIVE_EXTRACT_FFLAGS)
|
|
a->todo |= TODO_FFLAGS;
|
|
if (a->flags & ARCHIVE_EXTRACT_SECURE_SYMLINKS) {
|
|
ret = check_symlinks(a);
|
|
if (ret != ARCHIVE_OK)
|
|
goto done;
|
|
}
|
|
#ifdef HAVE_FCHDIR
|
|
/* If path exceeds PATH_MAX, shorten the path. */
|
|
edit_deep_directories(a);
|
|
#endif
|
|
|
|
ret = restore_entry(a);
|
|
|
|
#ifdef HAVE_FCHDIR
|
|
/* If we changed directory above, restore it here. */
|
|
if (a->restore_pwd >= 0) {
|
|
fchdir(a->restore_pwd);
|
|
close(a->restore_pwd);
|
|
a->restore_pwd = -1;
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* Fixup uses the unedited pathname from archive_entry_pathname(),
|
|
* because it is relative to the base dir and the edited path
|
|
* might be relative to some intermediate dir as a result of the
|
|
* deep restore logic.
|
|
*/
|
|
if (a->deferred & TODO_MODE) {
|
|
fe = current_fixup(a, archive_entry_pathname(entry));
|
|
fe->fixup |= TODO_MODE_BASE;
|
|
fe->mode = a->mode;
|
|
}
|
|
|
|
if (a->deferred & TODO_TIMES) {
|
|
fe = current_fixup(a, archive_entry_pathname(entry));
|
|
fe->fixup |= TODO_TIMES;
|
|
fe->mtime = archive_entry_mtime(entry);
|
|
fe->mtime_nanos = archive_entry_mtime_nsec(entry);
|
|
fe->atime = archive_entry_atime(entry);
|
|
fe->atime_nanos = archive_entry_atime_nsec(entry);
|
|
}
|
|
|
|
if (a->deferred & TODO_FFLAGS) {
|
|
fe = current_fixup(a, archive_entry_pathname(entry));
|
|
fe->fixup |= TODO_FFLAGS;
|
|
/* TODO: Complete this.. defer fflags from below. */
|
|
}
|
|
|
|
/* We've created the object and are ready to pour data into it. */
|
|
if (ret == ARCHIVE_OK)
|
|
a->archive.state = ARCHIVE_STATE_DATA;
|
|
/*
|
|
* If it's not open, tell our client not to try writing.
|
|
* In particular, dirs, links, etc, don't get written to.
|
|
*/
|
|
if (a->fd < 0) {
|
|
archive_entry_set_size(entry, 0);
|
|
a->filesize = 0;
|
|
}
|
|
done:
|
|
/* Restore the user's umask before returning. */
|
|
umask(a->user_umask);
|
|
|
|
return (ret);
|
|
}
|
|
|
|
int
|
|
archive_write_disk_set_skip_file(struct archive *_a, dev_t d, ino_t i)
|
|
{
|
|
struct archive_write_disk *a = (struct archive_write_disk *)_a;
|
|
__archive_check_magic(&a->archive, ARCHIVE_WRITE_DISK_MAGIC,
|
|
ARCHIVE_STATE_ANY, "archive_write_disk_set_skip_file");
|
|
a->skip_file_dev = d;
|
|
a->skip_file_ino = i;
|
|
return (ARCHIVE_OK);
|
|
}
|
|
|
|
static ssize_t
|
|
_archive_write_data_block(struct archive *_a,
|
|
const void *buff, size_t size, off_t offset)
|
|
{
|
|
struct archive_write_disk *a = (struct archive_write_disk *)_a;
|
|
ssize_t bytes_written = 0;
|
|
ssize_t block_size, bytes_to_write;
|
|
int r = ARCHIVE_OK;
|
|
|
|
__archive_check_magic(&a->archive, ARCHIVE_WRITE_DISK_MAGIC,
|
|
ARCHIVE_STATE_DATA, "archive_write_disk_block");
|
|
if (a->fd < 0) {
|
|
archive_set_error(&a->archive, 0, "File not open");
|
|
return (ARCHIVE_WARN);
|
|
}
|
|
archive_clear_error(&a->archive);
|
|
|
|
if (a->flags & ARCHIVE_EXTRACT_SPARSE) {
|
|
if ((r = _archive_write_disk_lazy_stat(a)) != ARCHIVE_OK)
|
|
return (r);
|
|
block_size = a->pst->st_blksize;
|
|
} else
|
|
block_size = -1;
|
|
|
|
if ((off_t)(offset + size) > a->filesize) {
|
|
size = (size_t)(a->filesize - a->offset);
|
|
archive_set_error(&a->archive, 0,
|
|
"Write request too large");
|
|
r = ARCHIVE_WARN;
|
|
}
|
|
|
|
/* Write the data. */
|
|
while (size > 0) {
|
|
if (block_size != -1) {
|
|
const char *buf;
|
|
|
|
for (buf = buff; size; ++buf, --size, ++offset) {
|
|
if (*buf != '\0')
|
|
break;
|
|
}
|
|
if (size == 0)
|
|
break;
|
|
bytes_to_write = block_size - offset % block_size;
|
|
buff = buf;
|
|
} else
|
|
bytes_to_write = size;
|
|
/* Seek if necessary to the specified offset. */
|
|
if (offset != a->last_offset) {
|
|
if (lseek(a->fd, offset, SEEK_SET) < 0) {
|
|
archive_set_error(&a->archive, errno, "Seek failed");
|
|
return (ARCHIVE_FATAL);
|
|
}
|
|
}
|
|
bytes_written = write(a->fd, buff, size);
|
|
if (bytes_written < 0) {
|
|
archive_set_error(&a->archive, errno, "Write failed");
|
|
return (ARCHIVE_WARN);
|
|
}
|
|
buff = (const char *)buff + bytes_written;
|
|
size -= bytes_written;
|
|
offset += bytes_written;
|
|
a->last_offset = a->offset = offset;
|
|
}
|
|
a->offset = offset;
|
|
return (r);
|
|
}
|
|
|
|
static ssize_t
|
|
_archive_write_data(struct archive *_a, const void *buff, size_t size)
|
|
{
|
|
struct archive_write_disk *a = (struct archive_write_disk *)_a;
|
|
int r;
|
|
|
|
__archive_check_magic(&a->archive, ARCHIVE_WRITE_DISK_MAGIC,
|
|
ARCHIVE_STATE_DATA, "archive_write_data");
|
|
if (a->fd < 0)
|
|
return (ARCHIVE_OK);
|
|
|
|
r = _archive_write_data_block(_a, buff, size, a->offset);
|
|
if (r < ARCHIVE_OK)
|
|
return (r);
|
|
return size;
|
|
}
|
|
|
|
static int
|
|
_archive_write_finish_entry(struct archive *_a)
|
|
{
|
|
struct archive_write_disk *a = (struct archive_write_disk *)_a;
|
|
int ret = ARCHIVE_OK;
|
|
|
|
__archive_check_magic(&a->archive, ARCHIVE_WRITE_DISK_MAGIC,
|
|
ARCHIVE_STATE_HEADER | ARCHIVE_STATE_DATA,
|
|
"archive_write_finish_entry");
|
|
if (a->archive.state & ARCHIVE_STATE_HEADER)
|
|
return (ARCHIVE_OK);
|
|
archive_clear_error(&a->archive);
|
|
|
|
if (a->last_offset != a->filesize && a->fd >= 0) {
|
|
if (ftruncate(a->fd, a->filesize) == -1 &&
|
|
a->filesize == 0) {
|
|
archive_set_error(&a->archive, errno,
|
|
"File size could not be restored");
|
|
return (ARCHIVE_FAILED);
|
|
}
|
|
/*
|
|
* Explicitly stat the file as some platforms might not
|
|
* implement the XSI option to extend files via ftruncate.
|
|
*/
|
|
a->pst = NULL;
|
|
if ((ret = _archive_write_disk_lazy_stat(a)) != ARCHIVE_OK)
|
|
return (ret);
|
|
if (a->st.st_size != a->filesize) {
|
|
const char nul = '\0';
|
|
if (lseek(a->fd, a->st.st_size - 1, SEEK_SET) < 0) {
|
|
archive_set_error(&a->archive, errno, "Seek failed");
|
|
return (ARCHIVE_FATAL);
|
|
}
|
|
if (write(a->fd, &nul, 1) < 0) {
|
|
archive_set_error(&a->archive, errno,
|
|
"Write to restore size failed");
|
|
return (ARCHIVE_FATAL);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Restore metadata. */
|
|
|
|
/*
|
|
* Look up the "real" UID only if we're going to need it.
|
|
* TODO: the TODO_SGID condition can be dropped here, can't it?
|
|
*/
|
|
if (a->todo & (TODO_OWNER | TODO_SUID | TODO_SGID)) {
|
|
a->uid = a->lookup_uid(a->lookup_uid_data,
|
|
archive_entry_uname(a->entry),
|
|
archive_entry_uid(a->entry));
|
|
}
|
|
/* Look up the "real" GID only if we're going to need it. */
|
|
/* TODO: the TODO_SUID condition can be dropped here, can't it? */
|
|
if (a->todo & (TODO_OWNER | TODO_SGID | TODO_SUID)) {
|
|
a->gid = a->lookup_gid(a->lookup_gid_data,
|
|
archive_entry_gname(a->entry),
|
|
archive_entry_gid(a->entry));
|
|
}
|
|
/*
|
|
* If restoring ownership, do it before trying to restore suid/sgid
|
|
* bits. If we set the owner, we know what it is and can skip
|
|
* a stat() call to examine the ownership of the file on disk.
|
|
*/
|
|
if (a->todo & TODO_OWNER)
|
|
ret = set_ownership(a);
|
|
if (a->todo & TODO_MODE) {
|
|
int r2 = set_mode(a, a->mode);
|
|
if (r2 < ret) ret = r2;
|
|
}
|
|
if (a->todo & TODO_TIMES) {
|
|
int r2 = set_time(a);
|
|
if (r2 < ret) ret = r2;
|
|
}
|
|
if (a->todo & TODO_ACLS) {
|
|
int r2 = set_acls(a);
|
|
if (r2 < ret) ret = r2;
|
|
}
|
|
if (a->todo & TODO_XATTR) {
|
|
int r2 = set_xattrs(a);
|
|
if (r2 < ret) ret = r2;
|
|
}
|
|
if (a->todo & TODO_FFLAGS) {
|
|
int r2 = set_fflags(a);
|
|
if (r2 < ret) ret = r2;
|
|
}
|
|
|
|
/* If there's an fd, we can close it now. */
|
|
if (a->fd >= 0) {
|
|
close(a->fd);
|
|
a->fd = -1;
|
|
}
|
|
/* If there's an entry, we can release it now. */
|
|
if (a->entry) {
|
|
archive_entry_free(a->entry);
|
|
a->entry = NULL;
|
|
}
|
|
a->archive.state = ARCHIVE_STATE_HEADER;
|
|
return (ret);
|
|
}
|
|
|
|
int
|
|
archive_write_disk_set_group_lookup(struct archive *_a,
|
|
void *private_data,
|
|
gid_t (*lookup_gid)(void *private, const char *gname, gid_t gid),
|
|
void (*cleanup_gid)(void *private))
|
|
{
|
|
struct archive_write_disk *a = (struct archive_write_disk *)_a;
|
|
__archive_check_magic(&a->archive, ARCHIVE_WRITE_DISK_MAGIC,
|
|
ARCHIVE_STATE_ANY, "archive_write_disk_set_group_lookup");
|
|
|
|
a->lookup_gid = lookup_gid;
|
|
a->cleanup_gid = cleanup_gid;
|
|
a->lookup_gid_data = private_data;
|
|
return (ARCHIVE_OK);
|
|
}
|
|
|
|
int
|
|
archive_write_disk_set_user_lookup(struct archive *_a,
|
|
void *private_data,
|
|
uid_t (*lookup_uid)(void *private, const char *uname, uid_t uid),
|
|
void (*cleanup_uid)(void *private))
|
|
{
|
|
struct archive_write_disk *a = (struct archive_write_disk *)_a;
|
|
__archive_check_magic(&a->archive, ARCHIVE_WRITE_DISK_MAGIC,
|
|
ARCHIVE_STATE_ANY, "archive_write_disk_set_user_lookup");
|
|
|
|
a->lookup_uid = lookup_uid;
|
|
a->cleanup_uid = cleanup_uid;
|
|
a->lookup_uid_data = private_data;
|
|
return (ARCHIVE_OK);
|
|
}
|
|
|
|
|
|
/*
|
|
* Create a new archive_write_disk object and initialize it with global state.
|
|
*/
|
|
struct archive *
|
|
archive_write_disk_new(void)
|
|
{
|
|
struct archive_write_disk *a;
|
|
|
|
a = (struct archive_write_disk *)malloc(sizeof(*a));
|
|
if (a == NULL)
|
|
return (NULL);
|
|
memset(a, 0, sizeof(*a));
|
|
a->archive.magic = ARCHIVE_WRITE_DISK_MAGIC;
|
|
/* We're ready to write a header immediately. */
|
|
a->archive.state = ARCHIVE_STATE_HEADER;
|
|
a->archive.vtable = archive_write_disk_vtable();
|
|
a->lookup_uid = trivial_lookup_uid;
|
|
a->lookup_gid = trivial_lookup_gid;
|
|
#ifdef HAVE_GETEUID
|
|
a->user_uid = geteuid();
|
|
#endif /* HAVE_GETEUID */
|
|
if (archive_string_ensure(&a->path_safe, 512) == NULL) {
|
|
free(a);
|
|
return (NULL);
|
|
}
|
|
return (&a->archive);
|
|
}
|
|
|
|
|
|
/*
|
|
* If pathname is longer than PATH_MAX, chdir to a suitable
|
|
* intermediate dir and edit the path down to a shorter suffix. Note
|
|
* that this routine never returns an error; if the chdir() attempt
|
|
* fails for any reason, we just go ahead with the long pathname. The
|
|
* object creation is likely to fail, but any error will get handled
|
|
* at that time.
|
|
*/
|
|
#ifdef HAVE_FCHDIR
|
|
static void
|
|
edit_deep_directories(struct archive_write_disk *a)
|
|
{
|
|
int ret;
|
|
char *tail = a->name;
|
|
|
|
a->restore_pwd = -1;
|
|
|
|
/* If path is short, avoid the open() below. */
|
|
if (strlen(tail) <= PATH_MAX)
|
|
return;
|
|
|
|
/* Try to record our starting dir. */
|
|
a->restore_pwd = open(".", O_RDONLY | O_BINARY);
|
|
if (a->restore_pwd < 0)
|
|
return;
|
|
|
|
/* As long as the path is too long... */
|
|
while (strlen(tail) > PATH_MAX) {
|
|
/* Locate a dir prefix shorter than PATH_MAX. */
|
|
tail += PATH_MAX - 8;
|
|
while (tail > a->name && *tail != '/')
|
|
tail--;
|
|
/* Exit if we find a too-long path component. */
|
|
if (tail <= a->name)
|
|
return;
|
|
/* Create the intermediate dir and chdir to it. */
|
|
*tail = '\0'; /* Terminate dir portion */
|
|
ret = create_dir(a, a->name);
|
|
if (ret == ARCHIVE_OK && chdir(a->name) != 0)
|
|
ret = ARCHIVE_WARN;
|
|
*tail = '/'; /* Restore the / we removed. */
|
|
if (ret != ARCHIVE_OK)
|
|
return;
|
|
tail++;
|
|
/* The chdir() succeeded; we've now shortened the path. */
|
|
a->name = tail;
|
|
}
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* The main restore function.
|
|
*/
|
|
static int
|
|
restore_entry(struct archive_write_disk *a)
|
|
{
|
|
int ret = ARCHIVE_OK, en;
|
|
|
|
if (a->flags & ARCHIVE_EXTRACT_UNLINK && !S_ISDIR(a->mode)) {
|
|
/*
|
|
* TODO: Fix this. Apparently, there are platforms
|
|
* that still allow root to hose the entire filesystem
|
|
* by unlinking a dir. The S_ISDIR() test above
|
|
* prevents us from using unlink() here if the new
|
|
* object is a dir, but that doesn't mean the old
|
|
* object isn't a dir.
|
|
*/
|
|
if (unlink(a->name) == 0) {
|
|
/* We removed it, reset cached stat. */
|
|
a->pst = NULL;
|
|
} else if (errno == ENOENT) {
|
|
/* File didn't exist, that's just as good. */
|
|
} else if (rmdir(a->name) == 0) {
|
|
/* It was a dir, but now it's gone. */
|
|
a->pst = NULL;
|
|
} else {
|
|
/* We tried, but couldn't get rid of it. */
|
|
archive_set_error(&a->archive, errno,
|
|
"Could not unlink");
|
|
return(ARCHIVE_WARN);
|
|
}
|
|
}
|
|
|
|
/* Try creating it first; if this fails, we'll try to recover. */
|
|
en = create_filesystem_object(a);
|
|
|
|
if ((en == ENOTDIR || en == ENOENT)
|
|
&& !(a->flags & ARCHIVE_EXTRACT_NO_AUTODIR)) {
|
|
/* If the parent dir doesn't exist, try creating it. */
|
|
create_parent_dir(a, a->name);
|
|
/* Now try to create the object again. */
|
|
en = create_filesystem_object(a);
|
|
}
|
|
|
|
if ((en == EISDIR || en == EEXIST)
|
|
&& (a->flags & ARCHIVE_EXTRACT_NO_OVERWRITE)) {
|
|
/* If we're not overwriting, we're done. */
|
|
archive_set_error(&a->archive, en, "Already exists");
|
|
return (ARCHIVE_WARN);
|
|
}
|
|
|
|
/*
|
|
* Some platforms return EISDIR if you call
|
|
* open(O_WRONLY | O_EXCL | O_CREAT) on a directory, some
|
|
* return EEXIST. POSIX is ambiguous, requiring EISDIR
|
|
* for open(O_WRONLY) on a dir and EEXIST for open(O_EXCL | O_CREAT)
|
|
* on an existing item.
|
|
*/
|
|
if (en == EISDIR) {
|
|
/* A dir is in the way of a non-dir, rmdir it. */
|
|
if (rmdir(a->name) != 0) {
|
|
archive_set_error(&a->archive, errno,
|
|
"Can't remove already-existing dir");
|
|
return (ARCHIVE_WARN);
|
|
}
|
|
a->pst = NULL;
|
|
/* Try again. */
|
|
en = create_filesystem_object(a);
|
|
} else if (en == EEXIST) {
|
|
/*
|
|
* We know something is in the way, but we don't know what;
|
|
* we need to find out before we go any further.
|
|
*/
|
|
if (lstat(a->name, &a->st) != 0) {
|
|
archive_set_error(&a->archive, errno,
|
|
"Can't stat existing object");
|
|
return (ARCHIVE_WARN);
|
|
}
|
|
|
|
/* TODO: if it's a symlink... */
|
|
|
|
if (a->flags & ARCHIVE_EXTRACT_NO_OVERWRITE_NEWER) {
|
|
if (!older(&(a->st), a->entry)) {
|
|
archive_set_error(&a->archive, 0,
|
|
"File on disk is not older; skipping.");
|
|
return (ARCHIVE_FAILED);
|
|
}
|
|
}
|
|
|
|
/* If it's our archive, we're done. */
|
|
if (a->skip_file_dev > 0 &&
|
|
a->skip_file_ino > 0 &&
|
|
a->st.st_dev == a->skip_file_dev &&
|
|
a->st.st_ino == a->skip_file_ino) {
|
|
archive_set_error(&a->archive, 0, "Refusing to overwrite archive");
|
|
return (ARCHIVE_FAILED);
|
|
}
|
|
|
|
if (!S_ISDIR(a->st.st_mode)) {
|
|
/* A non-dir is in the way, unlink it. */
|
|
if (unlink(a->name) != 0) {
|
|
archive_set_error(&a->archive, errno,
|
|
"Can't unlink already-existing object");
|
|
return (ARCHIVE_WARN);
|
|
}
|
|
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 (rmdir(a->name) != 0) {
|
|
archive_set_error(&a->archive, errno,
|
|
"Can't remove already-existing dir");
|
|
return (ARCHIVE_WARN);
|
|
}
|
|
/* Try again. */
|
|
en = create_filesystem_object(a);
|
|
} else {
|
|
/*
|
|
* There's a dir in the way of a dir. Don't
|
|
* waste time with rmdir()/mkdir(), just fix
|
|
* up the permissions on the existing dir.
|
|
* Note that we don't change perms on existing
|
|
* dirs unless _EXTRACT_PERM is specified.
|
|
*/
|
|
if ((a->mode != a->st.st_mode)
|
|
&& (a->todo & TODO_MODE_FORCE))
|
|
a->deferred |= (a->todo & TODO_MODE);
|
|
/* Ownership doesn't need deferred fixup. */
|
|
en = 0; /* Forget the EEXIST. */
|
|
}
|
|
}
|
|
|
|
if (en) {
|
|
/* Everything failed; give up here. */
|
|
archive_set_error(&a->archive, en, "Can't create '%s'", a->name);
|
|
return (ARCHIVE_WARN);
|
|
}
|
|
|
|
a->pst = NULL; /* Cached stat data no longer valid. */
|
|
return (ret);
|
|
}
|
|
|
|
/*
|
|
* Returns 0 if creation succeeds, or else returns errno value from
|
|
* the failed system call. Note: This function should only ever perform
|
|
* a single system call.
|
|
*/
|
|
int
|
|
create_filesystem_object(struct archive_write_disk *a)
|
|
{
|
|
/* Create the entry. */
|
|
const char *linkname;
|
|
mode_t final_mode, mode;
|
|
int r;
|
|
|
|
/* We identify hard/symlinks according to the link names. */
|
|
/* Since link(2) and symlink(2) don't handle modes, we're done here. */
|
|
linkname = archive_entry_hardlink(a->entry);
|
|
if (linkname != NULL) {
|
|
r = link(linkname, a->name) ? errno : 0;
|
|
/*
|
|
* New cpio and pax formats allow hardlink entries
|
|
* to carry data, so we may have to open the file
|
|
* for hardlink entries.
|
|
*
|
|
* If the hardlink was successfully created and
|
|
* the archive doesn't have carry data for it,
|
|
* consider it to be non-authoritive for meta data.
|
|
* This is consistent with GNU tar and BSD pax.
|
|
* If the hardlink does carry data, let the last
|
|
* archive entry decide ownership.
|
|
*/
|
|
if (r == 0 && a->filesize == 0) {
|
|
a->todo = 0;
|
|
a->deferred = 0;
|
|
} if (r == 0 && a->filesize > 0) {
|
|
a->fd = open(a->name, O_WRONLY | O_TRUNC | O_BINARY);
|
|
if (a->fd < 0)
|
|
r = errno;
|
|
}
|
|
return (r);
|
|
}
|
|
linkname = archive_entry_symlink(a->entry);
|
|
if (linkname != NULL)
|
|
return symlink(linkname, a->name) ? errno : 0;
|
|
|
|
/*
|
|
* The remaining system calls all set permissions, so let's
|
|
* try to take advantage of that to avoid an extra chmod()
|
|
* call. (Recall that umask is set to zero right now!)
|
|
*/
|
|
|
|
/* Mode we want for the final restored object (w/o file type bits). */
|
|
final_mode = a->mode & 07777;
|
|
/*
|
|
* The mode that will actually be restored in this step. Note
|
|
* that SUID, SGID, etc, require additional work to ensure
|
|
* security, so we never restore them at this point.
|
|
*/
|
|
mode = final_mode & 0777;
|
|
|
|
switch (a->mode & AE_IFMT) {
|
|
default:
|
|
/* POSIX requires that we fall through here. */
|
|
/* FALLTHROUGH */
|
|
case AE_IFREG:
|
|
a->fd = open(a->name,
|
|
O_WRONLY | O_CREAT | O_EXCL | O_BINARY, mode);
|
|
r = (a->fd < 0);
|
|
break;
|
|
case AE_IFCHR:
|
|
#ifdef HAVE_MKNOD
|
|
/* Note: we use AE_IFCHR for the case label, and
|
|
* S_IFCHR for the mknod() call. This is correct. */
|
|
r = mknod(a->name, mode | S_IFCHR,
|
|
archive_entry_rdev(a->entry));
|
|
#else
|
|
/* TODO: Find a better way to warn about our inability
|
|
* to restore a char device node. */
|
|
return (EINVAL);
|
|
#endif /* HAVE_MKNOD */
|
|
break;
|
|
case AE_IFBLK:
|
|
#ifdef HAVE_MKNOD
|
|
r = mknod(a->name, mode | S_IFBLK,
|
|
archive_entry_rdev(a->entry));
|
|
#else
|
|
/* TODO: Find a better way to warn about our inability
|
|
* to restore a block device node. */
|
|
return (EINVAL);
|
|
#endif /* HAVE_MKNOD */
|
|
break;
|
|
case AE_IFDIR:
|
|
mode = (mode | MINIMUM_DIR_MODE) & MAXIMUM_DIR_MODE;
|
|
r = mkdir(a->name, mode);
|
|
if (r == 0) {
|
|
/* Defer setting dir times. */
|
|
a->deferred |= (a->todo & TODO_TIMES);
|
|
a->todo &= ~TODO_TIMES;
|
|
/* Never use an immediate chmod(). */
|
|
/* We can't avoid the chmod() entirely if EXTRACT_PERM
|
|
* because of SysV SGID inheritance. */
|
|
if ((mode != final_mode)
|
|
|| (a->flags & ARCHIVE_EXTRACT_PERM))
|
|
a->deferred |= (a->todo & TODO_MODE);
|
|
a->todo &= ~TODO_MODE;
|
|
}
|
|
break;
|
|
case AE_IFIFO:
|
|
#ifdef HAVE_MKFIFO
|
|
r = mkfifo(a->name, mode);
|
|
#else
|
|
/* TODO: Find a better way to warn about our inability
|
|
* to restore a fifo. */
|
|
return (EINVAL);
|
|
#endif /* HAVE_MKFIFO */
|
|
break;
|
|
}
|
|
|
|
/* All the system calls above set errno on failure. */
|
|
if (r)
|
|
return (errno);
|
|
|
|
/* If we managed to set the final mode, we've avoided a chmod(). */
|
|
if (mode == final_mode)
|
|
a->todo &= ~TODO_MODE;
|
|
return (0);
|
|
}
|
|
|
|
/*
|
|
* Cleanup function for archive_extract. Mostly, this involves processing
|
|
* the fixup list, which is used to address a number of problems:
|
|
* * Dir permissions might prevent us from restoring a file in that
|
|
* dir, so we restore the dir with minimum 0700 permissions first,
|
|
* then correct the mode at the end.
|
|
* * Similarly, the act of restoring a file touches the directory
|
|
* and changes the timestamp on the dir, so we have to touch-up dir
|
|
* timestamps at the end as well.
|
|
* * Some file flags can interfere with the restore by, for example,
|
|
* preventing the creation of hardlinks to those files.
|
|
*
|
|
* Note that tar/cpio do not require that archives be in a particular
|
|
* order; there is no way to know when the last file has been restored
|
|
* within a directory, so there's no way to optimize the memory usage
|
|
* here by fixing up the directory any earlier than the
|
|
* end-of-archive.
|
|
*
|
|
* XXX TODO: Directory ACLs should be restored here, for the same
|
|
* reason we set directory perms here. XXX
|
|
*/
|
|
static int
|
|
_archive_write_close(struct archive *_a)
|
|
{
|
|
struct archive_write_disk *a = (struct archive_write_disk *)_a;
|
|
struct fixup_entry *next, *p;
|
|
int ret;
|
|
|
|
__archive_check_magic(&a->archive, ARCHIVE_WRITE_DISK_MAGIC,
|
|
ARCHIVE_STATE_HEADER | ARCHIVE_STATE_DATA,
|
|
"archive_write_disk_close");
|
|
ret = _archive_write_finish_entry(&a->archive);
|
|
|
|
/* Sort dir list so directories are fixed up in depth-first order. */
|
|
p = sort_dir_list(a->fixup_list);
|
|
|
|
while (p != NULL) {
|
|
a->pst = NULL; /* Mark stat cache as out-of-date. */
|
|
if (p->fixup & TODO_TIMES) {
|
|
#ifdef HAVE_UTIMES
|
|
/* {f,l,}utimes() are preferred, when available. */
|
|
struct timeval times[2];
|
|
times[1].tv_sec = p->mtime;
|
|
times[1].tv_usec = p->mtime_nanos / 1000;
|
|
times[0].tv_sec = p->atime;
|
|
times[0].tv_usec = p->atime_nanos / 1000;
|
|
#ifdef HAVE_LUTIMES
|
|
lutimes(p->name, times);
|
|
#else
|
|
utimes(p->name, times);
|
|
#endif
|
|
#else
|
|
/* utime() is more portable, but less precise. */
|
|
struct utimbuf times;
|
|
times.modtime = p->mtime;
|
|
times.actime = p->atime;
|
|
|
|
utime(p->name, ×);
|
|
#endif
|
|
}
|
|
if (p->fixup & TODO_MODE_BASE)
|
|
chmod(p->name, p->mode);
|
|
|
|
if (p->fixup & TODO_FFLAGS)
|
|
set_fflags_platform(a, -1, p->name,
|
|
p->mode, p->fflags_set, 0);
|
|
|
|
next = p->next;
|
|
free(p->name);
|
|
free(p);
|
|
p = next;
|
|
}
|
|
a->fixup_list = NULL;
|
|
return (ret);
|
|
}
|
|
|
|
static int
|
|
_archive_write_finish(struct archive *_a)
|
|
{
|
|
struct archive_write_disk *a = (struct archive_write_disk *)_a;
|
|
int ret;
|
|
ret = _archive_write_close(&a->archive);
|
|
if (a->cleanup_gid != NULL && a->lookup_gid_data != NULL)
|
|
(a->cleanup_gid)(a->lookup_gid_data);
|
|
if (a->cleanup_uid != NULL && a->lookup_uid_data != NULL)
|
|
(a->cleanup_uid)(a->lookup_uid_data);
|
|
archive_string_free(&a->_name_data);
|
|
archive_string_free(&a->archive.error_string);
|
|
archive_string_free(&a->path_safe);
|
|
free(a);
|
|
return (ret);
|
|
}
|
|
|
|
/*
|
|
* Simple O(n log n) merge sort to order the fixup list. In
|
|
* particular, we want to restore dir timestamps depth-first.
|
|
*/
|
|
static struct fixup_entry *
|
|
sort_dir_list(struct fixup_entry *p)
|
|
{
|
|
struct fixup_entry *a, *b, *t;
|
|
|
|
if (p == NULL)
|
|
return (NULL);
|
|
/* A one-item list is already sorted. */
|
|
if (p->next == NULL)
|
|
return (p);
|
|
|
|
/* Step 1: split the list. */
|
|
t = p;
|
|
a = p->next->next;
|
|
while (a != NULL) {
|
|
/* Step a twice, t once. */
|
|
a = a->next;
|
|
if (a != NULL)
|
|
a = a->next;
|
|
t = t->next;
|
|
}
|
|
/* Now, t is at the mid-point, so break the list here. */
|
|
b = t->next;
|
|
t->next = NULL;
|
|
a = p;
|
|
|
|
/* Step 2: Recursively sort the two sub-lists. */
|
|
a = sort_dir_list(a);
|
|
b = sort_dir_list(b);
|
|
|
|
/* Step 3: Merge the returned lists. */
|
|
/* Pick the first element for the merged list. */
|
|
if (strcmp(a->name, b->name) > 0) {
|
|
t = p = a;
|
|
a = a->next;
|
|
} else {
|
|
t = p = b;
|
|
b = b->next;
|
|
}
|
|
|
|
/* Always put the later element on the list first. */
|
|
while (a != NULL && b != NULL) {
|
|
if (strcmp(a->name, b->name) > 0) {
|
|
t->next = a;
|
|
a = a->next;
|
|
} else {
|
|
t->next = b;
|
|
b = b->next;
|
|
}
|
|
t = t->next;
|
|
}
|
|
|
|
/* Only one list is non-empty, so just splice it on. */
|
|
if (a != NULL)
|
|
t->next = a;
|
|
if (b != NULL)
|
|
t->next = b;
|
|
|
|
return (p);
|
|
}
|
|
|
|
/*
|
|
* Returns a new, initialized fixup entry.
|
|
*
|
|
* TODO: Reduce the memory requirements for this list by using a tree
|
|
* structure rather than a simple list of names.
|
|
*/
|
|
static struct fixup_entry *
|
|
new_fixup(struct archive_write_disk *a, const char *pathname)
|
|
{
|
|
struct fixup_entry *fe;
|
|
|
|
fe = (struct fixup_entry *)malloc(sizeof(struct fixup_entry));
|
|
if (fe == NULL)
|
|
return (NULL);
|
|
fe->next = a->fixup_list;
|
|
a->fixup_list = fe;
|
|
fe->fixup = 0;
|
|
fe->name = strdup(pathname);
|
|
return (fe);
|
|
}
|
|
|
|
/*
|
|
* Returns a fixup structure for the current entry.
|
|
*/
|
|
static struct fixup_entry *
|
|
current_fixup(struct archive_write_disk *a, const char *pathname)
|
|
{
|
|
if (a->current_fixup == NULL)
|
|
a->current_fixup = new_fixup(a, 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.
|
|
*/
|
|
/*
|
|
* TODO: Someday, integrate this with the deep dir support; they both
|
|
* scan the path and both can be optimized by comparing against other
|
|
* recent paths.
|
|
*/
|
|
static int
|
|
check_symlinks(struct archive_write_disk *a)
|
|
{
|
|
char *pn, *p;
|
|
char c;
|
|
int r;
|
|
struct stat st;
|
|
|
|
/*
|
|
* Guard against symlink tricks. Reject any archive entry whose
|
|
* destination would be altered by a symlink.
|
|
*/
|
|
/* Whatever we checked last time doesn't need to be re-checked. */
|
|
pn = a->name;
|
|
p = a->path_safe.s;
|
|
while ((*pn != '\0') && (*p == *pn))
|
|
++p, ++pn;
|
|
c = pn[0];
|
|
/* Keep going until we've checked the entire name. */
|
|
while (pn[0] != '\0' && (pn[0] != '/' || pn[1] != '\0')) {
|
|
/* Skip the next path element. */
|
|
while (*pn != '\0' && *pn != '/')
|
|
++pn;
|
|
c = pn[0];
|
|
pn[0] = '\0';
|
|
/* Check that we haven't hit a symlink. */
|
|
r = lstat(a->name, &st);
|
|
if (r != 0) {
|
|
/* We've hit a dir that doesn't exist; stop now. */
|
|
if (errno == ENOENT)
|
|
break;
|
|
} else if (S_ISLNK(st.st_mode)) {
|
|
if (c == '\0') {
|
|
/*
|
|
* Last element is symlink; remove it
|
|
* so we can overwrite it with the
|
|
* item being extracted.
|
|
*/
|
|
if (unlink(a->name)) {
|
|
archive_set_error(&a->archive, errno,
|
|
"Could not remove symlink %s",
|
|
a->name);
|
|
pn[0] = c;
|
|
return (ARCHIVE_WARN);
|
|
}
|
|
a->pst = NULL;
|
|
/*
|
|
* Even if we did remove it, a warning
|
|
* is in order. The warning is silly,
|
|
* though, if we're just replacing one
|
|
* symlink with another symlink.
|
|
*/
|
|
if (!S_ISLNK(a->mode)) {
|
|
archive_set_error(&a->archive, 0,
|
|
"Removing symlink %s",
|
|
a->name);
|
|
}
|
|
/* Symlink gone. No more problem! */
|
|
pn[0] = c;
|
|
return (0);
|
|
} else if (a->flags & ARCHIVE_EXTRACT_UNLINK) {
|
|
/* User asked us to remove problems. */
|
|
if (unlink(a->name) != 0) {
|
|
archive_set_error(&a->archive, 0,
|
|
"Cannot remove intervening symlink %s",
|
|
a->name);
|
|
pn[0] = c;
|
|
return (ARCHIVE_WARN);
|
|
}
|
|
a->pst = NULL;
|
|
} else {
|
|
archive_set_error(&a->archive, 0,
|
|
"Cannot extract through symlink %s",
|
|
a->name);
|
|
pn[0] = c;
|
|
return (ARCHIVE_WARN);
|
|
}
|
|
}
|
|
}
|
|
pn[0] = c;
|
|
/* We've checked and/or cleaned the whole path, so remember it. */
|
|
archive_strcpy(&a->path_safe, a->name);
|
|
return (ARCHIVE_OK);
|
|
}
|
|
|
|
/*
|
|
* Canonicalize the pathname. In particular, this strips duplicate
|
|
* '/' characters, '.' elements, and trailing '/'. It also raises an
|
|
* error for an empty path, a trailing '..' or (if _SECURE_NODOTDOT is
|
|
* set) any '..' in the path.
|
|
*/
|
|
static int
|
|
cleanup_pathname(struct archive_write_disk *a)
|
|
{
|
|
char *dest, *src;
|
|
char separator = '\0';
|
|
int lastdotdot = 0; /* True if last elt copied was '..' */
|
|
|
|
dest = src = a->name;
|
|
if (*src == '\0') {
|
|
archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC,
|
|
"Invalid empty pathname");
|
|
return (ARCHIVE_FAILED);
|
|
}
|
|
|
|
/* Skip leading '/'. */
|
|
if (*src == '/')
|
|
separator = *src++;
|
|
|
|
/* Scan the pathname one element at a time. */
|
|
for (;;) {
|
|
/* src points to first char after '/' */
|
|
if (src[0] == '\0') {
|
|
break;
|
|
} else if (src[0] == '/') {
|
|
/* Found '//', ignore second one. */
|
|
src++;
|
|
continue;
|
|
} else if (src[0] == '.') {
|
|
if (src[1] == '\0') {
|
|
/* Ignore trailing '.' */
|
|
break;
|
|
} else if (src[1] == '/') {
|
|
/* Skip './'. */
|
|
src += 2;
|
|
continue;
|
|
} else if (src[1] == '.') {
|
|
if (src[2] == '/' || src[2] == '\0') {
|
|
/* Conditionally warn about '..' */
|
|
if (a->flags & ARCHIVE_EXTRACT_SECURE_NODOTDOT) {
|
|
archive_set_error(&a->archive,
|
|
ARCHIVE_ERRNO_MISC,
|
|
"Path contains '..'");
|
|
return (ARCHIVE_FAILED);
|
|
}
|
|
lastdotdot = 1;
|
|
} else
|
|
lastdotdot = 0;
|
|
/*
|
|
* Note: Under no circumstances do we
|
|
* remove '..' elements. In
|
|
* particular, restoring
|
|
* '/foo/../bar/' should create the
|
|
* 'foo' dir as a side-effect.
|
|
*/
|
|
} else
|
|
lastdotdot = 0;
|
|
} else
|
|
lastdotdot = 0;
|
|
|
|
/* Copy current element, including leading '/'. */
|
|
if (separator)
|
|
*dest++ = '/';
|
|
while (*src != '\0' && *src != '/') {
|
|
*dest++ = *src++;
|
|
}
|
|
|
|
if (*src == '\0')
|
|
break;
|
|
|
|
/* Skip '/' separator. */
|
|
separator = *src++;
|
|
}
|
|
/*
|
|
* We've just copied zero or more path elements, not including the
|
|
* final '/'.
|
|
*/
|
|
if (lastdotdot) {
|
|
/* Trailing '..' is always wrong. */
|
|
archive_set_error(&a->archive,
|
|
ARCHIVE_ERRNO_MISC,
|
|
"Path contains trailing '..'");
|
|
return (ARCHIVE_FAILED);
|
|
}
|
|
if (dest == a->name) {
|
|
/*
|
|
* Nothing got copied. The path must have been something
|
|
* like '.' or '/' or './' or '/././././/./'.
|
|
*/
|
|
if (separator)
|
|
*dest++ = '/';
|
|
else
|
|
*dest++ = '.';
|
|
}
|
|
/* Terminate the result. */
|
|
*dest = '\0';
|
|
return (ARCHIVE_OK);
|
|
}
|
|
|
|
/*
|
|
* Create the parent directory of the specified path, assuming path
|
|
* is already in mutable storage.
|
|
*/
|
|
static int
|
|
create_parent_dir(struct archive_write_disk *a, char *path)
|
|
{
|
|
char *slash;
|
|
int r;
|
|
|
|
/* Remove tail element to obtain parent name. */
|
|
slash = strrchr(path, '/');
|
|
if (slash == NULL)
|
|
return (ARCHIVE_OK);
|
|
*slash = '\0';
|
|
r = create_dir(a, path);
|
|
*slash = '/';
|
|
return (r);
|
|
}
|
|
|
|
/*
|
|
* Create the specified dir, recursing to create parents as necessary.
|
|
*
|
|
* Returns ARCHIVE_OK if the path exists when we're done here.
|
|
* Otherwise, returns ARCHIVE_WARN.
|
|
* Assumes path is in mutable storage; path is unchanged on exit.
|
|
*/
|
|
static int
|
|
create_dir(struct archive_write_disk *a, char *path)
|
|
{
|
|
struct stat st;
|
|
struct fixup_entry *le;
|
|
char *slash, *base;
|
|
mode_t mode_final, mode;
|
|
int r;
|
|
|
|
r = ARCHIVE_OK;
|
|
|
|
/* Check for special names and just skip them. */
|
|
slash = strrchr(path, '/');
|
|
if (slash == NULL)
|
|
base = path;
|
|
else
|
|
base = slash + 1;
|
|
|
|
if (base[0] == '\0' ||
|
|
(base[0] == '.' && base[1] == '\0') ||
|
|
(base[0] == '.' && base[1] == '.' && base[2] == '\0')) {
|
|
/* Don't bother trying to create null path, '.', or '..'. */
|
|
if (slash != NULL) {
|
|
*slash = '\0';
|
|
r = create_dir(a, path);
|
|
*slash = '/';
|
|
return (r);
|
|
}
|
|
return (ARCHIVE_OK);
|
|
}
|
|
|
|
/*
|
|
* Yes, this should be stat() and not lstat(). Using lstat()
|
|
* here loses the ability to extract through symlinks. Also note
|
|
* that this should not use the a->st cache.
|
|
*/
|
|
if (stat(path, &st) == 0) {
|
|
if (S_ISDIR(st.st_mode))
|
|
return (ARCHIVE_OK);
|
|
if ((a->flags & ARCHIVE_EXTRACT_NO_OVERWRITE)) {
|
|
archive_set_error(&a->archive, EEXIST,
|
|
"Can't create directory '%s'", path);
|
|
return (ARCHIVE_WARN);
|
|
}
|
|
if (unlink(path) != 0) {
|
|
archive_set_error(&a->archive, errno,
|
|
"Can't create directory '%s': "
|
|
"Conflicting file cannot be removed");
|
|
return (ARCHIVE_WARN);
|
|
}
|
|
} else if (errno != ENOENT && errno != ENOTDIR) {
|
|
/* Stat failed? */
|
|
archive_set_error(&a->archive, errno, "Can't test directory '%s'", path);
|
|
return (ARCHIVE_WARN);
|
|
} else if (slash != NULL) {
|
|
*slash = '\0';
|
|
r = create_dir(a, path);
|
|
*slash = '/';
|
|
if (r != ARCHIVE_OK)
|
|
return (r);
|
|
}
|
|
|
|
/*
|
|
* Mode we want for the final restored directory. Per POSIX,
|
|
* implicitly-created dirs must be created obeying the umask.
|
|
* There's no mention whether this is different for privileged
|
|
* restores (which the rest of this code handles by pretending
|
|
* umask=0). I've chosen here to always obey the user's umask for
|
|
* implicit dirs, even if _EXTRACT_PERM was specified.
|
|
*/
|
|
mode_final = DEFAULT_DIR_MODE & ~a->user_umask;
|
|
/* Mode we want on disk during the restore process. */
|
|
mode = mode_final;
|
|
mode |= MINIMUM_DIR_MODE;
|
|
mode &= MAXIMUM_DIR_MODE;
|
|
if (mkdir(path, mode) == 0) {
|
|
if (mode != mode_final) {
|
|
le = new_fixup(a, path);
|
|
le->fixup |=TODO_MODE_BASE;
|
|
le->mode = mode_final;
|
|
}
|
|
return (ARCHIVE_OK);
|
|
}
|
|
|
|
/*
|
|
* Without the following check, a/b/../b/c/d fails at the
|
|
* second visit to 'b', so 'd' can't be created. Note that we
|
|
* don't add it to the fixup list here, as it's already been
|
|
* added.
|
|
*/
|
|
if (stat(path, &st) == 0 && S_ISDIR(st.st_mode))
|
|
return (ARCHIVE_OK);
|
|
|
|
archive_set_error(&a->archive, errno, "Failed to create dir '%s'", path);
|
|
return (ARCHIVE_WARN);
|
|
}
|
|
|
|
/*
|
|
* Note: Although we can skip setting the user id if the desired user
|
|
* id matches the current user, we cannot skip setting the group, as
|
|
* many systems set the gid based on the containing directory. So
|
|
* we have to perform a chown syscall if we want to set the SGID
|
|
* bit. (The alternative is to stat() and then possibly chown(); it's
|
|
* more efficient to skip the stat() and just always chown().) Note
|
|
* that a successful chown() here clears the TODO_SGID_CHECK bit, which
|
|
* allows set_mode to skip the stat() check for the GID.
|
|
*/
|
|
static int
|
|
set_ownership(struct archive_write_disk *a)
|
|
{
|
|
/* If we know we can't change it, don't bother trying. */
|
|
if (a->user_uid != 0 && a->user_uid != a->uid) {
|
|
archive_set_error(&a->archive, errno,
|
|
"Can't set UID=%d", a->uid);
|
|
return (ARCHIVE_WARN);
|
|
}
|
|
|
|
#ifdef HAVE_FCHOWN
|
|
/* If we have an fd, we can avoid a race. */
|
|
if (a->fd >= 0 && fchown(a->fd, a->uid, a->gid) == 0) {
|
|
/* We've set owner and know uid/gid are correct. */
|
|
a->todo &= ~(TODO_OWNER | TODO_SGID_CHECK | TODO_SUID_CHECK);
|
|
return (ARCHIVE_OK);
|
|
}
|
|
#endif
|
|
|
|
/* We prefer lchown() but will use chown() if that's all we have. */
|
|
/* Of course, if we have neither, this will always fail. */
|
|
#ifdef HAVE_LCHOWN
|
|
if (lchown(a->name, a->uid, a->gid) == 0) {
|
|
/* We've set owner and know uid/gid are correct. */
|
|
a->todo &= ~(TODO_OWNER | TODO_SGID_CHECK | TODO_SUID_CHECK);
|
|
return (ARCHIVE_OK);
|
|
}
|
|
#elif HAVE_CHOWN
|
|
if (!S_ISLNK(a->mode) && chown(a->name, a->uid, a->gid) == 0) {
|
|
/* We've set owner and know uid/gid are correct. */
|
|
a->todo &= ~(TODO_OWNER | TODO_SGID_CHECK | TODO_SUID_CHECK);
|
|
return (ARCHIVE_OK);
|
|
}
|
|
#endif
|
|
|
|
archive_set_error(&a->archive, errno,
|
|
"Can't set user=%d/group=%d for %s", a->uid, a->gid,
|
|
a->name);
|
|
return (ARCHIVE_WARN);
|
|
}
|
|
|
|
#ifdef HAVE_UTIMES
|
|
/*
|
|
* The utimes()-family functions provide high resolution and
|
|
* a way to set time on an fd or a symlink. We prefer them
|
|
* when they're available.
|
|
*/
|
|
static int
|
|
set_time(struct archive_write_disk *a)
|
|
{
|
|
struct timeval times[2];
|
|
|
|
times[1].tv_sec = archive_entry_mtime(a->entry);
|
|
times[1].tv_usec = archive_entry_mtime_nsec(a->entry) / 1000;
|
|
|
|
times[0].tv_sec = archive_entry_atime(a->entry);
|
|
times[0].tv_usec = archive_entry_atime_nsec(a->entry) / 1000;
|
|
|
|
#ifdef HAVE_FUTIMES
|
|
if (a->fd >= 0 && futimes(a->fd, times) == 0) {
|
|
return (ARCHIVE_OK);
|
|
}
|
|
#endif
|
|
|
|
#ifdef HAVE_LUTIMES
|
|
if (lutimes(a->name, times) != 0)
|
|
#else
|
|
if (!S_ISLNK(a->mode) && utimes(a->name, times) != 0)
|
|
#endif
|
|
{
|
|
archive_set_error(&a->archive, errno, "Can't update time for %s",
|
|
a->name);
|
|
return (ARCHIVE_WARN);
|
|
}
|
|
|
|
/*
|
|
* Note: POSIX does not provide a portable way to restore ctime.
|
|
* (Apart from resetting the system clock, which is distasteful.)
|
|
* So, any restoration of ctime will necessarily be OS-specific.
|
|
*/
|
|
|
|
/* XXX TODO: Can FreeBSD restore ctime? XXX */
|
|
return (ARCHIVE_OK);
|
|
}
|
|
#elif defined(HAVE_UTIME)
|
|
/*
|
|
* utime() is an older, more standard interface that we'll use
|
|
* if utimes() isn't available.
|
|
*/
|
|
static int
|
|
set_time(struct archive_write_disk *a)
|
|
{
|
|
struct utimbuf times;
|
|
|
|
times.modtime = archive_entry_mtime(a->entry);
|
|
times.actime = archive_entry_atime(a->entry);
|
|
if (!S_ISLNK(a->mode) && utime(a->name, ×) != 0) {
|
|
archive_set_error(&a->archive, errno,
|
|
"Can't update time for %s", a->name);
|
|
return (ARCHIVE_WARN);
|
|
}
|
|
return (ARCHIVE_OK);
|
|
}
|
|
#else
|
|
/* This platform doesn't give us a way to restore the time. */
|
|
static int
|
|
set_time(struct archive_write_disk *a)
|
|
{
|
|
(void)a; /* UNUSED */
|
|
archive_set_error(&a->archive, errno,
|
|
"Can't update time for %s", a->name);
|
|
return (ARCHIVE_WARN);
|
|
}
|
|
#endif
|
|
|
|
|
|
static int
|
|
set_mode(struct archive_write_disk *a, int mode)
|
|
{
|
|
int r = ARCHIVE_OK;
|
|
mode &= 07777; /* Strip off file type bits. */
|
|
|
|
if (a->todo & TODO_SGID_CHECK) {
|
|
/*
|
|
* If we don't know the GID is right, we must stat()
|
|
* to verify it. We can't just check the GID of this
|
|
* process, since systems sometimes set GID from
|
|
* the enclosing dir or based on ACLs.
|
|
*/
|
|
if ((r = _archive_write_disk_lazy_stat(a)) != ARCHIVE_OK)
|
|
return (r);
|
|
if (a->pst->st_gid != a->gid) {
|
|
mode &= ~ S_ISGID;
|
|
if (a->flags & ARCHIVE_EXTRACT_OWNER) {
|
|
/*
|
|
* This is only an error if you
|
|
* requested owner restore. If you
|
|
* didn't, we'll try to restore
|
|
* sgid/suid, but won't consider it a
|
|
* problem if we can't.
|
|
*/
|
|
archive_set_error(&a->archive, -1,
|
|
"Can't restore SGID bit");
|
|
r = ARCHIVE_WARN;
|
|
}
|
|
}
|
|
/* While we're here, double-check the UID. */
|
|
if (a->pst->st_uid != a->uid
|
|
&& (a->todo & TODO_SUID)) {
|
|
mode &= ~ S_ISUID;
|
|
if (a->flags & ARCHIVE_EXTRACT_OWNER) {
|
|
archive_set_error(&a->archive, -1,
|
|
"Can't restore SUID bit");
|
|
r = ARCHIVE_WARN;
|
|
}
|
|
}
|
|
a->todo &= ~TODO_SGID_CHECK;
|
|
a->todo &= ~TODO_SUID_CHECK;
|
|
} else if (a->todo & TODO_SUID_CHECK) {
|
|
/*
|
|
* If we don't know the UID is right, we can just check
|
|
* the user, since all systems set the file UID from
|
|
* the process UID.
|
|
*/
|
|
if (a->user_uid != a->uid) {
|
|
mode &= ~ S_ISUID;
|
|
if (a->flags & ARCHIVE_EXTRACT_OWNER) {
|
|
archive_set_error(&a->archive, -1,
|
|
"Can't make file SUID");
|
|
r = ARCHIVE_WARN;
|
|
}
|
|
}
|
|
a->todo &= ~TODO_SUID_CHECK;
|
|
}
|
|
|
|
if (S_ISLNK(a->mode)) {
|
|
#ifdef HAVE_LCHMOD
|
|
/*
|
|
* If this is a symlink, use lchmod(). If the
|
|
* platform doesn't support lchmod(), just skip it. A
|
|
* platform that doesn't provide a way to set
|
|
* permissions on symlinks probably ignores
|
|
* permissions on symlinks, so a failure here has no
|
|
* impact.
|
|
*/
|
|
if (lchmod(a->name, mode) != 0) {
|
|
archive_set_error(&a->archive, errno,
|
|
"Can't set permissions to 0%o", (int)mode);
|
|
r = ARCHIVE_WARN;
|
|
}
|
|
#endif
|
|
} else if (!S_ISDIR(a->mode)) {
|
|
/*
|
|
* If it's not a symlink and not a dir, then use
|
|
* fchmod() or chmod(), depending on whether we have
|
|
* an fd. Dirs get their perms set during the
|
|
* post-extract fixup, which is handled elsewhere.
|
|
*/
|
|
#ifdef HAVE_FCHMOD
|
|
if (a->fd >= 0) {
|
|
if (fchmod(a->fd, mode) != 0) {
|
|
archive_set_error(&a->archive, errno,
|
|
"Can't set permissions to 0%o", (int)mode);
|
|
r = ARCHIVE_WARN;
|
|
}
|
|
} else
|
|
#endif
|
|
/* If this platform lacks fchmod(), then
|
|
* we'll just use chmod(). */
|
|
if (chmod(a->name, mode) != 0) {
|
|
archive_set_error(&a->archive, errno,
|
|
"Can't set permissions to 0%o", (int)mode);
|
|
r = ARCHIVE_WARN;
|
|
}
|
|
}
|
|
return (r);
|
|
}
|
|
|
|
static int
|
|
set_fflags(struct archive_write_disk *a)
|
|
{
|
|
struct fixup_entry *le;
|
|
unsigned long set, clear;
|
|
int r;
|
|
int critical_flags;
|
|
mode_t mode = archive_entry_mode(a->entry);
|
|
|
|
/*
|
|
* Make 'critical_flags' hold all file flags that can't be
|
|
* immediately restored. For example, on BSD systems,
|
|
* SF_IMMUTABLE prevents hardlinks from being created, so
|
|
* should not be set until after any hardlinks are created. To
|
|
* preserve some semblance of portability, this uses #ifdef
|
|
* extensively. Ugly, but it works.
|
|
*
|
|
* Yes, Virginia, this does create a security race. It's mitigated
|
|
* somewhat by the practice of creating dirs 0700 until the extract
|
|
* is done, but it would be nice if we could do more than that.
|
|
* People restoring critical file systems should be wary of
|
|
* other programs that might try to muck with files as they're
|
|
* being restored.
|
|
*/
|
|
/* Hopefully, the compiler will optimize this mess into a constant. */
|
|
critical_flags = 0;
|
|
#ifdef SF_IMMUTABLE
|
|
critical_flags |= SF_IMMUTABLE;
|
|
#endif
|
|
#ifdef UF_IMMUTABLE
|
|
critical_flags |= UF_IMMUTABLE;
|
|
#endif
|
|
#ifdef SF_APPEND
|
|
critical_flags |= SF_APPEND;
|
|
#endif
|
|
#ifdef UF_APPEND
|
|
critical_flags |= UF_APPEND;
|
|
#endif
|
|
#ifdef EXT2_APPEND_FL
|
|
critical_flags |= EXT2_APPEND_FL;
|
|
#endif
|
|
#ifdef EXT2_IMMUTABLE_FL
|
|
critical_flags |= EXT2_IMMUTABLE_FL;
|
|
#endif
|
|
|
|
if (a->todo & TODO_FFLAGS) {
|
|
archive_entry_fflags(a->entry, &set, &clear);
|
|
|
|
/*
|
|
* The first test encourages the compiler to eliminate
|
|
* all of this if it's not necessary.
|
|
*/
|
|
if ((critical_flags != 0) && (set & critical_flags)) {
|
|
le = current_fixup(a, a->name);
|
|
le->fixup |= TODO_FFLAGS;
|
|
le->fflags_set = set;
|
|
/* Store the mode if it's not already there. */
|
|
if ((le->fixup & TODO_MODE) == 0)
|
|
le->mode = mode;
|
|
} else {
|
|
r = set_fflags_platform(a, a->fd,
|
|
a->name, mode, set, clear);
|
|
if (r != ARCHIVE_OK)
|
|
return (r);
|
|
}
|
|
}
|
|
return (ARCHIVE_OK);
|
|
}
|
|
|
|
|
|
#if ( defined(HAVE_LCHFLAGS) || defined(HAVE_CHFLAGS) || defined(HAVE_FCHFLAGS) ) && !defined(__linux)
|
|
static int
|
|
set_fflags_platform(struct archive_write_disk *a, int fd, const char *name,
|
|
mode_t mode, unsigned long set, unsigned long clear)
|
|
{
|
|
int r;
|
|
|
|
(void)mode; /* UNUSED */
|
|
if (set == 0 && clear == 0)
|
|
return (ARCHIVE_OK);
|
|
|
|
/*
|
|
* XXX Is the stat here really necessary? Or can I just use
|
|
* the 'set' flags directly? In particular, I'm not sure
|
|
* about the correct approach if we're overwriting an existing
|
|
* file that already has flags on it. XXX
|
|
*/
|
|
if ((r = _archive_write_disk_lazy_stat(a)) != ARCHIVE_OK)
|
|
return (r);
|
|
|
|
a->st.st_flags &= ~clear;
|
|
a->st.st_flags |= set;
|
|
#ifdef HAVE_FCHFLAGS
|
|
/* If platform has fchflags() and we were given an fd, use it. */
|
|
if (fd >= 0 && fchflags(fd, a->st.st_flags) == 0)
|
|
return (ARCHIVE_OK);
|
|
#endif
|
|
/*
|
|
* If we can't use the fd to set the flags, we'll use the
|
|
* pathname to set flags. We prefer lchflags() but will use
|
|
* chflags() if we must.
|
|
*/
|
|
#ifdef HAVE_LCHFLAGS
|
|
if (lchflags(name, a->st.st_flags) == 0)
|
|
return (ARCHIVE_OK);
|
|
#elif defined(HAVE_CHFLAGS)
|
|
if (S_ISLNK(a->st.st_mode)) {
|
|
archive_set_error(&a->archive, errno,
|
|
"Can't set file flags on symlink.");
|
|
return (ARCHIVE_WARN);
|
|
}
|
|
if (chflags(name, a->st.st_flags) == 0)
|
|
return (ARCHIVE_OK);
|
|
#endif
|
|
archive_set_error(&a->archive, errno,
|
|
"Failed to set file flags");
|
|
return (ARCHIVE_WARN);
|
|
}
|
|
|
|
#elif defined(__linux) && defined(EXT2_IOC_GETFLAGS) && defined(EXT2_IOC_SETFLAGS)
|
|
|
|
/*
|
|
* Linux has flags too, but uses ioctl() to access them instead of
|
|
* having a separate chflags() system call.
|
|
*/
|
|
static int
|
|
set_fflags_platform(struct archive_write_disk *a, int fd, const char *name,
|
|
mode_t mode, unsigned long set, unsigned long clear)
|
|
{
|
|
int ret;
|
|
int myfd = fd;
|
|
unsigned long newflags, oldflags;
|
|
unsigned long sf_mask = 0;
|
|
|
|
if (set == 0 && clear == 0)
|
|
return (ARCHIVE_OK);
|
|
/* Only regular files and dirs can have flags. */
|
|
if (!S_ISREG(mode) && !S_ISDIR(mode))
|
|
return (ARCHIVE_OK);
|
|
|
|
/* If we weren't given an fd, open it ourselves. */
|
|
if (myfd < 0)
|
|
myfd = open(name, O_RDONLY | O_NONBLOCK | O_BINARY);
|
|
if (myfd < 0)
|
|
return (ARCHIVE_OK);
|
|
|
|
/*
|
|
* Linux has no define for the flags that are only settable by
|
|
* the root user. This code may seem a little complex, but
|
|
* there seem to be some Linux systems that lack these
|
|
* defines. (?) The code below degrades reasonably gracefully
|
|
* if sf_mask is incomplete.
|
|
*/
|
|
#ifdef EXT2_IMMUTABLE_FL
|
|
sf_mask |= EXT2_IMMUTABLE_FL;
|
|
#endif
|
|
#ifdef EXT2_APPEND_FL
|
|
sf_mask |= EXT2_APPEND_FL;
|
|
#endif
|
|
/*
|
|
* XXX As above, this would be way simpler if we didn't have
|
|
* to read the current flags from disk. XXX
|
|
*/
|
|
ret = ARCHIVE_OK;
|
|
/* Try setting the flags as given. */
|
|
if (ioctl(myfd, EXT2_IOC_GETFLAGS, &oldflags) >= 0) {
|
|
newflags = (oldflags & ~clear) | set;
|
|
if (ioctl(myfd, EXT2_IOC_SETFLAGS, &newflags) >= 0)
|
|
goto cleanup;
|
|
if (errno != EPERM)
|
|
goto fail;
|
|
}
|
|
/* If we couldn't set all the flags, try again with a subset. */
|
|
if (ioctl(myfd, EXT2_IOC_GETFLAGS, &oldflags) >= 0) {
|
|
newflags &= ~sf_mask;
|
|
oldflags &= sf_mask;
|
|
newflags |= oldflags;
|
|
if (ioctl(myfd, EXT2_IOC_SETFLAGS, &newflags) >= 0)
|
|
goto cleanup;
|
|
}
|
|
/* We couldn't set the flags, so report the failure. */
|
|
fail:
|
|
archive_set_error(&a->archive, errno,
|
|
"Failed to set file flags");
|
|
ret = ARCHIVE_WARN;
|
|
cleanup:
|
|
if (fd < 0)
|
|
close(myfd);
|
|
return (ret);
|
|
}
|
|
|
|
#else /* Not HAVE_CHFLAGS && Not __linux */
|
|
|
|
/*
|
|
* Of course, some systems have neither BSD chflags() nor Linux' flags
|
|
* support through ioctl().
|
|
*/
|
|
static int
|
|
set_fflags_platform(struct archive_write_disk *a, int fd, const char *name,
|
|
mode_t mode, unsigned long set, unsigned long clear)
|
|
{
|
|
(void)a; /* UNUSED */
|
|
(void)fd; /* UNUSED */
|
|
(void)name; /* UNUSED */
|
|
(void)mode; /* UNUSED */
|
|
(void)set; /* UNUSED */
|
|
(void)clear; /* UNUSED */
|
|
return (ARCHIVE_OK);
|
|
}
|
|
|
|
#endif /* __linux */
|
|
|
|
#ifndef HAVE_POSIX_ACL
|
|
/* Default empty function body to satisfy mainline code. */
|
|
static int
|
|
set_acls(struct archive_write_disk *a)
|
|
{
|
|
(void)a; /* UNUSED */
|
|
return (ARCHIVE_OK);
|
|
}
|
|
|
|
#else
|
|
|
|
/*
|
|
* XXX TODO: What about ACL types other than ACCESS and DEFAULT?
|
|
*/
|
|
static int
|
|
set_acls(struct archive_write_disk *a)
|
|
{
|
|
int ret;
|
|
|
|
ret = set_acl(a, a->fd, a->entry, ACL_TYPE_ACCESS,
|
|
ARCHIVE_ENTRY_ACL_TYPE_ACCESS, "access");
|
|
if (ret != ARCHIVE_OK)
|
|
return (ret);
|
|
ret = set_acl(a, a->fd, a->entry, ACL_TYPE_DEFAULT,
|
|
ARCHIVE_ENTRY_ACL_TYPE_DEFAULT, "default");
|
|
return (ret);
|
|
}
|
|
|
|
|
|
static int
|
|
set_acl(struct archive_write_disk *a, int fd, struct archive_entry *entry,
|
|
acl_type_t acl_type, int ae_requested_type, const char *tname)
|
|
{
|
|
acl_t acl;
|
|
acl_entry_t acl_entry;
|
|
acl_permset_t acl_permset;
|
|
int ret;
|
|
int ae_type, ae_permset, ae_tag, ae_id;
|
|
uid_t ae_uid;
|
|
gid_t ae_gid;
|
|
const char *ae_name;
|
|
int entries;
|
|
const char *name;
|
|
|
|
ret = ARCHIVE_OK;
|
|
entries = archive_entry_acl_reset(entry, ae_requested_type);
|
|
if (entries == 0)
|
|
return (ARCHIVE_OK);
|
|
acl = acl_init(entries);
|
|
while (archive_entry_acl_next(entry, ae_requested_type, &ae_type,
|
|
&ae_permset, &ae_tag, &ae_id, &ae_name) == ARCHIVE_OK) {
|
|
acl_create_entry(&acl, &acl_entry);
|
|
|
|
switch (ae_tag) {
|
|
case ARCHIVE_ENTRY_ACL_USER:
|
|
acl_set_tag_type(acl_entry, ACL_USER);
|
|
ae_uid = a->lookup_uid(a->lookup_uid_data,
|
|
ae_name, ae_id);
|
|
acl_set_qualifier(acl_entry, &ae_uid);
|
|
break;
|
|
case ARCHIVE_ENTRY_ACL_GROUP:
|
|
acl_set_tag_type(acl_entry, ACL_GROUP);
|
|
ae_gid = a->lookup_gid(a->lookup_gid_data,
|
|
ae_name, ae_id);
|
|
acl_set_qualifier(acl_entry, &ae_gid);
|
|
break;
|
|
case ARCHIVE_ENTRY_ACL_USER_OBJ:
|
|
acl_set_tag_type(acl_entry, ACL_USER_OBJ);
|
|
break;
|
|
case ARCHIVE_ENTRY_ACL_GROUP_OBJ:
|
|
acl_set_tag_type(acl_entry, ACL_GROUP_OBJ);
|
|
break;
|
|
case ARCHIVE_ENTRY_ACL_MASK:
|
|
acl_set_tag_type(acl_entry, ACL_MASK);
|
|
break;
|
|
case ARCHIVE_ENTRY_ACL_OTHER:
|
|
acl_set_tag_type(acl_entry, ACL_OTHER);
|
|
break;
|
|
default:
|
|
/* XXX */
|
|
break;
|
|
}
|
|
|
|
acl_get_permset(acl_entry, &acl_permset);
|
|
acl_clear_perms(acl_permset);
|
|
if (ae_permset & ARCHIVE_ENTRY_ACL_EXECUTE)
|
|
acl_add_perm(acl_permset, ACL_EXECUTE);
|
|
if (ae_permset & ARCHIVE_ENTRY_ACL_WRITE)
|
|
acl_add_perm(acl_permset, ACL_WRITE);
|
|
if (ae_permset & ARCHIVE_ENTRY_ACL_READ)
|
|
acl_add_perm(acl_permset, ACL_READ);
|
|
}
|
|
|
|
name = archive_entry_pathname(entry);
|
|
|
|
/* Try restoring the ACL through 'fd' if we can. */
|
|
#if HAVE_ACL_SET_FD
|
|
if (fd >= 0 && acl_type == ACL_TYPE_ACCESS && acl_set_fd(fd, acl) == 0)
|
|
ret = ARCHIVE_OK;
|
|
else
|
|
#else
|
|
#if HAVE_ACL_SET_FD_NP
|
|
if (fd >= 0 && acl_set_fd_np(fd, acl, acl_type) == 0)
|
|
ret = ARCHIVE_OK;
|
|
else
|
|
#endif
|
|
#endif
|
|
if (acl_set_file(name, acl_type, acl) != 0) {
|
|
archive_set_error(&a->archive, errno, "Failed to set %s acl", tname);
|
|
ret = ARCHIVE_WARN;
|
|
}
|
|
acl_free(acl);
|
|
return (ret);
|
|
}
|
|
#endif
|
|
|
|
#if HAVE_LSETXATTR
|
|
/*
|
|
* Restore extended attributes - Linux implementation
|
|
*/
|
|
static int
|
|
set_xattrs(struct archive_write_disk *a)
|
|
{
|
|
struct archive_entry *entry = a->entry;
|
|
static int warning_done = 0;
|
|
int ret = ARCHIVE_OK;
|
|
int i = archive_entry_xattr_reset(entry);
|
|
|
|
while (i--) {
|
|
const char *name;
|
|
const void *value;
|
|
size_t size;
|
|
archive_entry_xattr_next(entry, &name, &value, &size);
|
|
if (name != NULL &&
|
|
strncmp(name, "xfsroot.", 8) != 0 &&
|
|
strncmp(name, "system.", 7) != 0) {
|
|
int e;
|
|
#if HAVE_FSETXATTR
|
|
if (a->fd >= 0)
|
|
e = fsetxattr(a->fd, name, value, size, 0);
|
|
else
|
|
#endif
|
|
{
|
|
e = lsetxattr(archive_entry_pathname(entry),
|
|
name, value, size, 0);
|
|
}
|
|
if (e == -1) {
|
|
if (errno == ENOTSUP) {
|
|
if (!warning_done) {
|
|
warning_done = 1;
|
|
archive_set_error(&a->archive, errno,
|
|
"Cannot restore extended "
|
|
"attributes on this file "
|
|
"system");
|
|
}
|
|
} else
|
|
archive_set_error(&a->archive, errno,
|
|
"Failed to set extended attribute");
|
|
ret = ARCHIVE_WARN;
|
|
}
|
|
} else {
|
|
archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT,
|
|
"Invalid extended attribute encountered");
|
|
ret = ARCHIVE_WARN;
|
|
}
|
|
}
|
|
return (ret);
|
|
}
|
|
#else
|
|
/*
|
|
* Restore extended attributes - stub implementation for unsupported systems
|
|
*/
|
|
static int
|
|
set_xattrs(struct archive_write_disk *a)
|
|
{
|
|
static int warning_done = 0;
|
|
|
|
/* If there aren't any extended attributes, then it's okay not
|
|
* to extract them, otherwise, issue a single warning. */
|
|
if (archive_entry_xattr_count(a->entry) != 0 && !warning_done) {
|
|
warning_done = 1;
|
|
archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT,
|
|
"Cannot restore extended attributes on this system");
|
|
return (ARCHIVE_WARN);
|
|
}
|
|
/* Warning was already emitted; suppress further warnings. */
|
|
return (ARCHIVE_OK);
|
|
}
|
|
#endif
|
|
|
|
|
|
/*
|
|
* Trivial implementations of gid/uid lookup functions.
|
|
* These are normally overridden by the client, but these stub
|
|
* versions ensure that we always have something that works.
|
|
*/
|
|
static gid_t
|
|
trivial_lookup_gid(void *private_data, const char *gname, gid_t gid)
|
|
{
|
|
(void)private_data; /* UNUSED */
|
|
(void)gname; /* UNUSED */
|
|
return (gid);
|
|
}
|
|
|
|
static uid_t
|
|
trivial_lookup_uid(void *private_data, const char *uname, uid_t uid)
|
|
{
|
|
(void)private_data; /* UNUSED */
|
|
(void)uname; /* UNUSED */
|
|
return (uid);
|
|
}
|
|
|
|
/*
|
|
* Test if file on disk is older than entry.
|
|
*/
|
|
static int
|
|
older(struct stat *st, struct archive_entry *entry)
|
|
{
|
|
/* First, test the seconds and return if we have a definite answer. */
|
|
/* Definitely older. */
|
|
if (st->st_mtime < archive_entry_mtime(entry))
|
|
return (1);
|
|
/* Definitely younger. */
|
|
if (st->st_mtime > archive_entry_mtime(entry))
|
|
return (0);
|
|
/* If this platform supports fractional seconds, try those. */
|
|
#if HAVE_STRUCT_STAT_ST_MTIMESPEC_TV_NSEC
|
|
/* Definitely older. */
|
|
if (st->st_mtimespec.tv_nsec < archive_entry_mtime_nsec(entry))
|
|
return (1);
|
|
/* Definitely younger. */
|
|
if (st->st_mtimespec.tv_nsec > archive_entry_mtime_nsec(entry))
|
|
return (0);
|
|
#elif HAVE_STRUCT_STAT_ST_MTIM_TV_NSEC
|
|
/* Definitely older. */
|
|
if (st->st_mtim.tv_nsec < archive_entry_mtime_nsec(entry))
|
|
return (1);
|
|
/* Definitely older. */
|
|
if (st->st_mtim.tv_nsec > archive_entry_mtime_nsec(entry))
|
|
return (0);
|
|
#else
|
|
/* This system doesn't have high-res timestamps. */
|
|
#endif
|
|
/* Same age, so not older. */
|
|
return (0);
|
|
}
|