]>
Commit | Line | Data |
---|---|---|
85f7554c | 1 | /* Test open and openat with O_TMPFILE. |
dff8da6b | 2 | Copyright (C) 2016-2024 Free Software Foundation, Inc. |
85f7554c FW |
3 | This file is part of the GNU C Library. |
4 | ||
5 | The GNU C Library is free software; you can redistribute it and/or | |
6 | modify it under the terms of the GNU Lesser General Public | |
7 | License as published by the Free Software Foundation; either | |
8 | version 2.1 of the License, or (at your option) any later version. | |
9 | ||
10 | The GNU C Library is distributed in the hope that it will be useful, | |
11 | but WITHOUT ANY WARRANTY; without even the implied warranty of | |
12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
13 | Lesser General Public License for more details. | |
14 | ||
15 | You should have received a copy of the GNU Lesser General Public | |
16 | License along with the GNU C Library; if not, see | |
5a82c748 | 17 | <https://www.gnu.org/licenses/>. */ |
85f7554c FW |
18 | |
19 | /* This test verifies that open and openat work as expected, i.e. they | |
20 | create a deleted file with the requested file mode. */ | |
21 | ||
22 | #include <errno.h> | |
23 | #include <fcntl.h> | |
24 | #include <stdbool.h> | |
25 | #include <stdio.h> | |
26 | #include <stdlib.h> | |
27 | #include <string.h> | |
28 | #include <sys/stat.h> | |
29 | #include <unistd.h> | |
30 | ||
c23de0aa | 31 | #include <support/support.h> |
85f7554c FW |
32 | |
33 | #ifdef O_TMPFILE | |
34 | typedef int (*wrapper_func) (const char *, int, mode_t); | |
35 | ||
36 | /* Error-checking wrapper for the open function, compatible with the | |
37 | wrapper_func type. */ | |
38 | static int | |
39 | wrap_open (const char *path, int flags, mode_t mode) | |
40 | { | |
41 | int ret = open (path, flags, mode); | |
42 | if (ret < 0) | |
43 | { | |
44 | printf ("error: open (\"%s\", 0x%x, 0%03o): %m\n", path, flags, mode); | |
45 | exit (1); | |
46 | } | |
47 | return ret; | |
48 | } | |
49 | ||
50 | /* Error-checking wrapper for the openat function, compatible with the | |
51 | wrapper_func type. */ | |
52 | static int | |
53 | wrap_openat (const char *path, int flags, mode_t mode) | |
54 | { | |
55 | int ret = openat (AT_FDCWD, path, flags, mode); | |
56 | if (ret < 0) | |
57 | { | |
58 | printf ("error: openat (\"%s\", 0x%x, 0%03o): %m\n", path, flags, mode); | |
59 | exit (1); | |
60 | } | |
61 | return ret; | |
62 | } | |
63 | ||
67b73ea4 FW |
64 | /* Error-checking wrapper for the open64 function, compatible with the |
65 | wrapper_func type. */ | |
66 | static int | |
67 | wrap_open64 (const char *path, int flags, mode_t mode) | |
68 | { | |
69 | int ret = open64 (path, flags, mode); | |
70 | if (ret < 0) | |
71 | { | |
72 | printf ("error: open64 (\"%s\", 0x%x, 0%03o): %m\n", path, flags, mode); | |
73 | exit (1); | |
74 | } | |
75 | return ret; | |
76 | } | |
77 | ||
78 | /* Error-checking wrapper for the openat64 function, compatible with the | |
79 | wrapper_func type. */ | |
80 | static int | |
81 | wrap_openat64 (const char *path, int flags, mode_t mode) | |
82 | { | |
83 | int ret = openat64 (AT_FDCWD, path, flags, mode); | |
84 | if (ret < 0) | |
85 | { | |
86 | printf ("error: openat64 (\"%s\", 0x%x, 0%03o): %m\n", path, flags, mode); | |
87 | exit (1); | |
88 | } | |
89 | return ret; | |
90 | } | |
91 | ||
85f7554c FW |
92 | /* Return true if FD is flagged as deleted in /proc/self/fd, false if |
93 | not. */ | |
94 | static bool | |
e1b02c5e | 95 | is_file_deleted (int fd) |
85f7554c FW |
96 | { |
97 | char *proc_fd_path = xasprintf ("/proc/self/fd/%d", fd); | |
98 | char file_path[4096]; | |
99 | ssize_t file_path_length | |
100 | = readlink (proc_fd_path, file_path, sizeof (file_path)); | |
101 | if (file_path_length < 0) | |
102 | { | |
103 | printf ("error: readlink (\"%s\"): %m", proc_fd_path); | |
104 | free (proc_fd_path); | |
105 | exit (1); | |
106 | } | |
107 | free (proc_fd_path); | |
108 | if (file_path_length == sizeof (file_path)) | |
109 | { | |
110 | printf ("error: path in /proc resolves to overlong file name: %.*s\n", | |
111 | (int) file_path_length, file_path); | |
112 | exit (1); | |
113 | } | |
114 | const char *deleted = " (deleted)"; | |
115 | if (file_path_length < strlen (deleted)) | |
116 | { | |
117 | printf ("error: path in /proc is too short: %.*s\n", | |
118 | (int) file_path_length, file_path); | |
119 | exit (1); | |
120 | } | |
121 | return memcmp (file_path + file_path_length - strlen (deleted), | |
122 | deleted, strlen (deleted)) == 0; | |
123 | } | |
124 | ||
67b73ea4 FW |
125 | /* Obtain a file name which is difficult to guess. */ |
126 | static char * | |
127 | get_random_name (void) | |
128 | { | |
129 | unsigned long long bytes[2]; | |
130 | int random_device = open ("/dev/urandom", O_RDONLY); | |
131 | if (random_device < 0) | |
132 | { | |
133 | printf ("error: open (\"/dev/urandom\"): %m\n"); | |
134 | exit (1); | |
135 | } | |
136 | ssize_t ret = read (random_device, bytes, sizeof (bytes)); | |
137 | if (ret < 0) | |
138 | { | |
139 | printf ("error: read (\"/dev/urandom\"): %m\n"); | |
140 | exit (1); | |
141 | } | |
142 | if (ret != sizeof (bytes)) | |
143 | { | |
144 | printf ("error: short read from /dev/urandom: %zd\n", ret); | |
145 | exit (1); | |
146 | } | |
147 | close (random_device); | |
148 | return xasprintf ("tst-open-tmpfile-%08llx%08llx.tmp", bytes[0], bytes[1]); | |
149 | } | |
150 | ||
85f7554c FW |
151 | /* Check open/openat (as specified by OP and WRAPPER) with a specific |
152 | PATH/FLAGS/MODE combination. */ | |
153 | static void | |
154 | check_wrapper_flags_mode (const char *op, wrapper_func wrapper, | |
155 | const char *path, int flags, mode_t mode) | |
156 | { | |
157 | int fd = wrapper (path, flags | O_TMPFILE, mode); | |
158 | struct stat64 st; | |
159 | if (fstat64 (fd, &st) != 0) | |
160 | { | |
161 | printf ("error: fstat64: %m\n"); | |
162 | exit (1); | |
163 | } | |
164 | ||
165 | /* Verify that the mode was correctly processed. */ | |
166 | int actual_mode = st.st_mode & 0777; | |
167 | if (actual_mode != mode) | |
168 | { | |
169 | printf ("error: unexpected mode; expected 0%03o, actual 0%03o\n", | |
170 | mode, actual_mode); | |
171 | exit (1); | |
172 | } | |
173 | ||
174 | /* Check that the file is marked as deleted in /proc. */ | |
e1b02c5e | 175 | if (!is_file_deleted (fd)) |
85f7554c FW |
176 | { |
177 | printf ("error: path in /proc is not marked as deleted\n"); | |
178 | exit (1); | |
179 | } | |
180 | ||
67b73ea4 FW |
181 | /* Check that the file can be turned into a regular file with |
182 | linkat. Open a file descriptor for the directory at PATH. Use | |
183 | AT_FDCWD if PATH is ".", to exercise that functionality as | |
184 | well. */ | |
185 | int path_fd; | |
186 | if (strcmp (path, ".") == 0) | |
187 | path_fd = AT_FDCWD; | |
188 | else | |
189 | { | |
190 | path_fd = open (path, O_RDONLY | O_DIRECTORY); | |
191 | if (path_fd < 0) | |
192 | { | |
193 | printf ("error: open (\"%s\"): %m\n", path); | |
194 | exit (1); | |
195 | } | |
196 | } | |
197 | ||
198 | /* Use a hard-to-guess name for the new directory entry. */ | |
199 | char *new_name = get_random_name (); | |
200 | ||
201 | /* linkat does not require privileges if the path in /proc/self/fd | |
202 | is used. */ | |
203 | char *proc_fd_path = xasprintf ("/proc/self/fd/%d", fd); | |
204 | if (linkat (AT_FDCWD, proc_fd_path, path_fd, new_name, | |
205 | AT_SYMLINK_FOLLOW) == 0) | |
206 | { | |
207 | if (unlinkat (path_fd, new_name, 0) != 0 && errno != ENOENT) | |
208 | { | |
209 | printf ("error: unlinkat (\"%s/%s\"): %m\n", path, new_name); | |
210 | exit (1); | |
211 | } | |
212 | } | |
213 | else | |
214 | { | |
215 | /* linkat failed. This is expected if O_EXCL was specified. */ | |
216 | if ((flags & O_EXCL) == 0) | |
217 | { | |
218 | printf ("error: linkat failed after %s (\"%s\", 0x%x, 0%03o): %m\n", | |
219 | op, path, flags, mode); | |
220 | exit (1); | |
221 | } | |
222 | } | |
223 | ||
224 | free (proc_fd_path); | |
225 | free (new_name); | |
226 | if (path_fd != AT_FDCWD) | |
227 | close (path_fd); | |
85f7554c FW |
228 | close (fd); |
229 | } | |
230 | ||
231 | /* Check OP/WRAPPER with various flags at a specific PATH and | |
232 | MODE. */ | |
233 | static void | |
234 | check_wrapper_mode (const char *op, wrapper_func wrapper, | |
235 | const char *path, mode_t mode) | |
236 | { | |
237 | check_wrapper_flags_mode (op, wrapper, path, O_WRONLY, mode); | |
238 | check_wrapper_flags_mode (op, wrapper, path, O_WRONLY | O_EXCL, mode); | |
239 | check_wrapper_flags_mode (op, wrapper, path, O_RDWR, mode); | |
240 | check_wrapper_flags_mode (op, wrapper, path, O_RDWR | O_EXCL, mode); | |
241 | } | |
242 | ||
243 | /* Check open/openat with varying permissions. */ | |
244 | static void | |
245 | check_wrapper (const char *op, wrapper_func wrapper, | |
246 | const char *path) | |
247 | { | |
248 | printf ("info: testing %s at: %s\n", op, path); | |
249 | check_wrapper_mode (op, wrapper, path, 0); | |
250 | check_wrapper_mode (op, wrapper, path, 0640); | |
251 | check_wrapper_mode (op, wrapper, path, 0600); | |
252 | check_wrapper_mode (op, wrapper, path, 0755); | |
253 | check_wrapper_mode (op, wrapper, path, 0750); | |
254 | } | |
255 | ||
256 | /* Verify that the directory at PATH supports O_TMPFILE. Exit with | |
257 | status 77 (unsupported) if the kernel does not support O_TMPFILE. | |
258 | Even with kernel support, not all file systems O_TMPFILE, so return | |
259 | true if the directory supports O_TMPFILE, false if not. */ | |
260 | static bool | |
261 | probe_path (const char *path) | |
262 | { | |
263 | int fd = openat (AT_FDCWD, path, O_TMPFILE | O_RDWR, 0); | |
264 | if (fd < 0) | |
265 | { | |
266 | if (errno == EISDIR) | |
267 | /* The system does not support O_TMPFILE. */ | |
268 | { | |
269 | printf ("info: kernel does not support O_TMPFILE\n"); | |
270 | exit (77); | |
271 | } | |
272 | if (errno == EOPNOTSUPP) | |
273 | { | |
274 | printf ("info: path does not support O_TMPFILE: %s\n", path); | |
275 | return false; | |
276 | } | |
277 | printf ("error: openat (\"%s\", O_TMPFILE | O_RDWR): %m\n", path); | |
278 | exit (1); | |
279 | } | |
280 | close (fd); | |
281 | return true; | |
282 | } | |
283 | ||
284 | static int | |
285 | do_test (void) | |
286 | { | |
287 | umask (0); | |
288 | const char *paths[] = { ".", "/dev/shm", "/tmp", | |
289 | getenv ("TEST_TMPFILE_PATH"), | |
290 | NULL }; | |
291 | bool supported = false; | |
292 | for (int i = 0; paths[i] != NULL; ++i) | |
293 | if (probe_path (paths[i])) | |
294 | { | |
295 | supported = true; | |
296 | check_wrapper ("open", wrap_open, paths[i]); | |
297 | check_wrapper ("openat", wrap_openat, paths[i]); | |
67b73ea4 FW |
298 | check_wrapper ("open64", wrap_open64, paths[i]); |
299 | check_wrapper ("openat64", wrap_openat64, paths[i]); | |
85f7554c FW |
300 | } |
301 | ||
302 | if (!supported) | |
303 | return 77; | |
304 | ||
305 | return 0; | |
306 | } | |
307 | ||
308 | #else /* !O_TMPFILE */ | |
309 | ||
310 | static int | |
311 | do_test (void) | |
312 | { | |
313 | return 77; | |
314 | } | |
315 | ||
316 | #endif /* O_TMPFILE */ | |
c23de0aa FW |
317 | |
318 | #include <support/test-driver.c> |