| // Copyright 2021 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 <fcntl.h> |
| #include <fidl/fuchsia.io/cpp/wire.h> |
| #include <lib/async-loop/cpp/loop.h> |
| #include <lib/async-loop/default.h> |
| #include <lib/fdio/directory.h> |
| #include <lib/fdio/namespace.h> |
| #include <lib/file-lock/file-lock.h> |
| #include <lib/fpromise/single_threaded_executor.h> |
| #include <sys/file.h> |
| #include <sys/stat.h> |
| #include <unistd.h> |
| |
| #include <future> |
| |
| #include <fbl/unique_fd.h> |
| #include <zxtest/zxtest.h> |
| |
| #include "src/lib/fxl/strings/substitute.h" |
| #include "src/storage/lib/vfs/cpp/remote_dir.h" |
| #include "src/storage/memfs/mounted_memfs.h" |
| |
| namespace { |
| constexpr char kTmpfsPath[] = "/fshost-flock-tmp"; |
| constexpr char kFlockDir[] = "flock-dir"; |
| constexpr char kTmpfsPathFile[] = "/fshost-flock-tmp/flock_smoke"; |
| const ssize_t FILE_SIZE = 1024; |
| |
| class FlockTest : public zxtest::Test { |
| public: |
| FlockTest() : memfs_loop_(&kAsyncLoopConfigNoAttachToCurrentThread) {} |
| |
| void SetUp() override { |
| ASSERT_OK(memfs_loop_.StartThread()); |
| |
| zx::result memfs = MountedMemfs::Create(memfs_loop_.dispatcher(), kTmpfsPath); |
| ASSERT_OK(memfs); |
| memfs_.emplace(std::move(memfs.value())); |
| } |
| |
| void TearDown() override { |
| if (std::optional memfs = std::exchange(memfs_, std::nullopt); memfs.has_value()) { |
| std::promise<zx_status_t> promise; |
| memfs.value()->Shutdown([&promise](zx_status_t status) { promise.set_value(status); }); |
| ASSERT_OK(promise.get_future().get(), ); |
| } |
| } |
| |
| protected: |
| fbl::RefPtr<fs::RemoteDir> GetRemoteDir() { |
| auto [client, server] = fidl::Endpoints<fuchsia_io::Directory>::Create(); |
| EXPECT_EQ(ZX_OK, fdio_open(kTmpfsPath, |
| static_cast<uint32_t>(fuchsia_io::wire::OpenFlags::kRightReadable | |
| fuchsia_io::wire::OpenFlags::kRightExecutable), |
| server.TakeChannel().release())); |
| return fbl::MakeRefCounted<fs::RemoteDir>(std::move(client)); |
| } |
| |
| void AddFile(size_t content_size) { |
| std::string contents(content_size, 'X'); |
| int fd = open(kTmpfsPathFile, O_CREAT | O_RDWR, S_IRUSR | S_IWUSR); |
| use_first_fd_ = true; // first |GetFd| gets this one |
| EXPECT_LT(-1, fd); |
| fds_.push_back(fd); |
| ASSERT_EQ(write(fd, contents.c_str(), content_size), content_size); |
| } |
| |
| void CloseFile() { |
| std::for_each(fds_.begin(), fds_.end(), &close); |
| unlink(kTmpfsPathFile); |
| } |
| |
| void MakeDir(const std::string& path) { |
| ASSERT_EQ(0, mkdir(fxl::Substitute("$0/$1", kTmpfsPath, path).c_str(), 0666)); |
| } |
| |
| int GetFd() { |
| if (use_first_fd_) { |
| EXPECT_EQ(1, fds_.size()); |
| use_first_fd_ = false; |
| return fds_[0]; |
| } |
| int fd = open(kTmpfsPathFile, O_RDWR); |
| EXPECT_LT(-1, fd); |
| return fd; |
| } |
| |
| private: |
| // File lock related. |
| std::vector<int> fds_; |
| bool use_first_fd_; |
| |
| async::Loop memfs_loop_; |
| std::optional<MountedMemfs> memfs_; |
| }; |
| |
| TEST_F(FlockTest, FlockOnDir) { |
| // Initialize test directory |
| MakeDir(kFlockDir); |
| |
| int fd = open(fxl::Substitute("$0/$1", kTmpfsPath, kFlockDir).c_str(), O_RDONLY | O_DIRECTORY); |
| ASSERT_NE(-1, fd); |
| |
| ASSERT_EQ(0, flock(fd, LOCK_EX)); |
| |
| int fd2 = open(fxl::Substitute("$0/$1", kTmpfsPath, kFlockDir).c_str(), O_RDONLY | O_DIRECTORY); |
| ASSERT_LT(-1, fd2); |
| ASSERT_EQ(-1, flock(fd2, LOCK_EX | LOCK_NB)); |
| |
| ASSERT_EQ(0, flock(fd, LOCK_UN)); |
| close(fd); |
| close(fd2); |
| } |
| |
| TEST_F(FlockTest, FlockExclusiveNoBlock) { |
| AddFile(FILE_SIZE); |
| int fd_a = GetFd(); |
| int fd_b = GetFd(); |
| |
| EXPECT_EQ(0, flock(fd_a, LOCK_EX)); |
| EXPECT_EQ(-1, flock(fd_b, LOCK_EX | LOCK_NB)); |
| EXPECT_EQ(errno, EWOULDBLOCK); |
| EXPECT_EQ(0, flock(fd_a, LOCK_UN)); |
| |
| EXPECT_EQ(0, flock(fd_b, LOCK_EX)); |
| EXPECT_EQ(0, flock(fd_b, LOCK_UN)); |
| CloseFile(); |
| } |
| |
| TEST_F(FlockTest, FlockVsShare) { |
| AddFile(FILE_SIZE); |
| int fd_a = GetFd(); |
| int fd_b = GetFd(); |
| |
| ASSERT_EQ(0, flock(fd_a, LOCK_SH)); |
| ASSERT_EQ(0, flock(fd_b, LOCK_SH)); |
| ASSERT_EQ(0, flock(fd_a, LOCK_UN)); |
| ASSERT_EQ(0, flock(fd_b, LOCK_UN)); |
| CloseFile(); |
| } |
| |
| TEST_F(FlockTest, FlockLockUnlock) { |
| AddFile(FILE_SIZE); |
| int fd_a = GetFd(); |
| ASSERT_EQ(0, flock(fd_a, LOCK_SH)); |
| ASSERT_EQ(0, flock(fd_a, LOCK_UN)); |
| ASSERT_EQ(0, flock(fd_a, LOCK_SH)); |
| ASSERT_EQ(0, flock(fd_a, LOCK_UN)); |
| ASSERT_EQ(0, flock(fd_a, LOCK_EX)); |
| ASSERT_EQ(0, flock(fd_a, LOCK_UN)); |
| ASSERT_EQ(0, flock(fd_a, LOCK_EX)); |
| ASSERT_EQ(0, flock(fd_a, LOCK_UN)); |
| ASSERT_EQ(0, flock(fd_a, LOCK_SH)); |
| ASSERT_EQ(0, flock(fd_a, LOCK_UN)); |
| CloseFile(); |
| } |
| |
| TEST_F(FlockTest, FlockTwoShared) { |
| AddFile(FILE_SIZE); |
| int fd_a = GetFd(); |
| int fd_b = GetFd(); |
| ASSERT_EQ(0, flock(fd_a, LOCK_SH)); |
| ASSERT_EQ(0, flock(fd_b, LOCK_SH)); |
| ASSERT_EQ(0, flock(fd_a, LOCK_UN)); |
| ASSERT_EQ(0, flock(fd_b, LOCK_UN)); |
| CloseFile(); |
| } |
| |
| TEST_F(FlockTest, FlockSharedNoBlockExclusive) { |
| AddFile(FILE_SIZE); |
| int fd_a = GetFd(); |
| int fd_b = GetFd(); |
| |
| ASSERT_EQ(0, flock(fd_a, LOCK_SH)); |
| ASSERT_EQ(-1, flock(fd_b, LOCK_EX | LOCK_NB)); |
| ASSERT_EQ(EWOULDBLOCK, errno); |
| ASSERT_EQ(0, flock(fd_a, LOCK_UN)); |
| CloseFile(); |
| } |
| |
| TEST_F(FlockTest, FlockExclusiveNoBlockShared) { |
| AddFile(FILE_SIZE); |
| int fd_a = GetFd(); |
| int fd_b = GetFd(); |
| |
| ASSERT_EQ(0, flock(fd_a, LOCK_EX)); |
| ASSERT_EQ(-1, flock(fd_b, LOCK_SH | LOCK_NB)); |
| ASSERT_EQ(EWOULDBLOCK, errno); |
| ASSERT_EQ(0, flock(fd_a, LOCK_UN)); |
| CloseFile(); |
| } |
| |
| TEST_F(FlockTest, FlockExclusiveNoBlockExclusive) { |
| AddFile(FILE_SIZE); |
| int fd_a = GetFd(); |
| int fd_b = GetFd(); |
| |
| ASSERT_EQ(0, flock(fd_a, LOCK_EX)); |
| ASSERT_EQ(-1, flock(fd_b, LOCK_EX | LOCK_NB)); |
| ASSERT_EQ(EWOULDBLOCK, errno); |
| ASSERT_EQ(0, flock(fd_a, LOCK_UN)); |
| CloseFile(); |
| } |
| |
| TEST_F(FlockTest, FlockExclusiveToShared) { |
| AddFile(FILE_SIZE); |
| int fd_a = GetFd(); |
| int fd_b = GetFd(); |
| |
| ASSERT_EQ(0, flock(fd_a, LOCK_EX)); |
| ASSERT_EQ(-1, flock(fd_b, LOCK_SH | LOCK_NB)); |
| ASSERT_EQ(EWOULDBLOCK, errno); |
| ASSERT_EQ(0, flock(fd_a, LOCK_SH)); |
| ASSERT_EQ(0, flock(fd_b, LOCK_SH)); |
| ASSERT_EQ(0, flock(fd_a, LOCK_UN)); |
| ASSERT_EQ(0, flock(fd_b, LOCK_UN)); |
| CloseFile(); |
| } |
| |
| TEST_F(FlockTest, FlockSharedToExclusive) { |
| AddFile(FILE_SIZE); |
| int fd_a = GetFd(); |
| int fd_b = GetFd(); |
| |
| ASSERT_EQ(0, flock(fd_a, LOCK_SH)); |
| ASSERT_EQ(0, flock(fd_b, LOCK_SH)); |
| ASSERT_EQ(0, flock(fd_b, LOCK_UN)); |
| ASSERT_EQ(0, flock(fd_a, LOCK_EX)); |
| ASSERT_EQ(-1, flock(fd_b, LOCK_SH | LOCK_NB)); |
| ASSERT_EQ(EWOULDBLOCK, errno); |
| CloseFile(); |
| } |
| } // namespace |