X-Git-Url: https://git.dogcows.com/gitweb?a=blobdiff_plain;f=src%2Fextract.c;h=80f2f9ceec68d1616d311680c6537e30cb83b348;hb=2f800ce3fb9c38360e1b6dac70ee0d8ceae72c6a;hp=1fe4276e352cdbee0be62c674b1a5aaf2c884c56;hpb=9ae5a981fc0aae7307578ee82e9f65416fe09da4;p=chaz%2Ftar diff --git a/src/extract.c b/src/extract.c index 1fe4276..80f2f9c 100644 --- a/src/extract.c +++ b/src/extract.c @@ -1,7 +1,7 @@ /* Extract files from a tar archive. Copyright (C) 1988, 1992, 1993, 1994, 1996, 1997, 1998, 1999, 2000, - 2001, 2003, 2004 Free Software Foundation, Inc. + 2001, 2003, 2004, 2005 Free Software Foundation, Inc. Written by John Gilmore, on 1985-11-19. @@ -17,26 +17,17 @@ 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 #include -#if HAVE_UTIME_H -# include -#else -struct utimbuf - { - long actime; - long modtime; - }; -#endif - #include "common.h" -bool 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) */ @@ -57,8 +48,8 @@ enum permstatus /* 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 + represents an element where AFTER_LINKS is nonzero and [^D] + represents an element where AFTER_LINKS 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. */ @@ -70,28 +61,31 @@ struct delayed_set_stat size_t file_name_len; mode_t invert_permissions; enum permstatus permstatus; - bool after_symlinks; + bool after_links; 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 +/* List of links whose creation we have delayed. */ +struct delayed_link { - /* The next delayed symbolic link in the list. */ - struct delayed_symlink *next; + /* The next delayed link in the list. */ + struct delayed_link *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. */ + /* True if the link is symbolic. */ + bool is_symlink; + + /* The desired owner and group of the link, if it is a symlink. */ uid_t uid; gid_t gid; - /* A list of sources for this symlink. The sources are all to be + /* A list of sources for this link. The sources are all to be hard-linked together. */ struct string_list *sources; @@ -99,7 +93,7 @@ struct delayed_symlink char target[1]; }; -static struct delayed_symlink *delayed_symlink_head; +static struct delayed_link *delayed_link_head; struct string_list { @@ -114,13 +108,12 @@ extr_init (void) we_are_root = geteuid () == 0; same_permissions_option += we_are_root; same_owner_option += we_are_root; - xalloc_fail_func = extract_finish; /* 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; + struct stat st; char *dir = xgetcwd (); if (deref_stat (true, dir, &st)) @@ -128,7 +121,7 @@ extr_init (void) 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. */ @@ -202,15 +195,30 @@ set_mode (char const *file_name, /* Check time after successfully setting FILE_NAME's time stamp to T. */ static void -check_time (char const *file_name, time_t t) +check_time (char const *file_name, struct timespec t) { - time_t now; - if (t <= 0) + if (t.tv_sec <= 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))); + file_name, tartime (t, true))); + else if (timespec_cmp (start_time, t) < 0) + { + struct timespec now; + gettime (&now); + if (timespec_cmp (now, t) < 0) + { + unsigned long int ds = t.tv_sec - now.tv_sec; + int dns = t.tv_nsec - now.tv_nsec; + char dnsbuf[sizeof ".FFFFFFFFF"]; + if (dns < 0) + { + 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)); + } + } } /* Restore stat attributes (owner, group, mode and times) for @@ -234,8 +242,6 @@ set_stat (char const *file_name, mode_t invert_permissions, enum permstatus permstatus, char typeflag) { - struct utimbuf utimbuf; - if (typeflag != SYMTYPE) { /* We do the utime before the chmod because some versions of utime are @@ -249,19 +255,16 @@ set_stat (char const *file_name, /* FIXME: incremental_option should set ctime too, but how? */ - if (incremental_option) - utimbuf.actime = stat_info->st_atime; - else - utimbuf.actime = start_time; - - utimbuf.modtime = stat_info->st_mtime; + struct timespec ts[2]; + ts[0] = incremental_option ? get_stat_atime (stat_info) : start_time; + ts[1] = get_stat_mtime (stat_info); - if (utime (file_name, &utimbuf) < 0) + if (utimens (file_name, ts) != 0) utime_error (file_name); else { - check_time (file_name, utimbuf.actime); - check_time (file_name, utimbuf.modtime); + check_time (file_name, ts[0]); + check_time (file_name, ts[1]); } } @@ -322,7 +325,7 @@ delay_set_stat (char const *file_name, struct stat const *stat_info, strcpy (data->file_name, file_name); data->invert_permissions = invert_permissions; data->permstatus = permstatus; - data->after_symlinks = 0; + data->after_links = 0; data->stat_info = *stat_info; data->next = delayed_set_stat_head; delayed_set_stat_head = data; @@ -361,7 +364,7 @@ repair_delayed_set_stat (char const *dir, quotearg_colon (dir))); } -/* After a file/link/symlink/directory creation has failed, see if +/* After a file/link/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. */ @@ -441,7 +444,8 @@ file_newer_p (const char *file_name, struct tar_stat_info *tar_stat) if (stat (file_name, &st)) { stat_warn (file_name); - return true; /* Be on the safe side */ + /* 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) @@ -451,41 +455,6 @@ file_newer_p (const char *file_name, struct tar_stat_info *tar_stat) return false; } -/* Prepare to extract a file. - Return zero if extraction should not proceed. */ - -static int -prepare_to_extract (char const *file_name) -{ - if (to_stdout_option) - return 0; - - switch (old_files_option) - { - case UNLINK_FIRST_OLD_FILES: - if (!remove_any_file (file_name, recursive_unlink_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"), file_name)); - return 0; - } - break; - - default: - break; - } - - return 1; -} - /* 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 @@ -520,7 +489,7 @@ maybe_recoverable (char *file_name, int *interdir_made) 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; } @@ -547,12 +516,12 @@ 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 + which are not ancestors of FILE_NAME. If AFTER_LINKS 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. */ + links are applied. */ static void -apply_nonancestor_delayed_set_stat (char const *file_name, bool after_symlinks) +apply_nonancestor_delayed_set_stat (char const *file_name, bool after_links) { size_t file_name_len = strlen (file_name); bool check_for_renamed_directories = 0; @@ -564,9 +533,9 @@ apply_nonancestor_delayed_set_stat (char const *file_name, bool after_symlinks) struct stat st; struct stat const *cur_info = 0; - check_for_renamed_directories |= data->after_symlinks; + check_for_renamed_directories |= data->after_links; - if (after_symlinks < data->after_symlinks + if (after_links < data->after_links || (data->file_name_len < file_name_len && file_name[data->file_name_len] && (ISSLASH (file_name[data->file_name_len]) @@ -601,540 +570,638 @@ apply_nonancestor_delayed_set_stat (char const *file_name, bool after_symlinks) } } -/* Extract a file from the archive. */ -void -extract_archive (void) + + +/* Extractor functions for various member types */ + +static int +extract_dir (char *file_name, int typeflag) { - union block *data_block; - int fd; int status; - size_t count; - size_t written; - int openflag; mode_t mode; - off_t size; int interdir_made = 0; - char typeflag; - char *file_name; - - 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)) - { - skip_member (); - return; - } - - /* Print the block from current_header and current_stat. */ + 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 (); - if (verbose_option) - print_header (¤t_stat_info, -1); + mode = (current_stat_info.stat.st_mode | (we_are_root ? 0 : MODE_WXUSR)) & MODE_RWX; - file_name = safer_name_suffix (current_stat_info.file_name, false); - if (strip_name_components) + while ((status = mkdir (file_name, mode))) { - size_t prefix_len = stripped_prefix_len (file_name, strip_name_components); - if (prefix_len == (size_t) -1) + if (errno == EEXIST + && (interdir_made + || old_files_option == DEFAULT_OLD_FILES + || old_files_option == OVERWRITE_OLD_FILES)) { - skip_member (); - return; + struct stat st; + if (stat (file_name, &st) == 0) + { + 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; } - file_name += prefix_len; - } - - apply_nonancestor_delayed_set_stat (file_name, 0); - - /* Take a safety backup of a previously existing file. */ - - if (backup_option && !to_stdout_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; - switch (typeflag) - { - case GNUTYPE_SPARSE: - /* Fall through. */ + if (maybe_recoverable (file_name, &interdir_made)) + continue; - case AREGTYPE: - case REGTYPE: - case CONTTYPE: + if (errno != EEXIST) + { + mkdir_error (file_name); + return 1; + } + break; + } - /* Appears to be a file. But BSD tar uses the convention that a slash - suffix means a directory. */ + 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)); - if (current_stat_info.had_trailing_slash) - goto really_dir; + return status; +} - /* FIXME: deal with protection issues. */ - again_file: - openflag = (O_WRONLY | O_BINARY | O_CREAT +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 = current_stat_info.stat.st_mode & MODE_RWX & ~ current_umask; - - if (to_stdout_option) - { - fd = STDOUT_FILENO; - goto extract_file; - } - - if (! prepare_to_extract (file_name)) - { - skip_member (); - if (backup_option) - undo_last_backup (); - break; - } + 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. */ + /* 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); + 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 (typeflag == CONTTYPE) + { + static int conttype_diagnosed; - if (!conttype_diagnosed) - { - conttype_diagnosed = 1; - WARN ((0, 0, _("Extracting contiguous files as regular files"))); - } + if (!conttype_diagnosed) + { + conttype_diagnosed = 1; + WARN ((0, 0, _("Extracting contiguous files as regular files"))); } - fd = open (file_name, openflag, mode); + } + 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 (maybe_recoverable (file_name, &interdir_made)) - goto again_file; - - open_error (file_name); skip_member (); - if (backup_option) - undo_last_backup (); - break; + return 0; } + } + else + { + do + fd = open_output_file (file_name, typeflag); + while (fd < 0 && maybe_recoverable (file_name, &interdir_made)); - extract_file: - if (current_stat_info.is_sparse) + if (fd < 0) { - sparse_extract_file (fd, ¤t_stat_info, &size); + open_error (file_name); + return 1; } - else - for (size = current_stat_info.stat.st_size; size > 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; ) + { + if (multi_volume_option) { - if (multi_volume_option) - { - assign_string (&save_name, current_stat_info.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. */ - - data_block = find_next_block (); - if (! data_block) - { - ERROR ((0, 0, _("Unexpected EOF in archive"))); - break; /* FIXME: What happens, then? */ - } - - written = available_space_after (data_block); - - if (written > size) - written = size; - errno = 0; - count = full_write (fd, data_block->buffer, written); - size -= count; - - set_next_block_after ((union block *) - (data_block->buffer + written - 1)); - if (count != written) - { - write_error_details (file_name, count, written); - break; - } + assign_string (&save_name, current_stat_info.orig_file_name); + save_totsize = current_stat_info.stat.st_size; + save_sizeleft = size; } - skip_file (size); + /* Locate data, determine max length writeable, write it, + block that we have used the data, then check if the write + worked. */ - if (multi_volume_option) - assign_string (&save_name, 0); + data_block = find_next_block (); + if (! data_block) + { + ERROR ((0, 0, _("Unexpected EOF in archive"))); + break; /* FIXME: What happens, then? */ + } - /* 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. */ + written = available_space_after (data_block); - if (to_stdout_option) - break; + if (written > size) + written = size; + errno = 0; + count = full_write (fd, data_block->buffer, written); + size -= written; - status = close (fd); - if (status < 0) - { - close_error (file_name); - if (backup_option) - undo_last_backup (); - } + 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; + } + } - set_stat (file_name, ¤t_stat_info.stat, 0, 0, - (old_files_option == OVERWRITE_OLD_FILES - ? UNKNOWN_PERMSTATUS - : ARCHIVED_PERMSTATUS), - typeflag); - break; + skip_file (size); - case SYMTYPE: -#ifdef HAVE_SYMLINK - if (! prepare_to_extract (file_name)) - break; + if (multi_volume_option) + assign_string (&save_name, 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), - status != 0) - if (!maybe_recoverable (file_name, &interdir_made)) - break; + /* 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 (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; + if (to_stdout_option) + return 0; - while (fd = open (file_name, O_WRONLY | O_CREAT | O_EXCL, 0), - fd < 0) - if (! maybe_recoverable (file_name, &interdir_made)) - break; + status = close (fd); + if (status < 0) + close_error (file_name); - status = -1; - if (fd < 0) - open_error (file_name); - else if (fstat (fd, &st) != 0) - { - stat_error (file_name); - close (fd); - } - else if (close (fd) != 0) - close_error (file_name); - else + 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); + + return status; +} + +/* Create a placeholder file with name FILE_NAME, which will be + replaced after other extraction is done by a symbolic link if + IS_SYMLINK is true, and by a hard link otherwise. Set + *INTERDIR_MADE if an intermediate directory is made in the + process. */ + +static int +create_placeholder_file (char *file_name, bool is_symlink, int *interdir_made) +{ + int fd; + struct stat st; + + while ((fd = open (file_name, O_WRONLY | O_CREAT | O_EXCL, 0)) < 0) + if (! maybe_recoverable (file_name, interdir_made)) + break; + + if (fd < 0) + open_error (file_name); + else if (fstat (fd, &st) != 0) + { + stat_error (file_name); + close (fd); + } + else if (close (fd) != 0) + close_error (file_name); + else + { + struct delayed_set_stat *h; + struct delayed_link *p = + xmalloc (offsetof (struct delayed_link, target) + + strlen (current_stat_info.link_name) + + 1); + p->next = delayed_link_head; + delayed_link_head = p; + p->dev = st.st_dev; + p->ino = st.st_ino; + p->mtime = st.st_mtime; + p->is_symlink = is_symlink; + if (is_symlink) + { + 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_links + && strncmp (file_name, h->file_name, h->file_name_len) == 0 + && ISSLASH (file_name[h->file_name_len]) + && (base_name (file_name) == file_name + h->file_name_len + 1)) + { + do { - 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)) + h->after_links = 1; + + if (stat (h->file_name, &st) != 0) + stat_error (h->file_name); + else { - 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); + h->stat_info.st_dev = st.st_dev; + h->stat_info.st_ino = st.st_ino; } - - status = 0; } + while ((h = h->next) && ! h->after_links); } - if (status != 0 && backup_option) - undo_last_backup (); + return 0; + } + + return -1; +} + +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 (! absolute_names_option && contains_dot_dot (link_name)) + return create_placeholder_file (file_name, false, &interdir_made); + + do + { + struct stat st1, st2; + int e; + int status = link (link_name, file_name); + e = errno; + + if (status == 0) + { + struct delayed_link *ds = delayed_link_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; + 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))) + return create_placeholder_file (file_name, true, &interdir_made); + + 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); + return status; + #else - { - static int warned_once; + static int warned_once; - if (!warned_once) - { - warned_once = 1; - WARN ((0, 0, - _("Attempting extraction of symbolic links as hard links"))); - } - } - typeflag = LNKTYPE; - /* Fall through. */ + if (!warned_once) + { + warned_once = 1; + WARN ((0, 0, _("Attempting extraction of symbolic links as hard links"))); + } + return extract_link (file_name, typeflag); #endif +} - case LNKTYPE: - if (! prepare_to_extract (file_name)) - break; +#if S_IFCHR || S_IFBLK +static int +extract_node (char *file_name, int typeflag) +{ + int status; + int interdir_made = 0; - again_link: - { - char const *link_name = safer_name_suffix (current_stat_info.link_name, - true); - struct stat st1, st2; - int e; + 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)); - /* MSDOS does not implement links. However, djgpp's link() actually - copies the file. */ - status = link (link_name, file_name); - e = errno; + 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 (status == 0) - { - 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; - } - break; - } +#if HAVE_MKFIFO || defined mkfifo +static int +extract_fifo (char *file_name, int typeflag) +{ + int status; + int interdir_made = 0; - 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)) - break; + while ((status = mkfifo (file_name, current_stat_info.stat.st_mode))) + if (!maybe_recoverable (file_name, &interdir_made)) + break; - errno = e; - if (maybe_recoverable (file_name, &interdir_made)) - goto again_link; + if (status == 0) + set_stat (file_name, ¤t_stat_info.stat, NULL, 0, + ARCHIVED_PERMSTATUS, typeflag); + else + mkfifo_error (file_name); + return status; +} +#endif - if (incremental_option && errno == EEXIST) - break; +static int +extract_mangle_wrapper (char *file_name, int typeflag) +{ + extract_mangle (); + return 0; +} - link_error (link_name, file_name); - if (backup_option) - undo_last_backup (); - } + +static int +extract_failure (char *file_name, int typeflag) +{ + return 1; +} + +typedef int (*tar_extractor_t) (char *file_name, int typeflag); + + + +/* Prepare to extract a file. Find extractor function. + Return zero if extraction should not proceed. */ + +static int +prepare_to_extract (char const *file_name, int typeflag, tar_extractor_t *fun) +{ + int rc = 1; + + if (EXTRACT_OVER_PIPE) + rc = 0; + + /* 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 + { + *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_info.stat.st_mode |= S_IFCHR; - goto make_node; + *fun = extract_node; + break; #endif #if S_IFBLK case BLKTYPE: current_stat_info.stat.st_mode |= S_IFBLK; -#endif - -#if S_IFCHR || S_IFBLK - make_node: - if (! prepare_to_extract (file_name)) - break; - - status = mknod (file_name, current_stat_info.stat.st_mode, - current_stat_info.stat.st_rdev); - if (status != 0) - { - if (maybe_recoverable (file_name, &interdir_made)) - goto make_node; - mknod_error (file_name); - if (backup_option) - undo_last_backup (); - break; - }; - set_stat (file_name, ¤t_stat_info.stat, 0, 0, - ARCHIVED_PERMSTATUS, typeflag); + *fun = extract_node; break; #endif #if HAVE_MKFIFO || defined mkfifo case FIFOTYPE: - if (! prepare_to_extract (file_name)) - break; - - while (status = mkfifo (file_name, current_stat_info.stat.st_mode), - status != 0) - if (!maybe_recoverable (file_name, &interdir_made)) - break; - - if (status == 0) - set_stat (file_name, ¤t_stat_info.stat, NULL, 0, - ARCHIVED_PERMSTATUS, typeflag); - else - { - mkfifo_error (file_name); - if (backup_option) - undo_last_backup (); - } + *fun = extract_fifo; break; #endif case DIRTYPE: case GNUTYPE_DUMPDIR: - really_dir: - if (incremental_option) - { - /* Read the entry and delete files that aren't listed in the - archive. */ - - purge_directory (file_name); - } - else if (typeflag == GNUTYPE_DUMPDIR) - skip_member (); - - mode = ((current_stat_info.stat.st_mode - | (we_are_root ? 0 : MODE_WXUSR)) - & MODE_RWX); - - status = prepare_to_extract (file_name); - if (status == 0) - break; - if (status < 0) - goto directory_exists; - - again_dir: - status = mkdir (file_name, mode); - - if (status != 0) - { - 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); - break; - } - if (S_ISDIR (st.st_mode)) - { - mode = st.st_mode & ~ current_umask; - goto directory_exists; - } - } - errno = EEXIST; - } - - if (maybe_recoverable (file_name, &interdir_made)) - goto again_dir; - - if (errno != EEXIST) - { - mkdir_error (file_name); - if (backup_option) - undo_last_backup (); - break; - } - } - - directory_exists: - 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)); + *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: - extract_mangle (); + *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))); - skip_member (); - if (backup_option) - undo_last_backup (); + *fun = extract_failure; break; case GNUTYPE_LONGNAME: case GNUTYPE_LONGLINK: - ERROR ((0, 0, _("Visible long name error"))); - skip_member (); - if (backup_option) - undo_last_backup (); + ERROR ((0, 0, _("Unexpected long name header"))); + *fun = extract_failure; break; default: WARN ((0, 0, - _("%s: Unknown file type '%c', extracted as normal file"), + _("%s: Unknown file type `%c', extracted as normal file"), quotearg_colon (file_name), typeflag)); - goto again_file; + *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; + } + + return 1; +} + +/* Extract a file from the archive. */ +void +extract_archive (void) +{ + 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); + + if (interactive_option && !confirm ("extract", current_stat_info.file_name)) + { + skip_member (); + return; + } + + /* Print the block from current_header and current_stat. */ + + if (verbose_option) + print_header (¤t_stat_info, -1); + + file_name = safer_name_suffix (current_stat_info.file_name, + false, absolute_names_option); + if (strip_name_components) + { + size_t prefix_len = stripped_prefix_len (file_name, strip_name_components); + if (prefix_len == (size_t) -1) + { + skip_member (); + return; + } + file_name += prefix_len; + } + + 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) +apply_delayed_links (void) { - struct delayed_symlink *ds; + struct delayed_link *ds; - for (ds = delayed_symlink_head; ds; ) + for (ds = delayed_link_head; ds; ) { struct string_list *sources = ds->sources; char const *valid_source = 0; @@ -1145,7 +1212,7 @@ apply_delayed_symlinks (void) struct stat st; /* Make sure the placeholder file is still there. If not, - don't create a symlink, as the placeholder was probably + don't create a link, as the placeholder was probably removed by a later extraction. */ if (lstat (source, &st) == 0 && st.st_dev == ds->dev @@ -1158,6 +1225,11 @@ apply_delayed_symlinks (void) unlink_error (source); else if (valid_source && link (valid_source, source) == 0) ; + else if (!ds->is_symlink) + { + if (link (ds->target, source) != 0) + link_error (ds->target, source); + } else if (symlink (ds->target, source) != 0) symlink_error (ds->target, source); else @@ -1178,13 +1250,13 @@ apply_delayed_symlinks (void) } { - struct delayed_symlink *next = ds->next; + struct delayed_link *next = ds->next; free (ds); ds = next; } } - delayed_symlink_head = 0; + delayed_link_head = 0; } /* Finish the extraction of an archive. */ @@ -1194,12 +1266,12 @@ 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 + /* Then, apply delayed links, so that they don't affect delayed directory status-setting for ordinary directories. */ - apply_delayed_symlinks (); + apply_delayed_links (); /* Finally, fix the status of directories that are ancestors - of delayed symlinks. */ + of delayed links. */ apply_nonancestor_delayed_set_stat ("", 1); } @@ -1210,3 +1282,10 @@ fatal_exit (void) error (TAREXIT_FAILURE, 0, _("Error is not recoverable: exiting now")); abort (); } + +void +xalloc_die (void) +{ + error (0, 0, "%s", _("memory exhausted")); + fatal_exit (); +}