diff lib/lchown.c @ 12314:e92d9385e4ca

chown: work around OpenBSD bug chown(name,geteuid(),-1) failed to update the change time if name was already owned by the current effective user. Work around it by using chmod, which does not have this bug. Unfortunately, lchown has the same bug, but OpenBSD 4.0 lacks lchmod and lutimes, so there is no way to affect ctime without unlinking and recreating the symlink, which is too dangerous. * lib/chown.c (rpl_chown): Work around the bug. * lib/lchown.c (rpl_lchown): Attempt to do likewise. * m4/chown.m4 (gl_FUNC_CHOWN): Test for ctime bug. * m4/lchown.m4 (gl_FUNC_LCHOWN): Check for lchmod. * modules/chown (Depends-on): Add stdbool. * modules/lchown (Depends-on): Likewise. * doc/posix-functions/chown.texi (chown): Document the bug. * doc/posix-functions/lchown.texi (lchown): Likewise. * tests/test-lchown.h (test_chown): Relax test. Signed-off-by: Eric Blake <ebb9@byu.net>
author Eric Blake <ebb9@byu.net>
date Mon, 16 Nov 2009 14:35:41 -0700
parents 7e3695d9b328
children b5e42ef33b49
line wrap: on
line diff
--- a/lib/lchown.c
+++ b/lib/lchown.c
@@ -23,6 +23,7 @@
 #include <unistd.h>
 
 #include <errno.h>
+#include <stdbool.h>
 #include <string.h>
 #include <sys/stat.h>
 
@@ -69,10 +70,47 @@
 int
 rpl_lchown (const char *file, uid_t uid, gid_t gid)
 {
-  size_t len = strlen (file);
-  if (len && file[len - 1] == '/')
-    return chown (file, uid, gid);
-  return lchown (file, uid, gid);
+  struct stat st;
+  bool stat_valid = false;
+  int result;
+
+# if CHOWN_CHANGE_TIME_BUG
+  if (gid != (gid_t) -1 || uid != (uid_t) -1)
+    {
+      if (lstat (file, &st))
+        return -1;
+      stat_valid = true;
+      if (!S_ISLNK (st.st_mode))
+        return chown (file, uid, gid);
+    }
+# endif
+
+# if CHOWN_TRAILING_SLASH_BUG
+  if (!stat_valid)
+    {
+      size_t len = strlen (file);
+      if (len && file[len - 1] == '/')
+        return chown (file, uid, gid);
+    }
+# endif
+
+  result = lchown (file, uid, gid);
+
+# if CHOWN_CHANGE_TIME_BUG && HAVE_LCHMOD
+  if (result == 0 && stat_valid
+      && (uid == st.st_uid || uid == (uid_t) -1)
+      && (gid == st.st_gid || gid == (gid_t) -1))
+    {
+      /* No change in ownership, but at least one argument was not -1,
+         so we are required to update ctime.  Since lchown succeeded,
+         we assume that lchmod will do likewise.  But if the system
+         lacks lchmod and lutimes, we are out of luck.  Oh well.  */
+      result = lchmod (file, st.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO
+                                           | S_ISUID | S_ISGID | S_ISVTX));
+    }
+# endif
+
+  return result;
 }
 
 #endif /* HAVE_LCHOWN */