Coverage Report

Created: 2026-02-22 06:10

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/util-linux/lib/path.c
Line
Count
Source
1
/*
2
 * No copyright is claimed.  This code is in the public domain; do with
3
 * it what you wish.
4
 *
5
 * Written by Karel Zak <kzak@redhat.com> [2018]
6
 *
7
 *
8
 * Simple functions to access files. Paths can be globally prefixed to read
9
 * data from an alternative source (e.g. a /proc dump for regression tests).
10
 *
11
 * The paths is possible to format by printf-like way for functions with "f"
12
 * postfix in the name (e.g. readf, openf, ... ul_path_readf_u64()).
13
 *
14
 * The ul_path_read_* API is possible to use without path_cxt handler. In this
15
 * case is not possible to use global prefix and printf-like formatting.
16
 */
17
#include <stdarg.h>
18
#include <string.h>
19
#include <unistd.h>
20
#include <stdio.h>
21
#include <inttypes.h>
22
#include <errno.h>
23
24
#include "c.h"
25
#include "fileutils.h"
26
#include "all-io.h"
27
#include "path.h"
28
#include "debug.h"
29
#include "strutils.h"
30
31
/*
32
 * Debug stuff (based on include/debug.h)
33
 */
34
static UL_DEBUG_DEFINE_MASK(ulpath);
35
UL_DEBUG_DEFINE_MASKNAMES(ulpath) = UL_DEBUG_EMPTY_MASKNAMES;
36
37
0
#define ULPATH_DEBUG_INIT (1 << 1)
38
0
#define ULPATH_DEBUG_CXT  (1 << 2)
39
40
0
#define DBG(m, x)       __UL_DBG(ulpath, ULPATH_DEBUG_, m, x)
41
#define ON_DBG(m, x)    __UL_DBG_CALL(ulpath, ULPATH_DEBUG_, m, x)
42
43
0
#define UL_DEBUG_CURRENT_MASK UL_DEBUG_MASK(ulpath)
44
#include "debugobj.h"
45
46
void ul_path_init_debug(void)
47
0
{
48
0
  if (ulpath_debug_mask)
49
0
    return;
50
0
  __UL_INIT_DEBUG_FROM_ENV(ulpath, ULPATH_DEBUG_, 0, ULPATH_DEBUG);
51
0
}
52
53
struct path_cxt *ul_new_path(const char *dir, ...)
54
0
{
55
0
  struct path_cxt *pc = calloc(1, sizeof(*pc));
56
57
0
  if (!pc)
58
0
    return NULL;
59
60
0
  DBG(CXT, ul_debugobj(pc, "alloc"));
61
62
0
  pc->refcount = 1;
63
0
  pc->dir_fd = -1;
64
65
0
  if (dir) {
66
0
    int rc;
67
0
    va_list ap;
68
69
0
    va_start(ap, dir);
70
0
    rc = vasprintf(&pc->dir_path, dir, ap);
71
0
    va_end(ap);
72
73
0
    if (rc < 0 || !pc->dir_path)
74
0
      goto fail;
75
0
  }
76
0
  return pc;
77
0
fail:
78
0
  ul_unref_path(pc);
79
0
  return NULL;
80
0
}
81
82
void ul_ref_path(struct path_cxt *pc)
83
0
{
84
0
  if (pc)
85
0
    pc->refcount++;
86
0
}
87
88
void ul_unref_path(struct path_cxt *pc)
89
0
{
90
0
  if (!pc)
91
0
    return;
92
93
0
  pc->refcount--;
94
95
0
  if (pc->refcount <= 0) {
96
0
    DBG(CXT, ul_debugobj(pc, "dealloc"));
97
0
    if (pc->dialect)
98
0
      pc->free_dialect(pc);
99
0
    ul_path_close_dirfd(pc);
100
0
    free(pc->dir_path);
101
0
    free(pc->prefix);
102
0
    free(pc);
103
0
  }
104
0
}
105
106
int ul_path_set_prefix(struct path_cxt *pc, const char *prefix)
107
0
{
108
0
  char *p = NULL;
109
110
0
  assert(pc->dir_fd < 0);
111
112
0
  if (prefix) {
113
0
    p = strdup(prefix);
114
0
    if (!p)
115
0
      return -ENOMEM;
116
0
  }
117
118
0
  free(pc->prefix);
119
0
  pc->prefix = p;
120
0
  DBG(CXT, ul_debugobj(pc, "new prefix: '%s'", p));
121
0
  return 0;
122
0
}
123
124
const char *ul_path_get_prefix(struct path_cxt *pc)
125
0
{
126
0
  return pc ? pc->prefix : NULL;
127
0
}
128
129
int ul_path_set_dir(struct path_cxt *pc, const char *dir)
130
0
{
131
0
  char *p = NULL;
132
133
0
  if (dir) {
134
0
    p = strdup(dir);
135
0
    if (!p)
136
0
      return -ENOMEM;
137
0
  }
138
139
0
  if (pc->dir_fd >= 0) {
140
0
    close(pc->dir_fd);
141
0
    pc->dir_fd = -1;
142
0
  }
143
144
0
  free(pc->dir_path);
145
0
  pc->dir_path = p;
146
0
  DBG(CXT, ul_debugobj(pc, "new dir: '%s'", p));
147
0
  return 0;
148
0
}
149
150
const char *ul_path_get_dir(struct path_cxt *pc)
151
0
{
152
0
  return pc ? pc->dir_path : NULL;
153
0
}
154
155
int ul_path_set_dialect(struct path_cxt *pc, void *data, void free_data(struct path_cxt *))
156
0
{
157
0
  pc->dialect = data;
158
0
  pc->free_dialect = free_data;
159
0
  DBG(CXT, ul_debugobj(pc, "(re)set dialect"));
160
0
  return 0;
161
0
}
162
163
void *ul_path_get_dialect(struct path_cxt *pc)
164
0
{
165
0
  return pc ? pc->dialect : NULL;
166
0
}
167
168
int ul_path_set_enoent_redirect(struct path_cxt *pc, int (*func)(struct path_cxt *, const char *, int *))
169
0
{
170
0
  pc->redirect_on_enoent = func;
171
0
  return 0;
172
0
}
173
174
static const char *get_absdir(struct path_cxt *pc)
175
0
{
176
0
  int rc;
177
0
  const char *dirpath;
178
179
0
  if (!pc->prefix)
180
0
    return pc->dir_path;
181
182
0
  dirpath = pc->dir_path;
183
0
  if (!dirpath)
184
0
    return pc->prefix;
185
0
  if (*dirpath == '/')
186
0
    dirpath++;
187
188
0
  rc = snprintf(pc->path_buffer, sizeof(pc->path_buffer), "%s/%s", pc->prefix, dirpath);
189
0
  if (rc < 0)
190
0
    return NULL;
191
0
  if ((size_t)rc >= sizeof(pc->path_buffer)) {
192
0
    errno = ENAMETOOLONG;
193
0
    return NULL;
194
0
  }
195
196
0
  return pc->path_buffer;
197
0
}
198
199
int ul_path_is_accessible(struct path_cxt *pc)
200
0
{
201
0
  const char *path;
202
0
  assert(pc);
203
204
0
  if (pc->dir_fd >= 0)
205
0
    return 1;
206
207
0
  path = get_absdir(pc);
208
0
  if (!path)
209
0
    return 0;
210
0
  return access(path, F_OK) == 0;
211
0
}
212
213
int ul_path_get_dirfd(struct path_cxt *pc)
214
0
{
215
0
  assert(pc);
216
0
  assert(pc->dir_path);
217
218
0
  if (pc->dir_fd < 0) {
219
0
    const char *path = get_absdir(pc);
220
0
    if (!path)
221
0
      return -errno;
222
223
0
    DBG(CXT, ul_debugobj(pc, "opening dir: '%s'", path));
224
0
    pc->dir_fd = open(path, O_RDONLY|O_CLOEXEC);
225
0
  }
226
227
0
  return pc->dir_fd;
228
0
}
229
230
/* Note that next ul_path_get_dirfd() will reopen the directory */
231
void ul_path_close_dirfd(struct path_cxt *pc)
232
0
{
233
0
  assert(pc);
234
235
0
  if (pc->dir_fd >= 0) {
236
0
    DBG(CXT, ul_debugobj(pc, "closing dir"));
237
0
    close(pc->dir_fd);
238
0
    pc->dir_fd = -1;
239
0
  }
240
0
}
241
242
int ul_path_isopen_dirfd(struct path_cxt *pc)
243
0
{
244
0
  return pc && pc->dir_fd >= 0;
245
0
}
246
247
static const char *ul_path_mkpath(struct path_cxt *pc, const char *path, va_list ap)
248
0
{
249
0
  int rc;
250
251
0
  errno = 0;
252
253
0
  rc = vsnprintf(pc->path_buffer, sizeof(pc->path_buffer), path, ap);
254
0
  if (rc < 0) {
255
0
    if (!errno)
256
0
      errno = EINVAL;
257
0
    return NULL;
258
0
  }
259
260
0
  if ((size_t)rc >= sizeof(pc->path_buffer)) {
261
0
    errno = ENAMETOOLONG;
262
0
    return NULL;
263
0
  }
264
265
0
  return pc->path_buffer;
266
0
}
267
268
char *ul_path_get_abspath(struct path_cxt *pc, char *buf, size_t bufsz, const char *path, ...)
269
0
{
270
0
  if (path) {
271
0
    int rc;
272
0
    va_list ap;
273
0
    const char *tail = NULL, *dirpath = pc->dir_path;
274
275
0
    va_start(ap, path);
276
0
    tail = ul_path_mkpath(pc, path, ap);
277
0
    va_end(ap);
278
279
0
    if (dirpath && *dirpath == '/')
280
0
      dirpath++;
281
0
    if (tail && *tail == '/')
282
0
      tail++;
283
284
0
    rc = snprintf(buf, bufsz, "%s/%s/%s",
285
0
        pc->prefix ? pc->prefix : "",
286
0
        dirpath ? dirpath : "",
287
0
        tail ? tail : "");
288
289
0
    if ((size_t)rc >= bufsz) {
290
0
      errno = ENAMETOOLONG;
291
0
      return NULL;
292
0
    }
293
0
  } else {
294
0
    const char *tmp = get_absdir(pc);
295
296
0
    if (!tmp)
297
0
      return NULL;
298
0
    xstrncpy(buf, tmp, bufsz);
299
0
  }
300
301
0
  return buf;
302
0
}
303
304
305
int ul_path_access(struct path_cxt *pc, int mode, const char *path)
306
0
{
307
0
  int rc;
308
309
0
  if (!path)
310
0
    return -EINVAL;
311
0
  if (!pc) {
312
0
    rc = access(path, mode);
313
0
    DBG(CXT, ul_debug("access '%s' [no context, rc=%d]", path, rc));
314
0
  } else {
315
0
    int dir = ul_path_get_dirfd(pc);
316
0
    if (dir < 0)
317
0
      return dir;
318
0
    if (*path == '/')
319
0
      path++;
320
321
0
    rc = faccessat(dir, path, mode, 0);
322
323
0
    if (rc && errno == ENOENT
324
0
        && pc->redirect_on_enoent
325
0
        && pc->redirect_on_enoent(pc, path, &dir) == 0)
326
0
      rc = faccessat(dir, path, mode, 0);
327
328
0
    DBG(CXT, ul_debugobj(pc, "access: '%s' [rc=%d]", path, rc));
329
0
  }
330
0
  return rc;
331
0
}
332
333
int ul_path_accessf(struct path_cxt *pc, int mode, const char *path, ...)
334
0
{
335
0
  va_list ap;
336
0
  const char *p;
337
338
0
  va_start(ap, path);
339
0
  p = ul_path_mkpath(pc, path, ap);
340
0
  va_end(ap);
341
342
0
  return !p ? -errno : ul_path_access(pc, mode, p);
343
0
}
344
345
/*
346
 * If @path is NULL, then stat() is called for the directory itself addressed by @pc.
347
 */
