maint.mk: Update system header list for #include syntax checks.
[gnulib.git] / tests / test-chown.h
blobda58838ad810f710e064b0762c6c67fbdafc9e49
1 /* Tests of chown.
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. */
19 #include "nap.h"
21 #if !HAVE_GETGID
22 # define getgid() ((gid_t) -1)
23 #endif
25 #if !HAVE_GETEGID
26 # define getegid() ((gid_t) -1)
27 #endif
29 /* This file is designed to test chown(n,o,g) and
30 chownat(AT_FDCWD,n,o,g,0). FUNC is the function to test. Assumes
31 that BASE and ASSERT are already defined, and that appropriate
32 headers are already included. If PRINT, warn before skipping
33 symlink tests with status 77. */
35 static int
36 test_chown (int (*func) (char const *, uid_t, gid_t), bool print)
38 struct stat st1;
39 struct stat st2;
40 gid_t *gids = NULL;
41 int gids_count;
42 int result;
44 /* Solaris 8 is interesting - if the current process belongs to
45 multiple groups, the current directory is owned by a group that
46 the current process belongs to but different than getegid(), and
47 the current directory does not have the S_ISGID bit, then regular
48 files created in the directory belong to the directory's group,
49 but symlinks belong to the current effective group id. If
50 S_ISGID is set, then both files and symlinks belong to the
51 directory's group. However, it is possible to run the testsuite
52 from within a directory owned by a group we don't belong to, in
53 which case all things that we create belong to the current
54 effective gid. So, work around the issues by creating a
55 subdirectory (we are guaranteed that the subdirectory will be
56 owned by one of our current groups), change ownership of that
57 directory to the current effective gid (which will thus succeed),
58 then create all other files within that directory (eliminating
59 questions on whether inheritance or current id triumphs, since
60 the two methods resolve to the same gid). */
61 ASSERT (mkdir (BASE "dir", 0700) == 0);
62 ASSERT (stat (BASE "dir", &st1) == 0);
64 /* Filter out mingw and file systems which have no concept of groups. */
65 result = func (BASE "dir", st1.st_uid, getegid ());
66 if (result == -1 && (errno == ENOSYS || errno == EPERM))
68 ASSERT (rmdir (BASE "dir") == 0);
69 if (print)
70 fputs ("skipping test: no support for ownership\n", stderr);
71 return 77;
73 ASSERT (result == 0);
75 ASSERT (close (creat (BASE "dir/file", 0600)) == 0);
76 ASSERT (stat (BASE "dir/file", &st1) == 0);
77 ASSERT (st1.st_uid != (uid_t) -1);
78 ASSERT (st1.st_gid != (gid_t) -1);
79 /* On macOS 12, when logged in through ssh, getgid () and getegid () are both
80 == (gid_t) -1. */
81 if (getgid () != (gid_t) -1)
82 ASSERT (st1.st_gid == getegid ());
84 /* Sanity check of error cases. */
85 errno = 0;
86 ASSERT (func ("", -1, -1) == -1);
87 ASSERT (errno == ENOENT);
88 errno = 0;
89 ASSERT (func ("no_such", -1, -1) == -1);
90 ASSERT (errno == ENOENT);
91 errno = 0;
92 ASSERT (func ("no_such/", -1, -1) == -1);
93 ASSERT (errno == ENOENT);
94 errno = 0;
95 ASSERT (func (BASE "dir/file/", -1, -1) == -1);
96 ASSERT (errno == ENOTDIR);
98 /* Check that -1 does not alter ownership. */
99 ASSERT (func (BASE "dir/file", -1, st1.st_gid) == 0);
100 ASSERT (func (BASE "dir/file", st1.st_uid, -1) == 0);
101 ASSERT (func (BASE "dir/file", (uid_t) -1, (gid_t) -1) == 0);
102 ASSERT (stat (BASE "dir/file", &st2) == 0);
103 ASSERT (st1.st_uid == st2.st_uid);
104 ASSERT (st1.st_gid == st2.st_gid);
106 /* Even if the values aren't changing, ctime is required to change
107 if at least one argument is not -1. */
108 nap ();
109 ASSERT (func (BASE "dir/file", st1.st_uid, st1.st_gid) == 0);
110 ASSERT (stat (BASE "dir/file", &st2) == 0);
111 ASSERT (st1.st_ctime < st2.st_ctime
112 || (st1.st_ctime == st2.st_ctime
113 && get_stat_ctime_ns (&st1) < get_stat_ctime_ns (&st2)));
115 /* Test symlink behavior. */
116 if (symlink ("link", BASE "dir/link2"))
118 ASSERT (unlink (BASE "dir/file") == 0);
119 ASSERT (rmdir (BASE "dir") == 0);
120 if (print)
121 fputs ("skipping test: symlinks not supported on this file system\n",
122 stderr);
123 return 77;
125 errno = 0;
126 ASSERT (func (BASE "dir/link2", -1, -1) == -1);
127 ASSERT (errno == ENOENT);
128 errno = 0;
129 ASSERT (func (BASE "dir/link2/", st1.st_uid, st1.st_gid) == -1);
130 ASSERT (errno == ENOENT);
131 ASSERT (symlink ("file", BASE "dir/link") == 0);
133 /* For non-privileged users, chown can only portably succeed at
134 changing group ownership of a file we own. If we belong to at
135 least two groups, then verifying the correct change is simple.
136 But if we belong to only one group, then we fall back on the
137 other observable effect of chown: the ctime must be updated. */
138 gids_count = mgetgroups (NULL, st1.st_gid, &gids);
139 if (1 < gids_count)
141 ASSERT (gids[1] != st1.st_gid);
142 if (getgid () != (gid_t) -1)
143 ASSERT (gids[1] != (gid_t) -1);
144 ASSERT (lstat (BASE "dir/link", &st2) == 0);
145 ASSERT (st1.st_uid == st2.st_uid);
146 ASSERT (st1.st_gid == st2.st_gid);
147 ASSERT (lstat (BASE "dir/link2", &st2) == 0);
148 ASSERT (st1.st_uid == st2.st_uid);
149 ASSERT (st1.st_gid == st2.st_gid);
151 errno = 0;
152 ASSERT (func (BASE "dir/link2/", -1, gids[1]) == -1);
153 ASSERT (errno == ENOTDIR);
154 ASSERT (stat (BASE "dir/file", &st2) == 0);
155 ASSERT (st1.st_uid == st2.st_uid);
156 ASSERT (st1.st_gid == st2.st_gid);
157 ASSERT (lstat (BASE "dir/link", &st2) == 0);
158 ASSERT (st1.st_uid == st2.st_uid);
159 ASSERT (st1.st_gid == st2.st_gid);
160 ASSERT (lstat (BASE "dir/link2", &st2) == 0);
161 ASSERT (st1.st_uid == st2.st_uid);
162 ASSERT (st1.st_gid == st2.st_gid);
164 ASSERT (func (BASE "dir/link2", -1, gids[1]) == 0);
165 ASSERT (stat (BASE "dir/file", &st2) == 0);
166 ASSERT (st1.st_uid == st2.st_uid);
167 if (getgid () != (gid_t) -1)
168 ASSERT (gids[1] == st2.st_gid);
169 ASSERT (lstat (BASE "dir/link", &st2) == 0);
170 ASSERT (st1.st_uid == st2.st_uid);
171 ASSERT (st1.st_gid == st2.st_gid);
172 ASSERT (lstat (BASE "dir/link2", &st2) == 0);
173 ASSERT (st1.st_uid == st2.st_uid);
174 ASSERT (st1.st_gid == st2.st_gid);
176 else
178 struct stat l1;
179 struct stat l2;
180 ASSERT (stat (BASE "dir/file", &st1) == 0);
181 ASSERT (lstat (BASE "dir/link", &l1) == 0);
182 ASSERT (lstat (BASE "dir/link2", &l2) == 0);
184 nap ();
185 errno = 0;
186 ASSERT (func (BASE "dir/link2/", -1, st1.st_gid) == -1);
187 ASSERT (errno == ENOTDIR);
188 ASSERT (stat (BASE "dir/file", &st2) == 0);
189 ASSERT (st1.st_ctime == st2.st_ctime);
190 ASSERT (get_stat_ctime_ns (&st1) == get_stat_ctime_ns (&st2));
191 ASSERT (lstat (BASE "dir/link", &st2) == 0);
192 ASSERT (l1.st_ctime == st2.st_ctime);
193 ASSERT (get_stat_ctime_ns (&l1) == get_stat_ctime_ns (&st2));
194 ASSERT (lstat (BASE "dir/link2", &st2) == 0);
195 ASSERT (l2.st_ctime == st2.st_ctime);
196 ASSERT (get_stat_ctime_ns (&l2) == get_stat_ctime_ns (&st2));
198 ASSERT (func (BASE "dir/link2", -1, st1.st_gid) == 0);
199 ASSERT (stat (BASE "dir/file", &st2) == 0);
200 ASSERT (st1.st_ctime < st2.st_ctime
201 || (st1.st_ctime == st2.st_ctime
202 && get_stat_ctime_ns (&st1) < get_stat_ctime_ns (&st2)));
203 ASSERT (lstat (BASE "dir/link", &st2) == 0);
204 ASSERT (l1.st_ctime == st2.st_ctime);
205 ASSERT (get_stat_ctime_ns (&l1) == get_stat_ctime_ns (&st2));
206 ASSERT (lstat (BASE "dir/link2", &st2) == 0);
207 ASSERT (l2.st_ctime == st2.st_ctime);
208 ASSERT (get_stat_ctime_ns (&l2) == get_stat_ctime_ns (&st2));
211 /* Cleanup. */
212 free (gids);
213 ASSERT (unlink (BASE "dir/file") == 0);
214 ASSERT (unlink (BASE "dir/link") == 0);
215 ASSERT (unlink (BASE "dir/link2") == 0);
216 ASSERT (rmdir (BASE "dir") == 0);
217 return 0;