blob: 8e388aad04458a6e4740d4b1a1a479247bc8a46c [file] [log] [blame]
// Copyright 2017 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 <fcntl.h>
#include <fidl/fuchsia.io/cpp/wire.h>
#include <lib/fdio/cpp/caller.h>
#include <lib/zx/channel.h>
#include <stdio.h>
#include <sys/stat.h>
#include <unistd.h>
#include "src/storage/fs_test/fs_test_fixture.h"
#include "src/storage/fs_test/misc.h"
namespace fs_test {
namespace {
using DotDotTest = FilesystemTest;
namespace fio = fuchsia_io;
// Test cases of '..' where the path can be canonicalized on the client.
TEST_P(DotDotTest, DotDotClient) {
ASSERT_EQ(mkdir(GetPath("foo").c_str(), 0755), 0);
ASSERT_EQ(mkdir(GetPath("foo/bit").c_str(), 0755), 0);
ASSERT_EQ(mkdir(GetPath("foo/bar").c_str(), 0755), 0);
ASSERT_EQ(mkdir(GetPath("foo/bar/baz").c_str(), 0755), 0);
ExpectedDirectoryEntry foo_dir[] = {
{".", DT_DIR},
{"bar", DT_DIR},
{"bit", DT_DIR},
};
ExpectedDirectoryEntry bar_dir[] = {
{".", DT_DIR},
{"baz", DT_DIR},
};
// Test cases of client-side dot-dot when moving between directories.
DIR* dir = opendir(GetPath("foo/bar/..").c_str());
ASSERT_NE(dir, nullptr);
ASSERT_NO_FATAL_FAILURE(CheckDirectoryContents(dir, foo_dir));
ASSERT_EQ(closedir(dir), 0);
dir = opendir(GetPath("foo/bar/../bit/..//././//").c_str());
ASSERT_NE(dir, nullptr);
ASSERT_NO_FATAL_FAILURE(CheckDirectoryContents(dir, foo_dir));
ASSERT_EQ(closedir(dir), 0);
dir = opendir(GetPath("foo/bar/baz/../../../foo/bar/baz/..").c_str());
ASSERT_NE(dir, nullptr);
ASSERT_NO_FATAL_FAILURE(CheckDirectoryContents(dir, bar_dir));
ASSERT_EQ(closedir(dir), 0);
// Clean up
ASSERT_EQ(unlink(GetPath("foo/bar/baz").c_str()), 0);
ASSERT_EQ(unlink(GetPath("foo/bar").c_str()), 0);
ASSERT_EQ(unlink(GetPath("foo/bit").c_str()), 0);
ASSERT_EQ(unlink(GetPath("foo").c_str()), 0);
}
// Test cases of '..' where the path cannot be canonicalized on the client.
TEST_P(DotDotTest, DotDotServer) {
ASSERT_EQ(mkdir(GetPath("foo").c_str(), 0755), 0);
ASSERT_EQ(mkdir(GetPath("foo/bar").c_str(), 0755), 0);
int foo_fd = open(GetPath("foo").c_str(), O_RDONLY | O_DIRECTORY);
ASSERT_GT(foo_fd, 0);
// ".." from foo --> Not Supported
ASSERT_LT(openat(foo_fd, "..", O_RDONLY | O_DIRECTORY), 0);
// "bar/../.." from foo --> Not supported
ASSERT_LT(openat(foo_fd, "bar/../..", O_RDONLY | O_DIRECTORY), 0);
// "../../../../../bar" --> Not supported
ASSERT_LT(openat(foo_fd, "../../../../../bar", O_RDONLY | O_DIRECTORY), 0);
// Try to create a file named '..'
ASSERT_LT(openat(foo_fd, "..", O_RDWR | O_CREAT, S_IRUSR | S_IWUSR), 0);
ASSERT_LT(openat(foo_fd, ".", O_RDWR | O_CREAT, S_IRUSR | S_IWUSR), 0);
// Try to create a directory named '..'
ASSERT_LT(mkdirat(foo_fd, "..", 0666), 0);
ASSERT_LT(mkdirat(foo_fd, ".", 0666), 0);
// Clean up
ASSERT_EQ(close(foo_fd), 0);
ASSERT_EQ(unlink(GetPath("foo/bar").c_str()), 0);
ASSERT_EQ(unlink(GetPath("foo").c_str()), 0);
}
TEST_P(DotDotTest, RawOpenDotDirectoryCreate) {
ASSERT_EQ(mkdir(GetPath("foo").c_str(), 0755), 0);
fbl::unique_fd fd(open(GetPath("foo").c_str(), O_RDONLY | O_DIRECTORY));
ASSERT_TRUE(fd);
fdio_cpp::FdioCaller caller(std::move(fd));
// Opening with kOpenFlagCreate should succeed.
auto endpoints = fidl::Endpoints<fio::Node>::Create();
auto result = fidl::WireCall(caller.borrow_as<fio::Directory>())
->Open(fio::wire::OpenFlags::kRightReadable |
fio::wire::OpenFlags::kRightWritable | fio::wire::OpenFlags::kCreate,
{}, fidl::StringView("."), std::move(endpoints.server));
ASSERT_EQ(result.status(), ZX_OK);
const fidl::WireResult close_result = fidl::WireCall(endpoints.client)->Close();
ASSERT_EQ(close_result.status(), ZX_OK);
const fit::result close_response = close_result.value();
ASSERT_TRUE(close_response.is_ok()) << close_response.error_value();
}
TEST_P(DotDotTest, RawOpenDotDirectoryCreateIfAbsent) {
ASSERT_EQ(mkdir(GetPath("foo").c_str(), 0755), 0);
fbl::unique_fd fd(open(GetPath("foo").c_str(), O_RDONLY | O_DIRECTORY));
ASSERT_TRUE(fd);
fdio_cpp::FdioCaller caller(std::move(fd));
// Opening with kOpenFlagCreateIfAbsent should fail.
auto endpoints = fidl::Endpoints<fio::Node>::Create();
auto result =
fidl::WireCall(caller.borrow_as<fio::Directory>())
->Open(fio::wire::OpenFlags::kRightReadable | fio::wire::OpenFlags::kRightWritable |
fio::wire::OpenFlags::kCreate | fio::wire::OpenFlags::kCreateIfAbsent,
{}, fidl::StringView("."), std::move(endpoints.server));
ASSERT_EQ(result.status(), ZX_OK);
const fidl::WireResult close_result = fidl::WireCall(endpoints.client)->Close();
// Can't get an epitaph with LLCPP bindings, so this will do for now.
ASSERT_EQ(close_result.status(), ZX_ERR_PEER_CLOSED);
}
// Test cases of '..' which operate on multiple paths.
// This is mostly intended to test other pathways for client-side
// cleaning operations.
TEST_P(DotDotTest, DotDotRename) {
ASSERT_EQ(mkdir(GetPath("foo").c_str(), 0755), 0);
ASSERT_EQ(mkdir(GetPath("foo/bit").c_str(), 0755), 0);
ASSERT_EQ(mkdir(GetPath("foo/bar").c_str(), 0755), 0);
ASSERT_EQ(mkdir(GetPath("foo/bar/baz").c_str(), 0755), 0);
ExpectedDirectoryEntry foo_dir_bit[] = {
{".", DT_DIR},
{"bar", DT_DIR},
{"bit", DT_DIR},
};
ExpectedDirectoryEntry foo_dir_bits[] = {
{".", DT_DIR},
{"bar", DT_DIR},
{"bits", DT_DIR},
};
// Check that the source is cleaned
ASSERT_EQ(rename(GetPath("foo/bar/./../bit/./../bit").c_str(), GetPath("foo/bits").c_str()), 0);
ASSERT_NO_FATAL_FAILURE(CheckDirectoryContents(GetPath("foo").c_str(), foo_dir_bits));
// Check that the destination is cleaned
ASSERT_EQ(rename(GetPath("foo/bits").c_str(), GetPath("foo/bar/baz/../../././bit").c_str()), 0);
ASSERT_NO_FATAL_FAILURE(CheckDirectoryContents(GetPath("foo").c_str(), foo_dir_bit));
// Check that both are cleaned
ASSERT_EQ(
rename(GetPath("foo/bar/../bit/.").c_str(), GetPath("foo/bar/baz/../../././bits").c_str()),
0);
ASSERT_NO_FATAL_FAILURE(CheckDirectoryContents(GetPath("foo").c_str(), foo_dir_bits));
// Check that both are cleaned (including trailing '/')
ASSERT_EQ(rename(GetPath("foo/./bar/../bits/").c_str(),
GetPath("foo/bar/baz/../../././bit/.//").c_str()),
0);
ASSERT_NO_FATAL_FAILURE(CheckDirectoryContents(GetPath("foo").c_str(), foo_dir_bit));
// Clean up
ASSERT_EQ(unlink(GetPath("foo/bar/baz").c_str()), 0);
ASSERT_EQ(unlink(GetPath("foo/bar").c_str()), 0);
ASSERT_EQ(unlink(GetPath("foo/bit").c_str()), 0);
ASSERT_EQ(unlink(GetPath("foo").c_str()), 0);
}
INSTANTIATE_TEST_SUITE_P(/*no prefix*/, DotDotTest, testing::ValuesIn(AllTestFilesystems()),
testing::PrintToStringParamName());
} // namespace
} // namespace fs_test