348
int ul_path_stat(struct path_cxt *pc, struct stat *sb, int flags, const char *path)
349
0
{
350
0
  int rc;
351
352
0
  if (!pc) {
353
0
    rc = path ? stat(path, sb) : -EINVAL;
354
0
    DBG(CXT, ul_debug("stat '%s' [no context, rc=%d]", path, rc));
355
0
  } else {
356
0
    int dir = ul_path_get_dirfd(pc);
357
0
    if (dir < 0)
358
0
      return dir;
359
0
    if (path) {
360
0
      if  (*path == '/')
361
0
        path++;
362
0
      rc = fstatat(dir, path, sb, flags);
363
364
0
    } else
365
0
      rc = fstat(dir, sb); /* dir itself */
366
367
0
    if (rc && errno == ENOENT
368
0
        && path
369
0
        && pc->redirect_on_enoent
370
0
        && pc->redirect_on_enoent(pc, path, &dir) == 0)
371
0
      rc = fstatat(dir, path, sb, 0);
372
373
0
    DBG(CXT, ul_debugobj(pc, "stat '%s' [rc=%d]", path, rc));
374
0
  }
375
0
  return rc;
376
0
}
377
378
int ul_path_vstatf(struct path_cxt *pc, struct stat *sb, int flags, const char *path, va_list ap)
379
0
{
380
0
  const char *p = ul_path_mkpath(pc, path, ap);
381
382
0
  return !p ? -errno : ul_path_stat(pc, sb, flags, p);
383
0
}
384
385
int ul_path_statf(struct path_cxt *pc, struct stat *sb, int flags, const char *path, ...)
386
0
{
387
0
  va_list ap;
388
0
  int rc;
389
390
0
  va_start(ap, path);
391
0
  rc = ul_path_vstatf(pc, sb, flags, path, ap);
392
0
  va_end(ap);
393
394
0
  return rc;
395
0
}
396
#ifdef HAVE_STATX
397
/*
398
* This function follows the semantics of statx(). To call statx() for the directory
399
* itself addressed by @pc, use an empty string and the AT_EMPTY_PATH @flag.
400
*/
401
int ul_path_statx(struct path_cxt *pc, struct statx *stx, int flags, unsigned int mask,
402
      const char *path)
