[Top][All Lists]
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
Re: pending patches?
From: |
Eric Blake |
Subject: |
Re: pending patches? |
Date: |
Sat, 14 Nov 2009 12:48:30 -0700 |
User-agent: |
Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.23) Gecko/20090812 Thunderbird/2.0.0.23 Mnenhy/0.7.6.666 |
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1
According to Jim Meyering on 11/14/2009 12:20 AM:
>> Subject: [PATCH 1/2] chown: detect Solaris and FreeBSD bug
>>
>> Solaris 9 and FreeBSD 7.2 chown("link-to-file/",uid,gid)
>> mistakenly changes ownership of "file".
>>
> Looks nice. Solaris 9 and FreeBSD users should be grateful.
> I'll test after you push.
It took some more tweaks (test-chown.h can't call chdir, or it invalidates
using paths relative to "." within test-fchownat.c), but I have now
expanded the test to cover fchownat (Solaris 9 has fchownat, but it
ignores trailing slash), and completed successful testing on cygwin,
Linux, NetBSD, FreeBSD, Solaris 9, Solaris 10, and a skip on mingw. I
went ahead and pushed this.
However, I'm now seeing a failure on OpenBSD, where chown refuses to
change ctime on files if there is no change to ownership:
$ touch a
$ gstat -c %z a
2009-11-14 20:06:35.175893000 +0100
$ ktrace chgrp ericb a
$ kdump
...
20413 chgrp CALL chown(0x836d6000,0xffffffff,0x3f0)
20413 chgrp NAMI "a"
20413 chgrp RET chown 0
...
$ gstat -c %z a
2009-11-14 20:06:35.175893000 +0100
$
So, even though the syscall is taking place, the ctime is not getting
updated. I confirmed that when the gid changes, ctime is indeed updating.
Maybe I'll have to add a call to utimensat to force the ctime, unless
there is some easier call to force a ctime update without impacting atime
and mtime (or at most, affecting them by truncating to microseconds due to
missing utimensat).
- --
Don't work too hard, make some time for fun as well!
Eric Blake address@hidden
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.9 (Cygwin)
Comment: Public key at home.comcast.net/~ericblake/eblake.gpg
Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org/
iEYEARECAAYFAkr/CY4ACgkQ84KuGfSFAYD5wgCfcqfnrA7rIa+I6r+ZYfmheNTH
OZ4AoKNd0ITGNQZhxfYma69ey9WG0wzA
=7oXQ
-----END PGP SIGNATURE-----
>From 9cd8acbae7f8ca3985001de0752b22e2a28d0cd7 Mon Sep 17 00:00:00 2001
From: Eric Blake <address@hidden>
Date: Thu, 12 Nov 2009 21:45:20 -0700
Subject: [PATCH 1/3] chown: detect Solaris and FreeBSD bug
Solaris 9 and FreeBSD 7.2 chown("link-to-file/",uid,gid)
mistakenly changes ownership of "file".
* lib/chown.c (rpl_chown): Work around bug.
* m4/chown.m4 (gl_FUNC_CHOWN): Check for trailing slash bugs.
(gl_PREREQ_CHOWN): Delete.
* m4/unistd_h.m4 (gl_UNISTD_H_DEFAULTS): Add witness.
* modules/unistd (Makefile.am): Populate it.
* lib/unistd.in.h (chown): Update declaration.
* lib/lchown.c (chown): Update client.
* modules/lchown (Depends-on): Add lstat.
* doc/posix-functions/chown.texi (chown): Document the bug.
* doc/posix-functions/getgroups.texi (getgroups): Document
getgroups pitfall.
* modules/chown-tests: New file.
* tests/test-chown.h (test_chown): Likewise.
* tests/test-chown.c (main): Likewise.
Signed-off-by: Eric Blake <address@hidden>
---
ChangeLog | 18 +++
doc/posix-functions/chown.texi | 11 +-
doc/posix-functions/getgroups.texi | 6 +
lib/chown.c | 86 +++++++-----
lib/lchown.c | 11 +-
lib/unistd.in.h | 10 +-
m4/chown.m4 | 61 ++++++---
m4/unistd_h.m4 | 3 +-
modules/chown-tests | 20 +++
modules/lchown | 1 +
modules/unistd | 1 +
tests/test-chown.c | 56 ++++++++
tests/test-chown.h | 269 ++++++++++++++++++++++++++++++++++++
13 files changed, 483 insertions(+), 70 deletions(-)
create mode 100644 modules/chown-tests
create mode 100644 tests/test-chown.c
create mode 100644 tests/test-chown.h
diff --git a/ChangeLog b/ChangeLog
index 5fbff63..8ca5ad6 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,21 @@
+2009-11-14 Eric Blake <address@hidden>
+
+ chown: detect Solaris and FreeBSD bug
+ * lib/chown.c (rpl_chown): Work around bug.
+ * m4/chown.m4 (gl_FUNC_CHOWN): Check for trailing slash bugs.
+ (gl_PREREQ_CHOWN): Delete.
+ * m4/unistd_h.m4 (gl_UNISTD_H_DEFAULTS): Add witness.
+ * modules/unistd (Makefile.am): Populate it.
+ * lib/unistd.in.h (chown): Update declaration.
+ * lib/lchown.c (chown): Update client.
+ * modules/lchown (Depends-on): Add lstat.
+ * doc/posix-functions/chown.texi (chown): Document the bug.
+ * doc/posix-functions/getgroups.texi (getgroups): Document
+ getgroups pitfall.
+ * modules/chown-tests: New file.
+ * tests/test-chown.h (test_chown): Likewise.
+ * tests/test-chown.c (main): Likewise.
+
2009-11-14 Robert Millan <address@hidden> (tiny change)
gnulib-tool: correctly detect absence of m4 directories
diff --git a/doc/posix-functions/chown.texi b/doc/posix-functions/chown.texi
index c444f75..e5a80d6 100644
--- a/doc/posix-functions/chown.texi
+++ b/doc/posix-functions/chown.texi
@@ -9,17 +9,22 @@ chown
Portability problems fixed by Gnulib:
@itemize
@item
+Some platforms fail to detect trailing slash on non-directories, as in
address@hidden("link-to-file/",uid,gid)}:
+FreeBSD 7.2, Solaris 9.
address@hidden
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.
@item
When applied to a symbolic link, some implementations don't dereference
the symlink, i.e.@: they behave like @code{lchown}.
address@hidden
+This function is missing on some platforms; however, the replacement
+always fails with @code{ENOSYS}:
+mingw.
@end itemize
Portability problems not fixed by Gnulib:
@itemize
address@hidden
-This function is missing on some platforms:
-mingw.
@end itemize
diff --git a/doc/posix-functions/getgroups.texi
b/doc/posix-functions/getgroups.texi
index a8478eb..0e838f7 100644
--- a/doc/posix-functions/getgroups.texi
+++ b/doc/posix-functions/getgroups.texi
@@ -25,4 +25,10 @@ getgroups
Portability problems not fixed by Gnulib:
@itemize
address@hidden
+It is unspecified whether the effective group id will be included in
+the returned list, nor whether the list will be sorted in any
+particular order. For that matter, some platforms include the
+effective group id twice, if it is also a member of the current
+supplemental group ids.
@end itemize
diff --git a/lib/chown.c b/lib/chown.c
index b851cbc..edbccc6 100644
--- a/lib/chown.c
+++ b/lib/chown.c
@@ -23,19 +23,32 @@
/* Specification. */
#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
#include <stdbool.h>
-#include <sys/types.h>
+#include <string.h>
#include <sys/stat.h>
-#include <fcntl.h>
-#include <errno.h>
+
+#if !HAVE_CHOWN
+
+/* Simple stub that always fails with ENOSYS, for mingw. */
+int
+chown (const char *file _UNUSED_PARAMETER_, uid_t uid _UNUSED_PARAMETER_,
+ gid_t gid _UNUSED_PARAMETER_)
+{
+ errno = ENOSYS;
+ return -1;
+}
+
+#else /* HAVE_CHOWN */
/* Below we refer to the system's chown(). */
-#undef chown
+# undef chown
/* The results of open() in this file are not used with fchdir,
therefore save some unnecessary work in fchdir.c. */
-#undef open
-#undef close
+# undef open
+# undef close
/* Provide a more-closely POSIX-conforming version of chown on
systems with one or both of the following problems:
@@ -46,7 +59,6 @@
int
rpl_chown (const char *file, uid_t uid, gid_t gid)
{
-#if HAVE_CHOWN
# if CHOWN_FAILS_TO_HONOR_ID_OF_NEGATIVE_ONE
if (gid == (gid_t) -1 || uid == (uid_t) -1)
{
@@ -54,13 +66,13 @@ rpl_chown (const char *file, uid_t uid, gid_t gid)
/* Stat file to get id(s) that should remain unchanged. */
if (stat (file, &file_stats))
- return -1;
+ return -1;
if (gid == (gid_t) -1)
- gid = file_stats.st_gid;
+ gid = file_stats.st_gid;
if (uid == (uid_t) -1)
- uid = file_stats.st_uid;
+ uid = file_stats.st_uid;
}
# endif
@@ -74,36 +86,42 @@ rpl_chown (const char *file, uid_t uid, gid_t gid)
int open_flags = O_NONBLOCK | O_NOCTTY;
int fd = open (file, O_RDONLY | open_flags);
if (0 <= fd
- || (errno == EACCES
- && 0 <= (fd = open (file, O_WRONLY | open_flags))))
+ || (errno == EACCES
+ && 0 <= (fd = open (file, O_WRONLY | open_flags))))
{
- int result = fchown (fd, uid, gid);
- int saved_errno = errno;
-
- /* 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 != 0 && saved_errno == EINVAL
- && fstat (fd, &sb) == 0 && S_ISFIFO (sb.st_mode));
-
- close (fd);
-
- if (! fchown_socket_failure)
- {
- errno = saved_errno;
- return result;
- }
+ int result = fchown (fd, uid, gid);
+ int saved_errno = errno;
+
+ /* 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 != 0 && saved_errno == EINVAL
+ && fstat (fd, &sb) == 0 && S_ISFIFO (sb.st_mode));
+
+ close (fd);
+
+ if (! fchown_socket_failure)
+ {
+ errno = saved_errno;
+ return result;
+ }
}
else if (errno != EACCES)
return -1;
}
# endif
- return chown (file, uid, gid);
+# if CHOWN_TRAILING_SLASH_BUG
+ {
+ size_t len = strlen (file);
+ struct stat st;
+ if (len && file[len - 1] == '/' && stat (file, &st))
+ return -1;
+ }
+# endif
-#else /* !HAVE_CHOWN */
- errno = ENOSYS;
- return -1;
-#endif
+ return chown (file, uid, gid);
}
+
+#endif /* HAVE_CHOWN */
diff --git a/lib/lchown.c b/lib/lchown.c
index 8cf10dd..65434b4 100644
--- a/lib/lchown.c
+++ b/lib/lchown.c
@@ -20,16 +20,17 @@
#include <config.h>
-/* If the system chown does not follow symlinks, we don't want it
- replaced by gnulib's chown, which does follow symlinks. */
-#if CHOWN_MODIFIES_SYMLINK
-# define REPLACE_CHOWN 0
-#endif
#include <unistd.h>
#include <errno.h>
#include <sys/stat.h>
+/* If the system chown does not follow symlinks, we don't want it
+ replaced by gnulib's chown, which does follow symlinks. */
+#if CHOWN_MODIFIES_SYMLINK
+# undef chown
+#endif
+
/* Work just like chown, except when FILE is a symbolic link.
In that case, set errno to EOPNOTSUPP and return -1.
But if autoconf tests determined that chown modifies
diff --git a/lib/unistd.in.h b/lib/unistd.in.h
index c321987..6b0513b 100644
--- a/lib/unistd.in.h
+++ b/lib/unistd.in.h
@@ -126,18 +126,16 @@ extern "C" {
#if @GNULIB_CHOWN@
# if @REPLACE_CHOWN@
-# ifndef REPLACE_CHOWN
-# define REPLACE_CHOWN 1
-# endif
-# if REPLACE_CHOWN
+# undef chown
+# define chown rpl_chown
+# endif
+# if address@hidden@ || @REPLACE_CHOWN@
/* Change the owner of FILE to UID (if UID is not -1) and the group of FILE
to GID (if GID is not -1). Follow symbolic links.
Return 0 if successful, otherwise -1 and errno set.
See the POSIX:2001 specification
<http://www.opengroup.org/susv3xsh/chown.html>. */
-# define chown rpl_chown
extern int chown (const char *file, uid_t uid, gid_t gid);
-# endif
# endif
#elif defined GNULIB_POSIXCHECK
# undef chown
diff --git a/m4/chown.m4 b/m4/chown.m4
index ac76d3f..983dde8 100644
--- a/m4/chown.m4
+++ b/m4/chown.m4
@@ -1,4 +1,4 @@
-# serial 19
+# serial 20
# Determine whether we need the chown wrapper.
dnl Copyright (C) 1997-2001, 2003-2005, 2007, 2009
@@ -20,20 +20,45 @@ AC_DEFUN([gl_FUNC_CHOWN],
AC_REQUIRE([AC_TYPE_UID_T])
AC_REQUIRE([AC_FUNC_CHOWN])
AC_REQUIRE([gl_FUNC_CHOWN_FOLLOWS_SYMLINK])
- AC_CHECK_FUNCS_ONCE([chown])
+ AC_CHECK_FUNCS_ONCE([chown fchown])
- 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.])
- fi
-
- # If chown has either of the above problems, then we need the wrapper.
- if test $ac_cv_func_chown_works$gl_cv_func_chown_follows_symlink = yesyes;
then
- : # no wrapper needed
- else
- REPLACE_CHOWN=1
+ if test $ac_cv_func_chown = no; then
+ HAVE_CHOWN=0
AC_LIBOBJ([chown])
- gl_PREREQ_CHOWN
+ else
+ if test $gl_cv_func_chown_follows_symlink = no; then
+ REPLACE_CHOWN=1
+ AC_LIBOBJ([chown])
+ fi
+ 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
+ AC_CACHE_CHECK([whether chown honors trailing slash],
+ [gl_cv_func_chown_slash_works],
+ [touch conftest.file && rm -f conftest.link
+ AC_RUN_IFELSE([AC_LANG_PROGRAM([[
+#include <unistd.h>
+#include <stdlib.h>
+#include <errno.h>
+]], [[ if (symlink ("conftest.file", "conftest.link")) return 1;
+ if (chown ("conftest.link/", getuid (), getgid ()) == 0) return 2;
+ ]])],
+ [gl_cv_func_chown_slash_works=yes],
+ [gl_cv_func_chown_slash_works=no],
+ [gl_cv_func_chown_slash_works="guessing no"])
+ 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.])
+ REPLACE_CHOWN=1
+ AC_LIBOBJ([chown])
+ fi
+ if test $REPLACE_CHOWN = 1 && test $ac_cv_func_fchown = no; then
+ AC_LIBOBJ([fchown-stub])
+ fi
fi
])
@@ -41,8 +66,8 @@ AC_DEFUN([gl_FUNC_CHOWN],
AC_DEFUN([gl_FUNC_CHOWN_FOLLOWS_SYMLINK],
[
AC_CACHE_CHECK(
- [whether chown(2) dereferences symlinks],
- gl_cv_func_chown_follows_symlink,
+ [whether chown dereferences symlinks],
+ [gl_cv_func_chown_follows_symlink],
[
AC_RUN_IFELSE([AC_LANG_SOURCE([[
#include <unistd.h>
@@ -76,9 +101,3 @@ AC_DEFUN([gl_FUNC_CHOWN_FOLLOWS_SYMLINK],
[Define if chown modifies symlinks.])
fi
])
-
-# Prerequisites of lib/chown.c.
-AC_DEFUN([gl_PREREQ_CHOWN],
-[
- AC_CHECK_FUNC([fchown], , [AC_LIBOBJ([fchown-stub])])
-])
diff --git a/m4/unistd_h.m4 b/m4/unistd_h.m4
index 5c94916..bd368c6 100644
--- a/m4/unistd_h.m4
+++ b/m4/unistd_h.m4
@@ -1,4 +1,4 @@
-# unistd_h.m4 serial 32
+# unistd_h.m4 serial 33
dnl Copyright (C) 2006-2009 Free Software Foundation, Inc.
dnl This file is free software; the Free Software Foundation
dnl gives unlimited permission to copy and/or distribute it,
@@ -68,6 +68,7 @@ AC_DEFUN([gl_UNISTD_H_DEFAULTS],
GNULIB_UNLINKAT=0; AC_SUBST([GNULIB_UNLINKAT])
GNULIB_WRITE=0; AC_SUBST([GNULIB_WRITE])
dnl Assume proper GNU behavior unless another module says otherwise.
+ HAVE_CHOWN=1; AC_SUBST([HAVE_CHOWN])
HAVE_DUP2=1; AC_SUBST([HAVE_DUP2])
HAVE_DUP3=1; AC_SUBST([HAVE_DUP3])
HAVE_EUIDACCESS=1; AC_SUBST([HAVE_EUIDACCESS])
diff --git a/modules/chown-tests b/modules/chown-tests
new file mode 100644
index 0000000..74e4cfb
--- /dev/null
+++ b/modules/chown-tests
@@ -0,0 +1,20 @@
+Files:
+tests/test-chown.h
+tests/test-chown.c
+
+Depends-on:
+lstat
+mgetgroups
+progname
+sleep
+stat-time
+stdbool
+symlink
+
+configure.ac:
+AC_CHECK_FUNCS_ONCE([getegid usleep])
+
+Makefile.am:
+TESTS += test-chown
+check_PROGRAMS += test-chown
+test_chown_LDADD = $(LDADD) @LIBINTL@
diff --git a/modules/lchown b/modules/lchown
index 65c084c..c50537a 100644
--- a/modules/lchown
+++ b/modules/lchown
@@ -8,6 +8,7 @@ m4/lchown.m4
Depends-on:
chown
errno
+lstat
sys_stat
unistd
diff --git a/modules/unistd b/modules/unistd
index 48259ac..031d707 100644
--- a/modules/unistd
+++ b/modules/unistd
@@ -60,6 +60,7 @@ unistd.h: unistd.in.h
-e 's|@''GNULIB_UNLINK''@|$(GNULIB_UNLINK)|g' \
-e 's|@''GNULIB_UNLINKAT''@|$(GNULIB_UNLINKAT)|g' \
-e 's|@''GNULIB_WRITE''@|$(GNULIB_WRITE)|g' \
+ -e 's|@''HAVE_CHOWN''@|$(HAVE_CHOWN)|g' \
-e 's|@''HAVE_DUP2''@|$(HAVE_DUP2)|g' \
-e 's|@''HAVE_DUP3''@|$(HAVE_DUP3)|g' \
-e 's|@''HAVE_EUIDACCESS''@|$(HAVE_EUIDACCESS)|g' \
diff --git a/tests/test-chown.c b/tests/test-chown.c
new file mode 100644
index 0000000..4265970
--- /dev/null
+++ b/tests/test-chown.c
@@ -0,0 +1,56 @@
+/* Tests of chown.
+ Copyright (C) 2009 Free Software Foundation, Inc.
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+/* Written by Eric Blake <address@hidden>, 2009. */
+
+#include <config.h>
+
+#include <unistd.h>
+
+#include <fcntl.h>
+#include <errno.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+
+#include "mgetgroups.h"
+#include "stat-time.h"
+
+#define ASSERT(expr) \
+ do \
+ { \
+ if (!(expr)) \
+ { \
+ fprintf (stderr, "%s:%d: assertion failed\n", __FILE__, __LINE__); \
+ fflush (stderr); \
+ abort (); \
+ } \
+ } \
+ while (0)
+
+#define BASE "test-chown.t"
+
+#include "test-chown.h"
+
+int
+main (void)
+{
+ /* Remove any leftovers from a previous partial run. */
+ ASSERT (system ("rm -rf " BASE "*") == 0);
+
+ return test_chown (chown, true);
+}
diff --git a/tests/test-chown.h b/tests/test-chown.h
new file mode 100644
index 0000000..12082e4
--- /dev/null
+++ b/tests/test-chown.h
@@ -0,0 +1,269 @@
+/* Tests of chown.
+ Copyright (C) 2009 Free Software Foundation, Inc.
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+/* Written by Eric Blake <address@hidden>, 2009. */
+
+#define TEST_CHOWN_NAP
+/* Sleep long enough to notice a timestamp difference on the file
+ system in the current directory. */
+static void
+nap (void)
+{
+#if !HAVE_USLEEP
+ /* Assume the worst case file system of FAT, which has a granularity
+ of 2 seconds. */
+ sleep (2);
+#else /* HAVE_USLEEP */
+ static long delay;
+ if (!delay)
+ {
+ /* Initialize only once, by sleeping for 20 milliseconds (needed
+ since xfs has a quantization of about 10 milliseconds, even
+ though it has a granularity of 1 nanosecond, and since NTFS
+ has a default quantization of 15.25 milliseconds, even though
+ it has a granularity of 100 nanoseconds). If the seconds
+ differ, repeat the test one more time (in case we crossed a
+ quantization boundary on a file system with 1 second
+ resolution). If we can't observe a difference in only the
+ nanoseconds, then fall back to 2 seconds. However, note that
+ usleep (2000000) is allowed to fail with EINVAL. */
+ struct stat st1;
+ struct stat st2;
+ ASSERT (close (creat (BASE "tmp", 0600)) == 0);
+ ASSERT (stat (BASE "tmp", &st1) == 0);
+ ASSERT (unlink (BASE "tmp") == 0);
+ delay = 20000;
+ usleep (delay);
+ ASSERT (close (creat (BASE "tmp", 0600)) == 0);
+ ASSERT (stat (BASE "tmp", &st2) == 0);
+ ASSERT (unlink (BASE "tmp") == 0);
+ if (st1.st_mtime != st2.st_mtime)
+ {
+ /* Seconds differ, give it one more shot. */
+ st1 = st2;
+ usleep (delay);
+ ASSERT (close (creat (BASE "tmp", 0600)) == 0);
+ ASSERT (stat (BASE "tmp", &st2) == 0);
+ ASSERT (unlink (BASE "tmp") == 0);
+ }
+ if (! (st1.st_mtime == st2.st_mtime
+ && get_stat_mtime_ns (&st1) < get_stat_mtime_ns (&st2)))
+ delay = 2000000;
+ }
+ if (delay == 2000000)
+ sleep (2);
+ else
+ usleep (delay);
+#endif /* HAVE_USLEEP */
+}
+
+#if !HAVE_GETEGID
+# define getegid() (-1)
+#endif
+
+/* This file is designed to test chown(n,o,g) and
+ chownat(AT_FDCWD,n,o,g,0). FUNC is the function to test. Assumes
+ that BASE and ASSERT are already defined, and that appropriate
+ headers are already included. If PRINT, warn before skipping
+ symlink tests with status 77. */
+
+static int
+test_chown (int (*func) (char const *, uid_t, gid_t), bool print)
+{
+ struct stat st1;
+ struct stat st2;
+ gid_t *gids = NULL;
+ int gids_count;
+ int result;
+
+ /* Solaris 8 is interesting - if the current process belongs to
+ multiple groups, the current directory is owned by a a group that
+ the current process belongs to but different than getegid(), and
+ the current directory does not have the S_ISGID bit, then regular
+ files created in the directory belong to the directory's group,
+ but symlinks belong to the current effective group id. If
+ S_ISGID is set, then both files and symlinks belong to the
+ directory's group. However, it is possible to run the testsuite
+ from within a directory owned by a group we don't belong to, in
+ which case all things that we create belong to the current
+ effective gid. So, work around the issues by creating a
+ subdirectory (we are guaranteed that the subdirectory will be
+ owned by one of our current groups), change ownership of that
+ directory to the current effective gid (which will thus succeed),
+ then create all other files within that directory (eliminating
+ questions on whether inheritance or current id triumphs, since
+ the two methods resolve to the same gid). */
+ ASSERT (mkdir (BASE "dir", 0700) == 0);
+ ASSERT (stat (BASE "dir", &st1) == 0);
+
+ /* Filter out mingw, which has no concept of groups. */
+ result = func (BASE "dir", st1.st_uid, getegid ());
+ if (result == -1 && errno == ENOSYS)
+ {
+ ASSERT (rmdir (BASE "dir") == 0);
+ if (print)
+ fputs ("skipping test: no support for ownership\n", stderr);
+ return 77;
+ }
+ ASSERT (result == 0);
+
+ ASSERT (close (creat (BASE "dir/file", 0600)) == 0);
+ ASSERT (stat (BASE "dir/file", &st1) == 0);
+ ASSERT (st1.st_uid != -1);
+ ASSERT (st1.st_gid != -1);
+ ASSERT (st1.st_gid == getegid ());
+
+ /* Sanity check of error cases. */
+ errno = 0;
+ ASSERT (func ("", -1, -1) == -1);
+ ASSERT (errno == ENOENT);
+ errno = 0;
+ ASSERT (func ("no_such", -1, -1) == -1);
+ ASSERT (errno == ENOENT);
+ errno = 0;
+ ASSERT (func ("no_such/", -1, -1) == -1);
+ ASSERT (errno == ENOENT);
+ errno = 0;
+ ASSERT (func (BASE "dir/file/", -1, -1) == -1);
+ ASSERT (errno == ENOTDIR);
+
+ /* Check that -1 does not alter ownership. */
+ ASSERT (func (BASE "dir/file", -1, st1.st_gid) == 0);
+ ASSERT (func (BASE "dir/file", st1.st_uid, -1) == 0);
+ ASSERT (stat (BASE "dir/file", &st2) == 0);
+ ASSERT (st1.st_uid == st2.st_uid);
+ ASSERT (st1.st_gid == st2.st_gid);
+
+ /* Even if the values aren't changing, ctime is required to change
+ if at least one argument is not -1. */
+ nap ();
+ ASSERT (func (BASE "dir/file", st1.st_uid, st1.st_gid) == 0);
+ ASSERT (stat (BASE "dir/file", &st2) == 0);
+ ASSERT (st1.st_ctime < st2.st_ctime
+ || (st1.st_ctime == st2.st_ctime
+ && get_stat_ctime_ns (&st1) < get_stat_ctime_ns (&st2)));
+
+ /* Test symlink behavior. */
+ if (symlink ("link", BASE "dir/link2"))
+ {
+ ASSERT (unlink (BASE "dir/file") == 0);
+ ASSERT (rmdir (BASE "dir") == 0);
+ if (print)
+ fputs ("skipping test: symlinks not supported on this file system\n",
+ stderr);
+ return 77;
+ }
+ errno = 0;
+ ASSERT (func (BASE "dir/link2", -1, -1) == -1);
+ ASSERT (errno == ENOENT);
+ errno = 0;
+ ASSERT (func (BASE "dir/link2/", st1.st_uid, st1.st_gid) == -1);
+ ASSERT (errno == ENOENT);
+ ASSERT (symlink ("file", BASE "dir/link") == 0);
+
+ /* For non-privileged users, chown can only portably succeed at
+ changing group ownership of a file we own. If we belong to at
+ least two groups, then verifying the correct change is simple.
+ But if we belong to only one group, then we fall back on the
+ other observable effect of chown: the ctime must be updated.
+ Be careful of duplicates returned by getgroups. */
+ gids_count = mgetgroups (NULL, -1, &gids);
+ if (2 <= gids_count && gids[0] == gids[1] && 2 < gids_count--)
+ gids[1] = gids[2];
+ if (1 < gids_count || (gids_count == 1 && gids[0] != st1.st_gid))
+ {
+ if (gids[0] == st1.st_gid)
+ {
+ ASSERT (1 < gids_count);
+ ASSERT (gids[0] != gids[1]);
+ gids[0] = gids[1];
+ }
+ ASSERT (gids[0] != st1.st_gid);
+ ASSERT (gids[0] != -1);
+ ASSERT (lstat (BASE "dir/link", &st2) == 0);
+ ASSERT (st1.st_uid == st2.st_uid);
+ ASSERT (st1.st_gid == st2.st_gid);
+ ASSERT (lstat (BASE "dir/link2", &st2) == 0);
+ ASSERT (st1.st_uid == st2.st_uid);
+ ASSERT (st1.st_gid == st2.st_gid);
+
+ errno = 0;
+ ASSERT (func (BASE "dir/link2/", -1, gids[0]) == -1);
+ ASSERT (errno == ENOTDIR);
+ ASSERT (stat (BASE "dir/file", &st2) == 0);
+ ASSERT (st1.st_uid == st2.st_uid);
+ ASSERT (st1.st_gid == st2.st_gid);
+ ASSERT (lstat (BASE "dir/link", &st2) == 0);
+ ASSERT (st1.st_uid == st2.st_uid);
+ ASSERT (st1.st_gid == st2.st_gid);
+ ASSERT (lstat (BASE "dir/link2", &st2) == 0);
+ ASSERT (st1.st_uid == st2.st_uid);
+ ASSERT (st1.st_gid == st2.st_gid);
+
+ ASSERT (func (BASE "dir/link2", -1, gids[0]) == 0);
+ ASSERT (stat (BASE "dir/file", &st2) == 0);
+ ASSERT (st1.st_uid == st2.st_uid);
+ ASSERT (gids[0] == st2.st_gid);
+ ASSERT (lstat (BASE "dir/link", &st2) == 0);
+ ASSERT (st1.st_uid == st2.st_uid);
+ ASSERT (st1.st_gid == st2.st_gid);
+ ASSERT (lstat (BASE "dir/link2", &st2) == 0);
+ ASSERT (st1.st_uid == st2.st_uid);
+ ASSERT (st1.st_gid == st2.st_gid);
+ }
+ else
+ {
+ struct stat l1;
+ struct stat l2;
+ ASSERT (stat (BASE "dir/file", &st1) == 0);
+ ASSERT (lstat (BASE "dir/link", &l1) == 0);
+ ASSERT (lstat (BASE "dir/link2", &l2) == 0);
+
+ nap ();
+ errno = 0;
+ ASSERT (func (BASE "dir/link2/", -1, st1.st_gid) == -1);
+ ASSERT (errno == ENOTDIR);
+ ASSERT (stat (BASE "dir/file", &st2) == 0);
+ ASSERT (st1.st_ctime == st2.st_ctime);
+ ASSERT (get_stat_ctime_ns (&st1) == get_stat_ctime_ns (&st2));
+ ASSERT (lstat (BASE "dir/link", &st2) == 0);
+ ASSERT (l1.st_ctime == st2.st_ctime);
+ ASSERT (get_stat_ctime_ns (&l1) == get_stat_ctime_ns (&st2));
+ ASSERT (lstat (BASE "dir/link2", &st2) == 0);
+ ASSERT (l2.st_ctime == st2.st_ctime);
+ ASSERT (get_stat_ctime_ns (&l2) == get_stat_ctime_ns (&st2));
+
+ ASSERT (func (BASE "dir/link2", -1, gids[0]) == 0);
+ ASSERT (stat (BASE "dir/file", &st2) == 0);
+ ASSERT (st1.st_ctime < st2.st_ctime
+ || (st1.st_ctime == st2.st_ctime
+ && get_stat_ctime_ns (&st1) < get_stat_ctime_ns (&st2)));
+ ASSERT (lstat (BASE "dir/link", &st2) == 0);
+ ASSERT (l1.st_ctime == st2.st_ctime);
+ ASSERT (get_stat_ctime_ns (&l1) == get_stat_ctime_ns (&st2));
+ ASSERT (lstat (BASE "dir/link2", &st2) == 0);
+ ASSERT (l2.st_ctime == st2.st_ctime);
+ ASSERT (get_stat_ctime_ns (&l2) == get_stat_ctime_ns (&st2));
+ }
+
+ /* Cleanup. */
+ free (gids);
+ ASSERT (unlink (BASE "dir/file") == 0);
+ ASSERT (unlink (BASE "dir/link") == 0);
+ ASSERT (unlink (BASE "dir/link2") == 0);
+ ASSERT (rmdir (BASE "dir") == 0);
+ return 0;
+}
--
1.6.5.rc1
>From 6bd102382341d268d2e99349abd4c21546a5993e Mon Sep 17 00:00:00 2001
From: Eric Blake <address@hidden>
Date: Thu, 12 Nov 2009 21:45:20 -0700
Subject: [PATCH 2/3] lchown: detect Solaris and FreeBSD bug
Solaris 9 and FreeBSD 7.2 lchown("link-to-file/",uid,gid)
mistakenly changes ownership of "file".
* lib/lchown.c (rpl_lchown): Work around bug.
* m4/lchown.m4 (gl_FUNC_LCHOWN): Check for trailing slash bugs.
* m4/unistd_h.m4 (gl_UNISTD_H_DEFAULTS): Add witness.
* modules/unistd (Makefile.am): Populate it.
* lib/unistd.in.h (lchown): Update declaration.
* doc/posix-functions/lchown.texi (lchown): Document the bug.
* modules/lchown-tests: New file.
* tests/test-lchown.h (test_lchown): Likewise.
* tests/test-lchown.c (main): Likewise.
Signed-off-by: Eric Blake <address@hidden>
---
ChangeLog | 11 ++
doc/posix-functions/lchown.texi | 8 +-
lib/lchown.c | 35 ++++-
lib/unistd.in.h | 5 +-
m4/lchown.m4 | 12 +-
m4/unistd_h.m4 | 3 +-
modules/lchown | 2 +-
modules/lchown-tests | 19 +++
modules/unistd | 1 +
tests/test-lchown.c | 56 +++++++
tests/test-lchown.h | 311 +++++++++++++++++++++++++++++++++++++++
11 files changed, 448 insertions(+), 15 deletions(-)
create mode 100644 modules/lchown-tests
create mode 100644 tests/test-lchown.c
create mode 100644 tests/test-lchown.h
diff --git a/ChangeLog b/ChangeLog
index 8ca5ad6..f0e2a18 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,16 @@
2009-11-14 Eric Blake <address@hidden>
+ lchown: detect Solaris and FreeBSD bug
+ * lib/lchown.c (rpl_lchown): Work around bug.
+ * m4/lchown.m4 (gl_FUNC_LCHOWN): Check for trailing slash bugs.
+ * m4/unistd_h.m4 (gl_UNISTD_H_DEFAULTS): Add witness.
+ * modules/unistd (Makefile.am): Populate it.
+ * lib/unistd.in.h (lchown): Update declaration.
+ * doc/posix-functions/lchown.texi (lchown): Document the bug.
+ * modules/lchown-tests: New file.
+ * tests/test-lchown.h (test_lchown): Likewise.
+ * tests/test-lchown.c (main): Likewise.
+
chown: detect Solaris and FreeBSD bug
* lib/chown.c (rpl_chown): Work around bug.
* m4/chown.m4 (gl_FUNC_CHOWN): Check for trailing slash bugs.
diff --git a/doc/posix-functions/lchown.texi b/doc/posix-functions/lchown.texi
index e40be3f..0686bf3 100644
--- a/doc/posix-functions/lchown.texi
+++ b/doc/posix-functions/lchown.texi
@@ -9,7 +9,13 @@ lchown
Portability problems fixed by Gnulib:
@itemize
@item
-This function is missing on some platforms:
+Some platforms fail to detect trailing slash on non-directories, as in
address@hidden("link-to-file/",uid,gid)}:
+FreeBSD 7.2, Solaris 9.
address@hidden
+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:
MacOS X 10.3, mingw, BeOS.
@end itemize
diff --git a/lib/lchown.c b/lib/lchown.c
index 65434b4..265c2f7 100644
--- a/lib/lchown.c
+++ b/lib/lchown.c
@@ -23,13 +23,16 @@
#include <unistd.h>
#include <errno.h>
+#include <string.h>
#include <sys/stat.h>
+#if !HAVE_LCHOWN
+
/* If the system chown does not follow symlinks, we don't want it
replaced by gnulib's chown, which does follow symlinks. */
-#if CHOWN_MODIFIES_SYMLINK
-# undef chown
-#endif
+# if CHOWN_MODIFIES_SYMLINK
+# undef chown
+# endif
/* Work just like chown, except when FILE is a symbolic link.
In that case, set errno to EOPNOTSUPP and return -1.
@@ -39,8 +42,8 @@
int
lchown (const char *file, uid_t uid, gid_t gid)
{
-#if HAVE_CHOWN
-# if ! CHOWN_MODIFIES_SYMLINK
+# if HAVE_CHOWN
+# if ! CHOWN_MODIFIES_SYMLINK
struct stat stats;
if (lstat (file, &stats) == 0 && S_ISLNK (stats.st_mode))
@@ -48,12 +51,28 @@ lchown (const char *file, uid_t uid, gid_t gid)
errno = EOPNOTSUPP;
return -1;
}
-# endif
+# endif
return chown (file, uid, gid);
-#else /* !HAVE_CHOWN */
+# else /* !HAVE_CHOWN */
errno = ENOSYS;
return -1;
-#endif
+# endif
}
+
+#else /* HAVE_LCHOWN */
+
+# undef lchown
+
+/* Work around trailing slash bugs in lchown. */
+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);
+}
+
+#endif /* HAVE_LCHOWN */
diff --git a/lib/unistd.in.h b/lib/unistd.in.h
index 6b0513b..c026b71 100644
--- a/lib/unistd.in.h
+++ b/lib/unistd.in.h
@@ -565,12 +565,15 @@ extern void endusershell (void);
#if @GNULIB_LCHOWN@
# if @REPLACE_LCHOWN@
+# undef lchown
+# define lchown rpl_lchown
+# endif
+# if address@hidden@ || @REPLACE_LCHOWN@
/* Change the owner of FILE to UID (if UID is not -1) and the group of FILE
to GID (if GID is not -1). Do not follow symbolic links.
Return 0 if successful, otherwise -1 and errno set.
See the POSIX:2001 specification
<http://www.opengroup.org/susv3xsh/lchown.html>. */
-# define lchown rpl_lchown
extern int lchown (char const *file, uid_t owner, gid_t group);
# endif
#elif defined GNULIB_POSIXCHECK
diff --git a/m4/lchown.m4 b/m4/lchown.m4
index f509fde..e40c437 100644
--- a/m4/lchown.m4
+++ b/m4/lchown.m4
@@ -1,14 +1,16 @@
-# serial 13
+# serial 14
# Determine whether we need the lchown wrapper.
-dnl Copyright (C) 1998, 2001, 2003-2007, 2009 Free Software Foundation, Inc.
+dnl Copyright (C) 1998, 2001, 2003-2007, 2009 Free Software
+dnl Foundation, Inc.
dnl This file is free software; the Free Software Foundation
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.
+dnl Provide lchown on systems that lack it, and work around trailing
+dnl slash bugs on systems that have it.
AC_DEFUN([gl_FUNC_LCHOWN],
[
@@ -16,6 +18,10 @@ AC_DEFUN([gl_FUNC_LCHOWN],
AC_REQUIRE([gl_FUNC_CHOWN])
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.
+ AC_LIBOBJ([lchown])
REPLACE_LCHOWN=1
fi
])
diff --git a/m4/unistd_h.m4 b/m4/unistd_h.m4
index bd368c6..88e60a0 100644
--- a/m4/unistd_h.m4
+++ b/m4/unistd_h.m4
@@ -1,4 +1,4 @@
-# unistd_h.m4 serial 33
+# unistd_h.m4 serial 34
dnl Copyright (C) 2006-2009 Free Software Foundation, Inc.
dnl This file is free software; the Free Software Foundation
dnl gives unlimited permission to copy and/or distribute it,
@@ -82,6 +82,7 @@ AC_DEFUN([gl_UNISTD_H_DEFAULTS],
HAVE_GETHOSTNAME=1; AC_SUBST([HAVE_GETHOSTNAME])
HAVE_GETPAGESIZE=1; AC_SUBST([HAVE_GETPAGESIZE])
HAVE_GETUSERSHELL=1; AC_SUBST([HAVE_GETUSERSHELL])
+ HAVE_LCHOWN=1; AC_SUBST([HAVE_LCHOWN])
HAVE_LINK=1; AC_SUBST([HAVE_LINK])
HAVE_LINKAT=1; AC_SUBST([HAVE_LINKAT])
HAVE_PIPE2=1; AC_SUBST([HAVE_PIPE2])
diff --git a/modules/lchown b/modules/lchown
index c50537a..233e334 100644
--- a/modules/lchown
+++ b/modules/lchown
@@ -25,4 +25,4 @@ License:
GPL
Maintainer:
-Jim Meyering
+Jim Meyering, Eric Blake
diff --git a/modules/lchown-tests b/modules/lchown-tests
new file mode 100644
index 0000000..c33103a
--- /dev/null
+++ b/modules/lchown-tests
@@ -0,0 +1,19 @@
+Files:
+tests/test-lchown.h
+tests/test-lchown.c
+
+Depends-on:
+mgetgroups
+progname
+sleep
+stat-time
+stdbool
+symlink
+
+configure.ac:
+AC_CHECK_FUNCS_ONCE([getegid usleep])
+
+Makefile.am:
+TESTS += test-lchown
+check_PROGRAMS += test-lchown
+test_lchown_LDADD = $(LDADD) @LIBINTL@
diff --git a/modules/unistd b/modules/unistd
index 031d707..1cb3b0b 100644
--- a/modules/unistd
+++ b/modules/unistd
@@ -74,6 +74,7 @@ unistd.h: unistd.in.h
-e 's|@''HAVE_GETHOSTNAME''@|$(HAVE_GETHOSTNAME)|g' \
-e 's|@''HAVE_GETPAGESIZE''@|$(HAVE_GETPAGESIZE)|g' \
-e 's|@''HAVE_GETUSERSHELL''@|$(HAVE_GETUSERSHELL)|g' \
+ -e 's|@''HAVE_LCHOWN''@|$(HAVE_LCHOWN)|g' \
-e 's|@''HAVE_LINK''@|$(HAVE_LINK)|g' \
-e 's|@''HAVE_LINKAT''@|$(HAVE_LINKAT)|g' \
-e 's|@''HAVE_PIPE2''@|$(HAVE_PIPE2)|g' \
diff --git a/tests/test-lchown.c b/tests/test-lchown.c
new file mode 100644
index 0000000..78c2940
--- /dev/null
+++ b/tests/test-lchown.c
@@ -0,0 +1,56 @@
+/* Tests of lchown.
+ Copyright (C) 2009 Free Software Foundation, Inc.
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+/* Written by Eric Blake <address@hidden>, 2009. */
+
+#include <config.h>
+
+#include <unistd.h>
+
+#include <fcntl.h>
+#include <errno.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+
+#include "mgetgroups.h"
+#include "stat-time.h"
+
+#define ASSERT(expr) \
+ do \
+ { \
+ if (!(expr)) \
+ { \
+ fprintf (stderr, "%s:%d: assertion failed\n", __FILE__, __LINE__); \
+ fflush (stderr); \
+ abort (); \
+ } \
+ } \
+ while (0)
+
+#define BASE "test-lchown.t"
+
+#include "test-lchown.h"
+
+int
+main (void)
+{
+ /* Remove any leftovers from a previous partial run. */
+ ASSERT (system ("rm -rf " BASE "*") == 0);
+
+ return test_lchown (lchown, true);
+}
diff --git a/tests/test-lchown.h b/tests/test-lchown.h
new file mode 100644
index 0000000..b0987c5
--- /dev/null
+++ b/tests/test-lchown.h
@@ -0,0 +1,311 @@
+/* Tests of lchown.
+ Copyright (C) 2009 Free Software Foundation, Inc.
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+/* Written by Eric Blake <address@hidden>, 2009. */
+
+#ifndef TEST_CHOWN_NAP
+/* Sleep long enough to notice a timestamp difference on the file
+ system in the current directory. */
+static void
+nap (void)
+{
+# if !HAVE_USLEEP
+ /* Assume the worst case file system of FAT, which has a granularity
+ of 2 seconds. */
+ sleep (2);
+# else /* HAVE_USLEEP */
+ static long delay;
+ if (!delay)
+ {
+ /* Initialize only once, by sleeping for 20 milliseconds (needed
+ since xfs has a quantization of about 10 milliseconds, even
+ though it has a granularity of 1 nanosecond, and since NTFS
+ has a default quantization of 15.25 milliseconds, even though
+ it has a granularity of 100 nanoseconds). If the seconds
+ differ, repeat the test one more time (in case we crossed a
+ quantization boundary on a file system with 1 second
+ resolution). If we can't observe a difference in only the
+ nanoseconds, then fall back to 2 seconds. However, note that
+ usleep (2000000) is allowed to fail with EINVAL. */
+ struct stat st1;
+ struct stat st2;
+ ASSERT (close (creat (BASE "tmp", 0600)) == 0);
+ ASSERT (stat (BASE "tmp", &st1) == 0);
+ ASSERT (unlink (BASE "tmp") == 0);
+ delay = 20000;
+ usleep (delay);
+ ASSERT (close (creat (BASE "tmp", 0600)) == 0);
+ ASSERT (stat (BASE "tmp", &st2) == 0);
+ ASSERT (unlink (BASE "tmp") == 0);
+ if (st1.st_mtime != st2.st_mtime)
+ {
+ /* Seconds differ, give it one more shot. */
+ st1 = st2;
+ usleep (delay);
+ ASSERT (close (creat (BASE "tmp", 0600)) == 0);
+ ASSERT (stat (BASE "tmp", &st2) == 0);
+ ASSERT (unlink (BASE "tmp") == 0);
+ }
+ if (! (st1.st_mtime == st2.st_mtime
+ && get_stat_mtime_ns (&st1) < get_stat_mtime_ns (&st2)))
+ delay = 2000000;
+ }
+ if (delay == 2000000)
+ sleep (2);
+ else
+ usleep (delay);
+# endif /* HAVE_USLEEP */
+}
+#endif /* !TEST_CHOWN_NAP */
+
+#if !HAVE_GETEGID
+# define getegid() (-1)
+#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
+ that appropriate headers are already included. If PRINT, warn
+ before skipping symlink tests with status 77. */
+
+static int
+test_lchown (int (*func) (char const *, uid_t, gid_t), bool print)
+{
+ struct stat st1;
+ struct stat st2;
+ gid_t *gids = NULL;
+ int gids_count;
+ int result;
+
+ /* Solaris 8 is interesting - if the current process belongs to
+ multiple groups, the current directory is owned by a a group that
+ the current process belongs to but different than getegid(), and
+ the current directory does not have the S_ISGID bit, then regular
+ files created in the directory belong to the directory's group,
+ but symlinks belong to the current effective group id. If
+ S_ISGID is set, then both files and symlinks belong to the
+ directory's group. However, it is possible to run the testsuite
+ from within a directory owned by a group we don't belong to, in
+ which case all things that we create belong to the current
+ effective gid. So, work around the issues by creating a
+ subdirectory (we are guaranteed that the subdirectory will be
+ owned by one of our current groups), change ownership of that
+ directory to the current effective gid (which will thus succeed),
+ then create all other files within that directory (eliminating
+ questions on whether inheritance or current id triumphs, since
+ the two methods resolve to the same gid). */
+ ASSERT (mkdir (BASE "dir", 0700) == 0);
+ ASSERT (stat (BASE "dir", &st1) == 0);
+
+ /* Filter out mingw, which has no concept of groups. */
+ result = func (BASE "dir", st1.st_uid, getegid ());
+ if (result == -1 && errno == ENOSYS)
+ {
+ ASSERT (rmdir (BASE "dir") == 0);
+ if (print)
+ fputs ("skipping test: no support for ownership\n", stderr);
+ return 77;
+ }
+ ASSERT (result == 0);
+
+ ASSERT (close (creat (BASE "dir/file", 0600)) == 0);
+ ASSERT (stat (BASE "dir/file", &st1) == 0);
+ ASSERT (st1.st_uid != -1);
+ ASSERT (st1.st_gid != -1);
+ ASSERT (st1.st_gid == getegid ());
+
+ /* Sanity check of error cases. */
+ errno = 0;
+ ASSERT (func ("", -1, -1) == -1);
+ ASSERT (errno == ENOENT);
+ errno = 0;
+ ASSERT (func ("no_such", -1, -1) == -1);
+ ASSERT (errno == ENOENT);
+ errno = 0;
+ ASSERT (func ("no_such/", -1, -1) == -1);
+ ASSERT (errno == ENOENT);
+ errno = 0;
+ ASSERT (func (BASE "dir/file/", -1, -1) == -1);
+ ASSERT (errno == ENOTDIR);
+
+ /* Check that -1 does not alter ownership. */
+ ASSERT (func (BASE "dir/file", -1, st1.st_gid) == 0);
+ ASSERT (func (BASE "dir/file", st1.st_uid, -1) == 0);
+ ASSERT (stat (BASE "dir/file", &st2) == 0);
+ ASSERT (st1.st_uid == st2.st_uid);
+ ASSERT (st1.st_gid == st2.st_gid);
+
+ /* Even if the values aren't changing, ctime is required to change
+ if at least one argument is not -1. */
+ nap ();
+ ASSERT (func (BASE "dir/file", st1.st_uid, st1.st_gid) == 0);
+ ASSERT (stat (BASE "dir/file", &st2) == 0);
+ ASSERT (st1.st_ctime < st2.st_ctime
+ || (st1.st_ctime == st2.st_ctime
+ && get_stat_ctime_ns (&st1) < get_stat_ctime_ns (&st2)));
+
+ /* Test symlink behavior. */
+ if (symlink ("link", BASE "dir/link2"))
+ {
+ ASSERT (unlink (BASE "dir/file") == 0);
+ ASSERT (rmdir (BASE "dir") == 0);
+ if (print)
+ fputs ("skipping test: symlinks not supported on this file system\n",
+ stderr);
+ return 77;
+ }
+ result = func (BASE "dir/link2", -1, -1);
+ if (result == -1 && errno == ENOSYS)
+ {
+ ASSERT (unlink (BASE "dir/file") == 0);
+ ASSERT (unlink (BASE "dir/link2") == 0);
+ ASSERT (rmdir (BASE "dir") == 0);
+ if (print)
+ fputs ("skipping test: symlink ownership not supported\n", stderr);
+ return 77;
+ }
+ ASSERT (result == 0);
+ errno = 0;
+ ASSERT (func (BASE "dir/link2/", st1.st_uid, st1.st_gid) == -1);
+ ASSERT (errno == ENOENT);
+ ASSERT (symlink ("file", BASE "dir/link") == 0);
+ ASSERT (mkdir (BASE "dir/sub", 0700) == 0);
+ ASSERT (symlink ("sub", BASE "dir/link3") == 0);
+
+ /* For non-privileged users, lchown can only portably succeed at
+ changing group ownership of a file we own. If we belong to at
+ least two groups, then verifying the correct change is simple.
+ But if we belong to only one group, then we fall back on the
+ other observable effect of lchown: the ctime must be updated.
+ Be careful of duplicates returned by getgroups. */
+ gids_count = mgetgroups (NULL, -1, &gids);
+ if (2 <= gids_count && gids[0] == gids[1] && 2 < gids_count--)
+ gids[1] = gids[2];
+ if (1 < gids_count || (gids_count == 1 && gids[0] != st1.st_gid))
+ {
+ if (gids[0] == st1.st_gid)
+ {
+ ASSERT (1 < gids_count);
+ ASSERT (gids[0] != gids[1]);
+ gids[0] = gids[1];
+ }
+ ASSERT (gids[0] != st1.st_gid);
+ ASSERT (gids[0] != -1);
+ ASSERT (lstat (BASE "dir/link", &st2) == 0);
+ ASSERT (st1.st_uid == st2.st_uid);
+ ASSERT (st1.st_gid == st2.st_gid);
+ ASSERT (lstat (BASE "dir/link2", &st2) == 0);
+ ASSERT (st1.st_uid == st2.st_uid);
+ ASSERT (st1.st_gid == st2.st_gid);
+
+ errno = 0;
+ ASSERT (func (BASE "dir/link2/", -1, gids[0]) == -1);
+ ASSERT (errno == ENOTDIR);
+ ASSERT (stat (BASE "dir/file", &st2) == 0);
+ ASSERT (st1.st_uid == st2.st_uid);
+ ASSERT (st1.st_gid == st2.st_gid);
+ ASSERT (lstat (BASE "dir/link", &st2) == 0);
+ ASSERT (st1.st_uid == st2.st_uid);
+ ASSERT (st1.st_gid == st2.st_gid);
+ ASSERT (lstat (BASE "dir/link2", &st2) == 0);
+ ASSERT (st1.st_uid == st2.st_uid);
+ ASSERT (st1.st_gid == st2.st_gid);
+
+ ASSERT (func (BASE "dir/link2", -1, gids[0]) == 0);
+ ASSERT (stat (BASE "dir/file", &st2) == 0);
+ ASSERT (st1.st_uid == st2.st_uid);
+ ASSERT (st1.st_gid == st2.st_gid);
+ ASSERT (lstat (BASE "dir/link", &st2) == 0);
+ ASSERT (st1.st_uid == st2.st_uid);
+ ASSERT (st1.st_gid == st2.st_gid);
+ ASSERT (lstat (BASE "dir/link2", &st2) == 0);
+ ASSERT (st1.st_uid == st2.st_uid);
+ ASSERT (gids[0] == st2.st_gid);
+
+ /* Trailing slash follows through to directory. */
+ ASSERT (lstat (BASE "dir/link3", &st2) == 0);
+ ASSERT (st1.st_uid == st2.st_uid);
+ ASSERT (st1.st_gid == st2.st_gid);
+ ASSERT (lstat (BASE "dir/sub", &st2) == 0);
+ ASSERT (st1.st_uid == st2.st_uid);
+ ASSERT (st1.st_gid == st2.st_gid);
+
+ ASSERT (func (BASE "dir/link3/", -1, gids[0]) == 0);
+ ASSERT (lstat (BASE "dir/link3", &st2) == 0);
+ ASSERT (st1.st_uid == st2.st_uid);
+ ASSERT (st1.st_gid == st2.st_gid);
+ ASSERT (lstat (BASE "dir/sub", &st2) == 0);
+ ASSERT (st1.st_uid == st2.st_uid);
+ ASSERT (gids[0] == st2.st_gid);
+ }
+ else
+ {
+ struct stat l1;
+ struct stat l2;
+ ASSERT (stat (BASE "dir/file", &st1) == 0);
+ ASSERT (lstat (BASE "dir/link", &l1) == 0);
+ ASSERT (lstat (BASE "dir/link2", &l2) == 0);
+
+ nap ();
+ errno = 0;
+ ASSERT (func (BASE "dir/link2/", -1, st1.st_gid) == -1);
+ ASSERT (errno == ENOTDIR);
+ ASSERT (stat (BASE "dir/file", &st2) == 0);
+ ASSERT (st1.st_ctime == st2.st_ctime);
+ ASSERT (get_stat_ctime_ns (&st1) == get_stat_ctime_ns (&st2));
+ ASSERT (lstat (BASE "dir/link", &st2) == 0);
+ ASSERT (l1.st_ctime == st2.st_ctime);
+ ASSERT (get_stat_ctime_ns (&l1) == get_stat_ctime_ns (&st2));
+ ASSERT (lstat (BASE "dir/link2", &st2) == 0);
+ ASSERT (l2.st_ctime == st2.st_ctime);
+ ASSERT (get_stat_ctime_ns (&l2) == get_stat_ctime_ns (&st2));
+
+ ASSERT (func (BASE "dir/link2", -1, gids[0]) == 0);
+ ASSERT (stat (BASE "dir/file", &st2) == 0);
+ ASSERT (st1.st_ctime == st2.st_ctime);
+ ASSERT (get_stat_ctime_ns (&st1) == get_stat_ctime_ns (&st2));
+ ASSERT (lstat (BASE "dir/link", &st2) == 0);
+ ASSERT (l1.st_ctime == st2.st_ctime);
+ ASSERT (get_stat_ctime_ns (&l1) == get_stat_ctime_ns (&st2));
+ ASSERT (lstat (BASE "dir/link2", &st2) == 0);
+ ASSERT (l2.st_ctime < st2.st_ctime
+ || (l2.st_ctime == st2.st_ctime
+ && get_stat_ctime_ns (&l2) < get_stat_ctime_ns (&st2)));
+
+ /* Trailing slash follows through to directory. */
+ ASSERT (lstat (BASE "dir/sub", &st1) == 0);
+ ASSERT (lstat (BASE "dir/link3", &l1) == 0);
+ nap ();
+ ASSERT (func (BASE "dir/link3/", -1, gids[0]) == 0);
+ ASSERT (lstat (BASE "dir/link3", &st2) == 0);
+ ASSERT (l1.st_ctime == st2.st_ctime);
+ ASSERT (get_stat_ctime_ns (&l1) == get_stat_ctime_ns (&st2));
+ ASSERT (lstat (BASE "dir/sub", &st2) == 0);
+ ASSERT (st1.st_ctime < st2.st_ctime
+ || (st1.st_ctime == st2.st_ctime
+ && get_stat_ctime_ns (&st1) < get_stat_ctime_ns (&st2)));
+ }
+
+ /* Cleanup. */
+ free (gids);
+ ASSERT (unlink (BASE "dir/file") == 0);
+ ASSERT (unlink (BASE "dir/link") == 0);
+ ASSERT (unlink (BASE "dir/link2") == 0);
+ ASSERT (unlink (BASE "dir/link3") == 0);
+ ASSERT (rmdir (BASE "dir/sub") == 0);
+ ASSERT (rmdir (BASE "dir") == 0);
+ return 0;
+}
--
1.6.5.rc1
>From c13ca15f1a9efad6c85e2debf5bcc54db425d017 Mon Sep 17 00:00:00 2001
From: Eric Blake <address@hidden>
Date: Sat, 14 Nov 2009 08:17:44 -0700
Subject: [PATCH 3/3] openat: detect Solaris fchownat bug
Solaris 9 fchownat(dir,"name/",uid,gid,flag) has same bugs as
chown and lchown.
* lib/fchownat.c (rpl_fchownat): Work around Solaris bug. Avoid
penalizing glibc chownat when only lchownat is broken.
* m4/openat.m4 (gl_FUNC_FCHOWNAT): Replace fchownat if there are
trailing slash bugs.
* doc/posix-functions/fchownat.texi (fchownat): Document the bug.
* modules/openat-tests (Files): Include more files.
(Depends-on): Add mgetgroups, sleep, stat-time.
(configure.ac): Add additional checks.
(Makefile.am): Build new test.
* tests/test-fchownat.c: New file.
Signed-off-by: Eric Blake <address@hidden>
---
ChangeLog | 12 +++++
doc/posix-functions/fchownat.texi | 13 +++++-
lib/fchownat.c | 79 ++++++++++++++++++++++++++++++---
m4/openat.m4 | 10 +++-
modules/openat-tests | 13 +++++-
tests/test-fchownat.c | 89 +++++++++++++++++++++++++++++++++++++
6 files changed, 204 insertions(+), 12 deletions(-)
create mode 100644 tests/test-fchownat.c
diff --git a/ChangeLog b/ChangeLog
index f0e2a18..5c8f1b6 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,17 @@
2009-11-14 Eric Blake <address@hidden>
+ openat: detect Solaris fchownat bug
+ * lib/fchownat.c (rpl_fchownat): Work around Solaris bug. Avoid
+ penalizing glibc chownat when only lchownat is broken.
+ * m4/openat.m4 (gl_FUNC_FCHOWNAT): Replace fchownat if there are
+ trailing slash bugs.
+ * doc/posix-functions/fchownat.texi (fchownat): Document the bug.
+ * modules/openat-tests (Files): Include more files.
+ (Depends-on): Add mgetgroups, sleep, stat-time.
+ (configure.ac): Add additional checks.
+ (Makefile.am): Build new test.
+ * tests/test-fchownat.c: New file.
+
lchown: detect Solaris and FreeBSD bug
* lib/lchown.c (rpl_lchown): Work around bug.
* m4/lchown.m4 (gl_FUNC_LCHOWN): Check for trailing slash bugs.
diff --git a/doc/posix-functions/fchownat.texi
b/doc/posix-functions/fchownat.texi
index 7ddd3f3..6285750 100644
--- a/doc/posix-functions/fchownat.texi
+++ b/doc/posix-functions/fchownat.texi
@@ -9,10 +9,21 @@ fchownat
Portability problems fixed by Gnulib:
@itemize
@item
+Some platforms fail to detect trailing slash on non-directories, as in
address@hidden(dir,"link-to-file/",uid,gid,flag)}:
+Solaris 9.
address@hidden
+Some platforms mistakenly dereference symlinks when using
address@hidden:
+Linux kernel 2.6.17.
address@hidden
This function is missing on some platforms:
glibc 2.3.6, MacOS X 10.3, FreeBSD 6.0, NetBSD 3.0, OpenBSD 3.8, AIX
5.1, HP-UX 11, IRIX 6.5, OSF/1 5.1, Cygwin 1.5.x, mingw, Interix 3.5, BeOS.
-But the replacement function is not safe to be used in libraries and is not
multithread-safe.
+But the replacement function is not safe to be used in libraries and
+is not multithread-safe. Also, the replacement may fail to change
+symlinks if @code{lchown} is unsupported, or fail altogether if
address@hidden is unsupported.
@end itemize
Portability problems not fixed by Gnulib:
diff --git a/lib/fchownat.c b/lib/fchownat.c
index 09b4aa8..0492dcc 100644
--- a/lib/fchownat.c
+++ b/lib/fchownat.c
@@ -25,6 +25,13 @@
#include <unistd.h>
+#include <errno.h>
+#include <string.h>
+
+#include "openat.h"
+
+#if !HAVE_FCHOWNAT
+
/* Replacement for Solaris' function by the same name.
Invoke chown or lchown on file, FILE, using OWNER and GROUP, in the
directory open on descriptor FD. If FLAG is AT_SYMLINK_NOFOLLOW, then
@@ -33,10 +40,68 @@
then (chown|lchown)/restore_cwd. If either the save_cwd or the
restore_cwd fails, then give a diagnostic and exit nonzero. */
-#define AT_FUNC_NAME fchownat
-#define AT_FUNC_F1 lchown
-#define AT_FUNC_F2 chown
-#define AT_FUNC_USE_F1_COND AT_SYMLINK_NOFOLLOW
-#define AT_FUNC_POST_FILE_PARAM_DECLS , uid_t owner, gid_t group, int flag
-#define AT_FUNC_POST_FILE_ARGS , owner, group
-#include "at-func.c"
+# define AT_FUNC_NAME fchownat
+# define AT_FUNC_F1 lchown
+# define AT_FUNC_F2 chown
+# define AT_FUNC_USE_F1_COND AT_SYMLINK_NOFOLLOW
+# define AT_FUNC_POST_FILE_PARAM_DECLS , uid_t owner, gid_t group, int flag
+# define AT_FUNC_POST_FILE_ARGS , owner, group
+# include "at-func.c"
+# undef AT_FUNC_NAME
+# undef AT_FUNC_F1
+# undef AT_FUNC_F2
+# undef AT_FUNC_USE_F1_COND
+# undef AT_FUNC_POST_FILE_PARAM_DECLS
+# undef AT_FUNC_POST_FILE_ARGS
+
+#else /* HAVE_FCHOWNAT */
+
+# undef fchownat
+
+# if FCHOWNAT_NOFOLLOW_BUG
+
+/* Failure to handle AT_SYMLINK_NOFOLLOW requires the /proc/self/fd or
+ fchdir workaround to call lchown for lchownat, but there is no need
+ to penalize chownat. */
+static int
+local_lchownat (int fd, char const *file, uid_t owner, gid_t group);
+
+# define AT_FUNC_NAME local_lchownat
+# define AT_FUNC_F1 lchown
+# define AT_FUNC_POST_FILE_PARAM_DECLS , uid_t owner, gid_t group
+# define AT_FUNC_POST_FILE_ARGS , owner, group
+# include "at-func.c"
+# undef AT_FUNC_NAME
+# undef AT_FUNC_F1
+# undef AT_FUNC_POST_FILE_PARAM_DECLS
+# undef AT_FUNC_POST_FILE_ARGS
+
+# endif
+
+/* Work around bugs with trailing slash, using the same workarounds as
+ chown and lchown. */
+
+int
+rpl_fchownat (int fd, char const *file, uid_t owner, gid_t group, int flag)
+{
+# if FCHOWNAT_NOFOLLOW_BUG
+ if (flag == AT_SYMLINK_NOFOLLOW)
+ return local_lchownat (fd, file, owner, group);
+# endif
+# if CHOWN_TRAILING_SLASH_BUG
+ {
+ size_t len = strlen (file);
+ struct stat st;
+ if (len && file[len - 1] == '/')
+ {
+ if (statat (fd, file, &st))
+ return -1;
+ if (flag == AT_SYMLINK_NOFOLLOW)
+ return fchownat (fd, file, owner, group, 0);
+ }
+ }
+# endif
+ return fchownat (fd, file, owner, group, flag);
+}
+
+#endif /* HAVE_FCHOWNAT */
diff --git a/m4/openat.m4 b/m4/openat.m4
index 6b4f95c..e6ea25e 100644
--- a/m4/openat.m4
+++ b/m4/openat.m4
@@ -1,4 +1,4 @@
-# serial 25
+# serial 26
# See if we need to use our replacement for Solaris' openat et al functions.
dnl Copyright (C) 2004-2009 Free Software Foundation, Inc.
@@ -102,9 +102,15 @@ main ()
# Also use the replacement function if fchownat is simply not available.
AC_DEFUN([gl_FUNC_FCHOWNAT],
[
+ AC_REQUIRE([gl_FUNC_CHOWN])
AC_CHECK_FUNC([fchownat],
- [gl_FUNC_FCHOWNAT_DEREF_BUG([REPLACE_FCHOWNAT=1])],
+ [gl_FUNC_FCHOWNAT_DEREF_BUG([REPLACE_FCHOWNAT=1
+ AC_DEFINE([FCHOWNAT_NOFOLLOW_BUG], [1], [Define to 1 if your
+ platform has fchownat, but it cannot perform lchown tasks.])])],
[HAVE_FCHOWNAT=0])
+ if test $REPLACE_CHOWN = 1; then
+ REPLACE_FCHOWNAT=1
+ fi
if test $HAVE_FCHOWNAT = 0 || test $REPLACE_FCHOWNAT = 1; then
AC_LIBOBJ([fchownat])
fi
diff --git a/modules/openat-tests b/modules/openat-tests
index 1440a9b..62cef88 100644
--- a/modules/openat-tests
+++ b/modules/openat-tests
@@ -1,24 +1,33 @@
Files:
+tests/test-chown.h
+tests/test-lchown.h
tests/test-lstat.h
tests/test-mkdir.h
tests/test-rmdir.h
tests/test-stat.h
tests/test-unlink.h
+tests/test-fchownat.c
tests/test-fstatat.c
tests/test-mkdirat.c
tests/test-openat.c
tests/test-unlinkat.c
Depends-on:
+mgetgroups
pathmax
+sleep
+stat-time
symlink
unlinkdir
configure.ac:
+AC_CHECK_FUNCS_ONCE([getegid usleep])
Makefile.am:
-TESTS += test-fstatat test-mkdirat test-openat test-unlinkat
-check_PROGRAMS += test-fstatat test-mkdirat test-openat test-unlinkat
+TESTS += test-fchownat test-fstatat test-mkdirat test-openat test-unlinkat
+check_PROGRAMS += test-fchownat test-fstatat test-mkdirat test-openat \
+ test-unlinkat
+test_fchownat_LDADD = $(LDADD) @LIBINTL@
test_fstatat_LDADD = $(LDADD) @LIBINTL@
test_mkdirat_LDADD = $(LDADD) @LIBINTL@
test_openat_LDADD = $(LDADD) @LIBINTL@
diff --git a/tests/test-fchownat.c b/tests/test-fchownat.c
new file mode 100644
index 0000000..2dbc857
--- /dev/null
+++ b/tests/test-fchownat.c
@@ -0,0 +1,89 @@
+/* Tests of fchownat.
+ Copyright (C) 2009 Free Software Foundation, Inc.
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+/* Written by Eric Blake <address@hidden>, 2009. */
+
+#include <config.h>
+
+#include <unistd.h>
+
+#include <fcntl.h>
+#include <errno.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+
+#include "mgetgroups.h"
+#include "openat.h"
+#include "stat-time.h"
+
+#define ASSERT(expr) \
+ do \
+ { \
+ if (!(expr)) \
+ { \
+ fprintf (stderr, "%s:%d: assertion failed\n", __FILE__, __LINE__); \
+ fflush (stderr); \
+ abort (); \
+ } \
+ } \
+ while (0)
+
+#define BASE "test-fchownat.t"
+
+#include "test-chown.h"
+#include "test-lchown.h"
+
+static int dfd = AT_FDCWD;
+
+/* Wrapper around fchownat to test chown behavior. */
+static int
+do_chown (char const *name, uid_t user, gid_t group)
+{
+ return chownat (dfd, name, user, group);
+}
+
+/* Wrapper around fchownat to test lchown behavior. */
+static int
+do_lchown (char const *name, uid_t user, gid_t group)
+{
+ return lchownat (dfd, name, user, group);
+}
+
+int
+main (void)
+{
+ int result1; /* Skip because of no chown/symlink support. */
+ int result2; /* Skip because of no lchown support. */
+
+ /* Clean up any trash from prior testsuite runs. */
+ ASSERT (system ("rm -rf " BASE "*") == 0);
+
+ /* Basic tests. */
+ result1 = test_chown (do_chown, true);
+ result2 = test_lchown (do_lchown, result1 == 0);
+ dfd = open (".", O_RDONLY);
+ ASSERT (0 <= dfd);
+ ASSERT (test_chown (do_chown, false) == result1);
+ ASSERT (test_lchown (do_lchown, false) == result2);
+ /* We expect 0/0, 0/77, or 77/77, but not 77/0. */
+ ASSERT (result1 <= result2);
+ ASSERT (close (dfd) == 0);
+
+ /* FIXME - add additional tests of dfd not at current directory. */
+ return result1 | result2;
+}
--
1.6.5.rc1
- pending patches?, Jim Meyering, 2009/11/13
- Re: pending patches?, Eric Blake, 2009/11/13
- Re: pending patches?, Eric Blake, 2009/11/13
- Re: pending patches?, Pádraig Brady, 2009/11/13
- Re: pending patches?, Jim Meyering, 2009/11/14
- Re: pending patches?, Eric Blake, 2009/11/14
- Re: pending patches?,
Eric Blake <=
- OpenBSD chown (was: pending patches?), Eric Blake, 2009/11/17
- Re: OpenBSD chown, Eric Blake, 2009/11/18