bug-coreutils
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: Bug#249177: coreutils: chown is not POSIXLY_CORRECT even when the va


From: Paul Eggert
Subject: Re: Bug#249177: coreutils: chown is not POSIXLY_CORRECT even when the variable is set
Date: Tue, 08 Jun 2004 15:18:42 -0700
User-agent: Gnus/5.1006 (Gnus v5.10.6) Emacs/21.3 (gnu/linux)

Jim Meyering <address@hidden> writes:

> Shouldn't we should either deprecate and undocument that option or
> make it work once again?

Either is fine with me.  Here's a proposed patch to make it work.  I
noticed some other bugs in this area (also in chmod) and this patch
fixes them too.

2004-06-08  Paul Eggert  <address@hidden>

        Adjust chmod and chown to be similar if -c or -v are given.  In
        particular, a no-op chown is no longer reported as a change; this
        reverts to previous behavior.  Also, fix both commands so that -v
        report failures even if the failure is not due to the chmod or
        chown syscalls.

        * src/chmod.c (CH_NOT_APPLIED): New constant.
        (describe_change): Handle it.
        (process_file): Use it, if a symlink wasn't changed.
        (mode_changed): Return bool, not int.  Accept new argument
        NEW_MODE; all callers changed.  This lets us avoid statting the
        file unless the new mode has unusual bits.
        (process_file): Return -1 on error.  With -v, report all errors
        verbosely, not just some.

        * src/chown-core.c (change_file_owner): Return -1 on error, not
        1 sometimes and -1 on others.  Our caller ORs together our results,
        and (-1 | 1) == 0 on ones-complement hosts.
        With -v report all errors verbosely, not just some.
        Fix bug when chopt->root_dev_ino && !chopt->affect_symlink_referent:
        file_stats wasn't set properly in that case.

        * tests/chgrp/basic: Adjust to above changes.

