lchown: detect Solaris and FreeBSD bug
[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 # 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 #endif /* !TEST_CHOWN_NAP */
73
74 #if !HAVE_GETEGID
75 # define getegid() (-1)
76 #endif
77
78 /* This file is designed to test lchown(n,o,g) and
79    chownat(AT_FDCWD,n,o,g,AT_SYMLINK_NOFOLLOW).  FUNC is the function
80    to test.  Assumes that BASE and ASSERT are already defined, and
81    that appropriate headers are already included.  If PRINT, warn
82    before skipping symlink tests with status 77.  */
83
84 static int
85 test_lchown (int (*func) (char const *, uid_t, gid_t), bool print)
86 {
87   struct stat st1;
88   struct stat st2;
89   gid_t *gids = NULL;
90   int gids_count;
91   int result;
92
93   /* Solaris 8 is interesting - if the current process belongs to
94      multiple groups, the current directory is owned by a a group that
95      the current process belongs to but different than getegid(), and
96      the current directory does not have the S_ISGID bit, then regular
97      files created in the directory belong to the directory's group,
98      but symlinks belong to the current effective group id.  If
99      S_ISGID is set, then both files and symlinks belong to the
100      directory's group.  However, it is possible to run the testsuite
101      from within a directory owned by a group we don't belong to, in
102      which case all things that we create belong to the current
103      effective gid.  So, work around the issues by creating a
104      subdirectory (we are guaranteed that the subdirectory will be
105      owned by one of our current groups), change ownership of that
106      directory to the current effective gid (which will thus succeed),
107      then create all other files within that directory (eliminating
108      questions on whether inheritance or current id triumphs, since
109      the two methods resolve to the same gid).  */
110   ASSERT (mkdir (BASE "dir", 0700) == 0);
111   ASSERT (stat (BASE "dir", &st1) == 0);
112
113   /* Filter out mingw, which has no concept of groups.  */
114   result = func (BASE "dir", st1.st_uid, getegid ());
115   if (result == -1 && errno == ENOSYS)
116     {
117       ASSERT (rmdir (BASE "dir") == 0);
118       if (print)
119         fputs ("skipping test: no support for ownership\n", stderr);
120       return 77;
121     }
122   ASSERT (result == 0);
123
124   ASSERT (close (creat (BASE "dir/file", 0600)) == 0);
125   ASSERT (stat (BASE "dir/file", &st1) == 0);
126   ASSERT (st1.st_uid != -1);
127   ASSERT (st1.st_gid != -1);
128   ASSERT (st1.st_gid == getegid ());
129
130   /* Sanity check of error cases.  */
131   errno = 0;
132   ASSERT (func ("", -1, -1) == -1);
133   ASSERT (errno == ENOENT);
134   errno = 0;
135   ASSERT (func ("no_such", -1, -1) == -1);
136   ASSERT (errno == ENOENT);
137   errno = 0;
138   ASSERT (func ("no_such/", -1, -1) == -1);
139   ASSERT (errno == ENOENT);
140   errno = 0;
141   ASSERT (func (BASE "dir/file/", -1, -1) == -1);
142   ASSERT (errno == ENOTDIR);
143
144   /* Check that -1 does not alter ownership.  */
145   ASSERT (func (BASE "dir/file", -1, st1.st_gid) == 0);
146   ASSERT (func (BASE "dir/file", st1.st_uid, -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      Be careful of duplicates returned by getgroups.  */
194   gids_count = mgetgroups (NULL, -1, &gids);
195   if (2 <= gids_count && gids[0] == gids[1] && 2 < gids_count--)
196     gids[1] = gids[2];
197   if (1 < gids_count || (gids_count == 1 && gids[0] != st1.st_gid))
198     {
199       if (gids[0] == st1.st_gid)
200         {
201           ASSERT (1 < gids_count);
202           ASSERT (gids[0] != gids[1]);
203           gids[0] = gids[1];
204         }
205       ASSERT (gids[0] != st1.st_gid);
206       ASSERT (gids[0] != -1);
207       ASSERT (lstat (BASE "dir/link", &st2) == 0);
208       ASSERT (st1.st_uid == st2.st_uid);
209       ASSERT (st1.st_gid == st2.st_gid);
210       ASSERT (lstat (BASE "dir/link2", &st2) == 0);
211       ASSERT (st1.st_uid == st2.st_uid);
212       ASSERT (st1.st_gid == st2.st_gid);
213
214       errno = 0;
215       ASSERT (func (BASE "dir/link2/", -1, gids[0]) == -1);
216       ASSERT (errno == ENOTDIR);
217       ASSERT (stat (BASE "dir/file", &st2) == 0);
218       ASSERT (st1.st_uid == st2.st_uid);
219       ASSERT (st1.st_gid == 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       ASSERT (func (BASE "dir/link2", -1, gids[0]) == 0);
228       ASSERT (stat (BASE "dir/file", &st2) == 0);
229       ASSERT (st1.st_uid == st2.st_uid);
230       ASSERT (st1.st_gid == st2.st_gid);
231       ASSERT (lstat (BASE "dir/link", &st2) == 0);
232       ASSERT (st1.st_uid == st2.st_uid);
233       ASSERT (st1.st_gid == st2.st_gid);
234       ASSERT (lstat (BASE "dir/link2", &st2) == 0);
235       ASSERT (st1.st_uid == st2.st_uid);
236       ASSERT (gids[0] == st2.st_gid);
237
238       /* Trailing slash follows through to directory.  */
239       ASSERT (lstat (BASE "dir/link3", &st2) == 0);
240       ASSERT (st1.st_uid == st2.st_uid);
241       ASSERT (st1.st_gid == st2.st_gid);
242       ASSERT (lstat (BASE "dir/sub", &st2) == 0);
243       ASSERT (st1.st_uid == st2.st_uid);
244       ASSERT (st1.st_gid == st2.st_gid);
245
246       ASSERT (func (BASE "dir/link3/", -1, gids[0]) == 0);
247       ASSERT (lstat (BASE "dir/link3", &st2) == 0);
248       ASSERT (st1.st_uid == st2.st_uid);
249       ASSERT (st1.st_gid == st2.st_gid);
250       ASSERT (lstat (BASE "dir/sub", &st2) == 0);
251       ASSERT (st1.st_uid == st2.st_uid);
252       ASSERT (gids[0] == st2.st_gid);
253     }
254   else
255     {
256       struct stat l1;
257       struct stat l2;
258       ASSERT (stat (BASE "dir/file", &st1) == 0);
259       ASSERT (lstat (BASE "dir/link", &l1) == 0);
260       ASSERT (lstat (BASE "dir/link2", &l2) == 0);
261
262       nap ();
263       errno = 0;
264       ASSERT (func (BASE "dir/link2/", -1, st1.st_gid) == -1);
265       ASSERT (errno == ENOTDIR);
266       ASSERT (stat (BASE "dir/file", &st2) == 0);
267       ASSERT (st1.st_ctime == st2.st_ctime);
268       ASSERT (get_stat_ctime_ns (&st1) == get_stat_ctime_ns (&st2));
269       ASSERT (lstat (BASE "dir/link", &st2) == 0);
270       ASSERT (l1.st_ctime == st2.st_ctime);
271       ASSERT (get_stat_ctime_ns (&l1) == get_stat_ctime_ns (&st2));
272       ASSERT (lstat (BASE "dir/link2", &st2) == 0);
273       ASSERT (l2.st_ctime == st2.st_ctime);
274       ASSERT (get_stat_ctime_ns (&l2) == get_stat_ctime_ns (&st2));
275
276       ASSERT (func (BASE "dir/link2", -1, gids[0]) == 0);
277       ASSERT (stat (BASE "dir/file", &st2) == 0);
278       ASSERT (st1.st_ctime == st2.st_ctime);
279       ASSERT (get_stat_ctime_ns (&st1) == get_stat_ctime_ns (&st2));
280       ASSERT (lstat (BASE "dir/link", &st2) == 0);
281       ASSERT (l1.st_ctime == st2.st_ctime);
282       ASSERT (get_stat_ctime_ns (&l1) == get_stat_ctime_ns (&st2));
283       ASSERT (lstat (BASE "dir/link2", &st2) == 0);
284       ASSERT (l2.st_ctime < st2.st_ctime
285               || (l2.st_ctime == st2.st_ctime
286                   && get_stat_ctime_ns (&l2) < get_stat_ctime_ns (&st2)));
287
288       /* Trailing slash follows through to directory.  */
289       ASSERT (lstat (BASE "dir/sub", &st1) == 0);
290       ASSERT (lstat (BASE "dir/link3", &l1) == 0);
291       nap ();
292       ASSERT (func (BASE "dir/link3/", -1, gids[0]) == 0);
293       ASSERT (lstat (BASE "dir/link3", &st2) == 0);
294       ASSERT (l1.st_ctime == st2.st_ctime);
295       ASSERT (get_stat_ctime_ns (&l1) == get_stat_ctime_ns (&st2));
296       ASSERT (lstat (BASE "dir/sub", &st2) == 0);
297       ASSERT (st1.st_ctime < st2.st_ctime
298               || (st1.st_ctime == st2.st_ctime
299                   && get_stat_ctime_ns (&st1) < get_stat_ctime_ns (&st2)));
300     }
301
302   /* Cleanup.  */
303   free (gids);
304   ASSERT (unlink (BASE "dir/file") == 0);
305   ASSERT (unlink (BASE "dir/link") == 0);
306   ASSERT (unlink (BASE "dir/link2") == 0);
307   ASSERT (unlink (BASE "dir/link3") == 0);
308   ASSERT (rmdir (BASE "dir/sub") == 0);
309   ASSERT (rmdir (BASE "dir") == 0);
310   return 0;
311 }