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