| // Copyright 2016 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <dirent.h> |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <fuchsia/io/llcpp/fidl.h> |
| #include <lib/fdio/cpp/caller.h> |
| #include <limits.h> |
| #include <stdio.h> |
| #include <sys/stat.h> |
| #include <unistd.h> |
| #include <zircon/errors.h> |
| |
| #include <string_view> |
| |
| #include <fbl/unique_fd.h> |
| |
| #include "src/storage/fs_test/fs_test_fixture.h" |
| #include "src/storage/fs_test/misc.h" |
| |
| namespace fs_test { |
| namespace { |
| |
| using RenameTest = FilesystemTest; |
| |
| namespace fio = ::llcpp::fuchsia::io; |
| |
| TEST_P(RenameTest, Basic) { |
| // Cannot rename when src does not exist |
| ASSERT_EQ(rename(GetPath("alpha").c_str(), GetPath("bravo").c_str()), -1); |
| |
| // Renaming to self is fine |
| ASSERT_EQ(mkdir(GetPath("alpha").c_str(), 0755), 0); |
| ASSERT_EQ(rename(GetPath("alpha").c_str(), GetPath("alpha").c_str()), 0); |
| ASSERT_EQ(rename(GetPath("alpha/.").c_str(), GetPath("alpha/.").c_str()), 0); |
| ASSERT_EQ(rename(GetPath("alpha/").c_str(), GetPath("alpha").c_str()), 0); |
| ASSERT_EQ(rename(GetPath("alpha").c_str(), GetPath("alpha/").c_str()), 0); |
| ASSERT_EQ(rename(GetPath("alpha/").c_str(), GetPath("alpha/").c_str()), 0); |
| ASSERT_EQ(rename(GetPath("alpha/./../alpha").c_str(), GetPath("alpha/./../alpha").c_str()), 0); |
| |
| // Cannot rename dir to file |
| int fd = open(GetPath("bravo").c_str(), O_RDWR | O_CREAT | O_EXCL, 0644); |
| ASSERT_GT(fd, 0); |
| ASSERT_EQ(close(fd), 0); |
| ASSERT_EQ(rename(GetPath("alpha").c_str(), GetPath("bravo").c_str()), -1); |
| ASSERT_EQ(unlink(GetPath("bravo").c_str()), 0); |
| |
| // Rename dir (dst does not exist) |
| ASSERT_EQ(rename(GetPath("alpha").c_str(), GetPath("bravo").c_str()), 0); |
| ASSERT_EQ(mkdir(GetPath("alpha").c_str(), 0755), 0); |
| // Rename dir (dst does exist) |
| ASSERT_EQ(rename(GetPath("bravo").c_str(), GetPath("alpha").c_str()), 0); |
| |
| // Rename file (dst does not exist) |
| fd = open(GetPath("alpha/charlie").c_str(), O_RDWR | O_CREAT | O_EXCL, 0644); |
| ASSERT_GT(fd, 0); |
| ASSERT_EQ(rename(GetPath("alpha/charlie").c_str(), GetPath("alpha/delta").c_str()), 0); |
| // File rename to self |
| ASSERT_EQ(rename(GetPath("alpha/delta").c_str(), GetPath("alpha/delta").c_str()), 0); |
| // Not permitted with trailing '/' |
| ASSERT_EQ(rename(GetPath("alpha/delta").c_str(), GetPath("alpha/delta/").c_str()), -1); |
| ASSERT_EQ(rename(GetPath("alpha/delta/").c_str(), GetPath("alpha/delta").c_str()), -1); |
| ASSERT_EQ(rename(GetPath("alpha/delta/").c_str(), GetPath("alpha/delta/").c_str()), -1); |
| ASSERT_EQ(close(fd), 0); |
| |
| // Rename file (dst does not exist) |
| fd = open(GetPath("alpha/charlie").c_str(), O_RDWR | O_CREAT | O_EXCL, 0644); |
| ASSERT_GT(fd, 0); |
| ASSERT_EQ(rename(GetPath("alpha/delta").c_str(), GetPath("alpha/charlie").c_str()), 0); |
| ASSERT_EQ(close(fd), 0); |
| |
| // Rename to different directory |
| ASSERT_EQ(mkdir(GetPath("bravo").c_str(), 0755), 0); |
| ASSERT_EQ(rename(GetPath("alpha/charlie").c_str(), GetPath("charlie").c_str()), 0); |
| ASSERT_EQ(rename(GetPath("charlie").c_str(), GetPath("alpha/charlie").c_str()), 0); |
| ASSERT_EQ(rename(GetPath("bravo").c_str(), GetPath("alpha/bravo").c_str()), 0); |
| ASSERT_EQ(rename(GetPath("alpha/charlie").c_str(), GetPath("alpha/bravo/charlie").c_str()), 0); |
| |
| // Cannot rename directory to subdirectory of itself |
| ASSERT_EQ(rename(GetPath("alpha").c_str(), GetPath("alpha/bravo").c_str()), -1); |
| ASSERT_EQ(rename(GetPath("alpha").c_str(), GetPath("alpha/bravo/charlie").c_str()), -1); |
| ASSERT_EQ(rename(GetPath("alpha").c_str(), GetPath("alpha/bravo/charlie/delta").c_str()), -1); |
| ASSERT_EQ(rename(GetPath("alpha").c_str(), GetPath("alpha/delta").c_str()), -1); |
| ASSERT_EQ(rename(GetPath("alpha/bravo").c_str(), GetPath("alpha/bravo/charlie").c_str()), -1); |
| ASSERT_EQ(rename(GetPath("alpha/bravo").c_str(), GetPath("alpha/bravo/charlie/delta").c_str()), |
| -1); |
| // Cannot rename to non-empty directory |
| ASSERT_EQ(rename(GetPath("alpha/bravo/charlie").c_str(), GetPath("alpha/bravo").c_str()), -1); |
| ASSERT_EQ(rename(GetPath("alpha/bravo/charlie").c_str(), GetPath("alpha").c_str()), -1); |
| ASSERT_EQ(rename(GetPath("alpha/bravo").c_str(), GetPath("alpha").c_str()), -1); |
| |
| // Clean up |
| ASSERT_EQ(unlink(GetPath("alpha/bravo/charlie").c_str()), 0); |
| ASSERT_EQ(unlink(GetPath("alpha/bravo").c_str()), 0); |
| ASSERT_EQ(unlink(GetPath("alpha").c_str()), 0); |
| } |
| |
| TEST_P(RenameTest, Children) { |
| ASSERT_EQ(mkdir(GetPath("dir_before_move").c_str(), 0755), 0); |
| ASSERT_EQ(mkdir(GetPath("dir_before_move/dir1").c_str(), 0755), 0); |
| ASSERT_EQ(mkdir(GetPath("dir_before_move/dir2").c_str(), 0755), 0); |
| ASSERT_EQ(mkdir(GetPath("dir_before_move/dir2/subdir").c_str(), 0755), 0); |
| int fd = open(GetPath("dir_before_move/file").c_str(), O_RDWR | O_CREAT, 0644); |
| ASSERT_GT(fd, 0); |
| |
| static constexpr uint8_t file_contents_array[] = "This should be in the file"; |
| constexpr std::basic_string_view file_contents = file_contents_array; |
| ASSERT_EQ(write(fd, file_contents.data(), file_contents.size()), |
| static_cast<ssize_t>(file_contents.size())); |
| |
| ASSERT_EQ(rename(GetPath("dir_before_move").c_str(), GetPath("dir").c_str()), 0); |
| |
| // Check that the directory layout has persisted across rename |
| ExpectedDirectoryEntry dir_contents[] = { |
| {".", DT_DIR}, |
| {"dir1", DT_DIR}, |
| {"dir2", DT_DIR}, |
| {"file", DT_REG}, |
| }; |
| ASSERT_NO_FATAL_FAILURE(CheckDirectoryContents(GetPath("dir").c_str(), dir_contents)); |
| ExpectedDirectoryEntry dir2_contents[] = { |
| {".", DT_DIR}, |
| {"subdir", DT_DIR}, |
| }; |
| ASSERT_NO_FATAL_FAILURE(CheckDirectoryContents(GetPath("dir/dir2").c_str(), dir2_contents)); |
| |
| // Check the our file data has lasted (without re-opening) |
| ASSERT_NO_FATAL_FAILURE(CheckFileContents(fd, file_contents)); |
| |
| // Check the our file data has lasted (with re-opening) |
| ASSERT_EQ(close(fd), 0); |
| fd = open(GetPath("dir/file").c_str(), O_RDONLY, 06444); |
| ASSERT_GT(fd, 0); |
| ASSERT_NO_FATAL_FAILURE(CheckFileContents(fd, file_contents)); |
| ASSERT_EQ(close(fd), 0); |
| |
| // Clean up |
| ASSERT_EQ(unlink(GetPath("dir/dir1").c_str()), 0); |
| ASSERT_EQ(unlink(GetPath("dir/dir2/subdir").c_str()), 0); |
| ASSERT_EQ(unlink(GetPath("dir/dir2").c_str()), 0); |
| ASSERT_EQ(unlink(GetPath("dir/file").c_str()), 0); |
| ASSERT_EQ(unlink(GetPath("dir").c_str()), 0); |
| } |
| |
| TEST_P(RenameTest, AbsoluteRelative) { |
| char cwd[PATH_MAX]; |
| ASSERT_NE(getcwd(cwd, sizeof(cwd)), nullptr); |
| |
| // Change the cwd to a known directory |
| ASSERT_EQ(mkdir(GetPath("working_dir").c_str(), 0755), 0); |
| DIR* dir = opendir(GetPath("working_dir").c_str()); |
| ASSERT_NE(dir, nullptr); |
| ASSERT_EQ(chdir(GetPath("working_dir").c_str()), 0); |
| |
| // Make a "foo" directory in the cwd |
| int fd = dirfd(dir); |
| ASSERT_NE(fd, -1); |
| ASSERT_EQ(mkdirat(fd, "foo", 0755), 0); |
| ExpectedDirectoryEntry dir_contents_foo[] = { |
| {".", DT_DIR}, |
| {"foo", DT_DIR}, |
| }; |
| ASSERT_NO_FATAL_FAILURE(CheckDirectoryContents(dir, dir_contents_foo)); |
| |
| // Rename "foo" to "bar" using mixed paths |
| ASSERT_EQ(rename(GetPath("working_dir/foo").c_str(), "bar"), 0); |
| ExpectedDirectoryEntry dir_contents_bar[] = { |
| {".", DT_DIR}, |
| {"bar", DT_DIR}, |
| }; |
| ASSERT_NO_FATAL_FAILURE(CheckDirectoryContents(dir, dir_contents_bar)); |
| |
| // Rename "bar" back to "foo" using mixed paths in the other direction |
| ASSERT_EQ(rename("bar", GetPath("working_dir/foo").c_str()), 0); |
| ASSERT_NO_FATAL_FAILURE(CheckDirectoryContents(dir, dir_contents_foo)); |
| |
| ASSERT_EQ(rmdir(GetPath("working_dir/foo").c_str()), 0); |
| |
| // Change the cwd back to the original, whatever it was before |
| // this test started |
| ASSERT_EQ(chdir(cwd), 0) << "Could not return to original cwd"; |
| |
| ASSERT_EQ(rmdir(GetPath("working_dir").c_str()), 0); |
| ASSERT_EQ(closedir(dir), 0); |
| } |
| |
| TEST_P(RenameTest, At) { |
| ASSERT_EQ(mkdir(GetPath("foo").c_str(), 0755), 0); |
| ASSERT_EQ(mkdir(GetPath("foo/baz").c_str(), 0755), 0); |
| ASSERT_EQ(mkdir(GetPath("bar").c_str(), 0755), 0); |
| |
| // Normal case of renameat, from one directory to another |
| int foofd = open(GetPath("foo").c_str(), O_RDONLY | O_DIRECTORY, 0644); |
| ASSERT_GT(foofd, 0); |
| int barfd = open(GetPath("bar").c_str(), O_RDONLY | O_DIRECTORY, 0644); |
| ASSERT_GT(barfd, 0); |
| |
| ASSERT_EQ(renameat(foofd, "baz", barfd, "zab"), 0); |
| |
| ExpectedDirectoryEntry empty_contents[] = { |
| {".", DT_DIR}, |
| }; |
| ASSERT_NO_FATAL_FAILURE(CheckDirectoryContents(GetPath("foo").c_str(), empty_contents)); |
| ExpectedDirectoryEntry contains_zab[] = { |
| {".", DT_DIR}, |
| {"zab", DT_DIR}, |
| }; |
| ASSERT_NO_FATAL_FAILURE(CheckDirectoryContents(GetPath("bar").c_str(), contains_zab)); |
| |
| // Alternate case of renameat, where an absolute path ignores |
| // the file descriptor. |
| // |
| // Here, barfd is used (in the first argument) but ignored (in the second argument). |
| ASSERT_EQ(renameat(barfd, "zab", barfd, GetPath("foo/baz").c_str()), 0); |
| ExpectedDirectoryEntry contains_baz[] = { |
| {".", DT_DIR}, |
| {"baz", DT_DIR}, |
| }; |
| ASSERT_NO_FATAL_FAILURE(CheckDirectoryContents(GetPath("foo").c_str(), contains_baz)); |
| ASSERT_NO_FATAL_FAILURE(CheckDirectoryContents(GetPath("bar").c_str(), empty_contents)); |
| |
| // The 'absolute-path-ignores-fd' case should also work with invalid fds. |
| ASSERT_EQ(renameat(-1, GetPath("foo/baz").c_str(), -1, GetPath("bar/baz").c_str()), 0); |
| ASSERT_NO_FATAL_FAILURE(CheckDirectoryContents(GetPath("foo").c_str(), empty_contents)); |
| ASSERT_NO_FATAL_FAILURE(CheckDirectoryContents(GetPath("bar").c_str(), contains_baz)); |
| |
| // However, relative paths should not be allowed with invalid fds. |
| ASSERT_EQ(renameat(-1, "baz", foofd, "baz"), -1); |
| ASSERT_EQ(errno, EBADF); |
| |
| // Additionally, we shouldn't be able to renameat to a file. |
| int fd = openat(barfd, "filename", O_CREAT | O_RDWR | O_EXCL); |
| ASSERT_GT(fd, 0); |
| ASSERT_EQ(renameat(foofd, "baz", fd, "baz"), -1); |
| // NOTE: not checking for "ENOTDIR", since ENOTSUPPORTED might be returned instead. |
| |
| // Clean up |
| ASSERT_EQ(close(fd), 0); |
| ASSERT_EQ(unlink(GetPath("bar/filename").c_str()), 0); |
| ASSERT_EQ(rmdir(GetPath("bar/baz").c_str()), 0); |
| ASSERT_EQ(close(foofd), 0); |
| ASSERT_EQ(close(barfd), 0); |
| ASSERT_EQ(rmdir(GetPath("foo").c_str()), 0); |
| ASSERT_EQ(rmdir(GetPath("bar").c_str()), 0); |
| } |
| |
| TEST_P(RenameTest, RenameDirOverFileFails) { |
| std::string src_dir = GetPath("a/b/"); |
| std::string dst = GetPath("a/c"); |
| |
| ASSERT_EQ(mkdir(GetPath("a").c_str(), 0755), 0); |
| ASSERT_EQ(mkdir(src_dir.c_str(), 0755), 0); |
| |
| // Renaming over a file fails. |
| int fd = open(dst.c_str(), O_CREAT | O_RDWR); |
| ASSERT_GT(fd, 0); |
| close(fd); |
| |
| ASSERT_EQ(rename(src_dir.c_str(), dst.c_str()), -1); |
| ASSERT_EQ(errno, ENOTDIR); |
| // ... and check with no trailing slash |
| ASSERT_EQ(rename(GetPath("a/b").c_str(), dst.c_str()), -1); |
| ASSERT_EQ(errno, ENOTDIR); |
| |
| ASSERT_EQ(unlink(dst.c_str()), 0); |
| } |
| |
| TEST_P(RenameTest, RenameDirOverEmptyDirSucceeds) { |
| std::string src_dir = GetPath("a/b/"); |
| std::string dst = GetPath("a/c"); |
| |
| ASSERT_EQ(mkdir(GetPath("a").c_str(), 0755), 0); |
| ASSERT_EQ(mkdir(src_dir.c_str(), 0755), 0); |
| |
| ASSERT_EQ(mkdir(dst.c_str(), 0755), 0); |
| ASSERT_EQ(mkdir(GetPath("a/b/test").c_str(), 0755), 0); |
| |
| ASSERT_EQ(rename(src_dir.c_str(), dst.c_str()), 0); |
| |
| ExpectedDirectoryEntry contents[] = { |
| {".", DT_DIR}, |
| {"test", DT_DIR}, |
| }; |
| ASSERT_NO_FATAL_FAILURE(CheckDirectoryContents(dst.c_str(), contents)); |
| |
| ASSERT_EQ(rmdir(GetPath("a/c/test").c_str()), 0); |
| ASSERT_EQ(rmdir(dst.c_str()), 0); |
| } |
| |
| // If we try and rename a/b/ when b is a file, the rename should fail. |
| TEST_P(RenameTest, RenameFileTrailingSlashFails) { |
| std::string src_dir = GetPath("a/b/"); |
| std::string dst = GetPath("a/c"); |
| ASSERT_EQ(mkdir(GetPath("a").c_str(), 0755), 0); |
| int fd = open(GetPath("a/b").c_str(), O_CREAT | O_RDWR); |
| ASSERT_GT(fd, 0); |
| close(fd); |
| |
| ASSERT_EQ(rename(src_dir.c_str(), dst.c_str()), -1); |
| ASSERT_EQ(errno, ENOTDIR); |
| } |
| |
| TEST_P(RenameTest, RenameDirOverNonEmptyDirFails) { |
| std::string b_dir = GetPath("a/b/"); |
| std::string c_dir = GetPath("a/c/"); |
| ASSERT_EQ(mkdir(GetPath("a").c_str(), 0755), 0); |
| ASSERT_EQ(mkdir(b_dir.c_str(), 0755), 0); |
| ASSERT_EQ(mkdir(c_dir.c_str(), 0755), 0); |
| ASSERT_EQ(mkdir(GetPath("a/b/d").c_str(), 0755), 0); |
| ASSERT_EQ(mkdir(GetPath("a/c/e").c_str(), 0755), 0); |
| |
| ASSERT_EQ(rename(b_dir.c_str(), c_dir.c_str()), -1); |
| ASSERT_EQ(errno, ENOTEMPTY); |
| } |
| |
| TEST_P(RenameTest, RenameFileOverDirFails) { |
| std::string src = GetPath("a/b"); |
| std::string dst = GetPath("a/c/"); |
| ASSERT_EQ(mkdir(GetPath("a").c_str(), 0755), 0); |
| ASSERT_EQ(mkdir(dst.c_str(), 0755), 0); |
| |
| int fd = open(src.c_str(), O_CREAT | O_RDWR); |
| ASSERT_GT(fd, 0); |
| close(fd); |
| |
| ASSERT_EQ(rename(src.c_str(), dst.c_str()), -1); |
| ASSERT_EQ(errno, ENOTDIR); |
| ASSERT_EQ(rename(src.c_str(), GetPath("a/c").c_str()), -1); |
| ASSERT_EQ(errno, EISDIR); |
| } |
| |
| TEST_P(RenameTest, RenameFileOverNonexistantDirPathFails) { |
| std::string src = GetPath("a/b"); |
| std::string dst = GetPath("a/c/"); |
| ASSERT_EQ(mkdir(GetPath("a").c_str(), 0755), 0); |
| int fd = open(src.c_str(), O_CREAT | O_RDWR); |
| ASSERT_GT(fd, 0); |
| close(fd); |
| |
| ASSERT_EQ(rename(src.c_str(), dst.c_str()), -1); |
| ASSERT_EQ(errno, ENOTDIR); |
| } |
| |
| TEST_P(RenameTest, RenameFileOverNonexistantFilePathSucceeds) { |
| std::string src = GetPath("a/b"); |
| std::string dst = GetPath("a/c"); |
| ASSERT_EQ(mkdir(GetPath("a").c_str(), 0755), 0); |
| int fd = open(src.c_str(), O_CREAT | O_RDWR); |
| ASSERT_GT(fd, 0); |
| close(fd); |
| |
| ASSERT_EQ(rename(src.c_str(), dst.c_str()), 0); |
| } |
| |
| // Rename using the raw FIDL interface. |
| TEST_P(RenameTest, Raw) { |
| ASSERT_EQ(mkdir(GetPath("alpha").c_str(), 0755), 0); |
| ASSERT_EQ(mkdir(GetPath("alpha/bravo").c_str(), 0755), 0); |
| ASSERT_EQ(mkdir(GetPath("alpha/bravo/charlie").c_str(), 0755), 0); |
| |
| fbl::unique_fd fd(open(GetPath("alpha").c_str(), O_RDONLY | O_DIRECTORY, 0644)); |
| ASSERT_TRUE(fd); |
| fdio_cpp::FdioCaller caller(std::move(fd)); |
| |
| auto token_result = fio::Directory::Call::GetToken(caller.channel()); |
| ASSERT_EQ(token_result.status(), ZX_OK); |
| ASSERT_EQ(token_result.Unwrap()->s, ZX_OK); |
| |
| // Pass a path, instead of a name, to rename. |
| // Observe that paths are rejected. |
| constexpr char src[] = "bravo/charlie"; |
| constexpr char dst[] = "bravo/delta"; |
| auto rename_result = |
| fio::Directory::Call::Rename(caller.channel(), fidl::StringView(src), |
| std::move(token_result.Unwrap()->token), fidl::StringView(dst)); |
| ASSERT_EQ(rename_result.status(), ZX_OK); |
| ASSERT_EQ(rename_result.Unwrap()->s, ZX_ERR_INVALID_ARGS); |
| |
| // Clean up |
| ASSERT_EQ(unlink(GetPath("alpha/bravo/charlie").c_str()), 0); |
| ASSERT_EQ(unlink(GetPath("alpha/bravo").c_str()), 0); |
| ASSERT_EQ(unlink(GetPath("alpha").c_str()), 0); |
| } |
| |
| TEST_P(RenameTest, RenameDirIntoRootSuceeds) { |
| ASSERT_EQ(mkdir(GetPath("alpha").c_str(), 0755), 0); |
| ASSERT_EQ(mkdir(GetPath("alpha/bravo").c_str(), 0755), 0); |
| EXPECT_EQ(rename(GetPath("alpha/bravo").c_str(), GetPath("bravo").c_str()), 0); |
| } |
| |
| INSTANTIATE_TEST_SUITE_P(/*no prefix*/, RenameTest, testing::ValuesIn(AllTestFilesystems()), |
| testing::PrintToStringParamName()); |
| |
| } // namespace |
| } // namespace fs_test |