1 // 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 
35 namespace android {
36 namespace fs_mgr {
37 
38 using namespace std::literals;
39 using android::base::unique_fd;
40 
PollForFile(const std::string & path,const std::chrono::milliseconds relative_timeout)41 bool 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 
PollForFileDeleted(const std::string & path,const std::chrono::milliseconds relative_timeout)55 bool 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__)
70 class 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 
OneShotInotify(const std::string & path,uint32_t mask,const std::chrono::milliseconds relative_timeout)93 OneShotInotify::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 
Wait()127 bool 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 
WaitImpl()144 OneShotInotify::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 
CheckCompleted()180 bool 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 
ConsumeEvents()191 bool 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 
RemainingMs() const208 int64_t OneShotInotify::RemainingMs() const {
209     if (relative_timeout_ == std::chrono::milliseconds::max()) {
210         return std::chrono::milliseconds::max().count();
211     }
212     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 
WaitForFile(const std::string & path,const std::chrono::milliseconds relative_timeout)218 bool 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.
WaitForFileDeleted(const std::string & path,const std::chrono::milliseconds relative_timeout)228 bool 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
239