X-Git-Url: https://git.dogcows.com/gitweb?a=blobdiff_plain;f=src%2Fextract.c;h=0f75f2653e015d5575dfb296a64d0185c1072238;hb=46f09016e8219719179da09ee499981911054b5a;hp=35331249599d5a2f52cf63e9065574dec9bef013;hpb=6e9d1539b665c8f3c173b36702ca1650cac977ad;p=chaz%2Ftar diff --git a/src/extract.c b/src/extract.c index 3533124..0f75f26 100644 --- a/src/extract.c +++ b/src/extract.c @@ -1,5 +1,8 @@ /* Extract files from a tar archive. - Copyright 1988, 92,93,94,96,97,98, 1999 Free Software Foundation, Inc. + + Copyright (C) 1988, 1992, 1993, 1994, 1996, 1997, 1998, 1999, 2000, + 2001, 2003, 2004 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 @@ -14,10 +17,12 @@ You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., - 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#include "system.h" +#include #include +#include +#include #if HAVE_UTIME_H # include @@ -31,7 +36,7 @@ struct utimbuf #include "common.h" -static int we_are_root; /* true if our effective uid == 0 */ +static bool we_are_root; /* true if our effective uid == 0 */ static mode_t newdir_umask; /* umask when creating new directories */ static mode_t current_umask; /* current umask (which is set to 0 if -p) */ @@ -50,9 +55,14 @@ enum permstatus }; /* List of directories whose statuses we need to extract after we've - finished extracting their subsidiary files. The head of the list - has the longest name; each non-head element in the list is an - ancestor (in the directory hierarchy) of the preceding element. */ + finished extracting their subsidiary files. If you consider each + contiguous subsequence of elements of the form [D]?[^D]*, where [D] + represents an element where AFTER_SYMLINKS is nonzero and [^D] + represents an element where AFTER_SYMLINKS is zero, then the head + of the subsequence has the longest name, and each non-head element + in the prefix is an ancestor (in the directory hierarchy) of the + preceding element. */ + struct delayed_set_stat { struct delayed_set_stat *next; @@ -60,15 +70,44 @@ struct delayed_set_stat size_t file_name_len; mode_t invert_permissions; enum permstatus permstatus; + bool after_symlinks; char file_name[1]; }; static struct delayed_set_stat *delayed_set_stat_head; -/*--------------------------. -| Set up to extract files. | -`--------------------------*/ +/* List of symbolic links whose creation we have delayed. */ +struct delayed_symlink + { + /* The next delayed symbolic link in the list. */ + struct delayed_symlink *next; + + /* The device, inode number and last-modified time of the placeholder. */ + dev_t dev; + ino_t ino; + time_t mtime; + + /* The desired owner and group of the symbolic link. */ + uid_t uid; + gid_t gid; + /* A list of sources for this symlink. The sources are all to be + hard-linked together. */ + struct string_list *sources; + + /* The desired target of the desired link. */ + char target[1]; + }; + +static struct delayed_symlink *delayed_symlink_head; + +struct string_list + { + struct string_list *next; + char string[1]; + }; + +/* Set up to extract files. */ void extr_init (void) { @@ -76,6 +115,19 @@ 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. */ @@ -91,12 +143,15 @@ extr_init (void) } /* If restoring permissions, restore the mode for FILE_NAME from - information given in *STAT_INFO; otherwise invert the + information given in *STAT_INFO (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. TYPEFLAG specifies the type of the file. */ static void -set_mode (char *file_name, struct stat *stat_info, +set_mode (char const *file_name, + struct stat const *stat_info, + struct stat const *cur_info, mode_t invert_permissions, enum permstatus permstatus, char typeflag) { @@ -128,24 +183,39 @@ set_mode (char *file_name, struct stat *stat_info, that we created, so there's no point optimizing this code for other cases. */ struct stat st; - if (stat (file_name, &st) != 0) + if (! cur_info) { - stat_error (file_name); - return; + if (stat (file_name, &st) != 0) + { + stat_error (file_name); + return; + } + cur_info = &st; } - mode = st.st_mode ^ invert_permissions; + mode = cur_info->st_mode ^ invert_permissions; } if (chmod (file_name, mode) != 0) - { - int e = errno; - ERROR ((0, e, _("%s: Cannot change mode to %04lo"), - quotearg_colon (file_name), (unsigned long) mode)); - } + 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) +{ + 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))); } /* Restore stat attributes (owner, group, mode and times) for FILE_NAME, using information given in *STAT_INFO. + If CUR_INFO is nonzero, *CUR_INFO is the + file's currernt 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. @@ -157,7 +227,9 @@ set_mode (char *file_name, struct stat *stat_info, punt for the rest. Sigh! */ static void -set_stat (char *file_name, struct stat *stat_info, +set_stat (char const *file_name, + struct stat const *stat_info, + struct stat const *cur_info, mode_t invert_permissions, enum permstatus permstatus, char typeflag) { @@ -184,11 +256,11 @@ set_stat (char *file_name, struct stat *stat_info, utimbuf.modtime = stat_info->st_mtime; if (utime (file_name, &utimbuf) < 0) + utime_error (file_name); + else { - int e = errno; - ERROR ((0, e, - _("%s: Cannot change access and modification times"), - quotearg_colon (file_name))); + check_time (file_name, utimbuf.actime); + check_time (file_name, utimbuf.modtime); } } @@ -196,7 +268,7 @@ set_stat (char *file_name, struct stat *stat_info, 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, + set_mode (file_name, stat_info, cur_info, invert_permissions, permstatus, typeflag); } @@ -211,31 +283,21 @@ set_stat (char *file_name, struct stat *stat_info, { #if HAVE_LCHOWN if (lchown (file_name, stat_info->st_uid, stat_info->st_gid) < 0) - { - int e = errno; - ERROR ((0, e, _("%s: Cannot lchown to uid %lu gid %lu"), - quotearg_colon (file_name), - (unsigned long) stat_info->st_uid, - (unsigned long) stat_info->st_gid)); - } + chown_error_details (file_name, + stat_info->st_uid, stat_info->st_gid); #endif } else { if (chown (file_name, stat_info->st_uid, stat_info->st_gid) < 0) - { - int e = errno; - ERROR ((0, e, _("%s: Cannot chown to uid %lu gid %lu"), - quotearg_colon (file_name), - (unsigned long) stat_info->st_uid, - (unsigned long) stat_info->st_gid)); - } + 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, + set_mode (file_name, stat_info, 0, invert_permissions, permstatus, typeflag); } } @@ -252,22 +314,25 @@ delay_set_stat (char const *file_name, struct stat const *stat_info, mode_t invert_permissions, enum permstatus permstatus) { size_t file_name_len = strlen (file_name); - struct delayed_set_stat *data = xmalloc (sizeof *data + file_name_len); + struct delayed_set_stat *data = + xmalloc (offsetof (struct delayed_set_stat, file_name) + + file_name_len + 1); data->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; delayed_set_stat_head = data; } /* Update the delayed_set_stat info for an intermediate directory - created on the path to DIR_NAME. The intermediate directory - turned out to be the same as this directory, due to ".." or - symbolic links. *DIR_STAT_INFO is the status of the directory. */ + created within the file name of DIR. The intermediate directory turned + out to be the same as this directory, e.g. due to ".." or symbolic + links. *DIR_STAT_INFO is the status of the directory. */ static void -repair_delayed_set_stat (char const *dir_name, +repair_delayed_set_stat (char const *dir, struct stat const *dir_stat_info) { struct delayed_set_stat *data; @@ -283,52 +348,52 @@ repair_delayed_set_stat (char const *dir_name, if (st.st_dev == dir_stat_info->st_dev && st.st_ino == dir_stat_info->st_ino) { - data->stat_info = current_stat; - data->invert_permissions = (MODE_RWX - & (current_stat.st_mode ^ st.st_mode)); + data->stat_info = current_stat_info.stat; + data->invert_permissions = + (MODE_RWX & (current_stat_info.stat.st_mode ^ st.st_mode)); data->permstatus = ARCHIVED_PERMSTATUS; return; } } - ERROR ((0, 0, _("Unexpected inconsistency when making directory %s"), - quote (dir_name))); + ERROR ((0, 0, _("%s: Unexpected inconsistency when making directory"), + quotearg_colon (dir))); } -/*-----------------------------------------------------------------------. -| After a file/link/symlink/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. | -`-----------------------------------------------------------------------*/ - +/* After a file/link/symlink/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. */ static int make_directories (char *file_name) { - char *cursor; /* points into path */ + char *cursor0 = file_name + FILE_SYSTEM_PREFIX_LEN (file_name); + char *cursor; /* points into the file name */ int did_something = 0; /* did we do anything yet? */ - int saved_errno = errno; /* remember caller's errno */ int mode; int invert_permissions; int status; - for (cursor = strchr (file_name, '/'); - cursor; - cursor = strchr (cursor + 1, '/')) + + for (cursor = cursor0; *cursor; cursor++) { + if (! ISSLASH (*cursor)) + continue; + /* Avoid mkdir of empty string, if leading or double '/'. */ - if (cursor == file_name || cursor[-1] == '/') + if (cursor == cursor0 || ISSLASH (cursor[-1])) continue; - /* Avoid mkdir where last part of path is "." or "..". */ + /* Avoid mkdir where last part of file name is "." or "..". */ if (cursor[-1] == '.' - && (cursor == file_name + 1 || cursor[-2] == '/' + && (cursor == cursor0 + 1 || ISSLASH (cursor[-2]) || (cursor[-2] == '.' - && (cursor == file_name + 2 || cursor[-3] == '/')))) + && (cursor == cursor0 + 2 || ISSLASH (cursor[-3]))))) continue; - *cursor = '\0'; /* truncate the path there */ + *cursor = '\0'; /* truncate the name there */ mode = MODE_RWX & ~ newdir_umask; invert_permissions = we_are_root ? 0 : MODE_WXUSR & ~ mode; status = mkdir (file_name, mode ^ invert_permissions); @@ -339,7 +404,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 /* ignored */, + ¤t_stat_info.stat /* ignored */, invert_permissions, INTERDIR_PERMSTATUS); print_for_mkdir (file_name, cursor - file_name, mode); @@ -351,53 +416,50 @@ make_directories (char *file_name) *cursor = '/'; - if (errno == EEXIST -#if MSDOS - /* Turbo C mkdir gives a funny errno. */ - || errno == EACCES -#endif - ) - /* Directory already exists. */ + if (errno == EEXIST) + continue; /* Directory already exists. */ + else if ((errno == ENOSYS /* Automounted dirs on Solaris return + this. Reported by Warren Hyde + */ + || ERRNO_IS_EACCES) /* Turbo C mkdir gives a funny errno. */ + && access (file_name, W_OK) == 0) continue; /* Some other error in the mkdir. We return to the caller. */ break; } - errno = saved_errno; return did_something; /* tell them to retry if we made one */ } -/* Prepare to extract a file. - Return zero if extraction should not proceed. */ - -static int -prepare_to_extract (char const *file_name) +static bool +file_newer_p (const char *file_name, struct tar_stat_info *tar_stat) { - if (to_stdout_option) - return 0; + struct stat st; - if (old_files_option == UNLINK_FIRST_OLD_FILES - && !remove_any_file (file_name, recursive_unlink_option) - && errno != ENOENT) + if (stat (file_name, &st)) { - unlink_error (file_name); - return 0; + stat_warn (file_name); + /* Be on the safe side: if the file does exist assume it is newer */ + return errno != ENOENT; } - - return 1; + if (!S_ISDIR (st.st_mode) + && st.st_mtime >= tar_stat->stat.st_mtime) + { + return true; + } + return false; } -/*--------------------------------------------------------------------. -| 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. | -`--------------------------------------------------------------------*/ - +/* 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. */ static int maybe_recoverable (char *file_name, int *interdir_made) { + int e = errno; + if (*interdir_made) return 0; @@ -408,22 +470,37 @@ maybe_recoverable (char *file_name, int *interdir_made) switch (old_files_option) { - default: + case KEEP_OLD_FILES: return 0; + case KEEP_NEWER_FILES: + if (file_newer_p (file_name, ¤t_stat_info)) + { + errno = e; + return 0; + } + /* FALL THROUGH */ + case DEFAULT_OLD_FILES: + case NO_OVERWRITE_DIR_OLD_FILES: case OVERWRITE_OLD_FILES: { - int r = remove_any_file (file_name, 0); + int r = remove_any_file (file_name, ORDINARY_REMOVE_OPTION); errno = EEXIST; return r; } + + case UNLINK_FIRST_OLD_FILES: + break; } case ENOENT: /* Attempt creating missing intermediate directories. */ if (! make_directories (file_name)) - return 0; + { + errno = ENOENT; + return 0; + } *interdir_made = 1; return 1; @@ -434,678 +511,756 @@ maybe_recoverable (char *file_name, int *interdir_made) } } -/*---. -| ? | -`---*/ - +/* Fix the statuses of all directories whose statuses need fixing, and + which are not ancestors of FILE_NAME. If AFTER_SYMLINKS 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. */ static void -extract_sparse_file (int fd, off_t *sizeleft, off_t totalsize, char *name) +apply_nonancestor_delayed_set_stat (char const *file_name, bool after_symlinks) { - int sparse_ind = 0; - size_t written; - ssize_t count; - - /* assuming sizeleft is initially totalsize */ + size_t file_name_len = strlen (file_name); + bool check_for_renamed_directories = 0; - while (*sizeleft > 0) + while (delayed_set_stat_head) { - union block *data_block = find_next_block (); - if (! data_block) - { - ERROR ((0, 0, _("Unexpected EOF on archive file"))); - return; - } - if (lseek (fd, sparsearray[sparse_ind].offset, SEEK_SET) < 0) + struct delayed_set_stat *data = delayed_set_stat_head; + bool skip_this_one = 0; + struct stat st; + struct stat const *cur_info = 0; + + check_for_renamed_directories |= data->after_symlinks; + + if (after_symlinks < data->after_symlinks + || (data->file_name_len < file_name_len + && file_name[data->file_name_len] + && (ISSLASH (file_name[data->file_name_len]) + || ISSLASH (file_name[data->file_name_len - 1])) + && memcmp (file_name, data->file_name, data->file_name_len) == 0)) + break; + + if (check_for_renamed_directories) { - char buf[UINTMAX_STRSIZE_BOUND]; - int e = errno; - ERROR ((0, e, _("%s: lseek error at byte %s"), - quotearg_colon (name), - STRINGIFY_BIGINT (sparsearray[sparse_ind].offset, buf))); - return; + cur_info = &st; + if (stat (data->file_name, &st) != 0) + { + 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))) + { + ERROR ((0, 0, + _("%s: Directory renamed before its status could be extracted"), + quotearg_colon (data->file_name))); + skip_this_one = 1; + } } - written = sparsearray[sparse_ind++].numbytes; - while (written > BLOCKSIZE) + + if (! skip_this_one) + set_stat (data->file_name, &data->stat_info, cur_info, + data->invert_permissions, data->permstatus, DIRTYPE); + + delayed_set_stat_head = data->next; + free (data); + } +} + + + +/* Extractor functions for various member types */ + +static int +extract_dir (char *file_name, int typeflag) +{ + int status; + mode_t mode; + int interdir_made = 0; + + 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; + + while ((status = mkdir (file_name, mode))) + { + if (errno == EEXIST + && (interdir_made + || old_files_option == DEFAULT_OLD_FILES + || old_files_option == OVERWRITE_OLD_FILES)) { - count = full_write (fd, data_block->buffer, BLOCKSIZE); - if (count < 0) - write_error (name); - written -= count; - *sizeleft -= count; - set_next_block_after (data_block); - data_block = find_next_block (); - if (! data_block) + struct stat st; + if (stat (file_name, &st) == 0) { - ERROR ((0, 0, _("Unexpected EOF on archive file"))); - return; + if (interdir_made) + { + repair_delayed_set_stat (file_name, &st); + return 0; + } + if (S_ISDIR (st.st_mode)) + { + mode = st.st_mode & ~ current_umask; + break; + } } + errno = EEXIST; } - count = full_write (fd, data_block->buffer, written); + if (maybe_recoverable (file_name, &interdir_made)) + continue; - if (count < 0) - write_error (name); - else if (count != written) + if (errno != EEXIST) { - char buf1[UINTMAX_STRSIZE_BOUND]; - char buf2[UINTMAX_STRSIZE_BOUND]; - ERROR ((0, 0, _("%s: Could only write %s of %s bytes"), - quotearg_colon (name), - STRINGIFY_BIGINT (totalsize - *sizeleft, buf1), - STRINGIFY_BIGINT (totalsize, buf2))); - skip_file (*sizeleft); + mkdir_error (file_name); + return 1; } - - written -= count; - *sizeleft -= count; - set_next_block_after (data_block); + break; } - free (sparsearray); + 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)); + + return status; } -/*----------------------------------. -| Extract a file from the archive. | -`----------------------------------*/ -void -extract_archive (void) +static int +open_output_file (char *file_name, int typeflag) { - union block *data_block; int fd; - int status; - ssize_t sstatus; - size_t name_length; - size_t written; - int openflag; - mode_t mode; - off_t size; - int skipcrud; - int counter; - int interdir_made = 0; - char typeflag; -#if 0 - int sparse_ind = 0; -#endif - union block *exhdr; + 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; -#define CURRENT_FILE_NAME (skipcrud + current_file_name) +#if O_CTG + /* Contiguous files (on the Masscomp) have to specify the size in + the open call that creates them. */ - set_next_block_after (current_header); - decode_header (current_header, ¤t_stat, ¤t_format, 1); + if (typeflag == CONTTYPE) + fd = open (file_name, openflag | O_CTG, mode, current_stat_info.stat.st_size); + else + fd = open (file_name, openflag, mode); - if (interactive_option && !confirm ("extract", current_file_name)) +#else /* not O_CTG */ + if (typeflag == CONTTYPE) { - if (current_header->oldgnu_header.isextended) - skip_extended_headers (); - skip_file (current_stat.st_size); - return; + static int conttype_diagnosed; + + if (!conttype_diagnosed) + { + conttype_diagnosed = 1; + WARN ((0, 0, _("Extracting contiguous files as regular files"))); + } } + fd = open (file_name, openflag, mode); - /* Print the block from `current_header' and `current_stat'. */ +#endif /* not O_CTG */ - if (verbose_option) - print_header (); + return fd; +} + +static int +extract_file (char *file_name, int typeflag) +{ + int fd; + off_t size; + union block *data_block; + int status; + size_t count; + size_t written; + int interdir_made = 0; - /* Check for fully specified file names and other atrocities. */ + /* FIXME: deal with protection issues. */ - skipcrud = 0; - if (! absolute_names_option) + if (to_stdout_option) + fd = STDOUT_FILENO; + else if (to_command_option) { - while (CURRENT_FILE_NAME[0] == '/') + fd = sys_exec_command (file_name, 'f', ¤t_stat_info); + if (fd < 0) { - static int warned_once; - - if (!warned_once) - { - warned_once = 1; - WARN ((0, 0, _("Removing leading `/' from member names"))); - } - skipcrud++; /* force relative path */ + skip_member (); + return 0; } + } + else + { + do + fd = open_output_file (file_name, typeflag); + while (fd < 0 && maybe_recoverable (file_name, &interdir_made)); - if (contains_dot_dot (CURRENT_FILE_NAME)) + if (fd < 0) { - ERROR ((0, 0, _("%s: Member name contains `..'"), - quotearg_colon (CURRENT_FILE_NAME))); - if (current_header->oldgnu_header.isextended) - skip_extended_headers (); - skip_file (current_stat.st_size); - return; + open_error (file_name); + return 1; } } - /* Take a safety backup of a previously existing file. */ - - if (backup_option && !to_stdout_option) - if (!maybe_backup_file (CURRENT_FILE_NAME, 0)) + 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; ) { - int e = errno; - ERROR ((0, e, _("%s: Was unable to backup this file"), - quotearg_colon (CURRENT_FILE_NAME))); - if (current_header->oldgnu_header.isextended) - skip_extended_headers (); - skip_file (current_stat.st_size); - return; - } + 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; + } - /* Extract the archive entry according to its type. */ + /* Locate data, determine max length writeable, write it, + block that we have used the data, then check if the write + worked. */ - typeflag = current_header->header.typeflag; - switch (typeflag) - { - /* JK - What we want to do if the file is sparse is loop through - the array of sparse structures in the header and read in and - translate the character strings representing 1) the offset at - which to write and 2) how many bytes to write into numbers, - which we store into the scratch array, "sparsearray". This - array makes our life easier the same way it did in creating the - tar file that had to deal with a sparse file. - - After we read in the first five (at most) sparse structures, we - check to see if the file has an extended header, i.e., if more - sparse structures are needed to describe the contents of the new - file. If so, we read in the extended headers and continue to - store their contents into the sparsearray. */ + data_block = find_next_block (); + if (! data_block) + { + ERROR ((0, 0, _("Unexpected EOF in archive"))); + break; /* FIXME: What happens, then? */ + } - case GNUTYPE_SPARSE: - sp_array_size = 10; - sparsearray = - xmalloc (sp_array_size * sizeof (struct sp_array)); + written = available_space_after (data_block); - for (counter = 0; counter < SPARSES_IN_OLDGNU_HEADER; counter++) - { - struct sparse const *s = ¤t_header->oldgnu_header.sp[counter]; - sparsearray[counter].offset = OFF_FROM_HEADER (s->offset); - sparsearray[counter].numbytes = SIZE_FROM_HEADER (s->numbytes); - if (!sparsearray[counter].numbytes) + 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) + { + if (!to_command_option) + write_error_details (file_name, count, written); + /* FIXME: shouldn't we restore from backup? */ break; - } + } + } - if (current_header->oldgnu_header.isextended) - { - /* Read in the list of extended headers and translate them - into the sparsearray as before. Note that this - invalidates current_header. */ + skip_file (size); - /* static */ int ind = SPARSES_IN_OLDGNU_HEADER; + if (multi_volume_option) + assign_string (&save_name, 0); - while (1) - { - exhdr = find_next_block (); - if (! exhdr) - { - ERROR ((0, 0, _("Unexpected EOF on archive file"))); - return; - } - for (counter = 0; counter < SPARSES_IN_SPARSE_HEADER; counter++) - { - struct sparse const *s = &exhdr->sparse_header.sp[counter]; - if (counter + ind > sp_array_size - 1) - { - /* Realloc the scratch area since we've run out of - room. */ + /* 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. */ - sp_array_size *= 2; - sparsearray = - xrealloc (sparsearray, - sp_array_size * sizeof (struct sp_array)); - } - if (s->numbytes[0] == 0) - break; - sparsearray[counter + ind].offset = - OFF_FROM_HEADER (s->offset); - sparsearray[counter + ind].numbytes = - SIZE_FROM_HEADER (s->numbytes); - } - if (!exhdr->sparse_header.isextended) - break; - else - { - ind += SPARSES_IN_SPARSE_HEADER; - set_next_block_after (exhdr); - } - } - set_next_block_after (exhdr); - } - /* Fall through. */ + if (to_stdout_option) + return 0; - case AREGTYPE: - case REGTYPE: - case CONTTYPE: + status = close (fd); + if (status < 0) + close_error (file_name); - /* Appears to be a file. But BSD tar uses the convention that a slash - suffix means a directory. */ + 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); - name_length = strlen (CURRENT_FILE_NAME); - if (name_length && CURRENT_FILE_NAME[name_length - 1] == '/') - goto really_dir; + return status; +} - /* FIXME: deal with protection issues. */ +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; - again_file: - openflag = (O_WRONLY | O_BINARY | O_CREAT - | (old_files_option == OVERWRITE_OLD_FILES - ? O_TRUNC - : O_EXCL)); - mode = current_stat.st_mode & MODE_RWX & ~ current_umask; + do + { + struct stat st1, st2; + int e; + int status = link (link_name, file_name); + e = errno; - if (to_stdout_option) + if (status == 0) { - fd = STDOUT_FILENO; - goto extract_file; + struct delayed_symlink *ds = delayed_symlink_head; + if (ds && lstat (link_name, &st1) == 0) + for (; ds; ds = ds->next) + if (ds->dev == st1.st_dev + && ds->ino == st1.st_ino + && ds->mtime == st1.st_mtime) + { + struct string_list *p = xmalloc (offsetof (struct string_list, string) + + strlen (file_name) + 1); + strcpy (p->string, file_name); + p->next = ds->sources; + ds->sources = p; + break; + } + return 0; } + else if ((e == EEXIST && strcmp (link_name, file_name) == 0) + || (lstat (link_name, &st1) == 0 + && lstat (file_name, &st2) == 0 + && st1.st_dev == st2.st_dev + && st1.st_ino == st2.st_ino)) + return 0; - if (! prepare_to_extract (CURRENT_FILE_NAME)) - { - if (current_header->oldgnu_header.isextended) - skip_extended_headers (); - skip_file (current_stat.st_size); - if (backup_option) - undo_last_backup (); - break; - } + errno = e; + } + while (maybe_recoverable (file_name, &interdir_made)); -#if O_CTG - /* Contiguous files (on the Masscomp) have to specify the size in - the open call that creates them. */ + if (!(incremental_option && errno == EEXIST)) + { + link_error (link_name, file_name); + return 1; + } + return 0; +} - if (typeflag == CONTTYPE) - fd = open (CURRENT_FILE_NAME, openflag | O_CTG, - mode, current_stat.st_size); - else - fd = open (CURRENT_FILE_NAME, openflag, mode); +static int +extract_symlink (char *file_name, int typeflag) +{ +#ifdef HAVE_SYMLINK + int status, fd; + int interdir_made = 0; -#else /* not O_CTG */ - if (typeflag == CONTTYPE) - { - static int conttype_diagnosed; + 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; - if (!conttype_diagnosed) - { - conttype_diagnosed = 1; - WARN ((0, 0, _("Extracting contiguous files as regular files"))); - } - } - fd = open (CURRENT_FILE_NAME, openflag, mode); + 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; -#endif /* not O_CTG */ + while ((fd = open (file_name, O_WRONLY | O_CREAT | O_EXCL, 0)) < 0) + if (! maybe_recoverable (file_name, &interdir_made)) + break; + status = -1; if (fd < 0) + open_error (file_name); + else if (fstat (fd, &st) != 0) { - if (maybe_recoverable (CURRENT_FILE_NAME, &interdir_made)) - goto again_file; - - open_error (CURRENT_FILE_NAME); - if (current_header->oldgnu_header.isextended) - skip_extended_headers (); - skip_file (current_stat.st_size); - if (backup_option) - undo_last_backup (); - break; + stat_error (file_name); + close (fd); } - - extract_file: - if (typeflag == GNUTYPE_SPARSE) + else if (close (fd) != 0) + close_error (file_name); + else { - char *name; - size_t name_length_bis; - - /* Kludge alert. NAME is assigned to header.name because - during the extraction, the space that contains the header - will get scribbled on, and the name will get munged, so any - error messages that happen to contain the filename will look - REAL interesting unless we do this. */ - - name_length_bis = strlen (CURRENT_FILE_NAME) + 1; - name = xmalloc (name_length_bis); - memcpy (name, CURRENT_FILE_NAME, name_length_bis); - size = current_stat.st_size; - extract_sparse_file (fd, &size, current_stat.st_size, name); + 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; } - else - for (size = current_stat.st_size; - size > 0; - size -= written) - { - if (multi_volume_option) - { - assign_string (&save_name, current_file_name); - save_totsize = current_stat.st_size; - save_sizeleft = size; - } - - /* Locate data, determine max length writeable, write it, - block that we have used the data, then check if the write - worked. */ - - data_block = find_next_block (); - if (! data_block) - { - ERROR ((0, 0, _("Unexpected EOF on archive file"))); - break; /* FIXME: What happens, then? */ - } - - written = available_space_after (data_block); - - if (written > size) - written = size; - errno = 0; - sstatus = full_write (fd, data_block->buffer, written); - - set_next_block_after ((union block *) - (data_block->buffer + written - 1)); - if (sstatus == written) - continue; - - /* Error in writing to file. Print it, skip to next file in - archive. */ - - if (sstatus < 0) - write_error (CURRENT_FILE_NAME); - else - ERROR ((0, 0, _("%s: Could only write %lu of %lu bytes"), - quotearg_colon (CURRENT_FILE_NAME), - (unsigned long) sstatus, - (unsigned long) written)); - skip_file (size - written); - break; /* still do the close, mod time, chmod, etc */ - } + } - if (multi_volume_option) - assign_string (&save_name, 0); + return status; - /* If writing to stdout, don't try to do anything to the filename; - it doesn't exist, or we don't want to touch it anyway. */ +#else + static int warned_once; - if (to_stdout_option) - break; + if (!warned_once) + { + warned_once = 1; + WARN ((0, 0, _("Attempting extraction of symbolic links as hard links"))); + } + return extract_link (file_name, typeflag); +#endif +} - status = close (fd); - if (status < 0) - { - close_error (CURRENT_FILE_NAME); - if (backup_option) - undo_last_backup (); - } +#if S_IFCHR || S_IFBLK +static int +extract_node (char *file_name, int typeflag) +{ + int status; + int interdir_made = 0; - set_stat (CURRENT_FILE_NAME, ¤t_stat, 0, - (old_files_option == OVERWRITE_OLD_FILES - ? UNKNOWN_PERMSTATUS - : ARCHIVED_PERMSTATUS), - typeflag); - break; + 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)); - case SYMTYPE: -#ifdef HAVE_SYMLINK - if (! prepare_to_extract (CURRENT_FILE_NAME)) - break; + if (status != 0) + mknod_error (file_name); + else + set_stat (file_name, ¤t_stat_info.stat, 0, 0, ARCHIVED_PERMSTATUS, typeflag); + return status; +} +#endif - while (status = symlink (current_link_name, CURRENT_FILE_NAME), - status != 0) - if (!maybe_recoverable (CURRENT_FILE_NAME, &interdir_made)) - break; +#if HAVE_MKFIFO || defined mkfifo +static int +extract_fifo (char *file_name, int typeflag) +{ + int status; + int interdir_made = 0; - if (status == 0) + while ((status = mkfifo (file_name, current_stat_info.stat.st_mode))) + if (!maybe_recoverable (file_name, &interdir_made)) + break; - /* Setting the attributes of symbolic links might, on some systems, - change the pointed to file, instead of the symbolic link itself. - At least some of these systems have a lchown call, and the - set_stat routine knows about this. */ + if (status == 0) + set_stat (file_name, ¤t_stat_info.stat, NULL, 0, + ARCHIVED_PERMSTATUS, typeflag); + else + mkfifo_error (file_name); + return status; +} +#endif - set_stat (CURRENT_FILE_NAME, ¤t_stat, 0, - ARCHIVED_PERMSTATUS, typeflag); +static int +extract_mangle_wrapper (char *file_name, int typeflag) +{ + extract_mangle (); + return 0; +} - else - { - int e = errno; - ERROR ((0, e, _("%s: Cannot create symlink to %s"), - quotearg_colon (CURRENT_FILE_NAME), - quote (current_link_name))); - if (backup_option) - undo_last_backup (); - } - break; -#else - { - static int warned_once; +static int +extract_failure (char *file_name, int typeflag) +{ + return 1; +} - if (!warned_once) - { - warned_once = 1; - WARN ((0, 0, - _("Attempting extraction of symbolic links as hard links"))); - } - } - typeflag = LNKTYPE; - /* Fall through. */ -#endif +typedef int (*tar_extractor_t) (char *file_name, int typeflag); - case LNKTYPE: - if (! prepare_to_extract (CURRENT_FILE_NAME)) - break; + - again_link: - { - struct stat st1, st2; - int e; +/* Prepare to extract a file. Find extractor function. + Return zero if extraction should not proceed. */ - /* MSDOS does not implement links. However, djgpp's link() actually - copies the file. */ - status = link (current_link_name, CURRENT_FILE_NAME); +static int +prepare_to_extract (char const *file_name, int typeflag, tar_extractor_t *fun) +{ + int rc = 1; - if (status == 0) - break; - if (maybe_recoverable (CURRENT_FILE_NAME, &interdir_made)) - goto again_link; + if (EXTRACT_OVER_PIPE) + rc = 0; - if (incremental_option && errno == EEXIST) - break; - e = errno; - if (stat (current_link_name, &st1) == 0 - && stat (CURRENT_FILE_NAME, &st2) == 0 - && st1.st_dev == st2.st_dev - && st1.st_ino == st2.st_ino) - break; + /* Select the extractor */ + switch (typeflag) + { + case GNUTYPE_SPARSE: + *fun = extract_file; + rc = 1; + break; - ERROR ((0, e, _("%s: Cannot link to %s"), - quotearg_colon (CURRENT_FILE_NAME), - quote (current_link_name))); - if (backup_option) - undo_last_backup (); - } + case AREGTYPE: + case REGTYPE: + case CONTTYPE: + /* Appears to be a file. But BSD tar uses the convention that a slash + suffix means a directory. */ + if (current_stat_info.had_trailing_slash) + *fun = extract_dir; + else + { + *fun = extract_file; + rc = 1; + } + break; + + case SYMTYPE: + *fun = extract_symlink; + break; + + case LNKTYPE: + *fun = extract_link; break; #if S_IFCHR case CHRTYPE: - current_stat.st_mode |= S_IFCHR; - goto make_node; + current_stat_info.stat.st_mode |= S_IFCHR; + *fun = extract_node; + break; #endif #if S_IFBLK case BLKTYPE: - current_stat.st_mode |= S_IFBLK; -#endif - -#if S_IFCHR || S_IFBLK - make_node: - if (! prepare_to_extract (CURRENT_FILE_NAME)) - break; - - status = mknod (CURRENT_FILE_NAME, current_stat.st_mode, - current_stat.st_rdev); - if (status != 0) - { - if (maybe_recoverable (CURRENT_FILE_NAME, &interdir_made)) - goto make_node; - mknod_error (CURRENT_FILE_NAME); - if (backup_option) - undo_last_backup (); - break; - }; - set_stat (CURRENT_FILE_NAME, ¤t_stat, 0, - ARCHIVED_PERMSTATUS, typeflag); + current_stat_info.stat.st_mode |= S_IFBLK; + *fun = extract_node; break; #endif #if HAVE_MKFIFO || defined mkfifo case FIFOTYPE: - if (! prepare_to_extract (CURRENT_FILE_NAME)) - break; - - while (status = mkfifo (CURRENT_FILE_NAME, current_stat.st_mode), - status != 0) - if (!maybe_recoverable (CURRENT_FILE_NAME, &interdir_made)) - break; - - if (status == 0) - set_stat (CURRENT_FILE_NAME, ¤t_stat, 0, - ARCHIVED_PERMSTATUS, typeflag); - else - { - mkfifo_error (CURRENT_FILE_NAME); - if (backup_option) - undo_last_backup (); - } + *fun = extract_fifo; break; #endif case DIRTYPE: case GNUTYPE_DUMPDIR: - name_length = strlen (CURRENT_FILE_NAME); - - really_dir: - /* Remove trailing "/" and "/.", unless that would result in the - empty string. */ - for (;;) - { - if (1 < name_length && CURRENT_FILE_NAME[name_length - 1] == '/') - CURRENT_FILE_NAME[--name_length] = '\0'; - else if (2 < name_length - && CURRENT_FILE_NAME[name_length - 1] == '.' - && CURRENT_FILE_NAME[name_length - 2] == '/') - CURRENT_FILE_NAME[name_length -= 2] = '\0'; - else - break; - } - - if (incremental_option) - { - /* Read the entry and delete files that aren't listed in the - archive. */ - - gnu_restore (skipcrud); - } - else if (typeflag == GNUTYPE_DUMPDIR) - skip_file (current_stat.st_size); - - if (! prepare_to_extract (CURRENT_FILE_NAME)) - break; - - mode = ((current_stat.st_mode - | (we_are_root ? 0 : MODE_WXUSR)) - & MODE_RWX); - - again_dir: - status = mkdir (CURRENT_FILE_NAME, mode); - if (status != 0) - { - if (errno == EEXIST && interdir_made - && contains_dot_dot (CURRENT_FILE_NAME)) - { - int e = errno; - struct stat st; - if (stat (CURRENT_FILE_NAME, &st) == 0) - { - repair_delayed_set_stat (CURRENT_FILE_NAME, &st); - break; - } - e = errno; - } - - if (maybe_recoverable (CURRENT_FILE_NAME, &interdir_made)) - goto again_dir; - - if (errno != EEXIST || old_files_option == KEEP_OLD_FILES) - { - int e = errno; - ERROR ((0, e, "%s: mkdir", quotearg_colon (CURRENT_FILE_NAME))); - if (backup_option) - undo_last_backup (); - break; - } - } - - if (status == 0 - || old_files_option == OVERWRITE_OLD_FILES) - delay_set_stat (CURRENT_FILE_NAME, ¤t_stat, - mode & ~ current_stat.st_mode, - (status == 0 - ? ARCHIVED_PERMSTATUS - : UNKNOWN_PERMSTATUS)); + *fun = extract_dir; break; case GNUTYPE_VOLHDR: if (verbose_option) - fprintf (stdlis, _("Reading %s\n"), quote (current_file_name)); + fprintf (stdlis, _("Reading %s\n"), quote (current_stat_info.file_name)); + *fun = NULL; break; case GNUTYPE_NAMES: - extract_mangle (); + *fun = extract_mangle_wrapper; break; case GNUTYPE_MULTIVOL: ERROR ((0, 0, - _("Cannot extract %s -- file is continued from another volume"), - quote (current_file_name))); - skip_file (current_stat.st_size); - if (backup_option) - undo_last_backup (); + _("%s: Cannot extract -- file is continued from another volume"), + quotearg_colon (current_stat_info.file_name))); + *fun = extract_failure; break; case GNUTYPE_LONGNAME: case GNUTYPE_LONGLINK: - ERROR ((0, 0, _("Visible long name error"))); - skip_file (current_stat.st_size); - if (backup_option) - undo_last_backup (); + ERROR ((0, 0, _("Unexpected long name header"))); + *fun = extract_failure; break; default: WARN ((0, 0, - _("Unknown file type '%c' for %s, extracted as normal file"), - typeflag, quote (CURRENT_FILE_NAME))); - goto again_file; + _("%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 + : ORDINARY_REMOVE_OPTION) + && errno && errno != ENOENT) + { + unlink_error (file_name); + return 0; + } + break; + + case KEEP_NEWER_FILES: + if (file_newer_p (file_name, ¤t_stat_info)) + { + WARN ((0, 0, _("Current %s is newer or same age"), + quote (file_name))); + return 0; + } + break; + + default: + break; } -#undef CURRENT_FILE_NAME + return 1; } -/* Fix the statuses of all directories that are not ancestors of FILE_NAME. */ +/* Extract a file from the archive. */ void -apply_delayed_set_stat (char const *file_name) +extract_archive (void) { - size_t file_name_len = strlen (file_name); + char typeflag; + char *file_name; + tar_extractor_t fun; - while (delayed_set_stat_head) + 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)) { - struct delayed_set_stat *data = delayed_set_stat_head; - delayed_set_stat_head = data->next; - if (data->file_name_len < file_name_len - && file_name[data->file_name_len] == '/' - && memcmp (file_name, data->file_name, data->file_name_len) == 0) - break; - set_stat (data->file_name, &data->stat_info, - data->invert_permissions, data->permstatus, DIRTYPE); - free (data); + 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); + 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; + } + + 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)) + { + int e = errno; + ERROR ((0, e, _("%s: Was unable to backup this file"), + quotearg_colon (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 (fun && (*fun) (file_name, typeflag) && backup_option) + undo_last_backup (); + } + else + skip_member (); + +} + +/* Extract the symbolic links whose final extraction were delayed. */ +static void +apply_delayed_symlinks (void) +{ + struct delayed_symlink *ds; + + for (ds = delayed_symlink_head; ds; ) + { + struct string_list *sources = ds->sources; + char const *valid_source = 0; + + 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 + 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) + { + /* Unlink the placeholder, then create a hard link if possible, + a symbolic link otherwise. */ + if (unlink (source) != 0) + unlink_error (source); + else if (valid_source && link (valid_source, source) == 0) + ; + else if (symlink (ds->target, source) != 0) + symlink_error (ds->target, source); + else + { + valid_source = source; + st.st_uid = ds->uid; + st.st_gid = ds->gid; + set_stat (source, &st, 0, 0, 0, SYMTYPE); + } + } + } + + for (sources = ds->sources; sources; ) + { + struct string_list *next = sources->next; + free (sources); + sources = next; + } + + { + struct delayed_symlink *next = ds->next; + free (ds); + ds = next; + } + } + + delayed_symlink_head = 0; +} + +/* Finish the extraction of an archive. */ +void +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 + directory status-setting for ordinary directories. */ + apply_delayed_symlinks (); + + /* Finally, fix the status of directories that are ancestors + of delayed symlinks. */ + apply_nonancestor_delayed_set_stat ("", 1); } void fatal_exit (void) { - apply_delayed_set_stat (""); + extract_finish (); error (TAREXIT_FAILURE, 0, _("Error is not recoverable: exiting now")); abort (); } + +void +xalloc_die (void) +{ + error (0, 0, "%s", _("memory exhausted")); + fatal_exit (); +}