// 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 { 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(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