From: Eric Blake Date: Mon, 16 Nov 2009 21:35:41 +0000 (-0700) Subject: chown: work around OpenBSD bug X-Git-Tag: v0.1~5173 X-Git-Url: http://erislabs.org.uk/gitweb/?a=commitdiff_plain;h=84405cbc35207f178b1b50617254cb85ea803128;p=gnulib.git 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 --- diff --git a/ChangeLog b/ChangeLog index 64b11d23e..599a94413 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,16 @@ 2009-11-17 Eric Blake + 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 e5a80d6a1..88e25cd21 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 0686bf391..595a05ce9 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 edbccc6d6..66bb0c8fe 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 265c2f72e..19eb9c6c5 100644 --- a/lib/lchown.c +++ b/lib/lchown.c @@ -23,6 +23,7 @@ #include #include +#include #include #include @@ -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 5bedfa192..0dced4bce 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 +#include +#include +#include +#include +]], [[ 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 e40c43762..f0d67fe80 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 88d0cd458..4c296ac2c 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 233e33479..75672b44a 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 b0987c51c..a1e8b6809 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);