Merge "Introduce inotify-based replacements for fs_mgr_wait_for_file."
This commit is contained in:
commit
02427f5f71
5 changed files with 362 additions and 0 deletions
|
@ -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
235
fs_mgr/file_wait.cpp
Normal 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
|
34
fs_mgr/include/fs_mgr/file_wait.h
Normal file
34
fs_mgr/include/fs_mgr/file_wait.h
Normal 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
|
|
@ -25,6 +25,7 @@ cc_test {
|
|||
"libfstab",
|
||||
],
|
||||
srcs: [
|
||||
"file_wait_test.cpp",
|
||||
"fs_mgr_test.cpp",
|
||||
],
|
||||
|
||||
|
|
91
fs_mgr/tests/file_wait_test.cpp
Normal file
91
fs_mgr/tests/file_wait_test.cpp
Normal 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);
|
||||
}
|
Loading…
Reference in a new issue