blob: 126070601d3c4ec217609379aed71173a263c4bb [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 <fuchsia/io/llcpp/fidl.h>
#include <lib/fzl/fdio.h>
#include <lib/zx/handle.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <zircon/compiler.h>
#include <fbl/algorithm.h>
#include <fbl/unique_fd.h>
#include "filesystems.h"
#include "misc.h"
namespace fio = ::llcpp::fuchsia::io;
bool TestRenameBasic() {
BEGIN_TEST;
// Cannot rename when src does not exist
ASSERT_EQ(rename("::alpha", "::bravo"), -1, "");
// Renaming to self is fine
ASSERT_EQ(mkdir("::alpha", 0755), 0, "");
ASSERT_EQ(rename("::alpha", "::alpha"), 0, "");
ASSERT_EQ(rename("::alpha/.", "::alpha/."), 0, "");
ASSERT_EQ(rename("::alpha/", "::alpha"), 0, "");
ASSERT_EQ(rename("::alpha", "::alpha/"), 0, "");
ASSERT_EQ(rename("::alpha/", "::alpha/"), 0, "");
ASSERT_EQ(rename("::alpha/./../alpha", "::alpha/./../alpha"), 0, "");
// Cannot rename dir to file
int fd = open("::bravo", O_RDWR | O_CREAT | O_EXCL, 0644);
ASSERT_GT(fd, 0, "");
ASSERT_EQ(close(fd), 0, "");
ASSERT_EQ(rename("::alpha", "::bravo"), -1, "");
ASSERT_EQ(unlink("::bravo"), 0, "");
// Rename dir (dst does not exist)
ASSERT_EQ(rename("::alpha", "::bravo"), 0, "");
ASSERT_EQ(mkdir("::alpha", 0755), 0, "");
// Rename dir (dst does exist)
ASSERT_EQ(rename("::bravo", "::alpha"), 0, "");
// Rename file (dst does not exist)
fd = open("::alpha/charlie", O_RDWR | O_CREAT | O_EXCL, 0644);
ASSERT_GT(fd, 0, "");
ASSERT_EQ(rename("::alpha/charlie", "::alpha/delta"), 0, "");
// File rename to self
ASSERT_EQ(rename("::alpha/delta", "::alpha/delta"), 0, "");
// Not permitted with trailing '/'
ASSERT_EQ(rename("::alpha/delta", "::alpha/delta/"), -1, "");
ASSERT_EQ(rename("::alpha/delta/", "::alpha/delta"), -1, "");
ASSERT_EQ(rename("::alpha/delta/", "::alpha/delta/"), -1, "");
ASSERT_EQ(close(fd), 0, "");
// Rename file (dst does not exist)
fd = open("::alpha/charlie", O_RDWR | O_CREAT | O_EXCL, 0644);
ASSERT_GT(fd, 0, "");
ASSERT_EQ(rename("::alpha/delta", "::alpha/charlie"), 0, "");
ASSERT_EQ(close(fd), 0, "");
// Rename to different directory
ASSERT_EQ(mkdir("::bravo", 0755), 0, "");
ASSERT_EQ(rename("::alpha/charlie", "::charlie"), 0, "");
ASSERT_EQ(rename("::charlie", "::alpha/charlie"), 0, "");
ASSERT_EQ(rename("::bravo", "::alpha/bravo"), 0, "");
ASSERT_EQ(rename("::alpha/charlie", "::alpha/bravo/charlie"), 0, "");
// Cannot rename directory to subdirectory of itself
ASSERT_EQ(rename("::alpha", "::alpha/bravo"), -1, "");
ASSERT_EQ(rename("::alpha", "::alpha/bravo/charlie"), -1, "");
ASSERT_EQ(rename("::alpha", "::alpha/bravo/charlie/delta"), -1, "");
ASSERT_EQ(rename("::alpha", "::alpha/delta"), -1, "");
ASSERT_EQ(rename("::alpha/bravo", "::alpha/bravo/charlie"), -1, "");
ASSERT_EQ(rename("::alpha/bravo", "::alpha/bravo/charlie/delta"), -1, "");
// Cannot rename to non-empty directory
ASSERT_EQ(rename("::alpha/bravo/charlie", "::alpha/bravo"), -1, "");
ASSERT_EQ(rename("::alpha/bravo/charlie", "::alpha"), -1, "");
ASSERT_EQ(rename("::alpha/bravo", "::alpha"), -1, "");
// Clean up
ASSERT_EQ(unlink("::alpha/bravo/charlie"), 0, "");
ASSERT_EQ(unlink("::alpha/bravo"), 0, "");
ASSERT_EQ(unlink("::alpha"), 0, "");
END_TEST;
}
bool TestRenameWithChildren() {
BEGIN_TEST;
ASSERT_EQ(mkdir("::dir_before_move", 0755), 0, "");
ASSERT_EQ(mkdir("::dir_before_move/dir1", 0755), 0, "");
ASSERT_EQ(mkdir("::dir_before_move/dir2", 0755), 0, "");
ASSERT_EQ(mkdir("::dir_before_move/dir2/subdir", 0755), 0, "");
int fd = open("::dir_before_move/file", O_RDWR | O_CREAT, 0644);
ASSERT_GT(fd, 0, "");
const char file_contents[] = "This should be in the file";
ASSERT_STREAM_ALL(write, fd, (uint8_t*)file_contents, strlen(file_contents));
ASSERT_EQ(rename("::dir_before_move", "::dir"), 0, "Could not rename");
// Check that the directory layout has persisted across rename
expected_dirent_t dir_contents[] = {
{false, ".", DT_DIR},
{false, "dir1", DT_DIR},
{false, "dir2", DT_DIR},
{false, "file", DT_REG},
};
ASSERT_TRUE(check_dir_contents("::dir", dir_contents, fbl::count_of(dir_contents)), "");
expected_dirent_t dir2_contents[] = {
{false, ".", DT_DIR},
{false, "subdir", DT_DIR},
};
ASSERT_TRUE(check_dir_contents("::dir/dir2", dir2_contents, fbl::count_of(dir2_contents)), "");
// Check the our file data has lasted (without re-opening)
ASSERT_TRUE(check_file_contents(fd, (uint8_t*)file_contents, strlen(file_contents)), "");
// Check the our file data has lasted (with re-opening)
ASSERT_EQ(close(fd), 0, "");
fd = open("::dir/file", O_RDONLY, 06444);
ASSERT_GT(fd, 0, "");
ASSERT_TRUE(check_file_contents(fd, (uint8_t*)file_contents, strlen(file_contents)), "");
ASSERT_EQ(close(fd), 0, "");
// Clean up
ASSERT_EQ(unlink("::dir/dir1"), 0, "");
ASSERT_EQ(unlink("::dir/dir2/subdir"), 0, "");
ASSERT_EQ(unlink("::dir/dir2"), 0, "");
ASSERT_EQ(unlink("::dir/file"), 0, "");
ASSERT_EQ(unlink("::dir"), 0, "");
END_TEST;
}
bool TestRenameAbsoluteRelative() {
BEGIN_TEST;
char cwd[PATH_MAX];
ASSERT_NONNULL(getcwd(cwd, sizeof(cwd)), "");
// Change the cwd to a known directory
ASSERT_EQ(mkdir("::working_dir", 0755), 0, "");
DIR* dir = opendir("::working_dir");
ASSERT_NONNULL(dir, "");
ASSERT_EQ(chdir("::working_dir"), 0, "");
// Make a "foo" directory in the cwd
int fd = dirfd(dir);
ASSERT_NE(fd, -1, "");
ASSERT_EQ(mkdirat(fd, "foo", 0755), 0, "");
expected_dirent_t dir_contents_foo[] = {
{false, ".", DT_DIR},
{false, "foo", DT_DIR},
};
ASSERT_TRUE(fcheck_dir_contents(dir, dir_contents_foo, fbl::count_of(dir_contents_foo)), "");
// Rename "foo" to "bar" using mixed paths
ASSERT_EQ(rename("::working_dir/foo", "bar"), 0, "Could not rename foo to bar");
expected_dirent_t dir_contents_bar[] = {
{false, ".", DT_DIR},
{false, "bar", DT_DIR},
};
ASSERT_TRUE(fcheck_dir_contents(dir, dir_contents_bar, fbl::count_of(dir_contents_bar)), "");
// Rename "bar" back to "foo" using mixed paths in the other direction
ASSERT_EQ(rename("bar", "::working_dir/foo"), 0, "Could not rename bar to foo");
ASSERT_TRUE(fcheck_dir_contents(dir, dir_contents_foo, fbl::count_of(dir_contents_foo)), "");
ASSERT_EQ(rmdir("::working_dir/foo"), 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("::working_dir"), 0, "");
ASSERT_EQ(closedir(dir), 0, "");
END_TEST;
}
bool TestRenameAt() {
BEGIN_TEST;
ASSERT_EQ(mkdir("::foo", 0755), 0, "");
ASSERT_EQ(mkdir("::foo/baz", 0755), 0, "");
ASSERT_EQ(mkdir("::bar", 0755), 0, "");
// Normal case of renameat, from one directory to another
int foofd = open("::foo", O_RDONLY | O_DIRECTORY, 0644);
ASSERT_GT(foofd, 0, "");
int barfd = open("::bar", O_RDONLY | O_DIRECTORY, 0644);
ASSERT_GT(barfd, 0, "");
ASSERT_EQ(renameat(foofd, "baz", barfd, "zab"), 0, "");
expected_dirent_t empty_contents[] = {
{false, ".", DT_DIR},
};
ASSERT_TRUE(check_dir_contents("::foo", empty_contents, fbl::count_of(empty_contents)), "");
expected_dirent_t contains_zab[] = {
{false, ".", DT_DIR},
{false, "zab", DT_DIR},
};
ASSERT_TRUE(check_dir_contents("::bar", contains_zab, fbl::count_of(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, "::foo/baz"), 0, "");
expected_dirent_t contains_baz[] = {
{false, ".", DT_DIR},
{false, "baz", DT_DIR},
};
ASSERT_TRUE(check_dir_contents("::foo", contains_baz, fbl::count_of(contains_baz)), "");
ASSERT_TRUE(check_dir_contents("::bar", empty_contents, fbl::count_of(empty_contents)), "");
// The 'absolute-path-ignores-fd' case should also work with invalid fds.
ASSERT_EQ(renameat(-1, "::foo/baz", -1, "::bar/baz"), 0, "");
ASSERT_TRUE(check_dir_contents("::foo", empty_contents, fbl::count_of(empty_contents)), "");
ASSERT_TRUE(check_dir_contents("::bar", contains_baz, fbl::count_of(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("::bar/filename"), 0, "");
ASSERT_EQ(rmdir("::bar/baz"), 0, "");
ASSERT_EQ(close(foofd), 0, "");
ASSERT_EQ(close(barfd), 0, "");
ASSERT_EQ(rmdir("::foo"), 0, "");
ASSERT_EQ(rmdir("::bar"), 0, "");
END_TEST;
}
// Rename using the raw FIDL interface.
bool TestRenameRaw() {
BEGIN_TEST;
ASSERT_EQ(mkdir("::alpha", 0755), 0, "");
ASSERT_EQ(mkdir("::alpha/bravo", 0755), 0, "");
ASSERT_EQ(mkdir("::alpha/bravo/charlie", 0755), 0, "");
fbl::unique_fd fd(open("::alpha", O_RDONLY | O_DIRECTORY, 0644));
ASSERT_TRUE(fd);
fzl::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("::alpha/bravo/charlie"), 0, "");
ASSERT_EQ(unlink("::alpha/bravo"), 0, "");
ASSERT_EQ(unlink("::alpha"), 0, "");
END_TEST;
}
RUN_FOR_ALL_FILESYSTEMS(rename_tests,
RUN_TEST_MEDIUM(TestRenameBasic) RUN_TEST_MEDIUM(TestRenameWithChildren)
RUN_TEST_MEDIUM(TestRenameAbsoluteRelative)
RUN_TEST_MEDIUM(TestRenameAt) RUN_TEST_MEDIUM(TestRenameRaw))