/src/libwebsockets/lib/misc/dir-notify/dir-notify.c
Line | Count | Source |
1 | | /* |
2 | | * libwebsockets - small server side websockets and web server implementation |
3 | | * |
4 | | * Copyright (C) 2010 - 2026 Andy Green <andy@warmcat.com> |
5 | | * |
6 | | * Permission is hereby granted, free of charge, to any person obtaining a copy |
7 | | * of this software and associated documentation files (the "Software"), to |
8 | | * deal in the Software without restriction, including without limitation the |
9 | | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or |
10 | | * sell copies of the Software, and to permit persons to whom the Software is |
11 | | * furnished to do so, subject to the following conditions: |
12 | | * |
13 | | * The above copyright notice and this permission notice shall be included in |
14 | | * all copies or substantial portions of the Software. |
15 | | * |
16 | | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
17 | | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
18 | | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
19 | | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
20 | | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING |
21 | | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS |
22 | | * IN THE SOFTWARE. |
23 | | */ |
24 | | |
25 | | #include "private-lib-core.h" |
26 | | |
27 | | #if defined(LWS_WITH_DIR) |
28 | | |
29 | | struct lws_dir_notify { |
30 | | struct lws_context *ctx; |
31 | | lws_dir_notify_cb_t cb; |
32 | | void *user; |
33 | | int fd; |
34 | | int dir_fd; |
35 | | struct lws *wsi; |
36 | | }; |
37 | | |
38 | | #if defined(__linux__) || defined(__linux) |
39 | | #include <sys/inotify.h> |
40 | | #include <limits.h> |
41 | | |
42 | | static int |
43 | | lws_dir_notify_rx(struct lws *wsi, enum lws_callback_reasons reason, |
44 | | void *user, void *in, size_t len) |
45 | 0 | { |
46 | 0 | struct lws_dir_notify *dn = (struct lws_dir_notify *)lws_get_opaque_user_data(wsi); |
47 | 0 | if (reason == LWS_CALLBACK_RAW_RX_FILE) { |
48 | 0 | char buf[4096] __attribute__ ((aligned(__alignof__(struct inotify_event)))); |
49 | 0 | const struct inotify_event *event; |
50 | 0 | ssize_t n; |
51 | |
|
52 | 0 | n = read(dn->fd, buf, sizeof(buf)); |
53 | 0 | if (n <= 0) |
54 | 0 | return 0; |
55 | | |
56 | 0 | for (char *ptr = buf; ptr < buf + n; ptr += sizeof(struct inotify_event) + event->len) { |
57 | 0 | event = (const struct inotify_event *)ptr; |
58 | | /* We only notify if there's actually a filename provided. */ |
59 | 0 | if (event->len) { |
60 | 0 | int is_file = !(event->mask & IN_ISDIR); |
61 | 0 | dn->cb(event->name, is_file, dn->user); |
62 | 0 | } |
63 | 0 | } |
64 | 0 | } else if (reason == LWS_CALLBACK_RAW_CLOSE_FILE) { |
65 | | /* Clean up if the raw file wsi closes unexpectedly */ |
66 | 0 | if (dn) { |
67 | 0 | if (dn->fd >= 0) |
68 | 0 | close(dn->fd); |
69 | 0 | dn->fd = -1; |
70 | 0 | dn->wsi = NULL; |
71 | | /* we can't safely free(dn) if lws_dir_notify_destroy |
72 | | * hasn't been called, since user code holds a ptr. */ |
73 | 0 | } |
74 | 0 | } |
75 | 0 | return 0; |
76 | 0 | } |
77 | | |
78 | | const struct lws_protocols protocol_lws_dir_notify = { |
79 | | "lws-dir-notify", |
80 | | lws_dir_notify_rx, |
81 | | 0, 0, 0, NULL, 0 |
82 | | }; |
83 | | |
84 | | struct lws_dir_notify * |
85 | | lws_dir_notify_create(struct lws_context *ctx, const char *path, |
86 | | lws_dir_notify_cb_t cb, void *user) |
87 | 0 | { |
88 | 0 | struct lws_dir_notify *dn; |
89 | 0 | struct lws_vhost *vh = lws_get_vhost_by_name(ctx, "system"); |
90 | 0 | lws_sock_file_fd_type sock; |
91 | 0 | int wd; |
92 | |
|
93 | 0 | if (!vh) { |
94 | 0 | vh = ctx->vhost_list; |
95 | 0 | if (!vh) |
96 | 0 | return NULL; |
97 | 0 | } |
98 | | |
99 | 0 | dn = lws_malloc(sizeof(*dn), __func__); |
100 | 0 | if (!dn) |
101 | 0 | return NULL; |
102 | | |
103 | 0 | dn->ctx = ctx; |
104 | 0 | dn->cb = cb; |
105 | 0 | dn->user = user; |
106 | |
|
107 | 0 | dn->fd = inotify_init1(IN_NONBLOCK | IN_CLOEXEC); |
108 | 0 | if (dn->fd < 0) |
109 | 0 | goto bail; |
110 | | |
111 | 0 | wd = inotify_add_watch(dn->fd, path, IN_MODIFY | IN_CREATE | IN_DELETE | IN_MOVED_TO | IN_MOVED_FROM); |
112 | 0 | if (wd < 0) { |
113 | 0 | close(dn->fd); |
114 | 0 | goto bail; |
115 | 0 | } |
116 | | |
117 | 0 | sock.filefd = dn->fd; |
118 | 0 | dn->wsi = lws_adopt_descriptor_vhost(vh, LWS_ADOPT_RAW_FILE_DESC, sock, |
119 | 0 | protocol_lws_dir_notify.name, NULL); |
120 | 0 | if (!dn->wsi) { |
121 | 0 | close(dn->fd); |
122 | 0 | goto bail; |
123 | 0 | } |
124 | | |
125 | 0 | lws_set_opaque_user_data(dn->wsi, dn); |
126 | |
|
127 | 0 | return dn; |
128 | | |
129 | 0 | bail: |
130 | 0 | lws_free(dn); |
131 | 0 | return NULL; |
132 | 0 | } |
133 | | |
134 | | void |
135 | | lws_dir_notify_destroy(struct lws_dir_notify **pdn) |
136 | 0 | { |
137 | 0 | struct lws_dir_notify *dn = *pdn; |
138 | 0 | if (!dn) |
139 | 0 | return; |
140 | | |
141 | 0 | if (dn->wsi) |
142 | 0 | lws_set_timeout(dn->wsi, 1, LWS_TO_KILL_ASYNC); |
143 | 0 | else if (dn->fd >= 0) |
144 | 0 | close(dn->fd); |
145 | |
|
146 | 0 | lws_free(dn); |
147 | | *pdn = NULL; |
148 | 0 | } |
149 | | |
150 | | #elif defined(__APPLE__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) |
151 | | |
152 | | #include <sys/types.h> |
153 | | #include <sys/event.h> |
154 | | #include <sys/time.h> |
155 | | #include <fcntl.h> |
156 | | |
157 | | static int |
158 | | lws_dir_notify_rx(struct lws *wsi, enum lws_callback_reasons reason, |
159 | | void *user, void *in, size_t len) |
160 | | { |
161 | | struct lws_dir_notify *dn = (struct lws_dir_notify *)lws_get_opaque_user_data(wsi); |
162 | | if (reason == LWS_CALLBACK_RAW_RX_FILE) { |
163 | | struct kevent kev; |
164 | | struct timespec ts = {0, 0}; |
165 | | int n; |
166 | | |
167 | | n = kevent(dn->fd, NULL, 0, &kev, 1, &ts); |
168 | | if (n > 0) { |
169 | | /* We got an event. kqueue doesn't cleanly separate files and dirs |
170 | | * within the directory for EVFILT_VNODE. But we can trigger the callback |
171 | | * with is_file=0 for the dir itself. */ |
172 | | dn->cb("", 0, dn->user); |
173 | | } |
174 | | } else if (reason == LWS_CALLBACK_RAW_CLOSE_FILE) { |
175 | | if (dn) { |
176 | | if (dn->fd >= 0) |
177 | | close(dn->fd); |
178 | | if (dn->dir_fd >= 0) |
179 | | close(dn->dir_fd); |
180 | | dn->fd = -1; |
181 | | dn->dir_fd = -1; |
182 | | dn->wsi = NULL; |
183 | | } |
184 | | } |
185 | | return 0; |
186 | | } |
187 | | |
188 | | const struct lws_protocols protocol_lws_dir_notify = { |
189 | | "lws-dir-notify", |
190 | | lws_dir_notify_rx, |
191 | | 0, 0, 0, NULL, 0 |
192 | | }; |
193 | | |
194 | | struct lws_dir_notify * |
195 | | lws_dir_notify_create(struct lws_context *ctx, const char *path, |
196 | | lws_dir_notify_cb_t cb, void *user) |
197 | | { |
198 | | struct lws_dir_notify *dn; |
199 | | struct lws_vhost *vh = lws_get_vhost_by_name(ctx, "system"); |
200 | | lws_sock_file_fd_type sock; |
201 | | struct kevent kev; |
202 | | |
203 | | if (!vh) { |
204 | | vh = ctx->vhost_list; |
205 | | if (!vh) |
206 | | return NULL; |
207 | | } |
208 | | |
209 | | dn = lws_malloc(sizeof(*dn), __func__); |
210 | | if (!dn) |
211 | | return NULL; |
212 | | |
213 | | dn->ctx = ctx; |
214 | | dn->cb = cb; |
215 | | dn->user = user; |
216 | | |
217 | | dn->dir_fd = open(path, O_RDONLY | O_NONBLOCK); |
218 | | if (dn->dir_fd < 0) |
219 | | goto bail; |
220 | | |
221 | | dn->fd = kqueue(); |
222 | | if (dn->fd < 0) { |
223 | | close(dn->dir_fd); |
224 | | goto bail; |
225 | | } |
226 | | |
227 | | EV_SET(&kev, dn->dir_fd, EVFILT_VNODE, EV_ADD | EV_CLEAR, |
228 | | NOTE_WRITE | NOTE_EXTEND | NOTE_ATTRIB | NOTE_RENAME | NOTE_REVOKE | NOTE_DELETE, |
229 | | 0, NULL); |
230 | | |
231 | | if (kevent(dn->fd, &kev, 1, NULL, 0, NULL) == -1) { |
232 | | close(dn->dir_fd); |
233 | | close(dn->fd); |
234 | | goto bail; |
235 | | } |
236 | | |
237 | | sock.filefd = dn->fd; |
238 | | dn->wsi = lws_adopt_descriptor_vhost(vh, LWS_ADOPT_RAW_FILE_DESC, sock, |
239 | | protocol_lws_dir_notify.name, NULL); |
240 | | if (!dn->wsi) { |
241 | | close(dn->dir_fd); |
242 | | close(dn->fd); |
243 | | goto bail; |
244 | | } |
245 | | |
246 | | lws_set_opaque_user_data(dn->wsi, dn); |
247 | | |
248 | | return dn; |
249 | | |
250 | | bail: |
251 | | lws_free(dn); |
252 | | return NULL; |
253 | | } |
254 | | |
255 | | void |
256 | | lws_dir_notify_destroy(struct lws_dir_notify **pdn) |
257 | | { |
258 | | struct lws_dir_notify *dn = *pdn; |
259 | | if (!dn) |
260 | | return; |
261 | | |
262 | | if (dn->wsi) |
263 | | lws_set_timeout(dn->wsi, 1, LWS_TO_KILL_ASYNC); |
264 | | else { |
265 | | if (dn->fd >= 0) |
266 | | close(dn->fd); |
267 | | if (dn->dir_fd >= 0) |
268 | | close(dn->dir_fd); |
269 | | } |
270 | | |
271 | | lws_free(dn); |
272 | | *pdn = NULL; |
273 | | } |
274 | | |
275 | | #elif defined(WIN32) |
276 | | |
277 | | struct lws_dir_notify_thread_ctx { |
278 | | struct lws_dir_notify *dn; |
279 | | HANDLE hEvent; |
280 | | HANDLE hThread; |
281 | | volatile int quit; |
282 | | }; |
283 | | |
284 | | static DWORD WINAPI |
285 | | lws_dir_notify_windows_thread(LPVOID lpParam) |
286 | | { |
287 | | struct lws_dir_notify_thread_ctx *tctx = (struct lws_dir_notify_thread_ctx *)lpParam; |
288 | | |
289 | | while (!tctx->quit) { |
290 | | DWORD wait_status = WaitForSingleObject(tctx->hEvent, 1000); |
291 | | if (wait_status == WAIT_OBJECT_0) { |
292 | | /* Event triggered */ |
293 | | if (!tctx->quit && tctx->dn && tctx->dn->cb) { |
294 | | tctx->dn->cb("", 0, tctx->dn->user); |
295 | | } |
296 | | FindNextChangeNotification(tctx->hEvent); |
297 | | lws_cancel_service(tctx->dn->ctx); |
298 | | } |
299 | | } |
300 | | return 0; |
301 | | } |
302 | | |
303 | | const struct lws_protocols protocol_lws_dir_notify = { |
304 | | "lws-dir-notify", |
305 | | NULL, |
306 | | 0, 0, 0, NULL, 0 |
307 | | }; |
308 | | |
309 | | struct lws_dir_notify * |
310 | | lws_dir_notify_create(struct lws_context *ctx, const char *path, |
311 | | lws_dir_notify_cb_t cb, void *user) |
312 | | { |
313 | | struct lws_dir_notify *dn; |
314 | | struct lws_dir_notify_thread_ctx *tctx; |
315 | | WCHAR wpath[MAX_PATH]; |
316 | | |
317 | | dn = lws_malloc(sizeof(*dn), __func__); |
318 | | if (!dn) |
319 | | return NULL; |
320 | | |
321 | | tctx = lws_zalloc(sizeof(*tctx), __func__); |
322 | | if (!tctx) { |
323 | | lws_free(dn); |
324 | | return NULL; |
325 | | } |
326 | | |
327 | | dn->ctx = ctx; |
328 | | dn->cb = cb; |
329 | | dn->user = user; |
330 | | dn->wsi = (struct lws *)tctx; /* store thread context pointer in dn->wsi for Windows */ |
331 | | |
332 | | MultiByteToWideChar(CP_UTF8, 0, path, -1, wpath, LWS_ARRAY_SIZE(wpath)); |
333 | | |
334 | | tctx->hEvent = FindFirstChangeNotificationW(wpath, FALSE, |
335 | | FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME | |
336 | | FILE_NOTIFY_CHANGE_ATTRIBUTES | FILE_NOTIFY_CHANGE_SIZE | |
337 | | FILE_NOTIFY_CHANGE_LAST_WRITE); |
338 | | |
339 | | if (tctx->hEvent == INVALID_HANDLE_VALUE) { |
340 | | lws_free(tctx); |
341 | | lws_free(dn); |
342 | | return NULL; |
343 | | } |
344 | | |
345 | | tctx->dn = dn; |
346 | | tctx->hThread = CreateThread(NULL, 0, lws_dir_notify_windows_thread, tctx, 0, NULL); |
347 | | if (!tctx->hThread) { |
348 | | FindCloseChangeNotification(tctx->hEvent); |
349 | | lws_free(tctx); |
350 | | lws_free(dn); |
351 | | return NULL; |
352 | | } |
353 | | |
354 | | return dn; |
355 | | } |
356 | | |
357 | | void |
358 | | lws_dir_notify_destroy(struct lws_dir_notify **pdn) |
359 | | { |
360 | | struct lws_dir_notify *dn = *pdn; |
361 | | struct lws_dir_notify_thread_ctx *tctx; |
362 | | |
363 | | if (!dn) |
364 | | return; |
365 | | |
366 | | tctx = (struct lws_dir_notify_thread_ctx *)dn->wsi; |
367 | | if (tctx) { |
368 | | tctx->quit = 1; |
369 | | WaitForSingleObject(tctx->hThread, INFINITE); |
370 | | CloseHandle(tctx->hThread); |
371 | | FindCloseChangeNotification(tctx->hEvent); |
372 | | lws_free(tctx); |
373 | | } |
374 | | |
375 | | lws_free(dn); |
376 | | *pdn = NULL; |
377 | | } |
378 | | |
379 | | #else |
380 | | /* Stubs for non-Linux implementations */ |
381 | | const struct lws_protocols protocol_lws_dir_notify = { |
382 | | "lws-dir-notify", |
383 | | NULL, |
384 | | 0, 0, 0, NULL, 0 |
385 | | }; |
386 | | |
387 | | struct lws_dir_notify * |
388 | | lws_dir_notify_create(struct lws_context *ctx, const char *path, |
389 | | lws_dir_notify_cb_t cb, void *user) |
390 | | { |
391 | | lwsl_err("%s: lws_dir_notify not yet implemented natively on this platform\n", __func__); |
392 | | return NULL; |
393 | | } |
394 | | |
395 | | void |
396 | | lws_dir_notify_destroy(struct lws_dir_notify **pdn) |
397 | | { |
398 | | } |
399 | | #endif |
400 | | |
401 | | #endif /* LWS_WITH_DIR_NOTIFY */ |