403
0
{
404
0
  int rc;
405
406
0
  if (!path)
407
0
    return -EINVAL;
408
0
  if (!pc)
409
0
    rc = path ? statx(AT_FDCWD, path, flags, mask, stx) : - EINVAL;
410
0
  else {
411
0
    int dir = ul_path_get_dirfd(pc);
412
0
    if (dir < 0)
413
0
      return dir;
414
0
    if (*path == '/')
415
0
      path++;
416
417
0
    rc = statx(dir, path, flags, mask, stx);
418
0
  }
419
420
0
  return rc;
421
0
}
422
#else
423
int ul_path_statx(struct path_cxt *pc __attribute__((__unused__)),
424
      struct statx *stx __attribute__((__unused__)),
425
      int flags __attribute__((__unused__)),
426
      unsigned int mask __attribute__((__unused__)),
427
      const char *path __attribute__((__unused__)))
428
{
429
  errno = ENOSYS;
430
  return -1;
431
}
432
#endif /* HAVE_STATX */
433
434
int ul_path_vstatxf(struct path_cxt *pc, struct statx *stx, int flags, unsigned int mask,
435
        const char *path, va_list ap)
436
0
{
437
0
  const char *p = ul_path_mkpath(pc, path, ap);
438
439
0
  return !p ? -errno : ul_path_statx(pc, stx, flags, mask, p);
440
0
}
441
442
int ul_path_statxf(struct path_cxt *pc, struct statx *stx, int flags, unsigned int mask,
443
       const char *path, ...)
444
0
{
445
0
  va_list ap;
446
0
  int rc;
447
448
0
  va_start(ap, path);
449
0
  rc = ul_path_vstatxf(pc, stx, flags, mask, path, ap);
450
0
  va_end(ap);
451
452
0
  return rc;
453
0
}
454
455
int ul_path_open(struct path_cxt *pc, int flags, const char *path)
456
0
{
457
0
  int fd;
458
459
0
  if (!path)
460
0
    return -EINVAL;
461
0
  if (!pc) {
462
0
    fd = open(path, flags);
463
0
    DBG(CXT, ul_debug("opening '%s' [no context]", path));
464
0
  } else {
465
0
    int fdx;
466
0
    int dir = ul_path_get_dirfd(pc);
467
0
    if (dir < 0)
468
0
      return dir;
469
470
0
    if (*path == '/')
471
0
      path++;
472
473
0
    fdx = fd = openat(dir, path, flags);
474
475
0
    if (fd < 0 && errno == ENOENT
476
0
        && pc->redirect_on_enoent
477
0
        && pc->redirect_on_enoent(pc, path, &dir) == 0)
478
0
      fd = openat(dir, path, flags);
479
480
0
    DBG(CXT, ul_debugobj(pc, "opening '%s'%s", path, fdx != fd ? " [redirected]" : ""));
481
0
  }
482
0
  return fd;
483
0
}
484
485
int ul_path_vopenf(struct path_cxt *pc, int flags, const char *path, va_list ap)
486
0
{
487
0
  const char *p = ul_path_mkpath(pc, path, ap);
488
489
0
  return !p ? -errno : ul_path_open(pc, flags, p);
490
0
}
491
492
int ul_path_openf(struct path_cxt *pc, int flags, const char *path, ...)
493
0
{
494
0
  va_list ap;
495
0
  int rc;
496
497
0
  va_start(ap, path);
498
0
  rc = ul_path_vopenf(pc, flags, path, ap);
499
0
  va_end(ap);
500
501
0
  return rc;
502
0
}
503
504
/*
505
 * Maybe stupid, but good enough ;-)
506
 */
