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. */
32static char *directory;
33static int directory_fd;
34
35/* Paths within that directory. */
36static char *old_path; /* File is called "old". */
37static char *new_path; /* File is called "new". */
38
39/* Subdirectory within the directory above. */
40static char *subdirectory;
41int subdirectory_fd;
42
43/* And a pathname in that directory (called "file"). */
44static char *subdir_path;
45
46static void
47prepare (int argc, char **argv)
48{
49 directory = support_create_temp_directory (base: "tst-renameat2-");
50 directory_fd = xopen (path: directory, O_RDONLY | O_DIRECTORY, 0);
51 old_path = xasprintf (format: "%s/old", directory);
52 add_temp_file (name: old_path);
53 new_path = xasprintf (format: "%s/new", directory);
54 add_temp_file (name: new_path);
55 subdirectory = xasprintf (format: "%s/subdir", directory);
56 xmkdir (path: subdirectory, 0777);
57 add_temp_file (name: subdirectory);
58 subdirectory_fd = xopen (path: subdirectory, O_RDONLY | O_DIRECTORY, 0);
59 subdir_path = xasprintf (format: "%s/file", subdirectory);
60 add_temp_file (name: subdir_path);
61}
62
63/* Delete all files, preparing a clean slate for the next test. */
64static void
65delete_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 (name: 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. */
74static bool
75file_exists (const char *path)
76{
77 return access (name: path, F_OK) == 0;
78}
79
80/* Check that PATH exists and has size EXPECTED_SIZE. */
81static void
82check_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. */
93static void
94rename_without_existing_target (unsigned int flags)
95{
96 delete_all_files ();
97 support_write_file_string (path: old_path, contents: "");
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 (path: old_path, contents: "");
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 (path: old_path, contents: "");
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
116static int
117do_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 (flags: 0);
123
124 /* renameat2 without flags replaces an existing destination. */
125 delete_all_files ();
126 support_write_file_string (path: old_path, contents: "123");
127 support_write_file_string (path: new_path, contents: "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 (path: new_path, expected_size: 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 (path: old_path, contents: "");
136 if (renameat2 (AT_FDCWD, old: old_path, AT_FDCWD, new: new_path, RENAME_NOREPLACE)
137 != 0)
138 {
139 if (errno == EINVAL)
140 puts (s: "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 (path: old_path, contents: "123");
152 support_write_file_string (path: new_path, contents: "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 (path: old_path, expected_size: 3);
157 check_size (path: new_path, expected_size: 4);
158
159 delete_all_files ();
160 support_write_file_string (path: old_path, contents: "123");
161 support_write_file_string (path: new_path, contents: "1234");
162 TEST_COMPARE (renameat2 (directory_fd, "old", AT_FDCWD, new_path,
163 RENAME_NOREPLACE), -1);
164 TEST_COMPARE (errno, EEXIST);
165 check_size (path: old_path, expected_size: 3);
166 check_size (path: new_path, expected_size: 4);
167
168 delete_all_files ();
169 support_write_file_string (path: old_path, contents: "123");
170 support_write_file_string (path: subdir_path, contents: "1234");
171 TEST_COMPARE (renameat2 (directory_fd, "old", subdirectory_fd, "file",
172 RENAME_NOREPLACE), -1);
173 TEST_COMPARE (errno, EEXIST);
174 check_size (path: old_path, expected_size: 3);
175 check_size (path: subdir_path, expected_size: 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 (path: old_path, contents: "");
188 support_write_file_string (path: new_path, contents: "");
189 support_write_file_string (path: subdir_path, contents: "");
190
191 free (ptr: directory);
192 free (ptr: subdirectory);
193 free (ptr: old_path);
194 free (ptr: new_path);
195 free (ptr: 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

source code of glibc/stdio-common/tst-renameat2.c