Index: src/chmod.c
===================================================================
RCS file: /home/meyering/coreutils/cu/src/chmod.c,v
retrieving revision 1.101
diff -p -u -r1.101 chmod.c
--- src/chmod.c 27 Mar 2004 08:43:36 -0000      1.101
+++ src/chmod.c 8 Jun 2004 22:07:23 -0000
@@ -39,6 +39,7 @@
 
 enum Change_status
 {
+  CH_NOT_APPLIED,
   CH_SUCCEEDED,
   CH_FAILED,
   CH_NO_CHANGE_REQUESTED
@@ -100,19 +101,30 @@ static struct option const long_options[
   {0, 0, 0, 0}
 };
 
-static int
-mode_changed (const char *file, mode_t old_mode)
-{
-  struct stat new_stats;
+/* Return true if the chmodable permission bits of FILE changed.
+   The old mode was OLD_MODE, but it was changed to NEW_MODE.  */
 
-  if (stat (file, &new_stats))
+static bool
+mode_changed (char const *file, mode_t old_mode, mode_t new_mode)
+{
+  if (new_mode & (S_ISUID | S_ISGID | S_ISVTX))
     {
-      if (force_silent == 0)
-       error (0, errno, _("getting new attributes of %s"), quote (file));
-      return 0;
+      /* The new mode contains unusual bits that the call to chmod may
+        have silently cleared.  Check whether they actually changed.  */
+
+      struct stat new_stats;
+
+      if (stat (file, &new_stats) != 0)
+       {
+         if (!force_silent)
+           error (0, errno, _("getting new attributes of %s"), quote (file));
+         return 0;
+       }
+
+      new_mode = new_stats.st_mode;
     }
 
-  return old_mode != new_stats.st_mode;
+  return ((old_mode ^ new_mode) & CHMOD_MODE_BITS) != 0;
 }
 
 /* Tell the user how/if the MODE of FILE has been changed.
@@ -125,6 +137,13 @@ describe_change (const char *file, mode_
   char perms[11];              /* "-rwxrwxrwx" ls-style modes. */
   const char *fmt;
 
+  if (changed == CH_NOT_APPLIED)
+    {
+      printf (_("neither symbolic link %s nor referent has been changed\n"),
+             quote (file));
+      return;
+    }
+
   mode_string (mode, perms);
   perms[10] = '\0';            /* `mode_string' does not null terminate. */
   switch (changed)
@@ -146,70 +165,85 @@ describe_change (const char *file, mode_
 }
 
 /* Change the mode of FILE according to the list of operations CHANGES.
-   Return 0 if successful, 1 if errors occurred.  This function is called
+   Return 0 if successful, -1 if errors occurred.  This function is called
    once for every file system object that fts encounters.  */
 
 static int
 process_file (FTS *fts, FTSENT *ent, const struct mode_change *changes)
 {
-  const char *file_full_name = ent->fts_path;
-  const char *file = ent->fts_accpath;
-  const struct stat *sb = ent->fts_statp;
-  mode_t newmode;
+  char const *file_full_name = ent->fts_path;
+  char const *file = ent->fts_accpath;
+  const struct stat *file_stats = ent->fts_statp;
+  mode_t new_mode IF_LINT (= 0);
   int errors = 0;
-  int fail;
-  int saved_errno;
+  bool do_chmod;
+  bool symlink_changed = true;
 
   switch (ent->fts_info)
     {
+    case FTS_DP:
+      return 0;
+
     case FTS_NS:
       error (0, ent->fts_errno, _("cannot access %s"), quote (file_full_name));
-      return 1;
+      errors = -1;
+      break;
 
     case FTS_ERR:
       error (0, ent->fts_errno, _("%s"), quote (file_full_name));
-      return 1;
+      errors = -1;
+      break;
 
     case FTS_DNR:
       error (0, ent->fts_errno, _("cannot read directory %s"),
             quote (file_full_name));
-      return 1;
+      errors = -1;
+      break;
 
     default:
       break;
     }
 
-  /* If this is the second (post-order) encounter with a directory,
-     then return right away.  */
-  if (ent->fts_info == FTS_DP)
-    return 0;
+  do_chmod = !errors;
 
-  if (ROOT_DEV_INO_CHECK (root_dev_ino, sb))
+  if (do_chmod && ROOT_DEV_INO_CHECK (root_dev_ino, file_stats))
     {
       ROOT_DEV_INO_WARN (file_full_name);
-      return 1;
+      errors = -1;
+      do_chmod = false;
     }
 
-  if (S_ISLNK (sb->st_mode))
-    return 0;
-
-  newmode = mode_adjust (sb->st_mode, changes);
-
-  fail = chmod (file, newmode);
-  saved_errno = errno;
+  if (do_chmod)
+    {
+      new_mode = mode_adjust (file_stats->st_mode, changes);
 
-  if (verbosity == V_high
-      || (verbosity == V_changes_only
-         && !fail && mode_changed (file, sb->st_mode)))
-    describe_change (file_full_name, newmode,
-                    (fail ? CH_FAILED : CH_SUCCEEDED));
+      if (S_ISLNK (file_stats->st_mode))
+       symlink_changed = false;
+      else
+       {
+         errors = chmod (file, new_mode);
+
+         if (errors && !force_silent)
+           error (0, errno, _("changing permissions of %s"),
+                  quote (file_full_name));
+       }
+    }
 
-  if (fail)
+  if (verbosity != V_off)
     {
-      if (force_silent == 0)
-       error (0, saved_errno, _("changing permissions of %s"),
-              quote (file_full_name));
-      errors = 1;
+      bool changed =
+       (!errors && symlink_changed
+        && mode_changed (file, file_stats->st_mode, new_mode));
+
+      if (changed || verbosity == V_high)
+       {
+         enum Change_status ch_status =
+           (errors ? CH_FAILED
+            : !symlink_changed ? CH_NOT_APPLIED
+            : !changed ? CH_NO_CHANGE_REQUESTED
+            : CH_SUCCEEDED);
+         describe_change (file_full_name, new_mode, ch_status);
+       }
     }
 
   if ( ! recurse)
Index: src/chown-core.c
===================================================================
RCS file: /home/meyering/coreutils/cu/src/chown-core.c,v
retrieving revision 1.24
diff -p -u -r1.24 chown-core.c
--- src/chown-core.c    8 Jun 2004 14:57:57 -0000       1.24
+++ src/chown-core.c    8 Jun 2004 21:56:50 -0000
@@ -167,18 +167,19 @@ describe_change (const char *file, enum 
    to UID and/or GID as appropriate.
    FIXME: describe old_uid and old_gid.
    CHOPT specifies additional options.
-   Return nonzero upon error, zero otherwise.  */
+   Return 0 if successful, -1 if errors occurred.  */
 static int
 change_file_owner (FTS *fts, FTSENT *ent,
                   uid_t uid, gid_t gid,
                   uid_t old_uid, gid_t old_gid,
                   struct Chown_option const *chopt)
 {
-  const char *file_full_name = ent->fts_path;
+  char const *file_full_name = ent->fts_path;
+  char const *file = ent->fts_accpath;
   struct stat const *file_stats IF_LINT (= NULL);
   struct stat stat_buf;
   int errors = 0;
-  bool do_chown = true;
+  bool do_chown;
   bool symlink_changed = true;
 
   switch (ent->fts_info)
@@ -195,23 +196,30 @@ change_file_owner (FTS *fts, FTSENT *ent
 
     case FTS_NS:
       error (0, ent->fts_errno, _("cannot access %s"), quote (file_full_name));
-      return 1;
+      errors = -1;
+      break;
 
     case FTS_ERR:
       error (0, ent->fts_errno, _("%s"), quote (file_full_name));
-      return 1;
+      errors = -1;
+      break;
 
     case FTS_DNR:
       error (0, ent->fts_errno, _("cannot read directory %s"),
             quote (file_full_name));
-      return 1;
+      errors = -1;
+      break;
 
     default:
       break;
     }
 
-  if (old_uid != (uid_t) -1 || old_gid != (gid_t) -1
-      || (chopt->root_dev_ino && chopt->affect_symlink_referent))
+  if (errors)
+    do_chown = false;
+  else if (old_uid == (uid_t) -1 && old_gid == (gid_t) -1
+          && chopt->verbosity == V_off && ! chopt->root_dev_ino)
+    do_chown = true;
+  else
     {
       file_stats = ent->fts_statp;
 
@@ -219,32 +227,30 @@ change_file_owner (FTS *fts, FTSENT *ent
         stat it to get the permissions of the referent.  */
       if (S_ISLNK (file_stats->st_mode) && chopt->affect_symlink_referent)
        {
-         if (stat (ent->fts_accpath, &stat_buf) != 0)
+         if (stat (file, &stat_buf) != 0)
            {
              error (0, errno, _("cannot dereference %s"),
                     quote (file_full_name));
-             return 1;
+             errors = -1;
            }
 
          file_stats = &stat_buf;
        }
 
-      do_chown = ((old_uid == (uid_t) -1
-                  || file_stats->st_uid == old_uid)
-                 && (old_gid == (gid_t) -1
-                     || file_stats->st_gid == old_gid));
+      do_chown = (!errors
+                 && (old_uid == (uid_t) -1 || old_uid == file_stats->st_uid)
+                 && (old_gid == (gid_t) -1 || old_gid == file_stats->st_gid));
     }
 
-  if (do_chown)
+  if (do_chown && ROOT_DEV_INO_CHECK (chopt->root_dev_ino, file_stats))
     {
-      const char *file = ent->fts_accpath;
-
-      if (ROOT_DEV_INO_CHECK (chopt->root_dev_ino, file_stats))
-       {
-         ROOT_DEV_INO_WARN (file_full_name);
-         return 1;
-       }
+      ROOT_DEV_INO_WARN (file_full_name);
+      errors = -1;
+      do_chown = false;
+    }
 
+  if (do_chown)
+    {
       if (chopt->affect_symlink_referent)
        {
          /* Applying chown to a symlink and expecting it to affect
@@ -280,15 +286,23 @@ change_file_owner (FTS *fts, FTSENT *ent
               quote (file_full_name));
     }
 
-  if (chopt->verbosity == V_high
-      || (chopt->verbosity == V_changes_only && !errors))
+  if (chopt->verbosity != V_off)
     {
-      enum Change_status ch_status = (!do_chown ? CH_NO_CHANGE_REQUESTED
-                                     : !symlink_changed ? CH_NOT_APPLIED
-                                     : errors ? CH_FAILED
-                                     : CH_SUCCEEDED);
-      describe_change (file_full_name, ch_status,
-                      chopt->user_name, chopt->group_name);
+      bool changed =
+       (do_chown && !errors && symlink_changed
+        && ! ((uid == (uid_t) -1 || uid == file_stats->st_uid)
+              && (gid == (gid_t) -1 || gid == file_stats->st_gid)));
+
+      if (changed || chopt->verbosity == V_high)
+       {
+         enum Change_status ch_status =
+           (errors ? CH_FAILED
+            : !symlink_changed ? CH_NOT_APPLIED
+            : !changed ? CH_NO_CHANGE_REQUESTED
+            : CH_SUCCEEDED);
+         describe_change (file_full_name, ch_status,
+                          chopt->user_name, chopt->group_name);
+       }
     }
 
   if ( ! chopt->recurse)
@@ -315,9 +329,8 @@ chown_files (char **files, int bit_flags
   int fail = 0;
 
   /* Use lstat and stat only if they're needed.  */
-  int stat_flags = ((chopt->root_dev_ino
-                    || required_uid != (uid_t) -1
-                    || required_gid != (gid_t) -1)
+  int stat_flags = ((required_uid != (uid_t) -1 || required_gid != (gid_t) -1
+                    || chopt->verbosity != V_off || chopt->root_dev_ino)
                    ? 0
                    : FTS_NOSTAT);
 
Index: tests/chgrp/basic
===================================================================
RCS file: /home/meyering/coreutils/cu/tests/chgrp/basic,v
retrieving revision 1.16
diff -p -u -r1.16 basic
--- tests/chgrp/basic   8 Jun 2004 16:58:28 -0000       1.16
+++ tests/chgrp/basic   8 Jun 2004 19:38:38 -0000
@@ -87,9 +87,8 @@ test "$VERBOSE" = yes && set +x
 cat <<\EOF > expected
 changed group of `f' to G1
 changed group of `f' to G2
-changed group of `f' to G2
-changed group of `f' to G1
 changed group of `f' to G1
+group of `f' retained as G1
 changed group of `f' to G2
 changed group of `d/f3' to G2
 changed group of `d' to G2
@@ -100,7 +99,6 @@ changed group of `d' to G2
 changed group of `d/f3' to G1
 changed group of `d' to G1
 changed group of `d' to G2
-changed group of `symlink' to G2
 changed ownership of `f' to :G2
 changed group of `symlink' to G1
 changed ownership of `f' to :G2




reply via email to

[Prev in Thread] Current Thread [Next in Thread]