Coverage Report

Created: 2026-04-01 06:25

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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 */