]> git.ipfire.org Git - thirdparty/coreutils.git/commitdiff
Declare lstat.
authorJim Meyering <jim@meyering.net>
Fri, 15 Dec 2000 13:24:47 +0000 (13:24 +0000)
committerJim Meyering <jim@meyering.net>
Fri, 15 Dec 2000 13:24:47 +0000 (13:24 +0000)
Rename change_symlinks member to `dereference' and use the DEREF_*
enum values.
(describe_change): Merge the chgrp and chown switch statements.
Use xmalloc to form the `user:group' string.
(change_file_owner): Record (and later, use) is_symlink and is_directory
from the lstat stats, in order to control whether we operate on symlinks
and whether (with -R) we traverse symlinks to directories.
When dereferencing, use open/fchown (rather than chown) on symlinks.

src/chown-core.c

index 0d7a1c43d637ed1ac93f53886f4589d624d75af3..91e2559d91d393317f61d6c4c20184121dbdfdf4 100644 (file)
@@ -36,11 +36,13 @@ struct group *getgrnam ();
 struct group *getgrgid ();
 #endif
 
+int lstat ();
+
 void
 chopt_init (struct Chown_option *chopt)
 {
   chopt->verbosity = V_off;
-  chopt->change_symlinks = 1;
+  chopt->dereference = DEREF_NEVER;
   chopt->recurse = 0;
   chopt->force_silent = 0;
   chopt->user_name = 0;
@@ -79,6 +81,8 @@ describe_change (const char *file, enum Change_status changed,
                 char const *user, char const *group)
 {
   const char *fmt;
+  char *spec;
+  int spec_allocated = 0;
 
   if (changed == CH_NOT_APPLIED)
     {
@@ -87,46 +91,49 @@ describe_change (const char *file, enum Change_status changed,
       return;
     }
 
-  if (user == NULL)
+  if (user)
     {
-      switch (changed)
+      if (group)
+       {
+         spec = xmalloc (strlen (user) + 1 + strlen (group) + 1);
+         stpcpy (stpcpy (stpcpy (spec, user), ":"), group);
+         spec_allocated = 1;
+       }
+      else
        {
-       case CH_SUCCEEDED:
-         fmt = _("group of %s changed to %s\n");
-         break;
-       case CH_FAILED:
-         fmt = _("failed to change group of %s to %s\n");
-         break;
-       case CH_NO_CHANGE_REQUESTED:
-         fmt = _("group of %s retained as %s\n");
-         break;
-       default:
-         abort ();
+         spec = (char *) user;
        }
-      printf (fmt, quote (file), group);
     }
   else
     {
-      switch (changed)
-       {
-       case CH_SUCCEEDED:
-         fmt = _("ownership of %s changed to ");
-         break;
-       case CH_FAILED:
-         fmt = _("failed to change ownership of %s to ");
-         break;
-       case CH_NO_CHANGE_REQUESTED:
-         fmt = _("ownership of %s retained as ");
-         break;
-       default:
-         abort ();
-       }
-      printf (fmt, file);
-      if (group)
-       printf ("%s.%s\n", user, group);
-      else
-       printf ("%s\n", user);
+      spec = (char *) group;
+    }
+
+  switch (changed)
+    {
+    case CH_SUCCEEDED:
+      fmt = (user
+            ? _("changed ownership of %s to %s\n")
+            : _("changed group of %s to %s\n"));
+      break;
+    case CH_FAILED:
+      fmt = (user
+            ? _("failed to change ownership of %s to %s\n")
+            : _("failed to change group of %s to %s\n"));
+      break;
+    case CH_NO_CHANGE_REQUESTED:
+      fmt = (user
+            ? _("ownership of %s retained as %s\n")
+            : _("group of %s retained as %s\n"));
+      break;
+    default:
+      abort ();
     }
+
+  printf (fmt, quote (file), spec);
+
+  if (spec_allocated)
+    free (spec);
 }
 
 /* Recursively change the ownership of the files in directory DIR
@@ -194,6 +201,8 @@ change_file_owner (int cmdline_arg, const char *file, uid_t user, gid_t group,
   uid_t newuser;
   gid_t newgroup;
   int errors = 0;
+  int is_symlink;
+  int is_directory;
 
   if (lstat (file, &file_stats))
     {
@@ -202,8 +211,33 @@ change_file_owner (int cmdline_arg, const char *file, uid_t user, gid_t group,
       return 1;
     }
 
-  if ((old_user == (uid_t) -1  || file_stats.st_uid == old_user) &&
-      (old_group == (gid_t) -1 || file_stats.st_gid == old_group))
+  /* If it's a symlink and we're dereferencing, then use stat
+     to get the attributes of the referent.  */
+  if (S_ISLNK (file_stats.st_mode))
+    {
+      if (chopt->dereference == DEREF_ALWAYS
+         && stat (file, &file_stats))
+       {
+         if (chopt->force_silent == 0)
+           error (0, errno, _("getting attributes of %s"), quote (file));
+         return 1;
+       }
+
+      is_symlink = 1;
+
+      /* With -R, don't traverse through symlinks-to-directories.
+        But of course, this will all change with POSIX's new
+        -H, -L, -P options.  */
+      is_directory = 0;
+    }
+  else
+    {
+      is_symlink = 0;
+      is_directory = S_ISDIR (file_stats.st_mode);
+    }
+
+  if ((old_user == (uid_t) -1 || file_stats.st_uid == old_user)
+      && (old_group == (gid_t) -1 || file_stats.st_gid == old_group))
     {
       newuser = user == (uid_t) -1 ? file_stats.st_uid : user;
       newgroup = group == (gid_t) -1 ? file_stats.st_gid : group;
@@ -212,17 +246,35 @@ change_file_owner (int cmdline_arg, const char *file, uid_t user, gid_t group,
          int fail;
          int symlink_changed = 1;
          int saved_errno;
+         int called_lchown = 0;
 
-         if (S_ISLNK (file_stats.st_mode) && chopt->change_symlinks)
+         if (is_symlink)
            {
-             fail = lchown (file, newuser, newgroup);
+             if (chopt->dereference == DEREF_NEVER)
+               {
+                 called_lchown = 1;
+                 fail = lchown (file, newuser, newgroup);
 
-             /* Ignore the failure if it's due to lack of support (ENOSYS)
-                and this is not a command line argument.  */
-             if (!cmdline_arg && fail && errno == ENOSYS)
+                 /* Ignore the failure if it's due to lack of support (ENOSYS)
+                    and this is not a command line argument.  */
+                 if (!cmdline_arg && fail && errno == ENOSYS)
+                   {
+                     fail = 0;
+                     symlink_changed = 0;
+                   }
+               }
+             else if (chopt->dereference == DEREF_ALWAYS)
+               {
+                 /* Applying chown to a symlink and expecting it to affect
+                    the referent is not portable.  So instead, open the
+                    file and use fchown on the resulting descriptor.  */
+                 int fd = open (file, O_RDONLY | O_NONBLOCK | O_NOCTTY);
+                 fail = (fd == -1 ? 1 : fchown (fd, newuser, newgroup));
+               }
+             else
                {
-                 fail = 0;
-                 symlink_changed = 0;
+                 /* FIXME */
+                 abort ();
                }
            }
          else
@@ -257,9 +309,10 @@ change_file_owner (int cmdline_arg, const char *file, uid_t user, gid_t group,
                 resets the `special' permission bits.  When run by a
                 `privileged' user, this program must ensure that at least
                 the set-uid and set-group ones are still set.  */
-             if (file_stats.st_mode & ~S_IRWXUGO
-                 /* If this is a symlink and we changed *it*, then skip it.  */
-                 && ! (S_ISLNK (file_stats.st_mode) && chopt->change_symlinks))
+             if (file_stats.st_mode & ~(S_IFMT | S_IRWXUGO)
+                 /* If we called lchown above (which means this is a symlink),
+                    then skip it.  */
+                 && ! called_lchown)
                {
                  if (chmod (file, file_stats.st_mode))
                    {
@@ -278,7 +331,7 @@ change_file_owner (int cmdline_arg, const char *file, uid_t user, gid_t group,
        }
     }
 
-  if (chopt->recurse && S_ISDIR (file_stats.st_mode))
+  if (chopt->recurse && is_directory)
     errors |= change_dir_owner (file, user, group,
                                old_user, old_group, &file_stats, chopt);
   return errors;