X-Git-Url: https://git.dogcows.com/gitweb?a=blobdiff_plain;f=src%2Fextract.c;h=0d23d4ab55ec9027b45bfd5ae3dff676ef223e3f;hb=9447e799ab5faa6e8be43de4000e1061e0879242;hp=0f50d918c4e081937bf9454c8b969e5bfc47262f;hpb=eca497ef34cc119ad9134a7638fdad6ff36d7065;p=chaz%2Ftar diff --git a/src/extract.c b/src/extract.c index 0f50d91..0d23d4a 100644 --- a/src/extract.c +++ b/src/extract.c @@ -1,10 +1,13 @@ /* Extract files from a tar archive. - Copyright 1988,92,93,94,96,97,98,99,2000,2001 Free Software Foundation, Inc. + + Copyright (C) 1988, 1992, 1993, 1994, 1996, 1997, 1998, 1999, 2000, + 2001, 2003, 2004, 2005, 2006, 2007, 2010 Free Software Foundation, Inc. + Written by John Gilmore, on 1985-11-19. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the - Free Software Foundation; either version 2, or (at your option) any later + Free Software Foundation; either version 3, or (at your option) any later version. This program is distributed in the hope that it will be useful, but @@ -14,79 +17,134 @@ You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., - 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#include "system.h" +#include #include - -#if HAVE_UTIME_H -# include -#else -struct utimbuf - { - long actime; - long modtime; - }; -#endif +#include +#include +#include #include "common.h" -int we_are_root; /* true if our effective uid == 0 */ +static bool we_are_root; /* true if our effective uid == 0 */ static mode_t newdir_umask; /* umask when creating new directories */ static mode_t current_umask; /* current umask (which is set to 0 if -p) */ -/* Status of the permissions of a file that we are extracting. */ -enum permstatus -{ - /* This file may have existed already; its permissions are unknown. */ - UNKNOWN_PERMSTATUS, +#define ALL_MODE_BITS ((mode_t) ~ (mode_t) 0) - /* This file was created using the permissions from the archive. */ - ARCHIVED_PERMSTATUS, +#if ! HAVE_FCHMOD && ! defined fchmod +# define fchmod(fd, mode) (errno = ENOSYS, -1) +#endif +#if ! HAVE_FCHOWN && ! defined fchown +# define fchown(fd, uid, gid) (errno = ENOSYS, -1) +#endif - /* This is an intermediate directory; the archive did not specify - its permissions. */ - INTERDIR_PERMSTATUS -}; +/* Return true if an error number ERR means the system call is + supported in this case. */ +static bool +implemented (int err) +{ + return ! (err == ENOSYS + || err == ENOTSUP + || (EOPNOTSUPP != ENOTSUP && err == EOPNOTSUPP)); +} /* List of directories whose statuses we need to extract after we've - finished extracting their subsidiary files. The head of the list - has the longest name; each non-head element in the list is an - ancestor (in the directory hierarchy) of the preceding element. */ + finished extracting their subsidiary files. If you consider each + contiguous subsequence of elements of the form [D]?[^D]*, where [D] + represents an element where AFTER_LINKS is nonzero and [^D] + represents an element where AFTER_LINKS is zero, then the head + of the subsequence has the longest name, and each non-head element + in the prefix is an ancestor (in the directory hierarchy) of the + preceding element. */ + struct delayed_set_stat { + /* Next directory in list. */ struct delayed_set_stat *next; - struct stat stat_info; + + /* Metadata for this directory. */ + dev_t dev; + ino_t ino; + mode_t mode; /* The desired mode is MODE & ~ current_umask. */ + uid_t uid; + gid_t gid; + struct timespec atime; + struct timespec mtime; + + /* An estimate of the directory's current mode, along with a mask + specifying which bits of this estimate are known to be correct. + If CURRENT_MODE_MASK is zero, CURRENT_MODE's value doesn't + matter. */ + mode_t current_mode; + mode_t current_mode_mask; + + /* This directory is an intermediate directory that was created + as an ancestor of some other directory; it was not mentioned + in the archive, so do not set its uid, gid, atime, or mtime, + and don't alter its mode outside of MODE_RWX. */ + bool interdir; + + /* Whether symbolic links should be followed when accessing the + directory. */ + int atflag; + + /* Do not set the status of this directory until after delayed + links are created. */ + bool after_links; + + /* Directory that the name is relative to. */ + int change_dir; + + /* Length and contents of name. */ size_t file_name_len; - mode_t invert_permissions; - enum permstatus permstatus; char file_name[1]; }; static struct delayed_set_stat *delayed_set_stat_head; -/* List of symbolic links whose creation we have delayed. */ -struct delayed_symlink +/* List of links whose creation we have delayed. */ +struct delayed_link { - /* The next delayed symbolic link in the list. */ - struct delayed_symlink *next; + /* The next delayed link in the list. */ + struct delayed_link *next; - /* The device, inode number and last-modified time of the - placeholder symbolic link. */ + /* The device, inode number and ctime of the placeholder. Use + ctime, not mtime, to make false matches less likely if some + other process removes the placeholder. */ dev_t dev; ino_t ino; - time_t mtime; + struct timespec ctime; - /* The desired owner and group of the symbolic link. */ + /* True if the link is symbolic. */ + bool is_symlink; + + /* The desired metadata, valid only the link is symbolic. */ + mode_t mode; uid_t uid; gid_t gid; + struct timespec atime; + struct timespec mtime; + + /* The directory that the sources and target are relative to. */ + int change_dir; - /* The location and desired target of the desired link, as two - adjacent character strings, both null-terminated. */ - char names[1]; + /* A list of sources for this link. The sources are all to be + hard-linked together. */ + struct string_list *sources; + + /* The desired target of the desired link. */ + char target[1]; }; -static struct delayed_symlink *delayed_symlink_head; +static struct delayed_link *delayed_link_head; + +struct string_list + { + struct string_list *next; + char string[1]; + }; /* Set up to extract files. */ void @@ -95,7 +153,6 @@ extr_init (void) we_are_root = geteuid () == 0; same_permissions_option += we_are_root; same_owner_option += we_are_root; - xalloc_fail_func = extract_finish; /* Option -p clears the kernel umask, so it does not affect proper restoration of file permissions. New intermediate directories will @@ -111,188 +168,288 @@ extr_init (void) } } -/* If restoring permissions, restore the mode for FILE_NAME from - information given in *STAT_INFO; otherwise invert the - INVERT_PERMISSIONS bits from the file's current permissions. - PERMSTATUS specifies the status of the file's permissions. - TYPEFLAG specifies the type of the file. */ -static void -set_mode (char const *file_name, struct stat const *stat_info, - mode_t invert_permissions, enum permstatus permstatus, - char typeflag) +/* Use fchmod if possible, fchmodat otherwise. */ +static int +fd_chmod (int fd, char const *file, mode_t mode, int atflag) { - mode_t mode; + if (0 <= fd) + { + int result = fchmod (fd, mode); + if (result == 0 || implemented (errno)) + return result; + } + return fchmodat (chdir_fd, file, mode, atflag); +} - if (0 < same_permissions_option - && permstatus != INTERDIR_PERMSTATUS) +/* Use fchown if possible, fchownat otherwise. */ +static int +fd_chown (int fd, char const *file, uid_t uid, gid_t gid, int atflag) +{ + if (0 <= fd) { - mode = stat_info->st_mode; - - /* If we created the file and it has a usual mode, then its mode - is normally set correctly already. But on many hosts, some - directories inherit the setgid bits from their parents, so we - we must set directories' modes explicitly. */ - if (permstatus == ARCHIVED_PERMSTATUS - && ! (mode & ~ MODE_RWX) - && typeflag != DIRTYPE - && typeflag != GNUTYPE_DUMPDIR) - return; + int result = fchown (fd, uid, gid); + if (result == 0 || implemented (errno)) + return result; } - else if (! invert_permissions) - return; - else + return fchownat (chdir_fd, file, uid, gid, atflag); +} + +/* Use fstat if possible, fstatat otherwise. */ +static int +fd_stat (int fd, char const *file, struct stat *st, int atflag) +{ + return (0 <= fd + ? fstat (fd, st) + : fstatat (chdir_fd, file, st, atflag)); +} + +/* Set the mode for FILE_NAME to MODE. + MODE_MASK specifies the bits of MODE that we care about; + thus if MODE_MASK is zero, do nothing. + If FD is nonnegative, it is a file descriptor for the file. + CURRENT_MODE and CURRENT_MODE_MASK specify information known about + the file's current mode, using the style of struct delayed_set_stat. + TYPEFLAG specifies the type of the file. + ATFLAG specifies the flag to use when statting the file. */ +static void +set_mode (char const *file_name, + mode_t mode, mode_t mode_mask, int fd, + mode_t current_mode, mode_t current_mode_mask, + char typeflag, int atflag) +{ + if (((current_mode ^ mode) | ~ current_mode_mask) & mode_mask) { - /* We must inspect a directory's current permissions, since the - directory may have inherited its setgid bit from its parent. + if (MODE_ALL & ~ mode_mask & ~ current_mode_mask) + { + struct stat st; + if (fd_stat (fd, file_name, &st, atflag) != 0) + { + stat_error (file_name); + return; + } + current_mode = st.st_mode; + } - INVERT_PERMISSIONS happens to be nonzero only for directories - that we created, so there's no point optimizing this code for - other cases. */ - struct stat st; - if (stat (file_name, &st) != 0) + current_mode &= MODE_ALL; + mode = (current_mode & ~ mode_mask) | (mode & mode_mask); + + if (current_mode != mode) { - stat_error (file_name); - return; + int chmod_errno = + fd_chmod (fd, file_name, mode, atflag) == 0 ? 0 : errno; + + /* On Solaris, chmod may fail if we don't have PRIV_ALL, because + setuid-root files would otherwise be a backdoor. See + http://opensolaris.org/jive/thread.jspa?threadID=95826 + (2009-09-03). */ + if (chmod_errno == EPERM && (mode & S_ISUID) + && priv_set_restore_linkdir () == 0) + { + chmod_errno = + fd_chmod (fd, file_name, mode, atflag) == 0 ? 0 : errno; + priv_set_remove_linkdir (); + } + + /* Linux fchmodat does not support AT_SYMLINK_NOFOLLOW, and + returns ENOTSUP even when operating on non-symlinks, try + again with the flag disabled if it does not appear to be + supported and if the file is not a symlink. This + introduces a race, alas. */ + if (atflag && typeflag != SYMTYPE && ! implemented (chmod_errno)) + chmod_errno = fd_chmod (fd, file_name, mode, 0) == 0 ? 0 : errno; + + if (chmod_errno + && (typeflag != SYMTYPE || implemented (chmod_errno))) + { + errno = chmod_errno; + chmod_error_details (file_name, mode); + } } - mode = st.st_mode ^ invert_permissions; } - - if (chmod (file_name, mode) != 0) - chmod_error_details (file_name, mode); } /* Check time after successfully setting FILE_NAME's time stamp to T. */ static void -check_time (char const *file_name, time_t t) +check_time (char const *file_name, struct timespec t) { - time_t now; - if (start_time < t && (now = time (0)) < t) - WARN ((0, 0, _("%s: time stamp %s is %lu s in the future"), - file_name, tartime (t), (unsigned long) (t - now))); + if (t.tv_sec <= 0) + WARNOPT (WARN_TIMESTAMP, + (0, 0, _("%s: implausibly old time stamp %s"), + file_name, tartime (t, true))); + else if (timespec_cmp (volume_start_time, t) < 0) + { + struct timespec now; + gettime (&now); + if (timespec_cmp (now, t) < 0) + { + char buf[TIMESPEC_STRSIZE_BOUND]; + struct timespec diff; + diff.tv_sec = t.tv_sec - now.tv_sec; + diff.tv_nsec = t.tv_nsec - now.tv_nsec; + if (diff.tv_nsec < 0) + { + diff.tv_nsec += BILLION; + diff.tv_sec--; + } + WARNOPT (WARN_TIMESTAMP, + (0, 0, _("%s: time stamp %s is %s s in the future"), + file_name, tartime (t, true), code_timespec (diff, buf))); + } + } } /* Restore stat attributes (owner, group, mode and times) for - FILE_NAME, using information given in *STAT_INFO. - If not restoring permissions, invert the - INVERT_PERMISSIONS bits from the file's current permissions. - PERMSTATUS specifies the status of the file's permissions. - TYPEFLAG specifies the type of the file. */ - -/* FIXME: About proper restoration of symbolic link attributes, we still do - not have it right. Pretesters' reports tell us we need further study and - probably more configuration. For now, just use lchown if it exists, and - punt for the rest. Sigh! */ + FILE_NAME, using information given in *ST. + If FD is nonnegative, it is a file descriptor for the file. + CURRENT_MODE and CURRENT_MODE_MASK specify information known about + the file's current mode, using the style of struct delayed_set_stat. + TYPEFLAG specifies the type of the file. + If INTERDIR, this is an intermediate directory. + ATFLAG specifies the flag to use when statting the file. */ static void -set_stat (char const *file_name, struct stat const *stat_info, - mode_t invert_permissions, enum permstatus permstatus, - char typeflag) +set_stat (char const *file_name, + struct tar_stat_info const *st, + int fd, mode_t current_mode, mode_t current_mode_mask, + char typeflag, bool interdir, int atflag) { - struct utimbuf utimbuf; + /* Do the utime before the chmod because some versions of utime are + broken and trash the modes of the file. */ - if (typeflag != SYMTYPE) + if (! touch_option && ! interdir) { - /* We do the utime before the chmod because some versions of utime are - broken and trash the modes of the file. */ + struct timespec ts[2]; + if (incremental_option) + ts[0] = st->atime; + else + ts[0].tv_nsec = UTIME_OMIT; + ts[1] = st->mtime; - if (! touch_option && permstatus != INTERDIR_PERMSTATUS) + if (fdutimensat (fd, chdir_fd, file_name, ts, atflag) == 0) { - /* We set the accessed time to `now', which is really the time we - started extracting files, unless incremental_option is used, in - which case .st_atime is used. */ - - /* FIXME: incremental_option should set ctime too, but how? */ - if (incremental_option) - utimbuf.actime = stat_info->st_atime; - else - utimbuf.actime = start_time; - - utimbuf.modtime = stat_info->st_mtime; - - if (utime (file_name, &utimbuf) < 0) - utime_error (file_name); - else - { - check_time (file_name, stat_info->st_atime); - check_time (file_name, stat_info->st_mtime); - } + check_time (file_name, ts[0]); + check_time (file_name, ts[1]); } + else if (typeflag != SYMTYPE || implemented (errno)) + utime_error (file_name); + } + if (0 < same_owner_option && ! interdir) + { /* Some systems allow non-root users to give files away. Once this - done, it is not possible anymore to change file permissions, so we - have to set permissions prior to possibly giving files away. */ - - set_mode (file_name, stat_info, - invert_permissions, permstatus, typeflag); + done, it is not possible anymore to change file permissions. + However, setting file permissions now would be incorrect, since + they would apply to the wrong user, and there would be a race + condition. So, don't use systems that allow non-root users to + give files away. */ + uid_t uid = st->stat.st_uid; + gid_t gid = st->stat.st_gid; + + if (fd_chown (fd, file_name, uid, gid, atflag) == 0) + { + /* Changing the owner can clear st_mode bits in some cases. */ + if ((current_mode | ~ current_mode_mask) & S_IXUGO) + current_mode_mask &= ~ (current_mode & (S_ISUID | S_ISGID)); + } + else if (typeflag != SYMTYPE || implemented (errno)) + chown_error_details (file_name, uid, gid); } - if (0 < same_owner_option && permstatus != INTERDIR_PERMSTATUS) + set_mode (file_name, + st->stat.st_mode & ~ current_umask, + 0 < same_permissions_option && ! interdir ? MODE_ALL : MODE_RWX, + fd, current_mode, current_mode_mask, typeflag, atflag); +} + +/* For each entry H in the leading prefix of entries in HEAD that do + not have after_links marked, mark H and fill in its dev and ino + members. Assume HEAD && ! HEAD->after_links. */ +static void +mark_after_links (struct delayed_set_stat *head) +{ + struct delayed_set_stat *h = head; + + do { - /* When lchown exists, it should be used to change the attributes of - the symbolic link itself. In this case, a mere chown would change - the attributes of the file the symbolic link is pointing to, and - should be avoided. */ + struct stat st; + h->after_links = 1; - if (typeflag == SYMTYPE) - { -#if HAVE_LCHOWN - if (lchown (file_name, stat_info->st_uid, stat_info->st_gid) < 0) - chown_error_details (file_name, - stat_info->st_uid, stat_info->st_gid); -#endif - } + if (deref_stat (h->file_name, &st) != 0) + stat_error (h->file_name); else { - if (chown (file_name, stat_info->st_uid, stat_info->st_gid) < 0) - chown_error_details (file_name, - stat_info->st_uid, stat_info->st_gid); - - /* On a few systems, and in particular, those allowing to give files - away, changing the owner or group destroys the suid or sgid bits. - So let's attempt setting these bits once more. */ - if (stat_info->st_mode & (S_ISUID | S_ISGID | S_ISVTX)) - set_mode (file_name, stat_info, - invert_permissions, permstatus, typeflag); + h->dev = st.st_dev; + h->ino = st.st_ino; } } + while ((h = h->next) && ! h->after_links); } /* Remember to restore stat attributes (owner, group, mode and times) - for the directory FILE_NAME, using information given in *STAT_INFO, + for the directory FILE_NAME, using information given in *ST, once we stop extracting files into that directory. - If not restoring permissions, remember to invert the - INVERT_PERMISSIONS bits from the file's current permissions. - PERMSTATUS specifies the status of the file's permissions. */ + + If ST is null, merely create a placeholder node for an intermediate + directory that was created by make_directories. + + NOTICE: this works only if the archive has usual member order, i.e. + directory, then the files in that directory. Incremental archive have + somewhat reversed order: first go subdirectories, then all other + members. To help cope with this case the variable + delay_directory_restore_option is set by prepare_to_extract. + + If an archive was explicitely created so that its member order is + reversed, some directory timestamps can be restored incorrectly, + e.g.: + tar --no-recursion -cf archive dir dir/file1 foo dir/file2 +*/ static void -delay_set_stat (char const *file_name, struct stat const *stat_info, - mode_t invert_permissions, enum permstatus permstatus) +delay_set_stat (char const *file_name, struct tar_stat_info const *st, + mode_t current_mode, mode_t current_mode_mask, + mode_t mode, int atflag) { size_t file_name_len = strlen (file_name); - struct delayed_set_stat *data = xmalloc (sizeof *data + file_name_len); + struct delayed_set_stat *data = + xmalloc (offsetof (struct delayed_set_stat, file_name) + + file_name_len + 1); + data->next = delayed_set_stat_head; + data->mode = mode; + if (st) + { + data->dev = st->stat.st_dev; + data->ino = st->stat.st_ino; + data->uid = st->stat.st_uid; + data->gid = st->stat.st_gid; + data->atime = st->atime; + data->mtime = st->mtime; + } data->file_name_len = file_name_len; + data->current_mode = current_mode; + data->current_mode_mask = current_mode_mask; + data->interdir = ! st; + data->atflag = atflag; + data->after_links = 0; + data->change_dir = chdir_current; strcpy (data->file_name, file_name); - data->invert_permissions = invert_permissions; - data->permstatus = permstatus; - data->stat_info = *stat_info; - data->next = delayed_set_stat_head; delayed_set_stat_head = data; + if (must_be_dot_or_slash (file_name)) + mark_after_links (data); } /* Update the delayed_set_stat info for an intermediate directory - created on the path to DIR_NAME. The intermediate directory turned - out to be the same as this directory, e.g. due trailing slash or - ".." or symbolic links. *DIR_STAT_INFO is the status of the - directory. */ + created within the file name of DIR. The intermediate directory turned + out to be the same as this directory, e.g. due to ".." or symbolic + links. *DIR_STAT_INFO is the status of the directory. */ static void -repair_delayed_set_stat (char const *dir_name, +repair_delayed_set_stat (char const *dir, struct stat const *dir_stat_info) { struct delayed_set_stat *data; for (data = delayed_set_stat_head; data; data = data->next) { struct stat st; - if (stat (data->file_name, &st) != 0) + if (fstatat (chdir_fd, data->file_name, &st, data->atflag) != 0) { stat_error (data->file_name); return; @@ -301,35 +458,41 @@ repair_delayed_set_stat (char const *dir_name, if (st.st_dev == dir_stat_info->st_dev && st.st_ino == dir_stat_info->st_ino) { - data->stat_info = current_stat; - data->invert_permissions = (MODE_RWX - & (current_stat.st_mode ^ st.st_mode)); - data->permstatus = ARCHIVED_PERMSTATUS; + data->dev = current_stat_info.stat.st_dev; + data->ino = current_stat_info.stat.st_ino; + data->mode = current_stat_info.stat.st_mode; + data->uid = current_stat_info.stat.st_uid; + data->gid = current_stat_info.stat.st_gid; + data->atime = current_stat_info.atime; + data->mtime = current_stat_info.mtime; + data->current_mode = st.st_mode; + data->current_mode_mask = ALL_MODE_BITS; + data->interdir = false; return; } } ERROR ((0, 0, _("%s: Unexpected inconsistency when making directory"), - quotearg_colon (dir_name))); + quotearg_colon (dir))); } -/* After a file/link/symlink/directory creation has failed, see if +/* After a file/link/directory creation has failed, see if it's because some required directory was not present, and if so, - create all required directories. Return non-zero if a directory - was created. */ + create all required directories. Return zero if all the required + directories were created, nonzero (issuing a diagnostic) otherwise. + Set *INTERDIR_MADE if at least one directory was created. */ static int -make_directories (char *file_name) +make_directories (char *file_name, bool *interdir_made) { - char *cursor0 = file_name + FILESYSTEM_PREFIX_LEN (file_name); - char *cursor; /* points into path */ - int did_something = 0; /* did we do anything yet? */ - int mode; - int invert_permissions; - int status; + char *cursor0 = file_name + FILE_SYSTEM_PREFIX_LEN (file_name); + char *cursor; /* points into the file name */ - for (cursor = cursor0; *cursor; cursor++) { + mode_t mode; + mode_t desired_mode; + int status; + if (! ISSLASH (*cursor)) continue; @@ -338,7 +501,7 @@ make_directories (char *file_name) if (cursor == cursor0 || ISSLASH (cursor[-1])) continue; - /* Avoid mkdir where last part of path is "." or "..". */ + /* Avoid mkdir where last part of file name is "." or "..". */ if (cursor[-1] == '.' && (cursor == cursor0 + 1 || ISSLASH (cursor[-2]) @@ -346,837 +509,1066 @@ make_directories (char *file_name) && (cursor == cursor0 + 2 || ISSLASH (cursor[-3]))))) continue; - *cursor = '\0'; /* truncate the path there */ - mode = MODE_RWX & ~ newdir_umask; - invert_permissions = we_are_root ? 0 : MODE_WXUSR & ~ mode; - status = mkdir (file_name, mode ^ invert_permissions); + *cursor = '\0'; /* truncate the name there */ + desired_mode = MODE_RWX & ~ newdir_umask; + mode = desired_mode | (we_are_root ? 0 : MODE_WXUSR); + status = mkdirat (chdir_fd, file_name, mode); if (status == 0) { /* Create a struct delayed_set_stat even if - invert_permissions is zero, because + mode == desired_mode, because repair_delayed_set_stat may need to update the struct. */ delay_set_stat (file_name, - ¤t_stat /* ignored */, - invert_permissions, INTERDIR_PERMSTATUS); + 0, mode & ~ current_umask, MODE_RWX, + desired_mode, AT_SYMLINK_NOFOLLOW); - print_for_mkdir (file_name, cursor - file_name, mode); - did_something = 1; - - *cursor = '/'; - continue; + print_for_mkdir (file_name, cursor - file_name, desired_mode); + *interdir_made = true; + } + else if (errno == EEXIST) + status = 0; + else + { + /* Check whether the desired file exists. Even when the + file exists, mkdir can fail with some errno value E other + than EEXIST, so long as E describes an error condition + that also applies. */ + int e = errno; + struct stat st; + status = fstatat (chdir_fd, file_name, &st, 0); + if (status) + { + errno = e; + mkdir_error (file_name); + } } *cursor = '/'; - - if (errno == EEXIST -#if MSDOS - /* Turbo C mkdir gives a funny errno. */ - || errno == EACCES -#endif - ) - /* Directory already exists. */ - continue; - - /* Some other error in the mkdir. We return to the caller. */ - break; + if (status) + return status; } - return did_something; /* tell them to retry if we made one */ + return 0; } -/* Prepare to extract a file. - Return zero if extraction should not proceed. */ - -static int -prepare_to_extract (char const *file_name) +/* Return true if FILE_NAME (with status *STP, if STP) is not a + directory, and has a time stamp newer than (or equal to) that of + TAR_STAT. */ +static bool +file_newer_p (const char *file_name, struct stat const *stp, + struct tar_stat_info *tar_stat) { - if (to_stdout_option) - return 0; + struct stat st; - if (old_files_option == UNLINK_FIRST_OLD_FILES - && !remove_any_file (file_name, recursive_unlink_option) - && errno && errno != ENOENT) + if (!stp) { - unlink_error (file_name); - return 0; + if (deref_stat (file_name, &st) != 0) + { + if (errno != ENOENT) + { + stat_warn (file_name); + /* Be safer: if the file exists, assume it is newer. */ + return true; + } + return false; + } + stp = &st; } - return 1; + return (! S_ISDIR (stp->st_mode) + && tar_timespec_cmp (tar_stat->mtime, get_stat_mtime (stp)) <= 0); } +#define RECOVER_NO 0 +#define RECOVER_OK 1 +#define RECOVER_SKIP 2 + /* Attempt repairing what went wrong with the extraction. Delete an already existing file or create missing intermediate directories. - Return nonzero if we somewhat increased our chances at a successful - extraction. errno is properly restored on zero return. */ + Return RECOVER_OK if we somewhat increased our chances at a successful + extraction, RECOVER_NO if there are no chances, and RECOVER_SKIP if the + caller should skip extraction of that member. The value of errno is + properly restored on returning RECOVER_NO. + + If REGULAR, the caller was trying to extract onto a regular file. + + Set *INTERDIR_MADE if an intermediate directory is made as part of + the recovery process. */ + static int -maybe_recoverable (char *file_name, int *interdir_made) +maybe_recoverable (char *file_name, bool regular, bool *interdir_made) { + int e = errno; + struct stat st; + struct stat const *stp = 0; + if (*interdir_made) - return 0; + return RECOVER_NO; - switch (errno) + switch (e) { + case ELOOP: + if (! regular + || old_files_option != OVERWRITE_OLD_FILES || dereference_option) + break; + if (strchr (file_name, '/')) + { + if (deref_stat (file_name, &st) != 0) + break; + stp = &st; + } + + /* The caller tried to open a symbolic link with O_NOFOLLOW. + Fall through, treating it as an already-existing file. */ + case EEXIST: /* Remove an old file, if the options allow this. */ switch (old_files_option) { - default: - return 0; + case KEEP_OLD_FILES: + return RECOVER_SKIP; + + case KEEP_NEWER_FILES: + if (file_newer_p (file_name, stp, ¤t_stat_info)) + break; + /* FALL THROUGH */ case DEFAULT_OLD_FILES: + case NO_OVERWRITE_DIR_OLD_FILES: case OVERWRITE_OLD_FILES: - { - int r = remove_any_file (file_name, 0); - errno = EEXIST; - return r; - } + if (0 < remove_any_file (file_name, ORDINARY_REMOVE_OPTION)) + return RECOVER_OK; + break; + + case UNLINK_FIRST_OLD_FILES: + break; } case ENOENT: /* Attempt creating missing intermediate directories. */ - if (! make_directories (file_name)) - { - errno = ENOENT; - return 0; - } - *interdir_made = 1; - return 1; + if (make_directories (file_name, interdir_made) == 0 && *interdir_made) + return RECOVER_OK; + break; default: /* Just say we can't do anything about it... */ - - return 0; + break; } + + errno = e; + return RECOVER_NO; } +/* Fix the statuses of all directories whose statuses need fixing, and + which are not ancestors of FILE_NAME. If AFTER_LINKS is + nonzero, do this for all such directories; otherwise, stop at the + first directory that is marked to be fixed up only after delayed + links are applied. */ static void -extract_sparse_file (int fd, off_t *sizeleft, off_t totalsize, char *name) +apply_nonancestor_delayed_set_stat (char const *file_name, bool after_links) { - int sparse_ind = 0; - - /* assuming sizeleft is initially totalsize */ + size_t file_name_len = strlen (file_name); + bool check_for_renamed_directories = 0; - while (*sizeleft > 0) + while (delayed_set_stat_head) { - size_t written; - size_t count; - union block *data_block = find_next_block (); - if (! data_block) - { - ERROR ((0, 0, _("Unexpected EOF in archive"))); - return; - } - if (lseek (fd, sparsearray[sparse_ind].offset, SEEK_SET) < 0) - { - seek_error_details (name, sparsearray[sparse_ind].offset); - return; - } - written = sparsearray[sparse_ind++].numbytes; - while (written > BLOCKSIZE) + struct delayed_set_stat *data = delayed_set_stat_head; + bool skip_this_one = 0; + struct stat st; + mode_t current_mode = data->current_mode; + mode_t current_mode_mask = data->current_mode_mask; + + check_for_renamed_directories |= data->after_links; + + if (after_links < data->after_links + || (data->file_name_len < file_name_len + && file_name[data->file_name_len] + && (ISSLASH (file_name[data->file_name_len]) + || ISSLASH (file_name[data->file_name_len - 1])) + && memcmp (file_name, data->file_name, data->file_name_len) == 0)) + break; + + chdir_do (data->change_dir); + + if (check_for_renamed_directories) { - count = full_write (fd, data_block->buffer, BLOCKSIZE); - written -= count; - *sizeleft -= count; - if (count != BLOCKSIZE) + if (fstatat (chdir_fd, data->file_name, &st, data->atflag) != 0) { - write_error_details (name, count, BLOCKSIZE); - return; + stat_error (data->file_name); + skip_this_one = 1; } - set_next_block_after (data_block); - data_block = find_next_block (); - if (! data_block) + else { - ERROR ((0, 0, _("Unexpected EOF in archive"))); - return; + current_mode = st.st_mode; + current_mode_mask = ALL_MODE_BITS; + if (! (st.st_dev == data->dev && st.st_ino == data->ino)) + { + ERROR ((0, 0, + _("%s: Directory renamed before its status could be extracted"), + quotearg_colon (data->file_name))); + skip_this_one = 1; + } } } - count = full_write (fd, data_block->buffer, written); - *sizeleft -= count; - - if (count != written) + if (! skip_this_one) { - write_error_details (name, count, written); - return; + struct tar_stat_info sb; + sb.stat.st_mode = data->mode; + sb.stat.st_uid = data->uid; + sb.stat.st_gid = data->gid; + sb.atime = data->atime; + sb.mtime = data->mtime; + set_stat (data->file_name, &sb, + -1, current_mode, current_mode_mask, + DIRTYPE, data->interdir, data->atflag); } - set_next_block_after (data_block); - } -} - -/* Fix the statuses of all directories whose statuses need fixing, and - which are not ancestors of FILE_NAME. */ -static void -apply_nonancestor_delayed_set_stat (char const *file_name) -{ - size_t file_name_len = strlen (file_name); - - while (delayed_set_stat_head) - { - struct delayed_set_stat *data = delayed_set_stat_head; - if (data->file_name_len < file_name_len - && file_name[data->file_name_len] - && (ISSLASH (file_name[data->file_name_len]) - || ISSLASH (file_name[data->file_name_len - 1])) - && memcmp (file_name, data->file_name, data->file_name_len) == 0) - break; delayed_set_stat_head = data->next; - set_stat (data->file_name, &data->stat_info, - data->invert_permissions, data->permstatus, DIRTYPE); free (data); } } -/* Extract a file from the archive. */ -void -extract_archive (void) + + +/* Extractor functions for various member types */ + +static int +extract_dir (char *file_name, int typeflag) { - union block *data_block; - int fd; int status; - size_t count; - size_t name_length; - size_t written; - int openflag; mode_t mode; - off_t size; - size_t skipcrud; - int counter; - int interdir_made = 0; - char typeflag; - union block *exhdr; - -#define CURRENT_FILE_NAME (skipcrud + current_file_name) - - set_next_block_after (current_header); - decode_header (current_header, ¤t_stat, ¤t_format, 1); + mode_t current_mode = 0; + mode_t current_mode_mask = 0; + int atflag = 0; + bool interdir_made = false; - if (interactive_option && !confirm ("extract", current_file_name)) + /* Save 'root device' to avoid purging mount points. */ + if (one_file_system_option && root_device == 0) { - skip_member (); - return; - } - - /* Print the block from current_header and current_stat. */ - - if (verbose_option) - print_header (); + struct stat st; - /* Check for fully specified file names and other atrocities. */ + if (fstatat (chdir_fd, ".", &st, 0) != 0) + stat_diag ("."); + else + root_device = st.st_dev; + } - skipcrud = 0; - if (! absolute_names_option) + if (incremental_option) + /* Read the entry and delete files that aren't listed in the archive. */ + purge_directory (file_name); + else if (typeflag == GNUTYPE_DUMPDIR) + skip_member (); + + /* If ownership or permissions will be restored later, create the + directory with restrictive permissions at first, so that in the + meantime processes owned by other users do not inadvertently + create files under this directory that inherit the wrong owner, + group, or permissions from the directory. If not root, though, + make the directory writeable and searchable at first, so that + files can be created under it. */ + mode = ((current_stat_info.stat.st_mode + & (0 < same_owner_option || 0 < same_permissions_option + ? S_IRWXU + : MODE_RWX)) + | (we_are_root ? 0 : MODE_WXUSR)); + + for (;;) { - if (contains_dot_dot (CURRENT_FILE_NAME)) + status = mkdir (file_name, mode); + if (status == 0) { - ERROR ((0, 0, _("%s: Member name contains `..'"), - quotearg_colon (CURRENT_FILE_NAME))); - skip_member (); - return; + current_mode = mode & ~ current_umask; + current_mode_mask = MODE_RWX; + atflag = AT_SYMLINK_NOFOLLOW; + break; } - skipcrud = FILESYSTEM_PREFIX_LEN (current_file_name); - while (ISSLASH (CURRENT_FILE_NAME[0])) - skipcrud++; + if (errno == EEXIST + && (interdir_made + || old_files_option == DEFAULT_OLD_FILES + || old_files_option == OVERWRITE_OLD_FILES)) + { + struct stat st; + if (deref_stat (file_name, &st) == 0) + { + current_mode = st.st_mode; + current_mode_mask = ALL_MODE_BITS; + + if (S_ISDIR (current_mode)) + { + if (interdir_made) + { + repair_delayed_set_stat (file_name, &st); + return 0; + } + break; + } + } + errno = EEXIST; + } - if (skipcrud) + switch (maybe_recoverable (file_name, false, &interdir_made)) { - static int warned_once; - - if (!warned_once) + case RECOVER_OK: + continue; + + case RECOVER_SKIP: + break; + + case RECOVER_NO: + if (errno != EEXIST) { - warned_once = 1; - WARN ((0, 0, _("Removing leading `%.*s' from member names"), - (int) skipcrud, current_file_name)); + mkdir_error (file_name); + return 1; } + break; } + break; } - apply_nonancestor_delayed_set_stat (CURRENT_FILE_NAME); + if (status == 0 + || old_files_option == DEFAULT_OLD_FILES + || old_files_option == OVERWRITE_OLD_FILES) + delay_set_stat (file_name, ¤t_stat_info, + current_mode, current_mode_mask, + current_stat_info.stat.st_mode, atflag); + return status; +} - /* Take a safety backup of a previously existing file. */ - if (backup_option && !to_stdout_option) - if (!maybe_backup_file (CURRENT_FILE_NAME, 0)) - { - int e = errno; - ERROR ((0, e, _("%s: Was unable to backup this file"), - quotearg_colon (CURRENT_FILE_NAME))); - skip_member (); - return; - } - /* Extract the archive entry according to its type. */ +static int +open_output_file (char const *file_name, int typeflag, mode_t mode, + mode_t *current_mode, mode_t *current_mode_mask) +{ + int fd; + bool overwriting_old_files = old_files_option == OVERWRITE_OLD_FILES; + int openflag = (O_WRONLY | O_BINARY | O_CLOEXEC | O_NOCTTY | O_NONBLOCK + | O_CREAT + | (overwriting_old_files + ? O_TRUNC | (dereference_option ? 0 : O_NOFOLLOW) + : O_EXCL)); - typeflag = current_header->header.typeflag; - switch (typeflag) + if (typeflag == CONTTYPE) { - /* JK - What we want to do if the file is sparse is loop through - the array of sparse structures in the header and read in and - translate the character strings representing 1) the offset at - which to write and 2) how many bytes to write into numbers, - which we store into the scratch array, "sparsearray". This - array makes our life easier the same way it did in creating the - tar file that had to deal with a sparse file. - - After we read in the first five (at most) sparse structures, we - check to see if the file has an extended header, i.e., if more - sparse structures are needed to describe the contents of the new - file. If so, we read in the extended headers and continue to - store their contents into the sparsearray. */ - - case GNUTYPE_SPARSE: - sp_array_size = 10; - sparsearray = - xmalloc (sp_array_size * sizeof (struct sp_array)); + static int conttype_diagnosed; - for (counter = 0; counter < SPARSES_IN_OLDGNU_HEADER; counter++) + if (!conttype_diagnosed) { - struct sparse const *s = ¤t_header->oldgnu_header.sp[counter]; - sparsearray[counter].offset = OFF_FROM_HEADER (s->offset); - sparsearray[counter].numbytes = SIZE_FROM_HEADER (s->numbytes); - if (!sparsearray[counter].numbytes) - break; + conttype_diagnosed = 1; + WARNOPT (WARN_CONTIGUOUS_CAST, + (0, 0, _("Extracting contiguous files as regular files"))); } + } - if (current_header->oldgnu_header.isextended) + fd = openat (chdir_fd, file_name, openflag, mode); + if (0 <= fd) + { + if (overwriting_old_files) { - /* Read in the list of extended headers and translate them - into the sparsearray as before. Note that this - invalidates current_header. */ + struct stat st; + if (fstat (fd, &st) != 0) + { + int e = errno; + close (fd); + errno = e; + return -1; + } + if (! S_ISREG (st.st_mode)) + { + close (fd); + errno = EEXIST; + return -1; + } + *current_mode = st.st_mode; + *current_mode_mask = ALL_MODE_BITS; + } + else + { + *current_mode = mode & ~ current_umask; + *current_mode_mask = MODE_RWX; + } + } - /* static */ int ind = SPARSES_IN_OLDGNU_HEADER; + return fd; +} - while (1) - { - exhdr = find_next_block (); - if (! exhdr) - { - ERROR ((0, 0, _("Unexpected EOF in archive"))); - return; - } - for (counter = 0; counter < SPARSES_IN_SPARSE_HEADER; counter++) - { - struct sparse const *s = &exhdr->sparse_header.sp[counter]; - if (counter + ind > sp_array_size - 1) - { - /* Realloc the scratch area since we've run out of - room. */ +static int +extract_file (char *file_name, int typeflag) +{ + int fd; + off_t size; + union block *data_block; + int status; + size_t count; + size_t written; + bool interdir_made = false; + mode_t mode = (current_stat_info.stat.st_mode & MODE_RWX + & ~ (0 < same_owner_option ? S_IRWXG | S_IRWXO : 0)); + mode_t current_mode = 0; + mode_t current_mode_mask = 0; - sp_array_size *= 2; - sparsearray = - xrealloc (sparsearray, - sp_array_size * sizeof (struct sp_array)); - } - if (s->numbytes[0] == 0) - break; - sparsearray[counter + ind].offset = - OFF_FROM_HEADER (s->offset); - sparsearray[counter + ind].numbytes = - SIZE_FROM_HEADER (s->numbytes); - } - if (!exhdr->sparse_header.isextended) - break; - else - { - ind += SPARSES_IN_SPARSE_HEADER; - set_next_block_after (exhdr); - } + if (to_stdout_option) + fd = STDOUT_FILENO; + else if (to_command_option) + { + fd = sys_exec_command (file_name, 'f', ¤t_stat_info); + if (fd < 0) + { + skip_member (); + return 0; + } + } + else + { + while ((fd = open_output_file (file_name, typeflag, mode, + ¤t_mode, ¤t_mode_mask)) + < 0) + { + int recover = maybe_recoverable (file_name, true, &interdir_made); + if (recover != RECOVER_OK) + { + skip_member (); + if (recover == RECOVER_SKIP) + return 0; + open_error (file_name); + return 1; } - set_next_block_after (exhdr); } - /* Fall through. */ + } - case AREGTYPE: - case REGTYPE: - case CONTTYPE: + mv_begin_read (¤t_stat_info); + if (current_stat_info.is_sparse) + sparse_extract_file (fd, ¤t_stat_info, &size); + else + for (size = current_stat_info.stat.st_size; size > 0; ) + { + mv_size_left (size); - /* Appears to be a file. But BSD tar uses the convention that a slash - suffix means a directory. */ + /* Locate data, determine max length writeable, write it, + block that we have used the data, then check if the write + worked. */ - name_length = strlen (CURRENT_FILE_NAME); - if (FILESYSTEM_PREFIX_LEN (CURRENT_FILE_NAME) < name_length - && CURRENT_FILE_NAME[name_length - 1] == '/') - goto really_dir; + data_block = find_next_block (); + if (! data_block) + { + ERROR ((0, 0, _("Unexpected EOF in archive"))); + break; /* FIXME: What happens, then? */ + } - /* FIXME: deal with protection issues. */ + written = available_space_after (data_block); - again_file: - openflag = (O_WRONLY | O_BINARY | O_CREAT - | (old_files_option == OVERWRITE_OLD_FILES - ? O_TRUNC - : O_EXCL)); - mode = current_stat.st_mode & MODE_RWX & ~ current_umask; + if (written > size) + written = size; + errno = 0; + count = full_write (fd, data_block->buffer, written); + size -= written; - if (to_stdout_option) - { - fd = STDOUT_FILENO; - goto extract_file; - } + set_next_block_after ((union block *) + (data_block->buffer + written - 1)); + if (count != written) + { + if (!to_command_option) + write_error_details (file_name, count, written); + /* FIXME: shouldn't we restore from backup? */ + break; + } + } - if (! prepare_to_extract (CURRENT_FILE_NAME)) - { - skip_member (); - if (backup_option) - undo_last_backup (); - break; - } + skip_file (size); -#if O_CTG - /* Contiguous files (on the Masscomp) have to specify the size in - the open call that creates them. */ + mv_end (); - if (typeflag == CONTTYPE) - fd = open (CURRENT_FILE_NAME, openflag | O_CTG, - mode, current_stat.st_size); - else - fd = open (CURRENT_FILE_NAME, openflag, mode); + /* If writing to stdout, don't try to do anything to the filename; + it doesn't exist, or we don't want to touch it anyway. */ -#else /* not O_CTG */ - if (typeflag == CONTTYPE) - { - static int conttype_diagnosed; + if (to_stdout_option) + return 0; - if (!conttype_diagnosed) - { - conttype_diagnosed = 1; - WARN ((0, 0, _("Extracting contiguous files as regular files"))); - } - } - fd = open (CURRENT_FILE_NAME, openflag, mode); + if (! to_command_option) + set_stat (file_name, ¤t_stat_info, fd, + current_mode, current_mode_mask, typeflag, false, + (old_files_option == OVERWRITE_OLD_FILES + ? 0 : AT_SYMLINK_NOFOLLOW)); -#endif /* not O_CTG */ + status = close (fd); + if (status < 0) + close_error (file_name); - if (fd < 0) + if (to_command_option) + sys_wait_command (); + + return status; +} + +/* Create a placeholder file with name FILE_NAME, which will be + replaced after other extraction is done by a symbolic link if + IS_SYMLINK is true, and by a hard link otherwise. Set + *INTERDIR_MADE if an intermediate directory is made in the + process. */ + +static int +create_placeholder_file (char *file_name, bool is_symlink, bool *interdir_made) +{ + int fd; + struct stat st; + + while ((fd = openat (chdir_fd, file_name, O_WRONLY | O_CREAT | O_EXCL, 0)) < 0) + { + switch (maybe_recoverable (file_name, false, interdir_made)) { - if (maybe_recoverable (CURRENT_FILE_NAME, &interdir_made)) - goto again_file; + case RECOVER_OK: + continue; - open_error (CURRENT_FILE_NAME); - skip_member (); - if (backup_option) - undo_last_backup (); - break; + case RECOVER_SKIP: + return 0; + + case RECOVER_NO: + open_error (file_name); + return -1; } + } - extract_file: - if (typeflag == GNUTYPE_SPARSE) + if (fstat (fd, &st) != 0) + { + stat_error (file_name); + close (fd); + } + else if (close (fd) != 0) + close_error (file_name); + else + { + struct delayed_set_stat *h; + struct delayed_link *p = + xmalloc (offsetof (struct delayed_link, target) + + strlen (current_stat_info.link_name) + + 1); + p->next = delayed_link_head; + delayed_link_head = p; + p->dev = st.st_dev; + p->ino = st.st_ino; + p->ctime = get_stat_ctime (&st); + p->is_symlink = is_symlink; + if (is_symlink) { - char *name; - size_t name_length_bis; - - /* Kludge alert. NAME is assigned to header.name because - during the extraction, the space that contains the header - will get scribbled on, and the name will get munged, so any - error messages that happen to contain the filename will look - REAL interesting unless we do this. */ - - name_length_bis = strlen (CURRENT_FILE_NAME) + 1; - name = xmalloc (name_length_bis); - memcpy (name, CURRENT_FILE_NAME, name_length_bis); - size = current_stat.st_size; - extract_sparse_file (fd, &size, current_stat.st_size, name); - free (sparsearray); + p->mode = current_stat_info.stat.st_mode; + p->uid = current_stat_info.stat.st_uid; + p->gid = current_stat_info.stat.st_gid; + p->atime = current_stat_info.atime; + p->mtime = current_stat_info.mtime; } - else - for (size = current_stat.st_size; size > 0; ) - { - if (multi_volume_option) - { - assign_string (&save_name, current_file_name); - save_totsize = current_stat.st_size; - save_sizeleft = size; - } - - /* Locate data, determine max length writeable, write it, - block that we have used the data, then check if the write - worked. */ - - data_block = find_next_block (); - if (! data_block) - { - ERROR ((0, 0, _("Unexpected EOF in archive"))); - break; /* FIXME: What happens, then? */ - } - - written = available_space_after (data_block); - - if (written > size) - written = size; - errno = 0; - count = full_write (fd, data_block->buffer, written); - size -= count; - - set_next_block_after ((union block *) - (data_block->buffer + written - 1)); - if (count != written) - { - write_error_details (CURRENT_FILE_NAME, count, written); - break; - } - } + p->change_dir = chdir_current; + p->sources = xmalloc (offsetof (struct string_list, string) + + strlen (file_name) + 1); + p->sources->next = 0; + strcpy (p->sources->string, file_name); + strcpy (p->target, current_stat_info.link_name); + + h = delayed_set_stat_head; + if (h && ! h->after_links + && strncmp (file_name, h->file_name, h->file_name_len) == 0 + && ISSLASH (file_name[h->file_name_len]) + && (last_component (file_name) == file_name + h->file_name_len + 1)) + mark_after_links (h); - skip_file (size); + return 0; + } + + return -1; +} + +static int +extract_link (char *file_name, int typeflag) +{ + bool interdir_made = false; + char const *link_name; + int rc; - if (multi_volume_option) - assign_string (&save_name, 0); + link_name = current_stat_info.link_name; - /* If writing to stdout, don't try to do anything to the filename; - it doesn't exist, or we don't want to touch it anyway. */ + if (! absolute_names_option && contains_dot_dot (link_name)) + return create_placeholder_file (file_name, false, &interdir_made); - if (to_stdout_option) - break; + do + { + struct stat st1, st2; + int e; + int status = linkat (chdir_fd, link_name, chdir_fd, file_name, 0); + e = errno; - status = close (fd); - if (status < 0) + if (status == 0) { - close_error (CURRENT_FILE_NAME); - if (backup_option) - undo_last_backup (); + struct delayed_link *ds = delayed_link_head; + if (ds + && fstatat (chdir_fd, link_name, &st1, AT_SYMLINK_NOFOLLOW) == 0) + for (; ds; ds = ds->next) + if (ds->change_dir == chdir_current + && ds->dev == st1.st_dev + && ds->ino == st1.st_ino + && timespec_cmp (ds->ctime, get_stat_ctime (&st1)) == 0) + { + struct string_list *p = xmalloc (offsetof (struct string_list, string) + + strlen (file_name) + 1); + strcpy (p->string, file_name); + p->next = ds->sources; + ds->sources = p; + break; + } + return 0; } + else if ((e == EEXIST && strcmp (link_name, file_name) == 0) + || ((fstatat (chdir_fd, link_name, &st1, AT_SYMLINK_NOFOLLOW) + == 0) + && (fstatat (chdir_fd, file_name, &st2, AT_SYMLINK_NOFOLLOW) + == 0) + && st1.st_dev == st2.st_dev + && st1.st_ino == st2.st_ino)) + return 0; + + errno = e; + } + while ((rc = maybe_recoverable (file_name, false, &interdir_made)) + == RECOVER_OK); - set_stat (CURRENT_FILE_NAME, ¤t_stat, 0, - (old_files_option == OVERWRITE_OLD_FILES - ? UNKNOWN_PERMSTATUS - : ARCHIVED_PERMSTATUS), - typeflag); - break; + if (rc == RECOVER_SKIP) + return 0; + if (!(incremental_option && errno == EEXIST)) + { + link_error (link_name, file_name); + return 1; + } + return 0; +} - case SYMTYPE: +static int +extract_symlink (char *file_name, int typeflag) +{ #ifdef HAVE_SYMLINK - if (! prepare_to_extract (CURRENT_FILE_NAME)) - break; + bool interdir_made = false; - if (absolute_names_option - || (ISSLASH (current_link_name - [FILESYSTEM_PREFIX_LEN (current_link_name)]) - && ! contains_dot_dot (current_link_name))) - { - while (status = symlink (current_link_name, CURRENT_FILE_NAME), - status != 0) - if (!maybe_recoverable (CURRENT_FILE_NAME, &interdir_made)) - break; + if (! absolute_names_option + && (IS_ABSOLUTE_FILE_NAME (current_stat_info.link_name) + || contains_dot_dot (current_stat_info.link_name))) + return create_placeholder_file (file_name, true, &interdir_made); - if (status == 0) - set_stat (CURRENT_FILE_NAME, ¤t_stat, 0, 0, SYMTYPE); - else - symlink_error (current_link_name, CURRENT_FILE_NAME); - } - else - { - /* This symbolic link is potentially dangerous. Don't - create it now; instead, create a placeholder file, which - will be replaced after other extraction is done. */ - struct stat st; + while (symlinkat (current_stat_info.link_name, chdir_fd, file_name) != 0) + switch (maybe_recoverable (file_name, false, &interdir_made)) + { + case RECOVER_OK: + continue; - while (fd = open (CURRENT_FILE_NAME, O_WRONLY | O_CREAT | O_EXCL, 0), - fd < 0) - if (! maybe_recoverable (CURRENT_FILE_NAME, &interdir_made)) - break; + case RECOVER_SKIP: + return 0; - status = -1; - if (fd < 0) - open_error (CURRENT_FILE_NAME); - else if (fstat (fd, &st) != 0) - { - stat_error (CURRENT_FILE_NAME); - close (fd); - } - else if (close (fd) != 0) - close_error (CURRENT_FILE_NAME); - else - { - size_t filelen = strlen (CURRENT_FILE_NAME); - size_t linklen = strlen (current_link_name); - struct delayed_symlink *p = - xmalloc (sizeof *p + filelen + linklen + 1); - p->next = delayed_symlink_head; - delayed_symlink_head = p; - p->dev = st.st_dev; - p->ino = st.st_ino; - p->mtime = st.st_mtime; - p->uid = current_stat.st_uid; - p->gid = current_stat.st_gid; - memcpy (p->names, CURRENT_FILE_NAME, filelen + 1); - memcpy (p->names + filelen + 1, current_link_name, linklen + 1); - status = 0; - } - } - - if (status != 0 && backup_option) - undo_last_backup (); - break; + case RECOVER_NO: + symlink_error (current_stat_info.link_name, file_name); + return -1; + } + + set_stat (file_name, ¤t_stat_info, -1, 0, 0, + SYMTYPE, false, AT_SYMLINK_NOFOLLOW); + return 0; #else + static int warned_once; + + if (!warned_once) + { + warned_once = 1; + WARNOPT (WARN_SYMBOLIC_CAST, + (0, 0, + _("Attempting extraction of symbolic links as hard links"))); + } + return extract_link (file_name, typeflag); +#endif +} + +#if S_IFCHR || S_IFBLK +static int +extract_node (char *file_name, int typeflag) +{ + bool interdir_made = false; + mode_t mode = (current_stat_info.stat.st_mode & MODE_RWX + & ~ (0 < same_owner_option ? S_IRWXG | S_IRWXO : 0)); + + while (mknodat (chdir_fd, file_name, mode, current_stat_info.stat.st_rdev) + != 0) + switch (maybe_recoverable (file_name, false, &interdir_made)) { - static int warned_once; + case RECOVER_OK: + continue; - if (!warned_once) - { - warned_once = 1; - WARN ((0, 0, - _("Attempting extraction of symbolic links as hard links"))); - } + case RECOVER_SKIP: + return 0; + + case RECOVER_NO: + mknod_error (file_name); + return -1; } - typeflag = LNKTYPE; - /* Fall through. */ + + set_stat (file_name, ¤t_stat_info, -1, + mode & ~ current_umask, MODE_RWX, + typeflag, false, AT_SYMLINK_NOFOLLOW); + return 0; +} #endif - case LNKTYPE: - if (! prepare_to_extract (CURRENT_FILE_NAME)) - break; +#if HAVE_MKFIFO || defined mkfifo +static int +extract_fifo (char *file_name, int typeflag) +{ + bool interdir_made = false; + mode_t mode = (current_stat_info.stat.st_mode & MODE_RWX + & ~ (0 < same_owner_option ? S_IRWXG | S_IRWXO : 0)); - again_link: + while (mkfifoat (chdir_fd, file_name, mode) != 0) + switch (maybe_recoverable (file_name, false, &interdir_made)) { - struct stat st1, st2; - int e; + case RECOVER_OK: + continue; - /* MSDOS does not implement links. However, djgpp's link() actually - copies the file. */ - status = link (current_link_name, CURRENT_FILE_NAME); + case RECOVER_SKIP: + return 0; - if (status == 0) - break; - if (maybe_recoverable (CURRENT_FILE_NAME, &interdir_made)) - goto again_link; + case RECOVER_NO: + mkfifo_error (file_name); + return -1; + } - if (incremental_option && errno == EEXIST) - break; - e = errno; - if (stat (current_link_name, &st1) == 0 - && stat (CURRENT_FILE_NAME, &st2) == 0 - && st1.st_dev == st2.st_dev - && st1.st_ino == st2.st_ino) - break; + set_stat (file_name, ¤t_stat_info, -1, + mode & ~ current_umask, MODE_RWX, + typeflag, false, AT_SYMLINK_NOFOLLOW); + return 0; +} +#endif - ERROR ((0, e, _("%s: Cannot link to %s"), - quotearg_colon (CURRENT_FILE_NAME), - quote (current_link_name))); - if (backup_option) - undo_last_backup (); - } +static int +extract_volhdr (char *file_name, int typeflag) +{ + skip_member (); + return 0; +} + +static int +extract_failure (char *file_name, int typeflag) +{ + return 1; +} + +typedef int (*tar_extractor_t) (char *file_name, int typeflag); + + + +/* Prepare to extract a file. Find extractor function. + Return zero if extraction should not proceed. */ + +static int +prepare_to_extract (char const *file_name, int typeflag, tar_extractor_t *fun) +{ + int rc = 1; + + if (EXTRACT_OVER_PIPE) + rc = 0; + + /* Select the extractor */ + switch (typeflag) + { + case GNUTYPE_SPARSE: + *fun = extract_file; + rc = 1; + break; + + case AREGTYPE: + case REGTYPE: + case CONTTYPE: + /* Appears to be a file. But BSD tar uses the convention that a slash + suffix means a directory. */ + if (current_stat_info.had_trailing_slash) + *fun = extract_dir; + else + { + *fun = extract_file; + rc = 1; + } + break; + + case SYMTYPE: + *fun = extract_symlink; + break; + + case LNKTYPE: + *fun = extract_link; break; #if S_IFCHR case CHRTYPE: - current_stat.st_mode |= S_IFCHR; - goto make_node; + current_stat_info.stat.st_mode |= S_IFCHR; + *fun = extract_node; + break; #endif #if S_IFBLK case BLKTYPE: - current_stat.st_mode |= S_IFBLK; -#endif - -#if S_IFCHR || S_IFBLK - make_node: - if (! prepare_to_extract (CURRENT_FILE_NAME)) - break; - - status = mknod (CURRENT_FILE_NAME, current_stat.st_mode, - current_stat.st_rdev); - if (status != 0) - { - if (maybe_recoverable (CURRENT_FILE_NAME, &interdir_made)) - goto make_node; - mknod_error (CURRENT_FILE_NAME); - if (backup_option) - undo_last_backup (); - break; - }; - set_stat (CURRENT_FILE_NAME, ¤t_stat, 0, - ARCHIVED_PERMSTATUS, typeflag); + current_stat_info.stat.st_mode |= S_IFBLK; + *fun = extract_node; break; #endif #if HAVE_MKFIFO || defined mkfifo case FIFOTYPE: - if (! prepare_to_extract (CURRENT_FILE_NAME)) - break; - - while (status = mkfifo (CURRENT_FILE_NAME, current_stat.st_mode), - status != 0) - if (!maybe_recoverable (CURRENT_FILE_NAME, &interdir_made)) - break; - - if (status == 0) - set_stat (CURRENT_FILE_NAME, ¤t_stat, 0, - ARCHIVED_PERMSTATUS, typeflag); - else - { - mkfifo_error (CURRENT_FILE_NAME); - if (backup_option) - undo_last_backup (); - } + *fun = extract_fifo; break; #endif case DIRTYPE: case GNUTYPE_DUMPDIR: - name_length = strlen (CURRENT_FILE_NAME); - - really_dir: + *fun = extract_dir; + if (current_stat_info.is_dumpdir) + delay_directory_restore_option = true; + break; - if (incremental_option) - { - /* Read the entry and delete files that aren't listed in the - archive. */ + case GNUTYPE_VOLHDR: + *fun = extract_volhdr; + break; - gnu_restore (skipcrud); - } - else if (typeflag == GNUTYPE_DUMPDIR) - skip_member (); + case GNUTYPE_MULTIVOL: + ERROR ((0, 0, + _("%s: Cannot extract -- file is continued from another volume"), + quotearg_colon (current_stat_info.file_name))); + *fun = extract_failure; + break; - if (! prepare_to_extract (CURRENT_FILE_NAME)) - break; + case GNUTYPE_LONGNAME: + case GNUTYPE_LONGLINK: + ERROR ((0, 0, _("Unexpected long name header"))); + *fun = extract_failure; + break; - mode = ((current_stat.st_mode - | (we_are_root ? 0 : MODE_WXUSR)) - & MODE_RWX); + default: + WARNOPT (WARN_UNKNOWN_CAST, + (0, 0, + _("%s: Unknown file type `%c', extracted as normal file"), + quotearg_colon (file_name), typeflag)); + *fun = extract_file; + } - again_dir: - { - /* Do not pass redundant trailing "/" to mkdir, as POSIX does - not allow mkdir to ignore it. */ - size_t len = name_length; - char ch = '\0'; - while (FILESYSTEM_PREFIX_LEN (CURRENT_FILE_NAME) < len - && CURRENT_FILE_NAME[len - 1] == '/') - len--, ch = '/'; - CURRENT_FILE_NAME[len] = '\0'; - status = mkdir (CURRENT_FILE_NAME, mode); - CURRENT_FILE_NAME[len] = ch; - } + /* Determine whether the extraction should proceed */ + if (rc == 0) + return 0; - if (status != 0) + switch (old_files_option) + { + case UNLINK_FIRST_OLD_FILES: + if (!remove_any_file (file_name, + recursive_unlink_option ? RECURSIVE_REMOVE_OPTION + : ORDINARY_REMOVE_OPTION) + && errno && errno != ENOENT) { - if (errno == EEXIST && interdir_made) - { - struct stat st; - if (stat (CURRENT_FILE_NAME, &st) == 0) - { - repair_delayed_set_stat (CURRENT_FILE_NAME, &st); - break; - } - errno = EEXIST; - } - - if (maybe_recoverable (CURRENT_FILE_NAME, &interdir_made)) - goto again_dir; - - if (errno != EEXIST) - { - mkdir_error (CURRENT_FILE_NAME); - if (backup_option) - undo_last_backup (); - break; - } + unlink_error (file_name); + return 0; } - - if (status == 0 - || old_files_option == OVERWRITE_OLD_FILES) - delay_set_stat (CURRENT_FILE_NAME, ¤t_stat, - mode & ~ current_stat.st_mode, - (status == 0 - ? ARCHIVED_PERMSTATUS - : UNKNOWN_PERMSTATUS)); break; - case GNUTYPE_VOLHDR: - if (verbose_option) - fprintf (stdlis, _("Reading %s\n"), quote (current_file_name)); + case KEEP_NEWER_FILES: + if (file_newer_p (file_name, 0, ¤t_stat_info)) + { + WARNOPT (WARN_IGNORE_NEWER, + (0, 0, _("Current %s is newer or same age"), + quote (file_name))); + return 0; + } break; - case GNUTYPE_NAMES: - extract_mangle (); + default: break; + } - case GNUTYPE_MULTIVOL: - ERROR ((0, 0, - _("%s: Cannot extract -- file is continued from another volume"), - quotearg_colon (current_file_name))); - skip_member (); - if (backup_option) - undo_last_backup (); - break; + return 1; +} - case GNUTYPE_LONGNAME: - case GNUTYPE_LONGLINK: - ERROR ((0, 0, _("Visible long name error"))); +/* Extract a file from the archive. */ +void +extract_archive (void) +{ + char typeflag; + tar_extractor_t fun; + + fatal_exit_hook = extract_finish; + + set_next_block_after (current_header); + + if (!current_stat_info.file_name[0] + || (interactive_option + && !confirm ("extract", current_stat_info.file_name))) + { skip_member (); - if (backup_option) - undo_last_backup (); - break; + return; + } - default: - WARN ((0, 0, - _("%s: Unknown file type '%c', extracted as normal file"), - quotearg_colon (CURRENT_FILE_NAME), typeflag)); - goto again_file; + /* Print the block from current_header and current_stat. */ + if (verbose_option) + print_header (¤t_stat_info, current_header, -1); + + /* Restore stats for all non-ancestor directories, unless + it is an incremental archive. + (see NOTICE in the comment to delay_set_stat above) */ + if (!delay_directory_restore_option) + { + int dir = chdir_current; + apply_nonancestor_delayed_set_stat (current_stat_info.file_name, 0); + chdir_do (dir); } -#undef CURRENT_FILE_NAME + /* Take a safety backup of a previously existing file. */ + + if (backup_option) + if (!maybe_backup_file (current_stat_info.file_name, 0)) + { + int e = errno; + ERROR ((0, e, _("%s: Was unable to backup this file"), + quotearg_colon (current_stat_info.file_name))); + skip_member (); + return; + } + + /* Extract the archive entry according to its type. */ + /* KLUDGE */ + typeflag = sparse_member_p (¤t_stat_info) ? + GNUTYPE_SPARSE : current_header->header.typeflag; + + if (prepare_to_extract (current_stat_info.file_name, typeflag, &fun)) + { + if (fun && (*fun) (current_stat_info.file_name, typeflag) + && backup_option) + undo_last_backup (); + } + else + skip_member (); + } -/* Extract the symbolic links whose final extraction were delayed. */ +/* Extract the links whose final extraction were delayed. */ static void -apply_delayed_symlinks (void) +apply_delayed_links (void) { - struct delayed_symlink *p; - struct delayed_symlink *next; + struct delayed_link *ds; - for (p = delayed_symlink_head; p; p = next) + for (ds = delayed_link_head; ds; ) { - char const *file = p->names; - struct stat st; + struct string_list *sources = ds->sources; + char const *valid_source = 0; - /* Before doing anything, make sure the placeholder file is still - there. If the placeholder isn't there, don't worry about it, as - it may have been removed by a later extraction. */ - if (lstat (file, &st) == 0 - && st.st_dev == p->dev - && st.st_ino == p->ino - && st.st_mtime == p->mtime) + chdir_do (ds->change_dir); + + for (sources = ds->sources; sources; sources = sources->next) { - if (unlink (file) != 0) - unlink_error (file); - else + char const *source = sources->string; + struct stat st; + + /* Make sure the placeholder file is still there. If not, + don't create a link, as the placeholder was probably + removed by a later extraction. */ + if (fstatat (chdir_fd, source, &st, AT_SYMLINK_NOFOLLOW) == 0 + && st.st_dev == ds->dev + && st.st_ino == ds->ino + && timespec_cmp (get_stat_ctime (&st), ds->ctime) == 0) { - char const *contents = file + strlen (file) + 1; - if (symlink (contents, file) != 0) - symlink_error (contents, file); + /* Unlink the placeholder, then create a hard link if possible, + a symbolic link otherwise. */ + if (unlinkat (chdir_fd, source, 0) != 0) + unlink_error (source); + else if (valid_source + && (linkat (chdir_fd, valid_source, chdir_fd, source, 0) + == 0)) + ; + else if (!ds->is_symlink) + { + if (linkat (chdir_fd, ds->target, chdir_fd, source, 0) != 0) + link_error (ds->target, source); + } + else if (symlinkat (ds->target, chdir_fd, source) != 0) + symlink_error (ds->target, source); else { - st.st_uid = p->uid; - st.st_gid = p->gid; - set_stat (file, &st, 0, 0, SYMTYPE); + struct tar_stat_info st1; + st1.stat.st_mode = ds->mode; + st1.stat.st_uid = ds->uid; + st1.stat.st_gid = ds->gid; + st1.atime = ds->atime; + st1.mtime = ds->mtime; + set_stat (source, &st1, -1, 0, 0, SYMTYPE, + false, AT_SYMLINK_NOFOLLOW); + valid_source = source; } } } - next = p->next; - free (p); + for (sources = ds->sources; sources; ) + { + struct string_list *next = sources->next; + free (sources); + sources = next; + } + + { + struct delayed_link *next = ds->next; + free (ds); + ds = next; + } } - delayed_symlink_head = 0; + delayed_link_head = 0; } /* Finish the extraction of an archive. */ void extract_finish (void) { - /* Apply delayed symlinks last, so that they don't affect - delayed directory status-setting. */ - apply_nonancestor_delayed_set_stat (""); - apply_delayed_symlinks (); + /* First, fix the status of ordinary directories that need fixing. */ + apply_nonancestor_delayed_set_stat ("", 0); + + /* Then, apply delayed links, so that they don't affect delayed + directory status-setting for ordinary directories. */ + apply_delayed_links (); + + /* Finally, fix the status of directories that are ancestors + of delayed links. */ + apply_nonancestor_delayed_set_stat ("", 1); } -void -fatal_exit (void) +bool +rename_directory (char *src, char *dst) { - extract_finish (); - error (TAREXIT_FAILURE, 0, _("Error is not recoverable: exiting now")); - abort (); + if (renameat (chdir_fd, src, chdir_fd, dst) != 0) + { + int e = errno; + bool interdir_made; + + switch (e) + { + case ENOENT: + if (make_directories (dst, &interdir_made) == 0) + { + if (renameat (chdir_fd, src, chdir_fd, dst) == 0) + return true; + e = errno; + } + break; + + case EXDEV: + /* FIXME: Fall back to recursive copying */ + + default: + break; + } + + ERROR ((0, e, _("Cannot rename %s to %s"), + quote_n (0, src), + quote_n (1, dst))); + return false; + } + return true; }