X-Git-Url: https://git.dogcows.com/gitweb?a=blobdiff_plain;f=src%2Fextract.c;h=40d55282ff4275d4c44586bcad5ec8fce2864b49;hb=b216fed6340d073d33351145be033ecc5b903c7a;hp=d391e3e3372c0340b58e398203f2b5e6197ce5fe;hpb=49ea4c50575f66e8f0450e463e1d882eed6255ec;p=chaz%2Ftar diff --git a/src/extract.c b/src/extract.c index d391e3e..40d5528 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, 2005, 2006 Free Software Foundation, Inc. + 2001, 2003, 2004, 2005, 2006, 2007 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 @@ -24,6 +24,7 @@ #include #include #include +#include #include "common.h" @@ -37,7 +38,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 @@ -143,18 +145,22 @@ set_mode (char const *file_name, char typeflag) { mode_t mode; - + bool failed; + 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; @@ -182,7 +188,17 @@ set_mode (char const *file_name, mode = cur_info->st_mode ^ invert_permissions; } - if (chmod (file_name, mode) != 0) + failed = chmod (file_name, mode) != 0; + if (failed && errno == EPERM) + { + /* On Solaris, chmod may fail if we don't have PRIV_ALL. */ + if (priv_set_restore_linkdir () == 0) + { + failed = chmod (file_name, mode) != 0; + priv_set_remove_linkdir (); + } + } + if (failed) chmod_error_details (file_name, mode); } @@ -217,7 +233,7 @@ check_time (char const *file_name, struct timespec t) /* Restore stat attributes (owner, group, mode and times) for FILE_NAME, using information given in *ST. 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. @@ -265,11 +281,11 @@ set_stat (char const *file_name, } /* 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, &st->stat, 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,29 +294,36 @@ 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, st->stat.st_uid, st->stat.st_gid) < 0) - chown_error_details (file_name, - st->stat.st_uid, st->stat.st_gid); + chown_result = lchown (file_name, st->stat.st_uid, st->stat.st_gid); #endif } else { - if (chown (file_name, st->stat.st_uid, st->stat.st_gid) < 0) - chown_error_details (file_name, - st->stat.st_uid, st->stat.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 (st->stat.st_mode & (S_ISUID | S_ISGID | S_ISVTX)) - set_mode (file_name, &st->stat, 0, - invert_permissions, permstatus, typeflag); + chown_result = chown (file_name, 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, cur_info, + invert_permissions, permstatus, typeflag); } /* Remember to restore stat attributes (owner, group, mode and times) @@ -374,7 +397,8 @@ repair_delayed_set_stat (char const *dir, 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; } @@ -474,17 +498,24 @@ file_newer_p (const char *file_name, struct tar_stat_info *tar_stat) 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) { int e = errno; if (*interdir_made) - return 0; + return RECOVER_NO; switch (errno) { @@ -494,13 +525,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 */ @@ -510,7 +541,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: @@ -522,15 +553,15 @@ 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; + return RECOVER_OK; default: /* Just say we can't do anything about it... */ - return 0; + return RECOVER_NO; } } @@ -581,13 +612,13 @@ apply_nonancestor_delayed_set_stat (char const *file_name, bool after_links) if (! skip_this_one) { - struct tar_stat_info st; - st.stat.st_mode = data->mode; - st.stat.st_uid = data->uid; - st.stat.st_gid = data->gid; - st.atime = data->atime; - st.mtime = data->mtime; - set_stat (data->file_name, &st, cur_info, + 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, cur_info, data->invert_permissions, data->permstatus, DIRTYPE); } @@ -626,8 +657,9 @@ extract_dir (char *file_name, int typeflag) 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))) { @@ -653,13 +685,21 @@ extract_dir (char *file_name, int typeflag) 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; } @@ -670,7 +710,8 @@ extract_dir (char *file_name, int typeflag) { if (status == 0) delay_set_stat (file_name, ¤t_stat_info, - MODE_RWX & (mode ^ current_stat_info.stat.st_mode), + ((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, @@ -682,14 +723,13 @@ extract_dir (char *file_name, int typeflag) 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 @@ -728,6 +768,9 @@ extract_file (char *file_name, int typeflag) size_t count; size_t written; int interdir_made = 0; + 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. */ @@ -744,12 +787,18 @@ 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; } @@ -810,7 +859,7 @@ extract_file (char *file_name, int typeflag) if (to_command_option) sys_wait_command (); else - set_stat (file_name, ¤t_stat_info, NULL, 0, + set_stat (file_name, ¤t_stat_info, NULL, invert_permissions, (old_files_option == OVERWRITE_OLD_FILES ? UNKNOWN_PERMSTATUS : ARCHIVED_PERMSTATUS), typeflag); @@ -897,10 +946,11 @@ create_placeholder_file (char *file_name, bool is_symlink, int *interdir_made) static int extract_link (char *file_name, int typeflag) { - char const *link_name = safer_name_suffix (current_stat_info.link_name, - true, absolute_names_option); int interdir_made = 0; + char const *link_name; + 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); @@ -988,16 +1038,19 @@ extract_node (char *file_name, int typeflag) { int status; int interdir_made = 0; + 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; do - status = mknod (file_name, current_stat_info.stat.st_mode, + status = mknod (file_name, mode ^ invert_permissions, 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, NULL, 0, + set_stat (file_name, ¤t_stat_info, NULL, invert_permissions, ARCHIVED_PERMSTATUS, typeflag); return status; } @@ -1009,13 +1062,16 @@ extract_fifo (char *file_name, int typeflag) { int status; int interdir_made = 0; + 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 ((status = mkfifo (file_name, current_stat_info.stat.st_mode))) + while ((status = mkfifo (file_name, mode)) != 0) if (!maybe_recoverable (file_name, &interdir_made)) break; if (status == 0) - set_stat (file_name, ¤t_stat_info, NULL, 0, + set_stat (file_name, ¤t_stat_info, NULL, invert_permissions, ARCHIVED_PERMSTATUS, typeflag); else mkfifo_error (file_name); @@ -1174,6 +1230,9 @@ extract_archive (void) char typeflag; tar_extractor_t fun; + /* Try to disable the ability to unlink a directory. */ + priv_set_remove_linkdir (); + set_next_block_after (current_header); decode_header (current_header, ¤t_stat_info, ¤t_format, 1); if (!current_stat_info.file_name[0]