- register char *data;
- int fd, check, namelen, written, openflag;
- long size;
- struct utimbuf acc_upd_times;
- register int skipcrud;
- register int i;
-/* int sparse_ind = 0;*/
- union record *exhdr;
- struct saved_dir_info *tmp;
-/* int end_nulls; */
- char **longp;
- char *bp;
-
- saverec(&head); /* Make sure it sticks around */
- userec(head); /* And go past it in the archive */
- decode_header(head, &hstat, &head_standard, 1); /* Snarf fields */
-
- if(f_confirm && !confirm("extract",current_file_name)) {
- if (head->header.isextended)
- skip_extended_headers();
- skip_file((long)hstat.st_size);
- saverec((union record **)0);
- return;
- }
-
- /* Print the record from 'head' and 'hstat' */
- if (f_verbose)
- print_header();
-
- /*
- * Check for fully specified pathnames and other atrocities.
- *
- * Note, we can't just make a pointer to the new file name,
- * since saverec() might move the header and adjust "head".
- * We have to start from "head" every time we want to touch
- * the header record.
- */
- skipcrud = 0;
- while (!f_absolute_paths
- && '/' == current_file_name[skipcrud]) {
- static int warned_once = 0;
-
- skipcrud++; /* Force relative path */
- if (!warned_once++) {
- msg("Removing leading / from absolute path names in the archive.");
- }
- }
-
- switch (head->header.linkflag) {
-
- default:
- msg("Unknown file type '%c' for %s, extracted as normal file",
- head->header.linkflag, skipcrud+current_file_name);
- /* FALL THRU */
-
- /*
- * JK - What we want to do if the file is sparse is loop through
- * the array of sparse structures in the header and read in
- * and translate the character strings representing 1) the offset
- * at which to write and 2) how many bytes to write into numbers,
- * which we store into the scratch array, "sparsearray". This
- * array makes our life easier the same way it did in creating
- * the tar file that had to deal with a sparse file.
- *
- * After we read in the first five (at most) sparse structures,
- * we check to see if the file has an extended header, i.e.,
- * if more sparse structures are needed to describe the contents
- * of the new file. If so, we read in the extended headers
- * and continue to store their contents into the sparsearray.
- */
- case LF_SPARSE:
- sp_array_size = 10;
- sparsearray = (struct sp_array *) malloc(sp_array_size * sizeof(struct sp_array));
- for (i = 0; i < SPARSE_IN_HDR; i++) {
- sparsearray[i].offset =
- from_oct(1+12, head->header.sp[i].offset);
- sparsearray[i].numbytes =
- from_oct(1+12, head->header.sp[i].numbytes);
- if (!sparsearray[i].numbytes)
- break;
- }
-
- /* end_nulls = from_oct(1+12, head->header.ending_blanks);*/
-
- if (head->header.isextended) {
- /* read in the list of extended headers
- and translate them into the sparsearray
- as before */
-
- /* static */ int ind = SPARSE_IN_HDR;
-
- for (;;) {
-
- exhdr = findrec();
- for (i = 0; i < SPARSE_EXT_HDR; i++) {
-
- if (i+ind > sp_array_size-1) {
- /*
- * realloc the scratch area
- * since we've run out of room --
- */
- sparsearray = (struct sp_array *)
- realloc(sparsearray,
- 2 * sp_array_size * (sizeof(struct sp_array)));
- sp_array_size *= 2;
- }
- if (!exhdr->ext_hdr.sp[i].numbytes)
- break;
- sparsearray[i+ind].offset =
- from_oct(1+12, exhdr->ext_hdr.sp[i].offset);
- sparsearray[i+ind].numbytes =
- from_oct(1+12, exhdr->ext_hdr.sp[i].numbytes);
- }
- if (!exhdr->ext_hdr.isextended)
- break;
- else {
- ind += SPARSE_EXT_HDR;
- userec(exhdr);
- }
- }
- userec(exhdr);
- }
-
- /* FALL THRU */
- case LF_OLDNORMAL:
- case LF_NORMAL:
- case LF_CONTIG:
- /*
- * Appears to be a file.
- * See if it's really a directory.
- */
- namelen = strlen(skipcrud+current_file_name)-1;
- if (current_file_name[skipcrud+namelen] == '/')
- goto really_dir;
-
- /* FIXME, deal with protection issues */
- again_file:
- openflag = (f_keep?
- O_BINARY|O_NDELAY|O_WRONLY|O_CREAT|O_EXCL:
- O_BINARY|O_NDELAY|O_WRONLY|O_CREAT|O_TRUNC)
- | ((head->header.linkflag == LF_SPARSE) ? 0 : O_APPEND);
- /*
- * JK - The last | is a kludge to solve the problem
- * the O_APPEND flag causes with files we are
- * trying to make sparse: when a file is opened
- * with O_APPEND, it writes to the last place
- * that something was written, thereby ignoring
- * any lseeks that we have done. We add this
- * extra condition to make it able to lseek when
- * a file is sparse, i.e., we don't open the new
- * file with this flag. (Grump -- this bug caused
- * me to waste a good deal of time, I might add)
- */
-
- if(f_exstdout) {
- fd = 1;
- goto extract_file;
- }
-#ifdef O_CTG
- /*
- * Contiguous files (on the Masscomp) have to specify
- * the size in the open call that creates them.
- */
- if (head->header.linkflag == LF_CONTIG)
- fd = open((longname ? longname : head->header.name)
- + skipcrud,
- openflag | O_CTG,
- hstat.st_mode, hstat.st_size);
- else
-#endif
- {
-#ifdef NO_OPEN3
- /*
- * On raw V7 we won't let them specify -k (f_keep), but
- * we just bull ahead and create the files.
- */
- fd = creat((longname
- ? longname
- : head->header.name) + skipcrud,
- hstat.st_mode);
-#else
- /*
- * With 3-arg open(), we can do this up right.
- */
- fd = open(skipcrud + current_file_name,
- openflag, hstat.st_mode);
-#endif
- }
-
- if (fd < 0) {
- if (make_dirs(skipcrud + current_file_name))
- goto again_file;
- msg_perror("Could not create file %s",
- skipcrud + current_file_name);
- if (head->header.isextended)
- skip_extended_headers();
- skip_file((long)hstat.st_size);
- goto quit;
- }
-
- extract_file:
- if (head->header.linkflag == LF_SPARSE) {
- char *name;
- int namelen;
-
- /*
- * Kludge alert. NAME is assigned to header.name
- * because during the extraction, the space that
- * contains the header will get scribbled on, and
- * the name will get munged, so any error messages
- * that happen to contain the filename will look
- * REAL interesting unless we do this.
- */
- namelen = strlen(skipcrud + current_file_name);
- name = (char *) malloc((sizeof(char)) * namelen);
- bcopy(skipcrud+current_file_name, name, namelen);
- size = hstat.st_size;
- extract_sparse_file(fd, &size, hstat.st_size, name);
- }
- else
- for (size = hstat.st_size;
- size > 0;
- size -= written) {
-
- /* long offset,
- numbytes;*/
-
- if(f_multivol) {
- save_name=current_file_name;
- save_totsize=hstat.st_size;
- save_sizeleft=size;
- }
-
- /*
- * Locate data, determine max length
- * writeable, write it, record that
- * we have used the data, then check
- * if the write worked.
- */
- data = findrec()->charptr;
- if (data == NULL) { /* Check it... */
- msg("Unexpected EOF on archive file");
- break;
- }
- /*
- * JK - If the file is sparse, use the sparsearray
- * that we created before to lseek into the new
- * file the proper amount, and to see how many
- * bytes we want to write at that position.
- */
- /* if (head->header.linkflag == LF_SPARSE) {
- off_t pos;
-
- pos = lseek(fd, (off_t) sparsearray[sparse_ind].offset, 0);
- printf("%d at %d\n", (int) pos, sparse_ind);
- written = sparsearray[sparse_ind++].numbytes;
- } else*/
- written = endofrecs()->charptr - data;
- if (written > size)
- written = size;
- errno = 0;
- check = write(fd, data, written);
- /*
- * The following is in violation of strict
- * typing, since the arg to userec
- * should be a struct rec *. FIXME.
- */
- userec((union record *)(data + written - 1));
- if (check == written) continue;
- /*
- * Error in writing to file.
- * Print it, skip to next file in archive.
- */
- if(check<0)
- msg_perror("couldn't write to file %s",
- skipcrud + current_file_name);
- else
- msg("could only write %d of %d bytes to file %s",
- written,check,skipcrud + current_file_name);
- skip_file((long)(size - written));
- break; /* Still do the close, mod time, chmod, etc */
- }
-
- if(f_multivol)
- save_name = 0;
-
- /* If writing to stdout, don't try to do anything
- to the filename; it doesn't exist, or we don't
- want to touch it anyway */
- if(f_exstdout)
- break;
-
- /* if (head->header.isextended) {
- register union record *exhdr;
- register int i;
-
- for (i = 0; i < 21; i++) {
- long offset;
-
- if (!exhdr->ext_hdr.sp[i].numbytes)
- break;
- offset = from_oct(1+12,
- exhdr->ext_hdr.sp[i].offset);
- written = from_oct(1+12,
- exhdr->ext_hdr.sp[i].numbytes);
- lseek(fd, offset, 0);
- check = write(fd, data, written);
- if (check == written) continue;
-
- }
-
-
- }*/
- check = close(fd);
- if (check < 0) {
- msg_perror("Error while closing %s",
- skipcrud + current_file_name);
- }
-
-
- set_filestat:
-
- /*
- * If we are root, set the owner and group of the extracted
- * file. This does what is wanted both on real Unix and on
- * System V. If we are running as a user, we extract as that
- * user; if running as root, we extract as the original owner.
- */
- if (we_are_root || f_do_chown) {
- if (chown(skipcrud + current_file_name,
- hstat.st_uid, hstat.st_gid) < 0) {
- msg_perror("cannot chown file %s to uid %d gid %d",
- skipcrud + current_file_name,
- hstat.st_uid,hstat.st_gid);
- }
- }
-
- /*
- * Set the modified time of the file.
- *
- * Note that we set the accessed time to "now", which
- * is really "the time we started extracting files".
- * unless f_gnudump is used, in which case .st_atime is used
- */
- if (!f_modified) {
- /* fixme if f_gnudump should set ctime too, but how? */
- if(f_gnudump)
- acc_upd_times.actime=hstat.st_atime;
- else
- acc_upd_times.actime = now; /* Accessed now */
- acc_upd_times.modtime = hstat.st_mtime; /* Mod'd */
- if (utime(skipcrud + current_file_name,
- &acc_upd_times) < 0) {
- msg_perror("couldn't change access and modification times of %s",skipcrud + current_file_name);
- }
+ struct stat st;
+
+ if (!stp)
+ {
+ if (deref_stat (file_name, &st) != 0)
+ {
+ if (errno != ENOENT)
+ {
+ stat_warn (file_name);
+ /* Be safer: if the file exists, assume it is newer. */
+ return true;
+ }
+ return false;
+ }
+ stp = &st;
+ }
+
+ return (! S_ISDIR (stp->st_mode)
+ && tar_timespec_cmp (tar_stat->mtime, get_stat_mtime (stp)) <= 0);
+}
+
+#define RECOVER_NO 0
+#define RECOVER_OK 1
+#define RECOVER_SKIP 2
+
+/* Attempt repairing what went wrong with the extraction. Delete an
+ already existing file or create missing intermediate directories.
+ Return RECOVER_OK if we somewhat increased our chances at a successful
+ extraction, RECOVER_NO if there are no chances, and RECOVER_SKIP if the
+ caller should skip extraction of that member. The value of errno is
+ properly restored on returning RECOVER_NO.
+
+ If REGULAR, the caller was trying to extract onto a regular file.
+
+ Set *INTERDIR_MADE if an intermediate directory is made as part of
+ the recovery process. */
+
+static int
+maybe_recoverable (char *file_name, bool regular, bool *interdir_made)
+{
+ int e = errno;
+ struct stat st;
+ struct stat const *stp = 0;
+
+ if (*interdir_made)
+ return RECOVER_NO;
+
+ switch (e)
+ {
+ case ELOOP:
+ if (! regular
+ || old_files_option != OVERWRITE_OLD_FILES || dereference_option)
+ break;
+ if (strchr (file_name, '/'))
+ {
+ if (deref_stat (file_name, &st) != 0)
+ break;
+ stp = &st;
+ }
+
+ /* The caller tried to open a symbolic link with O_NOFOLLOW.
+ Fall through, treating it as an already-existing file. */
+
+ case EEXIST:
+ /* Remove an old file, if the options allow this. */
+
+ switch (old_files_option)
+ {
+ case KEEP_OLD_FILES:
+ return RECOVER_SKIP;
+
+ case KEEP_NEWER_FILES:
+ if (file_newer_p (file_name, stp, ¤t_stat_info))
+ break;
+ /* FALL THROUGH */
+
+ case DEFAULT_OLD_FILES:
+ case NO_OVERWRITE_DIR_OLD_FILES:
+ case OVERWRITE_OLD_FILES:
+ if (0 < remove_any_file (file_name, ORDINARY_REMOVE_OPTION))
+ return RECOVER_OK;
+ break;
+
+ case UNLINK_FIRST_OLD_FILES:
+ break;
+ }
+
+ case ENOENT:
+ /* Attempt creating missing intermediate directories. */
+ if (make_directories (file_name, interdir_made) == 0 && *interdir_made)
+ return RECOVER_OK;
+ break;
+
+ default:
+ /* Just say we can't do anything about it... */
+ break;
+ }
+
+ errno = e;
+ return RECOVER_NO;
+}
+
+/* Fix the statuses of all directories whose statuses need fixing, and
+ 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
+ links are applied. */
+static void
+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;
+
+ while (delayed_set_stat_head)
+ {
+ struct delayed_set_stat *data = delayed_set_stat_head;
+ bool skip_this_one = 0;
+ struct stat st;
+ mode_t current_mode = data->current_mode;
+ mode_t current_mode_mask = data->current_mode_mask;
+
+ check_for_renamed_directories |= data->after_links;
+
+ 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])
+ || ISSLASH (file_name[data->file_name_len - 1]))
+ && memcmp (file_name, data->file_name, data->file_name_len) == 0))
+ break;
+
+ chdir_do (data->change_dir);
+
+ if (check_for_renamed_directories)
+ {
+ if (fstatat (chdir_fd, data->file_name, &st, data->atflag) != 0)
+ {
+ stat_error (data->file_name);
+ skip_this_one = 1;
+ }
+ else
+ {
+ current_mode = st.st_mode;
+ current_mode_mask = ALL_MODE_BITS;
+ if (! (st.st_dev == data->dev && st.st_ino == data->ino))
+ {
+ ERROR ((0, 0,
+ _("%s: Directory renamed before its status could be extracted"),
+ quotearg_colon (data->file_name)));
+ skip_this_one = 1;