- default:
- msg("Unknown file type '%c' for %s, extracted as normal file",
- head->header.linkflag, skipcrud+head->header.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+head->header.name)-1;
- if (head->header.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(skipcrud+head->header.name, openflag | O_CTG,
- hstat.st_mode, hstat.st_size);
- else
+ /* these three calls must be done *after* fd_chown() call because fd_chown
+ causes that linux capabilities becomes cleared. */
+ xattrs_xattrs_set (st, file_name, typeflag, 1);
+ xattrs_acls_set (st, file_name, typeflag);
+ xattrs_selinux_set (st, file_name, typeflag);
+}
+
+/* For each entry H in the leading prefix of entries in HEAD that do
+ not have after_links marked, mark H and fill in its dev and ino
+ members. Assume HEAD && ! HEAD->after_links. */
+static void
+mark_after_links (struct delayed_set_stat *head)
+{
+ struct delayed_set_stat *h = head;
+
+ do
+ {
+ struct stat st;
+ h->after_links = 1;
+
+ if (deref_stat (h->file_name, &st) != 0)
+ stat_error (h->file_name);
+ else
+ {
+ h->dev = st.st_dev;
+ h->ino = st.st_ino;
+ }
+ }
+ while ((h = h->next) && ! h->after_links);
+}
+
+/* Remember to restore stat attributes (owner, group, mode and times)
+ for the directory FILE_NAME, using information given in *ST,
+ once we stop extracting files into that directory.
+
+ If ST is null, merely create a placeholder node for an intermediate
+ directory that was created by make_directories.
+
+ NOTICE: this works only if the archive has usual member order, i.e.
+ directory, then the files in that directory. Incremental archive have
+ somewhat reversed order: first go subdirectories, then all other
+ members. To help cope with this case the variable
+ delay_directory_restore_option is set by prepare_to_extract.
+
+ If an archive was explicitely created so that its member order is
+ reversed, some directory timestamps can be restored incorrectly,
+ e.g.:
+ tar --no-recursion -cf archive dir dir/file1 foo dir/file2
+*/
+static void
+delay_set_stat (char const *file_name, struct tar_stat_info const *st,
+ mode_t current_mode, mode_t current_mode_mask,
+ mode_t mode, int atflag)
+{
+ size_t file_name_len = strlen (file_name);
+ struct delayed_set_stat *data =
+ xmalloc (offsetof (struct delayed_set_stat, file_name)
+ + file_name_len + 1);
+ data->next = delayed_set_stat_head;
+ data->mode = mode;
+ if (st)
+ {
+ data->dev = st->stat.st_dev;
+ data->ino = st->stat.st_ino;
+ data->uid = st->stat.st_uid;
+ data->gid = st->stat.st_gid;
+ data->atime = st->atime;
+ data->mtime = st->mtime;
+ }
+ data->file_name_len = file_name_len;
+ data->current_mode = current_mode;
+ data->current_mode_mask = current_mode_mask;
+ data->interdir = ! st;
+ data->atflag = atflag;
+ data->after_links = 0;
+ data->change_dir = chdir_current;
+ data->cntx_name = NULL;
+ if (st)
+ assign_string (&data->cntx_name, st->cntx_name);
+ if (st && st->acls_a_ptr)
+ {
+ data->acls_a_ptr = xmemdup (st->acls_a_ptr, st->acls_a_len + 1);
+ data->acls_a_len = st->acls_a_len;
+ }
+ else
+ {
+ data->acls_a_ptr = NULL;
+ data->acls_a_len = 0;
+ }
+ if (st && st->acls_d_ptr)
+ {
+ data->acls_d_ptr = xmemdup (st->acls_d_ptr, st->acls_d_len + 1);
+ data->acls_d_len = st->acls_d_len;
+ }
+ else
+ {
+ data->acls_d_ptr = NULL;
+ data->acls_d_len = 0;
+ }
+ if (st)
+ xheader_xattr_copy (st, &data->xattr_map, &data->xattr_map_size);
+ else
+ {
+ data->xattr_map = NULL;
+ data->xattr_map_size = 0;
+ }
+ strcpy (data->file_name, file_name);
+ delayed_set_stat_head = data;
+ if (must_be_dot_or_slash (file_name))
+ mark_after_links (data);
+}
+
+/* Update the delayed_set_stat info for an intermediate directory
+ created within the file name of DIR. 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,
+ 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 (fstatat (chdir_fd, data->file_name, &st, data->atflag) != 0)
+ {
+ stat_error (data->file_name);
+ return;
+ }
+
+ if (st.st_dev == dir_stat_info->st_dev
+ && st.st_ino == dir_stat_info->st_ino)
+ {
+ data->dev = current_stat_info.stat.st_dev;
+ data->ino = current_stat_info.stat.st_ino;
+ data->mode = current_stat_info.stat.st_mode;
+ data->uid = current_stat_info.stat.st_uid;
+ data->gid = current_stat_info.stat.st_gid;
+ data->atime = current_stat_info.atime;
+ data->mtime = current_stat_info.mtime;
+ data->current_mode = st.st_mode;
+ data->current_mode_mask = ALL_MODE_BITS;
+ data->interdir = false;
+ return;
+ }
+ }
+
+ ERROR ((0, 0, _("%s: Unexpected inconsistency when making directory"),
+ quotearg_colon (dir)));
+}
+
+/* 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 zero if all the required
+ directories were created, nonzero (issuing a diagnostic) otherwise.
+ Set *INTERDIR_MADE if at least one directory was created. */
+static int
+make_directories (char *file_name, bool *interdir_made)
+{
+ char *cursor0 = file_name + FILE_SYSTEM_PREFIX_LEN (file_name);
+ char *cursor; /* points into the file name */
+
+ for (cursor = cursor0; *cursor; cursor++)
+ {
+ mode_t mode;
+ mode_t desired_mode;
+ int status;
+
+ if (! ISSLASH (*cursor))
+ continue;
+
+ /* Avoid mkdir of empty string, if leading or double '/'. */
+
+ if (cursor == cursor0 || ISSLASH (cursor[-1]))
+ continue;
+
+ /* Avoid mkdir where last part of file name is "." or "..". */
+
+ if (cursor[-1] == '.'
+ && (cursor == cursor0 + 1 || ISSLASH (cursor[-2])
+ || (cursor[-2] == '.'
+ && (cursor == cursor0 + 2 || ISSLASH (cursor[-3])))))
+ continue;
+
+ *cursor = '\0'; /* truncate the name there */
+ desired_mode = MODE_RWX & ~ newdir_umask;
+ mode = desired_mode | (we_are_root ? 0 : MODE_WXUSR);
+ status = mkdirat (chdir_fd, file_name, mode);
+
+ if (status == 0)
+ {
+ /* Create a struct delayed_set_stat even if
+ mode == desired_mode, because
+ repair_delayed_set_stat may need to update the struct. */
+ delay_set_stat (file_name,
+ 0, mode & ~ current_umask, MODE_RWX,
+ desired_mode, AT_SYMLINK_NOFOLLOW);
+
+ print_for_mkdir (file_name, cursor - file_name, desired_mode);
+ *interdir_made = true;
+ }
+ else if (errno == EEXIST)
+ status = 0;
+ else
+ {
+ /* Check whether the desired file exists. Even when the
+ file exists, mkdir can fail with some errno value E other
+ than EEXIST, so long as E describes an error condition
+ that also applies. */
+ int e = errno;
+ struct stat st;
+ status = fstatat (chdir_fd, file_name, &st, 0);
+ if (status)
+ {
+ errno = e;
+ mkdir_error (file_name);
+ }
+ }
+
+ *cursor = '/';
+ if (status)
+ return status;
+ }
+
+ return 0;
+}
+
+/* Return true if FILE_NAME (with status *STP, if STP) is not a
+ directory, and has a time stamp newer than (or equal to) that of
+ TAR_STAT. */
+static bool
+file_newer_p (const char *file_name, struct stat const *stp,
+ struct tar_stat_info *tar_stat)
+{
+ 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:
+
+ /* With open ("symlink", O_NOFOLLOW|...), POSIX says errno == ELOOP,
+ but some operating systems do not conform to the standard. */
+#ifdef EFTYPE
+ /* NetBSD uses errno == EFTYPE; see <http://gnats.netbsd.org/43154>. */
+ case EFTYPE: