summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMartin Matuska <martin@matuska.org>2019-05-22 18:00:11 +0200
committerMartin Matuska <martin@matuska.org>2019-05-24 09:05:38 +0200
commit7e84f2662a4a482a0be510f243e2ba7c42fb4a63 (patch)
tree4a72b9368f7af472c00b05ceb112cf48347bb691
parent75489099e13cff564b6c702ae9b592d88819d72a (diff)
downloadlibarchive-no_chdir_writer.tar.gz
archive_write_disk.c: support chdir()-less operationno_chdir_writer
Fixes #1203
-rw-r--r--CMakeLists.txt8
-rw-r--r--configure.ac12
-rw-r--r--libarchive/archive.h7
-rw-r--r--libarchive/archive_disk_acl_darwin.c29
-rw-r--r--libarchive/archive_disk_acl_freebsd.c45
-rw-r--r--libarchive/archive_disk_acl_linux.c38
-rw-r--r--libarchive/archive_disk_acl_sunos.c33
-rw-r--r--libarchive/archive_write_disk.335
-rw-r--r--libarchive/archive_write_disk_posix.c677
-rw-r--r--libarchive/archive_write_disk_private.h2
-rw-r--r--libarchive/archive_write_disk_windows.c20
-rw-r--r--tar/bsdtar.c1
-rw-r--r--tar/read.c20
13 files changed, 788 insertions, 139 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 0d6b8f14..dd533d28 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1290,12 +1290,14 @@ ENDIF (CMAKE_C_COMPILER_ID MATCHES "^GNU$" OR
CHECK_SYMBOL_EXISTS(_CrtSetReportMode "crtdbg.h" HAVE__CrtSetReportMode)
CHECK_FUNCTION_EXISTS_GLIBC(arc4random_buf HAVE_ARC4RANDOM_BUF)
CHECK_FUNCTION_EXISTS_GLIBC(chflags HAVE_CHFLAGS)
+CHECK_FUNCTION_EXISTS_GLIBC(chflagsat HAVE_CHFLAGSAT)
CHECK_FUNCTION_EXISTS_GLIBC(chown HAVE_CHOWN)
CHECK_FUNCTION_EXISTS_GLIBC(chroot HAVE_CHROOT)
CHECK_FUNCTION_EXISTS_GLIBC(ctime_r HAVE_CTIME_R)
CHECK_FUNCTION_EXISTS_GLIBC(fchdir HAVE_FCHDIR)
CHECK_FUNCTION_EXISTS_GLIBC(fchflags HAVE_FCHFLAGS)
CHECK_FUNCTION_EXISTS_GLIBC(fchmod HAVE_FCHMOD)
+CHECK_FUNCTION_EXISTS_GLIBC(fchmodat HAVE_FCHMODAT)
CHECK_FUNCTION_EXISTS_GLIBC(fchown HAVE_FCHOWN)
CHECK_FUNCTION_EXISTS_GLIBC(fcntl HAVE_FCNTL)
CHECK_FUNCTION_EXISTS_GLIBC(fdopendir HAVE_FDOPENDIR)
@@ -1320,14 +1322,18 @@ CHECK_FUNCTION_EXISTS_GLIBC(lchflags HAVE_LCHFLAGS)
CHECK_FUNCTION_EXISTS_GLIBC(lchmod HAVE_LCHMOD)
CHECK_FUNCTION_EXISTS_GLIBC(lchown HAVE_LCHOWN)
CHECK_FUNCTION_EXISTS_GLIBC(link HAVE_LINK)
+CHECK_FUNCTION_EXISTS_GLIBC(linkat HAVE_LINKAT)
CHECK_FUNCTION_EXISTS_GLIBC(localtime_r HAVE_LOCALTIME_R)
CHECK_FUNCTION_EXISTS_GLIBC(lstat HAVE_LSTAT)
CHECK_FUNCTION_EXISTS_GLIBC(lutimes HAVE_LUTIMES)
CHECK_FUNCTION_EXISTS_GLIBC(mbrtowc HAVE_MBRTOWC)
CHECK_FUNCTION_EXISTS_GLIBC(memmove HAVE_MEMMOVE)
CHECK_FUNCTION_EXISTS_GLIBC(mkdir HAVE_MKDIR)
+CHECK_FUNCTION_EXISTS_GLIBC(mkdirat HAVE_MKDIRAT)
CHECK_FUNCTION_EXISTS_GLIBC(mkfifo HAVE_MKFIFO)
+CHECK_FUNCTION_EXISTS_GLIBC(mkfifoat HAVE_MKFIFOAT)
CHECK_FUNCTION_EXISTS_GLIBC(mknod HAVE_MKNOD)
+CHECK_FUNCTION_EXISTS_GLIBC(mknodat HAVE_MKNODAT)
CHECK_FUNCTION_EXISTS_GLIBC(mkstemp HAVE_MKSTEMP)
CHECK_FUNCTION_EXISTS_GLIBC(nl_langinfo HAVE_NL_LANGINFO)
CHECK_FUNCTION_EXISTS_GLIBC(openat HAVE_OPENAT)
@@ -1348,8 +1354,10 @@ CHECK_FUNCTION_EXISTS_GLIBC(strerror HAVE_STRERROR)
CHECK_FUNCTION_EXISTS_GLIBC(strncpy_s HAVE_STRNCPY_S)
CHECK_FUNCTION_EXISTS_GLIBC(strrchr HAVE_STRRCHR)
CHECK_FUNCTION_EXISTS_GLIBC(symlink HAVE_SYMLINK)
+CHECK_FUNCTION_EXISTS_GLIBC(symlinkat HAVE_SYMLINKAT)
CHECK_FUNCTION_EXISTS_GLIBC(timegm HAVE_TIMEGM)
CHECK_FUNCTION_EXISTS_GLIBC(tzset HAVE_TZSET)
+CHECK_FUNCTION_EXISTS_GLIBC(unlinkat HAVE_UNLINKAT)
CHECK_FUNCTION_EXISTS_GLIBC(unsetenv HAVE_UNSETENV)
CHECK_FUNCTION_EXISTS_GLIBC(utime HAVE_UTIME)
CHECK_FUNCTION_EXISTS_GLIBC(utimes HAVE_UTIMES)
diff --git a/configure.ac b/configure.ac
index 64f77a67..106ebff0 100644
--- a/configure.ac
+++ b/configure.ac
@@ -623,20 +623,20 @@ AC_FUNC_VPRINTF
# To avoid necessity for including windows.h or special forward declaration
# workarounds, we use 'void *' for 'struct SECURITY_ATTRIBUTES *'
AC_CHECK_STDCALL_FUNC([CreateHardLinkA],[const char *, const char *, void *])
-AC_CHECK_FUNCS([arc4random_buf chflags chown chroot ctime_r])
-AC_CHECK_FUNCS([fchdir fchflags fchmod fchown fcntl fdopendir fork])
+AC_CHECK_FUNCS([arc4random_buf chflags chflagsat chown chroot ctime_r])
+AC_CHECK_FUNCS([fchdir fchflags fchmod fchmodat fchown fcntl fdopendir fork])
AC_CHECK_FUNCS([fstat fstatat fstatfs fstatvfs ftruncate])
AC_CHECK_FUNCS([futimens futimes futimesat])
AC_CHECK_FUNCS([geteuid getpid getgrgid_r getgrnam_r])
AC_CHECK_FUNCS([getpwnam_r getpwuid_r getvfsbyname gmtime_r])
-AC_CHECK_FUNCS([lchflags lchmod lchown link localtime_r lstat lutimes])
+AC_CHECK_FUNCS([lchflags lchmod lchown link linkat localtime_r lstat lutimes])
AC_CHECK_FUNCS([mbrtowc memmove memset])
-AC_CHECK_FUNCS([mkdir mkfifo mknod mkstemp])
+AC_CHECK_FUNCS([mkdir mkdirat mkfifo mkfifoat mknod mknodat mkstemp unlinkat])
AC_CHECK_FUNCS([nl_langinfo openat pipe poll posix_spawnp readlink readlinkat])
AC_CHECK_FUNCS([readpassphrase])
AC_CHECK_FUNCS([select setenv setlocale sigaction statfs statvfs])
-AC_CHECK_FUNCS([strchr strdup strerror strncpy_s strrchr symlink timegm])
-AC_CHECK_FUNCS([tzset unsetenv utime utimensat utimes vfork])
+AC_CHECK_FUNCS([strchr strdup strerror strncpy_s strrchr symlink symlinkat])
+AC_CHECK_FUNCS([timegm tzset unsetenv utime utimensat utimes vfork])
AC_CHECK_FUNCS([wcrtomb wcscmp wcscpy wcslen wctomb wmemcmp wmemcpy wmemmove])
AC_CHECK_FUNCS([_ctime64_s _fseeki64])
AC_CHECK_FUNCS([_get_timezone _localtime64_s _mkgmtime64])
diff --git a/libarchive/archive.h b/libarchive/archive.h
index daaaf783..fe47d841 100644
--- a/libarchive/archive.h
+++ b/libarchive/archive.h
@@ -941,6 +941,13 @@ __LA_DECL int archive_write_disk_set_user_lookup(struct archive *,
void (* /* cleanup */)(void *));
__LA_DECL la_int64_t archive_write_disk_gid(struct archive *, const char *, la_int64_t);
__LA_DECL la_int64_t archive_write_disk_uid(struct archive *, const char *, la_int64_t);
+/*
+ * Set a working directory on platforms that have openat(), fstatat() and all
+ * other required ...at() functions. Once set the writer operates with a
+ * directory file descriptor. It does not call chdir() or fchdir() and is not * affected by these calls.
+ */
+__LA_DECL int archive_write_disk_set_chdir(struct archive *, const char *);
+__LA_DECL int archive_write_disk_set_fchdir(struct archive *, int);
/*
* ARCHIVE_READ_DISK API
diff --git a/libarchive/archive_disk_acl_darwin.c b/libarchive/archive_disk_acl_darwin.c
index 48ad0165..5100bd0f 100644
--- a/libarchive/archive_disk_acl_darwin.c
+++ b/libarchive/archive_disk_acl_darwin.c
@@ -542,18 +542,41 @@ archive_read_disk_entry_setup_acls(struct archive_read_disk *a,
}
int
-archive_write_disk_set_acls(struct archive *a, int fd, const char *name,
- struct archive_acl *abstract_acl, __LA_MODE_T mode)
+archive_write_disk_set_acls(struct archive *a, int parent_fd, int fd,
+ const char *name, struct archive_acl *abstract_acl, __LA_MODE_T mode)
{
int ret = ARCHIVE_OK;
+#if defined(HAVE_OPENAT) && defined(HAVE_ACL_SET_FD_NP)
+ int fd_opened = 0;
+#else
+ (void)parent_fd;
+#endif
+ (void)mode; /* UNUSED */
- (void)mode; /* UNUSED */
+#if defined(HAVE_OPENAT) && defined(HAVE_ACL_SET_FD_NP)
+ if (fd < 0 && (archive_acl_types(abstract_acl) &
+ ARCHIVE_ENTRY_ACL_TYPE_NFS4) != 0) {
+ fd = openat(parent_fd, name,
+ O_WRONLY | O_CLOEXEC | O_NOFOLLOW);
+ if (fd >= 0)
+ fd_opened = 1;
+ else {
+ archive_set_error(a, errno, "Failed to open for "
+ "setting ACL: %s", name);
+ return (ARCHIVE_WARN);
+ }
+ }
+#endif
if ((archive_acl_types(abstract_acl) &
ARCHIVE_ENTRY_ACL_TYPE_NFS4) != 0) {
ret = set_acl(a, fd, name, abstract_acl,
ARCHIVE_ENTRY_ACL_TYPE_NFS4, "nfs4");
}
+#if defined(HAVE_OPENAT) && defined(HAVE_ACL_SET_FD_NP)
+ if (fd_opened)
+ close(fd);
+#endif
return (ret);
}
#endif /* ARCHIVE_ACL_DARWIN */
diff --git a/libarchive/archive_disk_acl_freebsd.c b/libarchive/archive_disk_acl_freebsd.c
index aba41e5d..cb243c32 100644
--- a/libarchive/archive_disk_acl_freebsd.c
+++ b/libarchive/archive_disk_acl_freebsd.c
@@ -666,21 +666,50 @@ archive_read_disk_entry_setup_acls(struct archive_read_disk *a,
}
int
-archive_write_disk_set_acls(struct archive *a, int fd, const char *name,
- struct archive_acl *abstract_acl, __LA_MODE_T mode)
+archive_write_disk_set_acls(struct archive *a, int parent_fd, int fd,
+ const char *name, struct archive_acl *abstract_acl, __LA_MODE_T mode)
{
int ret = ARCHIVE_OK;
-
+#if defined(HAVE_OPENAT) && defined(HAVE_ACL_SET_FD_NP)
+ int fd_opened = 0;
+#else
+ (void)parent_fd;
+#endif
(void)mode; /* UNUSED */
+
+#if defined(HAVE_OPENAT) && defined(HAVE_ACL_SET_FD_NP)
+ if (fd < 0 && (archive_acl_types(abstract_acl) &
+ (ARCHIVE_ENTRY_ACL_TYPE_POSIX1E
+#if ARCHIVE_ACL_FREEBSD_NFS4
+ | ARCHIVE_ENTRY_ACL_TYPE_NFS4
+#endif
+ )) != 0) {
+ fd = openat(parent_fd, name,
+ O_WRONLY | O_CLOEXEC | O_NOFOLLOW);
+ if (fd >= 0)
+ fd_opened = 1;
+ else {
+ archive_set_error(a, errno, "Failed to open for "
+ "setting ACL: %s", name);
+ return (ARCHIVE_WARN);
+ }
+ }
+#endif
+
if ((archive_acl_types(abstract_acl)
& ARCHIVE_ENTRY_ACL_TYPE_POSIX1E) != 0) {
if ((archive_acl_types(abstract_acl)
& ARCHIVE_ENTRY_ACL_TYPE_ACCESS) != 0) {
ret = set_acl(a, fd, name, abstract_acl,
ARCHIVE_ENTRY_ACL_TYPE_ACCESS, "access");
- if (ret != ARCHIVE_OK)
+ if (ret != ARCHIVE_OK) {
+#if defined(HAVE_OPENAT) && defined(HAVE_ACL_SET_FD_NP)
+ if (fd_opened)
+ close(fd);
+#endif
return (ret);
+ }
}
if ((archive_acl_types(abstract_acl)
& ARCHIVE_ENTRY_ACL_TYPE_DEFAULT) != 0)
@@ -688,6 +717,10 @@ archive_write_disk_set_acls(struct archive *a, int fd, const char *name,
ARCHIVE_ENTRY_ACL_TYPE_DEFAULT, "default");
/* Simultaneous POSIX.1e and NFSv4 is not supported */
+#if defined(HAVE_OPENAT) && defined(HAVE_ACL_SET_FD_NP)
+ if (fd_opened)
+ close(fd);
+#endif
return (ret);
}
#if ARCHIVE_ACL_FREEBSD_NFS4
@@ -697,6 +730,10 @@ archive_write_disk_set_acls(struct archive *a, int fd, const char *name,
ARCHIVE_ENTRY_ACL_TYPE_NFS4, "nfs4");
}
#endif
+#if defined(HAVE_OPENAT) && defined(HAVE_ACL_SET_FD_NP)
+ if (fd_opened)
+ close(fd);
+#endif
return (ret);
}
#endif /* ARCHIVE_ACL_FREEBSD */
diff --git a/libarchive/archive_disk_acl_linux.c b/libarchive/archive_disk_acl_linux.c
index 3928f3d6..e84eb2ea 100644
--- a/libarchive/archive_disk_acl_linux.c
+++ b/libarchive/archive_disk_acl_linux.c
@@ -703,14 +703,33 @@ archive_read_disk_entry_setup_acls(struct archive_read_disk *a,
}
int
-archive_write_disk_set_acls(struct archive *a, int fd, const char *name,
- struct archive_acl *abstract_acl, __LA_MODE_T mode)
+archive_write_disk_set_acls(struct archive *a, int parent_fd, int fd,
+ const char *name, struct archive_acl *abstract_acl, __LA_MODE_T mode)
{
- int ret = ARCHIVE_OK;
-
+ int ret = ARCHIVE_OK;
#if !ARCHIVE_ACL_LIBRICHACL
(void)mode; /* UNUSED */
#endif
+#if defined(HAVE_OPENAT) && defined(HAVE_ACL_SET_FD)
+ int fd_opened = 0;
+
+ if (fd < 0 && (archive_acl_types(abstract_acl) &
+ (ARCHIVE_ENTRY_ACL_TYPE_POSIX1E
+#if ARCHIVE_ACL_LIBRICHACL
+ | ARCHIVE_ENTRY_ACL_TYPE_NFS4
+#endif
+ )) != 0) {
+ fd = openat(parent_fd, name,
+ O_WRONLY | O_CLOEXEC | O_NOFOLLOW);
+ if (fd >= 0)
+ fd_opened = 1;
+ else {
+ archive_set_error(a, errno, "Failed to open for "
+ "setting ACL: %s", name);
+ return (ARCHIVE_WARN);
+ }
+ }
+#endif
#if ARCHIVE_ACL_LIBRICHACL
if ((archive_acl_types(abstract_acl)
@@ -729,8 +748,13 @@ archive_write_disk_set_acls(struct archive *a, int fd, const char *name,
& ARCHIVE_ENTRY_ACL_TYPE_ACCESS) != 0) {
ret = set_acl(a, fd, name, abstract_acl,
ARCHIVE_ENTRY_ACL_TYPE_ACCESS, "access");
- if (ret != ARCHIVE_OK)
+ if (ret != ARCHIVE_OK) {
+#if defined(HAVE_OPENAT) && defined(HAVE_ACL_SET_FD)
+ if (fd_opened)
+ close(fd);
+#endif
return (ret);
+ }
}
if ((archive_acl_types(abstract_acl)
& ARCHIVE_ENTRY_ACL_TYPE_DEFAULT) != 0)
@@ -738,6 +762,10 @@ archive_write_disk_set_acls(struct archive *a, int fd, const char *name,
ARCHIVE_ENTRY_ACL_TYPE_DEFAULT, "default");
}
#endif /* ARCHIVE_ACL_LIBACL */
+#if defined(HAVE_OPENAT) && defined(HAVE_ACL_SET_FD)
+ if (fd_opened)
+ close(fd);
+#endif
return (ret);
}
#endif /* ARCHIVE_ACL_LIBACL || ARCHIVE_ACL_LIBRICHACL */
diff --git a/libarchive/archive_disk_acl_sunos.c b/libarchive/archive_disk_acl_sunos.c
index b0f5dfad..62babc8a 100644
--- a/libarchive/archive_disk_acl_sunos.c
+++ b/libarchive/archive_disk_acl_sunos.c
@@ -791,12 +791,31 @@ archive_read_disk_entry_setup_acls(struct archive_read_disk *a,
}
int
-archive_write_disk_set_acls(struct archive *a, int fd, const char *name,
- struct archive_acl *abstract_acl, __LA_MODE_T mode)
+archive_write_disk_set_acls(struct archive *a, int parent_fd, int fd,
+ const char *name, struct archive_acl *abstract_acl, __LA_MODE_T mode)
{
int ret = ARCHIVE_OK;
-
(void)mode; /* UNUSED */
+#if defined(HAVE_OPENAT) && defined(HAVE_FACL)
+ int fd_opened = 0;
+
+ if (fd < 0 && (archive_acl_types(abstract_acl) &
+ (ARCHIVE_ENTRY_ACL_TYPE_POSIX1E
+#if ARCHIVE_ACL_SUNOS_NFS4
+ | ARCHIVE_ENTRY_ACL_TYPE_NFS4
+#endif
+ )) != 0) {
+ fd = openat(parent_fd, name,
+ O_WRONLY | O_CLOEXEC | O_NOFOLLOW);
+ if (fd >= 0)
+ fd_opened = 1;
+ else {
+ archive_set_error(a, errno, "Failed to open for "
+ "setting ACL: %s", name);
+ return (ARCHIVE_WARN);
+ }
+ }
+#endif
if ((archive_acl_types(abstract_acl)
& ARCHIVE_ENTRY_ACL_TYPE_POSIX1E) != 0) {
@@ -805,6 +824,10 @@ archive_write_disk_set_acls(struct archive *a, int fd, const char *name,
ARCHIVE_ENTRY_ACL_TYPE_POSIX1E, "posix1e");
/* Simultaneous POSIX.1e and NFSv4 is not supported */
+#if defined(HAVE_OPENAT) && defined(HAVE_FACL)
+ if (fd_opened)
+ close(fd);
+#endif
return (ret);
}
#if ARCHIVE_ACL_SUNOS_NFS4
@@ -814,6 +837,10 @@ archive_write_disk_set_acls(struct archive *a, int fd, const char *name,
ARCHIVE_ENTRY_ACL_TYPE_NFS4, "nfs4");
}
#endif
+#if defined(HAVE_OPENAT) && defined(HAVE_FACL)
+ if (fd_opened)
+ close(fd);
+#endif
return (ret);
}
#endif /* ARCHIVE_ACL_SUNOS */
diff --git a/libarchive/archive_write_disk.3 b/libarchive/archive_write_disk.3
index 949c9ef1..488b74d5 100644
--- a/libarchive/archive_write_disk.3
+++ b/libarchive/archive_write_disk.3
@@ -24,11 +24,13 @@
.\"
.\" $FreeBSD$
.\"
-.Dd April 3, 2017
+.Dd May 23, 2019
.Dt ARCHIVE_WRITE_DISK 3
.Os
.Sh NAME
.Nm archive_write_disk_new ,
+.Nm archive_write_disk_set_chdir ,
+.Nm archive_write_disk_set_fchdir ,
.Nm archive_write_disk_set_options ,
.Nm archive_write_disk_set_skip_file ,
.Nm archive_write_disk_set_group_lookup ,
@@ -42,6 +44,10 @@ Streaming Archive Library (libarchive, -larchive)
.Ft struct archive *
.Fn archive_write_disk_new "void"
.Ft int
+.Fn archive_write_disk_set_chdir "struct archive *" "const char *"
+.Ft int
+.Fn archive_write_disk_set_fchdir "struct archive *" "int fd"
+.Ft int
.Fn archive_write_disk_set_options "struct archive *" "int flags"
.Ft int
.Fn archive_write_disk_set_skip_file "struct archive *" "dev_t" "ino_t"
@@ -85,6 +91,33 @@ interface used to write objects to a streaming archive.
Allocates and initializes a
.Tn struct archive
object suitable for writing objects to disk.
+.It Fn archive_write_disk_set_chdir
+.It Fn archive_write_disk_set_fchdir
+Sets the current working directory for writing disk objects.
+.Fn archive_write_disk_set_chdir
+takes a
+.Fa path
+argument and
+.Fn archive_write_disk_set_fchdir
+an open file descriptor to a directory or the special value
+.Cm AT_FDCWD
+to reset to previous behavior. Once called with success the writer does
+not perfom any
+.Fn chdir
+or
+.Fn fchdir
+operations anymore and is not dependent on the current working directory.
+This function does not operate on all platforms. It returns
+.Er ARCHIVE_OK
+on success and
+.Er ARCHIVE_FAILED
+on failure. Error codes are stored in the global variable
+.Fa errno .
+If
+.Fa errno
+is set to
+.Er EOPNOTSUPP
+the function is not operational on the current platform.
.It Fn archive_write_disk_set_skip_file
Records the device and inode numbers of a file that should not be
overwritten.
diff --git a/libarchive/archive_write_disk_posix.c b/libarchive/archive_write_disk_posix.c
index 84e8f80e..b3d1be3c 100644
--- a/libarchive/archive_write_disk_posix.c
+++ b/libarchive/archive_write_disk_posix.c
@@ -154,6 +154,9 @@ __FBSDID("$FreeBSD$");
#ifndef O_CLOEXEC
#define O_CLOEXEC 0
#endif
+#ifndef O_DIRECTORY
+#define O_DIRECTORY 0
+#endif
/* Ignore non-int O_NOFOLLOW constant. */
/* gnulib's fcntl.h does this on AIX, but it seems practical everywhere */
@@ -165,6 +168,26 @@ __FBSDID("$FreeBSD$");
#define O_NOFOLLOW 0
#endif
+#ifndef AT_SYMLINK_NOFOLLOW
+#define AT_SYMLINK_NOFOLLOW 0x200
+#endif
+
+#ifndef AT_REMOVEDIR
+#define AT_REMOVEDIR 0x800
+#endif
+
+#ifndef AT_FDCWD
+#define AT_FDCWD -100
+#endif
+
+#if defined(O_PATH)
+#define O_ARCHIVE_PATH O_PATH
+#elif defined(O_SEARCH)
+#define O_ARCHIVE_PATH O_SEARCH
+#elif defined(O_EXEC)
+#define O_ARCHIVE_PATH O_EXEC
+#endif
+
struct fixup_entry {
struct fixup_entry *next;
struct archive_acl acl;
@@ -265,8 +288,12 @@ struct archive_write_disk {
int64_t total_bytes_written;
/* Maximum size of file, -1 if unknown. */
int64_t filesize;
- /* Dir we were in before this restore; only for deep paths. */
+ /* Current chdir fd */
+ int chdir_pwd;
+ /* Dir fd we were in before this restore; only for deep paths. */
int restore_pwd;
+ /* Remember if chdir_pwd was originally AT_FDCWD; only for deep paths */
+ int chdir_fdcwd;
/* Mode we should use for this entry; affected by _PERM and umask. */
mode_t mode;
/* UID/GID to use in restoring this entry. */
@@ -348,10 +375,20 @@ struct archive_write_disk {
#define HFS_BLOCKS(s) ((s) >> 12)
+static int la_openat(int, const char *, int, ...);
+static int la_opendirat(int, const char *, int);
+static int la_fstatat(int, const char *, struct stat *, int, int);
+static int la_mknodat(int, const char *, mode_t, dev_t);
+static int la_mkfifoat(int, const char *, mode_t);
+static int la_mkdirat(int, const char *, mode_t);
+static int la_unlinkat(int, const char *, int);
+static int la_fchmodat(int, const char *, mode_t, int);
+static int la_linkat(int, const char *, int, const char *, int);
+static int la_symlinkat(const char *, int, const char *);
static void fsobj_error(int *, struct archive_string *, int, const char *,
const char *);
static int check_symlinks_fsobj(char *, int *, struct archive_string *,
- int);
+ int, int);
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 *,
@@ -379,7 +416,8 @@ static int set_fflags_platform(struct archive_write_disk *, int fd,
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(int, int, const char *, time_t, long, time_t, long);
+static int set_timeat(int, int, int, const char *, time_t, long, time_t,
+ long);
static int set_times(struct archive_write_disk *, int, int, const char *,
time_t, long, time_t, long, time_t, long, time_t, long);
static int set_times_from_entry(struct archive_write_disk *);
@@ -401,6 +439,229 @@ static ssize_t _archive_write_disk_data_block(struct archive *, const void *,
size_t, int64_t);
static int
+la_fstatat(int fd, const char *path, struct stat *sb, int flag, int lazy)
+{
+#ifdef HAVE_FSTATAT
+ (void)lazy; /* UNUSED */
+
+ return (fstatat(fd, path, sb, flag));
+#else /* !HAVE_FSTATAT */
+ (void)fd; /* UNUSED */
+#ifndef HAVE_LSTAT
+ (void)lazy; /* UNUSED */
+#endif
+
+ if (flag & AT_SYMLINK_NOFOLLOW) {
+#ifdef HAVE_LSTAT
+ return (lstat(path, sb));
+#else
+ if (lazy)
+ return (la_stat(path, sb));
+ else
+ return (-1);
+#endif
+ } else
+ return (la_stat(path, sb));
+#endif /* !HAVE_FSTATAT */
+}
+
+static int
+la_openat(int fd, const char *path, int oflag, ...)
+{
+ va_list ap;
+ int has_mode = 0;
+ mode_t mode;
+#ifndef HAVE_OPENAT
+ (void)fd; /* UNUSED */
+#endif
+
+ va_start(ap, oflag);
+ mode = va_arg(ap, int);
+ has_mode = 1;
+ va_end(ap);
+
+ if (has_mode)
+#ifdef HAVE_OPENAT
+ return (openat(fd, path, oflag, mode));
+#else
+ return (open(path, oflag, mode));
+#endif
+ else
+#ifdef HAVE_OPENAT
+ return (openat(fd, path, oflag));
+#else
+ return (open(path, oflag));
+#endif
+}
+
+static int
+la_opendirat(int fd, const char *path, int o_path) {
+ int r = -1;
+#ifndef HAVE_OPENAT
+ (void)fd; /* UNUSED */
+#endif
+
+#ifdef HAVE_OPENAT
+ r = openat(fd, path,
+ O_RDONLY | O_BINARY | O_DIRECTORY | O_CLOEXEC);
+#ifdef O_ARCHIVE_PATH
+ if (r < 0 && o_path) {
+ r = openat(fd, path,
+ O_ARCHIVE_PATH | O_DIRECTORY | O_CLOEXEC);
+ }
+#endif
+#else /* !HAVE_OPENAT */
+ r = open(path, O_RDONLY | O_BINARY | O_DIRECTORY | O_CLOEXEC);
+#ifdef O_ARCHIVE_PATH
+ if (r < 0 && o_path)
+ r = open(path, O_ARCHIVE_PATH | O_DIRECTORY | O_CLOEXEC);
+#endif
+#endif /* !HAVE_OPENAT */
+ return (r);
+}
+
+static int
+la_mknodat(int fd, const char *path, mode_t mode, dev_t dev)
+{
+#ifdef HAVE_MKNODAT
+ return (mknodat(fd, path, mode, dev));
+#else /* !HAVE_MKNODAT */
+ (void)fd; /* UNUSED */
+#ifdef HAVE_MKNOD
+ return (mknod(path, mode, dev));
+#else
+ (void)path; /* UNUSED */
+ (void)mode; /* UNUSED */
+ (void)dev; /* UNUSED */
+
+ errno = EOPNOTSUPP;
+ return (-1);
+#endif
+#endif /* !HAVE_MKNODAT */
+}
+
+static int
+la_mkfifoat(int fd, const char *path, mode_t mode)
+{
+#ifdef HAVE_MKFIFOAT
+ return (mkfifoat(fd, path, mode));
+#else /* !HAVE_MKFIFOAT */
+ (void)fd; /* UNUSED */
+#ifdef HAVE_MKFIFO
+
+ return (mkfifo(path, mode));
+#else
+ (void)path; /* UNUSED */
+ (void)mode; /* UNUSED */
+
+ errno = EOPNOTSUPP;
+ return (-1);
+#endif
+#endif /* !HAVE_MKFIFOAT */
+}
+
+static int
+la_mkdirat(int fd, const char *path, mode_t mode)
+{
+#ifdef HAVE_MKDIRAT
+ return (mkdirat(fd, path, mode));
+#else /* !HAVE_MKDIRAT */
+ (void)fd; /* UNUSED */
+#ifdef HAVE_MKDIR
+
+ return (mkdir(path, mode));
+#else
+ (void)path; /* UNUSED */
+ (void)mode; /* UNUSED */
+
+ errno = EOPNOTSUPP;
+ return (-1);
+#endif
+#endif /* !HAVE_MKDIRAT */
+}
+
+static int
+la_unlinkat(int fd, const char *path, int flag)
+{
+#ifdef HAVE_UNLINKAT
+ return (unlinkat(fd, path, flag));
+#else /* !HAVE_UNLINKAT */
+ (void)fd; /* UNUSED */
+
+ if (flag & AT_REMOVEDIR)
+ return (rmdir(path));
+ else
+ return (unlink(path));
+#endif /* !HAVE_UNLINKAT */
+}
+
+static int
+la_fchmodat(int fd, const char *path, mode_t mode, int flag)
+{
+#ifdef HAVE_FCHMODAT
+#ifdef __GLIBC__
+ /* Linux does not implement AT_SYMLINK_NOFOLLOW */
+ flag = 0;
+#endif
+ return (fchmodat(fd, path, mode, flag));
+#else /* !HAVE_FCHMODAT */
+ (void)fd; /* UNUSED */
+#ifndef HAVE_LCHMOD
+ (void)flag; /* UNUSED */
+#endif
+
+#if defined(HAVE_LCHMOD)
+ if (flag & AT_SYMLINK_NOFOLLOW)
+ return (lchmod(path, mode));
+ else
+#endif
+ return (chmod(path, mode));
+#endif /* !HAVE_FCHMODAT */
+}
+
+static int
+la_linkat(int fd1, const char *name1, int fd2, const char *name2, int flag)
+{
+#ifdef HAVE_LINKAT
+ return(linkat(fd1, name1, fd2, name2, flag));
+#else /* !HAVE_LINKAT */
+ (void)fd1; /* UNUSED */
+ (void)fd2; /* UNUSED */
+ (void)flag; /* UNUSED */
+
+#ifdef HAVE_LINK
+ return(link(name1, name2));
+#else
+ (void)name1; /* UNUSED */
+ (void)name2; /* UNUSED */
+
+ errno = EOPNOTSUPP;
+ return (-1);
+#endif
+#endif /* !HAVE_LINKAT */
+}
+
+static int
+la_symlinkat(const char *name1, int fd, const char *name2)
+{
+#ifdef HAVE_SYMLINKAT
+ return (symlinkat(name1, fd, name2));
+#else /* !HAVE_SYMLINKAT */
+ (void)fd; /* UNUSED */
+#ifdef HAVE_SYMLINK
+
+ return (symlink(name1, name2));
+#else
+ (void)name1; /* UNUSED */
+ (void)name2; /* UNUSED */
+
+ errno = EOPNOTSUPP;
+ return (-1);
+#endif
+#endif /* !HAVE_SYMLINKAT */
+}
+
+static int
lazy_stat(struct archive_write_disk *a)
{
if (a->pst != NULL) {
@@ -417,10 +678,12 @@ lazy_stat(struct archive_write_disk *a)
* XXX At this point, symlinks should not be hit, otherwise
* XXX a race occurred. Do we want to check explicitly for that?
*/
- if (lstat(a->name, &a->st) == 0) {
+ if(la_fstatat(a->chdir_pwd, a->name, &a->st,
+ AT_SYMLINK_NOFOLLOW, 0)) {
a->pst = &a->st;
return (ARCHIVE_OK);
}
+
archive_set_error(&a->archive, errno, "Couldn't stat file");
return (ARCHIVE_WARN);
}
@@ -465,6 +728,63 @@ archive_write_disk_set_options(struct archive *_a, int flags)
return (ARCHIVE_OK);
}
+int
+archive_write_disk_set_fchdir(struct archive *_a, int fd)
+{
+#if defined(HAVE_OPENAT) && defined(HAVE_FSTATAT) && \
+ defined(HAVE_FCHMODAT) && defined(HAVE_LINKAT) && \
+ defined(HAVE_SYMLINKAT) && defined(HAVE_UNLINKAT) && \
+ defined(HAVE_MKDIRAT) && defined(HAVE_MKNODAT) && \
+ defined(HAVE_MKFIFOAT) && defined(HAVE_UTIMENSAT)
+ struct archive_write_disk *a = (struct archive_write_disk *)_a;
+
+ if (fd >= 0 || fd == AT_FDCWD) {
+ if (a->chdir_pwd >= 0)
+ close(a->chdir_pwd);
+ a->chdir_pwd = fd;
+ if (fd == AT_FDCWD)
+ a->chdir_fdcwd = 1;
+ else
+ a->chdir_fdcwd = 0;
+ return (ARCHIVE_OK);
+ } else {
+ errno = EINVAL;
+ return (ARCHIVE_FAILED);
+ }
+#else /* !defined(HAVE_*AT) */
+ (void)_a; /* UNUSED */
+ (void)fd; /* UNUSED */
+
+ errno = EOPNOTSUPP;
+ return (ARCHIVE_FAILED);
+#endif
+}
+
+int
+archive_write_disk_set_chdir(struct archive *_a, const char *dir)
+{
+#if defined(HAVE_OPENAT) && defined(HAVE_FSTATAT) && \
+ defined(HAVE_FCHMODAT) && defined(HAVE_LINKAT) && \
+ defined(HAVE_SYMLINKAT) && defined(HAVE_UNLINKAT) && \
+ defined(HAVE_MKDIRAT) && defined(HAVE_MKNODAT) && \
+ defined(HAVE_MKFIFOAT) && defined(HAVE_UTIMENSAT)
+ struct archive_write_disk *a = (struct archive_write_disk *)_a;
+ int fd;
+
+ fd = la_opendirat(AT_FDCWD, dir, 1);
+ if (fd < 0) {
+ archive_set_error(&a->archive, errno,
+ "Couldn't chdir to: %s", dir);
+ return (ARCHIVE_FAILED);
+ }
+ return (archive_write_disk_set_fchdir(_a, fd));
+#else /* !defined(HAVE_*AT) */
+ (void)_a; /* UNUSED */
+ (void)dir; /* UNUSED */
+
+ return (ARCHIVE_FAILED);
+#endif /* !defined(HAVE_*AT) */
+}
/*
* Extract this entry to disk.
@@ -722,19 +1042,33 @@ _archive_write_disk_header(struct archive *_a, struct archive_entry *entry)
* intractable problem.
*/
-#ifdef HAVE_FCHDIR
/* If we changed directory above, restore it here. */
if (a->restore_pwd >= 0) {
- r = fchdir(a->restore_pwd);
- if (r != 0) {
- archive_set_error(&a->archive, errno,
- "chdir() failure");
- ret = ARCHIVE_FATAL;
+ if (a->chdir_pwd >= 0) {
+ if (a->chdir_pwd != a->restore_pwd) {
+ close(a->chdir_pwd);
+ if (a->chdir_fdcwd) {
+ close(a->restore_pwd);
+ a->chdir_pwd = AT_FDCWD;
+ } else {
+ a->chdir_pwd = a->restore_pwd;
+ }
+ }
+ a->restore_pwd = -1;
+ }
+#ifdef HAVE_FCHDIR
+ else {
+ r = fchdir(a->restore_pwd);
+ if (r != 0) {
+ archive_set_error(&a->archive, errno,
+ "chdir() failure");
+ ret = ARCHIVE_FATAL;
+ }
+ close(a->restore_pwd);
+ a->restore_pwd = -1;
}
- close(a->restore_pwd);
- a->restore_pwd = -1;
- }
#endif
+ }
/*
* Fixup uses the unedited pathname from archive_entry_pathname(),
@@ -1777,7 +2111,8 @@ _archive_write_disk_finish_entry(struct archive *_a)
*/
if (a->todo & TODO_ACLS) {
int r2;
- r2 = archive_write_disk_set_acls(&a->archive, a->fd,
+ r2 = archive_write_disk_set_acls(&a->archive,
+ a->chdir_pwd, a->fd,
archive_entry_pathname(a->entry),
archive_entry_acl(a->entry),
archive_entry_mode(a->entry));
@@ -1885,6 +2220,8 @@ archive_write_disk_new(void)
#ifdef HAVE_ZLIB_H
a->decmpfs_compression_level = 5;
#endif
+ a->chdir_pwd = AT_FDCWD;
+ a->chdir_fdcwd = 1;
return (&a->archive);
}
@@ -1903,13 +2240,21 @@ edit_deep_directories(struct archive_write_disk *a)
{
int ret;
char *tail = a->name;
+#ifdef HAVE_OPENAT
+ int fd;
+#endif
/* 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 | O_CLOEXEC);
+#ifdef HAVE_OPENAT
+ if (a->chdir_pwd >= 0)
+ a->restore_pwd = a->chdir_pwd;
+ else
+#endif
+ a->restore_pwd = la_opendirat(AT_FDCWD, ".", 1);
__archive_ensure_cloexec_flag(a->restore_pwd);
if (a->restore_pwd < 0)
return;
@@ -1926,8 +2271,25 @@ edit_deep_directories(struct archive_write_disk *a)
/* 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)
+ if (ret == ARCHIVE_OK) {
+#ifdef HAVE_OPENAT
+ fd = la_opendirat(a->chdir_pwd, a->name, 1);
+ if (fd < 0)
+ ret = ARCHIVE_FAILED;
+ else {
+ ret = ARCHIVE_OK;
+ if (a->chdir_pwd != a->restore_pwd)
+ close(a->chdir_pwd);
+ a->chdir_pwd = fd;
+ }
+#else /* !HAVE_OPENAT */
+ if (chdir(a->name) != 0) {
+ ret = ARCHIVE_FAILED;
+ }
+#endif
+ } else
ret = ARCHIVE_FAILED;
+
*tail = '/'; /* Restore the / we removed. */
if (ret != ARCHIVE_OK)
return;
@@ -1958,12 +2320,13 @@ restore_entry(struct archive_write_disk *a)
*/
if (a->flags & ARCHIVE_EXTRACT_CLEAR_NOCHANGE_FFLAGS)
(void)clear_nochange_fflags(a);
- if (unlink(a->name) == 0) {
+ if (la_unlinkat(a->chdir_pwd, a->name, 0) == 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) {
+ } else if (la_unlinkat(a->chdir_pwd, a->name,
+ AT_REMOVEDIR) == 0) {
/* It was a dir, but now it's gone. */
a->pst = NULL;
} else {
@@ -2012,7 +2375,7 @@ restore_entry(struct archive_write_disk *a)
*/
if (en == EISDIR) {
/* A dir is in the way of a non-dir, rmdir it. */
- if (rmdir(a->name) != 0) {
+ if (la_unlinkat(a->chdir_pwd, a->name, AT_REMOVEDIR) != 0) {
archive_set_error(&a->archive, errno,
"Can't remove already-existing dir");
return (ARCHIVE_FAILED);
@@ -2032,13 +2395,16 @@ restore_entry(struct archive_write_disk *a)
* follow the symlink if we're creating a dir.
*/
if (S_ISDIR(a->mode))
- r = la_stat(a->name, &a->st);
+ r = la_fstatat(a->chdir_pwd, a->name, &a->st, 0, 0);
+
/*
* If it's not a dir (or it's a broken symlink),
* then don't follow it.
*/
- if (r != 0 || !S_ISDIR(a->mode))
- r = lstat(a->name, &a->st);
+ if (r != 0 || !S_ISDIR(a->mode)) {
+ r = la_fstatat(a->chdir_pwd, a->name, &a->st,
+ AT_SYMLINK_NOFOLLOW, 0);
+ }
if (r != 0) {
archive_set_error(&a->archive, errno,
"Can't stat existing object");
@@ -2069,7 +2435,7 @@ restore_entry(struct archive_write_disk *a)
/* A non-dir is in the way, unlink it. */
if (a->flags & ARCHIVE_EXTRACT_CLEAR_NOCHANGE_FFLAGS)
(void)clear_nochange_fflags(a);
- if (unlink(a->name) != 0) {
+ if (la_unlinkat(a->chdir_pwd, a->name, 0) != 0) {
archive_set_error(&a->archive, errno,
"Can't unlink already-existing object");
return (ARCHIVE_FAILED);
@@ -2081,7 +2447,8 @@ restore_entry(struct archive_write_disk *a)
/* 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 (rmdir(a->name) != 0) {
+ if (la_unlinkat(a->chdir_pwd, a->name,
+ AT_REMOVEDIR) != 0) {
archive_set_error(&a->archive, errno,
"Can't replace existing directory with non-directory");
return (ARCHIVE_FAILED);
@@ -2164,7 +2531,7 @@ create_filesystem_object(struct archive_write_disk *a)
return (EPERM);
}
r = check_symlinks_fsobj(linkname_copy, &error_number,
- &error_string, a->flags);
+ &error_string, a->flags, a->chdir_pwd);
if (r != ARCHIVE_OK) {
archive_set_error(&a->archive, error_number, "%s",
error_string.s);
@@ -2178,7 +2545,8 @@ create_filesystem_object(struct archive_write_disk *a)
}
free(linkname_copy);
archive_string_free(&error_string);
- r = link(linkname, a->name) ? errno : 0;
+ r = la_linkat(a->chdir_pwd, linkname, a->chdir_pwd, a->name,
+ 0) ? errno : 0;
/*
* New cpio and pax formats allow hardlink entries
* to carry data, so we may have to open the file
@@ -2195,15 +2563,13 @@ create_filesystem_object(struct archive_write_disk *a)
a->todo = 0;
a->deferred = 0;
} else if (r == 0 && a->filesize > 0) {
-#ifdef HAVE_LSTAT
- r = lstat(a->name, &st);
-#else
- r = la_stat(a->name, &st);
-#endif
+ r = la_fstatat(a->chdir_pwd, a->name, &st,
+ AT_SYMLINK_NOFOLLOW, 1);
if (r != 0)
r = errno;
else if ((st.st_mode & AE_IFMT) == AE_IFREG) {
- a->fd = open(a->name, O_WRONLY | O_TRUNC |
+ a->fd = la_openat(a->chdir_pwd, a->name,
+ O_WRONLY | O_TRUNC |
O_BINARY | O_CLOEXEC | O_NOFOLLOW);
__archive_ensure_cloexec_flag(a->fd);
if (a->fd < 0)
@@ -2215,11 +2581,8 @@ create_filesystem_object(struct archive_write_disk *a)
}
linkname = archive_entry_symlink(a->entry);
if (linkname != NULL) {
-#if HAVE_SYMLINK
- return symlink(linkname, a->name) ? errno : 0;
-#else
- return (EPERM);
-#endif
+ return la_symlinkat(linkname, a->chdir_pwd,
+ a->name) ? errno : 0;
}
/*
@@ -2251,36 +2614,24 @@ create_filesystem_object(struct archive_write_disk *a)
/* POSIX requires that we fall through here. */
/* FALLTHROUGH */
case AE_IFREG:
- a->fd = open(a->name,
+ a->fd = la_openat(a->chdir_pwd, a->name,
O_WRONLY | O_CREAT | O_EXCL | O_BINARY | O_CLOEXEC, mode);
__archive_ensure_cloexec_flag(a->fd);
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,
+ r = la_mknodat(a->chdir_pwd, a->name, mode | S_IFCHR,
archive_entry_rdev(a->entry));
break;
-#else
- /* TODO: Find a better way to warn about our inability
- * to restore a char device node. */
- return (EINVAL);
-#endif /* HAVE_MKNOD */
case AE_IFBLK:
-#ifdef HAVE_MKNOD
- r = mknod(a->name, mode | S_IFBLK,
+ r = la_mknodat(a->chdir_pwd, a->name, mode | S_IFBLK,
archive_entry_rdev(a->entry));
break;
-#else
- /* TODO: Find a better way to warn about our inability
- * to restore a block device node. */
- return (EINVAL);
-#endif /* HAVE_MKNOD */
case AE_IFDIR:
mode = (mode | MINIMUM_DIR_MODE) & MAXIMUM_DIR_MODE;
- r = mkdir(a->name, mode);
+ r = la_mkdirat(a->chdir_pwd, a->name, mode);
if (r == 0) {
/* Defer setting dir times. */
a->deferred |= (a->todo & TODO_TIMES);
@@ -2295,14 +2646,8 @@ create_filesystem_object(struct archive_write_disk *a)
}
break;
case AE_IFIFO:
-#ifdef HAVE_MKFIFO
- r = mkfifo(a->name, mode);
+ r = la_mkfifoat(a->chdir_pwd, a->name, mode);
break;
-#else
- /* TODO: Find a better way to warn about our inability
- * to restore a fifo. */
- return (EINVAL);
-#endif /* HAVE_MKFIFO */
}
/* All the system calls above set errno on failure. */
@@ -2361,10 +2706,12 @@ _archive_write_disk_close(struct archive *_a)
p->mtime, p->mtime_nanos,
p->ctime, p->ctime_nanos);
}
- if (p->fixup & TODO_MODE_BASE)
- chmod(p->name, p->mode);
- if (p->fixup & TODO_ACLS)
- archive_write_disk_set_acls(&a->archive, -1, p->name,
+ if (p->fixup & TODO_MODE_BASE) {
+ la_fchmodat(a->chdir_pwd, p->name, p->mode,
+ AT_SYMLINK_NOFOLLOW);
+ } if (p->fixup & TODO_ACLS)
+ archive_write_disk_set_acls(&a->archive,
+ a->chdir_pwd, -1, p->name,
&p->acl, p->mode);
if (p->fixup & TODO_FFLAGS)
set_fflags_platform(a, -1, p->name,
@@ -2420,6 +2767,8 @@ _archive_write_disk_free(struct archive *_a)
}
}
#endif
+ if (a->chdir_pwd >= 0)
+ close(a->chdir_pwd);
free(a);
return (ret);
}
@@ -2548,25 +2897,29 @@ fsobj_error(int *a_eno, struct archive_string *a_estr,
*/
static int
check_symlinks_fsobj(char *path, int *a_eno, struct archive_string *a_estr,
- int flags)
+ int flags, int chdir_pwd)
{
-#if !defined(HAVE_LSTAT)
+#if !defined(HAVE_LSTAT) && !(defined(HAVE_OPENAT) && defined(HAVE_FSTATAT))
/* Platform doesn't have lstat, so we can't look for symlinks. */
(void)path; /* UNUSED */
(void)error_number; /* UNUSED */
(void)error_string; /* UNUSED */
(void)flags; /* UNUSED */
return (ARCHIVE_OK);
-#else
+#else /* defined(HAVE_LSTAT) */
int res = ARCHIVE_OK;
char *tail;
char *head;
int last;
char c;
- int r;
+ int fd, r;
struct stat st;
+#if defined(HAVE_OPENAT) && defined(HAVE_FSTATAT)
+ int orig_pwd;
+#else
int restore_pwd;
-
+ void(chdir_pwd); /* UNUSED */
+#endif
/* Nothing to do here if name is empty */
if(path[0] == '\0')
return (ARCHIVE_OK);
@@ -2586,13 +2939,22 @@ check_symlinks_fsobj(char *path, int *a_eno, struct archive_string *a_estr,
* c holds what used to be in *tail
* last is 1 if this is the last tail
*/
- restore_pwd = open(".", O_RDONLY | O_BINARY | O_CLOEXEC);
+#if defined(HAVE_OPENAT) && defined(HAVE_FSTATAT)
+ orig_pwd = chdir_pwd;
+ if (chdir_pwd < 0) {
+ chdir_pwd = la_opendirat(AT_FDCWD, ".", 1);
+ }
+ __archive_ensure_cloexec_flag(chdir_pwd);
+#else
+ restore_pwd = la_opendirat(AT_FDCWD, ".", 1);
__archive_ensure_cloexec_flag(restore_pwd);
+
if (restore_pwd < 0) {
fsobj_error(a_eno, a_estr, errno,
"Could not open ", path);
return (ARCHIVE_FATAL);
}
+#endif
head = path;
tail = path;
last = 0;
@@ -2621,7 +2983,7 @@ check_symlinks_fsobj(char *path, int *a_eno, struct archive_string *a_estr,
c = tail[0];
tail[0] = '\0';
/* Check that we haven't hit a symlink. */
- r = lstat(head, &st);
+ r = la_fstatat(chdir_pwd, head, &st, AT_SYMLINK_NOFOLLOW, 0);
if (r != 0) {
tail[0] = c;
/* We've hit a dir that doesn't exist; stop now. */
@@ -2647,10 +3009,29 @@ check_symlinks_fsobj(char *path, int *a_eno, struct archive_string *a_estr,
}
} else if (S_ISDIR(st.st_mode)) {
if (!last) {
- if (chdir(head) != 0) {
+#if defined(HAVE_OPENAT) && defined(HAVE_FSTATAT)
+ fd = la_opendirat(chdir_pwd, head, 1);
+ if (fd < 0)
+ r = -1;
+ else {
+ r = 0;
+ if (chdir_pwd != orig_pwd)
+ close(chdir_pwd);
+ chdir_pwd = fd;
+ }
+#else
+ r = chdir(head);
+#endif
+ if (r != 0) {
tail[0] = c;
+
fsobj_error(a_eno, a_estr, errno,
- "Could not chdir ", path);
+#if defined(HAVE_OPENAT) && defined(HAVE_FSTATAT)
+ "Could not opendirat ",
+#else
+ "Could not chdir ",
+#endif
+ path);
res = (ARCHIVE_FATAL);
break;
}
@@ -2664,7 +3045,7 @@ check_symlinks_fsobj(char *path, int *a_eno, struct archive_string *a_estr,
* so we can overwrite it with the
* item being extracted.
*/
- if (unlink(head)) {
+ if (la_unlinkat(chdir_pwd, head, 0)) {
tail[0] = c;
fsobj_error(a_eno, a_estr, errno,
"Could not remove symlink ",
@@ -2694,7 +3075,7 @@ check_symlinks_fsobj(char *path, int *a_eno, struct archive_string *a_estr,
break;
} else if (flags & ARCHIVE_EXTRACT_UNLINK) {
/* User asked us to remove problems. */
- if (unlink(head) != 0) {
+ if (la_unlinkat(chdir_pwd, head, 0) != 0) {
tail[0] = c;
fsobj_error(a_eno, a_estr, 0,
"Cannot remove intervening "
@@ -2712,7 +3093,7 @@ check_symlinks_fsobj(char *path, int *a_eno, struct archive_string *a_estr,
* This is needed to extract hardlinks over
* symlinks.
*/
- r = la_stat(head, &st);
+ r = la_fstatat(chdir_pwd, head, &st, 0, 0);
if (r != 0) {
tail[0] = c;
if (errno == ENOENT) {
@@ -2725,11 +3106,29 @@ check_symlinks_fsobj(char *path, int *a_eno, struct archive_string *a_estr,
break;
}
} else if (S_ISDIR(st.st_mode)) {
- if (chdir(head) != 0) {
+#if defined(HAVE_OPENAT) && defined(HAVE_FSTATAT)
+ fd = la_opendirat(chdir_pwd, head, 1);
+ if (fd < 0)
+ r = -1;
+ else {
+ r = 0;
+ if (chdir_pwd != orig_pwd)
+ close(chdir_pwd);
+ chdir_pwd = fd;
+ }
+#else
+ r = chdir(head);
+#endif
+ if (r != 0) {
tail[0] = c;
fsobj_error(a_eno, a_estr,
errno,
- "Could not chdir ", path);
+#if defined(HAVE_OPENAT) && defined(HAVE_FSTATAT)
+ "Could not opendirat ",
+#else
+ "Could not chdir ",
+#endif
+ path);
res = (ARCHIVE_FATAL);
break;
}
@@ -2761,24 +3160,27 @@ check_symlinks_fsobj(char *path, int *a_eno, struct archive_string *a_estr,
}
/* Catches loop exits via break */
tail[0] = c;
-#ifdef HAVE_FCHDIR
+
/* If we changed directory above, restore it here. */
- if (restore_pwd >= 0) {
- r = fchdir(restore_pwd);
- if (r != 0) {
- fsobj_error(a_eno, a_estr, errno,
- "chdir() failure", "");
- }
- close(restore_pwd);
- restore_pwd = -1;
- if (r != 0) {
- res = (ARCHIVE_FATAL);
- }
+#if defined(HAVE_OPENAT) && defined(HAVE_FSTATAT)
+ if (chdir_pwd != orig_pwd)
+ close(chdir_pwd);
+#elif HAVE_FCHDIR
+ r = fchdir(restore_pwd);
+ if (r != 0) {
+ fsobj_error(a_eno, a_estr, errno,
+ "chdir() failure", "");
+ }
+ close(restore_pwd);
+ restore_pwd = -1;
+ if (r != 0) {
+ res = (ARCHIVE_FATAL);
}
#endif
/* TODO: reintroduce a safe cache here? */
return res;
-#endif
+
+#endif /* defined(HAVE_LSTAT) */
}
/*
@@ -2793,7 +3195,7 @@ check_symlinks(struct archive_write_disk *a)
int rc;
archive_string_init(&error_string);
rc = check_symlinks_fsobj(a->name, &error_number, &error_string,
- a->flags);
+ a->flags, a->chdir_pwd);
if (rc != ARCHIVE_OK) {
archive_set_error(&a->archive, error_number, "%s",
error_string.s);
@@ -3052,7 +3454,14 @@ create_dir(struct archive_write_disk *a, char *path)
* here loses the ability to extract through symlinks. Also note
* that this should not use the a->st cache.
*/
- if (la_stat(path, &st) == 0) {
+#ifdef HAVE_FSTATAT
+ if (a->chdir_pwd >= 0) {
+ r = fstatat(a->chdir_pwd, path, &st, 0);
+ } else
+#endif
+ r = la_stat(path, &st);
+
+ if (r == 0) {
if (S_ISDIR(st.st_mode))
return (ARCHIVE_OK);
if ((a->flags & ARCHIVE_EXTRACT_NO_OVERWRITE)) {
@@ -3060,7 +3469,7 @@ create_dir(struct archive_write_disk *a, char *path)
"Can't create directory '%s'", path);
return (ARCHIVE_FAILED);
}
- if (unlink(path) != 0) {
+ if (la_unlinkat(a->chdir_pwd, path, 0) != 0) {
archive_set_error(&a->archive, errno,
"Can't create directory '%s': "
"Conflicting file cannot be removed",
@@ -3093,7 +3502,7 @@ create_dir(struct archive_write_disk *a, char *path)
mode = mode_final;
mode |= MINIMUM_DIR_MODE;
mode &= MAXIMUM_DIR_MODE;
- if (mkdir(path, mode) == 0) {
+ if (la_mkdirat(a->chdir_pwd, path, mode) == 0) {
if (mode != mode_final) {
le = new_fixup(a, path);
if (le == NULL)
@@ -3110,7 +3519,13 @@ create_dir(struct archive_write_disk *a, char *path)
* don't add it to the fixup list here, as it's already been
* added.
*/
- if (la_stat(path, &st) == 0 && S_ISDIR(st.st_mode))
+#ifdef HAVE_FSTATAT
+ if (a->chdir_pwd >= 0) {
+ r = fstatat(a->chdir_pwd, path, &st, 0);
+ } else
+#endif
+ r = la_stat(path, &st);
+ if (r == 0 && S_ISDIR(st.st_mode))
return (ARCHIVE_OK);
archive_set_error(&a->archive, errno, "Failed to create dir '%s'",
@@ -3180,7 +3595,7 @@ set_ownership(struct archive_write_disk *a)
* Note: Returns 0 on success, non-zero on failure.
*/
static int
-set_time(int fd, int mode, const char *name,
+set_timeat(int chdir_fd, int fd, int mode, const char *name,
time_t atime, long atime_nsec,
time_t mtime, long mtime_nsec)
{
@@ -3199,7 +3614,7 @@ set_time(int fd, int mode, const char *name,
ts[1].tv_nsec = mtime_nsec;
if (fd >= 0)
return futimens(fd, ts);
- return utimensat(AT_FDCWD, name, ts, AT_SYMLINK_NOFOLLOW);
+ return utimensat(chdir_fd, name, ts, AT_SYMLINK_NOFOLLOW);
#elif HAVE_UTIMES
/*
@@ -3323,14 +3738,14 @@ set_times(struct archive_write_disk *a,
*/
if (birthtime < mtime
|| (birthtime == mtime && birthtime_nanos < mtime_nanos))
- r1 = set_time(fd, mode, name,
+ r1 = set_timeat(a->chdir_pwd, fd, mode, name,
atime, atime_nanos,
birthtime, birthtime_nanos);
#else
(void)birthtime; /* UNUSED */
(void)birthtime_nanos; /* UNUSED */
#endif
- r2 = set_time(fd, mode, name,
+ r2 = set_timeat(a->chdir_pwd, fd, mode, name,
atime, atime_nanos,
mtime, mtime_nanos);
if (r1 != 0 || r2 != 0) {
@@ -3452,7 +3867,8 @@ set_mode(struct archive_write_disk *a, int mode)
* permissions on symlinks, so a failure here has no
* impact.
*/
- if (lchmod(a->name, mode) != 0) {
+ if (la_fchmodat(a->chdir_pwd, a->name, mode,
+ AT_SYMLINK_NOFOLLOW) != 0) {
switch (errno) {
case ENOTSUP:
case ENOSYS:
@@ -3486,15 +3902,19 @@ set_mode(struct archive_write_disk *a, int mode)
"Can't set permissions to 0%o", (int)mode);
r = ARCHIVE_WARN;
}
- } else
+ } else {
#endif
/* If this platform lacks fchmod(), then
* we'll just use chmod(). */
- if (chmod(a->name, mode) != 0) {
+ if (la_fchmodat(a->chdir_pwd, a->name, mode,
+ AT_SYMLINK_NOFOLLOW) != 0) {
archive_set_error(&a->archive, errno,
"Can't set permissions to 0%o", (int)mode);
r = ARCHIVE_WARN;
}
+#ifdef HAVE_FCHMOD
+ }
+#endif
}
return (r);
}
@@ -3605,7 +4025,7 @@ clear_nochange_fflags(struct archive_write_disk *a)
}
-#if ( defined(HAVE_LCHFLAGS) || defined(HAVE_CHFLAGS) || defined(HAVE_FCHFLAGS) ) && defined(HAVE_STRUCT_STAT_ST_FLAGS)
+#if (defined(HAVE_CHFLAGSAT) || defined(HAVE_LCHFLAGS) || defined(HAVE_CHFLAGS) || defined(HAVE_FCHFLAGS) ) && defined(HAVE_STRUCT_STAT_ST_FLAGS)
/*
* BSD reads flags using stat() and sets them with one of {f,l,}chflags()
*/
@@ -3614,6 +4034,9 @@ set_fflags_platform(struct archive_write_disk *a, int fd, const char *name,
mode_t mode, unsigned long set, unsigned long clear)
{
int r;
+#if defined(HAVE_FCHFLAGS) && !defined(HAVE_CHFLAGSAT)
+ int fd_opened = 0;
+#endif
const int sf_mask = 0
#ifdef SF_APPEND
| SF_APPEND
@@ -3651,16 +4074,34 @@ set_fflags_platform(struct archive_write_disk *a, int fd, const char *name,
a->st.st_flags &= ~sf_mask;
#ifdef HAVE_FCHFLAGS
+#if !defined(HAVE_CHFLAGSAT)
+ /* If we dont have chflagsat we might need to open a fd */
+ if (fd < 0 && a->chdir_pwd >= 0) {
+ fd = la_openat(a->chdir_pwd, name,
+ O_WRONLY | O_BINARY | O_NOFOLLOW | O_CLOEXEC);
+ if (fd >= 0)
+ fd_opened = 1;
+ }
+#endif
/* If platform has fchflags() and we were given an fd, use it. */
- if (fd >= 0 && fchflags(fd, a->st.st_flags) == 0)
+ if (fd >= 0 && fchflags(fd, a->st.st_flags) == 0) {
+#if !defined(HAVE_CHFLAGSAT)
+ if (fd_opened)
+ close(fd);
+#endif
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.
+ * pathname to set flags. We prefer chflagsat(), then lchflags()
+ * but will use chflags() if we must.
*/
-#ifdef HAVE_LCHFLAGS
+#ifdef HAVE_CHFLAGSAT
+ if (chflagsat(a->chdir_pwd, name, a->st.st_flags,
+ AT_SYMLINK_NOFOLLOW) == 0)
+ return (ARCHIVE_OK);
+#elif defined(HAVE_LCHFLAGS)
if (lchflags(name, a->st.st_flags) == 0)
return (ARCHIVE_OK);
#elif defined(HAVE_CHFLAGS)
@@ -3722,7 +4163,8 @@ set_fflags_platform(struct archive_write_disk *a, int fd, const char *name,
/* If we weren't given an fd, open it ourselves. */
if (myfd < 0) {
- myfd = open(name, O_RDONLY | O_NONBLOCK | O_BINARY | O_CLOEXEC);
+ myfd = la_openat(a->chdir_pwd, name,
+ O_RDONLY | O_NONBLOCK | O_BINARY | O_CLOEXEC);
__archive_ensure_cloexec_flag(myfd);
}
if (myfd < 0)
@@ -3984,7 +4426,7 @@ copy_metadata(struct archive_write_disk *a, const char *metadata,
* with at least a writing mode(O_RDWR or O_WRONLY). it
* makes the data fork uncompressed.
*/
- dffd = open(datafork, 0);
+ dffd = la_openat(a->chdir_pwd, datafork, 0);
if (dffd == -1) {
archive_set_error(&a->archive, errno,
"Failed to open the data fork for metadata");
@@ -4085,14 +4527,21 @@ fixup_appledouble(struct archive_write_disk *a, const char *pathname)
*/
archive_strncpy(&datafork, pathname, p - pathname);
archive_strcat(&datafork, p + 2);
- if (lstat(datafork.s, &st) == -1 ||
- (st.st_mode & AE_IFMT) != AE_IFREG)
+#ifdef HAVE_FSTATAT
+ if (a->chdir_pwd >= 0) {
+ fd = fstatat(a->chdir_pwd, datafork.s, &st,
+ AT_SYMLINK_NOFOLLOW);
+ } else
+#endif
+ fd = lstat(datafork.s, &st);
+ if (fd == -1 || (st.st_mode & AE_IFMT) != AE_IFREG)
goto skip_appledouble;
/*
* Check if the file is in the AppleDouble form.
*/
- fd = open(pathname, O_RDONLY | O_BINARY | O_CLOEXEC);
+ fd = la_openat(a->chdir_pwd, pathname,
+ O_RDONLY | O_BINARY | O_CLOEXEC);
__archive_ensure_cloexec_flag(fd);
if (fd == -1) {
archive_set_error(&a->archive, errno,
@@ -4122,7 +4571,7 @@ fixup_appledouble(struct archive_write_disk *a, const char *pathname)
0);
#endif
if (ret == ARCHIVE_OK) {
- unlink(pathname);
+ la_unlinkat(a->chdir_pwd, pathname, 0);
ret = ARCHIVE_EOF;
}
skip_appledouble:
diff --git a/libarchive/archive_write_disk_private.h b/libarchive/archive_write_disk_private.h
index b655dea2..c85f8b72 100644
--- a/libarchive/archive_write_disk_private.h
+++ b/libarchive/archive_write_disk_private.h
@@ -39,7 +39,7 @@
struct archive_write_disk;
-int archive_write_disk_set_acls(struct archive *, int, const char *,
+int archive_write_disk_set_acls(struct archive *, int, int, const char *,
struct archive_acl *, __LA_MODE_T);
#endif
diff --git a/libarchive/archive_write_disk_windows.c b/libarchive/archive_write_disk_windows.c
index 3d4d577f..fbc9b8b6 100644
--- a/libarchive/archive_write_disk_windows.c
+++ b/libarchive/archive_write_disk_windows.c
@@ -764,6 +764,26 @@ _archive_write_disk_filter_bytes(struct archive *_a, int n)
int
+archive_write_disk_set_chdir(struct archive *_a, const char *dir)
+{
+ (void)_a; /* UNUSED */
+ (void)dir; /* UNUSED */
+
+ errno = EOPNOTSUPP;
+ return (ARCHIVE_FAILED);
+}
+
+int
+archive_write_disk_set_fchdir(struct archive *_a, int fd)
+{
+ (void)_a; /* UNUSED */
+ (void)fd; /* UNUSED */
+
+ errno = EOPNOTSUPP;
+ return (ARCHIVE_FAILED);
+}
+
+int
archive_write_disk_set_options(struct archive *_a, int flags)
{
struct archive_write_disk *a = (struct archive_write_disk *)_a;
diff --git a/tar/bsdtar.c b/tar/bsdtar.c
index b59963d0..42e1860b 100644
--- a/tar/bsdtar.c
+++ b/tar/bsdtar.c
@@ -173,6 +173,7 @@ main(int argc, char **argv)
bsdtar->gid = -1;
bsdtar->uid = -1;
bsdtar->flags = 0;
+ bsdtar->pending_chdir = NULL;
compression = compression2 = '\0';
compression_name = compression2_name = NULL;
compress_program = NULL;
diff --git a/tar/read.c b/tar/read.c
index 658c810f..a0df1de3 100644
--- a/tar/read.c
+++ b/tar/read.c
@@ -108,7 +108,6 @@ tar_mode_x(struct bsdtar *bsdtar)
if ((bsdtar->flags & OPTFLAG_NUMERIC_OWNER) == 0)
archive_write_disk_set_standard_lookup(writer);
archive_write_disk_set_options(writer, bsdtar->extract_flags);
-
read_archive(bsdtar, 'x', writer);
if (unmatched_inclusions_warn(bsdtar->matching,
@@ -224,7 +223,24 @@ read_archive(struct bsdtar *bsdtar, char mode, struct archive *writer)
lafe_errc(1, 0, "Error opening archive: %s",
archive_error_string(a));
- do_chdir(bsdtar);
+ if (bsdtar->pending_chdir != NULL) {
+ /* It makes no sense to chdir in t mode */
+ if (writer != NULL) {
+ r = archive_write_disk_set_chdir(writer,
+ bsdtar->pending_chdir);
+ if (r != ARCHIVE_OK) {
+ if (errno == EOPNOTSUPP)
+ do_chdir(bsdtar);
+ else {
+ lafe_errc(1, 0,
+ "could not chdir to '%s'\n",
+ bsdtar->pending_chdir);
+ }
+ }
+ }
+ free(bsdtar->pending_chdir);
+ bsdtar->pending_chdir = NULL;
+ }
if (mode == 'x') {
/* Set an extract callback so that we can handle SIGINFO. */