chown: work around OpenBSD 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 #ifndef HAVE_LCHMOD
79 # define HAVE_LCHMOD 0
80 #endif
81
82 #ifndef CHOWN_CHANGE_TIME_BUG
83 # define CHOWN_CHANGE_TIME_BUG 0
84 #endif
85
86 /* This file is designed to test lchown(n,o,g) and
87    chownat(AT_FDCWD,n,o,g,AT_SYMLINK_NOFOLLOW).  FUNC is the function
88    to test.  Assumes that BASE and ASSERT are already defined, and
89    that appropriate headers are already included.  If PRINT, warn
90    before skipping symlink tests with status 77.  */
91
92 static int
93 test_lchown (int (*func) (char const *, uid_t, gid_t), bool print)
94 {
95   struct stat st1;
96   struct stat st2;
97   gid_t *gids = NULL;
98   int gids_count;
99   int result;
100
101   /* Solaris 8 is interesting - if the current process belongs to
102      multiple groups, the current directory is owned by a a group that
103      the current process belongs to but different than getegid(), and
104      the current directory does not have the S_ISGID bit, then regular
105      files created in the directory belong to the directory's group,
106      but symlinks belong to the current effective group id.  If
107      S_ISGID is set, then both files and symlinks belong to the
108      directory's group.  However, it is possible to run the testsuite
109      from within a directory owned by a group we don't belong to, in
110      which case all things that we create belong to the current
111      effective gid.  So, work around the issues by creating a
112      subdirectory (we are guaranteed that the subdirectory will be
113      owned by one of our current groups), change ownership of that
114      directory to the current effective gid (which will thus succeed),
115      then create all other files within that directory (eliminating
116      questions on whether inheritance or current id triumphs, since
117      the two methods resolve to the same gid).  */
118   ASSERT (mkdir (BASE "dir", 0700) == 0);
119   ASSERT (stat (BASE "dir", &st1) == 0);
120
121   /* Filter out mingw, which has no concept of groups.  */
122   result = func (BASE "dir", st1.st_uid, getegid ());
123   if (result == -1 && errno == ENOSYS)
124     {
125       ASSERT (rmdir (BASE "dir") == 0);
126       if (print)
127         fputs ("skipping test: no support for ownership\n", stderr);
128       return 77;
129     }
130   ASSERT (result == 0);
131
132   ASSERT (close (creat (BASE "dir/file", 0600)) == 0);
133   ASSERT (stat (BASE "dir/file", &st1) == 0);
134   ASSERT (st1.st_uid != -1);
135   ASSERT (st1.st_gid != -1);
136   ASSERT (st1.st_gid == getegid ());
137
138   /* Sanity check of error cases.  */
139   errno = 0;
140   ASSERT (func ("", -1, -1) == -1);
141   ASSERT (errno == ENOENT);
142   errno = 0;
143   ASSERT (func ("no_such", -1, -1) == -1);
144   ASSERT (errno == ENOENT);
145   errno = 0;
146   ASSERT (func ("no_such/", -1, -1) == -1);
147   ASSERT (errno == ENOENT);
148   errno = 0;
149   ASSERT (func (BASE "dir/file/", -1, -1) == -1);
150   ASSERT (errno == ENOTDIR);
151
152   /* Check that -1 does not alter ownership.  */
153   ASSERT (func (BASE "dir/file", -1, st1.st_gid) == 0);
154   ASSERT (func (BASE "dir/file", st1.st_uid, -1) == 0);
155   ASSERT (stat (BASE "dir/file", &st2) == 0);
156   ASSERT (st1.st_uid == st2.st_uid);
157   ASSERT (st1.st_gid == st2.st_gid);
158
159   /* Even if the values aren't changing, ctime is required to change
160      if at least one argument is not -1.  */
161   nap ();
162   ASSERT (func (BASE "dir/file", st1.st_uid, st1.st_gid) == 0);
163   ASSERT (stat (BASE "dir/file", &st2) == 0);
164   ASSERT (st1.st_ctime < st2.st_ctime
165           || (st1.st_ctime == st2.st_ctime
166               && get_stat_ctime_ns (&st1) < get_stat_ctime_ns (&st2)));
167
168   /* Test symlink behavior.  */
169   if (symlink ("link", BASE "dir/link2"))
170     {
171       ASSERT (unlink (BASE "dir/file") == 0);
172       ASSERT (rmdir (BASE "dir") == 0);
173       if (print)
174         fputs ("skipping test: symlinks not supported on this file system\n",
175                stderr);
176       return 77;
177     }
178   result = func (BASE "dir/link2", -1, -1);
179   if (result == -1 && errno == ENOSYS)
180     {
181       ASSERT (unlink (BASE "dir/file") == 0);
182       ASSERT (unlink (BASE "dir/link2") == 0);
183       ASSERT (rmdir (BASE "dir") == 0);
184       if (print)
185         fputs ("skipping test: symlink ownership not supported\n", stderr);
186       return 77;
187     }
188   ASSERT (result == 0);
189   errno = 0;
190   ASSERT (func (BASE "dir/link2/", st1.st_uid, st1.st_gid) == -1);
191   ASSERT (errno == ENOENT);
192   ASSERT (symlink ("file", BASE "dir/link") == 0);
193   ASSERT (mkdir (BASE "dir/sub", 0700) == 0);
194   ASSERT (symlink ("sub", BASE "dir/link3") == 0);
195
196   /* For non-privileged users, lchown can only portably succeed at
197      changing group ownership of a file we own.  If we belong to at
198      least two groups, then verifying the correct change is simple.
199      But if we belong to only one group, then we fall back on the
200      other observable effect of lchown: the ctime must be updated.
201      Be careful of duplicates returned by getgroups.  */
202   gids_count = mgetgroups (NULL, -1, &gids);
203   if (2 <= gids_count && gids[0] == gids[1] && 2 < gids_count--)
204     gids[1] = gids[2];
205   if (1 < gids_count || (gids_count == 1 && gids[0] != st1.st_gid))
206     {
207       if (gids[0] == st1.st_gid)
208         {
209           ASSERT (1 < gids_count);
210           ASSERT (gids[0] != gids[1]);
211           gids[0] = gids[1];
212         }
213       ASSERT (gids[0] != st1.st_gid);
214       ASSERT (gids[0] != -1);
215       ASSERT (lstat (BASE "dir/link", &st2) == 0);
216       ASSERT (st1.st_uid == st2.st_uid);
217       ASSERT (st1.st_gid == st2.st_gid);
218       ASSERT (lstat (BASE "dir/link2", &st2) == 0);
219       ASSERT (st1.st_uid == st2.st_uid);
220       ASSERT (st1.st_gid == st2.st_gid);
221
222       errno = 0;
223       ASSERT (func (BASE "dir/link2/", -1, gids[0]) == -1);
224       ASSERT (errno == ENOTDIR);
225       ASSERT (stat (BASE "dir/file", &st2) == 0);
226       ASSERT (st1.st_uid == st2.st_uid);
227       ASSERT (st1.st_gid == st2.st_gid);
228       ASSERT (lstat (BASE "dir/link", &st2) == 0);
229       ASSERT (st1.st_uid == st2.st_uid);
230       ASSERT (st1.st_gid == st2.st_gid);
231       ASSERT (lstat (BASE "dir/link2", &st2) == 0);
232       ASSERT (st1.st_uid == st2.st_uid);
233       ASSERT (st1.st_gid == st2.st_gid);
234
235       ASSERT (func (BASE "dir/link2", -1, gids[0]) == 0);
236       ASSERT (stat (BASE "dir/file", &st2) == 0);
237       ASSERT (st1.st_uid == st2.st_uid);
238       ASSERT (st1.st_gid == st2.st_gid);
239       ASSERT (lstat (BASE "dir/link", &st2) == 0);
240       ASSERT (st1.st_uid == st2.st_uid);
241       ASSERT (st1.st_gid == st2.st_gid);
242       ASSERT (lstat (BASE "dir/link2", &st2) == 0);
243       ASSERT (st1.st_uid == st2.st_uid);
244       ASSERT (gids[0] == st2.st_gid);
245
246       /* Trailing slash follows through to directory.  */
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 (st1.st_gid == st2.st_gid);
253
254       ASSERT (func (BASE "dir/link3/", -1, gids[0]) == 0);
255       ASSERT (lstat (BASE "dir/link3", &st2) == 0);
256       ASSERT (st1.st_uid == st2.st_uid);
257       ASSERT (st1.st_gid == st2.st_gid);
258       ASSERT (lstat (BASE "dir/sub", &st2) == 0);
259       ASSERT (st1.st_uid == st2.st_uid);
260       ASSERT (gids[0] == st2.st_gid);
261     }
262   else if (!CHOWN_CHANGE_TIME_BUG || HAVE_LCHMOD)
263     {
264       /* If we don't have lchmod, and lchown fails to change ctime,
265          then we can't test this part of lchown.  */
266       struct stat l1;
267       struct stat l2;
268       ASSERT (stat (BASE "dir/file", &st1) == 0);
269       ASSERT (lstat (BASE "dir/link", &l1) == 0);
270       ASSERT (lstat (BASE "dir/link2", &l2) == 0);
271
272       nap ();
273       errno = 0;
274       ASSERT (func (BASE "dir/link2/", -1, st1.st_gid) == -1);
275       ASSERT (errno == ENOTDIR);
276       ASSERT (stat (BASE "dir/file", &st2) == 0);
277       ASSERT (st1.st_ctime == st2.st_ctime);
278       ASSERT (get_stat_ctime_ns (&st1) == get_stat_ctime_ns (&st2));
279       ASSERT (lstat (BASE "dir/link", &st2) == 0);
280       ASSERT (l1.st_ctime == st2.st_ctime);
281       ASSERT (get_stat_ctime_ns (&l1) == get_stat_ctime_ns (&st2));
282       ASSERT (lstat (BASE "dir/link2", &st2) == 0);
283       ASSERT (l2.st_ctime == st2.st_ctime);
284       ASSERT (get_stat_ctime_ns (&l2) == get_stat_ctime_ns (&st2));
285
286       ASSERT (func (BASE "dir/link2", -1, gids[0]) == 0);
287       ASSERT (stat (BASE "dir/file", &st2) == 0);
288       ASSERT (st1.st_ctime == st2.st_ctime);
289       ASSERT (get_stat_ctime_ns (&st1) == get_stat_ctime_ns (&st2));
290       ASSERT (lstat (BASE "dir/link", &st2) == 0);
291       ASSERT (l1.st_ctime == st2.st_ctime);
292       ASSERT (get_stat_ctime_ns (&l1) == get_stat_ctime_ns (&st2));
293       ASSERT (lstat (BASE "dir/link2", &st2) == 0);
294       ASSERT (l2.st_ctime < st2.st_ctime
295               || (l2.st_ctime == st2.st_ctime
296                   && get_stat_ctime_ns (&l2) < get_stat_ctime_ns (&st2)));
297
298       /* Trailing slash follows through to directory.  */
299       ASSERT (lstat (BASE "dir/sub", &st1) == 0);
300       ASSERT (lstat (BASE "dir/link3", &l1) == 0);
301       nap ();
302       ASSERT (func (BASE "dir/link3/", -1, gids[0]) == 0);
303       ASSERT (lstat (BASE "dir/link3", &st2) == 0);
304       ASSERT (l1.st_ctime == st2.st_ctime);
305       ASSERT (get_stat_ctime_ns (&l1) == get_stat_ctime_ns (&st2));
306       ASSERT (lstat (BASE "dir/sub", &st2) == 0);
307       ASSERT (st1.st_ctime < st2.st_ctime
308               || (st1.st_ctime == st2.st_ctime
309                   && get_stat_ctime_ns (&st1) < get_stat_ctime_ns (&st2)));
310     }
311
312   /* Cleanup.  */
313   free (gids);
314   ASSERT (unlink (BASE "dir/file") == 0);
315   ASSERT (unlink (BASE "dir/link") == 0);
316   ASSERT (unlink (BASE "dir/link2") == 0);
317   ASSERT (unlink (BASE "dir/link3") == 0);
318   ASSERT (rmdir (BASE "dir/sub") == 0);
319   ASSERT (rmdir (BASE "dir") == 0);
320   return 0;
321 }