From c65121306a4c535456050326df6250d5eb03566b Mon Sep 17 00:00:00 2001 From: David Anderson Date: Mon, 10 Jun 2019 19:03:01 -0700 Subject: [PATCH] Introduce inotify-based replacements for fs_mgr_wait_for_file. Bug: 134966533 Test: fs_mgr_unit_test gtest Change-Id: I36802b87cec59b5277267eb919851ca390fea425 --- fs_mgr/Android.bp | 1 + fs_mgr/file_wait.cpp | 235 ++++++++++++++++++++++++++++++ fs_mgr/include/fs_mgr/file_wait.h | 34 +++++ fs_mgr/tests/Android.bp | 1 + fs_mgr/tests/file_wait_test.cpp | 91 ++++++++++++ 5 files changed, 362 insertions(+) create mode 100644 fs_mgr/file_wait.cpp create mode 100644 fs_mgr/include/fs_mgr/file_wait.h create mode 100644 fs_mgr/tests/file_wait_test.cpp diff --git a/fs_mgr/Android.bp b/fs_mgr/Android.bp index ffde1149f..3d3503cf2 100644 --- a/fs_mgr/Android.bp +++ b/fs_mgr/Android.bp @@ -35,6 +35,7 @@ cc_library { export_include_dirs: ["include"], include_dirs: ["system/vold"], srcs: [ + "file_wait.cpp", "fs_mgr.cpp", "fs_mgr_format.cpp", "fs_mgr_verity.cpp", diff --git a/fs_mgr/file_wait.cpp b/fs_mgr/file_wait.cpp new file mode 100644 index 000000000..cbf68456d --- /dev/null +++ b/fs_mgr/file_wait.cpp @@ -0,0 +1,235 @@ +// Copyright (C) 2019 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include + +#include +#if defined(__linux__) +#include +#include +#endif +#if defined(WIN32) +#include +#else +#include +#endif + +#include +#include + +#include +#include +#include + +namespace android { +namespace fs_mgr { + +using namespace std::literals; +using android::base::unique_fd; + +bool PollForFile(const std::string& path, const std::chrono::milliseconds relative_timeout) { + auto start_time = std::chrono::steady_clock::now(); + + while (true) { + if (!access(path.c_str(), F_OK) || errno != ENOENT) return true; + + std::this_thread::sleep_for(50ms); + + auto now = std::chrono::steady_clock::now(); + auto time_elapsed = std::chrono::duration_cast(now - start_time); + if (time_elapsed > relative_timeout) return false; + } +} + +bool PollForFileDeleted(const std::string& path, const std::chrono::milliseconds relative_timeout) { + auto start_time = std::chrono::steady_clock::now(); + + while (true) { + if (access(path.c_str(), F_OK) && errno == ENOENT) return true; + + std::this_thread::sleep_for(50ms); + + auto now = std::chrono::steady_clock::now(); + auto time_elapsed = std::chrono::duration_cast(now - start_time); + if (time_elapsed > relative_timeout) return false; + } +} + +#if defined(__linux__) +class OneShotInotify { + public: + OneShotInotify(const std::string& path, uint32_t mask, + const std::chrono::milliseconds relative_timeout); + + bool Wait(); + + private: + bool CheckCompleted(); + int64_t RemainingMs() const; + bool ConsumeEvents(); + + enum class Result { Success, Timeout, Error }; + Result WaitImpl(); + + unique_fd inotify_fd_; + std::string path_; + uint32_t mask_; + std::chrono::time_point start_time_; + std::chrono::milliseconds relative_timeout_; + bool finished_; +}; + +OneShotInotify::OneShotInotify(const std::string& path, uint32_t mask, + const std::chrono::milliseconds relative_timeout) + : path_(path), + mask_(mask), + start_time_(std::chrono::steady_clock::now()), + relative_timeout_(relative_timeout), + finished_(false) { + // If the condition is already met, don't bother creating an inotify. + if (CheckCompleted()) return; + + unique_fd inotify_fd(inotify_init1(IN_CLOEXEC | IN_NONBLOCK)); + if (inotify_fd < 0) { + PLOG(ERROR) << "inotify_init1 failed"; + return; + } + + std::string watch_path; + if (mask == IN_CREATE) { + watch_path = android::base::Dirname(path); + } else { + watch_path = path; + } + if (inotify_add_watch(inotify_fd, watch_path.c_str(), mask) < 0) { + PLOG(ERROR) << "inotify_add_watch failed"; + return; + } + + // It's possible the condition was met before the add_watch. Check for + // this and abort early if so. + if (CheckCompleted()) return; + + inotify_fd_ = std::move(inotify_fd); +} + +bool OneShotInotify::Wait() { + Result result = WaitImpl(); + if (result == Result::Success) return true; + if (result == Result::Timeout) return false; + + // Some kind of error with inotify occurred, so fallback to a poll. + std::chrono::milliseconds timeout(RemainingMs()); + if (mask_ == IN_CREATE) { + return PollForFile(path_, timeout); + } else if (mask_ == IN_DELETE_SELF) { + return PollForFileDeleted(path_, timeout); + } else { + LOG(ERROR) << "Unknown inotify mask: " << mask_; + return false; + } +} + +OneShotInotify::Result OneShotInotify::WaitImpl() { + // If the operation completed super early, we'll never have created an + // inotify instance. + if (finished_) return Result::Success; + if (inotify_fd_ < 0) return Result::Error; + + while (true) { + auto remaining_ms = RemainingMs(); + if (remaining_ms <= 0) return Result::Timeout; + + struct pollfd event = { + .fd = inotify_fd_, + .events = POLLIN, + .revents = 0, + }; + int rv = poll(&event, 1, static_cast(remaining_ms)); + if (rv <= 0) { + if (rv == 0 || errno == EINTR) { + continue; + } + PLOG(ERROR) << "poll for inotify failed"; + return Result::Error; + } + if (event.revents & POLLERR) { + LOG(ERROR) << "error reading inotify for " << path_; + return Result::Error; + } + + // Note that we don't bother checking what kind of event it is, since + // it's cheap enough to just see if the initial condition is satisified. + // If it's not, we consume all the events available and continue. + if (CheckCompleted()) return Result::Success; + if (!ConsumeEvents()) return Result::Error; + } +} + +bool OneShotInotify::CheckCompleted() { + if (mask_ == IN_CREATE) { + finished_ = !access(path_.c_str(), F_OK) || errno != ENOENT; + } else if (mask_ == IN_DELETE_SELF) { + finished_ = access(path_.c_str(), F_OK) && errno == ENOENT; + } else { + LOG(ERROR) << "Unexpected mask: " << mask_; + } + return finished_; +} + +bool OneShotInotify::ConsumeEvents() { + // According to the manpage, this is enough to read at least one event. + static constexpr size_t kBufferSize = sizeof(struct inotify_event) + NAME_MAX + 1; + char buffer[kBufferSize]; + + do { + ssize_t rv = TEMP_FAILURE_RETRY(read(inotify_fd_, buffer, sizeof(buffer))); + if (rv <= 0) { + if (rv == 0 || errno == EAGAIN) { + return true; + } + PLOG(ERROR) << "read inotify failed"; + return false; + } + } while (true); +} + +int64_t OneShotInotify::RemainingMs() const { + auto remaining = (std::chrono::steady_clock::now() - start_time_); + auto elapsed = std::chrono::duration_cast(remaining); + return (relative_timeout_ - elapsed).count(); +} +#endif + +bool WaitForFile(const std::string& path, const std::chrono::milliseconds relative_timeout) { +#if defined(__linux__) + OneShotInotify inotify(path, IN_CREATE, relative_timeout); + return inotify.Wait(); +#else + return PollForFile(path, relative_timeout); +#endif +} + +// Wait at most |relative_timeout| milliseconds for |path| to stop existing. +bool WaitForFileDeleted(const std::string& path, const std::chrono::milliseconds relative_timeout) { +#if defined(__linux__) + OneShotInotify inotify(path, IN_DELETE_SELF, relative_timeout); + return inotify.Wait(); +#else + return PollForFileDeleted(path, relative_timeout); +#endif +} + +} // namespace fs_mgr +} // namespace android diff --git a/fs_mgr/include/fs_mgr/file_wait.h b/fs_mgr/include/fs_mgr/file_wait.h new file mode 100644 index 000000000..74d160ed1 --- /dev/null +++ b/fs_mgr/include/fs_mgr/file_wait.h @@ -0,0 +1,34 @@ +// Copyright (C) 2019 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include +#include + +namespace android { +namespace fs_mgr { + +// Wait at most |relative_timeout| milliseconds for |path| to exist. dirname(path) +// must already exist. For example, to wait on /dev/block/dm-6, /dev/block must +// be a valid directory. +bool WaitForFile(const std::string& path, const std::chrono::milliseconds relative_timeout); + +// Wait at most |relative_timeout| milliseconds for |path| to stop existing. +// Note that this only returns true if the inode itself no longer exists, i.e., +// all outstanding file descriptors have been closed. +bool WaitForFileDeleted(const std::string& path, const std::chrono::milliseconds relative_timeout); + +} // namespace fs_mgr +} // namespace android diff --git a/fs_mgr/tests/Android.bp b/fs_mgr/tests/Android.bp index eb9f52597..83668e924 100644 --- a/fs_mgr/tests/Android.bp +++ b/fs_mgr/tests/Android.bp @@ -25,6 +25,7 @@ cc_test { "libfstab", ], srcs: [ + "file_wait_test.cpp", "fs_mgr_test.cpp", ], diff --git a/fs_mgr/tests/file_wait_test.cpp b/fs_mgr/tests/file_wait_test.cpp new file mode 100644 index 000000000..cc8b143b6 --- /dev/null +++ b/fs_mgr/tests/file_wait_test.cpp @@ -0,0 +1,91 @@ +// Copyright (C) 2019 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include + +#include +#include +#include +#include + +using namespace std::literals; +using android::base::unique_fd; +using android::fs_mgr::WaitForFile; +using android::fs_mgr::WaitForFileDeleted; + +class FileWaitTest : public ::testing::Test { + protected: + void SetUp() override { + const ::testing::TestInfo* tinfo = ::testing::UnitTest::GetInstance()->current_test_info(); + test_file_ = temp_dir_.path + "/"s + tinfo->name(); + } + + void TearDown() override { unlink(test_file_.c_str()); } + + TemporaryDir temp_dir_; + std::string test_file_; +}; + +TEST_F(FileWaitTest, FileExists) { + unique_fd fd(open(test_file_.c_str(), O_CREAT | O_TRUNC | O_RDWR, 0700)); + ASSERT_GE(fd, 0); + + ASSERT_TRUE(WaitForFile(test_file_, 500ms)); + ASSERT_FALSE(WaitForFileDeleted(test_file_, 500ms)); +} + +TEST_F(FileWaitTest, FileDoesNotExist) { + ASSERT_FALSE(WaitForFile(test_file_, 500ms)); + ASSERT_TRUE(WaitForFileDeleted(test_file_, 500ms)); +} + +TEST_F(FileWaitTest, CreateAsync) { + std::thread thread([this] { + std::this_thread::sleep_for(std::chrono::seconds(1)); + unique_fd fd(open(test_file_.c_str(), O_CREAT | O_TRUNC | O_RDWR, 0700)); + }); + EXPECT_TRUE(WaitForFile(test_file_, 3s)); + thread.join(); +} + +TEST_F(FileWaitTest, CreateOtherAsync) { + std::thread thread([this] { + std::this_thread::sleep_for(std::chrono::seconds(1)); + unique_fd fd(open(test_file_.c_str(), O_CREAT | O_TRUNC | O_RDWR, 0700)); + }); + EXPECT_FALSE(WaitForFile(test_file_ + ".wontexist", 2s)); + thread.join(); +} + +TEST_F(FileWaitTest, DeleteAsync) { + // Note: need to close the file, otherwise inotify considers it not deleted. + { + unique_fd fd(open(test_file_.c_str(), O_CREAT | O_TRUNC | O_RDWR, 0700)); + ASSERT_GE(fd, 0); + } + + std::thread thread([this] { + std::this_thread::sleep_for(std::chrono::seconds(1)); + unlink(test_file_.c_str()); + }); + EXPECT_TRUE(WaitForFileDeleted(test_file_, 3s)); + thread.join(); +} + +TEST_F(FileWaitTest, BadPath) { + ASSERT_FALSE(WaitForFile("/this/path/does/not/exist", 5ms)); + EXPECT_EQ(errno, ENOENT); +}