507
static int mode2flags(const char *mode)
508
0
{
509
0
  int flags = 0;
510
0
  const char *p;
511
512
0
  for (p = mode; p && *p; p++) {
513
0
    if (*p == 'r' && *(p + 1) == '+')
514
0
      flags |= O_RDWR;
515
0
    else if (*p == 'r')
516
0
      flags |= O_RDONLY;
517
518
0
    else if (*p == 'w' && *(p + 1) == '+')
519
0
      flags |= O_RDWR | O_TRUNC;
520
0
    else if (*p == 'w')
521
0
      flags |= O_WRONLY | O_TRUNC;
522
523
0
    else if (*p == 'a' && *(p + 1) == '+')
524
0
      flags |= O_RDWR | O_APPEND;
525
0
    else if (*p == 'a')
526
0
      flags |= O_WRONLY | O_APPEND;
527
0
#ifdef O_CLOEXEC
528
0
    else if (*p == *UL_CLOEXECSTR)
529
0
      flags |= O_CLOEXEC;
530
0
#endif
531
0
  }
532
533
0
  return flags;
534
0
}
535
536
FILE *ul_path_fopen(struct path_cxt *pc, const char *mode, const char *path)
537
0
{
538
0
  int flags = mode2flags(mode);
539
0
  int fd = ul_path_open(pc, flags, path);
540
541
0
  if (fd < 0)
542
0
    return NULL;
543
544
0
  return fdopen(fd, mode);
545
0
}
546
547
548
FILE *ul_path_vfopenf(struct path_cxt *pc, const char *mode, const char *path, va_list ap)
549
0
{
550
0
  const char *p = ul_path_mkpath(pc, path, ap);
551
552
0
  return !p ? NULL : ul_path_fopen(pc, mode, p);
553
0
}
554
555
FILE *ul_path_fopenf(struct path_cxt *pc, const char *mode, const char *path, ...)
556
0
{
557
0
  FILE *f;
558
0
  va_list ap;
559
560
0
  va_start(ap, path);
561
0
  f = ul_path_vfopenf(pc, mode, path, ap);
562
0
  va_end(ap);
563
564
0
  return f;
565
0
}
566
567
/*
568
 * Open directory @path in read-only mode. If the path is NULL then duplicate FD
569
 * to the directory addressed by @pc.
570
 */
571
DIR *ul_path_opendir(struct path_cxt *pc, const char *path)
572
0
{
573
0
  DIR *dir;
574
0
  int fd = -1;
575
576
0
  if (path)
577
0
    fd = ul_path_open(pc, O_RDONLY|O_CLOEXEC, path);
578
0
  else if (pc->dir_path) {
579
0
    int dirfd;
580
581
0
    DBG(CXT, ul_debugobj(pc, "duplicate dir path"));
582
0
    dirfd = ul_path_get_dirfd(pc);
583
0
    if (dirfd >= 0)
584
0
      fd = dup_fd_cloexec(dirfd, STDERR_FILENO + 1);
585
0
  }
586
587
0
  if (fd < 0)
588
0
    return NULL;
589
590
0
  dir = fdopendir(fd);
591
0
  if (!dir) {
592
0
    close(fd);
593
0
    return NULL;
594
0
  }
595
0
  if (!path)
596
0
     rewinddir(dir);
597
0
  return dir;
598
0
}
599
600
601
/*
602
 * Open directory @path in read-only mode. If the path is NULL then duplicate FD
603
 * to the directory addressed by @pc.
604
 */
605
DIR *ul_path_vopendirf(struct path_cxt *pc, const char *path, va_list ap)
606
0
{
607
0
  const char *p = ul_path_mkpath(pc, path, ap);
608
609
0
  return !p ? NULL : ul_path_opendir(pc, p);
610
0
}
611
612
/*
613
 * Open directory @path in read-only mode. If the path is NULL then duplicate FD
614
 * to the directory addressed by @pc.
615
 */
616
DIR *ul_path_opendirf(struct path_cxt *pc, const char *path, ...)
617
0
{
618
0
  va_list ap;
619
0
  DIR *dir;
620
621
0
  va_start(ap, path);
622
0
  dir = ul_path_vopendirf(pc, path, ap);
623
0
  va_end(ap);
624
625
0
  return dir;
626
0
}
627
628
/*
629
 * If @path is NULL then readlink is called on @pc directory.
630
 */
631
ssize_t ul_path_readlink(struct path_cxt *pc, char *buf, size_t bufsiz, const char *path)
632
0
{
633
0
  int dirfd;
634
0
  ssize_t ssz;
635
636
0
  if (!path) {
637
0
    const char *p = get_absdir(pc);
638
0
    if (!p)
639
0
      return -errno;
640
0
    ssz = readlink(p, buf, bufsiz - 1);
641
0
  } else {
642
0
    dirfd = ul_path_get_dirfd(pc);
643
0
    if (dirfd < 0)
644
0
      return dirfd;
645
646
0
    if (*path == '/')
647
0
      path++;
648
649
0
    ssz = readlinkat(dirfd, path, buf, bufsiz - 1);
650
0
  }
651
652
0
  if (ssz >= 0)
653
0
    buf[ssz] = '\0';
654
0
  return ssz;
655
0
}
656
657
/*
658
 * If @path is NULL then readlink is called on @pc directory.
659
 */
660
ssize_t ul_path_readlinkf(struct path_cxt *pc, char *buf, size_t bufsiz, const char *path, ...)
661
0
{
662
0
  const char *p;
663
0
  va_list ap;
664
665
0
  va_start(ap, path);
666
0
  p = ul_path_mkpath(pc, path, ap);
667
0
  va_end(ap);
668
669
0
  return !p ? -errno : ul_path_readlink(pc, buf, bufsiz, p);
670
0
}
671
672
int ul_path_read(struct path_cxt *pc, char *buf, size_t len, const char *path)
673
0
{
674
0
  int rc, errsv;
675
0
  int fd;
676
677
0
  fd = ul_path_open(pc, O_RDONLY|O_CLOEXEC, path);
678
0
  if (fd < 0)
679
0
    return -errno;
680
681
0
  DBG(CXT, ul_debug(" reading '%s'", path));
682
0
  rc = read_all(fd, buf, len);
683
684
0
  errsv = errno;
685
0
  close(fd);
686
0
  errno = errsv;
687
0
  return rc;
688
0
}
689
690
int ul_path_vreadf(struct path_cxt *pc, char *buf, size_t len, const char *path, va_list ap)
691
0
{
692
0
  const char *p = ul_path_mkpath(pc, path, ap);
693
694
0
  return !p ? -errno : ul_path_read(pc, buf, len, p);
695
0
}
696
697
int ul_path_readf(struct path_cxt *pc, char *buf, size_t len, const char *path, ...)
698
0
{
699
0
  va_list ap;
700
0
  int rc;
701
702
0
  va_start(ap, path);
703
0
  rc = ul_path_vreadf(pc, buf, len, path, ap);
704
0
  va_end(ap);
705
706
0
  return rc;
707
0
}
708
709
710
/*
711
 * Returns newly allocated buffer with data from file. Maximal size is BUFSIZ
712
 * (send patch if you need something bigger;-)
713
 *
714
 * Returns size of the string without \0, nothing is allocated if returns <= 0.
715
 */
