1 /* Test open and openat with O_TMPFILE.
2    Copyright (C) 2016-2022 Free Software Foundation, Inc.
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
17    <https://www.gnu.org/licenses/>.  */
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 
31 #include <support/support.h>
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
wrap_open(const char * path,int flags,mode_t mode)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
wrap_openat(const char * path,int flags,mode_t mode)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 
64 /* Error-checking wrapper for the open64 function, compatible with the
65    wrapper_func type.  */
66 static int
wrap_open64(const char * path,int flags,mode_t mode)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
wrap_openat64(const char * path,int flags,mode_t mode)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 
92 /* Return true if FD is flagged as deleted in /proc/self/fd, false if
93    not.  */
94 static bool
is_file_deteted(int fd)95 is_file_deteted (int fd)
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 
125 /* Obtain a file name which is difficult to guess.  */
126 static char *
get_random_name(void)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 
151 /* Check open/openat (as specified by OP and WRAPPER) with a specific
152    PATH/FLAGS/MODE combination.  */
153 static void
check_wrapper_flags_mode(const char * op,wrapper_func wrapper,const char * path,int flags,mode_t mode)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.  */
175   if (!is_file_deteted (fd))
176     {
177       printf ("error: path in /proc is not marked as deleted\n");
178       exit (1);
179     }
180 
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);
228   close (fd);
229 }
230 
231 /* Check OP/WRAPPER with various flags at a specific PATH and
232    MODE.  */
233 static void
check_wrapper_mode(const char * op,wrapper_func wrapper,const char * path,mode_t mode)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
check_wrapper(const char * op,wrapper_func wrapper,const char * path)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
probe_path(const char * path)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
do_test(void)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]);
298         check_wrapper ("open64", wrap_open64, paths[i]);
299         check_wrapper ("openat64", wrap_openat64, paths[i]);
300       }
301 
302   if (!supported)
303     return 77;
304 
305   return 0;
306 }
307 
308 #else  /* !O_TMPFILE */
309 
310 static int
do_test(void)311 do_test (void)
312 {
313   return 77;
314 }
315 
316 #endif  /* O_TMPFILE */
317 
318 #include <support/test-driver.c>
319