X-Git-Url: https://git.dogcows.com/gitweb?a=blobdiff_plain;f=src%2Fextract.c;h=9be0a8b1392b95ae200192bdb131c86f0963f532;hb=d659cbaccdc1f3279c49107cf15f15a639738529;hp=9fade45f46fc1e4f45f94da61cf4694fe39ae30f;hpb=92762770814035c4324ec0a38ade31f491ee425a;p=chaz%2Ftar diff --git a/src/extract.c b/src/extract.c index 9fade45..9be0a8b 100644 --- a/src/extract.c +++ b/src/extract.c @@ -1,869 +1,1269 @@ /* Extract files from a tar archive. - Copyright (C) 1988, 1992 Free Software Foundation -This file is part of GNU Tar. + Copyright (C) 1988, 1992, 1993, 1994, 1996, 1997, 1998, 1999, 2000, + 2001, 2003, 2004, 2005 Free Software Foundation, Inc. -GNU Tar 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 version. + Written by John Gilmore, on 1985-11-19. -GNU Tar is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. + 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 + version. -You should have received a copy of the GNU General Public License -along with GNU Tar; see the file COPYING. If not, write to -the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. */ + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + Public License for more details. -/* - * Extract files from a tar archive. - * - * Written 19 Nov 1985 by John Gilmore, ihnp4!hoptoad!gnu. - */ + 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., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#include +#include +#include +#include #include -#ifndef STDC_HEADERS -extern int errno; -#endif -#include -#include -time_t time (); +#include -#ifdef BSD42 -#include -#else -#ifndef V7 -#include -#endif -#endif +#include "common.h" -#ifdef NO_OPEN3 -/* We need the #define's even though we don't use them. */ -#include "open3.h" -#endif +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) */ -#ifdef EMUL_OPEN3 -/* Simulated 3-argument open for systems that don't have it */ -#include "open3.h" -#endif +/* Status of the permissions of a file that we are extracting. */ +enum permstatus +{ + /* This file may have existed already; its permissions are unknown. */ + UNKNOWN_PERMSTATUS, -#include "tar.h" -#include "port.h" + /* This file was created using the permissions from the archive. */ + ARCHIVED_PERMSTATUS, -#if defined(_POSIX_VERSION) -#include -#else -struct utimbuf -{ - long actime; - long modtime; + /* This is an intermediate directory; the archive did not specify + its permissions. */ + INTERDIR_PERMSTATUS }; -#endif +/* List of directories whose statuses we need to extract after we've + finished extracting their subsidiary files. If you consider each + contiguous subsequence of elements of the form [D]?[^D]*, where [D] + represents an element where AFTER_SYMLINKS is nonzero and [^D] + represents an element where AFTER_SYMLINKS is zero, then the head + 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; + struct stat stat_info; + 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; + +/* 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) +{ + we_are_root = geteuid () == 0; + same_permissions_option += we_are_root; + same_owner_option += we_are_root; -extern FILE *msg_file; + /* 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 (); -extern union record *head; /* Points to current tape header */ -extern struct stat hstat; /* Stat struct corresponding */ -extern int head_standard; /* Tape header is in ANSI format */ + if (deref_stat (true, dir, &st)) + stat_diag (dir); + else + root_device = st.st_dev; + } -extern char *save_name; -extern long save_totsize; -extern long save_sizeleft; + /* 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. */ -int confirm (); -void decode_header (); -void extract_mangle (); -void extract_sparse_file (); -long from_oct (); -void gnu_restore (); -extern void print_header (); -extern void skip_file (); -extern void skip_extended_headers (); -extern void pr_mkdir (); -void saverec (); + newdir_umask = umask (0); + if (0 < same_permissions_option) + current_umask = 0; + else + { + umask (newdir_umask); /* restore the kernel umask */ + current_umask = newdir_umask; + } +} -int make_dirs (); /* Makes required directories */ +/* If restoring permissions, restore the mode for FILE_NAME from + 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 const *file_name, + struct stat const *stat_info, + struct stat const *cur_info, + mode_t invert_permissions, enum permstatus permstatus, + char typeflag) +{ + mode_t mode; -static time_t now = 0; /* Current time */ -static we_are_root = 0; /* True if our effective uid == 0 */ -static int notumask = ~0; /* Masks out bits user doesn't want */ + 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 + directories inherit the setgid bits from their parents, so we + we must set directories' modes explicitly. */ + if (permstatus == ARCHIVED_PERMSTATUS + && ! (mode & ~ MODE_RWX) + && typeflag != DIRTYPE + && typeflag != GNUTYPE_DUMPDIR) + return; + } + else if (! invert_permissions) + return; + else + { + /* We must inspect a directory's current permissions, since the + directory may have inherited its setgid bit from its parent. + + INVERT_PERMISSIONS happens to be nonzero only for directories + that we created, so there's no point optimizing this code for + other cases. */ + struct stat st; + if (! cur_info) + { + if (stat (file_name, &st) != 0) + { + stat_error (file_name); + return; + } + cur_info = &st; + } + mode = cur_info->st_mode ^ invert_permissions; + } -/* - * "Scratch" space to store the information about a sparse file before - * writing the info into the header or extended header - */ -/*struct sp_array *sparsearray;*/ + if (chmod (file_name, mode) != 0) + chmod_error_details (file_name, mode); +} -/* number of elts storable in the sparsearray */ -/*int sp_array_size = 10;*/ +/* Check time after successfully setting FILE_NAME's time stamp to T. */ +static void +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_lt (start_time, t)) + { + struct timespec now; + gettime (&now); + if (timespec_lt (now, t)) + { + 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) + { + dns += 1000000000; + ds--; + } + 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)); + } + } +} -struct saved_dir_info +/* 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. + TYPEFLAG specifies the type of the file. */ + +/* FIXME: About proper restoration of symbolic link attributes, we still do + not have it right. Pretesters' reports tell us we need further study and + probably more configuration. For now, just use lchown if it exists, and + punt for the rest. Sigh! */ + +static void +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) { - char *path; - int mode; - int atime; - int mtime; - struct saved_dir_info *next; -}; + if (typeflag != SYMTYPE) + { + /* We do the utime before the chmod because some versions of utime are + broken and trash the modes of the file. */ -struct saved_dir_info *saved_dir_info_head; + if (! touch_option && permstatus != INTERDIR_PERMSTATUS) + { + /* We set the accessed time to `now', which is really the time we + started extracting files, unless incremental_option is used, in + which case .st_atime is used. */ -/* - * Set up to extract files. - */ -void -extr_init () -{ - int ourmask; - - now = time ((time_t *) 0); - if (geteuid () == 0) - we_are_root = 1; - - /* - * We need to know our umask. But if f_use_protection is set, - * leave our kernel umask at 0, and our "notumask" at ~0. - */ - ourmask = umask (0); /* Read it */ - if (!f_use_protection) + /* 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 (utimens (file_name, ts) != 0) + utime_error (file_name); + else + { + check_time (file_name, ts[0]); + check_time (file_name, ts[1]); + } + } + + /* Some systems allow non-root users to give files away. Once this + done, it is not possible anymore to change file permissions, so we + have to set permissions prior to possibly giving files away. */ + + set_mode (file_name, stat_info, cur_info, + invert_permissions, permstatus, typeflag); + } + + if (0 < same_owner_option && permstatus != INTERDIR_PERMSTATUS) { - (void) umask (ourmask); /* Set it back how it was */ - notumask = ~ourmask; /* Make umask override permissions */ + /* When lchown exists, it should be used to change the attributes of + 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. */ + + if (typeflag == SYMTYPE) + { +#if HAVE_LCHOWN + if (lchown (file_name, stat_info->st_uid, stat_info->st_gid) < 0) + chown_error_details (file_name, + stat_info->st_uid, stat_info->st_gid); +#endif + } + else + { + if (chown (file_name, stat_info->st_uid, stat_info->st_gid) < 0) + chown_error_details (file_name, + stat_info->st_uid, stat_info->st_gid); + + /* On a few systems, and in particular, those allowing to give files + away, changing the owner or group destroys the suid or sgid bits. + So let's attempt setting these bits once more. */ + if (stat_info->st_mode & (S_ISUID | S_ISGID | S_ISVTX)) + set_mode (file_name, stat_info, 0, + invert_permissions, permstatus, typeflag); + } } } +/* Remember to restore stat attributes (owner, group, mode and times) + for the directory FILE_NAME, using information given in *STAT_INFO, + 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. */ +static void +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 (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; +} -/* - * Extract a file from the archive. - */ -void -extract_archive () +/* Update the delayed_set_stat info for an intermediate 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, + struct stat const *dir_stat_info) { - register char *data; - int fd, check, namelen, written, openflag; - long size; - struct utimbuf acc_upd_times; - register int skipcrud; - register int i; - /* int sparse_ind = 0;*/ - union record *exhdr; - struct saved_dir_info *tmp; - /* int end_nulls; */ - - saverec (&head); /* Make sure it sticks around */ - userec (head); /* And go past it in the archive */ - decode_header (head, &hstat, &head_standard, 1); /* Snarf fields */ - - if (f_confirm && !confirm ("extract", current_file_name)) + struct delayed_set_stat *data; + for (data = delayed_set_stat_head; data; data = data->next) { - if (head->header.isextended) - skip_extended_headers (); - skip_file ((long) hstat.st_size); - saverec ((union record **) 0); - return; + struct stat st; + if (stat (data->file_name, &st) != 0) + { + stat_error (data->file_name); + return; + } + + 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->invert_permissions = + (MODE_RWX & (current_stat_info.stat.st_mode ^ st.st_mode)); + data->permstatus = ARCHIVED_PERMSTATUS; + return; + } } - /* Print the record from 'head' and 'hstat' */ - if (f_verbose) - print_header (); - - /* - * Check for fully specified pathnames and other atrocities. - * - * Note, we can't just make a pointer to the new file name, - * since saverec() might move the header and adjust "head". - * We have to start from "head" every time we want to touch - * the header record. - */ - skipcrud = 0; - while (!f_absolute_paths - && '/' == current_file_name[skipcrud]) + 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. */ +static int +make_directories (char *file_name) +{ + 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 mode; + int invert_permissions; + int status; + + + for (cursor = cursor0; *cursor; cursor++) { - static int warned_once = 0; + if (! ISSLASH (*cursor)) + continue; - skipcrud++; /* Force relative path */ - if (!warned_once++) + /* Avoid mkdir of empty string, if leading or double '/'. */ + + if (cursor == cursor0 || ISSLASH (cursor[-1])) + continue; + + /* Avoid mkdir where last part of file name is "." or "..". */ + + if (cursor[-1] == '.' + && (cursor == cursor0 + 1 || ISSLASH (cursor[-2]) + || (cursor[-2] == '.' + && (cursor == cursor0 + 2 || ISSLASH (cursor[-3]))))) + continue; + + *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); + + if (status == 0) { - msg ("Removing leading / from absolute path names in the archive."); + /* Create a struct delayed_set_stat even if + 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 */, + invert_permissions, INTERDIR_PERMSTATUS); + + print_for_mkdir (file_name, cursor - file_name, mode); + did_something = 1; + + *cursor = '/'; + continue; } + + *cursor = '/'; + + 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; + } + + return did_something; /* tell them to retry if we made one */ +} + +static bool +file_newer_p (const char *file_name, struct tar_stat_info *tar_stat) +{ + struct stat st; + + if (stat (file_name, &st)) + { + stat_warn (file_name); + /* Be on the safe side: if the file does exist assume it is newer */ + return errno != ENOENT; } + 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. */ +static int +maybe_recoverable (char *file_name, int *interdir_made) +{ + int e = errno; + + if (*interdir_made) + return 0; - switch (head->header.linkflag) + switch (errno) { + case EEXIST: + /* Remove an old file, if the options allow this. */ - default: - msg ("Unknown file type '%c' for %s, extracted as normal file", - head->header.linkflag, skipcrud + current_file_name); - /* FALL THRU */ - - /* - * 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. - */ - case LF_SPARSE: - sp_array_size = 10; - sparsearray = (struct sp_array *) malloc (sp_array_size * sizeof (struct sp_array)); - for (i = 0; i < SPARSE_IN_HDR; i++) + switch (old_files_option) { - sparsearray[i].offset = - from_oct (1 + 12, head->header.sp[i].offset); - sparsearray[i].numbytes = - from_oct (1 + 12, head->header.sp[i].numbytes); - if (!sparsearray[i].numbytes) - break; - } + 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, ORDINARY_REMOVE_OPTION); + errno = EEXIST; + return r; + } - /* end_nulls = from_oct(1+12, head->header.ending_blanks);*/ + case UNLINK_FIRST_OLD_FILES: + break; + } - if (head->header.isextended) + case ENOENT: + /* Attempt creating missing intermediate directories. */ + if (! make_directories (file_name)) { - /* read in the list of extended headers - and translate them into the sparsearray - as before */ + errno = ENOENT; + return 0; + } + *interdir_made = 1; + return 1; + + default: + /* Just say we can't do anything about it... */ + + return 0; + } +} + +/* 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 +apply_nonancestor_delayed_set_stat (char const *file_name, bool after_symlinks) +{ + size_t file_name_len = strlen (file_name); + bool check_for_renamed_directories = 0; - /* static */ int ind = SPARSE_IN_HDR; + while (delayed_set_stat_head) + { + 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; - for (;;) + if (check_for_renamed_directories) + { + 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; + } + } - exhdr = findrec (); - for (i = 0; i < SPARSE_EXT_HDR; i++) - { + if (! skip_this_one) + set_stat (data->file_name, &data->stat_info, cur_info, + data->invert_permissions, data->permstatus, DIRTYPE); - if (i + ind > sp_array_size - 1) - { - /* - * realloc the scratch area - * since we've run out of room -- - */ - sparsearray = (struct sp_array *) - realloc (sparsearray, - 2 * sp_array_size * (sizeof (struct sp_array))); - sp_array_size *= 2; - } - if (!exhdr->ext_hdr.sp[i].numbytes) - break; - sparsearray[i + ind].offset = - from_oct (1 + 12, exhdr->ext_hdr.sp[i].offset); - sparsearray[i + ind].numbytes = - from_oct (1 + 12, exhdr->ext_hdr.sp[i].numbytes); + 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)) + { + struct stat st; + if (stat (file_name, &st) == 0) + { + if (interdir_made) + { + repair_delayed_set_stat (file_name, &st); + return 0; } - if (!exhdr->ext_hdr.isextended) - break; - else + if (S_ISDIR (st.st_mode)) { - ind += SPARSE_EXT_HDR; - userec (exhdr); + mode = st.st_mode & ~ current_umask; + break; } } - userec (exhdr); + errno = EEXIST; } - /* FALL THRU */ - case LF_OLDNORMAL: - case LF_NORMAL: - case LF_CONTIG: - /* - * Appears to be a file. - * See if it's really a directory. - */ - namelen = strlen (skipcrud + current_file_name) - 1; - if (current_file_name[skipcrud + namelen] == '/') - goto really_dir; - - /* FIXME, deal with protection issues */ - again_file: - openflag = (f_keep ? - O_BINARY | O_NDELAY | O_WRONLY | O_CREAT | O_EXCL : - O_BINARY | O_NDELAY | O_WRONLY | O_CREAT | O_TRUNC) - | ((head->header.linkflag == LF_SPARSE) ? 0 : O_APPEND); - /* - * JK - The last | is a kludge to solve the problem - * the O_APPEND flag causes with files we are - * trying to make sparse: when a file is opened - * with O_APPEND, it writes to the last place - * that something was written, thereby ignoring - * any lseeks that we have done. We add this - * extra condition to make it able to lseek when - * a file is sparse, i.e., we don't open the new - * file with this flag. (Grump -- this bug caused - * me to waste a good deal of time, I might add) - */ - - if (f_exstdout) + if (maybe_recoverable (file_name, &interdir_made)) + continue; + + if (errno != EEXIST) { - fd = 1; - goto extract_file; + mkdir_error (file_name); + return 1; } -#ifdef O_CTG - /* - * Contiguous files (on the Masscomp) have to specify - * the size in the open call that creates them. - */ - if (head->header.linkflag == LF_CONTIG) - fd = open ((longname ? longname : head->header.name) - + skipcrud, - openflag | O_CTG, - hstat.st_mode, hstat.st_size); - else -#endif + break; + } + + if (status == 0 + || old_files_option == DEFAULT_OLD_FILES + || old_files_option == OVERWRITE_OLD_FILES) + delay_set_stat (file_name, ¤t_stat_info.stat, + MODE_RWX & (mode ^ current_stat_info.stat.st_mode), + (status == 0 + ? ARCHIVED_PERMSTATUS + : UNKNOWN_PERMSTATUS)); + + return status; +} + + +static int +open_output_file (char *file_name, int typeflag) +{ + 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 + the open call that creates them. */ + + if (typeflag == CONTTYPE) + fd = open (file_name, openflag | O_CTG, mode, current_stat_info.stat.st_size); + else + fd = open (file_name, openflag, mode); + +#else /* not O_CTG */ + if (typeflag == CONTTYPE) + { + static int conttype_diagnosed; + + if (!conttype_diagnosed) { -#ifdef NO_OPEN3 - /* - * On raw V7 we won't let them specify -k (f_keep), but - * we just bull ahead and create the files. - */ - fd = creat ((longname - ? longname - : head->header.name) + skipcrud, - hstat.st_mode); -#else - /* - * With 3-arg open(), we can do this up right. - */ - fd = open (skipcrud + current_file_name, - openflag, hstat.st_mode); -#endif + conttype_diagnosed = 1; + WARN ((0, 0, _("Extracting contiguous files as regular files"))); } + } + fd = open (file_name, openflag, mode); +#endif /* not O_CTG */ + + 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; + + /* FIXME: deal with protection issues. */ + + if (to_stdout_option) + fd = STDOUT_FILENO; + else if (to_command_option) + { + fd = sys_exec_command (file_name, 'f', ¤t_stat_info); if (fd < 0) { - if (make_dirs (skipcrud + current_file_name)) - goto again_file; - msg_perror ("Could not create file %s", - skipcrud + current_file_name); - if (head->header.isextended) - skip_extended_headers (); - skip_file ((long) hstat.st_size); - goto quit; + skip_member (); + return 0; } + } + else + { + do + fd = open_output_file (file_name, typeflag); + while (fd < 0 && maybe_recoverable (file_name, &interdir_made)); - extract_file: - if (head->header.linkflag == LF_SPARSE) + if (fd < 0) { - char *name; - int namelen; - - /* - * 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. - */ - namelen = strlen (skipcrud + current_file_name); - name = (char *) malloc ((sizeof (char)) * namelen); - bcopy (skipcrud + current_file_name, name, namelen); - size = hstat.st_size; - extract_sparse_file (fd, &size, hstat.st_size, name); + open_error (file_name); + return 1; } - else - for (size = hstat.st_size; - size > 0; - size -= written) + } + + 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; + } + + /* Locate data, determine max length writeable, write it, + block that we have used the data, then check if the write + worked. */ - /* long offset, - numbytes;*/ - - if (f_multivol) - { - save_name = current_file_name; - save_totsize = hstat.st_size; - save_sizeleft = size; - } - - /* - * Locate data, determine max length - * writeable, write it, record that - * we have used the data, then check - * if the write worked. - */ - data = findrec ()->charptr; - if (data == NULL) - { /* Check it... */ - msg ("Unexpected EOF on archive file"); - break; - } - /* - * JK - If the file is sparse, use the sparsearray - * that we created before to lseek into the new - * file the proper amount, and to see how many - * bytes we want to write at that position. - */ - /* if (head->header.linkflag == LF_SPARSE) { - off_t pos; - - pos = lseek(fd, (off_t) sparsearray[sparse_ind].offset, 0); - printf("%d at %d\n", (int) pos, sparse_ind); - written = sparsearray[sparse_ind++].numbytes; - } else*/ - written = endofrecs ()->charptr - data; - if (written > size) - written = size; - errno = 0; - check = write (fd, data, written); - /* - * The following is in violation of strict - * typing, since the arg to userec - * should be a struct rec *. FIXME. - */ - userec ((union record *) (data + written - 1)); - if (check == written) - continue; - /* - * Error in writing to file. - * Print it, skip to next file in archive. - */ - if (check < 0) - msg_perror ("couldn't write to file %s", - skipcrud + current_file_name); - else - msg ("could only write %d of %d bytes to file %s", - written, check, skipcrud + current_file_name); - skip_file ((long) (size - written)); - break; /* Still do the close, mod time, chmod, etc */ + data_block = find_next_block (); + if (! data_block) + { + ERROR ((0, 0, _("Unexpected EOF in archive"))); + break; /* FIXME: What happens, then? */ } - if (f_multivol) - save_name = 0; + written = available_space_after (data_block); - /* 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 */ - if (f_exstdout) - break; + if (written > size) + written = size; + errno = 0; + count = full_write (fd, data_block->buffer, written); + size -= written; - /* if (head->header.isextended) { - register union record *exhdr; - register int i; + 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; + } + } - for (i = 0; i < 21; i++) { - long offset; + skip_file (size); - if (!exhdr->ext_hdr.sp[i].numbytes) - break; - offset = from_oct(1+12, - exhdr->ext_hdr.sp[i].offset); - written = from_oct(1+12, - exhdr->ext_hdr.sp[i].numbytes); - lseek(fd, offset, 0); - check = write(fd, data, written); - if (check == written) continue; + if (multi_volume_option) + assign_string (&save_name, 0); - } + /* 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. */ + if (to_stdout_option) + return 0; - }*/ - check = close (fd); - if (check < 0) - { - msg_perror ("Error while closing %s", - skipcrud + current_file_name); - } + status = close (fd); + if (status < 0) + close_error (file_name); + if (to_command_option) + sys_wait_command (); + else + set_stat (file_name, ¤t_stat_info.stat, 0, 0, + (old_files_option == OVERWRITE_OLD_FILES ? + UNKNOWN_PERMSTATUS : ARCHIVED_PERMSTATUS), + typeflag); - set_filestat: + return status; +} + +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; - /* - * If we are root, set the owner and group of the extracted - * file. This does what is wanted both on real Unix and on - * System V. If we are running as a user, we extract as that - * user; if running as root, we extract as the original owner. - */ - if (we_are_root || f_do_chown) + do + { + struct stat st1, st2; + int e; + int status = link (link_name, file_name); + e = errno; + + if (status == 0) { - if (chown (skipcrud + current_file_name, - hstat.st_uid, hstat.st_gid) < 0) - { - msg_perror ("cannot chown file %s to uid %d gid %d", - skipcrud + current_file_name, - hstat.st_uid, hstat.st_gid); - } + 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; + + errno = e; + } + while (maybe_recoverable (file_name, &interdir_made)); + + if (!(incremental_option && errno == EEXIST)) + { + link_error (link_name, file_name); + return 1; + } + return 0; +} + +static int +extract_symlink (char *file_name, int typeflag) +{ +#ifdef HAVE_SYMLINK + int status, fd; + int interdir_made = 0; + + if (absolute_names_option + || ! (IS_ABSOLUTE_FILE_NAME (current_stat_info.link_name) + || contains_dot_dot (current_stat_info.link_name))) + { + while ((status = symlink (current_stat_info.link_name, file_name))) + if (!maybe_recoverable (file_name, &interdir_made)) + break; + + 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; + + while ((fd = open (file_name, O_WRONLY | O_CREAT | O_EXCL, 0)) < 0) + if (! maybe_recoverable (file_name, &interdir_made)) + break; - /* - * Set the modified time of the file. - * - * Note that we set the accessed time to "now", which - * is really "the time we started extracting files". - * unless f_gnudump is used, in which case .st_atime is used - */ - if (!f_modified) + status = -1; + if (fd < 0) + open_error (file_name); + else if (fstat (fd, &st) != 0) { - /* fixme if f_gnudump should set ctime too, but how? */ - if (f_gnudump) - acc_upd_times.actime = hstat.st_atime; - else - acc_upd_times.actime = now; /* Accessed now */ - acc_upd_times.modtime = hstat.st_mtime; /* Mod'd */ - if (utime (skipcrud + current_file_name, - &acc_upd_times) < 0) - { - msg_perror ("couldn't change access and modification times of %s", skipcrud + current_file_name); - } + stat_error (file_name); + close (fd); } - /* We do the utime before the chmod because some versions of - utime are broken and trash the modes of the file. Since - we then change the mode anyway, we don't care. . . */ - - /* - * If '-k' is not set, open() or creat() could have saved - * the permission bits from a previously created file, - * ignoring the ones we specified. - * Even if -k is set, if the file has abnormal - * mode bits, we must chmod since writing or chown() has - * probably reset them. - * - * If -k is set, we know *we* created this file, so the mode - * bits were set by our open(). If the file is "normal", we - * skip the chmod. This works because we did umask(0) if -p - * is set, so umask will have left the specified mode alone. - */ - if ((!f_keep) - || (hstat.st_mode & (S_ISUID | S_ISGID | S_ISVTX))) + else if (close (fd) != 0) + close_error (file_name); + else { - if (chmod (skipcrud + current_file_name, - notumask & (int) hstat.st_mode) < 0) + 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)) { - msg_perror ("cannot change mode of file %s to %ld", - skipcrud + current_file_name, - notumask & (int) hstat.st_mode); + 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; } + } - quit: - break; + return status; - case LF_LINK: - again_link: - { - struct stat st1, st2; +#else + static int warned_once; + + if (!warned_once) + { + warned_once = 1; + WARN ((0, 0, _("Attempting extraction of symbolic links as hard links"))); + } + return extract_link (file_name, typeflag); +#endif +} - check = link (current_link_name, skipcrud + current_file_name); +#if S_IFCHR || S_IFBLK +static int +extract_node (char *file_name, int typeflag) +{ + int status; + int interdir_made = 0; + + do + status = mknod (file_name, current_stat_info.stat.st_mode, + current_stat_info.stat.st_rdev); + while (status && maybe_recoverable (file_name, &interdir_made)); + + if (status != 0) + mknod_error (file_name); + else + set_stat (file_name, ¤t_stat_info.stat, 0, 0, ARCHIVED_PERMSTATUS, typeflag); + return status; +} +#endif - if (check == 0) - break; - if (make_dirs (skipcrud + current_file_name)) - goto again_link; - if (f_gnudump && errno == EEXIST) - break; - if (stat (current_link_name, &st1) == 0 - && stat (current_file_name + skipcrud, &st2) == 0 - && st1.st_dev == st2.st_dev - && st1.st_ino == st2.st_ino) - break; - msg_perror ("Could not link %s to %s", - skipcrud + current_file_name, - current_link_name); - } - break; +#if HAVE_MKFIFO || defined mkfifo +static int +extract_fifo (char *file_name, int typeflag) +{ + int status; + int interdir_made = 0; -#ifdef S_ISLNK - case LF_SYMLINK: - again_symlink: - check = symlink (current_link_name, - skipcrud + current_file_name); - /* FIXME, don't worry uid, gid, etc... */ - if (check == 0) - break; - if (make_dirs (current_file_name + skipcrud)) - goto again_symlink; - msg_perror ("Could not create symlink to %s", - current_link_name); + while ((status = mkfifo (file_name, current_stat_info.stat.st_mode))) + if (!maybe_recoverable (file_name, &interdir_made)) break; -#endif -#ifdef S_IFCHR - case LF_CHR: - hstat.st_mode |= S_IFCHR; - goto make_node; + if (status == 0) + set_stat (file_name, ¤t_stat_info.stat, NULL, 0, + ARCHIVED_PERMSTATUS, typeflag); + else + mkfifo_error (file_name); + return status; +} #endif -#ifdef S_IFBLK - case LF_BLK: - hstat.st_mode |= S_IFBLK; -#endif -#if defined(S_IFCHR) || defined(S_IFBLK) - make_node: - check = mknod (current_file_name + skipcrud, - (int) hstat.st_mode, (int) hstat.st_rdev); - if (check != 0) - { - if (make_dirs (skipcrud + current_file_name)) - goto make_node; - msg_perror ("Could not make %s", - current_file_name + skipcrud); - break; - }; - goto set_filestat; -#endif +static int +extract_mangle_wrapper (char *file_name, int typeflag) +{ + extract_mangle (); + return 0; +} -#ifdef S_ISFIFO - /* If local system doesn't support FIFOs, use default case */ - case LF_FIFO: - make_fifo: - check = mkfifo (current_file_name + skipcrud, - (int) hstat.st_mode); - if (check != 0) - { - if (make_dirs (current_file_name + skipcrud)) - goto make_fifo; - msg_perror ("Could not make %s", - skipcrud + current_file_name); - break; - }; - goto set_filestat; -#endif - case LF_DIR: - case LF_DUMPDIR: - namelen = strlen (current_file_name) + skipcrud - 1; - really_dir: - /* Check for trailing /, and zap as many as we find. */ - while (namelen - && current_file_name[skipcrud + namelen] == '/') - current_file_name[skipcrud + namelen--] = '\0'; - if (f_gnudump) - { /* Read the entry and delete files - that aren't listed in the archive */ - gnu_restore (skipcrud); +static int +extract_failure (char *file_name, int typeflag) +{ + return 1; +} - } - else if (head->header.linkflag == LF_DUMPDIR) - skip_file ((long) (hstat.st_size)); +typedef int (*tar_extractor_t) (char *file_name, int typeflag); + - again_dir: - check = mkdir (skipcrud + current_file_name, - (we_are_root ? 0 : 0300) | (int) hstat.st_mode); - if (check != 0) - { - struct stat st1; - - if (make_dirs (skipcrud + current_file_name)) - goto again_dir; - /* If we're trying to create '.', let it be. */ - if (current_file_name[skipcrud + namelen] == '.' && - (namelen == 0 || - current_file_name[skipcrud + namelen - 1] == '/')) - goto check_perms; - if (errno == EEXIST - && stat (skipcrud + current_file_name, &st1) == 0 - && (S_ISDIR (st1.st_mode))) - break; - msg_perror ("Could not create directory %s", skipcrud + current_file_name); - break; - } +/* Prepare to extract a file. Find extractor function. + Return zero if extraction should not proceed. */ - check_perms: - if (!we_are_root && 0300 != (0300 & (int) hstat.st_mode)) - { - hstat.st_mode |= 0300; - msg ("Added write and execute permission to directory %s", - skipcrud + current_file_name); - } +static int +prepare_to_extract (char const *file_name, int typeflag, tar_extractor_t *fun) +{ + int rc = 1; + + if (EXTRACT_OVER_PIPE) + rc = 0; - if (f_modified) - goto set_filestat; - tmp = (struct saved_dir_info *) malloc (sizeof (struct saved_dir_info)); - tmp->path = malloc (strlen (skipcrud + current_file_name) + 1); - strcpy (tmp->path, skipcrud + current_file_name); - tmp->mode = hstat.st_mode; - tmp->atime = hstat.st_atime; - tmp->mtime = hstat.st_mtime; - tmp->next = saved_dir_info_head; - saved_dir_info_head = tmp; - case LF_VOLHDR: - if (f_verbose) + /* Select the extractor */ + switch (typeflag) + { + case GNUTYPE_SPARSE: + *fun = extract_file; + rc = 1; + break; + + 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 { - printf ("Reading %s\n", current_file_name); + *fun = extract_file; + rc = 1; } break; - case LF_NAMES: - extract_mangle (head); + case SYMTYPE: + *fun = extract_symlink; break; - case LF_MULTIVOL: - msg ("Can't extract '%s'--file is continued from another volume\n", current_file_name); - skip_file ((long) hstat.st_size); + case LNKTYPE: + *fun = extract_link; break; - case LF_LONGNAME: - case LF_LONGLINK: - msg ("Visible long name error\n"); - skip_file ((long) hstat.st_size); +#if S_IFCHR + case CHRTYPE: + current_stat_info.stat.st_mode |= S_IFCHR; + *fun = extract_node; break; - } +#endif - /* We don't need to save it any longer. */ - saverec ((union record **) 0);/* Unsave it */ -} +#if S_IFBLK + case BLKTYPE: + current_stat_info.stat.st_mode |= S_IFBLK; + *fun = extract_node; + break; +#endif -/* - * After a file/link/symlink/dir creation has failed, see if - * it's because some required directory was not present, and if - * so, create all required dirs. - */ -int -make_dirs (pathname) - char *pathname; -{ - char *p; /* Points into path */ - int madeone = 0; /* Did we do anything yet? */ - int save_errno = errno; /* Remember caller's errno */ - int check; +#if HAVE_MKFIFO || defined mkfifo + case FIFOTYPE: + *fun = extract_fifo; + break; +#endif + + case DIRTYPE: + case GNUTYPE_DUMPDIR: + *fun = extract_dir; + 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; + break; + + case GNUTYPE_MULTIVOL: + ERROR ((0, 0, + _("%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, _("Unexpected long name header"))); + *fun = extract_failure; + break; - if (errno != ENOENT) - return 0; /* Not our problem */ + default: + WARN ((0, 0, + _("%s: Unknown file type `%c', extracted as normal file"), + quotearg_colon (file_name), typeflag)); + *fun = extract_file; + } + + /* Determine whether the extraction should proceed */ + if (rc == 0) + return 0; - for (p = index (pathname, '/'); p != NULL; p = index (p + 1, '/')) + switch (old_files_option) { - /* Avoid mkdir of empty string, if leading or double '/' */ - if (p == pathname || p[-1] == '/') - continue; - /* Avoid mkdir where last part of path is '.' */ - if (p[-1] == '.' && (p == pathname + 1 || p[-2] == '/')) - continue; - *p = 0; /* Truncate the path there */ - check = mkdir (pathname, 0777); /* Try to create it as a dir */ - if (check == 0) + case UNLINK_FIRST_OLD_FILES: + if (!remove_any_file (file_name, + recursive_unlink_option ? RECURSIVE_REMOVE_OPTION + : ORDINARY_REMOVE_OPTION) + && errno && errno != ENOENT) { - /* Fix ownership */ - if (we_are_root) - { - if (chown (pathname, hstat.st_uid, - hstat.st_gid) < 0) - { - msg_perror ("cannot change owner of %s to uid %d gid %d", pathname, hstat.st_uid, hstat.st_gid); - } - } - pr_mkdir (pathname, p - pathname, notumask & 0777); - madeone++; /* Remember if we made one */ - *p = '/'; - continue; + unlink_error (file_name); + return 0; } - *p = '/'; - if (errno == EEXIST) /* Directory already exists */ - continue; - /* - * Some other error in the mkdir. We return to the caller. - */ + 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; } - errno = save_errno; /* Restore caller's errno */ - return madeone; /* Tell them to retry if we made one */ + return 1; } +/* Extract a file from the archive. */ void -extract_sparse_file (fd, sizeleft, totalsize, name) - int fd; - long *sizeleft, totalsize; - char *name; +extract_archive (void) { - /* register char *data;*/ - union record *datarec; - int sparse_ind = 0; - int written, count; + char typeflag; + char *file_name; + tar_extractor_t fun; + + set_next_block_after (current_header); + decode_header (current_header, ¤t_stat_info, ¤t_format, 1); - /* assuming sizeleft is initially totalsize */ + if (interactive_option && !confirm ("extract", current_stat_info.file_name)) + { + skip_member (); + return; + } + /* Print the block from current_header and current_stat. */ - while (*sizeleft > 0) + 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) { - datarec = findrec (); - if (datarec == NULL) + size_t prefix_len = stripped_prefix_len (file_name, strip_name_components); + if (prefix_len == (size_t) -1) { - msg ("Unexpected EOF on archive file"); + skip_member (); return; } - lseek (fd, sparsearray[sparse_ind].offset, 0); - written = sparsearray[sparse_ind++].numbytes; - while (written > RECORDSIZE) - { - count = write (fd, datarec->charptr, RECORDSIZE); - if (count < 0) - msg_perror ("couldn't write to file %s", name); - written -= count; - *sizeleft -= count; - userec (datarec); - datarec = findrec (); - } + file_name += prefix_len; + } - count = write (fd, datarec->charptr, written); + apply_nonancestor_delayed_set_stat (file_name, 0); - if (count < 0) - { - msg_perror ("couldn't write to file %s", name); - } - else if (count != written) - { - msg ("could only write %d of %d bytes to file %s", totalsize - *sizeleft, totalsize, name); - skip_file ((long) (*sizeleft)); - } + /* 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; + } - written -= count; - *sizeleft -= count; - userec (datarec); + /* 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 (); } - free (sparsearray); - /* if (end_nulls) { - register int i; - - printf("%d\n", (int) end_nulls); - for (i = 0; i < end_nulls; i++) - write(fd, "\000", 1); - }*/ - userec (datarec); + else + skip_member (); + } -/* Set back the utime and mode for all the extracted directories. */ -void -restore_saved_dir_info () +/* Extract the symbolic links whose final extraction were delayed. */ +static void +apply_delayed_symlinks (void) { - struct utimbuf acc_upd_times; + struct delayed_symlink *ds; - while (saved_dir_info_head != NULL) + for (ds = delayed_symlink_head; ds; ) { - /* fixme if f_gnudump should set ctime too, but how? */ - if (f_gnudump) - acc_upd_times.actime = saved_dir_info_head->atime; - else - acc_upd_times.actime = now; /* Accessed now */ - acc_upd_times.modtime = saved_dir_info_head->mtime; /* Mod'd */ - if (utime (saved_dir_info_head->path, &acc_upd_times) < 0) - { - msg_perror ("couldn't change access and modification times of %s", - saved_dir_info_head->path); - } - if ((!f_keep) || (saved_dir_info_head->mode & (S_ISUID | S_ISGID | S_ISVTX))) + struct string_list *sources = ds->sources; + char const *valid_source = 0; + + for (sources = ds->sources; sources; sources = sources->next) { - if (chmod (saved_dir_info_head->path, - notumask & saved_dir_info_head->mode) < 0) + 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) { - msg_perror ("cannot change mode of file %s to %ld", - saved_dir_info_head->path, - notumask & saved_dir_info_head->mode); + /* 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); + } } } - saved_dir_info_head = saved_dir_info_head->next; + + 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) +{ + 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 (); }