Merge "Introduce inotify-based replacements for fs_mgr_wait_for_file."

This commit is contained in:
David Anderson 2019-06-19 19:39:34 +00:00 committed by Gerrit Code Review
commit 02427f5f71
5 changed files with 362 additions and 0 deletions

View file

@ -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",

235
fs_mgr/file_wait.cpp Normal file
View file

@ -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 <fs_mgr/file_wait.h>
#include <limits.h>
#if defined(__linux__)
#include <poll.h>
#include <sys/inotify.h>
#endif
#if defined(WIN32)
#include <io.h>
#else
#include <unistd.h>
#endif
#include <functional>
#include <thread>
#include <android-base/file.h>
#include <android-base/logging.h>
#include <android-base/unique_fd.h>
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<std::chrono::milliseconds>(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<std::chrono::milliseconds>(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<std::chrono::steady_clock> 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<int>(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<std::chrono::milliseconds>(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

View file

@ -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 <chrono>
#include <string>
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

View file

@ -25,6 +25,7 @@ cc_test {
"libfstab",
],
srcs: [
"file_wait_test.cpp",
"fs_mgr_test.cpp",
],

View file

@ -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 <chrono>
#include <string>
#include <thread>
#include <android-base/file.h>
#include <android-base/unique_fd.h>
#include <fs_mgr/file_wait.h>
#include <gtest/gtest.h>
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);
}