Coverage Report

Created: 2026-09-14 06:24

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libwebsockets/lib/misc/dir.c
Line
Count
Source
1
/*
2
 * libwebsockets - small server side websockets and web server implementation
3
 *
4
 * Copyright (C) 2010 - 2019 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
#if !defined(NO_GNU_SOURCE_THIS_TIME)
26
#define NO_GNU_SOURCE_THIS_TIME
27
#endif
28
#if !defined(_DARWIN_C_SOURCE)
29
#define _DARWIN_C_SOURCE
30
#endif
31
32
#include "private-lib-core.h"
33
#include <string.h>
34
#include <stdio.h>
35
#if defined(__linux__) && defined(__GLIBC__)
36
#include <sys/auxv.h>
37
#endif
38
39
#include <sys/stat.h>
40
#if defined(WIN32)
41
#include <direct.h>
42
#define read _read
43
#define open _open
44
#define close _close
45
#define write _write
46
#define mkdir(x,y) _mkdir(x)
47
#define rmdir _rmdir
48
#define unlink _unlink
49
#define HAVE_STRUCT_TIMESPEC
50
#if defined(pid_t)
51
#undef pid_t
52
#endif
53
#endif /* win32 */
54
55
0
#define COMBO_SIZEOF 512
56
57
#if !defined(LWS_PLAT_FREERTOS)
58
59
#if defined(WIN32)
60
#include "../../win32port/dirent/dirent-win32.h"
61
#else
62
#include <dirent.h>
63
#endif
64
65
static int filter(const struct dirent *ent)
66
0
{
67
0
  if (!strcmp(ent->d_name, ".") || !strcmp(ent->d_name, ".."))
68
0
    return 0;
69
70
0
  return 1;
71
0
}
72
73
74
#if !defined(WIN32)
75
static char csep = '/';
76
#else
77
static char csep = '\\';
78
#endif
79
80
static void
81
lws_dir_via_stat(char *combo, size_t l, const char *path, struct lws_dir_entry *lde)
82
0
{
83
0
        struct stat s;
84
85
0
        lws_strncpy(combo + l, path, COMBO_SIZEOF - l);
86
87
0
        lde->type = LDOT_UNKNOWN;
88
89
  /*
90
   * This is the fallback for filesystems that don't fill in d_type (XFS
91
   * with ftype=0, ZFS) and for platforms that never do.  It must agree
92
   * with what the d_type path reports, ie, a symlink must come out as
93
   * LDOT_LINK and not as whatever it points at... stat() follows the
94
   * link and would report a symlink-to-dir as LDOT_DIR, letting rm -rf
95
   * style callbacks recurse out of the tree they are walking.
96
   */
97
98
#if defined(WIN32) || defined(_WIN32)
99
        if (!stat(combo, &s)) {
100
#else
101
0
        if (!lstat(combo, &s)) {
102
0
#endif
103
0
    switch (s.st_mode & S_IFMT) {
104
0
    case S_IFBLK:
105
0
      lde->type = LDOT_BLOCK;
106
0
      break;
107
0
    case S_IFCHR:
108
0
      lde->type = LDOT_CHAR;
109
0
      break;
110
0
    case S_IFDIR:
111
0
      lde->type = LDOT_DIR;
112
0
      break;
113
0
    case S_IFIFO:
114
0
      lde->type = LDOT_FIFO;
115
0
      break;
116
0
#if !defined(WIN32)
117
0
    case S_IFLNK:
118
0
      lde->type = LDOT_LINK;
119
0
      break;
120
0
#endif
121
0
    case S_IFREG:
122
0
      lde->type = LDOT_FILE;
123
0
      break;
124
0
    default:
125
0
      break;
126
0
    }
127
0
        }
128
0
}
129
130
static void
131
_fill_lde(char *combo, size_t l, const char *name, unsigned int type, struct lws_dir_entry *lde)
132
0
{
133
0
  lde->name = name;
134
135
  /*
136
   * some filesystems don't report this (ZFS) and tell that
137
   * files are LDOT_UNKNOWN
138
   */
139
140
#if defined(__sun) || defined(__QNX__)
141
  lws_dir_via_stat(combo, l, name, lde);
142
#else
143
  /*
144
   * XFS on Linux doesn't fill in d_type at all, always zero.
145
   */
146
147
0
       if (DT_BLK  != DT_UNKNOWN && type == DT_BLK)
148
0
    lde->type = LDOT_BLOCK;
149
0
  else if (DT_CHR  != DT_UNKNOWN && type == DT_CHR)
150
0
    lde->type = LDOT_CHAR;
151
0
  else if (DT_DIR  != DT_UNKNOWN && type == DT_DIR)
152
0
    lde->type = LDOT_DIR;
153
0
  else if (DT_FIFO != DT_UNKNOWN && type == DT_FIFO)
154
0
    lde->type = LDOT_FIFO;
155
0
  else if (DT_LNK  != DT_UNKNOWN && type == DT_LNK)
156
0
    lde->type = LDOT_LINK;
157
0
  else if (DT_REG  != DT_UNKNOWN && type == DT_REG)
158
0
    lde->type = LDOT_FILE;
159
0
  else if (DT_SOCK != DT_UNKNOWN && type == DT_SOCK)
160
0
    lde->type = LDOTT_SOCKET;
161
0
  else {
162
0
    lde->type = LDOT_UNKNOWN;
163
0
    lws_dir_via_stat(combo, l, name, lde);
164
0
  }
165
0
#endif
166
0
}
167
168
int
169
lws_dir_via_info(struct lws_dir_info *info)
170
0
{
171
0
  struct dirent **namelist = NULL;
172
0
  struct lws_dir_entry lde;
173
0
  char combo[COMBO_SIZEOF];
174
0
  unsigned int type = 0;
175
0
  int n, i, ret = 1;
176
0
  size_t l;
177
178
0
  assert(info->dirpath);
179
0
  assert(info->cb);
180
181
0
  l = (size_t)(ssize_t)lws_snprintf(combo, COMBO_SIZEOF - 2, "%s", info->dirpath);
182
0
  combo[l++] = csep;
183
0
  combo[l] = '\0';
184
185
0
  n = scandir((char *)info->dirpath, &namelist, filter, alphasort);
186
0
  if (n < 0) {
187
0
    lwsl_info("Scandir on '%s' failed, errno %d\n", info->dirpath, LWS_ERRNO);
188
0
    return 1;
189
0
  }
190
191
0
  for (i = 0; i < n; i++) {
192
0
#if !defined(__sun) && !defined(__QNX__)
193
0
    type = namelist[i]->d_type;
194
0
#endif
195
0
    if ((char *)strchr(namelist[i]->d_name, '~'))
196
0
      goto skip;
197
198
0
    _fill_lde(combo, l, namelist[i]->d_name, type, &lde);
199
200
0
    if (info->cb(info->dirpath, info->user, &lde)) {
201
0
      while (i < n)
202
0
        free(namelist[i++]);
203
0
      ret = 0; /* told to stop by cb */
204
0
      goto bail;
205
0
    }
206
0
skip:
207
0
    free(namelist[i]);
208
0
  }
209
210
0
  if (info->do_toplevel_cb) {
211
0
    lde.name = "";
212
0
    lws_dir_via_stat(combo, l, "", &lde);
213
214
0
    if (info->cb(info->dirpath, info->user, &lde))
215
0
      ret = 0; /* told to stop by cb */
216
0
  }
217
218
0
bail:
219
0
  free(namelist);
220
221
0
  return ret;
222
0
}
223
224
int
225
lws_dir(const char *dirpath, void *user, lws_dir_callback_function cb)
226
0
{
227
0
  struct lws_dir_info info;
228
229
0
  memset(&info, 0, sizeof(info));
230
231
0
  info.dirpath = dirpath;
232
0
  info.user = user;
233
0
  info.cb = cb;
234
235
0
  return lws_dir_via_info(&info);
236
0
}
237
238
/*
239
 * Check filename against one globby filter
240
 *
241
 * We can support things like "*.rpm"
242
 */
243
244
static int
245
lws_dir_glob_check(const char *nm, const char *filt)
246
0
{
247
0
  while (*nm) {
248
0
    if (*filt == '*') {
249
0
      if (!strcmp(nm, filt + 1))
250
0
        return 1;
251
0
    } else {
252
0
      if (*nm != *filt)
253
0
        return 0;
254
0
      filt++;
255
0
    }
256
0
    nm++;
257
0
  }
258
259
0
  return 0;
260
0
}
261
262
int
263
lws_dir_du_cb(const char *dirpath, void *user, struct lws_dir_entry *lde)
264
0
{
265
0
  lws_dir_du_t *du = (lws_dir_du_t *)user;
266
0
  char path[384];
267
0
  struct stat s;
268
269
0
  if (!strcmp(lde->name, ".") || !strcmp(lde->name, ".."))
270
0
    return 0;
271
272
0
  lws_snprintf(path, sizeof(path), "%s%c%s", dirpath, csep, lde->name);
273
274
0
  if (lde->type == LDOT_DIR) {
275
0
    lws_dir(path, user, lws_dir_du_cb);
276
277
0
    return 0;
278
0
  }
279
280
0
  if (lde->type == LDOT_FILE) {
281
0
    if (stat(path, &s))
282
0
      lwsl_warn("%s: stat %s failed %d\n", __func__,
283
0
          path, errno);
284
0
    else {
285
0
      du->size_in_bytes += (uint64_t)s.st_size;
286
0
      du->count_files++;
287
0
    }
288
0
  }
289
290
0
  return 0;
291
0
}
292
293
/*
294
 * We get passed a single filter string, like "*.txt" or "mydir/\*.rpm" or so.
295
 */
296
297
int
298
lws_dir_glob_cb(const char *dirpath, void *user, struct lws_dir_entry *lde)
299
0
{
300
0
  lws_dir_glob_t *filter = (lws_dir_glob_t*)user;
301
0
  char path[384];
302
303
0
  if (!strcmp(lde->name, ".") || !strcmp(lde->name, ".."))
304
0
    return 0;
305
306
0
  if (lde->type == LDOT_DIR)
307
0
    return 0;
308
309
0
  if (lws_dir_glob_check(lde->name, filter->filter)) {
310
0
    lws_snprintf(path, sizeof(path), "%s%c%s", dirpath, csep,
311
0
                 lde->name);
312
0
    filter->cb(filter->user, path);
313
0
  }
314
315
0
  return 0;
316
0
}
317
318
int
319
lws_dir_rm_rf_cb(const char *dirpath, void *user, struct lws_dir_entry *lde)
320
0
{
321
0
  char path[384];
322
323
0
  if (!strcmp(lde->name, ".") || !strcmp(lde->name, ".."))
324
0
    return 0;
325
326
0
  lws_snprintf(path, sizeof(path), "%s%c%s", dirpath, csep, lde->name);
327
328
0
  if (lde->type == LDOT_DIR) {
329
0
#if !defined(WIN32) && !defined(_WIN32) && !defined(__COVERITY__)
330
0
    char dummy[8];
331
    /*
332
     * hm... eg, recursive dir symlinks can show up a LDOT_DIR
333
     * here.  If it's a symlink, don't recurse into it.
334
     *
335
     * Notice we immediately discard dummy without looking in it.
336
     * There is no way to get into trouble from its lack of NUL
337
     * termination in dummy[].  We just wanted to know if it was
338
     * a symlink at all.
339
     *
340
     * Hide this from Coverity since it flags any use of readlink()
341
     * even if safe.
342
     */
343
0
    if (readlink(path, dummy, sizeof(dummy)) < 0)
344
0
#endif
345
0
      lws_dir(path, NULL, lws_dir_rm_rf_cb);
346
347
0
    if (rmdir(path))
348
0
      lwsl_warn("%s: rmdir %s failed %d\n", __func__, path, errno);
349
0
  } else {
350
0
    if (unlink(path)) {
351
#if defined(WIN32)
352
      SetFileAttributesA(path, FILE_ATTRIBUTE_NORMAL);
353
      if (unlink(path))
354
#else
355
0
      if (rmdir(path))
356
0
#endif
357
0
      lwsl_warn("%s: unlink %s failed %d (type %d)\n",
358
0
          __func__, path, errno, lde->type);
359
0
    }
360
0
  }
361
362
0
  return 0;
363
0
}
364
365
366
#endif
367
368
struct lws_plugin *
369
lws_plugin_alloc(struct lws_plugin **pplugin)
370
0
{
371
0
  struct lws_plugin *pin = lws_malloc(sizeof(*pin), __func__);
372
373
0
  if (!pin)
374
0
    return NULL;
375
376
0
  pin->list = *pplugin;
377
0
  *pplugin = pin;
378
379
0
  return pin;
380
0
}
381
382
#if defined(LWS_WITH_PLUGINS_API)
383
384
#if defined(LWS_BUILTIN_PLUGIN_NAMES)
385
386
extern const lws_plugin_protocol_t *builtin_pcols[];
387
388
int
389
lws_plugins_handle_builtin(struct lws_plugin **pplugin,
390
         each_plugin_cb_t each, void *each_user)
391
{
392
  size_t n = 0;
393
394
  while (builtin_pcols[n]) {
395
    struct lws_plugin *pin = lws_plugin_alloc(pplugin);
396
    if (!pin)
397
      return 1;
398
399
    pin->u.l = NULL;
400
    pin->hdr = &builtin_pcols[n]->hdr;
401
402
    if (each)
403
      each(pin, each_user);
404
405
    n++;
406
  }
407
408
  return 0;
409
}
410
#endif
411
412
#if defined(LWS_WITH_NETWORK) && defined(LWS_WITH_PLUGINS_API)
413
struct lws_plugins_args {
414
  struct lws_plugin **pplugin;
415
  const char    *_class;
416
  const char    *filter;
417
  each_plugin_cb_t  each;
418
  void      *each_user;
419
};
420
421
static unsigned int
422
lws_plugin_get_priority(const lws_plugin_header_t *hdr)
423
{
424
  if (hdr->api_magic >= 192)
425
    return hdr->priority;
426
427
  return 0;
428
}
429
430
static void
431
lws_plugins_sort(struct lws_plugin **pplugin)
432
{
433
  struct lws_plugin *p1, *p2, *prev;
434
  int swapped;
435
436
  if (!pplugin || !*pplugin || !(*pplugin)->list)
437
    return;
438
439
  do {
440
    swapped = 0;
441
    p1 = *pplugin;
442
    prev = NULL;
443
444
    while (p1->list) {
445
      p2 = p1->list;
446
      if (lws_plugin_get_priority(p1->hdr) <
447
          lws_plugin_get_priority(p2->hdr)) {
448
        /* swap */
449
        p1->list = p2->list;
450
        p2->list = p1;
451
        if (prev)
452
          prev->list = p2;
453
        else
454
          *pplugin = p2;
455
456
        swapped = 1;
457
        prev = p2;
458
      } else {
459
        prev = p1;
460
        p1 = p1->list;
461
      }
462
    }
463
  } while (swapped);
464
}
465
466
static int
467
lws_plugins_dir_cb(const char *dirpath, void *user, struct lws_dir_entry *lde)
468
{
469
  struct lws_plugins_args *pa = (struct lws_plugins_args *)user;
470
  char path[256], base[64], *q = base;
471
  const lws_plugin_header_t *pl;
472
  const char *p;
473
474
  if (strlen(lde->name) < 7)
475
    return 0; /* keep going */
476
477
  /*
478
   * The actual plugin names for protocol plugins look like
479
   * "libprotocol_lws_ssh_base.so" and for event libs
480
   * "libwebsockets-evlib_ev.so"... to recover the base name of
481
   * "lws_ssh_base" and "evlib_ev" we strip from the left to after the
482
   * first _ or -, and then truncate at the first .
483
   */
484
485
  p = lde->name;
486
  while (*p && *p != '_' && *p != '-')
487
    p++;
488
  if (!*p)
489
    return 0;
490
  p++;
491
  while (*p && *p != '.' && lws_ptr_diff(q, base) < (int)sizeof(base) - 1)
492
    *q++ = *p++;
493
  *q = '\0';
494
495
  /* if he's given a filter, only match if base matches it */
496
  if (pa->filter && strcmp(base, pa->filter))
497
    return 0; /* keep going */
498
499
  lws_snprintf(path, sizeof(path) - 1, "%s/%s", dirpath, lde->name);
500
501
  pl = lws_plat_dlopen(pa->pplugin, path, base, pa->_class,
502
           NULL, NULL);
503
504
  /*
505
   * If we were looking for a specific plugin, finding it should make
506
   * us stop looking (eg, to account for directory precedence of the
507
   * same plugin).  If scanning for plugins in a dir, we always keep
508
   * going.
509
   */
510
511
  return pa->filter && pl;
512
}
513
514
int
515
lws_plugins_init(struct lws_plugin **pplugin, const char * const *d,
516
     const char *_class, const char *filter,
517
     each_plugin_cb_t each, void *each_user)
518
{
519
  struct lws_plugins_args pa;
520
  char *ld_env;
521
  int ret = 1;
522
523
  pa.pplugin = pplugin;
524
  pa._class = _class;
525
  pa.each = each;
526
  pa.each_user = each_user;
527
  pa.filter = filter;
528
529
  /*
530
   * Check LD_LIBRARY_PATH override path first if present
531
   */
532
533
  ld_env = getenv("LD_LIBRARY_PATH");
534
  /*
535
   * The dynamic loader ignores LD_LIBRARY_PATH for set-uid / set-gid
536
   * (AT_SECURE) processes precisely so an unprivileged caller cannot
537
   * pick the code a privileged process loads; scanning it ourselves
538
   * for plugins to dlopen() would reintroduce that.  Honour the same
539
   * rule.
540
   */
541
#if defined(__linux__) && defined(__GLIBC__)
542
  if (ld_env && getauxval(AT_SECURE))
543
    ld_env = NULL;
544
#elif defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) || \
545
      defined(__APPLE__)
546
  if (ld_env && issetugid())
547
    ld_env = NULL;
548
#endif
549
  if (ld_env) {
550
    char temp[128];
551
    struct lws_tokenize ts;
552
553
    memset(&ts, 0, sizeof(ts));
554
    ts.start = ld_env;
555
    ts.len = strlen(ld_env);
556
    ts.flags = LWS_TOKENIZE_F_SLASH_NONTERM |
557
         LWS_TOKENIZE_F_DOT_NONTERM |
558
         LWS_TOKENIZE_F_MINUS_NONTERM |
559
         LWS_TOKENIZE_F_NO_INTEGERS |
560
         LWS_TOKENIZE_F_NO_FLOATS;
561
562
    do {
563
      ts.e = (int8_t)lws_tokenize(&ts);
564
      if (ts.e != LWS_TOKZE_TOKEN)
565
        continue;
566
567
      lws_strnncpy(temp, ts.token,
568
             ts.token_len,
569
             sizeof(temp));
570
571
      lwsl_info("%s: trying %s\n", __func__, temp);
572
      if (!lws_dir(temp, &pa, lws_plugins_dir_cb))
573
        ret = 0;
574
575
    } while (ts.e > 0);
576
  }
577
578
  while (d && *d) {
579
    lwsl_info("%s: trying %s\n", __func__, *d);
580
    if (!lws_dir(*d, &pa, lws_plugins_dir_cb))
581
      ret = 0;
582
583
    d++;
584
  }
585
586
  if (*pplugin) {
587
    struct lws_plugin *p = *pplugin;
588
589
    lws_plugins_sort(pplugin);
590
591
    if (each) {
592
      p = *pplugin;
593
      while (p) {
594
        each(p, each_user);
595
        p = p->list;
596
      }
597
    }
598
  }
599
600
  return ret;
601
}
602
603
int
604
lws_plugins_destroy(struct lws_plugin **pplugin, each_plugin_cb_t each,
605
        void *each_user)
606
{
607
  struct lws_plugin *p = *pplugin, *p1;
608
609
  while (p) {
610
    if (each)
611
      each(p, each_user);
612
    if (p->u.l)
613
      lws_plat_destroy_dl(p);
614
    p1 = p->list;
615
    p->list = NULL;
616
    lws_free(p);
617
    p = p1;
618
  }
619
620
  *pplugin = NULL;
621
622
  return 0;
623
}
624
#endif
625
#endif