chown: detect Solaris and FreeBSD bug
[gnulib.git] / tests / test-chown.h
1 /* Tests of chown.
2    Copyright (C) 2009 Free Software Foundation, Inc.
3
4    This program is free software: you can redistribute it and/or modify
5    it under the terms of the GNU General Public License as published by
6    the Free Software Foundation; either version 3 of the License, or
7    (at your option) any later version.
8
9    This program is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12    GNU General Public License for more details.
13
14    You should have received a copy of the GNU General Public License
15    along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
16
17 /* Written by Eric Blake <ebb9@byu.net>, 2009.  */
18
19 #define TEST_CHOWN_NAP
20 /* Sleep long enough to notice a timestamp difference on the file
21    system in the current directory.  */
22 static void
23 nap (void)
24 {
25 #if !HAVE_USLEEP
26   /* Assume the worst case file system of FAT, which has a granularity
27      of 2 seconds.  */
28   sleep (2);
29 #else /* HAVE_USLEEP */
30   static long delay;
31   if (!delay)
32     {
33       /* Initialize only once, by sleeping for 20 milliseconds (needed
34          since xfs has a quantization of about 10 milliseconds, even
35          though it has a granularity of 1 nanosecond, and since NTFS
36          has a default quantization of 15.25 milliseconds, even though
37          it has a granularity of 100 nanoseconds).  If the seconds
38          differ, repeat the test one more time (in case we crossed a
39          quantization boundary on a file system with 1 second
40          resolution).  If we can't observe a difference in only the
41          nanoseconds, then fall back to 2 seconds.  However, note that
42          usleep (2000000) is allowed to fail with EINVAL.  */
43       struct stat st1;
44       struct stat st2;
45       ASSERT (close (creat (BASE "tmp", 0600)) == 0);
46       ASSERT (stat (BASE "tmp", &st1) == 0);
47       ASSERT (unlink (BASE "tmp") == 0);
48       delay = 20000;
49       usleep (delay);
50       ASSERT (close (creat (BASE "tmp", 0600)) == 0);
51       ASSERT (stat (BASE "tmp", &st2) == 0);
52       ASSERT (unlink (BASE "tmp") == 0);
53       if (st1.st_mtime != st2.st_mtime)
54         {
55           /* Seconds differ, give it one more shot.  */
56           st1 = st2;
57           usleep (delay);
58           ASSERT (close (creat (BASE "tmp", 0600)) == 0);
59           ASSERT (stat (BASE "tmp", &st2) == 0);
60           ASSERT (unlink (BASE "tmp") == 0);
61         }
62       if (! (st1.st_mtime == st2.st_mtime
63              && get_stat_mtime_ns (&st1) < get_stat_mtime_ns (&st2)))
64         delay = 2000000;
65     }
66   if (delay == 2000000)
67     sleep (2);
68   else
69     usleep (delay);
70 #endif /* HAVE_USLEEP */
71 }
72
73 #if !HAVE_GETEGID
74 # define getegid() (-1)
75 #endif
76
77 /* This file is designed to test chown(n,o,g) and
78    chownat(AT_FDCWD,n,o,g,0).  FUNC is the function to test.  Assumes
79    that BASE and ASSERT are already defined, and that appropriate
80    headers are already included.  If PRINT, warn before skipping
81    symlink tests with status 77.  */
82
83 static int
84 test_chown (int (*func) (char const *, uid_t, gid_t), bool print)
85 {
86   struct stat st1;
87   struct stat st2;
88   gid_t *gids = NULL;
89   int gids_count;
90   int result;
91
92   /* Solaris 8 is interesting - if the current process belongs to
93      multiple groups, the current directory is owned by a a group that
94      the current process belongs to but different than getegid(), and
95      the current directory does not have the S_ISGID bit, then regular
96      files created in the directory belong to the directory's group,
97      but symlinks belong to the current effective group id.  If
98      S_ISGID is set, then both files and symlinks belong to the
99      directory's group.  However, it is possible to run the testsuite
100      from within a directory owned by a group we don't belong to, in
101      which case all things that we create belong to the current
102      effective gid.  So, work around the issues by creating a
103      subdirectory (we are guaranteed that the subdirectory will be
104      owned by one of our current groups), change ownership of that
105      directory to the current effective gid (which will thus succeed),
106      then create all other files within that directory (eliminating
107      questions on whether inheritance or current id triumphs, since
108      the two methods resolve to the same gid).  */
109   ASSERT (mkdir (BASE "dir", 0700) == 0);
110   ASSERT (stat (BASE "dir", &st1) == 0);
111
112   /* Filter out mingw, which has no concept of groups.  */
113   result = func (BASE "dir", st1.st_uid, getegid ());
114   if (result == -1 && errno == ENOSYS)
115     {
116       ASSERT (rmdir (BASE "dir") == 0);
117       if (print)
118         fputs ("skipping test: no support for ownership\n", stderr);
119       return 77;
120     }
121   ASSERT (result == 0);
122
123   ASSERT (close (creat (BASE "dir/file", 0600)) == 0);
124   ASSERT (stat (BASE "dir/file", &st1) == 0);
125   ASSERT (st1.st_uid != -1);
126   ASSERT (st1.st_gid != -1);
127   ASSERT (st1.st_gid == getegid ());
128
129   /* Sanity check of error cases.  */
130   errno = 0;
131   ASSERT (func ("", -1, -1) == -1);
132   ASSERT (errno == ENOENT);
133   errno = 0;
134   ASSERT (func ("no_such", -1, -1) == -1);
135   ASSERT (errno == ENOENT);
136   errno = 0;
137   ASSERT (func ("no_such/", -1, -1) == -1);
138   ASSERT (errno == ENOENT);
139   errno = 0;
140   ASSERT (func (BASE "dir/file/", -1, -1) == -1);
141   ASSERT (errno == ENOTDIR);
142
143   /* Check that -1 does not alter ownership.  */
144   ASSERT (func (BASE "dir/file", -1, st1.st_gid) == 0);
145   ASSERT (func (BASE "dir/file", st1.st_uid, -1) == 0);
146   ASSERT (stat (BASE "dir/file", &st2) == 0);
147   ASSERT (st1.st_uid == st2.st_uid);
148   ASSERT (st1.st_gid == st2.st_gid);
149
150   /* Even if the values aren't changing, ctime is required to change
151      if at least one argument is not -1.  */
152   nap ();
153   ASSERT (func (BASE "dir/file", st1.st_uid, st1.st_gid) == 0);
154   ASSERT (stat (BASE "dir/file", &st2) == 0);
155   ASSERT (st1.st_ctime < st2.st_ctime
156           || (st1.st_ctime == st2.st_ctime
157               && get_stat_ctime_ns (&st1) < get_stat_ctime_ns (&st2)));
158
159   /* Test symlink behavior.  */
160   if (symlink ("link", BASE "dir/link2"))
161     {
162       ASSERT (unlink (BASE "dir/file") == 0);
163       ASSERT (rmdir (BASE "dir") == 0);
164       if (print)
165         fputs ("skipping test: symlinks not supported on this file system\n",
166                stderr);
167       return 77;
168     }
169   errno = 0;
170   ASSERT (func (BASE "dir/link2", -1, -1) == -1);
171   ASSERT (errno == ENOENT);
172   errno = 0;
173   ASSERT (func (BASE "dir/link2/", st1.st_uid, st1.st_gid) == -1);
174   ASSERT (errno == ENOENT);
175   ASSERT (symlink ("file", BASE "dir/link") == 0);
176
177   /* For non-privileged users, chown can only portably succeed at
178      changing group ownership of a file we own.  If we belong to at
179      least two groups, then verifying the correct change is simple.
180      But if we belong to only one group, then we fall back on the
181      other observable effect of chown: the ctime must be updated.
182      Be careful of duplicates returned by getgroups.  */
183   gids_count = mgetgroups (NULL, -1, &gids);
184   if (2 <= gids_count && gids[0] == gids[1] && 2 < gids_count--)
185     gids[1] = gids[2];
186   if (1 < gids_count || (gids_count == 1 && gids[0] != st1.st_gid))
187     {
188       if (gids[0] == st1.st_gid)
189         {
190           ASSERT (1 < gids_count);
191           ASSERT (gids[0] != gids[1]);
192           gids[0] = gids[1];
193         }
194       ASSERT (gids[0] != st1.st_gid);
195       ASSERT (gids[0] != -1);
196       ASSERT (lstat (BASE "dir/link", &st2) == 0);
197       ASSERT (st1.st_uid == st2.st_uid);
198       ASSERT (st1.st_gid == st2.st_gid);
199       ASSERT (lstat (BASE "dir/link2", &st2) == 0);
200       ASSERT (st1.st_uid == st2.st_uid);
201       ASSERT (st1.st_gid == st2.st_gid);
202
203       errno = 0;
204       ASSERT (func (BASE "dir/link2/", -1, gids[0]) == -1);
205       ASSERT (errno == ENOTDIR);
206       ASSERT (stat (BASE "dir/file", &st2) == 0);
207       ASSERT (st1.st_uid == st2.st_uid);
208       ASSERT (st1.st_gid == st2.st_gid);
209       ASSERT (lstat (BASE "dir/link", &st2) == 0);
210       ASSERT (st1.st_uid == st2.st_uid);
211       ASSERT (st1.st_gid == st2.st_gid);
212       ASSERT (lstat (BASE "dir/link2", &st2) == 0);
213       ASSERT (st1.st_uid == st2.st_uid);
214       ASSERT (st1.st_gid == st2.st_gid);
215
216       ASSERT (func (BASE "dir/link2", -1, gids[0]) == 0);
217       ASSERT (stat (BASE "dir/file", &st2) == 0);
218       ASSERT (st1.st_uid == st2.st_uid);
219       ASSERT (gids[0] == st2.st_gid);
220       ASSERT (lstat (BASE "dir/link", &st2) == 0);
221       ASSERT (st1.st_uid == st2.st_uid);
222       ASSERT (st1.st_gid == st2.st_gid);
223       ASSERT (lstat (BASE "dir/link2", &st2) == 0);
224       ASSERT (st1.st_uid == st2.st_uid);
225       ASSERT (st1.st_gid == st2.st_gid);
226     }
227   else
228     {
229       struct stat l1;
230       struct stat l2;
231       ASSERT (stat (BASE "dir/file", &st1) == 0);
232       ASSERT (lstat (BASE "dir/link", &l1) == 0);
233       ASSERT (lstat (BASE "dir/link2", &l2) == 0);
234
235       nap ();
236       errno = 0;
237       ASSERT (func (BASE "dir/link2/", -1, st1.st_gid) == -1);
238       ASSERT (errno == ENOTDIR);
239       ASSERT (stat (BASE "dir/file", &st2) == 0);
240       ASSERT (st1.st_ctime == st2.st_ctime);
241       ASSERT (get_stat_ctime_ns (&st1) == get_stat_ctime_ns (&st2));
242       ASSERT (lstat (BASE "dir/link", &st2) == 0);
243       ASSERT (l1.st_ctime == st2.st_ctime);
244       ASSERT (get_stat_ctime_ns (&l1) == get_stat_ctime_ns (&st2));
245       ASSERT (lstat (BASE "dir/link2", &st2) == 0);
246       ASSERT (l2.st_ctime == st2.st_ctime);
247       ASSERT (get_stat_ctime_ns (&l2) == get_stat_ctime_ns (&st2));
248
249       ASSERT (func (BASE "dir/link2", -1, gids[0]) == 0);
250       ASSERT (stat (BASE "dir/file", &st2) == 0);
251       ASSERT (st1.st_ctime < st2.st_ctime
252               || (st1.st_ctime == st2.st_ctime
253                   && get_stat_ctime_ns (&st1) < get_stat_ctime_ns (&st2)));
254       ASSERT (lstat (BASE "dir/link", &st2) == 0);
255       ASSERT (l1.st_ctime == st2.st_ctime);
256       ASSERT (get_stat_ctime_ns (&l1) == get_stat_ctime_ns (&st2));
257       ASSERT (lstat (BASE "dir/link2", &st2) == 0);
258       ASSERT (l2.st_ctime == st2.st_ctime);
259       ASSERT (get_stat_ctime_ns (&l2) == get_stat_ctime_ns (&st2));
260     }
261
262   /* Cleanup.  */
263   free (gids);
264   ASSERT (unlink (BASE "dir/file") == 0);
265   ASSERT (unlink (BASE "dir/link") == 0);
266   ASSERT (unlink (BASE "dir/link2") == 0);
267   ASSERT (rmdir (BASE "dir") == 0);
268   return 0;
269 }