X-Git-Url: https://git.dogcows.com/gitweb?a=blobdiff_plain;f=src%2Fextract.c;h=40d55282ff4275d4c44586bcad5ec8fce2864b49;hb=b216fed6340d073d33351145be033ecc5b903c7a;hp=89db09c8b20b81b39554dc590d60360111e18da9;hpb=ca43ee97694b4404db3798c9e1aa4d6a9d81ca39;p=chaz%2Ftar diff --git a/src/extract.c b/src/extract.c index 89db09c..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 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,17 +24,13 @@ #include #include #include +#include #include "common.h" 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) */ -static bool directories_first; /* Directory members precede non-directory - ones in the archive. This is detected for - incremental archives only. This variable - helps correctly restore directory - timestamps */ /* Status of the permissions of a file that we are extracting. */ enum permstatus @@ -42,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 @@ -148,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; @@ -187,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); } @@ -198,7 +209,7 @@ check_time (char const *file_name, struct timespec t) if (t.tv_sec <= 0) WARN ((0, 0, _("%s: implausibly old time stamp %s"), file_name, tartime (t, true))); - else if (timespec_cmp (start_time, t) < 0) + else if (timespec_cmp (volume_start_time, t) < 0) { struct timespec now; gettime (&now); @@ -222,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. @@ -270,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) @@ -283,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) @@ -318,13 +336,13 @@ set_stat (char const *file_name, 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 directories_first - is set by prepare_to_extract. + 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/subdir dir/subdir/file + tar --no-recursion -cf archive dir dir/file1 foo dir/file2 */ static void delay_set_stat (char const *file_name, struct tar_stat_info const *st, @@ -379,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; } @@ -397,13 +416,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)) @@ -433,7 +451,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 /* ignored */, + ¤t_stat_info, invert_permissions, INTERDIR_PERMSTATUS); print_for_mkdir (file_name, cursor - file_name, mode); @@ -480,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) { @@ -500,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 */ @@ -516,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: @@ -528,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; } } @@ -587,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); } @@ -632,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))) { @@ -652,20 +678,28 @@ extract_dir (char *file_name, int typeflag) } if (S_ISDIR (st.st_mode)) { - mode = st.st_mode & ~ current_umask; + 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; } @@ -673,25 +707,29 @@ extract_dir (char *file_name, int typeflag) if (status == 0 || old_files_option == DEFAULT_OLD_FILES || old_files_option == OVERWRITE_OLD_FILES) - delay_set_stat (file_name, ¤t_stat_info, - 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 @@ -730,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. */ @@ -746,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; } @@ -764,26 +811,26 @@ extract_file (char *file_name, int typeflag) for (size = current_stat_info.stat.st_size; size > 0; ) { 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) { 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 -= written; - + set_next_block_after ((union block *) (data_block->buffer + written - 1)); if (count != written) @@ -798,7 +845,7 @@ extract_file (char *file_name, int typeflag) skip_file (size); 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. */ @@ -812,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); @@ -873,7 +920,7 @@ create_placeholder_file (char *file_name, bool is_symlink, int *interdir_made) if (h && ! h->after_links && 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)) + && (last_component (file_name) == file_name + h->file_name_len + 1)) { do { @@ -899,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); @@ -990,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; } @@ -1011,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); @@ -1026,13 +1080,14 @@ extract_fifo (char *file_name, int typeflag) #endif static int -extract_mangle_wrapper (char *file_name, int typeflag) +extract_volhdr (char *file_name, int typeflag) { - extract_mangle (); + if (verbose_option) + fprintf (stdlis, _("Reading %s\n"), quote (current_stat_info.file_name)); + skip_member (); return 0; } - static int extract_failure (char *file_name, int typeflag) { @@ -1107,18 +1162,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.dumpdir) - directories_first = true; + 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: @@ -1179,51 +1228,39 @@ void extract_archive (void) { char typeflag; - char *file_name; 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 (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); - file_name = safer_name_suffix (current_stat_info.file_name, - false, absolute_names_option); - if (strip_name_components) - { - 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; - } - /* Restore stats for all non-ancestor directories, unless it is an incremental archive. (see NOTICE in the comment to delay_set_stat above) */ - if (!directories_first) - apply_nonancestor_delayed_set_stat (file_name, 0); - + if (!delay_directory_restore_option) + apply_nonancestor_delayed_set_stat (current_stat_info.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; } @@ -1233,9 +1270,10 @@ extract_archive (void) 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 @@ -1324,6 +1362,39 @@ extract_finish (void) apply_nonancestor_delayed_set_stat ("", 1); } +bool +rename_directory (char *src, char *dst) +{ + if (rename (src, dst)) + { + int e = errno; + + 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; +} + void fatal_exit (void) {