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
20bool 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
92bool 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
141bool 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
187bool 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
250RUN_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