+/* 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,
+
+ /* 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;
+ 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;
+
+/* 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 = extract_finish;
+
+ /* 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 (0 < same_permissions_option)
+ current_umask = 0;
+ else
+ {
+ umask (newdir_umask); /* restore the kernel umask */
+ current_umask = newdir_umask;
+ }
+}
+
+/* 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 const *file_name, struct stat const *stat_info,
+ mode_t invert_permissions, enum permstatus permstatus,
+ char typeflag)
+{
+ 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)
+ 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
+ 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
+ probably more configuration. For now, just use lchown if it exists, and
+ punt for the rest. Sigh! */
+
+static void
+set_stat (char const *file_name, struct stat const *stat_info,
+ mode_t invert_permissions, enum permstatus permstatus,
+ char typeflag)
+{
+ struct utimbuf utimbuf;
+
+ 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 && 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
+ which case .st_atime is used. */
+
+ /* FIXME: incremental_option should set ctime too, but how? */
+
+ if (incremental_option)
+ utimbuf.actime = stat_info->st_atime;
+ else
+ utimbuf.actime = start_time;
+
+ utimbuf.modtime = stat_info->st_mtime;
+
+ 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
+ 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,
+ invert_permissions, permstatus, typeflag);
+ }
+
+ if (0 < same_owner_option && permstatus != INTERDIR_PERMSTATUS)
+ {
+ /* 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 (typeflag == SYMTYPE)
+ {
+#if HAVE_LCHOWN
+ if (lchown (file_name, stat_info->st_uid, stat_info->st_gid) < 0)
+ chown_error_details (file_name,
+ stat_info->st_uid, stat_info->st_gid);