X-Git-Url: https://git.dogcows.com/gitweb?a=blobdiff_plain;f=src%2Fextract.c;h=89db09c8b20b81b39554dc590d60360111e18da9;hb=ca43ee97694b4404db3798c9e1aa4d6a9d81ca39;hp=80f2f9ceec68d1616d311680c6537e30cb83b348;hpb=2f800ce3fb9c38360e1b6dac70ee0d8ceae72c6a;p=chaz%2Ftar diff --git a/src/extract.c b/src/extract.c index 80f2f9c..89db09c 100644 --- a/src/extract.c +++ b/src/extract.c @@ -30,6 +30,11 @@ 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 @@ -57,7 +62,13 @@ 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; @@ -76,7 +87,7 @@ struct delayed_link /* The device, inode number and last-modified time of the placeholder. */ dev_t dev; ino_t ino; - time_t mtime; + struct timespec mtime; /* True if the link is symbolic. */ bool is_symlink; @@ -109,19 +120,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. */ @@ -206,23 +204,23 @@ check_time (char const *file_name, struct timespec t) gettime (&now); if (timespec_cmp (now, t) < 0) { - unsigned long int ds = t.tv_sec - now.tv_sec; - int dns = t.tv_nsec - now.tv_nsec; - char dnsbuf[sizeof ".FFFFFFFFF"]; - if (dns < 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) { - dns += 1000000000; - ds--; + diff.tv_nsec += BILLION; + diff.tv_sec--; } - code_ns_fraction (dns, dnsbuf); - WARN ((0, 0, _("%s: time stamp %s is %lu%s s in the future"), - file_name, tartime (t, true), ds, dnsbuf)); + WARN ((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 CUR_INFO is nonzero, *CUR_INFO is the file's currernt status. If not restoring permissions, invert the @@ -237,7 +235,7 @@ check_time (char const *file_name, struct timespec t) static void set_stat (char const *file_name, - struct stat const *stat_info, + struct tar_stat_info const *st, struct stat const *cur_info, mode_t invert_permissions, enum permstatus permstatus, char typeflag) @@ -256,8 +254,11 @@ set_stat (char const *file_name, /* FIXME: incremental_option should set ctime too, but how? */ struct timespec ts[2]; - ts[0] = incremental_option ? get_stat_atime (stat_info) : start_time; - ts[1] = get_stat_mtime (stat_info); + if (incremental_option) + ts[0] = st->atime; + else + ts[0] = start_time; + ts[1] = st->mtime; if (utimens (file_name, ts) != 0) utime_error (file_name); @@ -272,7 +273,7 @@ set_stat (char const *file_name, 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, + set_mode (file_name, &st->stat, cur_info, invert_permissions, permstatus, typeflag); } @@ -286,48 +287,66 @@ set_stat (char const *file_name, if (typeflag == SYMTYPE) { #if HAVE_LCHOWN - if (lchown (file_name, stat_info->st_uid, stat_info->st_gid) < 0) + if (lchown (file_name, st->stat.st_uid, st->stat.st_gid) < 0) chown_error_details (file_name, - stat_info->st_uid, stat_info->st_gid); + st->stat.st_uid, st->stat.st_gid); #endif } else { - if (chown (file_name, stat_info->st_uid, stat_info->st_gid) < 0) + if (chown (file_name, st->stat.st_uid, st->stat.st_gid) < 0) chown_error_details (file_name, - stat_info->st_uid, stat_info->st_gid); + 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 (stat_info->st_mode & (S_ISUID | S_ISGID | S_ISVTX)) - set_mode (file_name, stat_info, 0, + if (st->stat.st_mode & (S_ISUID | S_ISGID | S_ISVTX)) + set_mode (file_name, &st->stat, 0, 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 directories_first + 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 +*/ 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_links = 0; - data->stat_info = *stat_info; - data->next = delayed_set_stat_head; + strcpy (data->file_name, file_name); delayed_set_stat_head = data; } @@ -352,7 +371,13 @@ 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)); data->permstatus = ARCHIVED_PERMSTATUS; @@ -408,7 +433,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 /* ignored */, invert_permissions, INTERDIR_PERMSTATUS); print_for_mkdir (file_name, cursor - file_name, mode); @@ -448,7 +473,7 @@ file_newer_p (const char *file_name, struct tar_stat_info *tar_stat) return errno != ENOENT; } 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; } @@ -551,8 +576,7 @@ apply_nonancestor_delayed_set_stat (char const *file_name, bool after_links) 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"), @@ -562,8 +586,16 @@ apply_nonancestor_delayed_set_stat (char const *file_name, bool after_links) } if (! skip_this_one) - set_stat (data->file_name, &data->stat_info, cur_info, - data->invert_permissions, data->permstatus, DIRTYPE); + { + 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, + data->invert_permissions, data->permstatus, DIRTYPE); + } delayed_set_stat_head = data->next; free (data); @@ -581,13 +613,27 @@ extract_dir (char *file_name, int typeflag) mode_t mode; int interdir_made = 0; + /* Save 'root device' to avoid purging mount points. */ + if (one_file_system_option && root_device == 0) + { + struct stat st; + char *dir = xgetcwd (); + + if (deref_stat (true, dir, &st)) + stat_diag (dir); + else + root_device = st.st_dev; + free (dir); + } + 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)) & MODE_RWX; while ((status = mkdir (file_name, mode))) { @@ -627,7 +673,7 @@ 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.stat, + delay_set_stat (file_name, ¤t_stat_info, MODE_RWX & (mode ^ current_stat_info.stat.st_mode), (status == 0 ? ARCHIVED_PERMSTATUS @@ -711,37 +757,33 @@ extract_file (char *file_name, int typeflag) } } + mv_begin (¤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) { 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) @@ -755,9 +797,8 @@ extract_file (char *file_name, int typeflag) 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. */ @@ -771,7 +812,7 @@ extract_file (char *file_name, int typeflag) if (to_command_option) sys_wait_command (); else - set_stat (file_name, ¤t_stat_info.stat, 0, 0, + set_stat (file_name, ¤t_stat_info, NULL, 0, (old_files_option == OVERWRITE_OLD_FILES ? UNKNOWN_PERMSTATUS : ARCHIVED_PERMSTATUS), typeflag); @@ -815,7 +856,7 @@ create_placeholder_file (char *file_name, bool is_symlink, int *interdir_made) delayed_link_head = p; p->dev = st.st_dev; p->ino = st.st_ino; - p->mtime = st.st_mtime; + p->mtime = get_stat_mtime (&st); p->is_symlink = is_symlink; if (is_symlink) { @@ -842,8 +883,8 @@ create_placeholder_file (char *file_name, bool is_symlink, int *interdir_made) stat_error (h->file_name); else { - h->stat_info.st_dev = st.st_dev; - h->stat_info.st_ino = st.st_ino; + h->dev = st.st_dev; + h->ino = st.st_ino; } } while ((h = h->next) && ! h->after_links); @@ -879,7 +920,7 @@ extract_link (char *file_name, int typeflag) for (; ds; ds = ds->next) if (ds->dev == st1.st_dev && ds->ino == st1.st_ino - && ds->mtime == st1.st_mtime) + && timespec_cmp (ds->mtime, get_stat_mtime (&st1)) == 0) { struct string_list *p = xmalloc (offsetof (struct string_list, string) + strlen (file_name) + 1); @@ -926,7 +967,7 @@ extract_symlink (char *file_name, int typeflag) break; if (status == 0) - set_stat (file_name, ¤t_stat_info.stat, 0, 0, 0, SYMTYPE); + set_stat (file_name, ¤t_stat_info, NULL, 0, 0, SYMTYPE); else symlink_error (current_stat_info.link_name, file_name); return status; @@ -958,7 +999,8 @@ extract_node (char *file_name, int typeflag) if (status != 0) mknod_error (file_name); else - set_stat (file_name, ¤t_stat_info.stat, 0, 0, ARCHIVED_PERMSTATUS, typeflag); + set_stat (file_name, ¤t_stat_info, NULL, 0, + ARCHIVED_PERMSTATUS, typeflag); return status; } #endif @@ -975,7 +1017,7 @@ extract_fifo (char *file_name, int typeflag) break; if (status == 0) - set_stat (file_name, ¤t_stat_info.stat, NULL, 0, + set_stat (file_name, ¤t_stat_info, NULL, 0, ARCHIVED_PERMSTATUS, typeflag); else mkfifo_error (file_name); @@ -1065,6 +1107,8 @@ 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; break; case GNUTYPE_VOLHDR: @@ -1156,7 +1200,8 @@ extract_archive (void) false, absolute_names_option); if (strip_name_components) { - size_t prefix_len = stripped_prefix_len (file_name, strip_name_components); + size_t prefix_len = stripped_prefix_len (file_name, + strip_name_components); if (prefix_len == (size_t) -1) { skip_member (); @@ -1165,8 +1210,12 @@ extract_archive (void) file_name += prefix_len; } - apply_nonancestor_delayed_set_stat (file_name, 0); - + /* 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); + /* Take a safety backup of a previously existing file. */ if (backup_option) @@ -1180,7 +1229,6 @@ extract_archive (void) } /* Extract the archive entry according to its type. */ - /* KLUDGE */ typeflag = sparse_member_p (¤t_stat_info) ? GNUTYPE_SPARSE : current_header->header.typeflag; @@ -1217,7 +1265,7 @@ apply_delayed_links (void) if (lstat (source, &st) == 0 && st.st_dev == ds->dev && st.st_ino == ds->ino - && st.st_mtime == ds->mtime) + && timespec_cmp (get_stat_mtime (&st), ds->mtime) == 0) { /* Unlink the placeholder, then create a hard link if possible, a symbolic link otherwise. */ @@ -1234,10 +1282,11 @@ apply_delayed_links (void) 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, 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); } } }