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