X-Git-Url: https://git.dogcows.com/gitweb?a=blobdiff_plain;f=src%2Fextract.c;h=35331249599d5a2f52cf63e9065574dec9bef013;hb=aef747068b127bbf136b100db14ff7be5ea1b3aa;hp=ffb4c33311c097728739cddef7c8bc425d28e305;hpb=6a8c91fef33385cbd552a78061b75f1429f59542;p=chaz%2Ftar diff --git a/src/extract.c b/src/extract.c index ffb4c33..3533124 100644 --- a/src/extract.c +++ b/src/extract.c @@ -1,5 +1,5 @@ /* Extract files from a tar archive. - Copyright (C) 1988, 92,93,94,96,97,98, 1999 Free Software Foundation, Inc. + Copyright 1988, 92,93,94,96,97,98, 1999 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 @@ -17,9 +17,7 @@ 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "system.h" - -#include -time_t time (); +#include #if HAVE_UTIME_H # include @@ -33,25 +31,36 @@ struct utimbuf #include "common.h" -static time_t now; /* current time */ static 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) */ -#if 0 -/* "Scratch" space to store the information about a sparse file before - writing the info into the header or extended header. */ -struct sp_array *sparsearray; +/* 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, -/* Number of elts storable in the sparsearray. */ -int sp_array_size = 10; -#endif + /* This file was created using the permissions from the archive. */ + ARCHIVED_PERMSTATUS, + + /* This is an intermediate directory; the archive did not specify + its permissions. */ + INTERDIR_PERMSTATUS +}; +/* List of directories whose statuses we need to extract after we've + finished extracting their subsidiary files. The head of the list + has the longest name; each non-head element in the list is an + ancestor (in the directory hierarchy) of the preceding element. */ struct delayed_set_stat { struct delayed_set_stat *next; - char *file_name; struct stat stat_info; + size_t file_name_len; + mode_t invert_permissions; + enum permstatus permstatus; + char file_name[1]; }; static struct delayed_set_stat *delayed_set_stat_head; @@ -63,57 +72,84 @@ static struct delayed_set_stat *delayed_set_stat_head; void extr_init (void) { - now = time ((time_t *) 0); we_are_root = geteuid () == 0; + same_permissions_option += we_are_root; + same_owner_option += we_are_root; /* 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. */ newdir_umask = umask (0); - if (same_permissions_option) + if (0 < same_permissions_option) current_umask = 0; else { umask (newdir_umask); /* restore the kernel umask */ current_umask = newdir_umask; } - - /* FIXME: Just make sure we can add files in directories we create. Maybe - should we later remove permissions we are adding, here? */ - newdir_umask &= ~ MODE_WXUSR; } -/*------------------------------------------------------------------. -| Restore mode for FILE_NAME, from information given in STAT_INFO. | -`------------------------------------------------------------------*/ - +/* If restoring permissions, restore the mode for FILE_NAME from + information given in *STAT_INFO; otherwise invert the + INVERT_PERMISSIONS bits from the file's current permissions. + PERMSTATUS specifies the status of the file's permissions. + TYPEFLAG specifies the type of the file. */ static void -set_mode (char *file_name, struct stat *stat_info) +set_mode (char *file_name, struct stat *stat_info, + mode_t invert_permissions, enum permstatus permstatus, + char typeflag) { - /* We ought to force permission when -k is not selected, because if the - file already existed, open or creat would save the permission bits from - the previously created file, ignoring the ones we specified. - - But with -k selected, we know *we* created this file, so the mode - bits were set by our open. If the file has abnormal mode bits, we must - chmod since writing or chown has probably reset them. If the file is - normal, we merely skip the chmod. This works because we did umask (0) - when -p, so umask will have left the specified mode alone. */ - - if (!keep_old_files_option - || (stat_info->st_mode & (S_ISUID | S_ISGID | S_ISVTX))) - if (chmod (file_name, ~current_umask & stat_info->st_mode) < 0) - ERROR ((0, errno, _("%s: Cannot change mode to %04lo"), - file_name, - (unsigned long) (~current_umask & stat_info->st_mode))); + mode_t mode; + + 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 (stat (file_name, &st) != 0) + { + stat_error (file_name); + return; + } + mode = st.st_mode ^ invert_permissions; + } + + if (chmod (file_name, mode) != 0) + { + int e = errno; + ERROR ((0, e, _("%s: Cannot change mode to %04lo"), + quotearg_colon (file_name), (unsigned long) mode)); + } } -/*----------------------------------------------------------------------. -| Restore stat attributes (owner, group, mode and times) for FILE_NAME, | -| using information given in STAT_INFO. SYMLINK_FLAG is non-zero for a | -| freshly restored symbolic link. | -`----------------------------------------------------------------------*/ +/* Restore stat attributes (owner, group, mode and times) for + FILE_NAME, using information given in *STAT_INFO. + 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 @@ -121,16 +157,18 @@ 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, int symlink_flag) +set_stat (char *file_name, struct stat *stat_info, + mode_t invert_permissions, enum permstatus permstatus, + char typeflag) { struct utimbuf utimbuf; - if (!symlink_flag) + if (typeflag != SYMTYPE) { /* We do the utime before the chmod because some versions of utime are broken and trash the modes of the file. */ - if (!touch_option) + 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 @@ -141,74 +179,120 @@ set_stat (char *file_name, struct stat *stat_info, int symlink_flag) if (incremental_option) utimbuf.actime = stat_info->st_atime; else - utimbuf.actime = now; + utimbuf.actime = start_time; utimbuf.modtime = stat_info->st_mtime; if (utime (file_name, &utimbuf) < 0) - ERROR ((0, errno, - _("%s: Could not change access and modification times"), - file_name)); + { + int e = errno; + ERROR ((0, e, + _("%s: Cannot change access and modification times"), + quotearg_colon (file_name))); + } } /* 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); + set_mode (file_name, stat_info, + invert_permissions, permstatus, typeflag); } - /* If we are root, set the owner and group of the extracted file, so we - extract as the original owner. Or else, if we are running as a user, - leave the owner and group as they are, so we extract as that user. */ - - if (we_are_root || same_owner_option) + if (0 < same_owner_option && permstatus != INTERDIR_PERMSTATUS) { -#if HAVE_LCHOWN - /* 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 (symlink_flag) + if (typeflag == SYMTYPE) { +#if HAVE_LCHOWN if (lchown (file_name, stat_info->st_uid, stat_info->st_gid) < 0) - ERROR ((0, errno, _("%s: Cannot lchown to uid %lu gid %lu"), - file_name, - (unsigned long) stat_info->st_uid, - (unsigned long) stat_info->st_gid)); + { + int e = errno; + ERROR ((0, e, _("%s: Cannot lchown to uid %lu gid %lu"), + quotearg_colon (file_name), + (unsigned long) stat_info->st_uid, + (unsigned long) stat_info->st_gid)); + } +#endif } else { if (chown (file_name, stat_info->st_uid, stat_info->st_gid) < 0) - ERROR ((0, errno, _("%s: Cannot chown to uid %lu gid %lu"), - file_name, - (unsigned long) stat_info->st_uid, - (unsigned long) stat_info->st_gid)); - } - -#else /* not HAVE_LCHOWN */ - - if (!symlink_flag) - - if (chown (file_name, stat_info->st_uid, stat_info->st_gid) < 0) - ERROR ((0, errno, _("%s: Cannot chown to uid %lu gid %lu"), - file_name, - (unsigned long) stat_info->st_uid, - (unsigned long) stat_info->st_gid)); + { + int e = errno; + ERROR ((0, e, _("%s: Cannot chown to uid %lu gid %lu"), + quotearg_colon (file_name), + (unsigned long) stat_info->st_uid, + (unsigned long) stat_info->st_gid)); + } -#endif/* not HAVE_LCHOWN */ + /* 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, + invert_permissions, permstatus, typeflag); + } + } +} - if (!symlink_flag) +/* 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 (sizeof *data + file_name_len); + data->file_name_len = file_name_len; + strcpy (data->file_name, file_name); + data->invert_permissions = invert_permissions; + data->permstatus = permstatus; + data->stat_info = *stat_info; + data->next = delayed_set_stat_head; + delayed_set_stat_head = data; +} - /* 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. */ +/* 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. */ +static void +repair_delayed_set_stat (char const *dir_name, + struct stat const *dir_stat_info) +{ + struct delayed_set_stat *data; + for (data = delayed_set_stat_head; data; data = data->next) + { + struct stat st; + if (stat (data->file_name, &st) != 0) + { + stat_error (data->file_name); + return; + } - if (stat_info->st_mode & (S_ISUID | S_ISGID | S_ISVTX)) - set_mode (file_name, stat_info); + if (st.st_dev == dir_stat_info->st_dev + && st.st_ino == dir_stat_info->st_ino) + { + data->stat_info = current_stat; + data->invert_permissions = (MODE_RWX + & (current_stat.st_mode ^ st.st_mode)); + data->permstatus = ARCHIVED_PERMSTATUS; + return; + } } + + ERROR ((0, 0, _("Unexpected inconsistency when making directory %s"), + quote (dir_name))); } /*-----------------------------------------------------------------------. @@ -223,10 +307,12 @@ 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; for (cursor = strchr (file_name, '/'); - cursor != NULL; + cursor; cursor = strchr (cursor + 1, '/')) { /* Avoid mkdir of empty string, if leading or double '/'. */ @@ -234,28 +320,29 @@ make_directories (char *file_name) if (cursor == file_name || cursor[-1] == '/') continue; - /* Avoid mkdir where last part of path is '.'. */ + /* Avoid mkdir where last part of path is "." or "..". */ - if (cursor[-1] == '.' && (cursor == file_name + 1 || cursor[-2] == '/')) + if (cursor[-1] == '.' + && (cursor == file_name + 1 || cursor[-2] == '/' + || (cursor[-2] == '.' + && (cursor == file_name + 2 || cursor[-3] == '/')))) continue; *cursor = '\0'; /* truncate the path there */ - status = mkdir (file_name, ~newdir_umask & MODE_RWX); + mode = MODE_RWX & ~ newdir_umask; + invert_permissions = we_are_root ? 0 : MODE_WXUSR & ~ mode; + status = mkdir (file_name, mode ^ invert_permissions); if (status == 0) { - /* Fix ownership. */ - - if (we_are_root) - if (chown (file_name, current_stat.st_uid, current_stat.st_gid) < 0) - ERROR ((0, errno, - _("%s: Cannot change owner to uid %lu, gid %lu"), - file_name, - (unsigned long) current_stat.st_uid, - (unsigned long) current_stat.st_gid)); - - print_for_mkdir (file_name, cursor - file_name, - ~newdir_umask & MODE_RWX); + /* 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 /* ignored */, + invert_permissions, INTERDIR_PERMSTATUS); + + print_for_mkdir (file_name, cursor - file_name, mode); did_something = 1; *cursor = '/'; @@ -277,10 +364,30 @@ make_directories (char *file_name) break; } - errno = saved_errno; /* FIXME: errno should be read-only */ + errno = saved_errno; return did_something; /* tell them to retry if we made one */ } +/* Prepare to extract a file. + Return zero if extraction should not proceed. */ + +static int +prepare_to_extract (char const *file_name) +{ + if (to_stdout_option) + return 0; + + if (old_files_option == UNLINK_FIRST_OLD_FILES + && !remove_any_file (file_name, recursive_unlink_option) + && errno != ENOENT) + { + unlink_error (file_name); + return 0; + } + + return 1; +} + /*--------------------------------------------------------------------. | Attempt repairing what went wrong with the extraction. Delete an | | already existing file or create missing intermediate directories. | @@ -289,23 +396,36 @@ make_directories (char *file_name) `--------------------------------------------------------------------*/ static int -maybe_recoverable (char *file_name) +maybe_recoverable (char *file_name, int *interdir_made) { + if (*interdir_made) + return 0; + switch (errno) { case EEXIST: - /* Attempt deleting an existing file. However, with -k, just stay - quiet. */ + /* Remove an old file, if the options allow this. */ - if (keep_old_files_option) - return 0; + switch (old_files_option) + { + default: + return 0; - return remove_any_file (file_name, 0); + case DEFAULT_OLD_FILES: + case OVERWRITE_OLD_FILES: + { + int r = remove_any_file (file_name, 0); + errno = EEXIST; + return r; + } + } case ENOENT: /* Attempt creating missing intermediate directories. */ - - return make_directories (file_name); + if (! make_directories (file_name)) + return 0; + *interdir_made = 1; + return 1; default: /* Just say we can't do anything about it... */ @@ -330,7 +450,7 @@ extract_sparse_file (int fd, off_t *sizeleft, off_t totalsize, char *name) while (*sizeleft > 0) { union block *data_block = find_next_block (); - if (data_block == NULL) + if (! data_block) { ERROR ((0, 0, _("Unexpected EOF on archive file"))); return; @@ -338,9 +458,10 @@ extract_sparse_file (int fd, off_t *sizeleft, off_t totalsize, char *name) if (lseek (fd, sparsearray[sparse_ind].offset, SEEK_SET) < 0) { char buf[UINTMAX_STRSIZE_BOUND]; - ERROR ((0, errno, _("%s: lseek error at byte %s"), - STRINGIFY_BIGINT (sparsearray[sparse_ind].offset, buf), - name)); + int e = errno; + ERROR ((0, e, _("%s: lseek error at byte %s"), + quotearg_colon (name), + STRINGIFY_BIGINT (sparsearray[sparse_ind].offset, buf))); return; } written = sparsearray[sparse_ind++].numbytes; @@ -348,23 +469,28 @@ extract_sparse_file (int fd, off_t *sizeleft, off_t totalsize, char *name) { count = full_write (fd, data_block->buffer, BLOCKSIZE); if (count < 0) - ERROR ((0, errno, _("%s: Could not write to file"), name)); + write_error (name); written -= count; *sizeleft -= count; set_next_block_after (data_block); data_block = find_next_block (); + if (! data_block) + { + ERROR ((0, 0, _("Unexpected EOF on archive file"))); + return; + } } count = full_write (fd, data_block->buffer, written); if (count < 0) - ERROR ((0, errno, _("%s: Could not write to file"), name)); + write_error (name); else if (count != written) { char buf1[UINTMAX_STRSIZE_BOUND]; char buf2[UINTMAX_STRSIZE_BOUND]; ERROR ((0, 0, _("%s: Could only write %s of %s bytes"), - name, + quotearg_colon (name), STRINGIFY_BIGINT (totalsize - *sizeleft, buf1), STRINGIFY_BIGINT (totalsize, buf2))); skip_file (*sizeleft); @@ -392,15 +518,16 @@ extract_archive (void) size_t name_length; size_t written; int openflag; + mode_t mode; off_t size; int skipcrud; int counter; + int interdir_made = 0; char typeflag; #if 0 int sparse_ind = 0; #endif union block *exhdr; - struct delayed_set_stat *data; #define CURRENT_FILE_NAME (skipcrud + current_file_name) @@ -423,16 +550,28 @@ extract_archive (void) /* Check for fully specified file names and other atrocities. */ skipcrud = 0; - while (!absolute_names_option && CURRENT_FILE_NAME[0] == '/') + if (! absolute_names_option) { - static int warned_once = 0; + while (CURRENT_FILE_NAME[0] == '/') + { + static int warned_once; + + if (!warned_once) + { + warned_once = 1; + WARN ((0, 0, _("Removing leading `/' from member names"))); + } + skipcrud++; /* force relative path */ + } - skipcrud++; /* force relative path */ - if (!warned_once) + if (contains_dot_dot (CURRENT_FILE_NAME)) { - warned_once = 1; - WARN ((0, 0, _("\ -Removing leading `/' from absolute path names in the archive"))); + 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; } } @@ -441,8 +580,9 @@ Removing leading `/' from absolute path names in the archive"))); if (backup_option && !to_stdout_option) if (!maybe_backup_file (CURRENT_FILE_NAME, 0)) { - ERROR ((0, errno, _("%s: Was unable to backup this file"), - CURRENT_FILE_NAME)); + 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); @@ -470,15 +610,14 @@ Removing leading `/' from absolute path names in the archive"))); case GNUTYPE_SPARSE: sp_array_size = 10; - sparsearray = (struct sp_array *) + sparsearray = xmalloc (sp_array_size * sizeof (struct sp_array)); for (counter = 0; counter < SPARSES_IN_OLDGNU_HEADER; counter++) { - sparsearray[counter].offset = - OFF_FROM_OCT (current_header->oldgnu_header.sp[counter].offset); - sparsearray[counter].numbytes = - SIZE_FROM_OCT (current_header->oldgnu_header.sp[counter].numbytes); + struct sparse const *s = ¤t_header->oldgnu_header.sp[counter]; + sparsearray[counter].offset = OFF_FROM_HEADER (s->offset); + sparsearray[counter].numbytes = SIZE_FROM_HEADER (s->numbytes); if (!sparsearray[counter].numbytes) break; } @@ -494,26 +633,30 @@ Removing leading `/' from absolute path names in the archive"))); while (1) { exhdr = find_next_block (); + if (! exhdr) + { + ERROR ((0, 0, _("Unexpected EOF on archive file"))); + return; + } for (counter = 0; counter < SPARSES_IN_SPARSE_HEADER; counter++) { + struct sparse const *s = &exhdr->sparse_header.sp[counter]; if (counter + ind > sp_array_size - 1) { /* Realloc the scratch area since we've run out of room. */ sp_array_size *= 2; - sparsearray = (struct sp_array *) + sparsearray = xrealloc (sparsearray, - sp_array_size * (sizeof (struct sp_array))); + sp_array_size * sizeof (struct sp_array)); } - /* Compare to 0, or use !(int)..., for Pyramid's dumb - compiler. */ - if (exhdr->sparse_header.sp[counter].numbytes == 0) + if (s->numbytes[0] == 0) break; sparsearray[counter + ind].offset = - OFF_FROM_OCT (exhdr->sparse_header.sp[counter].offset); + OFF_FROM_HEADER (s->offset); sparsearray[counter + ind].numbytes = - SIZE_FROM_OCT (exhdr->sparse_header.sp[counter].numbytes); + SIZE_FROM_HEADER (s->numbytes); } if (!exhdr->sparse_header.isextended) break; @@ -534,35 +677,34 @@ Removing leading `/' from absolute path names in the archive"))); /* Appears to be a file. But BSD tar uses the convention that a slash suffix means a directory. */ - name_length = strlen (CURRENT_FILE_NAME) - 1; - if (CURRENT_FILE_NAME[name_length] == '/') + name_length = strlen (CURRENT_FILE_NAME); + if (name_length && CURRENT_FILE_NAME[name_length - 1] == '/') goto really_dir; /* FIXME: deal with protection issues. */ again_file: - openflag = (keep_old_files_option ? - O_BINARY | O_NDELAY | O_WRONLY | O_CREAT | O_EXCL : - O_BINARY | O_NDELAY | O_WRONLY | O_CREAT | O_TRUNC) - | ((typeflag == GNUTYPE_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) */ + openflag = (O_WRONLY | O_BINARY | O_CREAT + | (old_files_option == OVERWRITE_OLD_FILES + ? O_TRUNC + : O_EXCL)); + mode = current_stat.st_mode & MODE_RWX & ~ current_umask; if (to_stdout_option) { - fd = 1; + fd = STDOUT_FILENO; goto extract_file; } - if (unlink_first_option) - remove_any_file (CURRENT_FILE_NAME, recursive_unlink_option); + if (! prepare_to_extract (CURRENT_FILE_NAME)) + { + if (current_header->oldgnu_header.isextended) + skip_extended_headers (); + skip_file (current_stat.st_size); + if (backup_option) + undo_last_backup (); + break; + } #if O_CTG /* Contiguous files (on the Masscomp) have to specify the size in @@ -570,14 +712,14 @@ Removing leading `/' from absolute path names in the archive"))); if (typeflag == CONTTYPE) fd = open (CURRENT_FILE_NAME, openflag | O_CTG, - current_stat.st_mode, current_stat.st_size); + mode, current_stat.st_size); else - fd = open (CURRENT_FILE_NAME, openflag, current_stat.st_mode); + fd = open (CURRENT_FILE_NAME, openflag, mode); #else /* not O_CTG */ if (typeflag == CONTTYPE) { - static int conttype_diagnosed = 0; + static int conttype_diagnosed; if (!conttype_diagnosed) { @@ -585,17 +727,16 @@ Removing leading `/' from absolute path names in the archive"))); WARN ((0, 0, _("Extracting contiguous files as regular files"))); } } - fd = open (CURRENT_FILE_NAME, openflag, current_stat.st_mode); + fd = open (CURRENT_FILE_NAME, openflag, mode); #endif /* not O_CTG */ if (fd < 0) { - if (maybe_recoverable (CURRENT_FILE_NAME)) + if (maybe_recoverable (CURRENT_FILE_NAME, &interdir_made)) goto again_file; - ERROR ((0, errno, _("%s: Could not create file"), - CURRENT_FILE_NAME)); + open_error (CURRENT_FILE_NAME); if (current_header->oldgnu_header.isextended) skip_extended_headers (); skip_file (current_stat.st_size); @@ -617,7 +758,7 @@ Removing leading `/' from absolute path names in the archive"))); REAL interesting unless we do this. */ name_length_bis = strlen (CURRENT_FILE_NAME) + 1; - name = (char *) xmalloc (name_length_bis); + name = xmalloc (name_length_bis); memcpy (name, CURRENT_FILE_NAME, name_length_bis); size = current_stat.st_size; extract_sparse_file (fd, &size, current_stat.st_size, name); @@ -639,7 +780,7 @@ Removing leading `/' from absolute path names in the archive"))); worked. */ data_block = find_next_block (); - if (data_block == NULL) + if (! data_block) { ERROR ((0, 0, _("Unexpected EOF on archive file"))); break; /* FIXME: What happens, then? */ @@ -649,7 +790,7 @@ Removing leading `/' from absolute path names in the archive"))); if (written > size) written = size; - errno = 0; /* FIXME: errno should be read-only */ + errno = 0; sstatus = full_write (fd, data_block->buffer, written); set_next_block_after ((union block *) @@ -661,11 +802,10 @@ Removing leading `/' from absolute path names in the archive"))); archive. */ if (sstatus < 0) - ERROR ((0, errno, _("%s: Could not write to file"), - CURRENT_FILE_NAME)); + write_error (CURRENT_FILE_NAME); else ERROR ((0, 0, _("%s: Could only write %lu of %lu bytes"), - CURRENT_FILE_NAME, + quotearg_colon (CURRENT_FILE_NAME), (unsigned long) sstatus, (unsigned long) written)); skip_file (size - written); @@ -673,7 +813,7 @@ Removing leading `/' from absolute path names in the archive"))); } if (multi_volume_option) - assign_string (&save_name, NULL); + 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. */ @@ -684,25 +824,26 @@ Removing leading `/' from absolute path names in the archive"))); status = close (fd); if (status < 0) { - ERROR ((0, errno, _("%s: Error while closing"), CURRENT_FILE_NAME)); + close_error (CURRENT_FILE_NAME); if (backup_option) undo_last_backup (); } - set_stat (CURRENT_FILE_NAME, ¤t_stat, 0); + set_stat (CURRENT_FILE_NAME, ¤t_stat, 0, + (old_files_option == OVERWRITE_OLD_FILES + ? UNKNOWN_PERMSTATUS + : ARCHIVED_PERMSTATUS), + typeflag); break; case SYMTYPE: - if (to_stdout_option) +#ifdef HAVE_SYMLINK + if (! prepare_to_extract (CURRENT_FILE_NAME)) break; -#ifdef S_ISLNK - if (unlink_first_option) - remove_any_file (CURRENT_FILE_NAME, recursive_unlink_option); - while (status = symlink (current_link_name, CURRENT_FILE_NAME), status != 0) - if (!maybe_recoverable (CURRENT_FILE_NAME)) + if (!maybe_recoverable (CURRENT_FILE_NAME, &interdir_made)) break; if (status == 0) @@ -712,42 +853,43 @@ Removing leading `/' from absolute path names in the archive"))); 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, 1); + set_stat (CURRENT_FILE_NAME, ¤t_stat, 0, + ARCHIVED_PERMSTATUS, typeflag); else { - ERROR ((0, errno, _("%s: Could not create symlink to `%s'"), - CURRENT_FILE_NAME, current_link_name)); + int e = errno; + ERROR ((0, e, _("%s: Cannot create symlink to %s"), + quotearg_colon (CURRENT_FILE_NAME), + quote (current_link_name))); if (backup_option) undo_last_backup (); } break; -#else /* not S_ISLNK */ +#else { - static int warned_once = 0; + static int warned_once; if (!warned_once) { warned_once = 1; - WARN ((0, 0, _("\ -Attempting extraction of symbolic links as hard links"))); + WARN ((0, 0, + _("Attempting extraction of symbolic links as hard links"))); } } + typeflag = LNKTYPE; /* Fall through. */ - -#endif /* not S_ISLNK */ +#endif case LNKTYPE: - if (to_stdout_option) + if (! prepare_to_extract (CURRENT_FILE_NAME)) break; - if (unlink_first_option) - remove_any_file (CURRENT_FILE_NAME, recursive_unlink_option); - again_link: { struct stat st1, st2; + int e; /* MSDOS does not implement links. However, djgpp's link() actually copies the file. */ @@ -755,19 +897,21 @@ Attempting extraction of symbolic links as hard links"))); if (status == 0) break; - if (maybe_recoverable (CURRENT_FILE_NAME)) + if (maybe_recoverable (CURRENT_FILE_NAME, &interdir_made)) goto again_link; if (incremental_option && errno == EEXIST) break; + e = errno; if (stat (current_link_name, &st1) == 0 && stat (CURRENT_FILE_NAME, &st2) == 0 && st1.st_dev == st2.st_dev && st1.st_ino == st2.st_ino) break; - ERROR ((0, errno, _("%s: Could not link to `%s'"), - CURRENT_FILE_NAME, current_link_name)); + ERROR ((0, e, _("%s: Cannot link to %s"), + quotearg_colon (CURRENT_FILE_NAME), + quote (current_link_name))); if (backup_option) undo_last_backup (); } @@ -786,46 +930,41 @@ Attempting extraction of symbolic links as hard links"))); #if S_IFCHR || S_IFBLK make_node: - if (to_stdout_option) + if (! prepare_to_extract (CURRENT_FILE_NAME)) break; - if (unlink_first_option) - remove_any_file (CURRENT_FILE_NAME, recursive_unlink_option); - status = mknod (CURRENT_FILE_NAME, current_stat.st_mode, current_stat.st_rdev); if (status != 0) { - if (maybe_recoverable (CURRENT_FILE_NAME)) + if (maybe_recoverable (CURRENT_FILE_NAME, &interdir_made)) goto make_node; - - ERROR ((0, errno, _("%s: Could not make node"), CURRENT_FILE_NAME)); + mknod_error (CURRENT_FILE_NAME); if (backup_option) undo_last_backup (); break; }; - set_stat (CURRENT_FILE_NAME, ¤t_stat, 0); + set_stat (CURRENT_FILE_NAME, ¤t_stat, 0, + ARCHIVED_PERMSTATUS, typeflag); break; #endif -#ifdef S_ISFIFO +#if HAVE_MKFIFO || defined mkfifo case FIFOTYPE: - if (to_stdout_option) + if (! prepare_to_extract (CURRENT_FILE_NAME)) break; - if (unlink_first_option) - remove_any_file (CURRENT_FILE_NAME, recursive_unlink_option); - while (status = mkfifo (CURRENT_FILE_NAME, current_stat.st_mode), status != 0) - if (!maybe_recoverable (CURRENT_FILE_NAME)) + if (!maybe_recoverable (CURRENT_FILE_NAME, &interdir_made)) break; if (status == 0) - set_stat (CURRENT_FILE_NAME, ¤t_stat, 0); + set_stat (CURRENT_FILE_NAME, ¤t_stat, 0, + ARCHIVED_PERMSTATUS, typeflag); else { - ERROR ((0, errno, _("%s: Could not make fifo"), CURRENT_FILE_NAME)); + mkfifo_error (CURRENT_FILE_NAME); if (backup_option) undo_last_backup (); } @@ -834,12 +973,22 @@ Attempting extraction of symbolic links as hard links"))); case DIRTYPE: case GNUTYPE_DUMPDIR: - name_length = strlen (CURRENT_FILE_NAME) - 1; + name_length = strlen (CURRENT_FILE_NAME); really_dir: - /* Check for trailing /, and zap as many as we find. */ - while (name_length && CURRENT_FILE_NAME[name_length] == '/') - CURRENT_FILE_NAME[name_length--] = '\0'; + /* 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) { @@ -851,99 +1000,55 @@ Attempting extraction of symbolic links as hard links"))); else if (typeflag == GNUTYPE_DUMPDIR) skip_file (current_stat.st_size); - if (to_stdout_option) + if (! prepare_to_extract (CURRENT_FILE_NAME)) break; + mode = ((current_stat.st_mode + | (we_are_root ? 0 : MODE_WXUSR)) + & MODE_RWX); + again_dir: - status = mkdir (CURRENT_FILE_NAME, - ((we_are_root ? 0 : MODE_WXUSR) - | current_stat.st_mode)); + status = mkdir (CURRENT_FILE_NAME, mode); if (status != 0) { - /* If the directory creation fails, let's consider immediately the - case where the directory already exists. We have three good - reasons for clearing out this case before attempting recovery. - - 1) It would not be efficient recovering the error by deleting - the directory in maybe_recoverable, then recreating it right - away. We only hope we will be able to adjust its permissions - adequately, later. - - 2) Removing the directory might fail if it is not empty. By - exception, this real error is traditionally not reported. - - 3) Let's suppose `DIR' already exists and we are about to - extract `DIR/../DIR'. This would fail because the directory - already exists, and maybe_recoverable would react by removing - `DIR'. This then would fail again because there are missing - intermediate directories, and maybe_recoverable would react by - creating `DIR'. We would then have an extraction loop. */ - - if (errno == EEXIST) + if (errno == EEXIST && interdir_made + && contains_dot_dot (CURRENT_FILE_NAME)) { - struct stat st1; - int saved_errno = errno; - - if (stat (CURRENT_FILE_NAME, &st1) == 0 && S_ISDIR (st1.st_mode)) - goto check_perms; - - errno = saved_errno; /* FIXME: errno should be read-only */ + int e = errno; + struct stat st; + if (stat (CURRENT_FILE_NAME, &st) == 0) + { + repair_delayed_set_stat (CURRENT_FILE_NAME, &st); + break; + } + e = errno; } - - if (maybe_recoverable (CURRENT_FILE_NAME)) + + if (maybe_recoverable (CURRENT_FILE_NAME, &interdir_made)) goto again_dir; - /* If we're trying to create '.', let it be. */ - - /* FIXME: Strange style... */ - - if (CURRENT_FILE_NAME[name_length] == '.' - && (name_length == 0 - || CURRENT_FILE_NAME[name_length - 1] == '/')) - goto check_perms; - - ERROR ((0, errno, _("%s: Could not create directory"), - CURRENT_FILE_NAME)); - if (backup_option) - undo_last_backup (); - break; - } - - check_perms: - if (!we_are_root && MODE_WXUSR != (MODE_WXUSR & current_stat.st_mode)) - { - current_stat.st_mode |= MODE_WXUSR; - WARN ((0, 0, _("Added write and execute permission to directory %s"), - CURRENT_FILE_NAME)); + if (errno != EEXIST || old_files_option == KEEP_OLD_FILES) + { + int e = errno; + ERROR ((0, e, "%s: mkdir", quotearg_colon (CURRENT_FILE_NAME))); + if (backup_option) + undo_last_backup (); + break; + } } -#if !MSDOS - /* MSDOS does not associate timestamps with directories. In this - case, no need to try delaying their restoration. */ - - if (touch_option) - - /* FIXME: I do not believe in this. Ignoring time stamps does not - alleviate the need of delaying the restoration of directories' - mode. Let's ponder this for a little while. */ - - set_mode (CURRENT_FILE_NAME, ¤t_stat); - - else - { - data = ((struct delayed_set_stat *) - xmalloc (sizeof (struct delayed_set_stat))); - data->file_name = xstrdup (CURRENT_FILE_NAME); - data->stat_info = current_stat; - data->next = delayed_set_stat_head; - delayed_set_stat_head = data; - } -#endif /* !MSDOS */ + if (status == 0 + || old_files_option == OVERWRITE_OLD_FILES) + delay_set_stat (CURRENT_FILE_NAME, ¤t_stat, + mode & ~ current_stat.st_mode, + (status == 0 + ? ARCHIVED_PERMSTATUS + : UNKNOWN_PERMSTATUS)); break; case GNUTYPE_VOLHDR: if (verbose_option) - fprintf (stdlis, _("Reading %s\n"), current_file_name); + fprintf (stdlis, _("Reading %s\n"), quote (current_file_name)); break; case GNUTYPE_NAMES: @@ -951,9 +1056,9 @@ Attempting extraction of symbolic links as hard links"))); break; case GNUTYPE_MULTIVOL: - ERROR ((0, 0, _("\ -Cannot extract `%s' -- file is continued from another volume"), - current_file_name)); + ERROR ((0, 0, + _("Cannot extract %s -- file is continued from another volume"), + quote (current_file_name))); skip_file (current_stat.st_size); if (backup_option) undo_last_backup (); @@ -970,28 +1075,37 @@ Cannot extract `%s' -- file is continued from another volume"), default: WARN ((0, 0, _("Unknown file type '%c' for %s, extracted as normal file"), - typeflag, CURRENT_FILE_NAME)); + typeflag, quote (CURRENT_FILE_NAME))); goto again_file; } #undef CURRENT_FILE_NAME } -/*----------------------------------------------------------------. -| Set back the utime and mode for all the extracted directories. | -`----------------------------------------------------------------*/ - +/* Fix the statuses of all directories that are not ancestors of FILE_NAME. */ void -apply_delayed_set_stat (void) +apply_delayed_set_stat (char const *file_name) { - struct delayed_set_stat *data; + size_t file_name_len = strlen (file_name); - while (delayed_set_stat_head != NULL) + while (delayed_set_stat_head) { - data = delayed_set_stat_head; - delayed_set_stat_head = delayed_set_stat_head->next; - set_stat (data->file_name, &data->stat_info, 0); - free (data->file_name); + struct delayed_set_stat *data = delayed_set_stat_head; + delayed_set_stat_head = data->next; + if (data->file_name_len < file_name_len + && file_name[data->file_name_len] == '/' + && memcmp (file_name, data->file_name, data->file_name_len) == 0) + break; + set_stat (data->file_name, &data->stat_info, + data->invert_permissions, data->permstatus, DIRTYPE); free (data); } } + +void +fatal_exit (void) +{ + apply_delayed_set_stat (""); + error (TAREXIT_FAILURE, 0, _("Error is not recoverable: exiting now")); + abort (); +}