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