mgetgroups: reduce duplicate listings
[gnulib.git] / tests / test-lchown.h
1 /* Tests of lchown.
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 #ifndef 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   static long delay;
26   if (!delay)
27     {
28       /* Initialize only once, by sleeping for 20 milliseconds (needed
29          since xfs has a quantization of about 10 milliseconds, even
30          though it has a granularity of 1 nanosecond, and since NTFS
31          has a default quantization of 15.25 milliseconds, even though
32          it has a granularity of 100 nanoseconds).  If the seconds
33          differ, repeat the test one more time (in case we crossed a
34          quantization boundary on a file system with 1 second
35          resolution).  If we can't observe a difference in only the
36          nanoseconds, then fall back to 1 second if the time is odd,
37          and 2 seconds (needed for FAT) if time is even.  */
38       struct stat st1;
39       struct stat st2;
40       ASSERT (close (creat (BASE "tmp", 0600)) == 0);
41       ASSERT (stat (BASE "tmp", &st1) == 0);
42       ASSERT (unlink (BASE "tmp") == 0);
43       delay = 20000;
44       usleep (delay);
45       ASSERT (close (creat (BASE "tmp", 0600)) == 0);
46       ASSERT (stat (BASE "tmp", &st2) == 0);
47       ASSERT (unlink (BASE "tmp") == 0);
48       if (st1.st_mtime != st2.st_mtime)
49         {
50           /* Seconds differ, give it one more shot.  */
51           st1 = st2;
52           usleep (delay);
53           ASSERT (close (creat (BASE "tmp", 0600)) == 0);
54           ASSERT (stat (BASE "tmp", &st2) == 0);
55           ASSERT (unlink (BASE "tmp") == 0);
56         }
57       if (! (st1.st_mtime == st2.st_mtime
58              && get_stat_mtime_ns (&st1) < get_stat_mtime_ns (&st2)))
59         delay = (st1.st_mtime & 1) ? 1000000 : 2000000;
60     }
61   usleep (delay);
62 }
63 #endif /* !TEST_CHOWN_NAP */
64
65 #if !HAVE_GETEGID
66 # define getegid() ((gid_t) -1)
67 #endif
68
69 #ifndef HAVE_LCHMOD
70 # define HAVE_LCHMOD 0
71 #endif
72
73 #ifndef CHOWN_CHANGE_TIME_BUG
74 # define CHOWN_CHANGE_TIME_BUG 0
75 #endif
76
77 /* This file is designed to test lchown(n,o,g) and
78    chownat(AT_FDCWD,n,o,g,AT_SYMLINK_NOFOLLOW).  FUNC is the function
79    to test.  Assumes that BASE and ASSERT are already defined, and
80    that appropriate headers are already included.  If PRINT, warn
81    before skipping symlink tests with status 77.  */
82
83 static int
84 test_lchown (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 != (uid_t) -1);
126   ASSERT (st1.st_gid != (gid_t) -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 (func (BASE "dir/file", (uid_t) -1, (gid_t) -1) == 0);
147   ASSERT (stat (BASE "dir/file", &st2) == 0);
148   ASSERT (st1.st_uid == st2.st_uid);
149   ASSERT (st1.st_gid == st2.st_gid);
150
151   /* Even if the values aren't changing, ctime is required to change
152      if at least one argument is not -1.  */
153   nap ();
154   ASSERT (func (BASE "dir/file", st1.st_uid, st1.st_gid) == 0);
155   ASSERT (stat (BASE "dir/file", &st2) == 0);
156   ASSERT (st1.st_ctime < st2.st_ctime
157           || (st1.st_ctime == st2.st_ctime
158               && get_stat_ctime_ns (&st1) < get_stat_ctime_ns (&st2)));
159
160   /* Test symlink behavior.  */
161   if (symlink ("link", BASE "dir/link2"))
162     {
163       ASSERT (unlink (BASE "dir/file") == 0);
164       ASSERT (rmdir (BASE "dir") == 0);
165       if (print)
166         fputs ("skipping test: symlinks not supported on this file system\n",
167                stderr);
168       return 77;
169     }
170   result = func (BASE "dir/link2", -1, -1);
171   if (result == -1 && errno == ENOSYS)
172     {
173       ASSERT (unlink (BASE "dir/file") == 0);
174       ASSERT (unlink (BASE "dir/link2") == 0);
175       ASSERT (rmdir (BASE "dir") == 0);
176       if (print)
177         fputs ("skipping test: symlink ownership not supported\n", stderr);
178       return 77;
179     }
180   ASSERT (result == 0);
181   errno = 0;
182   ASSERT (func (BASE "dir/link2/", st1.st_uid, st1.st_gid) == -1);
183   ASSERT (errno == ENOENT);
184   ASSERT (symlink ("file", BASE "dir/link") == 0);
185   ASSERT (mkdir (BASE "dir/sub", 0700) == 0);
186   ASSERT (symlink ("sub", BASE "dir/link3") == 0);
187
188   /* For non-privileged users, lchown can only portably succeed at
189      changing group ownership of a file we own.  If we belong to at
190      least two groups, then verifying the correct change is simple.
191      But if we belong to only one group, then we fall back on the
192      other observable effect of lchown: the ctime must be updated.  */
193   gids_count = mgetgroups (NULL, st1.st_gid, &gids);
194   if (1 < gids_count)
195     {
196       ASSERT (gids[1] != st1.st_gid);
197       ASSERT (gids[1] != (gid_t) -1);
198       ASSERT (lstat (BASE "dir/link", &st2) == 0);
199       ASSERT (st1.st_uid == st2.st_uid);
200       ASSERT (st1.st_gid == st2.st_gid);
201       ASSERT (lstat (BASE "dir/link2", &st2) == 0);
202       ASSERT (st1.st_uid == st2.st_uid);
203       ASSERT (st1.st_gid == st2.st_gid);
204
205       errno = 0;
206       ASSERT (func (BASE "dir/link2/", -1, gids[1]) == -1);
207       ASSERT (errno == ENOTDIR);
208       ASSERT (stat (BASE "dir/file", &st2) == 0);
209       ASSERT (st1.st_uid == st2.st_uid);
210       ASSERT (st1.st_gid == st2.st_gid);
211       ASSERT (lstat (BASE "dir/link", &st2) == 0);
212       ASSERT (st1.st_uid == st2.st_uid);
213       ASSERT (st1.st_gid == st2.st_gid);
214       ASSERT (lstat (BASE "dir/link2", &st2) == 0);
215       ASSERT (st1.st_uid == st2.st_uid);
216       ASSERT (st1.st_gid == st2.st_gid);
217
218       ASSERT (func (BASE "dir/link2", -1, gids[1]) == 0);
219       ASSERT (stat (BASE "dir/file", &st2) == 0);
220       ASSERT (st1.st_uid == st2.st_uid);
221       ASSERT (st1.st_gid == st2.st_gid);
222       ASSERT (lstat (BASE "dir/link", &st2) == 0);
223       ASSERT (st1.st_uid == st2.st_uid);
224       ASSERT (st1.st_gid == st2.st_gid);
225       ASSERT (lstat (BASE "dir/link2", &st2) == 0);
226       ASSERT (st1.st_uid == st2.st_uid);
227       ASSERT (gids[1] == st2.st_gid);
228
229       /* Trailing slash follows through to directory.  */
230       ASSERT (lstat (BASE "dir/link3", &st2) == 0);
231       ASSERT (st1.st_uid == st2.st_uid);
232       ASSERT (st1.st_gid == st2.st_gid);
233       ASSERT (lstat (BASE "dir/sub", &st2) == 0);
234       ASSERT (st1.st_uid == st2.st_uid);
235       ASSERT (st1.st_gid == st2.st_gid);
236
237       ASSERT (func (BASE "dir/link3/", -1, gids[1]) == 0);
238       ASSERT (lstat (BASE "dir/link3", &st2) == 0);
239       ASSERT (st1.st_uid == st2.st_uid);
240       ASSERT (st1.st_gid == st2.st_gid);
241       ASSERT (lstat (BASE "dir/sub", &st2) == 0);
242       ASSERT (st1.st_uid == st2.st_uid);
243       ASSERT (gids[1] == st2.st_gid);
244     }
245   else if (!CHOWN_CHANGE_TIME_BUG || HAVE_LCHMOD)
246     {
247       /* If we don't have lchmod, and lchown fails to change ctime,
248          then we can't test this part of lchown.  */
249       struct stat l1;
250       struct stat l2;
251       ASSERT (stat (BASE "dir/file", &st1) == 0);
252       ASSERT (lstat (BASE "dir/link", &l1) == 0);
253       ASSERT (lstat (BASE "dir/link2", &l2) == 0);
254
255       nap ();
256       errno = 0;
257       ASSERT (func (BASE "dir/link2/", -1, st1.st_gid) == -1);
258       ASSERT (errno == ENOTDIR);
259       ASSERT (stat (BASE "dir/file", &st2) == 0);
260       ASSERT (st1.st_ctime == st2.st_ctime);
261       ASSERT (get_stat_ctime_ns (&st1) == get_stat_ctime_ns (&st2));
262       ASSERT (lstat (BASE "dir/link", &st2) == 0);
263       ASSERT (l1.st_ctime == st2.st_ctime);
264       ASSERT (get_stat_ctime_ns (&l1) == get_stat_ctime_ns (&st2));
265       ASSERT (lstat (BASE "dir/link2", &st2) == 0);
266       ASSERT (l2.st_ctime == st2.st_ctime);
267       ASSERT (get_stat_ctime_ns (&l2) == get_stat_ctime_ns (&st2));
268
269       ASSERT (func (BASE "dir/link2", -1, st1.st_gid) == 0);
270       ASSERT (stat (BASE "dir/file", &st2) == 0);
271       ASSERT (st1.st_ctime == st2.st_ctime);
272       ASSERT (get_stat_ctime_ns (&st1) == get_stat_ctime_ns (&st2));
273       ASSERT (lstat (BASE "dir/link", &st2) == 0);
274       ASSERT (l1.st_ctime == st2.st_ctime);
275       ASSERT (get_stat_ctime_ns (&l1) == get_stat_ctime_ns (&st2));
276       ASSERT (lstat (BASE "dir/link2", &st2) == 0);
277       ASSERT (l2.st_ctime < st2.st_ctime
278               || (l2.st_ctime == st2.st_ctime
279                   && get_stat_ctime_ns (&l2) < get_stat_ctime_ns (&st2)));
280
281       /* Trailing slash follows through to directory.  */
282       ASSERT (lstat (BASE "dir/sub", &st1) == 0);
283       ASSERT (lstat (BASE "dir/link3", &l1) == 0);
284       nap ();
285       ASSERT (func (BASE "dir/link3/", -1, st1.st_gid) == 0);
286       ASSERT (lstat (BASE "dir/link3", &st2) == 0);
287       ASSERT (l1.st_ctime == st2.st_ctime);
288       ASSERT (get_stat_ctime_ns (&l1) == get_stat_ctime_ns (&st2));
289       ASSERT (lstat (BASE "dir/sub", &st2) == 0);
290       ASSERT (st1.st_ctime < st2.st_ctime
291               || (st1.st_ctime == st2.st_ctime
292                   && get_stat_ctime_ns (&st1) < get_stat_ctime_ns (&st2)));
293     }
294
295   /* Cleanup.  */
296   free (gids);
297   ASSERT (unlink (BASE "dir/file") == 0);
298   ASSERT (unlink (BASE "dir/link") == 0);
299   ASSERT (unlink (BASE "dir/link2") == 0);
300   ASSERT (unlink (BASE "dir/link3") == 0);
301   ASSERT (rmdir (BASE "dir/sub") == 0);
302   ASSERT (rmdir (BASE "dir") == 0);
303   return 0;
304 }