239 lines
7.4 KiB
C++
239 lines
7.4 KiB
C++
// 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 {
|
|
if (relative_timeout_ == std::chrono::milliseconds::max()) {
|
|
return std::chrono::milliseconds::max().count();
|
|
}
|
|
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
|