/* 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:
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)
{
/* 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
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 */
-# serial 19
+# serial 20
# Determine whether we need the chown wrapper.
dnl Copyright (C) 1997-2001, 2003-2005, 2007, 2009
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
])
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>
[Define if chown modifies symlinks.])
fi
])
-
-# Prerequisites of lib/chown.c.
-AC_DEFUN([gl_PREREQ_CHOWN],
-[
- AC_CHECK_FUNC([fchown], , [AC_LIBOBJ([fchown-stub])])
-])
--- /dev/null
+/* 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 <ebb9@byu.net>, 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;
+}