X-Git-Url: https://git.dogcows.com/gitweb?a=blobdiff_plain;f=src%2Fextract.c;h=d1401b32dcee13b0bf458550349a213d0f040908;hb=998ecf08c0297c3a9de3e5f925e3a42ffa2011ca;hp=58a55886ff92e1581203214b0084ec687a121ac6;hpb=53f16a59713b32843f27332dbe807c06c7fc34bf;p=chaz%2Ftar diff --git a/src/extract.c b/src/extract.c index 58a5588..d1401b3 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 1988, 1992, 1993, 1994, 1996, 1997, 1998, 1999, 2000, + 2001 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 @@ -31,7 +34,7 @@ struct utimbuf #include "common.h" -static int we_are_root; /* true if our effective uid == 0 */ +int 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) */ @@ -65,17 +68,45 @@ struct delayed_set_stat 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) { we_are_root = geteuid () == 0; same_permissions_option += we_are_root; same_owner_option += we_are_root; - xalloc_fail_func = apply_delayed_set_stat; + xalloc_fail_func = extract_finish; /* Option -p clears the kernel umask, so it does not affect proper restoration of file permissions. New intermediate directories will @@ -97,7 +128,7 @@ extr_init (void) 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, mode_t invert_permissions, enum permstatus permstatus, char typeflag) { @@ -141,6 +172,16 @@ set_mode (char *file_name, struct stat *stat_info, 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 (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 not restoring permissions, invert the @@ -154,7 +195,7 @@ 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, mode_t invert_permissions, enum permstatus permstatus, char typeflag) { @@ -182,6 +223,11 @@ set_stat (char *file_name, struct stat *stat_info, if (utime (file_name, &utimbuf) < 0) utime_error (file_name); + else + { + check_time (file_name, stat_info->st_atime); + check_time (file_name, stat_info->st_mtime); + } } /* Some systems allow non-root users to give files away. Once this @@ -234,7 +280,8 @@ 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); data->file_name_len = file_name_len; strcpy (data->file_name, file_name); data->invert_permissions = invert_permissions; @@ -245,9 +292,9 @@ delay_set_stat (char const *file_name, struct stat const *stat_info, } /* 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 on the path to DIR_NAME. 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, struct stat const *dir_stat_info) @@ -277,37 +324,37 @@ repair_delayed_set_stat (char const *dir_name, quotearg_colon (dir_name))); } -/*-----------------------------------------------------------------------. -| 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 *cursor0 = file_name + FILESYSTEM_PREFIX_LEN (file_name); char *cursor; /* points into path */ 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 "..". */ 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 */ @@ -346,7 +393,6 @@ make_directories (char *file_name) break; } - errno = saved_errno; return did_something; /* tell them to retry if we made one */ } @@ -370,13 +416,10 @@ prepare_to_extract (char const *file_name) 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 | -| 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) { @@ -405,7 +448,10 @@ maybe_recoverable (char *file_name, int *interdir_made) case ENOENT: /* Attempt creating missing intermediate directories. */ if (! make_directories (file_name)) - return 0; + { + errno = ENOENT; + return 0; + } *interdir_made = 1; return 1; @@ -416,21 +462,17 @@ maybe_recoverable (char *file_name, int *interdir_made) } } -/*---. -| ? | -`---*/ - static void extract_sparse_file (int fd, off_t *sizeleft, off_t totalsize, char *name) { int sparse_ind = 0; - size_t written; - ssize_t count; /* assuming sizeleft is initially totalsize */ while (*sizeleft > 0) { + size_t written; + size_t count; union block *data_block = find_next_block (); if (! data_block) { @@ -446,10 +488,13 @@ extract_sparse_file (int fd, off_t *sizeleft, off_t totalsize, char *name) while (written > BLOCKSIZE) { count = full_write (fd, data_block->buffer, BLOCKSIZE); - if (count < 0) - write_error (name); written -= count; *sizeleft -= count; + if (count != BLOCKSIZE) + { + write_error_details (name, count, BLOCKSIZE); + return; + } set_next_block_after (data_block); data_block = find_next_block (); if (! data_block) @@ -460,46 +505,58 @@ extract_sparse_file (int fd, off_t *sizeleft, off_t totalsize, char *name) } count = full_write (fd, data_block->buffer, written); + *sizeleft -= count; - if (count < 0) - write_error (name); - else if (count != written) + if (count != written) { write_error_details (name, count, written); - skip_file (*sizeleft); + return; } - written -= count; - *sizeleft -= count; set_next_block_after (data_block); } - - free (sparsearray); } -/*----------------------------------. -| Extract a file from the archive. | -`----------------------------------*/ +/* Fix the statuses of all directories whose statuses need fixing, and + which are not ancestors of FILE_NAME. */ +static void +apply_nonancestor_delayed_set_stat (char const *file_name) +{ + size_t file_name_len = strlen (file_name); + while (delayed_set_stat_head) + { + struct delayed_set_stat *data = delayed_set_stat_head; + if (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; + delayed_set_stat_head = data->next; + set_stat (data->file_name, &data->stat_info, + data->invert_permissions, data->permstatus, DIRTYPE); + free (data); + } +} + +/* Extract a file from the archive. */ void extract_archive (void) { union block *data_block; int fd; int status; - ssize_t sstatus; + size_t count; size_t name_length; size_t written; int openflag; mode_t mode; off_t size; - int skipcrud; + size_t skipcrud; int counter; int interdir_made = 0; char typeflag; -#if 0 - int sparse_ind = 0; -#endif union block *exhdr; #define CURRENT_FILE_NAME (skipcrud + current_file_name) @@ -509,13 +566,11 @@ extract_archive (void) if (interactive_option && !confirm ("extract", current_file_name)) { - if (current_header->oldgnu_header.isextended) - skip_extended_headers (); - skip_file (current_stat.st_size); + skip_member (); return; } - /* Print the block from `current_header' and `current_stat'. */ + /* Print the block from current_header and current_stat. */ if (verbose_option) print_header (); @@ -525,29 +580,33 @@ extract_archive (void) skipcrud = 0; if (! absolute_names_option) { - while (CURRENT_FILE_NAME[0] == '/') + if (contains_dot_dot (CURRENT_FILE_NAME)) + { + ERROR ((0, 0, _("%s: Member name contains `..'"), + quotearg_colon (CURRENT_FILE_NAME))); + skip_member (); + return; + } + + skipcrud = FILESYSTEM_PREFIX_LEN (current_file_name); + while (ISSLASH (CURRENT_FILE_NAME[0])) + skipcrud++; + + if (skipcrud) { static int warned_once; if (!warned_once) { warned_once = 1; - WARN ((0, 0, _("Removing leading `/' from member names"))); + WARN ((0, 0, _("Removing leading `%.*s' from member names"), + (int) skipcrud, current_file_name)); } - skipcrud++; /* force relative path */ - } - - if (contains_dot_dot (CURRENT_FILE_NAME)) - { - 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; } } + apply_nonancestor_delayed_set_stat (CURRENT_FILE_NAME); + /* Take a safety backup of a previously existing file. */ if (backup_option && !to_stdout_option) @@ -556,9 +615,7 @@ extract_archive (void) 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); + skip_member (); return; } @@ -651,7 +708,8 @@ extract_archive (void) suffix means a directory. */ name_length = strlen (CURRENT_FILE_NAME); - if (name_length && CURRENT_FILE_NAME[name_length - 1] == '/') + if (FILESYSTEM_PREFIX_LEN (CURRENT_FILE_NAME) < name_length + && CURRENT_FILE_NAME[name_length - 1] == '/') goto really_dir; /* FIXME: deal with protection issues. */ @@ -671,9 +729,7 @@ extract_archive (void) if (! prepare_to_extract (CURRENT_FILE_NAME)) { - if (current_header->oldgnu_header.isextended) - skip_extended_headers (); - skip_file (current_stat.st_size); + skip_member (); if (backup_option) undo_last_backup (); break; @@ -710,9 +766,7 @@ extract_archive (void) goto again_file; open_error (CURRENT_FILE_NAME); - if (current_header->oldgnu_header.isextended) - skip_extended_headers (); - skip_file (current_stat.st_size); + skip_member (); if (backup_option) undo_last_backup (); break; @@ -735,11 +789,10 @@ extract_archive (void) memcpy (name, CURRENT_FILE_NAME, name_length_bis); size = current_stat.st_size; extract_sparse_file (fd, &size, current_stat.st_size, name); + free (sparsearray); } else - for (size = current_stat.st_size; - size > 0; - size -= written) + for (size = current_stat.st_size; size > 0; ) { if (multi_volume_option) { @@ -764,21 +817,20 @@ extract_archive (void) if (written > size) written = size; errno = 0; - sstatus = full_write (fd, data_block->buffer, written); + count = full_write (fd, data_block->buffer, written); + size -= count; 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. */ - - write_error_details (CURRENT_FILE_NAME, sstatus, written); - skip_file (size - written); - break; /* still do the close, mod time, chmod, etc */ + if (count != written) + { + write_error_details (CURRENT_FILE_NAME, count, written); + break; + } } + skip_file (size); + if (multi_volume_option) assign_string (&save_name, 0); @@ -808,30 +860,68 @@ extract_archive (void) if (! prepare_to_extract (CURRENT_FILE_NAME)) break; - while (status = symlink (current_link_name, CURRENT_FILE_NAME), - status != 0) - if (!maybe_recoverable (CURRENT_FILE_NAME, &interdir_made)) - break; - - if (status == 0) - - /* 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. */ - - set_stat (CURRENT_FILE_NAME, ¤t_stat, 0, - ARCHIVED_PERMSTATUS, typeflag); + if (absolute_names_option + || ! (ISSLASH (current_link_name + [FILESYSTEM_PREFIX_LEN (current_link_name)]) + || contains_dot_dot (current_link_name))) + { + while (status = symlink (current_link_name, CURRENT_FILE_NAME), + status != 0) + if (!maybe_recoverable (CURRENT_FILE_NAME, &interdir_made)) + break; + if (status == 0) + set_stat (CURRENT_FILE_NAME, ¤t_stat, 0, 0, SYMTYPE); + else + symlink_error (current_link_name, CURRENT_FILE_NAME); + } 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 (); + /* 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 (CURRENT_FILE_NAME, O_WRONLY | O_CREAT | O_EXCL, 0), + fd < 0) + if (! maybe_recoverable (CURRENT_FILE_NAME, &interdir_made)) + break; + + status = -1; + if (fd < 0) + open_error (CURRENT_FILE_NAME); + else if (fstat (fd, &st) != 0) + { + stat_error (CURRENT_FILE_NAME); + close (fd); + } + else if (close (fd) != 0) + close_error (CURRENT_FILE_NAME); + else + { + size_t filelen = strlen (CURRENT_FILE_NAME); + size_t linklen = strlen (current_link_name); + struct delayed_symlink *p = + xmalloc (offsetof (struct delayed_symlink, target) + + linklen + 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.st_uid; + p->gid = current_stat.st_gid; + p->sources = xmalloc (offsetof (struct string_list, string) + + filelen + 1); + p->sources->next = 0; + memcpy (p->sources->string, CURRENT_FILE_NAME, filelen + 1); + memcpy (p->target, current_link_name, linklen + 1); + status = 0; + } } + + if (status != 0 && backup_option) + undo_last_backup (); break; #else @@ -863,7 +953,24 @@ extract_archive (void) status = link (current_link_name, CURRENT_FILE_NAME); if (status == 0) - break; + { + struct delayed_symlink *ds = delayed_symlink_head; + if (ds && stat (current_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 (CURRENT_FILE_NAME) + 1); + strcpy (p->string, CURRENT_FILE_NAME); + p->next = ds->sources; + ds->sources = p; + break; + } + break; + } if (maybe_recoverable (CURRENT_FILE_NAME, &interdir_made)) goto again_link; @@ -876,9 +983,7 @@ extract_archive (void) && st1.st_ino == st2.st_ino) break; - ERROR ((0, e, _("%s: Cannot link to %s"), - quotearg_colon (CURRENT_FILE_NAME), - quote (current_link_name))); + link_error (current_link_name, CURRENT_FILE_NAME); if (backup_option) undo_last_backup (); } @@ -943,19 +1048,11 @@ extract_archive (void) 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; - } + /* Remove any redundant trailing "/"s. */ + while (FILESYSTEM_PREFIX_LEN (CURRENT_FILE_NAME) < name_length + && CURRENT_FILE_NAME[name_length - 1] == '/') + name_length--; + CURRENT_FILE_NAME[name_length] = '\0'; if (incremental_option) { @@ -965,7 +1062,7 @@ extract_archive (void) gnu_restore (skipcrud); } else if (typeflag == GNUTYPE_DUMPDIR) - skip_file (current_stat.st_size); + skip_member (); if (! prepare_to_extract (CURRENT_FILE_NAME)) break; @@ -976,25 +1073,33 @@ extract_archive (void) again_dir: status = mkdir (CURRENT_FILE_NAME, mode); + if (status != 0) { - if (errno == EEXIST && interdir_made - && contains_dot_dot (CURRENT_FILE_NAME)) + if (errno == EEXIST + && (interdir_made || old_files_option == OVERWRITE_OLD_FILES)) { - int e = errno; struct stat st; if (stat (CURRENT_FILE_NAME, &st) == 0) { - repair_delayed_set_stat (CURRENT_FILE_NAME, &st); - break; + if (interdir_made) + { + repair_delayed_set_stat (CURRENT_FILE_NAME, &st); + break; + } + if (S_ISDIR (st.st_mode)) + { + mode = st.st_mode & ~ current_umask; + goto directory_exists; + } } - e = errno; + errno = EEXIST; } if (maybe_recoverable (CURRENT_FILE_NAME, &interdir_made)) goto again_dir; - if (errno != EEXIST || old_files_option == KEEP_OLD_FILES) + if (errno != EEXIST) { mkdir_error (CURRENT_FILE_NAME); if (backup_option) @@ -1003,10 +1108,11 @@ extract_archive (void) } } + directory_exists: if (status == 0 || old_files_option == OVERWRITE_OLD_FILES) delay_set_stat (CURRENT_FILE_NAME, ¤t_stat, - mode & ~ current_stat.st_mode, + MODE_RWX & (mode ^ current_stat.st_mode), (status == 0 ? ARCHIVED_PERMSTATUS : UNKNOWN_PERMSTATUS)); @@ -1025,7 +1131,7 @@ extract_archive (void) ERROR ((0, 0, _("%s: Cannot extract -- file is continued from another volume"), quotearg_colon (current_file_name))); - skip_file (current_stat.st_size); + skip_member (); if (backup_option) undo_last_backup (); break; @@ -1033,7 +1139,7 @@ extract_archive (void) case GNUTYPE_LONGNAME: case GNUTYPE_LONGLINK: ERROR ((0, 0, _("Visible long name error"))); - skip_file (current_stat.st_size); + skip_member (); if (backup_option) undo_last_backup (); break; @@ -1048,38 +1154,79 @@ extract_archive (void) #undef CURRENT_FILE_NAME } -/* Fix the status of all directories whose statuses need fixing. */ -void -apply_delayed_set_stat (void) +/* Extract the symbolic links whose final extraction were delayed. */ +static void +apply_delayed_symlinks (void) { - apply_nonancestor_delayed_set_stat (""); + 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, 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; } -/* Fix the statuses of all directories whose statuses need fixing, and - which are not ancestors of FILE_NAME. */ +/* Finish the extraction of an archive. */ void -apply_nonancestor_delayed_set_stat (char const *file_name) +extract_finish (void) { - size_t file_name_len = strlen (file_name); - - while (delayed_set_stat_head) - { - struct delayed_set_stat *data = delayed_set_stat_head; - 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; - delayed_set_stat_head = data->next; - set_stat (data->file_name, &data->stat_info, - data->invert_permissions, data->permstatus, DIRTYPE); - free (data); - } + /* Apply delayed symlinks last, so that they don't affect + delayed directory status-setting. */ + apply_nonancestor_delayed_set_stat (""); + apply_delayed_symlinks (); } void fatal_exit (void) { - apply_delayed_set_stat (); + extract_finish (); error (TAREXIT_FAILURE, 0, _("Error is not recoverable: exiting now")); abort (); }