- /* Do nothing unless we are restoring the original permissions.
-
- We must force permission when -k and -U are 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 or -U 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 ((we_are_root || same_permissions_option)
- && ((!keep_old_files_option && !unlink_first_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;
+ int chmod_errno;
+
+ if (0 < same_permissions_option
+ && permstatus != INTERDIR_PERMSTATUS)
+ {
+ mode = stat_info->st_mode;
+
+ /* If we created the file and it has a mode that we set already
+ with O_CREAT, then its mode is often set correctly already.
+ But if we are changing ownership, the mode's group and and
+ other permission bits were omitted originally, so it's less
+ likely that the mode is OK now. Also, 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 & ~ (0 < same_owner_option ? S_IRWXU : 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 (! cur_info)
+ {
+ if (fdstat (file_name, fd, &st) != 0)
+ {
+ stat_error (file_name);
+ return;
+ }
+ cur_info = &st;
+ }
+ mode = cur_info->st_mode ^ invert_permissions;
+ }
+
+ chmod_errno = fdchmod (file_name, fd, mode) == 0 ? 0 : errno;
+ if (chmod_errno == EPERM && (mode & S_ISUID) != 0)
+ {
+ /* On Solaris, chmod may fail if we don't have PRIV_ALL, because
+ setuid-root files would otherwise be a backdoor. See
+ http://opensolaris.org/jive/thread.jspa?threadID=95826
+ (2009-09-03). */
+ if (priv_set_restore_linkdir () == 0)
+ {
+ chmod_errno = fdchmod (file_name, fd, mode) == 0 ? 0 : errno;
+ priv_set_remove_linkdir ();
+ }
+ }
+ if (chmod_errno)
+ {
+ errno = chmod_errno;
+ 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, struct timespec t)
+{
+ if (t.tv_sec <= 0)
+ WARNOPT (WARN_TIMESTAMP,
+ (0, 0, _("%s: implausibly old time stamp %s"),
+ file_name, tartime (t, true)));
+ else if (timespec_cmp (volume_start_time, t) < 0)
+ {
+ struct timespec now;
+ gettime (&now);
+ if (timespec_cmp (now, t) < 0)
+ {
+ char buf[TIMESPEC_STRSIZE_BOUND];
+ struct timespec diff;
+ diff.tv_sec = t.tv_sec - now.tv_sec;
+ diff.tv_nsec = t.tv_nsec - now.tv_nsec;
+ if (diff.tv_nsec < 0)
+ {
+ diff.tv_nsec += BILLION;
+ diff.tv_sec--;
+ }
+ WARNOPT (WARN_TIMESTAMP,
+ (0, 0, _("%s: time stamp %s is %s s in the future"),
+ file_name, tartime (t, true), code_timespec (diff, buf)));
+ }
+ }