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