+/* Restore stat attributes (owner, group, mode and times) for
+ FILE_NAME, using information given in *ST.
+ If FD is nonnegative, it is a file descriptor for the file.
+ CURRENT_MODE and CURRENT_MODE_MASK specify information known about
+ the file's current mode, using the style of struct delayed_set_stat.
+ TYPEFLAG specifies the type of the file.
+ If INTERDIR, this is an intermediate directory.
+ ATFLAG specifies the flag to use when statting the file. */
+
+static void
+set_stat (char const *file_name,
+ struct tar_stat_info const *st,
+ int fd, mode_t current_mode, mode_t current_mode_mask,
+ char typeflag, bool interdir, int atflag)
+{
+ /* Do the utime before the chmod because some versions of utime are
+ broken and trash the modes of the file. */
+
+ if (! touch_option && ! interdir)
+ {
+ struct timespec ts[2];
+ if (incremental_option)
+ ts[0] = st->atime;
+ else
+ ts[0].tv_nsec = UTIME_OMIT;
+ ts[1] = st->mtime;
+
+ if (fdutimensat (fd, chdir_fd, file_name, ts, atflag) == 0)
+ {
+ if (incremental_option)
+ check_time (file_name, ts[0]);
+ check_time (file_name, ts[1]);
+ }
+ else if (typeflag != SYMTYPE || implemented (errno))
+ utime_error (file_name);
+ }
+
+ if (0 < same_owner_option && ! interdir)
+ {
+ /* Some systems allow non-root users to give files away. Once this
+ done, it is not possible anymore to change file permissions.
+ However, setting file permissions now would be incorrect, since
+ they would apply to the wrong user, and there would be a race
+ condition. So, don't use systems that allow non-root users to
+ give files away. */
+ uid_t uid = st->stat.st_uid;
+ gid_t gid = st->stat.st_gid;
+
+ if (fd_chown (fd, file_name, uid, gid, atflag) == 0)
+ {
+ /* Changing the owner can clear st_mode bits in some cases. */
+ if ((current_mode | ~ current_mode_mask) & S_IXUGO)
+ current_mode_mask &= ~ (current_mode & (S_ISUID | S_ISGID));
+ }
+ else if (typeflag != SYMTYPE || implemented (errno))
+ chown_error_details (file_name, uid, gid);
+ }
+
+ set_mode (file_name,
+ st->stat.st_mode & ~ current_umask,
+ 0 < same_permissions_option && ! interdir ? MODE_ALL : MODE_RWX,
+ fd, current_mode, current_mode_mask, typeflag, atflag);
+
+ /* 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: