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