|  | // 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 |