X-Git-Url: https://git.dogcows.com/gitweb?a=blobdiff_plain;f=src%2Fextract.c;h=2296066591624d6fb7bd8b8089632402cef3b225;hb=ecbcb7b6d74c2d69386c8d7e435486a4690c9993;hp=2247de9b206106b24d610ca3281b19382d34137f;hpb=2872a5ae81252fde90795b914b6088485abd7ab0;p=chaz%2Ftar diff --git a/src/extract.c b/src/extract.c index 2247de9..2296066 100644 --- a/src/extract.c +++ b/src/extract.c @@ -1,13 +1,13 @@ /* Extract files from a tar archive. Copyright (C) 1988, 1992, 1993, 1994, 1996, 1997, 1998, 1999, 2000, - 2001, 2003, 2004 Free Software Foundation, Inc. + 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 @@ -17,26 +17,16 @@ 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 #include #include -#include - -#if HAVE_UTIME_H -# include -#else -struct utimbuf - { - long actime; - long modtime; - }; -#endif +#include #include "common.h" -bool 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) */ @@ -46,7 +36,8 @@ enum permstatus /* This file may have existed already; its permissions are unknown. */ UNKNOWN_PERMSTATUS, - /* This file was created using the permissions from the archive. */ + /* This file was created using the permissions from the archive, + except with S_IRWXG | S_IRWXO masked out if 0 < same_owner_option. */ ARCHIVED_PERMSTATUS, /* This is an intermediate directory; the archive did not specify @@ -57,8 +48,8 @@ enum permstatus /* List of directories whose statuses we need to extract after we've 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_SYMLINKS is nonzero and [^D] - represents an element where AFTER_SYMLINKS is zero, then the head + 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. */ @@ -66,32 +57,47 @@ enum permstatus struct delayed_set_stat { struct delayed_set_stat *next; - struct stat stat_info; + dev_t dev; + ino_t ino; + mode_t mode; + uid_t uid; + gid_t gid; + struct timespec atime; + struct timespec mtime; size_t file_name_len; mode_t invert_permissions; enum permstatus permstatus; - bool after_symlinks; + bool after_links; + int change_dir; 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. */ + /* 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; + + /* True if the link is symbolic. */ + bool is_symlink; - /* The desired owner and group of the symbolic link. */ + /* The desired owner and group of the link, if it is a symlink. */ uid_t uid; gid_t gid; - /* A list of sources for this symlink. The sources are all to be + /* The directory that the sources and target are relative to. */ + int change_dir; + + /* A list of sources for this link. The sources are all to be hard-linked together. */ struct string_list *sources; @@ -99,7 +105,7 @@ struct delayed_symlink char target[1]; }; -static struct delayed_symlink *delayed_symlink_head; +static struct delayed_link *delayed_link_head; struct string_list { @@ -115,19 +121,6 @@ extr_init (void) same_permissions_option += we_are_root; same_owner_option += we_are_root; - /* Save 'root device' to avoid purging mount points. - FIXME: Should the same be done after handling -C option ? */ - if (one_file_system_option) - { - struct stat st; - char *dir = xgetcwd (); - - if (deref_stat (true, dir, &st)) - stat_diag (dir); - else - root_device = st.st_dev; - } - /* Option -p clears the kernel umask, so it does not affect proper restoration of file permissions. New intermediate directories will comply with umask at start of program. */ @@ -142,8 +135,40 @@ extr_init (void) } } +/* Use fchmod if possible, chmod otherwise. */ +static int +fdchmod (char const *file, int fd, mode_t mode) +{ +#if HAVE_FCHMOD + if (0 <= fd) + return fchmod (fd, mode); +#endif + return chmod (file, mode); +} + +/* Use fchown if possible, chown otherwise. */ +static int +fdchown (char const *file, int fd, uid_t uid, gid_t gid) +{ +#if HAVE_FCHOWN + if (0 <= fd) + return fchown (fd, uid, gid); +#endif + return chown (file, uid, gid); +} + +/* Use fstat if possible, stat otherwise. */ +static int +fdstat (char const *file, int fd, struct stat *st) +{ + if (0 <= fd) + return fstat (fd, st); + return stat (file, st); +} + /* If restoring permissions, restore the mode for FILE_NAME from - information given in *STAT_INFO (where *CUR_INFO gives + information given in *STAT_INFO (where FD is a file descriptor + for the file if FD is nonnegative, and where *CUR_INFO gives the current status if CUR_INFO is nonzero); otherwise invert the INVERT_PERMISSIONS bits from the file's current permissions. PERMSTATUS specifies the status of the file's permissions. @@ -151,23 +176,27 @@ extr_init (void) static void set_mode (char const *file_name, struct stat const *stat_info, - struct stat const *cur_info, + int fd, struct stat const *cur_info, mode_t invert_permissions, enum permstatus permstatus, char typeflag) { mode_t mode; + int chmod_errno; if (0 < same_permissions_option && permstatus != INTERDIR_PERMSTATUS) { 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 + /* If we created the file and it has a mode that we set already + with O_CREAT, then its mode is often set correctly already. + But if we are changing ownership, the mode's group and and + other permission bits were omitted originally, so it's less + likely that the mode is OK now. Also, 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) + if ((permstatus == ARCHIVED_PERMSTATUS + && ! (mode & ~ (0 < same_owner_option ? S_IRWXU : MODE_RWX))) && typeflag != DIRTYPE && typeflag != GNUTYPE_DUMPDIR) return; @@ -185,7 +214,7 @@ set_mode (char const *file_name, struct stat st; if (! cur_info) { - if (stat (file_name, &st) != 0) + if (fdstat (file_name, fd, &st) != 0) { stat_error (file_name); return; @@ -195,27 +224,61 @@ set_mode (char const *file_name, mode = cur_info->st_mode ^ invert_permissions; } - if (chmod (file_name, mode) != 0) - chmod_error_details (file_name, mode); + chmod_errno = fdchmod (file_name, fd, mode) == 0 ? 0 : errno; + if (chmod_errno == EPERM && (mode & S_ISUID) != 0) + { + /* 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 (priv_set_restore_linkdir () == 0) + { + chmod_errno = fdchmod (file_name, fd, mode) == 0 ? 0 : errno; + priv_set_remove_linkdir (); + } + } + if (chmod_errno) + { + errno = chmod_errno; + 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 (t <= 0) - WARN ((0, 0, _("%s: implausibly old time stamp %s"), - file_name, tartime (t))); - else 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. + FILE_NAME, using information given in *ST. + If FD is nonnegative, it is a file descriptor for the file. If CUR_INFO is nonzero, *CUR_INFO is the - file's currernt status. + file's current status. If not restoring permissions, invert the INVERT_PERMISSIONS bits from the file's current permissions. PERMSTATUS specifies the status of the file's permissions. @@ -228,13 +291,11 @@ check_time (char const *file_name, time_t t) static void set_stat (char const *file_name, - struct stat const *stat_info, - struct stat const *cur_info, + struct tar_stat_info const *st, + int fd, struct stat const *cur_info, mode_t invert_permissions, enum permstatus permstatus, char typeflag) { - struct utimbuf utimbuf; - if (typeflag != SYMTYPE) { /* We do the utime before the chmod because some versions of utime are @@ -242,34 +303,29 @@ set_stat (char const *file_name, if (! touch_option && permstatus != INTERDIR_PERMSTATUS) { - /* 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? */ - + struct timespec ts[2]; if (incremental_option) - utimbuf.actime = stat_info->st_atime; + ts[0] = st->atime; else - utimbuf.actime = start_time; - - utimbuf.modtime = stat_info->st_mtime; + ts[0].tv_nsec = UTIME_NOW; + ts[1] = st->mtime; - if (utime (file_name, &utimbuf) < 0) + if (fd_utimensat (fd, AT_FDCWD, file_name, ts, 0) != 0) utime_error (file_name); else { - check_time (file_name, utimbuf.actime); - check_time (file_name, utimbuf.modtime); + if (incremental_option) + check_time (file_name, ts[0]); + check_time (file_name, ts[1]); } } /* 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, cur_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. */ } if (0 < same_owner_option && permstatus != INTERDIR_PERMSTATUS) @@ -278,52 +334,79 @@ set_stat (char const *file_name, 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. */ + int chown_result = 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); + chown_result = lchown (file_name, st->stat.st_uid, st->stat.st_gid); #endif } 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, 0, - invert_permissions, permstatus, typeflag); + chown_result = fdchown (file_name, fd, + st->stat.st_uid, st->stat.st_gid); + } + + if (chown_result == 0) + { + /* Changing the owner can flip st_mode bits in some cases, so + ignore cur_info if it might be obsolete now. */ + if (cur_info + && cur_info->st_mode & S_IXUGO + && cur_info->st_mode & (S_ISUID | S_ISGID)) + cur_info = NULL; } + else if (chown_result < 0) + chown_error_details (file_name, + st->stat.st_uid, st->stat.st_gid); } + + if (typeflag != SYMTYPE) + set_mode (file_name, &st->stat, fd, cur_info, + invert_permissions, permstatus, typeflag); } /* 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. */ + PERMSTATUS specifies the status of the file's permissions. + + 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, +delay_set_stat (char const *file_name, struct tar_stat_info const *st, mode_t invert_permissions, enum permstatus permstatus) { size_t file_name_len = strlen (file_name); struct delayed_set_stat *data = xmalloc (offsetof (struct delayed_set_stat, file_name) + file_name_len + 1); + data->next = delayed_set_stat_head; + data->dev = st->stat.st_dev; + data->ino = st->stat.st_ino; + data->mode = st->stat.st_mode; + 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; - strcpy (data->file_name, file_name); data->invert_permissions = invert_permissions; data->permstatus = permstatus; - data->after_symlinks = 0; - data->stat_info = *stat_info; - data->next = delayed_set_stat_head; + data->after_links = 0; + data->change_dir = chdir_current; + strcpy (data->file_name, file_name); delayed_set_stat_head = data; } @@ -348,9 +431,16 @@ repair_delayed_set_stat (char const *dir, if (st.st_dev == dir_stat_info->st_dev && st.st_ino == dir_stat_info->st_ino) { - data->stat_info = current_stat_info.stat; + 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->invert_permissions = - (MODE_RWX & (current_stat_info.stat.st_mode ^ st.st_mode)); + ((current_stat_info.stat.st_mode ^ st.st_mode) + & MODE_RWX & ~ current_umask); data->permstatus = ARCHIVED_PERMSTATUS; return; } @@ -360,7 +450,7 @@ repair_delayed_set_stat (char const *dir, 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. */ @@ -368,13 +458,12 @@ static int make_directories (char *file_name) { char *cursor0 = file_name + FILE_SYSTEM_PREFIX_LEN (file_name); - char *cursor; /* points into the file name */ + char *cursor; /* points into the file name */ int did_something = 0; /* did we do anything yet? */ int mode; int invert_permissions; int status; - for (cursor = cursor0; *cursor; cursor++) { if (! ISSLASH (*cursor)) @@ -404,7 +493,7 @@ make_directories (char *file_name) invert_permissions is zero, because repair_delayed_set_stat may need to update the struct. */ delay_set_stat (file_name, - ¤t_stat_info.stat /* ignored */, + ¤t_stat_info, invert_permissions, INTERDIR_PERMSTATUS); print_for_mkdir (file_name, cursor - file_name, mode); @@ -439,28 +528,40 @@ file_newer_p (const char *file_name, struct tar_stat_info *tar_stat) if (stat (file_name, &st)) { - stat_warn (file_name); - return true; /* Be on the safe side */ + if (errno != ENOENT) + { + stat_warn (file_name); + /* Be on the safe side: if the file does exist assume it is newer */ + return true; + } + return false; } if (!S_ISDIR (st.st_mode) - && st.st_mtime >= tar_stat->stat.st_mtime) + && tar_timespec_cmp (tar_stat->mtime, get_stat_mtime (&st)) <= 0) { return true; } return false; } +#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. */ + static int -maybe_recoverable (char *file_name, int *interdir_made) +maybe_recoverable (char *file_name, bool *interdir_made) { int e = errno; if (*interdir_made) - return 0; + return RECOVER_NO; switch (errno) { @@ -470,13 +571,13 @@ maybe_recoverable (char *file_name, int *interdir_made) switch (old_files_option) { case KEEP_OLD_FILES: - return 0; + return RECOVER_SKIP; case KEEP_NEWER_FILES: if (file_newer_p (file_name, ¤t_stat_info)) { errno = e; - return 0; + return RECOVER_NO; } /* FALL THROUGH */ @@ -486,7 +587,7 @@ maybe_recoverable (char *file_name, int *interdir_made) { int r = remove_any_file (file_name, ORDINARY_REMOVE_OPTION); errno = EEXIST; - return r; + return r > 0 ? RECOVER_OK : RECOVER_NO; } case UNLINK_FIRST_OLD_FILES: @@ -498,25 +599,25 @@ maybe_recoverable (char *file_name, int *interdir_made) if (! make_directories (file_name)) { errno = ENOENT; - return 0; + return RECOVER_NO; } - *interdir_made = 1; - return 1; + *interdir_made = true; + return RECOVER_OK; default: /* Just say we can't do anything about it... */ - return 0; + return RECOVER_NO; } } /* Fix the statuses of all directories whose statuses need fixing, and - which are not ancestors of FILE_NAME. If AFTER_SYMLINKS is + 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 - symlinks are applied. */ + links are applied. */ static void -apply_nonancestor_delayed_set_stat (char const *file_name, bool after_symlinks) +apply_nonancestor_delayed_set_stat (char const *file_name, bool after_links) { size_t file_name_len = strlen (file_name); bool check_for_renamed_directories = 0; @@ -528,9 +629,9 @@ apply_nonancestor_delayed_set_stat (char const *file_name, bool after_symlinks) struct stat st; struct stat const *cur_info = 0; - check_for_renamed_directories |= data->after_symlinks; + check_for_renamed_directories |= data->after_links; - if (after_symlinks < data->after_symlinks + 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]) @@ -538,6 +639,8 @@ apply_nonancestor_delayed_set_stat (char const *file_name, bool after_symlinks) && memcmp (file_name, data->file_name, data->file_name_len) == 0)) break; + chdir_do (data->change_dir); + if (check_for_renamed_directories) { cur_info = &st; @@ -546,8 +649,7 @@ apply_nonancestor_delayed_set_stat (char const *file_name, bool after_symlinks) stat_error (data->file_name); skip_this_one = 1; } - else if (! (st.st_dev == data->stat_info.st_dev - && (st.st_ino == data->stat_info.st_ino))) + else if (! (st.st_dev == data->dev && st.st_ino == data->ino)) { ERROR ((0, 0, _("%s: Directory renamed before its status could be extracted"), @@ -557,8 +659,16 @@ apply_nonancestor_delayed_set_stat (char const *file_name, bool after_symlinks) } if (! skip_this_one) - set_stat (data->file_name, &data->stat_info, cur_info, - data->invert_permissions, data->permstatus, DIRTYPE); + { + 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, cur_info, + data->invert_permissions, data->permstatus, DIRTYPE); + } delayed_set_stat_head = data->next; free (data); @@ -574,15 +684,28 @@ extract_dir (char *file_name, int typeflag) { int status; mode_t mode; - int interdir_made = 0; - + bool interdir_made = false; + + /* Save 'root device' to avoid purging mount points. */ + if (one_file_system_option && root_device == 0) + { + struct stat st; + + if (stat (".", &st) != 0) + stat_diag ("."); + else + root_device = st.st_dev; + } + 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 (); - mode = (current_stat_info.stat.st_mode | (we_are_root ? 0 : MODE_WXUSR)) & MODE_RWX; + mode = current_stat_info.stat.st_mode | (we_are_root ? 0 : MODE_WXUSR); + if (0 < same_owner_option || current_stat_info.stat.st_mode & ~ MODE_RWX) + mode &= S_IRWXU; while ((status = mkdir (file_name, mode))) { @@ -601,46 +724,59 @@ extract_dir (char *file_name, int typeflag) } if (S_ISDIR (st.st_mode)) { - mode = st.st_mode & ~ current_umask; + status = 0; + mode = st.st_mode; break; } } errno = EEXIST; } - - if (maybe_recoverable (file_name, &interdir_made)) - continue; - - if (errno != EEXIST) + + switch (maybe_recoverable (file_name, &interdir_made)) { - mkdir_error (file_name); - return 1; + case RECOVER_OK: + continue; + + case RECOVER_SKIP: + break; + + case RECOVER_NO: + if (errno != EEXIST) + { + mkdir_error (file_name); + return 1; + } + break; } break; } - + if (status == 0 || old_files_option == DEFAULT_OLD_FILES || old_files_option == OVERWRITE_OLD_FILES) - delay_set_stat (file_name, ¤t_stat_info.stat, - MODE_RWX & (mode ^ current_stat_info.stat.st_mode), - (status == 0 - ? ARCHIVED_PERMSTATUS - : UNKNOWN_PERMSTATUS)); - + { + if (status == 0) + delay_set_stat (file_name, ¤t_stat_info, + ((mode ^ current_stat_info.stat.st_mode) + & MODE_RWX & ~ current_umask), + ARCHIVED_PERMSTATUS); + else /* For an already existing directory, invert_perms must be 0 */ + delay_set_stat (file_name, ¤t_stat_info, + 0, + UNKNOWN_PERMSTATUS); + } return status; } static int -open_output_file (char *file_name, int typeflag) +open_output_file (char *file_name, int typeflag, mode_t mode) { int fd; int openflag = (O_WRONLY | O_BINARY | O_CREAT | (old_files_option == OVERWRITE_OLD_FILES ? O_TRUNC : O_EXCL)); - mode_t mode = current_stat_info.stat.st_mode & MODE_RWX & ~ current_umask; #if O_CTG /* Contiguous files (on the Masscomp) have to specify the size in @@ -659,13 +795,14 @@ open_output_file (char *file_name, int typeflag) if (!conttype_diagnosed) { conttype_diagnosed = 1; - WARN ((0, 0, _("Extracting contiguous files as regular files"))); + WARNOPT (WARN_CONTIGUOUS_CAST, + (0, 0, _("Extracting contiguous files as regular files"))); } } fd = open (file_name, openflag, mode); #endif /* not O_CTG */ - + return fd; } @@ -678,8 +815,11 @@ extract_file (char *file_name, int typeflag) int status; size_t count; size_t written; - int interdir_made = 0; - + bool interdir_made = false; + mode_t mode = current_stat_info.stat.st_mode & MODE_RWX & ~ current_umask; + mode_t invert_permissions = + 0 < same_owner_option ? mode & (S_IRWXG | S_IRWXO) : 0; + /* FIXME: deal with protection issues. */ if (to_stdout_option) @@ -695,33 +835,35 @@ extract_file (char *file_name, int typeflag) } else { + int recover = RECOVER_NO; do - fd = open_output_file (file_name, typeflag); - while (fd < 0 && maybe_recoverable (file_name, &interdir_made)); - + fd = open_output_file (file_name, typeflag, mode ^ invert_permissions); + while (fd < 0 + && (recover = maybe_recoverable (file_name, &interdir_made)) + == RECOVER_OK); + if (fd < 0) { + skip_member (); + if (recover == RECOVER_SKIP) + return 0; open_error (file_name); return 1; } } - + + 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; ) { - if (multi_volume_option) - { - assign_string (&save_name, current_stat_info.orig_file_name); - save_totsize = current_stat_info.stat.st_size; - save_sizeleft = size; - } + mv_size_left (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) { @@ -730,13 +872,13 @@ extract_file (char *file_name, int typeflag) } written = available_space_after (data_block); - + if (written > size) written = size; errno = 0; count = full_write (fd, data_block->buffer, written); size -= written; - + set_next_block_after ((union block *) (data_block->buffer + written - 1)); if (count != written) @@ -747,11 +889,10 @@ extract_file (char *file_name, int typeflag) break; } } - + skip_file (size); - if (multi_volume_option) - assign_string (&save_name, 0); + mv_end (); /* 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. */ @@ -759,27 +900,121 @@ extract_file (char *file_name, int typeflag) if (to_stdout_option) return 0; + if (! to_command_option) + set_stat (file_name, ¤t_stat_info, fd, NULL, invert_permissions, + (old_files_option == OVERWRITE_OLD_FILES + ? UNKNOWN_PERMSTATUS : ARCHIVED_PERMSTATUS), + typeflag); + status = close (fd); if (status < 0) close_error (file_name); if (to_command_option) sys_wait_command (); - else - set_stat (file_name, ¤t_stat_info.stat, 0, 0, - (old_files_option == OVERWRITE_OLD_FILES ? - UNKNOWN_PERMSTATUS : ARCHIVED_PERMSTATUS), - typeflag); 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 = open (file_name, O_WRONLY | O_CREAT | O_EXCL, 0)) < 0) + { + switch (maybe_recoverable (file_name, interdir_made)) + { + case RECOVER_OK: + continue; + + case RECOVER_SKIP: + return 0; + + case RECOVER_NO: + open_error (file_name); + return -1; + } + } + + 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) + { + p->uid = current_stat_info.stat.st_uid; + p->gid = current_stat_info.stat.st_gid; + } + 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)) + { + do + { + h->after_links = 1; + + if (stat (h->file_name, &st) != 0) + stat_error (h->file_name); + else + { + h->dev = st.st_dev; + h->ino = st.st_ino; + } + } + while ((h = h->next) && ! h->after_links); + } + + return 0; + } + + return -1; +} static int extract_link (char *file_name, int typeflag) { - char const *link_name = safer_name_suffix (current_stat_info.link_name, true); - int interdir_made = 0; - + bool interdir_made = false; + char const *link_name; + int rc; + + link_name = current_stat_info.link_name; + + if (! absolute_names_option && contains_dot_dot (link_name)) + return create_placeholder_file (file_name, false, &interdir_made); + do { struct stat st1, st2; @@ -789,12 +1024,13 @@ extract_link (char *file_name, int typeflag) if (status == 0) { - struct delayed_symlink *ds = delayed_symlink_head; + struct delayed_link *ds = delayed_link_head; if (ds && lstat (link_name, &st1) == 0) for (; ds; ds = ds->next) - if (ds->dev == st1.st_dev + if (ds->change_dir == chdir_current + && ds->dev == st1.st_dev && ds->ino == st1.st_ino - && ds->mtime == st1.st_mtime) + && timespec_cmp (ds->ctime, get_stat_ctime (&st1)) == 0) { struct string_list *p = xmalloc (offsetof (struct string_list, string) + strlen (file_name) + 1); @@ -811,105 +1047,48 @@ extract_link (char *file_name, int typeflag) && st1.st_dev == st2.st_dev && st1.st_ino == st2.st_ino)) return 0; - + errno = e; } - while (maybe_recoverable (file_name, &interdir_made)); + while ((rc = maybe_recoverable (file_name, &interdir_made)) == RECOVER_OK); + if (rc == RECOVER_SKIP) + return 0; if (!(incremental_option && errno == EEXIST)) { link_error (link_name, file_name); return 1; } return 0; -} +} static int extract_symlink (char *file_name, int typeflag) { #ifdef HAVE_SYMLINK - int status, fd; - int interdir_made = 0; - - if (absolute_names_option - || ! (IS_ABSOLUTE_FILE_NAME (current_stat_info.link_name) - || contains_dot_dot (current_stat_info.link_name))) - { - while ((status = symlink (current_stat_info.link_name, file_name))) - if (!maybe_recoverable (file_name, &interdir_made)) - break; + bool interdir_made = false; - if (status == 0) - set_stat (file_name, ¤t_stat_info.stat, 0, 0, 0, SYMTYPE); - else - symlink_error (current_stat_info.link_name, 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; + 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); - while ((fd = open (file_name, O_WRONLY | O_CREAT | O_EXCL, 0)) < 0) - if (! maybe_recoverable (file_name, &interdir_made)) - break; + while (symlink (current_stat_info.link_name, file_name)) + switch (maybe_recoverable (file_name, &interdir_made)) + { + case RECOVER_OK: + continue; - status = -1; - if (fd < 0) - open_error (file_name); - else 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_symlink *p = xmalloc (offsetof (struct delayed_symlink, target) - + strlen (current_stat_info.link_name) - + 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_info.stat.st_uid; - p->gid = current_stat_info.stat.st_gid; - 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_symlinks - && strncmp (file_name, h->file_name, h->file_name_len) == 0 - && ISSLASH (file_name[h->file_name_len]) - && (base_name (file_name) == file_name + h->file_name_len + 1)) - { - do - { - h->after_symlinks = 1; - - if (stat (h->file_name, &st) != 0) - stat_error (h->file_name); - else - { - h->stat_info.st_dev = st.st_dev; - h->stat_info.st_ino = st.st_ino; - } - } - while ((h = h->next) && ! h->after_symlinks); - } - - status = 0; - } - } + case RECOVER_SKIP: + return 0; - return status; + case RECOVER_NO: + symlink_error (current_stat_info.link_name, file_name); + return -1; + } + + set_stat (file_name, ¤t_stat_info, -1, NULL, 0, 0, SYMTYPE); + return 0; #else static int warned_once; @@ -917,29 +1096,41 @@ extract_symlink (char *file_name, int typeflag) if (!warned_once) { warned_once = 1; - WARN ((0, 0, _("Attempting extraction of symbolic links as hard links"))); + WARNOPT (WARN_SYMBOLIC_CAST, + (0, 0, + _("Attempting extraction of symbolic links as hard links"))); } - return extract_link (file_name, typeflag); + return extract_link (file_name, typeflag); #endif -} +} #if S_IFCHR || S_IFBLK static int extract_node (char *file_name, int typeflag) { - int status; - int interdir_made = 0; - - do - status = mknod (file_name, current_stat_info.stat.st_mode, - current_stat_info.stat.st_rdev); - while (status && maybe_recoverable (file_name, &interdir_made)); - - if (status != 0) - mknod_error (file_name); - else - set_stat (file_name, ¤t_stat_info.stat, 0, 0, ARCHIVED_PERMSTATUS, typeflag); - return status; + bool interdir_made = false; + mode_t mode = current_stat_info.stat.st_mode & ~ current_umask; + mode_t invert_permissions = + 0 < same_owner_option ? mode & (S_IRWXG | S_IRWXO) : 0; + + while (mknod (file_name, mode ^ invert_permissions, + current_stat_info.stat.st_rdev)) + switch (maybe_recoverable (file_name, &interdir_made)) + { + case RECOVER_OK: + continue; + + case RECOVER_SKIP: + return 0; + + case RECOVER_NO: + mknod_error (file_name); + return -1; + } + + set_stat (file_name, ¤t_stat_info, -1, NULL, invert_permissions, + ARCHIVED_PERMSTATUS, typeflag); + return 0; } #endif @@ -948,29 +1139,38 @@ static int extract_fifo (char *file_name, int typeflag) { int status; - int interdir_made = 0; - - while ((status = mkfifo (file_name, current_stat_info.stat.st_mode))) - if (!maybe_recoverable (file_name, &interdir_made)) - break; + bool interdir_made = false; + mode_t mode = current_stat_info.stat.st_mode & ~ current_umask; + mode_t invert_permissions = + 0 < same_owner_option ? mode & (S_IRWXG | S_IRWXO) : 0; - if (status == 0) - set_stat (file_name, ¤t_stat_info.stat, NULL, 0, - ARCHIVED_PERMSTATUS, typeflag); - else - mkfifo_error (file_name); - return status; -} + while ((status = mkfifo (file_name, mode)) != 0) + switch (maybe_recoverable (file_name, &interdir_made)) + { + case RECOVER_OK: + continue; + + case RECOVER_SKIP: + return 0; + + case RECOVER_NO: + mkfifo_error (file_name); + return -1; + } + + set_stat (file_name, ¤t_stat_info, -1, NULL, invert_permissions, + ARCHIVED_PERMSTATUS, typeflag); + return 0; +} #endif static int -extract_mangle_wrapper (char *file_name, int typeflag) +extract_volhdr (char *file_name, int typeflag) { - extract_mangle (); + skip_member (); return 0; } - static int extract_failure (char *file_name, int typeflag) { @@ -988,7 +1188,7 @@ static int prepare_to_extract (char const *file_name, int typeflag, tar_extractor_t *fun) { int rc = 1; - + if (EXTRACT_OVER_PIPE) rc = 0; @@ -999,7 +1199,7 @@ prepare_to_extract (char const *file_name, int typeflag, tar_extractor_t *fun) *fun = extract_file; rc = 1; break; - + case AREGTYPE: case REGTYPE: case CONTTYPE: @@ -1045,16 +1245,12 @@ prepare_to_extract (char const *file_name, int typeflag, tar_extractor_t *fun) case DIRTYPE: case GNUTYPE_DUMPDIR: *fun = extract_dir; + if (current_stat_info.is_dumpdir) + delay_directory_restore_option = true; break; case GNUTYPE_VOLHDR: - if (verbose_option) - fprintf (stdlis, _("Reading %s\n"), quote (current_stat_info.file_name)); - *fun = NULL; - break; - - case GNUTYPE_NAMES: - *fun = extract_mangle_wrapper; + *fun = extract_volhdr; break; case GNUTYPE_MULTIVOL: @@ -1071,21 +1267,22 @@ prepare_to_extract (char const *file_name, int typeflag, tar_extractor_t *fun) break; default: - WARN ((0, 0, - _("%s: Unknown file type `%c', extracted as normal file"), - quotearg_colon (file_name), typeflag)); + WARNOPT (WARN_UNKNOWN_CAST, + (0, 0, + _("%s: Unknown file type `%c', extracted as normal file"), + quotearg_colon (file_name), typeflag)); *fun = extract_file; } /* Determine whether the extraction should proceed */ if (rc == 0) return 0; - + switch (old_files_option) { case UNLINK_FIRST_OLD_FILES: - if (!remove_any_file (file_name, - recursive_unlink_option ? RECURSIVE_REMOVE_OPTION + if (!remove_any_file (file_name, + recursive_unlink_option ? RECURSIVE_REMOVE_OPTION : ORDINARY_REMOVE_OPTION) && errno && errno != ENOENT) { @@ -1097,7 +1294,9 @@ prepare_to_extract (char const *file_name, int typeflag, tar_extractor_t *fun) case KEEP_NEWER_FILES: if (file_newer_p (file_name, ¤t_stat_info)) { - WARN ((0, 0, _("Current %s is newer"), quote (file_name))); + WARNOPT (WARN_IGNORE_NEWER, + (0, 0, _("Current %s is newer or same age"), + quote (file_name))); return 0; } break; @@ -1114,88 +1313,87 @@ void extract_archive (void) { char typeflag; - char *file_name; tar_extractor_t fun; - + + fatal_exit_hook = extract_finish; + set_next_block_after (current_header); - decode_header (current_header, ¤t_stat_info, ¤t_format, 1); - if (interactive_option && !confirm ("extract", current_stat_info.file_name)) + if (!current_stat_info.file_name[0] + || (interactive_option + && !confirm ("extract", current_stat_info.file_name))) { skip_member (); return; } /* Print the block from current_header and current_stat. */ - if (verbose_option) - print_header (¤t_stat_info, -1); + print_header (¤t_stat_info, current_header, -1); - file_name = safer_name_suffix (current_stat_info.file_name, false); - if (strip_name_components) + /* 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) { - size_t prefix_len = stripped_prefix_len (file_name, strip_name_components); - if (prefix_len == (size_t) -1) - { - skip_member (); - return; - } - file_name += prefix_len; + int dir = chdir_current; + apply_nonancestor_delayed_set_stat (current_stat_info.file_name, 0); + chdir_do (dir); } - apply_nonancestor_delayed_set_stat (file_name, 0); - /* Take a safety backup of a previously existing file. */ if (backup_option) - if (!maybe_backup_file (file_name, 0)) + 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 (file_name))); + 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 (file_name, typeflag, &fun)) + if (prepare_to_extract (current_stat_info.file_name, typeflag, &fun)) { - if (fun && (*fun) (file_name, typeflag) && backup_option) + 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 *ds; + struct delayed_link *ds; - for (ds = delayed_symlink_head; ds; ) + for (ds = delayed_link_head; ds; ) { struct string_list *sources = ds->sources; char const *valid_source = 0; + chdir_do (ds->change_dir); + for (sources = ds->sources; sources; sources = sources->next) { char const *source = sources->string; struct stat st; /* Make sure the placeholder file is still there. If not, - don't create a symlink, as the placeholder was probably + don't create a link, as the placeholder was probably removed by a later extraction. */ if (lstat (source, &st) == 0 && st.st_dev == ds->dev && st.st_ino == ds->ino - && st.st_mtime == ds->mtime) + && timespec_cmp (get_stat_ctime (&st), ds->ctime) == 0) { /* Unlink the placeholder, then create a hard link if possible, a symbolic link otherwise. */ @@ -1203,14 +1401,20 @@ apply_delayed_symlinks (void) unlink_error (source); else if (valid_source && link (valid_source, source) == 0) ; + else if (!ds->is_symlink) + { + if (link (ds->target, source) != 0) + link_error (ds->target, source); + } else if (symlink (ds->target, source) != 0) symlink_error (ds->target, source); else { + struct tar_stat_info st1; + st1.stat.st_uid = ds->uid; + st1.stat.st_gid = ds->gid; + set_stat (source, &st1, -1, NULL, 0, 0, SYMTYPE); valid_source = source; - st.st_uid = ds->uid; - st.st_gid = ds->gid; - set_stat (source, &st, 0, 0, 0, SYMTYPE); } } } @@ -1223,13 +1427,13 @@ apply_delayed_symlinks (void) } { - struct delayed_symlink *next = ds->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. */ @@ -1239,26 +1443,44 @@ extract_finish (void) /* First, fix the status of ordinary directories that need fixing. */ apply_nonancestor_delayed_set_stat ("", 0); - /* Then, apply delayed symlinks, so that they don't affect delayed + /* Then, apply delayed links, so that they don't affect delayed directory status-setting for ordinary directories. */ - apply_delayed_symlinks (); + apply_delayed_links (); /* Finally, fix the status of directories that are ancestors - of delayed symlinks. */ + 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 (rename (src, dst)) + { + int e = errno; -void -xalloc_die (void) -{ - error (0, 0, "%s", _("memory exhausted")); - fatal_exit (); + switch (e) + { + case ENOENT: + if (make_directories (dst)) + { + if (rename (src, 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; }