blob: af0699b7b5f8fbfe3b438f138ecdfba3296a16f9 [file] [log] [blame]
David Andersonc6512132019-06-10 19:03:01 -07001// Copyright (C) 2019 The Android Open Source Project
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15#include <fs_mgr/file_wait.h>
16
17#include <limits.h>
18#if defined(__linux__)
19#include <poll.h>
20#include <sys/inotify.h>
21#endif
22#if defined(WIN32)
23#include <io.h>
24#else
25#include <unistd.h>
26#endif
27
28#include <functional>
29#include <thread>
30
31#include <android-base/file.h>
32#include <android-base/logging.h>
33#include <android-base/unique_fd.h>
34
35namespace android {
36namespace fs_mgr {
37
38using namespace std::literals;
39using android::base::unique_fd;
40
41bool PollForFile(const std::string& path, const std::chrono::milliseconds relative_timeout) {
42 auto start_time = std::chrono::steady_clock::now();
43
44 while (true) {
45 if (!access(path.c_str(), F_OK) || errno != ENOENT) return true;
46
47 std::this_thread::sleep_for(50ms);
48
49 auto now = std::chrono::steady_clock::now();
50 auto time_elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(now - start_time);
51 if (time_elapsed > relative_timeout) return false;
52 }
53}
54
55bool PollForFileDeleted(const std::string& path, const std::chrono::milliseconds relative_timeout) {
56 auto start_time = std::chrono::steady_clock::now();
57
58 while (true) {
59 if (access(path.c_str(), F_OK) && errno == ENOENT) return true;
60
61 std::this_thread::sleep_for(50ms);
62
63 auto now = std::chrono::steady_clock::now();
64 auto time_elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(now - start_time);
65 if (time_elapsed > relative_timeout) return false;
66 }
67}
68
69#if defined(__linux__)
70class OneShotInotify {
71 public:
72 OneShotInotify(const std::string& path, uint32_t mask,
73 const std::chrono::milliseconds relative_timeout);
74
75 bool Wait();
76
77 private:
78 bool CheckCompleted();
79 int64_t RemainingMs() const;
80 bool ConsumeEvents();
81
82 enum class Result { Success, Timeout, Error };
83 Result WaitImpl();
84
85 unique_fd inotify_fd_;
86 std::string path_;
87 uint32_t mask_;
88 std::chrono::time_point<std::chrono::steady_clock> start_time_;
89 std::chrono::milliseconds relative_timeout_;
90 bool finished_;
91};
92
93OneShotInotify::OneShotInotify(const std::string& path, uint32_t mask,
94 const std::chrono::milliseconds relative_timeout)
95 : path_(path),
96 mask_(mask),
97 start_time_(std::chrono::steady_clock::now()),
98 relative_timeout_(relative_timeout),
99 finished_(false) {
100 // If the condition is already met, don't bother creating an inotify.
101 if (CheckCompleted()) return;
102
103 unique_fd inotify_fd(inotify_init1(IN_CLOEXEC | IN_NONBLOCK));
104 if (inotify_fd < 0) {
105 PLOG(ERROR) << "inotify_init1 failed";
106 return;
107 }
108
109 std::string watch_path;
110 if (mask == IN_CREATE) {
111 watch_path = android::base::Dirname(path);
112 } else {
113 watch_path = path;
114 }
115 if (inotify_add_watch(inotify_fd, watch_path.c_str(), mask) < 0) {
116 PLOG(ERROR) << "inotify_add_watch failed";
117 return;
118 }
119
120 // It's possible the condition was met before the add_watch. Check for
121 // this and abort early if so.
122 if (CheckCompleted()) return;
123
124 inotify_fd_ = std::move(inotify_fd);
125}
126
127bool OneShotInotify::Wait() {
128 Result result = WaitImpl();
129 if (result == Result::Success) return true;
130 if (result == Result::Timeout) return false;
131
132 // Some kind of error with inotify occurred, so fallback to a poll.
133 std::chrono::milliseconds timeout(RemainingMs());
134 if (mask_ == IN_CREATE) {
135 return PollForFile(path_, timeout);
136 } else if (mask_ == IN_DELETE_SELF) {
137 return PollForFileDeleted(path_, timeout);
138 } else {
139 LOG(ERROR) << "Unknown inotify mask: " << mask_;
140 return false;
141 }
142}
143
144OneShotInotify::Result OneShotInotify::WaitImpl() {
145 // If the operation completed super early, we'll never have created an
146 // inotify instance.
147 if (finished_) return Result::Success;
148 if (inotify_fd_ < 0) return Result::Error;
149
150 while (true) {
151 auto remaining_ms = RemainingMs();
152 if (remaining_ms <= 0) return Result::Timeout;
153
154 struct pollfd event = {
155 .fd = inotify_fd_,
156 .events = POLLIN,
157 .revents = 0,
158 };
159 int rv = poll(&event, 1, static_cast<int>(remaining_ms));
160 if (rv <= 0) {
161 if (rv == 0 || errno == EINTR) {
162 continue;
163 }
164 PLOG(ERROR) << "poll for inotify failed";
165 return Result::Error;
166 }
167 if (event.revents & POLLERR) {
168 LOG(ERROR) << "error reading inotify for " << path_;
169 return Result::Error;
170 }
171
172 // Note that we don't bother checking what kind of event it is, since
173 // it's cheap enough to just see if the initial condition is satisified.
174 // If it's not, we consume all the events available and continue.
175 if (CheckCompleted()) return Result::Success;
176 if (!ConsumeEvents()) return Result::Error;
177 }
178}
179
180bool OneShotInotify::CheckCompleted() {
181 if (mask_ == IN_CREATE) {
182 finished_ = !access(path_.c_str(), F_OK) || errno != ENOENT;
183 } else if (mask_ == IN_DELETE_SELF) {
184 finished_ = access(path_.c_str(), F_OK) && errno == ENOENT;
185 } else {
186 LOG(ERROR) << "Unexpected mask: " << mask_;
187 }
188 return finished_;
189}
190
191bool OneShotInotify::ConsumeEvents() {
192 // According to the manpage, this is enough to read at least one event.
193 static constexpr size_t kBufferSize = sizeof(struct inotify_event) + NAME_MAX + 1;
194 char buffer[kBufferSize];
195
196 do {
197 ssize_t rv = TEMP_FAILURE_RETRY(read(inotify_fd_, buffer, sizeof(buffer)));
198 if (rv <= 0) {
199 if (rv == 0 || errno == EAGAIN) {
200 return true;
201 }
202 PLOG(ERROR) << "read inotify failed";
203 return false;
204 }
205 } while (true);
206}
207
208int64_t OneShotInotify::RemainingMs() const {
David Anderson0e5ad5a2021-07-21 21:53:28 -0700209 if (relative_timeout_ == std::chrono::milliseconds::max()) {
210 return std::chrono::milliseconds::max().count();
211 }
David Andersonc6512132019-06-10 19:03:01 -0700212 auto remaining = (std::chrono::steady_clock::now() - start_time_);
213 auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(remaining);
214 return (relative_timeout_ - elapsed).count();
215}
216#endif
217
218bool WaitForFile(const std::string& path, const std::chrono::milliseconds relative_timeout) {
219#if defined(__linux__)
220 OneShotInotify inotify(path, IN_CREATE, relative_timeout);
221 return inotify.Wait();
222#else
223 return PollForFile(path, relative_timeout);
224#endif
225}
226
227// Wait at most |relative_timeout| milliseconds for |path| to stop existing.
228bool WaitForFileDeleted(const std::string& path, const std::chrono::milliseconds relative_timeout) {
229#if defined(__linux__)
230 OneShotInotify inotify(path, IN_DELETE_SELF, relative_timeout);
231 return inotify.Wait();
232#else
233 return PollForFileDeleted(path, relative_timeout);
234#endif
235}
236
237} // namespace fs_mgr
238} // namespace android