716
int ul_path_read_string(struct path_cxt *pc, char **str, const char *path)
717
0
{
718
0
  char buf[BUFSIZ];
719
0
  int rc;
720
721
0
  if (!str)
722
0
    return -EINVAL;
723
724
0
  *str = NULL;
725
726
0
  rc = ul_path_read_buffer(pc, buf, sizeof(buf), path);
727
0
  if (rc < 0)
728
0
    return rc;
729
730
0
  *str = strdup(buf);
731
0
  if (!*str)
732
0
    rc = -ENOMEM;
733
734
0
  return rc;
735
0
}
736
737
int ul_path_readf_string(struct path_cxt *pc, char **str, const char *path, ...)
738
0
{
739
0
  const char *p;
740
0
  va_list ap;
741
742
0
  va_start(ap, path);
743
0
  p = ul_path_mkpath(pc, path, ap);
744
0
  va_end(ap);
745
746
0
  return !p ? -errno : ul_path_read_string(pc, str, p);
747
0
}
748
749
int ul_path_read_buffer(struct path_cxt *pc, char *buf, size_t bufsz, const char *path)
750
0
{
751
0
  int rc = ul_path_read(pc, buf, bufsz - 1, path);
752
753
0
  if (rc == 0)
754
0
    buf[0] = '\0';
755
756
0
  else if (rc > 0) {
757
    /* Remove trailing newline (usual in sysfs) */
758
0
    if (*(buf + rc - 1) == '\n')
759
0
      buf[--rc] = '\0';
760
0
    else
761
0
      buf[rc] = '\0';
762
0
  }
763
764
0
  return rc;
765
0
}
766
767
int ul_path_vreadf_buffer(struct path_cxt *pc, char *buf, size_t bufsz, const char *path, va_list ap)
768
0
{
769
0
  const char *p;
770
771
0
  p = ul_path_mkpath(pc, path, ap);
772
773
0
  return !p ? -errno : ul_path_read_buffer(pc, buf, bufsz, p);
774
0
}
775
776
int ul_path_readf_buffer(struct path_cxt *pc, char *buf, size_t bufsz, const char *path, ...)
777
0
{
778
0
  va_list ap;
779
0
  int rc;
780
781
0
  va_start(ap, path);
782
0
  rc = ul_path_vreadf_buffer(pc, buf, bufsz, path, ap);
783
0
  va_end(ap);
784
785
0
  return rc;
786
0
}
787
788
int ul_path_scanf(struct path_cxt *pc, const char *path, const char *fmt, ...)
789
0
{
790
0
  FILE *f;
791
0
  va_list fmt_ap;
792
0
  int rc;
793
794
0
  f = ul_path_fopen(pc, "r" UL_CLOEXECSTR, path);
795
0
  if (!f)
796
0
    return -EINVAL;
797
798
0
  DBG(CXT, ul_debug(" fscanf [%s] '%s'", fmt, path));
799
800
0
  va_start(fmt_ap, fmt);
801
0
  rc = vfscanf(f, fmt, fmt_ap);
802
0
  va_end(fmt_ap);
803
804
0
  fclose(f);
805
0
  return rc;
806
0
}
807
808
int ul_path_scanff(struct path_cxt *pc, const char *path, va_list ap, const char *fmt, ...)
809
0
{
810
0
  FILE *f;
811
0
  va_list fmt_ap;
812
0
  int rc;
813
814
0
  f = ul_path_vfopenf(pc, "r" UL_CLOEXECSTR, path, ap);
815
0
  if (!f)
816
0
    return -EINVAL;
817
818
0
  va_start(fmt_ap, fmt);
819
0
  rc = vfscanf(f, fmt, fmt_ap);
820
0
  va_end(fmt_ap);
821
822
0
  fclose(f);
823
0
  return rc;
824
0
}
825
826
827
int ul_path_read_s64(struct path_cxt *pc, int64_t *res, const char *path)
828
0
{
829
0
  int64_t x = 0;
830
0
  int rc;
831
832
0
  rc = ul_path_scanf(pc, path, "%"SCNd64, &x);
833
0
  if (rc != 1)
834
0
    return -1;
835
0
  if (res)
836
0
    *res = x;
837
0
  return 0;
838
0
}
839
840
int ul_path_readf_s64(struct path_cxt *pc, int64_t *res, const char *path, ...)
841
0
{
842
0
  const char *p;
843
0
  va_list ap;
844
845
0
  va_start(ap, path);
846
0
  p = ul_path_mkpath(pc, path, ap);
847
0
  va_end(ap);
848
849
0
  return !p ? -errno : ul_path_read_s64(pc, res, p);
850
0
}
851
852
int ul_path_read_u64(struct path_cxt *pc, uint64_t *res, const char *path)
853
0
{
854
0
  uint64_t x = 0;
855
0
  int rc;
856
857
0
  rc = ul_path_scanf(pc, path, "%"SCNu64, &x);
858
0
  if (rc != 1)
859
0
    return -1;
860
0
  if (res)
861
0
    *res = x;
862
0
  return 0;
863
0
}
864
865
int ul_path_readf_u64(struct path_cxt *pc, uint64_t *res, const char *path, ...)
866
0
{
867
0
  const char *p;
868
0
  va_list ap;
869
870
0
  va_start(ap, path);
871
0
  p = ul_path_mkpath(pc, path, ap);
872
0
  va_end(ap);
873
874
0
  return !p ? -errno : ul_path_read_u64(pc, res, p);
875
0
}
876
877
int ul_path_read_s32(struct path_cxt *pc, int *res, const char *path)
878
0
{
879
0
  int rc, x = 0;
880
881
0
  rc = ul_path_scanf(pc, path, "%d", &x);
882
0
  if (rc != 1)
883
0
    return -1;
884
0
  if (res)
885
0
    *res = x;
886
0
  return 0;
887
0
}
888
889
int ul_path_readf_s32(struct path_cxt *pc, int *res, const char *path, ...)
890
0
{
891
0
  const char *p;
892
0
  va_list ap;
893
894
0
  va_start(ap, path);
895
0
  p = ul_path_mkpath(pc, path, ap);
896
0
  va_end(ap);
897
898
0
  return !p ? -errno : ul_path_read_s32(pc, res, p);
899
0
}
900
901
int ul_path_read_u32(struct path_cxt *pc, unsigned int *res, const char *path)
902
0
{
903
0
  int rc;
904
0
  unsigned int x = 0;
905
906
0
  rc = ul_path_scanf(pc, path, "%u", &x);
907
0
  if (rc != 1)
908
0
    return -1;
909
0
  if (res)
910
0
    *res = x;
911
0
  return 0;
912
0
}
913
914
int ul_path_readf_u32(struct path_cxt *pc, unsigned int *res, const char *path, ...)
915
0
{
916
0
  const char *p;
917
0
  va_list ap;
918
919
0
  va_start(ap, path);
920
0
  p = ul_path_mkpath(pc, path, ap);
921
0
  va_end(ap);
922
923
0
  return !p ? -errno : ul_path_read_u32(pc, res, p);
924
0
}
925
926
int ul_path_read_majmin(struct path_cxt *pc, dev_t *res, const char *path)
927
0
{
928
0
  int rc, maj = 0, min = 0;
929
930
0
  rc = ul_path_scanf(pc, path, "%d:%d", &maj, &min);
931
0
  if (rc != 2)
932
0
    return -1;
933
0
  if (res)
934
0
    *res = makedev(maj, min);
935
0
  return 0;
936
0
}
937
938
int ul_path_readf_majmin(struct path_cxt *pc, dev_t *res, const char *path, ...)
939
0
{
940
0
  const char *p;
941
0
  va_list ap;
942
943
0
  va_start(ap, path);
944
0
  p = ul_path_mkpath(pc, path, ap);
945
0
  va_end(ap);
946
947
0
  return !p ? -errno : ul_path_read_majmin(pc, res, p);
948
0
}
949
950
int ul_path_write_string(struct path_cxt *pc, const char *str, const char *path)
951
0
{
952
0
  int rc, errsv;
953
0
  int fd;
954
955
0
  fd = ul_path_open(pc, O_WRONLY|O_CLOEXEC, path);
956
0
  if (fd < 0)
957
0
    return -errno;
958
959
0
  rc = write_all(fd, str, strlen(str));
960
961
0
  errsv = errno;
962
0
  close(fd);
963
0
  errno = errsv;
964
0
  return rc;
965
0
}
966
967
int ul_path_writef_string(struct path_cxt *pc, const char *str, const char *path, ...)
968
0
{
969
0
  const char *p;
970
0
  va_list ap;
971
972
0
  va_start(ap, path);
973
0
  p = ul_path_mkpath(pc, path, ap);
974
0
  va_end(ap);
975
976
0
  return !p ? -errno : ul_path_write_string(pc, str, p);
977
0
}
978
979
int ul_path_write_s64(struct path_cxt *pc, int64_t num, const char *path)
980
0
{
981
0
  char buf[sizeof(stringify_value(LLONG_MAX))];
982
0
  int rc, errsv;
983
0
  int fd, len;
984
985
0
  fd = ul_path_open(pc, O_WRONLY|O_CLOEXEC, path);
986
0
  if (fd < 0)
987
0
    return -errno;
988
989
0
  len = snprintf(buf, sizeof(buf), "%" PRId64, num);
990
0
  if (len < 0 || (size_t) len >= sizeof(buf))
991
0
    rc = len < 0 ? -errno : -E2BIG;
992
0
  else
993
0
    rc = write_all(fd, buf, len);
994
995
0
  errsv = errno;
996
0
  close(fd);
997
0
  errno = errsv;
998
0
  return rc;
999
0
}
1000
1001
int ul_path_write_u64(struct path_cxt *pc, uint64_t num, const char *path)
1002
0
{
1003
0
  char buf[sizeof(stringify_value(ULLONG_MAX))];
1004
0
  int rc, errsv;
1005
0
  int fd, len;
1006
1007
0
  fd = ul_path_open(pc, O_WRONLY|O_CLOEXEC, path);
1008
0
  if (fd < 0)
1009
0
    return -errno;
1010
1011
0
  len = snprintf(buf, sizeof(buf), "%" PRIu64, num);
1012
0
  if (len < 0 || (size_t) len >= sizeof(buf))
1013
0
    rc = len < 0 ? -errno : -E2BIG;
1014
0
  else
1015
0
    rc = write_all(fd, buf, len);
1016
1017
0
  errsv = errno;
1018
0
  close(fd);
1019
0
  errno = errsv;
1020
0
  return rc;
1021
0
}
1022
1023
int ul_path_writef_u64(struct path_cxt *pc, uint64_t num, const char *path, ...)
1024
0
{
1025
0
  const char *p;
1026
0
  va_list ap;
1027
1028
0
  va_start(ap, path);
1029
0
  p = ul_path_mkpath(pc, path, ap);
1030
0
  va_end(ap);
1031
1032
0
  return !p ? -errno : ul_path_write_u64(pc, num, p);
1033
1034
0
}
1035
1036
int ul_path_count_dirents(struct path_cxt *pc, const char *path)
1037
0
{
1038
0
  DIR *dir;
1039
0
  int r = 0;
1040
1041
0
  dir = ul_path_opendir(pc, path);
1042
0
  if (!dir)
1043
0
    return 0;
1044
1045
0
  while (xreaddir(dir)) r++;
1046
1047
0
  closedir(dir);
1048
0
  return r;
1049
0
}
1050
1051
int ul_path_countf_dirents(struct path_cxt *pc, const char *path, ...)
1052
0
{
1053
0
  const char *p;
1054
0
  va_list ap;
1055
1056
0
  va_start(ap, path);
1057
0
  p = ul_path_mkpath(pc, path, ap);
1058
0
  va_end(ap);
1059
1060
0
  return !p ? -errno : ul_path_count_dirents(pc, p);
1061
0
}
1062
1063
/* first call (when @sub is NULL) opens the directory, last call closes the directory */
1064
int ul_path_next_dirent(struct path_cxt *pc, DIR **sub, const char *dirname, struct dirent **d)
1065
0
{
1066
0
  if (!pc || !sub || !d)
1067
0
    return -EINVAL;
1068
1069
0
  if (!*sub) {
1070
0
    *sub = ul_path_opendir(pc, dirname);
1071
0
    if (!*sub)
1072
0
      return -errno;
1073
0
  }
1074
1075
0
  *d = xreaddir(*sub);
1076
0
  if (*d)
1077
0
    return 0;
1078
1079
0
  closedir(*sub);
1080
0
  *sub = NULL;
1081
0
  return 1;
1082
0
}
1083
1084
#ifdef HAVE_CPU_SET_T
1085
static int ul_path_cpuparse(struct path_cxt *pc, cpu_set_t **set, int maxcpus, int islist, const char *path, va_list ap)
1086
0
{
1087
0
  size_t setsize, len = maxcpus * 7;
1088
0
  char *buf;
1089
0
  int rc;
1090
1091
0
  *set = NULL;
1092
1093
0
  buf = malloc(len);
1094
0
  if (!buf)
1095
0
    return -ENOMEM;
1096
1097
0
  rc = ul_path_vreadf_buffer(pc, buf, len, path, ap);
1098
0
  if (rc < 0)
1099
0
    goto out;
1100
1101
0
  *set = cpuset_alloc(maxcpus, &setsize, NULL);
1102
0
  if (!*set) {
1103
0
    rc = -EINVAL;
1104
0
    goto out;
1105
0
  }
1106
1107
0
  if (islist) {
1108
0
    if (cpulist_parse(buf, *set, setsize, 0)) {
1109
0
      errno = EINVAL;
1110
0
      rc = -errno;
1111
0
      goto out;
1112
0
    }
1113
0
  } else {
1114
0
    if (cpumask_parse(buf, *set, setsize)) {
1115
0
      errno = EINVAL;
1116
0
      rc = -errno;
1117
0
      goto out;
1118
0
    }
1119
0
  }
1120
0
  rc = 0;
1121
1122
0
out:
1123
0
  if (rc) {
1124
0
    cpuset_free(*set);
1125
0
    *set = NULL;
1126
0
  }
1127
0
  free(buf);
1128
0
  return rc;
1129
0
}
1130
1131
int ul_path_readf_cpuset(struct path_cxt *pc, cpu_set_t **set, int maxcpus, const char *path, ...)
1132
0
{
1133
0
  va_list ap;
1134
0
  int rc = 0;
1135
1136
0
  va_start(ap, path);
1137
0
  rc = ul_path_cpuparse(pc, set, maxcpus, 0, path, ap);
1138
0
  va_end(ap);
1139
1140
0
  return rc;
1141
0
}
1142
1143
int ul_path_readf_cpulist(struct path_cxt *pc, cpu_set_t **set, int maxcpus, const char *path, ...)
1144
0
{
1145
0
  va_list ap;
1146
0
  int rc = 0;
1147
1148
0
  va_start(ap, path);
1149
0
  rc = ul_path_cpuparse(pc, set, maxcpus, 1, path, ap);
1150
0
  va_end(ap);
1151
1152
0
  return rc;
1153
0
}
1154
1155
#endif /* HAVE_CPU_SET_T */
1156
1157
1158
#ifdef TEST_PROGRAM_PATH
1159
#include <getopt.h>
1160
1161
static void __attribute__((__noreturn__)) usage(void)
1162
{
1163
  fprintf(stdout, " %s [options] <dir> <command>\n\n", program_invocation_short_name);
1164
  fputs(" -p, --prefix <dir>      redirect hardcoded paths to <dir>\n", stdout);
1165
1166
  fputs(" Commands:\n", stdout);
1167
  fputs(" read-u64 <file>            read uint64_t from file\n", stdout);
1168
  fputs(" read-s64 <file>            read  int64_t from file\n", stdout);
1169
  fputs(" read-u32 <file>            read uint32_t from file\n", stdout);
1170
  fputs(" read-s32 <file>            read  int32_t from file\n", stdout);
1171
  fputs(" read-string <file>         read string  from file\n", stdout);
1172
  fputs(" read-majmin <file>         read devno from file\n", stdout);
1173
  fputs(" read-link <file>           read symlink\n", stdout);
1174
  fputs(" write-string <file> <str>  write string from file\n", stdout);
1175
  fputs(" write-u64 <file> <str>     write uint64_t from file\n", stdout);
1176
1177
  exit(EXIT_SUCCESS);
1178
}
1179
1180
int main(int argc, char *argv[])
1181
{
1182
  int c;
1183
  const char *prefix = NULL, *dir, *file, *command;
1184
  struct path_cxt *pc = NULL;
1185
1186
  static const struct option longopts[] = {
1187
    { "prefix", 1, NULL, 'p' },
1188
    { "help",       0, NULL, 'h' },
1189
    { NULL, 0, NULL, 0 },
1190
  };
1191
1192
  while((c = getopt_long(argc, argv, "p:h", longopts, NULL)) != -1) {
1193
    switch(c) {
1194
    case 'p':
1195
      prefix = optarg;
1196
      break;
1197
    case 'h':
1198
      usage();
1199
      break;
1200
    default:
1201
      err(EXIT_FAILURE, "try --help");
1202
    }
1203
  }
1204
1205
  if (optind == argc)
1206
    errx(EXIT_FAILURE, "<dir> not defined");
1207
  dir = argv[optind++];
1208
1209
  ul_path_init_debug();
1210
1211
  pc = ul_new_path("%s", dir);
1212
  if (!pc)
1213
    err(EXIT_FAILURE, "failed to initialize path context");
1214
  if (prefix)
1215
    ul_path_set_prefix(pc, prefix);
1216
1217
  if (optind == argc)
1218
    errx(EXIT_FAILURE, "<command> not defined");
1219
  command = argv[optind++];
1220
1221
  if (strcmp(command, "read-u32") == 0) {
1222
    uint32_t res;
1223
1224
    if (optind == argc)
1225
      errx(EXIT_FAILURE, "<file> not defined");
1226
    file = argv[optind++];
1227
1228
    if (ul_path_read_u32(pc, &res, file) != 0)
1229
      err(EXIT_FAILURE, "read u64 failed");
1230
    printf("read:  %s: %u\n", file, res);
1231
1232
    if (ul_path_readf_u32(pc, &res, "%s", file) != 0)
1233
      err(EXIT_FAILURE, "readf u64 failed");
1234
    printf("readf: %s: %u\n", file, res);
1235
1236
  } else if (strcmp(command, "read-s32") == 0) {
1237
    int32_t res;
1238
1239
    if (optind == argc)
1240
      errx(EXIT_FAILURE, "<file> not defined");
1241
    file = argv[optind++];
1242
1243
    if (ul_path_read_s32(pc, &res, file) != 0)
1244
      err(EXIT_FAILURE, "read u64 failed");
1245
    printf("read:  %s: %d\n", file, res);
1246
1247
    if (ul_path_readf_s32(pc, &res, "%s", file) != 0)
1248
      err(EXIT_FAILURE, "readf u64 failed");
1249
    printf("readf: %s: %d\n", file, res);
1250
1251
  } else if (strcmp(command, "read-u64") == 0) {
1252
    uint64_t res;
1253
1254
    if (optind == argc)
1255
      errx(EXIT_FAILURE, "<file> not defined");
1256
    file = argv[optind++];
1257
1258
    if (ul_path_read_u64(pc, &res, file) != 0)
1259
      err(EXIT_FAILURE, "read u64 failed");
1260
    printf("read:  %s: %" PRIu64 "\n", file, res);
1261
1262
    if (ul_path_readf_u64(pc, &res, "%s", file) != 0)
1263
      err(EXIT_FAILURE, "readf u64 failed");
1264
    printf("readf: %s: %" PRIu64 "\n", file, res);
1265
1266
  } else if (strcmp(command, "read-s64") == 0) {
1267
    int64_t res;
1268
1269
    if (optind == argc)
1270
      errx(EXIT_FAILURE, "<file> not defined");
1271
    file = argv[optind++];
1272
1273
    if (ul_path_read_s64(pc, &res, file) != 0)
1274
      err(EXIT_FAILURE, "read u64 failed");
1275
    printf("read:  %s: %" PRIu64 "\n", file, res);
1276
1277
    if (ul_path_readf_s64(pc, &res, "%s", file) != 0)
1278
      err(EXIT_FAILURE, "readf u64 failed");
1279
    printf("readf: %s: %" PRIu64 "\n", file, res);
1280
1281
  } else if (strcmp(command, "read-majmin") == 0) {
1282
    dev_t res;
1283
1284
    if (optind == argc)
1285
      errx(EXIT_FAILURE, "<file> not defined");
1286
    file = argv[optind++];
1287
1288
    if (ul_path_read_majmin(pc, &res, file) != 0)
1289
      err(EXIT_FAILURE, "read maj:min failed");
1290
    printf("read:  %s: %d\n", file, (int) res);
1291
1292
    if (ul_path_readf_majmin(pc, &res, "%s", file) != 0)
1293
      err(EXIT_FAILURE, "readf maj:min failed");
1294
    printf("readf: %s: %d\n", file, (int) res);
1295
1296
  } else if (strcmp(command, "read-string") == 0) {
1297
    char *res;
1298
1299
    if (optind == argc)
1300
      errx(EXIT_FAILURE, "<file> not defined");
1301
    file = argv[optind++];
1302
1303
    if (ul_path_read_string(pc, &res, file) <= 0)
1304
      err(EXIT_FAILURE, "read string failed");
1305
    printf("read:  %s: %s\n", file, res);
1306
1307
    if (ul_path_readf_string(pc, &res, "%s", file) <= 0)
1308
      err(EXIT_FAILURE, "readf string failed");
1309
    printf("readf: %s: %s\n", file, res);
1310
1311
  } else if (strcmp(command, "read-link") == 0) {
1312
    char res[PATH_MAX];
1313
1314
    if (optind == argc)
1315
      errx(EXIT_FAILURE, "<file> not defined");
1316
    file = argv[optind++];
1317
1318
    if (ul_path_readlink(pc, res, sizeof(res), file) < 0)
1319
      err(EXIT_FAILURE, "read symlink failed");
1320
    printf("read:  %s: %s\n", file, res);
1321
1322
    if (ul_path_readlinkf(pc, res, sizeof(res), "%s", file) < 0)
1323
      err(EXIT_FAILURE, "readf symlink failed");
1324
    printf("readf: %s: %s\n", file, res);
1325
1326
  } else if (strcmp(command, "write-string") == 0) {
1327
    char *str;
1328
1329
    if (optind + 1 == argc)
1330
      errx(EXIT_FAILURE, "<file> <string> not defined");
1331
    file = argv[optind++];
1332
    str = argv[optind++];
1333
1334
    if (ul_path_write_string(pc, str, file) != 0)
1335
      err(EXIT_FAILURE, "write string failed");
1336
    if (ul_path_writef_string(pc, str, "%s", file) != 0)
1337
      err(EXIT_FAILURE, "writef string failed");
1338
1339
  } else if (strcmp(command, "write-u64") == 0) {
1340
    uint64_t num;
1341
1342
    if (optind + 1 == argc)
1343
      errx(EXIT_FAILURE, "<file> <num> not defined");
1344
    file = argv[optind++];
1345
    num = strtoumax(argv[optind++], NULL, 0);
1346
1347
    if (ul_path_write_u64(pc, num, file) != 0)
1348
      err(EXIT_FAILURE, "write u64 failed");
1349
    if (ul_path_writef_u64(pc, num, "%s", file) != 0)
1350
      err(EXIT_FAILURE, "writef u64 failed");
1351
  }
1352
1353
  ul_unref_path(pc);
1354
  return EXIT_SUCCESS;
1355
}
1356
#endif /* TEST_PROGRAM_PATH */