Merge branch 'upstream' into stable
[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   gids_count = mgetgroups (NULL, st1.st_gid, &gids);
175   if (1 < gids_count)
176     {
177       ASSERT (gids[1] != st1.st_gid);
178       ASSERT (gids[1] != (gid_t) -1);
179       ASSERT (lstat (BASE "dir/link", &st2) == 0);
180       ASSERT (st1.st_uid == st2.st_uid);
181       ASSERT (st1.st_gid == st2.st_gid);
182       ASSERT (lstat (BASE "dir/link2", &st2) == 0);
183       ASSERT (st1.st_uid == st2.st_uid);
184       ASSERT (st1.st_gid == st2.st_gid);
185
186       errno = 0;
187       ASSERT (func (BASE "dir/link2/", -1, gids[1]) == -1);
188       ASSERT (errno == ENOTDIR);
189       ASSERT (stat (BASE "dir/file", &st2) == 0);
190       ASSERT (st1.st_uid == st2.st_uid);
191       ASSERT (st1.st_gid == st2.st_gid);
192       ASSERT (lstat (BASE "dir/link", &st2) == 0);
193       ASSERT (st1.st_uid == st2.st_uid);
194       ASSERT (st1.st_gid == st2.st_gid);
195       ASSERT (lstat (BASE "dir/link2", &st2) == 0);
196       ASSERT (st1.st_uid == st2.st_uid);
197       ASSERT (st1.st_gid == st2.st_gid);
198
199       ASSERT (func (BASE "dir/link2", -1, gids[1]) == 0);
200       ASSERT (stat (BASE "dir/file", &st2) == 0);
201       ASSERT (st1.st_uid == st2.st_uid);
202       ASSERT (gids[1] == st2.st_gid);
203       ASSERT (lstat (BASE "dir/link", &st2) == 0);
204       ASSERT (st1.st_uid == st2.st_uid);
205       ASSERT (st1.st_gid == st2.st_gid);
206       ASSERT (lstat (BASE "dir/link2", &st2) == 0);
207       ASSERT (st1.st_uid == st2.st_uid);
208       ASSERT (st1.st_gid == st2.st_gid);
209     }
210   else
211     {
212       struct stat l1;
213       struct stat l2;
214       ASSERT (stat (BASE "dir/file", &st1) == 0);
215       ASSERT (lstat (BASE "dir/link", &l1) == 0);
216       ASSERT (lstat (BASE "dir/link2", &l2) == 0);
217
218       nap ();
219       errno = 0;
220       ASSERT (func (BASE "dir/link2/", -1, st1.st_gid) == -1);
221       ASSERT (errno == ENOTDIR);
222       ASSERT (stat (BASE "dir/file", &st2) == 0);
223       ASSERT (st1.st_ctime == st2.st_ctime);
224       ASSERT (get_stat_ctime_ns (&st1) == get_stat_ctime_ns (&st2));
225       ASSERT (lstat (BASE "dir/link", &st2) == 0);
226       ASSERT (l1.st_ctime == st2.st_ctime);
227       ASSERT (get_stat_ctime_ns (&l1) == get_stat_ctime_ns (&st2));
228       ASSERT (lstat (BASE "dir/link2", &st2) == 0);
229       ASSERT (l2.st_ctime == st2.st_ctime);
230       ASSERT (get_stat_ctime_ns (&l2) == get_stat_ctime_ns (&st2));
231
232       ASSERT (func (BASE "dir/link2", -1, st1.st_gid) == 0);
233       ASSERT (stat (BASE "dir/file", &st2) == 0);
234       ASSERT (st1.st_ctime < st2.st_ctime
235               || (st1.st_ctime == st2.st_ctime
236                   && get_stat_ctime_ns (&st1) < get_stat_ctime_ns (&st2)));
237       ASSERT (lstat (BASE "dir/link", &st2) == 0);
238       ASSERT (l1.st_ctime == st2.st_ctime);
239       ASSERT (get_stat_ctime_ns (&l1) == get_stat_ctime_ns (&st2));
240       ASSERT (lstat (BASE "dir/link2", &st2) == 0);
241       ASSERT (l2.st_ctime == st2.st_ctime);
242       ASSERT (get_stat_ctime_ns (&l2) == get_stat_ctime_ns (&st2));
243     }
244
245   /* Cleanup.  */
246   free (gids);
247   ASSERT (unlink (BASE "dir/file") == 0);
248   ASSERT (unlink (BASE "dir/link") == 0);
249   ASSERT (unlink (BASE "dir/link2") == 0);
250   ASSERT (rmdir (BASE "dir") == 0);
251   return 0;
252 }