all: prefer https: URLs
[gnulib.git] / tests / test-lchown.h
blobac3d6d89cc59de347e8b164d69268d1fb4238b1f
1 /* Tests of lchown.
2 Copyright (C) 2009-2017 Free Software Foundation, Inc.
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.
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.
14 You should have received a copy of the GNU General Public License
15 along with this program. If not, see <https://www.gnu.org/licenses/>. */
17 /* Written by Eric Blake <ebb9@byu.net>, 2009. */
19 #include "nap.h"
21 #if !HAVE_GETEGID
22 # define getegid() ((gid_t) -1)
23 #endif
25 #ifndef HAVE_LCHMOD
26 # define HAVE_LCHMOD 0
27 #endif
29 #ifndef CHOWN_CHANGE_TIME_BUG
30 # define CHOWN_CHANGE_TIME_BUG 0
31 #endif
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. */
39 static int
40 test_lchown (int (*func) (char const *, uid_t, gid_t), bool print)
42 struct stat st1;
43 struct stat st2;
44 gid_t *gids = NULL;
45 int gids_count;
46 int result;
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);
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))
72 ASSERT (rmdir (BASE "dir") == 0);
73 if (print)
74 fputs ("skipping test: no support for ownership\n", stderr);
75 return 77;
77 ASSERT (result == 0);
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 ());
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);
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);
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)));
116 /* Test symlink behavior. */
117 if (symlink ("link", BASE "dir/link2"))
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;
126 result = func (BASE "dir/link2", -1, -1);
127 if (result == -1 && errno == ENOSYS)
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;
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);
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)
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);
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);
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);
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);
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);
201 else if (!CHOWN_CHANGE_TIME_BUG || HAVE_LCHMOD)
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);
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));
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)));
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)));
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;