1 // Copyright 2016 The Fuchsia Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include <dirent.h>
6 #include <errno.h>
7 #include <fcntl.h>
8 #include <limits.h>
9 #include <stdio.h>
10 #include <stdlib.h>
11 #include <sys/stat.h>
12 #include <sys/types.h>
13 #include <unistd.h>
14
15 #include <zircon/compiler.h>
16
17 #include "filesystems.h"
18 #include "misc.h"
19
test_rename_basic(void)20 bool test_rename_basic(void) {
21 BEGIN_TEST;
22 // Cannot rename when src does not exist
23 ASSERT_EQ(rename("::alpha", "::bravo"), -1, "");
24
25 // Renaming to self is fine
26 ASSERT_EQ(mkdir("::alpha", 0755), 0, "");
27 ASSERT_EQ(rename("::alpha", "::alpha"), 0, "");
28 ASSERT_EQ(rename("::alpha/.", "::alpha/."), 0, "");
29 ASSERT_EQ(rename("::alpha/", "::alpha"), 0, "");
30 ASSERT_EQ(rename("::alpha", "::alpha/"), 0, "");
31 ASSERT_EQ(rename("::alpha/", "::alpha/"), 0, "");
32 ASSERT_EQ(rename("::alpha/./../alpha", "::alpha/./../alpha"), 0, "");
33
34 // Cannot rename dir to file
35 int fd = open("::bravo", O_RDWR | O_CREAT | O_EXCL, 0644);
36 ASSERT_GT(fd, 0, "");
37 ASSERT_EQ(close(fd), 0, "");
38 ASSERT_EQ(rename("::alpha", "::bravo"), -1, "");
39 ASSERT_EQ(unlink("::bravo"), 0, "");
40
41 // Rename dir (dst does not exist)
42 ASSERT_EQ(rename("::alpha", "::bravo"), 0, "");
43 ASSERT_EQ(mkdir("::alpha", 0755), 0, "");
44 // Rename dir (dst does exist)
45 ASSERT_EQ(rename("::bravo", "::alpha"), 0, "");
46
47 // Rename file (dst does not exist)
48 fd = open("::alpha/charlie", O_RDWR | O_CREAT | O_EXCL, 0644);
49 ASSERT_GT(fd, 0, "");
50 ASSERT_EQ(rename("::alpha/charlie", "::alpha/delta"), 0, "");
51 // File rename to self
52 ASSERT_EQ(rename("::alpha/delta", "::alpha/delta"), 0, "");
53 // Not permitted with trailing '/'
54 ASSERT_EQ(rename("::alpha/delta", "::alpha/delta/"), -1, "");
55 ASSERT_EQ(rename("::alpha/delta/", "::alpha/delta"), -1, "");
56 ASSERT_EQ(rename("::alpha/delta/", "::alpha/delta/"), -1, "");
57 ASSERT_EQ(close(fd), 0, "");
58
59 // Rename file (dst does not exist)
60 fd = open("::alpha/charlie", O_RDWR | O_CREAT | O_EXCL, 0644);
61 ASSERT_GT(fd, 0, "");
62 ASSERT_EQ(rename("::alpha/delta", "::alpha/charlie"), 0, "");
63 ASSERT_EQ(close(fd), 0, "");
64
65 // Rename to different directory
66 ASSERT_EQ(mkdir("::bravo", 0755), 0, "");
67 ASSERT_EQ(rename("::alpha/charlie", "::charlie"), 0, "");
68 ASSERT_EQ(rename("::charlie", "::alpha/charlie"), 0, "");
69 ASSERT_EQ(rename("::bravo", "::alpha/bravo"), 0, "");
70 ASSERT_EQ(rename("::alpha/charlie", "::alpha/bravo/charlie"), 0, "");
71
72 // Cannot rename directory to subdirectory of itself
73 ASSERT_EQ(rename("::alpha", "::alpha/bravo"), -1, "");
74 ASSERT_EQ(rename("::alpha", "::alpha/bravo/charlie"), -1, "");
75 ASSERT_EQ(rename("::alpha", "::alpha/bravo/charlie/delta"), -1, "");
76 ASSERT_EQ(rename("::alpha", "::alpha/delta"), -1, "");
77 ASSERT_EQ(rename("::alpha/bravo", "::alpha/bravo/charlie"), -1, "");
78 ASSERT_EQ(rename("::alpha/bravo", "::alpha/bravo/charlie/delta"), -1, "");
79 // Cannot rename to non-empty directory
80 ASSERT_EQ(rename("::alpha/bravo/charlie", "::alpha/bravo"), -1, "");
81 ASSERT_EQ(rename("::alpha/bravo/charlie", "::alpha"), -1, "");
82 ASSERT_EQ(rename("::alpha/bravo", "::alpha"), -1, "");
83
84 // Clean up
85 ASSERT_EQ(unlink("::alpha/bravo/charlie"), 0, "");
86 ASSERT_EQ(unlink("::alpha/bravo"), 0, "");
87 ASSERT_EQ(unlink("::alpha"), 0, "");
88
89 END_TEST;
90 }
91
test_rename_with_children(void)92 bool test_rename_with_children(void) {
93 BEGIN_TEST;
94
95 ASSERT_EQ(mkdir("::dir_before_move", 0755), 0, "");
96 ASSERT_EQ(mkdir("::dir_before_move/dir1", 0755), 0, "");
97 ASSERT_EQ(mkdir("::dir_before_move/dir2", 0755), 0, "");
98 ASSERT_EQ(mkdir("::dir_before_move/dir2/subdir", 0755), 0, "");
99 int fd = open("::dir_before_move/file", O_RDWR | O_CREAT, 0644);
100 ASSERT_GT(fd, 0, "");
101
102 const char file_contents[] = "This should be in the file";
103 ASSERT_STREAM_ALL(write, fd, (uint8_t*) file_contents, strlen(file_contents));
104
105 ASSERT_EQ(rename("::dir_before_move", "::dir"), 0, "Could not rename");
106
107 // Check that the directory layout has persisted across rename
108 expected_dirent_t dir_contents[] = {
109 {false, ".", DT_DIR},
110 {false, "dir1", DT_DIR},
111 {false, "dir2", DT_DIR},
112 {false, "file", DT_REG},
113 };
114 ASSERT_TRUE(check_dir_contents("::dir", dir_contents, countof(dir_contents)), "");
115 expected_dirent_t dir2_contents[] = {
116 {false, ".", DT_DIR},
117 {false, "subdir", DT_DIR},
118 };
119 ASSERT_TRUE(check_dir_contents("::dir/dir2", dir2_contents, countof(dir2_contents)), "");
120
121 // Check the our file data has lasted (without re-opening)
122 ASSERT_TRUE(check_file_contents(fd, (uint8_t*) file_contents, strlen(file_contents)), "");
123
124 // Check the our file data has lasted (with re-opening)
125 ASSERT_EQ(close(fd), 0, "");
126 fd = open("::dir/file", O_RDONLY, 06444);
127 ASSERT_GT(fd, 0, "");
128 ASSERT_TRUE(check_file_contents(fd, (uint8_t*) file_contents, strlen(file_contents)), "");
129 ASSERT_EQ(close(fd), 0, "");
130
131 // Clean up
132 ASSERT_EQ(unlink("::dir/dir1"), 0, "");
133 ASSERT_EQ(unlink("::dir/dir2/subdir"), 0, "");
134 ASSERT_EQ(unlink("::dir/dir2"), 0, "");
135 ASSERT_EQ(unlink("::dir/file"), 0, "");
136 ASSERT_EQ(unlink("::dir"), 0, "");
137
138 END_TEST;
139 }
140
test_rename_absolute_relative(void)141 bool test_rename_absolute_relative(void) {
142 BEGIN_TEST;
143
144 char cwd[PATH_MAX];
145 ASSERT_NONNULL(getcwd(cwd, sizeof(cwd)), "");
146
147 // Change the cwd to a known directory
148 ASSERT_EQ(mkdir("::working_dir", 0755), 0, "");
149 DIR* dir = opendir("::working_dir");
150 ASSERT_NONNULL(dir, "");
151 ASSERT_EQ(chdir("::working_dir"), 0, "");
152
153 // Make a "foo" directory in the cwd
154 int fd = dirfd(dir);
155 ASSERT_NE(fd, -1, "");
156 ASSERT_EQ(mkdirat(fd, "foo", 0755), 0, "");
157 expected_dirent_t dir_contents_foo[] = {
158 {false, ".", DT_DIR},
159 {false, "foo", DT_DIR},
160 };
161 ASSERT_TRUE(fcheck_dir_contents(dir, dir_contents_foo, countof(dir_contents_foo)), "");
162
163 // Rename "foo" to "bar" using mixed paths
164 ASSERT_EQ(rename("::working_dir/foo", "bar"), 0, "Could not rename foo to bar");
165 expected_dirent_t dir_contents_bar[] = {
166 {false, ".", DT_DIR},
167 {false, "bar", DT_DIR},
168 };
169 ASSERT_TRUE(fcheck_dir_contents(dir, dir_contents_bar, countof(dir_contents_bar)), "");
170
171 // Rename "bar" back to "foo" using mixed paths in the other direction
172 ASSERT_EQ(rename("bar", "::working_dir/foo"), 0, "Could not rename bar to foo");
173 ASSERT_TRUE(fcheck_dir_contents(dir, dir_contents_foo, countof(dir_contents_foo)), "");
174
175 ASSERT_EQ(rmdir("::working_dir/foo"), 0, "");
176
177 // Change the cwd back to the original, whatever it was before
178 // this test started
179 ASSERT_EQ(chdir(cwd), 0, "Could not return to original cwd");
180
181 ASSERT_EQ(rmdir("::working_dir"), 0, "");
182 ASSERT_EQ(closedir(dir), 0, "");
183
184 END_TEST;
185 }
186
test_rename_at(void)187 bool test_rename_at(void) {
188 BEGIN_TEST;
189
190 ASSERT_EQ(mkdir("::foo", 0755), 0, "");
191 ASSERT_EQ(mkdir("::foo/baz", 0755), 0, "");
192 ASSERT_EQ(mkdir("::bar", 0755), 0, "");
193
194 // Normal case of renameat, from one directory to another
195 int foofd = open("::foo", O_RDONLY | O_DIRECTORY, 0644);
196 ASSERT_GT(foofd, 0, "");
197 int barfd = open("::bar", O_RDONLY | O_DIRECTORY, 0644);
198 ASSERT_GT(barfd, 0, "");
199
200 ASSERT_EQ(renameat(foofd, "baz", barfd, "zab"), 0, "");
201
202 expected_dirent_t empty_contents[] = {
203 {false, ".", DT_DIR},
204 };
205 ASSERT_TRUE(check_dir_contents("::foo", empty_contents, countof(empty_contents)), "");
206 expected_dirent_t contains_zab[] = {
207 {false, ".", DT_DIR},
208 {false, "zab", DT_DIR},
209 };
210 ASSERT_TRUE(check_dir_contents("::bar", contains_zab, countof(contains_zab)), "");
211
212 // Alternate case of renameat, where an absolute path ignores
213 // the file descriptor.
214 //
215 // Here, barfd is used (in the first argument) but ignored (in the second argument).
216 ASSERT_EQ(renameat(barfd, "zab", barfd, "::foo/baz"), 0, "");
217 expected_dirent_t contains_baz[] = {
218 {false, ".", DT_DIR},
219 {false, "baz", DT_DIR},
220 };
221 ASSERT_TRUE(check_dir_contents("::foo", contains_baz, countof(contains_baz)), "");
222 ASSERT_TRUE(check_dir_contents("::bar", empty_contents, countof(empty_contents)), "");
223
224 // The 'absolute-path-ignores-fd' case should also work with invalid fds.
225 ASSERT_EQ(renameat(-1, "::foo/baz", -1, "::bar/baz"), 0, "");
226 ASSERT_TRUE(check_dir_contents("::foo", empty_contents, countof(empty_contents)), "");
227 ASSERT_TRUE(check_dir_contents("::bar", contains_baz, countof(contains_baz)), "");
228
229 // However, relative paths should not be allowed with invalid fds.
230 ASSERT_EQ(renameat(-1, "baz", foofd, "baz"), -1, "");
231 ASSERT_EQ(errno, EBADF, "");
232
233 // Additionally, we shouldn't be able to renameat to a file.
234 int fd = openat(barfd, "filename", O_CREAT | O_RDWR | O_EXCL);
235 ASSERT_GT(fd, 0, "");
236 ASSERT_EQ(renameat(foofd, "baz", fd, "baz"), -1, "");
237 // NOTE: not checking for "ENOTDIR", since ENOTSUPPORTED might be returned instead.
238
239 // Clean up
240 ASSERT_EQ(close(fd), 0, "");
241 ASSERT_EQ(unlink("::bar/filename"), 0, "");
242 ASSERT_EQ(rmdir("::bar/baz"), 0, "");
243 ASSERT_EQ(close(foofd), 0, "");
244 ASSERT_EQ(close(barfd), 0, "");
245 ASSERT_EQ(rmdir("::foo"), 0, "");
246 ASSERT_EQ(rmdir("::bar"), 0, "");
247 END_TEST;
248 }
249
250 RUN_FOR_ALL_FILESYSTEMS(rename_tests,
251 RUN_TEST_MEDIUM(test_rename_basic)
252 RUN_TEST_MEDIUM(test_rename_with_children)
253 RUN_TEST_MEDIUM(test_rename_absolute_relative)
254 RUN_TEST_MEDIUM(test_rename_at)
255 )
256