summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ChangeLog11
-rw-r--r--doc/posix-functions/chown.texi4
-rw-r--r--doc/posix-functions/lchown.texi5
-rw-r--r--lib/chown.c71
-rw-r--r--lib/lchown.c46
-rw-r--r--m4/chown.m440
-rw-r--r--m4/lchown.m412
-rw-r--r--modules/chown3
-rw-r--r--modules/lchown1
-rw-r--r--tests/test-lchown.h12
10 files changed, 171 insertions, 34 deletions
diff --git a/ChangeLog b/ChangeLog
index 64b11d23e1..599a94413f 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,16 @@
2009-11-17 Eric Blake <ebb9@byu.net>
+ chown: work around OpenBSD bug
+ * 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.
+
mkstemp: avoid conflict with C++ keyword template
* lib/mkdtemp.c (mkdtemp): Change spelling of template.
* lib/mkostemp.c (mkostemp): Likewise.
diff --git a/doc/posix-functions/chown.texi b/doc/posix-functions/chown.texi
index e5a80d6a1f..88e25cd21b 100644
--- a/doc/posix-functions/chown.texi
+++ b/doc/posix-functions/chown.texi
@@ -13,6 +13,10 @@ Some platforms fail to detect trailing slash on non-directories, as in
@code{chown("link-to-file/",uid,gid)}:
FreeBSD 7.2, Solaris 9.
@item
+Some platforms fail to update the change time when at least one
+argument was not -1, but no ownership changes resulted:
+OpenBSD 4.0.
+@item
When passed an argument of -1, some implementations really set the owner
user/group id of the file to this value, rather than leaving that id of the
file alone.
diff --git a/doc/posix-functions/lchown.texi b/doc/posix-functions/lchown.texi
index 0686bf3917..595a05ce95 100644
--- a/doc/posix-functions/lchown.texi
+++ b/doc/posix-functions/lchown.texi
@@ -13,6 +13,11 @@ Some platforms fail to detect trailing slash on non-directories, as in
@code{lchown("link-to-file/",uid,gid)}:
FreeBSD 7.2, Solaris 9.
@item
+Some platforms fail to update the change time when at least one
+argument was not -1, but no ownership changes resulted. However,
+without @code{lchmod}, the replacement only fixes this for non-symlinks:
+OpenBSD 4.0.
+@item
This function is missing on some platforms; however, the replacement
fails on symlinks if @code{chown} is supported, and fails altogether
with @code{ENOSYS} otherwise:
diff --git a/lib/chown.c b/lib/chown.c
index edbccc6d6e..66bb0c8fe1 100644
--- a/lib/chown.c
+++ b/lib/chown.c
@@ -59,20 +59,29 @@ chown (const char *file _UNUSED_PARAMETER_, uid_t uid _UNUSED_PARAMETER_,
int
rpl_chown (const char *file, uid_t uid, gid_t 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 (stat (file, &st))
+ return -1;
+ stat_valid = true;
+ }
+# endif
+
# if CHOWN_FAILS_TO_HONOR_ID_OF_NEGATIVE_ONE
if (gid == (gid_t) -1 || uid == (uid_t) -1)
{
- struct stat file_stats;
-
/* Stat file to get id(s) that should remain unchanged. */
- if (stat (file, &file_stats))
+ if (!stat_valid && stat (file, &st))
return -1;
-
if (gid == (gid_t) -1)
- gid = file_stats.st_gid;
-
+ gid = st.st_gid;
if (uid == (uid_t) -1)
- uid = file_stats.st_uid;
+ uid = st.st_uid;
}
# endif
@@ -89,15 +98,18 @@ rpl_chown (const char *file, uid_t uid, gid_t gid)
|| (errno == EACCES
&& 0 <= (fd = open (file, O_WRONLY | open_flags))))
{
- int result = fchown (fd, uid, gid);
- int saved_errno = errno;
+ int saved_errno;
+ bool fchown_socket_failure;
- /* POSIX says fchown can fail with errno == EINVAL on sockets,
- so fall back on chown in that case. */
- struct stat sb;
- bool fchown_socket_failure =
+ result = fchown (fd, uid, gid);
+ saved_errno = errno;
+
+ /* POSIX says fchown can fail with errno == EINVAL on sockets
+ and pipes, so fall back on chown in that case. */
+ fchown_socket_failure =
(result != 0 && saved_errno == EINVAL
- && fstat (fd, &sb) == 0 && S_ISFIFO (sb.st_mode));
+ && fstat (fd, &st) == 0
+ && (S_ISFIFO (st.st_mode) || S_ISSOCK (st.st_mode)));
close (fd);
@@ -113,15 +125,32 @@ rpl_chown (const char *file, uid_t uid, gid_t gid)
# endif
# if CHOWN_TRAILING_SLASH_BUG
- {
- size_t len = strlen (file);
- struct stat st;
- if (len && file[len - 1] == '/' && stat (file, &st))
- return -1;
- }
+ if (!stat_valid)
+ {
+ size_t len = strlen (file);
+ if (len && file[len - 1] == '/' && stat (file, &st))
+ return -1;
+ }
+# endif
+
+ result = chown (file, uid, gid);
+
+# if CHOWN_CHANGE_TIME_BUG
+ 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 chown succeeded,
+ we assume that chmod will do likewise. Fortunately, on all
+ known systems where a 'no-op' chown skips the ctime update, a
+ 'no-op' chmod still does the trick. */
+ result = chmod (file, st.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO
+ | S_ISUID | S_ISGID | S_ISVTX));
+ }
# endif
- return chown (file, uid, gid);
+ return result;
}
#endif /* HAVE_CHOWN */
diff --git a/lib/lchown.c b/lib/lchown.c
index 265c2f72ec..19eb9c6c57 100644
--- 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 @@ lchown (const char *file, uid_t uid, gid_t gid)
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 */
diff --git a/m4/chown.m4 b/m4/chown.m4
index 5bedfa192b..0dced4bce0 100644
--- a/m4/chown.m4
+++ b/m4/chown.m4
@@ -1,4 +1,4 @@
-# serial 21
+# serial 22
# Determine whether we need the chown wrapper.
dnl Copyright (C) 1997-2001, 2003-2005, 2007, 2009
@@ -22,20 +22,27 @@ AC_DEFUN_ONCE([gl_FUNC_CHOWN],
AC_REQUIRE([gl_FUNC_CHOWN_FOLLOWS_SYMLINK])
AC_CHECK_FUNCS_ONCE([chown fchown])
+ dnl mingw lacks chown altogether.
if test $ac_cv_func_chown = no; then
HAVE_CHOWN=0
AC_LIBOBJ([chown])
else
+ dnl Some old systems treated chown like lchown.
if test $gl_cv_func_chown_follows_symlink = no; then
REPLACE_CHOWN=1
AC_LIBOBJ([chown])
fi
+
+ dnl Some old systems tried to use uid/gid -1 literally.
if test $ac_cv_func_chown_works = no; then
AC_DEFINE([CHOWN_FAILS_TO_HONOR_ID_OF_NEGATIVE_ONE], [1],
[Define if chown is not POSIX compliant regarding IDs of -1.])
REPLACE_CHOWN=1
AC_LIBOBJ([chown])
fi
+
+ dnl Solaris 9 ignores trailing slash.
+ dnl FreeBSD 7.2 mishandles trailing slash on symlinks.
AC_CACHE_CHECK([whether chown honors trailing slash],
[gl_cv_func_chown_slash_works],
[touch conftest.file && rm -f conftest.link
@@ -52,10 +59,39 @@ AC_DEFUN_ONCE([gl_FUNC_CHOWN],
rm -f conftest.link conftest.file])
if test "$gl_cv_func_chown_slash_works" != yes; then
AC_DEFINE([CHOWN_TRAILING_SLASH_BUG], [1],
- [Define if chown mishandles trailing slash.])
+ [Define to 1 if chown mishandles trailing slash.])
REPLACE_CHOWN=1
AC_LIBOBJ([chown])
fi
+
+ dnl OpenBSD fails to update ctime if ownership does not change.
+ AC_CACHE_CHECK([whether chown always updates ctime],
+ [gl_cv_func_chown_ctime_works],
+ [AC_RUN_IFELSE([AC_LANG_PROGRAM([[
+#include <unistd.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+]], [[ struct stat st1, st2;
+ if (close (creat ("conftest.file", 0600))) return 1;
+ if (stat ("conftest.file", &st1)) return 2;
+ sleep (1);
+ if (chown ("conftest.file", st1.st_uid, st1.st_gid)) return 3;
+ if (stat ("conftest.file", &st2)) return 4;
+ if (st2.st_ctime <= st1.st_ctime) return 5;
+ ]])],
+ [gl_cv_func_chown_ctime_works=yes],
+ [gl_cv_func_chown_ctime_works=no],
+ [gl_cv_func_chown_ctime_works="guessing no"])
+ rm -f conftest.file])
+ if test "$gl_cv_func_chown_ctime_works" != yes; then
+ AC_DEFINE([CHOWN_CHANGE_TIME_BUG], [1], [Define to 1 if chown fails
+ to change ctime when at least one argument was not -1.])
+ REPLACE_CHOWN=1
+ AC_LIBOBJ([chown])
+ fi
+
if test $REPLACE_CHOWN = 1 && test $ac_cv_func_fchown = no; then
AC_LIBOBJ([fchown-stub])
fi
diff --git a/m4/lchown.m4 b/m4/lchown.m4
index e40c437626..f0d67fe806 100644
--- a/m4/lchown.m4
+++ b/m4/lchown.m4
@@ -1,4 +1,4 @@
-# serial 14
+# serial 15
# Determine whether we need the lchown wrapper.
dnl Copyright (C) 1998, 2001, 2003-2007, 2009 Free Software
@@ -9,18 +9,20 @@ dnl gives unlimited permission to copy and/or distribute it,
dnl with or without modifications, as long as this notice is preserved.
dnl From Jim Meyering.
-dnl Provide lchown on systems that lack it, and work around trailing
-dnl slash bugs on systems that have it.
+dnl Provide lchown on systems that lack it, and work around bugs
+dnl on systems that have it.
AC_DEFUN([gl_FUNC_LCHOWN],
[
AC_REQUIRE([gl_UNISTD_H_DEFAULTS])
AC_REQUIRE([gl_FUNC_CHOWN])
+ AC_CHECK_FUNCS_ONCE([lchmod])
AC_REPLACE_FUNCS([lchown])
if test $ac_cv_func_lchown = no; then
HAVE_LCHOWN=0
- elif test "$gl_cv_func_chown_slash_works" != yes; then
- dnl Trailing slash bugs in chown also occur in lchown.
+ elif test "$gl_cv_func_chown_slash_works" != yes \
+ || test "$gl_cv_func_chown_ctime_works" != yes; then
+ dnl Trailing slash and ctime bugs in chown also occur in lchown.
AC_LIBOBJ([lchown])
REPLACE_LCHOWN=1
fi
diff --git a/modules/chown b/modules/chown
index 88d0cd458f..4c296ac2cc 100644
--- a/modules/chown
+++ b/modules/chown
@@ -8,9 +8,10 @@ m4/chown.m4
Depends-on:
open
-unistd
stat
+stdbool
sys_stat
+unistd
configure.ac:
gl_FUNC_CHOWN
diff --git a/modules/lchown b/modules/lchown
index 233e334790..75672b44a1 100644
--- a/modules/lchown
+++ b/modules/lchown
@@ -9,6 +9,7 @@ Depends-on:
chown
errno
lstat
+stdbool
sys_stat
unistd
diff --git a/tests/test-lchown.h b/tests/test-lchown.h
index b0987c51cc..a1e8b68099 100644
--- a/tests/test-lchown.h
+++ b/tests/test-lchown.h
@@ -75,6 +75,14 @@ nap (void)
# define getegid() (-1)
#endif
+#ifndef HAVE_LCHMOD
+# define HAVE_LCHMOD 0
+#endif
+
+#ifndef CHOWN_CHANGE_TIME_BUG
+# define CHOWN_CHANGE_TIME_BUG 0
+#endif
+
/* This file is designed to test lchown(n,o,g) and
chownat(AT_FDCWD,n,o,g,AT_SYMLINK_NOFOLLOW). FUNC is the function
to test. Assumes that BASE and ASSERT are already defined, and
@@ -251,8 +259,10 @@ test_lchown (int (*func) (char const *, uid_t, gid_t), bool print)
ASSERT (st1.st_uid == st2.st_uid);
ASSERT (gids[0] == st2.st_gid);
}
- else
+ else if (!CHOWN_CHANGE_TIME_BUG || HAVE_LCHMOD)
{
+ /* If we don't have lchmod, and lchown fails to change ctime,
+ then we can't test this part of lchown. */
struct stat l1;
struct stat l2;
ASSERT (stat (BASE "dir/file", &st1) == 0);