1 /* Linux implementation for renameat2 function.
2    Copyright (C) 2018-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 #include <array_length.h>
20 #include <errno.h>
21 #include <fcntl.h>
22 #include <stdbool.h>
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <support/check.h>
26 #include <support/support.h>
27 #include <support/temp_file.h>
28 #include <support/xunistd.h>
29 #include <unistd.h>
30 
31 /* Directory with the temporary files.  */
32 static char *directory;
33 static int directory_fd;
34 
35 /* Paths within that directory.  */
36 static char *old_path;          /* File is called "old".  */
37 static char *new_path;          /* File is called "new".  */
38 
39 /* Subdirectory within the directory above.  */
40 static char *subdirectory;
41 int subdirectory_fd;
42 
43 /* And a pathname in that directory (called "file").  */
44 static char *subdir_path;
45 
46 static void
prepare(int argc,char ** argv)47 prepare (int argc, char **argv)
48 {
49   directory = support_create_temp_directory ("tst-renameat2-");
50   directory_fd = xopen (directory, O_RDONLY | O_DIRECTORY, 0);
51   old_path = xasprintf ("%s/old", directory);
52   add_temp_file (old_path);
53   new_path = xasprintf ("%s/new", directory);
54   add_temp_file (new_path);
55   subdirectory = xasprintf ("%s/subdir", directory);
56   xmkdir (subdirectory, 0777);
57   add_temp_file (subdirectory);
58   subdirectory_fd = xopen (subdirectory, O_RDONLY | O_DIRECTORY, 0);
59   subdir_path = xasprintf ("%s/file", subdirectory);
60   add_temp_file (subdir_path);
61 }
62 
63 /* Delete all files, preparing a clean slate for the next test.  */
64 static void
delete_all_files(void)65 delete_all_files (void)
66 {
67   char *files[] = { old_path, new_path, subdir_path };
68   for (size_t i = 0; i < array_length (files); ++i)
69     if (unlink (files[i]) != 0 && errno != ENOENT)
70       FAIL_EXIT1 ("unlink (\"%s\"): %m", files[i]);
71 }
72 
73 /* Return true if PATH exists in the file system.  */
74 static bool
file_exists(const char * path)75 file_exists (const char *path)
76 {
77   return access (path, F_OK) == 0;
78 }
79 
80 /* Check that PATH exists and has size EXPECTED_SIZE.  */
81 static void
check_size(const char * path,off64_t expected_size)82 check_size (const char *path, off64_t expected_size)
83 {
84   struct stat64 st;
85   xstat (path, &st);
86   if (st.st_size != expected_size)
87     FAIL_EXIT1 ("file \"%s\": expected size %lld, actual size %lld",
88                 path, (unsigned long long int) expected_size,
89                 (unsigned long long int) st.st_size);
90 }
91 
92 /* Rename tests where the target does not exist.  */
93 static void
rename_without_existing_target(unsigned int flags)94 rename_without_existing_target (unsigned int flags)
95 {
96   delete_all_files ();
97   support_write_file_string (old_path, "");
98   TEST_COMPARE (renameat2 (AT_FDCWD, old_path, AT_FDCWD, new_path, flags), 0);
99   TEST_VERIFY (!file_exists (old_path));
100   TEST_VERIFY (file_exists (new_path));
101 
102   delete_all_files ();
103   support_write_file_string (old_path, "");
104   TEST_COMPARE (renameat2 (directory_fd, "old", AT_FDCWD, new_path, flags), 0);
105   TEST_VERIFY (!file_exists (old_path));
106   TEST_VERIFY (file_exists (new_path));
107 
108   delete_all_files ();
109   support_write_file_string (old_path, "");
110   TEST_COMPARE (renameat2 (directory_fd, "old", subdirectory_fd, "file", 0),
111                 0);
112   TEST_VERIFY (!file_exists (old_path));
113   TEST_VERIFY (file_exists (subdir_path));
114 }
115 
116 static int
do_test(void)117 do_test (void)
118 {
119   /* Tests with zero flags argument.  These are expected to succeed
120      because this renameat2 variant can be implemented with
121      renameat.  */
122   rename_without_existing_target (0);
123 
124   /* renameat2 without flags replaces an existing destination.  */
125   delete_all_files ();
126   support_write_file_string (old_path, "123");
127   support_write_file_string (new_path, "1234");
128   TEST_COMPARE (renameat2 (AT_FDCWD, old_path, AT_FDCWD, new_path, 0), 0);
129   TEST_VERIFY (!file_exists (old_path));
130   check_size (new_path, 3);
131 
132   /* Now we need to check for kernel support of renameat2 with
133      flags.  */
134   delete_all_files ();
135   support_write_file_string (old_path, "");
136   if (renameat2 (AT_FDCWD, old_path, AT_FDCWD, new_path, RENAME_NOREPLACE)
137       != 0)
138     {
139       if (errno == EINVAL)
140         puts ("warning: no support for renameat2 with flags");
141       else
142         FAIL_EXIT1 ("renameat2 probe failed: %m");
143     }
144   else
145     {
146       /* We have full renameat2 support.  */
147       rename_without_existing_target (RENAME_NOREPLACE);
148 
149       /* Now test RENAME_NOREPLACE with an existing target.  */
150       delete_all_files ();
151       support_write_file_string (old_path, "123");
152       support_write_file_string (new_path, "1234");
153       TEST_COMPARE (renameat2 (AT_FDCWD, old_path, AT_FDCWD, new_path,
154                                RENAME_NOREPLACE), -1);
155       TEST_COMPARE (errno, EEXIST);
156       check_size (old_path, 3);
157       check_size (new_path, 4);
158 
159       delete_all_files ();
160       support_write_file_string (old_path, "123");
161       support_write_file_string (new_path, "1234");
162       TEST_COMPARE (renameat2 (directory_fd, "old", AT_FDCWD, new_path,
163                                RENAME_NOREPLACE), -1);
164       TEST_COMPARE (errno, EEXIST);
165       check_size (old_path, 3);
166       check_size (new_path, 4);
167 
168       delete_all_files ();
169       support_write_file_string (old_path, "123");
170       support_write_file_string (subdir_path, "1234");
171       TEST_COMPARE (renameat2 (directory_fd, "old", subdirectory_fd, "file",
172                                RENAME_NOREPLACE), -1);
173       TEST_COMPARE (errno, EEXIST);
174       check_size (old_path, 3);
175       check_size (subdir_path, 4);
176 
177       /* The flag combination of RENAME_NOREPLACE and RENAME_EXCHANGE
178          is invalid.  */
179       TEST_COMPARE (renameat2 (directory_fd, "ignored",
180                                subdirectory_fd, "ignored",
181                                RENAME_NOREPLACE | RENAME_EXCHANGE), -1);
182       TEST_COMPARE (errno, EINVAL);
183     }
184 
185   /* Create all the pathnames to avoid warnings from the test
186      harness.  */
187   support_write_file_string (old_path, "");
188   support_write_file_string (new_path, "");
189   support_write_file_string (subdir_path, "");
190 
191   free (directory);
192   free (subdirectory);
193   free (old_path);
194   free (new_path);
195   free (subdir_path);
196 
197   xclose (directory_fd);
198   xclose (subdirectory_fd);
199 
200   return 0;
201 }
202 
203 #define PREPARE prepare
204 #include <support/test-driver.c>
205