blob: a18ee17cf643872cc1ac4aa311dfa45a977c26f0 [file] [log] [blame] [edit]
// 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 <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#include <string>
#include <string_view>
#include <fbl/unique_fd.h>
#include "src/storage/fs_test/fs_test_fixture.h"
namespace fs_test {
namespace {
using UnlinkTest = FilesystemTest;
// Make some files, then unlink them.
TEST_P(UnlinkTest, Simple) {
const std::string paths[] = {GetPath("abc"), GetPath("def"), GetPath("ghi"), GetPath("jkl"),
GetPath("mnopqrstuvxyz")};
for (size_t i = 0; i < std::size(paths); i++) {
fbl::unique_fd fd(open(paths[i].c_str(), O_RDWR | O_CREAT | O_EXCL, 0644));
ASSERT_TRUE(fd);
}
for (size_t i = 0; i < std::size(paths); i++) {
ASSERT_EQ(unlink(paths[i].c_str()), 0);
}
}
constexpr std::string_view kStringData[] = {
"Hello, world",
"Foo bar baz blat",
"This is yet another sample string",
};
void SimpleReadTest(int fd, size_t data_index) {
ASSERT_EQ(lseek(fd, 0, SEEK_SET), 0);
char buf[1024];
memset(buf, 0, sizeof(buf));
ssize_t len = kStringData[data_index].size();
ASSERT_EQ(read(fd, buf, len), static_cast<ssize_t>(len));
ASSERT_EQ(memcmp(kStringData[data_index].data(), buf, len), 0);
}
void SimpleWriteTest(int fd, size_t data_index) {
ASSERT_EQ(ftruncate(fd, 0), 0);
ASSERT_EQ(lseek(fd, 0, SEEK_SET), 0);
ssize_t len = kStringData[data_index].size();
ASSERT_EQ(write(fd, kStringData[data_index].data(), len), static_cast<ssize_t>(len));
SimpleReadTest(fd, data_index);
}
TEST_P(UnlinkTest, UseAfterwards) {
const std::string path = GetPath("foobar");
fbl::unique_fd fd(open(path.c_str(), O_RDWR | O_CREAT | O_EXCL, 0644));
ASSERT_TRUE(fd);
ASSERT_NO_FATAL_FAILURE(SimpleWriteTest(fd.get(), 1));
// When we unlink path, fd is still open.
ASSERT_EQ(unlink(path.c_str()), 0);
ASSERT_NO_FATAL_FAILURE(
SimpleReadTest(fd.get(), 1)); // It should contain the same data as before
ASSERT_NO_FATAL_FAILURE(SimpleWriteTest(fd.get(), 2)); // It should still be writable
ASSERT_EQ(close(fd.release()), 0); // This actually releases the file
// Now, opening the file should fail without O_CREAT
ASSERT_EQ(open(path.c_str(), O_RDWR, 0644), -1);
}
TEST_P(UnlinkTest, UseAfterRenameOver) {
const std::string path = GetPath("foobar");
fbl::unique_fd fd(open(path.c_str(), O_RDWR | O_CREAT | O_EXCL, 0644));
ASSERT_TRUE(fd);
ASSERT_NO_FATAL_FAILURE(SimpleWriteTest(fd.get(), 1));
// When we rename over path, fd is still open.
const std::string barfoo = GetPath("barfoo");
fbl::unique_fd fd2(open(barfoo.c_str(), O_RDWR | O_CREAT | O_EXCL, 0644));
ASSERT_EQ(rename(barfoo.c_str(), path.c_str()), 0);
ASSERT_NO_FATAL_FAILURE(
SimpleReadTest(fd.get(), 1)); // It should contain the same data as before
ASSERT_NO_FATAL_FAILURE(SimpleWriteTest(fd.get(), 2)); // It should still be writable
}
TEST_P(UnlinkTest, OpenElsewhere) {
const std::string path = GetPath("foobar");
fbl::unique_fd fd1(open(path.c_str(), O_RDWR | O_CREAT | O_EXCL, 0644));
ASSERT_TRUE(fd1);
fbl::unique_fd fd2(open(path.c_str(), O_RDWR, 0644));
ASSERT_TRUE(fd2);
ASSERT_NO_FATAL_FAILURE(SimpleWriteTest(fd1.get(), 0));
ASSERT_EQ(close(fd1.release()), 0);
// When we unlink path, fd2 is still open.
ASSERT_EQ(unlink(path.c_str()), 0);
ASSERT_NO_FATAL_FAILURE(
SimpleReadTest(fd2.get(), 0)); // It should contain the same data as before
ASSERT_NO_FATAL_FAILURE(SimpleWriteTest(fd2.get(), 1)); // It should still be writable
ASSERT_EQ(close(fd2.release()), 0); // This actually releases the file
// Now, opening the file should fail without O_CREAT
ASSERT_EQ(open(path.c_str(), O_RDWR, 0644), -1);
}
TEST_P(UnlinkTest, OpenElsewhereLongName) {
// Test a filename that's not 8.3
const std::string path = GetPath("really_really_long_file_name");
fbl::unique_fd fd1(open(path.c_str(), O_RDWR | O_CREAT | O_EXCL, 0644));
ASSERT_TRUE(fd1);
fbl::unique_fd fd2(open(path.c_str(), O_RDWR, 0644));
ASSERT_TRUE(fd2);
ASSERT_NO_FATAL_FAILURE(SimpleWriteTest(fd1.get(), 0));
ASSERT_EQ(close(fd1.release()), 0);
// When we unlink path, fd2 is still open.
ASSERT_EQ(unlink(path.c_str()), 0);
ASSERT_NO_FATAL_FAILURE(
SimpleReadTest(fd2.get(), 0)); // It should contain the same data as before
ASSERT_NO_FATAL_FAILURE(SimpleWriteTest(fd2.get(), 1)); // It should still be writable
ASSERT_EQ(close(fd2.release()), 0); // This actually releases the file
// Now, opening the file should fail without O_CREAT
ASSERT_EQ(open(path.c_str(), O_RDWR, 0644), -1);
}
TEST_P(UnlinkTest, Remove) {
// Test the trivial cases of removing files and directories
const std::string filename = GetPath("file");
fbl::unique_fd fd(open(filename.c_str(), O_RDWR | O_CREAT | O_EXCL, 0644));
ASSERT_TRUE(fd);
ASSERT_EQ(remove(filename.c_str()), 0);
ASSERT_EQ(remove(filename.c_str()), -1);
ASSERT_EQ(errno, ENOENT);
ASSERT_EQ(close(fd.release()), 0);
const std::string dirname = GetPath("dir");
ASSERT_EQ(mkdir(dirname.c_str(), 0666), 0);
ASSERT_EQ(remove(dirname.c_str()), 0);
ASSERT_EQ(remove(dirname.c_str()), -1);
ASSERT_EQ(errno, ENOENT);
// Test that we cannot remove non-empty directories, and that
// we see the expected error code too.
ASSERT_EQ(mkdir(dirname.c_str(), 0666), 0);
ASSERT_EQ(mkdir((dirname + "/subdir").c_str(), 0666), 0);
ASSERT_EQ(remove(dirname.c_str()), -1);
ASSERT_EQ(errno, ENOTEMPTY);
ASSERT_EQ(remove((dirname + "/subdir").c_str()), 0);
ASSERT_EQ(remove(dirname.c_str()), 0);
ASSERT_EQ(remove(dirname.c_str()), -1);
ASSERT_EQ(errno, ENOENT);
}
using UnlinkSparseTest = FilesystemTest;
TEST_P(UnlinkSparseTest, UnlinkLargeSparseFileAfterClose) {
const std::string foo = GetPath("foo");
fbl::unique_fd fd(open(foo.c_str(), O_RDWR | O_CREAT | O_EXCL, 0644));
ASSERT_TRUE(fd);
// The offset here is deliberately chosen so that it would involve Minfs's double indirect blocks,
// but also fits within memfs.
ASSERT_EQ(pwrite(fd.get(), "hello", 5, 0x20000000 - 5), 5);
ASSERT_EQ(fsync(fd.get()), 0); // Deliberate sync so that close is likely to unload the vnode.
ASSERT_EQ(close(fd.release()), 0);
ASSERT_EQ(unlink(foo.c_str()), 0);
}
INSTANTIATE_TEST_SUITE_P(/*no prefix*/, UnlinkTest, testing::ValuesIn(AllTestFilesystems()),
testing::PrintToStringParamName());
// These tests will only work on a file system that supports sparse files.
INSTANTIATE_TEST_SUITE_P(
/*no prefix*/, UnlinkSparseTest,
testing::ValuesIn(MapAndFilterAllTestFilesystems(
[](const TestFilesystemOptions& options) -> std::optional<TestFilesystemOptions> {
if (options.filesystem->GetTraits().supports_sparse_files) {
return options;
} else {
return std::nullopt;
}
})),
testing::PrintToStringParamName());
} // namespace
} // namespace fs_test