From e89bcb7fb33d6bae6db577bd8c6fe4599c5e2288 Mon Sep 17 00:00:00 2001 From: Paul Eggert Date: Tue, 24 Oct 2000 06:18:37 +0000 Subject: [PATCH] (we_are_root): Now global. (struct delayed_symlink): New type. (delayed_symlink_head): New var. (extr_init, fatal_exit): Invoke extract_finish on fatal errors, not apply_delayed_set_stat. (set_mode, set_stat): Pointer args are now const pointers. (check_time): New function. (set_stat): Warn if setting a file's timestamp to be the future. (make_directories): Do not save and restore errno. (maybe_recoverable): Set errno to ENOENT if we cannot make missing intermediate directories. (extract_archive): Invoke apply_nonancestor_delayed_set_stat here, not in caller. Extract potentially dangerous symbolic links more carefully, deferring their creation until the end, and using a regular file placeholder in the meantime. Do not remove trailing / and /. from file names. Do not bother checking for ".." when checking whether a directory loops back on itself, as loopbacks can occur with symlinks too. Also, in that case, do not bother saving and restoring errno; just set it to EEXIST. (apply_nonancestor_delayed_set_stat): A prefix is a potential ancestor if it ends in slash too (as well as ending in a char just before slash). (apply_delayed_set_stat): Remove. (apply_delayed_symlinks, extract_finish): New functions. --- src/extract.c | 309 +++++++++++++++++++++++++++++++------------------- 1 file changed, 194 insertions(+), 115 deletions(-) diff --git a/src/extract.c b/src/extract.c index 8ba4089..052348a 100644 --- a/src/extract.c +++ b/src/extract.c @@ -31,7 +31,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 +65,37 @@ 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 symbolic link. */ + dev_t dev; + ino_t ino; + time_t mtime; + + /* The desired owner and group of the symbolic link. */ + uid_t uid; + gid_t gid; + + /* The location and desired target of the desired link, as two + adjacent character strings, both null-terminated. */ + char names[1]; + }; +static struct delayed_symlink *delayed_symlink_head; + +/* 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 +117,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 +161,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 +184,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 +212,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 @@ -245,9 +280,10 @@ 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 trailing slash or + ".." 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,18 +313,15 @@ 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 *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; @@ -346,7 +379,6 @@ make_directories (char *file_name) break; } - errno = saved_errno; return did_something; /* tell them to retry if we made one */ } @@ -370,13 +402,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 +434,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,10 +448,6 @@ maybe_recoverable (char *file_name, int *interdir_made) } } -/*---. -| ? | -`---*/ - static void extract_sparse_file (int fd, off_t *sizeleft, off_t totalsize, char *name) { @@ -477,10 +505,30 @@ extract_sparse_file (int fd, off_t *sizeleft, off_t totalsize, char *name) 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] + && (file_name[data->file_name_len] == '/' + || 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) { @@ -497,9 +545,6 @@ extract_archive (void) 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 +554,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 (); @@ -541,13 +584,13 @@ extract_archive (void) { 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); + skip_member (); 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 +599,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; } @@ -671,9 +712,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 +749,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; @@ -808,30 +845,63 @@ 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 + || (current_link_name[0] != '/' + && ! 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 (sizeof *p + filelen + 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; + memcpy (p->names, CURRENT_FILE_NAME, filelen + 1); + memcpy (p->names + filelen + 1, current_link_name, linklen + 1); + status = 0; + } } + + if (status != 0 && backup_option) + undo_last_backup (); break; #else @@ -943,20 +1013,6 @@ 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; - } - if (incremental_option) { /* Read the entry and delete files that aren't listed in the @@ -965,7 +1021,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; @@ -978,17 +1034,15 @@ extract_archive (void) 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) { - int e = errno; struct stat st; if (stat (CURRENT_FILE_NAME, &st) == 0) { repair_delayed_set_stat (CURRENT_FILE_NAME, &st); break; } - e = errno; + errno = EEXIST; } if (maybe_recoverable (CURRENT_FILE_NAME, &interdir_made)) @@ -1025,7 +1079,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 +1087,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 +1102,63 @@ 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 *p; + struct delayed_symlink *next; + + for (p = delayed_symlink_head; p; p = next) + { + char const *file = p->names; + struct stat st; + + /* Before doing anything, make sure the placeholder file is still + there. If the placeholder isn't there, don't worry about it, as + it may have been removed by a later extraction. */ + if (lstat (file, &st) == 0 + && st.st_dev == p->dev + && st.st_ino == p->ino + && st.st_mtime == p->mtime) + { + if (unlink (file) != 0) + unlink_error (file); + else + { + char const *contents = file + strlen (file) + 1; + if (symlink (contents, file) != 0) + symlink_error (contents, file); + else + { + st.st_uid = p->uid; + st.st_gid = p->gid; + set_stat (file, &st, 0, 0, SYMTYPE); + } + } + } + + next = p->next; + free (p); + } + + 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 (); } -- 2.45.2