diff options
Diffstat (limited to 'libarchive/archive_write_disk_posix.c')
| -rw-r--r-- | libarchive/archive_write_disk_posix.c | 78 | 
1 files changed, 70 insertions, 8 deletions
| diff --git a/libarchive/archive_write_disk_posix.c b/libarchive/archive_write_disk_posix.c index df4b02f5efa1..cc53a3d318e0 100644 --- a/libarchive/archive_write_disk_posix.c +++ b/libarchive/archive_write_disk_posix.c @@ -253,6 +253,8 @@ struct archive_write_disk {  	struct archive_entry	*entry; /* Entry being extracted. */  	char			*name; /* Name of entry, possibly edited. */  	struct archive_string	 _name_data; /* backing store for 'name' */ +	char			*tmpname; /* Temporary name * */ +	struct archive_string	 _tmpname_data; /* backing store for 'tmpname' */  	/* Tasks remaining for this object. */  	int			 todo;  	/* Tasks deferred until end-of-archive. */ @@ -354,6 +356,7 @@ struct archive_write_disk {  static int	la_opendirat(int, const char *); +static int	la_mktemp(struct archive_write_disk *);  static void	fsobj_error(int *, struct archive_string *, int, const char *,  		    const char *);  static int	check_symlinks_fsobj(char *, int *, struct archive_string *, @@ -407,6 +410,30 @@ static ssize_t	_archive_write_disk_data_block(struct archive *, const void *,  		    size_t, int64_t);  static int +la_mktemp(struct archive_write_disk *a) +{ +	int oerrno, fd; +	mode_t mode; + +	archive_string_empty(&a->_tmpname_data); +	archive_string_sprintf(&a->_tmpname_data, "%s.XXXXXX", a->name); +	a->tmpname = a->_tmpname_data.s; + +	fd = __archive_mkstemp(a->tmpname); +	if (fd == -1) +		return -1; + +	mode = a->mode & 0777 & ~a->user_umask; +	if (fchmod(fd, mode) == -1) { +		oerrno = errno; +		close(fd); +		errno = oerrno; +		return -1; +	} +	return fd; +} + +static int  la_opendirat(int fd, const char *path) {  	const int flags = O_CLOEXEC  #if defined(O_BINARY) @@ -1826,6 +1853,14 @@ finish_metadata:  	if (a->fd >= 0) {  		close(a->fd);  		a->fd = -1; +		if (a->tmpname) { +			if (rename(a->tmpname, a->name) == -1) { +				archive_set_error(&a->archive, errno, +				    "rename failed"); +				ret = ARCHIVE_FATAL; +			} +			a->tmpname = NULL; +		}  	}  	/* If there's an entry, we can release it now. */  	archive_entry_free(a->entry); @@ -2103,17 +2138,28 @@ restore_entry(struct archive_write_disk *a)  		}  		if (!S_ISDIR(a->st.st_mode)) { -			/* 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) { -				archive_set_error(&a->archive, errno, -				    "Can't unlink already-existing object"); -				return (ARCHIVE_FAILED); + +			if ((a->flags & ARCHIVE_EXTRACT_SAFE_WRITES) && +			    S_ISREG(a->st.st_mode)) { +				/* Use a temporary file to extract */ +				if ((a->fd = la_mktemp(a)) == -1) +					return ARCHIVE_FAILED; +				a->pst = NULL; +				en = 0; +			} else { +				/* 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_FAILED); +				} +				a->pst = NULL; +				/* Try again. */ +				en = create_filesystem_object(a);  			} -			a->pst = NULL; -			/* Try again. */ -			en = create_filesystem_object(a);  		} else if (!S_ISDIR(a->mode)) {  			/* A dir is in the way of a non-dir, rmdir it. */  			if (a->flags & ARCHIVE_EXTRACT_CLEAR_NOCHANGE_FFLAGS) @@ -2215,6 +2261,13 @@ create_filesystem_object(struct archive_write_disk *a)  		}  		free(linkname_copy);  		archive_string_free(&error_string); +		/* +		 * Unlinking and linking here is really not atomic, +		 * but doing it right, would require us to construct +		 * an mktemplink() function, and then use rename(2). +		 */ +		if (a->flags & ARCHIVE_EXTRACT_SAFE_WRITES) +			unlink(a->name);  		r = link(linkname, a->name) ? errno : 0;  		/*  		 * New cpio and pax formats allow hardlink entries @@ -2253,6 +2306,13 @@ create_filesystem_object(struct archive_write_disk *a)  	linkname = archive_entry_symlink(a->entry);  	if (linkname != NULL) {  #if HAVE_SYMLINK +		/* +		 * Unlinking and linking here is really not atomic, +		 * but doing it right, would require us to construct +		 * an mktempsymlink() function, and then use rename(2). +		 */ +		if (a->flags & ARCHIVE_EXTRACT_SAFE_WRITES) +			unlink(a->name);  		return symlink(linkname, a->name) ? errno : 0;  #else  		return (EPERM); @@ -2288,6 +2348,7 @@ create_filesystem_object(struct archive_write_disk *a)  		/* POSIX requires that we fall through here. */  		/* FALLTHROUGH */  	case AE_IFREG: +		a->tmpname = NULL;  		a->fd = open(a->name,  		    O_WRONLY | O_CREAT | O_EXCL | O_BINARY | O_CLOEXEC, mode);  		__archive_ensure_cloexec_flag(a->fd); @@ -2449,6 +2510,7 @@ _archive_write_disk_free(struct archive *_a)  	archive_write_disk_set_user_lookup(&a->archive, NULL, NULL, NULL);  	archive_entry_free(a->entry);  	archive_string_free(&a->_name_data); +	archive_string_free(&a->_tmpname_data);  	archive_string_free(&a->archive.error_string);  	archive_string_free(&a->path_safe);  	a->archive.magic = 0; | 
