1 /*
2 * Copyright (c) 2025 Huawei Device Co., Ltd.
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_file_watcher.h"
16
17 #include <algorithm>
18 #include <cerrno>
19 #include <cstdint>
20 #include <poll.h>
21 #include <unistd.h>
22 #include <sys/eventfd.h>
23
24 #include "filemgmt_libhilog.h"
25 #include "uv.h"
26
27 namespace OHOS::FileManagement::ModuleFileIO {
28 using namespace std;
29
GetNotifyId()30 int32_t FsFileWatcher::GetNotifyId()
31 {
32 return notifyFd_;
33 }
34
InitNotify()35 bool FsFileWatcher::InitNotify()
36 {
37 notifyFd_ = inotify_init();
38 if (notifyFd_ < 0) {
39 HILOGE("Failed to init notify errCode:%{public}d", errno);
40 return false;
41 }
42 eventFd_ = eventfd(0, EFD_CLOEXEC);
43 if (eventFd_ < 0) {
44 HILOGE("Failed to init eventfd errCode:%{public}d", errno);
45 return false;
46 }
47 return true;
48 }
49
StartNotify(shared_ptr<WatcherInfo> info)50 int32_t FsFileWatcher::StartNotify(shared_ptr<WatcherInfo> info)
51 {
52 if (!info) {
53 HILOGE("Invalid param: info");
54 return EINVAL;
55 }
56
57 if (notifyFd_ < 0) {
58 HILOGE("Failed to start notify notifyFd_:%{public}d", notifyFd_);
59 return EIO;
60 }
61
62 auto [isWatched, wd] = dataCache_.FindWatchedWd(info->fileName, info->events);
63 if (isWatched && wd > 0) {
64 info->wd = wd;
65 return ERRNO_NOERR;
66 }
67
68 uint32_t watchEvents = 0;
69 if (wd != -1) {
70 watchEvents = dataCache_.GetFileEvents(info->fileName) | info->events;
71 } else {
72 watchEvents = info->events;
73 }
74
75 int32_t newWd = inotify_add_watch(notifyFd_, info->fileName.c_str(), watchEvents);
76 if (newWd < 0) {
77 HILOGE("Failed to start notify errCode:%{public}d", errno);
78 return errno;
79 }
80
81 info->wd = newWd;
82 dataCache_.UpdateWatchedEvents(info->fileName, newWd, watchEvents);
83 return ERRNO_NOERR;
84 }
85
NotifyToWatchNewEvents(const string & fileName,int32_t wd,uint32_t watchEvents)86 int32_t FsFileWatcher::NotifyToWatchNewEvents(const string &fileName, int32_t wd, uint32_t watchEvents)
87 {
88 int32_t newWd = inotify_add_watch(notifyFd_, fileName.c_str(), watchEvents);
89 if (newWd < 0) {
90 HILOGE("Failed to start new notify errCode:%{public}d", errno);
91 return errno;
92 }
93
94 if (newWd != wd) {
95 HILOGE("New notify wd is error");
96 return EIO;
97 }
98
99 dataCache_.UpdateWatchedEvents(fileName, wd, watchEvents);
100 return ERRNO_NOERR;
101 }
102
CloseNotifyFd()103 int32_t FsFileWatcher::CloseNotifyFd()
104 {
105 int32_t closeRet = ERRNO_NOERR;
106
107 if (!dataCache_.HasWatcherInfo()) {
108 run_ = false;
109 closeRet = close(notifyFd_);
110 if (closeRet != 0) {
111 HILOGE("Failed to stop notify close fd errCode:%{public}d", errno);
112 }
113 notifyFd_ = -1;
114
115 closeRet = close(eventFd_);
116 if (closeRet != 0) {
117 HILOGE("Failed to close eventfd errCode:%{public}d", errno);
118 }
119 eventFd_ = -1;
120 DestroyTaskThead();
121 }
122
123 closed_ = false;
124 return closeRet;
125 }
126
CloseNotifyFdLocked()127 int32_t FsFileWatcher::CloseNotifyFdLocked()
128 {
129 {
130 lock_guard<mutex> lock(readMutex_);
131 closed_ = true;
132 if (reading_) {
133 HILOGE("close while reading");
134 return ERRNO_NOERR;
135 }
136 }
137 return CloseNotifyFd();
138 }
139
StopNotify(shared_ptr<WatcherInfo> info)140 int32_t FsFileWatcher::StopNotify(shared_ptr<WatcherInfo> info)
141 {
142 if (!info) {
143 HILOGE("Invalid param: info");
144 return EINVAL;
145 }
146
147 if (notifyFd_ < 0) {
148 HILOGE("Failed to stop notify notifyFd_:%{public}d", notifyFd_);
149 return EIO;
150 }
151
152 uint32_t remainingEvents = RemoveWatcherInfo(info);
153 if (remainingEvents > 0) {
154 // There are still events remaining to be listened for.
155 if (access(info->fileName.c_str(), F_OK) == 0) {
156 return NotifyToWatchNewEvents(info->fileName, info->wd, remainingEvents);
157 }
158 HILOGE("The Watched file does not exist, and the remaining monitored events will be invalid.");
159 return ERRNO_NOERR;
160 }
161
162 // No events remain to be listened for, and proceed to the file watch removal process.
163 int32_t oldWd = -1;
164 {
165 lock_guard<mutex> lock(readMutex_);
166 if (!(closed_ && reading_)) {
167 oldWd = inotify_rm_watch(notifyFd_, info->wd);
168 } else {
169 HILOGE("Rm watch fail");
170 }
171 }
172
173 if (oldWd == -1) {
174 int32_t rmErr = errno;
175 if (access(info->fileName.c_str(), F_OK) == 0) {
176 HILOGE("Failed to stop notify errCode:%{public}d", rmErr);
177 dataCache_.RemoveFileWatcher(info->fileName);
178 CloseNotifyFdLocked();
179 return rmErr;
180 }
181 }
182
183 dataCache_.RemoveFileWatcher(info->fileName);
184 return CloseNotifyFdLocked();
185 }
186
ReadNotifyEvent()187 void FsFileWatcher::ReadNotifyEvent()
188 {
189 int32_t len = 0;
190 int32_t index = 0;
191 char buf[BUF_SIZE] = { 0 };
192 struct inotify_event *event = nullptr;
193
194 do {
195 len = read(notifyFd_, &buf, sizeof(buf));
196 if (len < 0 && errno != EINTR) {
197 HILOGE("Read notify event failed! ret: %d", errno);
198 break;
199 }
200 } while (len < 0);
201
202 while (index < len) {
203 event = reinterpret_cast<inotify_event *>(buf + index);
204 NotifyEvent(event);
205 index += sizeof(struct inotify_event) + static_cast<int32_t>(event->len);
206 }
207 }
208
ReadNotifyEventLocked()209 void FsFileWatcher::ReadNotifyEventLocked()
210 {
211 {
212 lock_guard<mutex> lock(readMutex_);
213 if (closed_) {
214 HILOGE("Read after close");
215 return;
216 }
217 reading_ = true;
218 }
219
220 ReadNotifyEvent();
221
222 {
223 lock_guard<mutex> lock(readMutex_);
224 reading_ = false;
225 if (closed_) {
226 HILOGE("Close after read");
227 CloseNotifyFd();
228 return;
229 }
230 }
231 }
232
AsyncGetNotifyEvent()233 void FsFileWatcher::AsyncGetNotifyEvent()
234 {
235 lock_guard<mutex> lock(taskMutex_);
236 if (!taskRunning_) {
237 taskRunning_ = true;
238 taskThead_ = thread(&FsFileWatcher::GetNotifyEvent, this);
239 }
240 }
241
GetNotifyEvent()242 void FsFileWatcher::GetNotifyEvent()
243 {
244 if (run_) {
245 return;
246 }
247
248 run_ = true;
249 nfds_t nfds = 2;
250 struct pollfd fds[2] = { { -1 } };
251 fds[0].fd = eventFd_;
252 fds[0].events = POLLIN;
253 fds[1].fd = notifyFd_;
254 fds[1].events = POLLIN;
255 int32_t ret = 0;
256
257 while (run_) {
258 ret = poll(fds, nfds, pollTimeoutMs);
259 if (ret > 0) {
260 if (static_cast<unsigned short>(fds[0].revents) & POLLNVAL) {
261 run_ = false;
262 break;
263 }
264 if (static_cast<unsigned short>(fds[0].revents) & POLLIN) {
265 run_ = false;
266 break;
267 }
268 if (static_cast<unsigned short>(fds[1].revents) & POLLIN) {
269 ReadNotifyEventLocked();
270 }
271 } else if (ret < 0 && errno != EINTR) {
272 HILOGE("Failed to poll NotifyFd, errno=%{public}d", errno);
273 break;
274 }
275 // Ignore cases where poll returns 0 (timeout) or EINTR (interrupted system call)
276 }
277 }
278
AddWatcherInfo(shared_ptr<WatcherInfo> info)279 bool FsFileWatcher::AddWatcherInfo(shared_ptr<WatcherInfo> info)
280 {
281 if (!info) {
282 HILOGE("Invalid param: info");
283 return false;
284 }
285 return dataCache_.AddWatcherInfo(info);
286 }
287
RemoveWatcherInfo(shared_ptr<WatcherInfo> info)288 uint32_t FsFileWatcher::RemoveWatcherInfo(shared_ptr<WatcherInfo> info)
289 {
290 if (!info) {
291 HILOGE("Invalid param: info");
292 return EINVAL;
293 }
294 return dataCache_.RemoveWatcherInfo(info);
295 }
296
NotifyEvent(const struct inotify_event * event)297 void FsFileWatcher::NotifyEvent(const struct inotify_event *event)
298 {
299 if (!event) {
300 HILOGE("Invalid inotify event");
301 return;
302 }
303
304 auto [matched, fileName, watcherInfos] = dataCache_.FindWatcherInfos(event->wd, event->mask);
305 if (!matched) {
306 HILOGE("Cannot find matched watcherInfos");
307 return;
308 }
309
310 for (const auto &info : watcherInfos) {
311 if (event->len > 0) {
312 fileName += "/" + string(event->name);
313 }
314 info->TriggerCallback(fileName, event->mask & IN_ALL_EVENTS, event->cookie);
315 }
316 }
317
CheckEventValid(uint32_t event)318 bool FsFileWatcher::CheckEventValid(uint32_t event)
319 {
320 if ((event & IN_ALL_EVENTS) == event) {
321 return true;
322 } else {
323 HILOGE("Param event:%{public}x is not valid", event);
324 return false;
325 }
326 }
327
DestroyTaskThead()328 void FsFileWatcher::DestroyTaskThead()
329 {
330 if (taskThead_.joinable()) {
331 if (taskThead_.get_id() != std::this_thread::get_id()) {
332 taskThead_.join();
333 } else {
334 taskThead_.detach();
335 }
336 }
337
338 {
339 lock_guard<mutex> lock(taskMutex_);
340 if (taskRunning_) {
341 taskRunning_ = false;
342 run_ = false;
343 }
344 }
345 dataCache_.ClearCache();
346 }
347 } // namespace OHOS::FileManagement::ModuleFileIO
348