blob: e51a6ef0cf23f83e476702d53f076ae49efd77cf [file] [log] [blame]
// 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 <fidl/fuchsia.io/cpp/wire.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 = 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 cpp20::span kFileContents(file_contents_array);
ASSERT_EQ(write(fd, kFileContents.data(), kFileContents.size()),
static_cast<ssize_t>(kFileContents.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, kFileContents));
// 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, kFileContents));
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, S_IRUSR | S_IWUSR);
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, S_IRUSR | S_IWUSR);
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, S_IRUSR | S_IWUSR);
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, S_IRUSR | S_IWUSR);
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, S_IRUSR | S_IWUSR);
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, S_IRUSR | S_IWUSR);
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 = fidl::WireCall(caller.borrow_as<fio::Directory>())->GetToken();
ASSERT_EQ(token_result.status(), ZX_OK);
ASSERT_EQ(token_result->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 = fidl::WireCall(caller.borrow_as<fio::Directory>())
->Rename(fidl::StringView(src), zx::event(token_result->token.get()),
fidl::StringView(dst));
ASSERT_TRUE(rename_result.ok());
ASSERT_TRUE(rename_result->is_error());
ASSERT_EQ(rename_result->error_value(), 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);
}
TEST_P(RenameTest, RenameDirectoryToChildIsForbidden) {
// This test ensures renaming a directory to a child is forbidden. The basic tests above test a
// variant of this, but this case is to specifically catch the issue described in https://fxbug.dev/42178035.
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);
ASSERT_EQ(mkdir(GetPath("alpha/bravo/charlie/delta").c_str(), 0755), 0);
EXPECT_EQ(rename(GetPath("alpha/bravo").c_str(), GetPath("alpha/bravo/charlie/delta").c_str()),
-1);
EXPECT_EQ(rename(GetPath("alpha/bravo").c_str(), GetPath("alpha/bravo/charlie").c_str()), -1);
EXPECT_EQ(rename(GetPath("alpha/bravo").c_str(), GetPath("alpha/bravo").c_str()), 0);
}
INSTANTIATE_TEST_SUITE_P(/*no prefix*/, RenameTest, testing::ValuesIn(AllTestFilesystems()),
testing::PrintToStringParamName());
} // namespace
} // namespace fs_test