Coverage Report

Created: 2026-09-13 06:13

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/util-linux/lib/fileutils.c
Line
Count
Source
1
/*
2
 * This code is in the public domain; do with it what you wish.
3
 *
4
 * Copyright (C) 2012 Sami Kerola <kerolasa@iki.fi>
5
 * Copyright (C) 2012-2024 Karel Zak <kzak@redhat.com>
6
 */
7
#include <stdio.h>
8
#include <stdlib.h>
9
#include <sys/types.h>
10
#include <sys/stat.h>
11
#include <unistd.h>
12
#include <sys/time.h>
13
#include <sys/resource.h>
14
#ifdef HAVE_SYS_SYSCALL_H
15
# include <sys/syscall.h>
16
#endif
17
#include <string.h>
18
#include <sys/wait.h>
19
#include <fcntl.h>
20
#include <errno.h>
21
22
#include "c.h"
23
#include "all-io.h"
24
#include "canonicalize.h"
25
#include "fileutils.h"
26
#include "pathnames.h"
27
#include "strutils.h"
28
29
int mkstemp_cloexec(char *template)
30
0
{
31
0
#ifdef HAVE_MKOSTEMP
32
0
  return mkostemp(template, O_RDWR|O_CREAT|O_EXCL|O_CLOEXEC);
33
#else
34
  int fd, old_flags, errno_save;
35
36
  fd = mkstemp(template);
37
  if (fd < 0)
38
    return fd;
39
40
  old_flags = fcntl(fd, F_GETFD, 0);
41
  if (old_flags < 0)
42
    goto unwind;
43
  if (fcntl(fd, F_SETFD, old_flags | O_CLOEXEC) < 0)
44
    goto unwind;
45
46
  return fd;
47
48
unwind:
49
  errno_save = errno;
50
  unlink(template);
51
  close(fd);
52
  errno = errno_save;
53
54
  return -1;
55
#endif
56
0
}
57
58
/* Create open temporary file in safe way.  Please notice that the
59
 * file permissions are -rw------- by default. */
60
int xmkstemp(char **tmpname, const char *dir, const char *prefix)
61
0
{
62
0
  char *localtmp;
63
0
  const char *tmpenv;
64
0
  mode_t old_mode;
65
0
  int fd, rc;
66
67
  /* Some use cases must be capable of being moved atomically
68
   * with rename(2), which is the reason why dir is here.  */
69
0
  tmpenv = dir ? dir : getenv("TMPDIR");
70
0
  if (!tmpenv)
71
0
    tmpenv = _PATH_TMP;
72
73
0
  rc = asprintf(&localtmp, "%s/%s.XXXXXX", tmpenv, prefix);
74
0
  if (rc < 0)
75
0
    return -1;
76
77
0
  old_mode = umask(077);
78
0
  fd = mkstemp_cloexec(localtmp);
79
0
  umask(old_mode);
80
0
  if (fd == -1) {
81
0
    free(localtmp);
82
0
    localtmp = NULL;
83
0
  }
84
0
  *tmpname = localtmp;
85
0
  return fd;
86
0
}
87
88
#ifdef F_DUPFD_CLOEXEC
89
int dup_fd_cloexec(int oldfd, int lowfd)
90
#else
91
int dup_fd_cloexec(int oldfd, int lowfd  __attribute__((__unused__)))
92
#endif
93
0
{
94
0
  int fd, flags, errno_save;
95
96
0
#ifdef F_DUPFD_CLOEXEC
97
0
  fd = fcntl(oldfd, F_DUPFD_CLOEXEC, lowfd);
98
0
  if (fd >= 0)
99
0
    return fd;
100
0
#endif
101
102
0
  fd = dup(oldfd);
103
0
  if (fd < 0)
104
0
    return fd;
105
106
0
  flags = fcntl(fd, F_GETFD);
107
0
  if (flags < 0)
108
0
    goto unwind;
109
0
  if (fcntl(fd, F_SETFD, flags | FD_CLOEXEC) < 0)
110
0
    goto unwind;
111
112
0
  return fd;
113
114
0
unwind:
115
0
  errno_save = errno;
116
0
  close(fd);
117
0
  errno = errno_save;
118
119
0
  return -1;
120
0
}
121
122
/*
123
 * portable getdtablesize()
124
 */
125
unsigned int get_fd_tabsize(void)
126
0
{
127
0
  int m;
128
129
0
#if defined(HAVE_GETDTABLESIZE)
130
0
  m = getdtablesize();
131
#elif defined(HAVE_GETRLIMIT) && defined(RLIMIT_NOFILE)
132
  struct rlimit rl;
133
134
  getrlimit(RLIMIT_NOFILE, &rl);
135
  m = rl.rlim_cur;
136
#elif defined(HAVE_SYSCONF) && defined(_SC_OPEN_MAX)
137
  m = sysconf(_SC_OPEN_MAX);
138
#else
139
  m = OPEN_MAX;
140
#endif
141
0
  return m;
142
0
}
143
144
void ul_close_all_fds(unsigned int first, unsigned int last)
145
0
{
146
0
  struct dirent *d;
147
0
  DIR *dir;
148
149
0
  dir = opendir(_PATH_PROC_FDDIR);
150
0
  if (dir) {
151
0
    while ((d = xreaddir(dir))) {
152
0
      char *end;
153
0
      unsigned int fd;
154
0
      int dfd;
155
156
0
      errno = 0;
157
0
      fd = strtoul(d->d_name, &end, 10);
158
159
0
      if (errno || end == d->d_name || !end || *end)
160
0
        continue;
161
0
      dfd = dirfd(dir);
162
0
      if (dfd < 0)
163
0
        continue;
164
0
      if ((unsigned int)dfd == fd)
165
0
        continue;
166
0
      if (fd < first || last < fd)
167
0
        continue;
168
0
      close(fd);
169
0
    }
170
0
    closedir(dir);
171
0
  } else {
172
0
    unsigned fd, tbsz = get_fd_tabsize();
173
174
0
    for (fd = 0; fd < tbsz; fd++) {
175
0
      if (first <= fd && fd <= last)
176
0
        close(fd);
177
0
    }
178
0
  }
179
0
}
180
181
/*
182
 * Fork, drop permissions, and call oper() and return result.
183
 */
184
char *ul_restricted_path_oper(const char *path,
185
      int (*oper)(const char *path, char **result, void *data),
186
      void *data)
187
0
{
188
0
  char *result = NULL;
189
0
  int errsv = 0;
190
0
  int pipes[2];
191
0
  ssize_t len;
192
0
  pid_t pid;
193
194
0
  if (!path || !*path)
195
0
    return NULL;
196
197
0
  if (pipe(pipes) != 0)
198
0
    return NULL;
199
  /*
200
   * To accurately assume identity of getuid() we must use setuid()
201
   * but if we do that, we lose ability to reassume euid of 0, so
202
   * we fork to do the check to keep euid intact.
203
   */
204
0
  pid = fork();
205
0
  switch (pid) {
206
0
  case -1:
207
0
    close(pipes[0]);
208
0
    close(pipes[1]);
209
0
    return NULL;     /* fork error */
210
0
  case 0:
211
0
    close(pipes[0]);    /* close unused end */
212
0
    pipes[0] = -1;
213
0
    errno = 0;
214
215
0
    if (drop_permissions() != 0)
216
0
      result = NULL; /* failed */
217
0
    else
218
0
      oper(path, &result, data);
219
220
0
    len = result ? (ssize_t) strlen(result) :
221
0
              errno ? -errno : -EINVAL;
222
223
    /* send length or errno */
224
0
    ul_write_all(pipes[1], (char *) &len, sizeof(len));
225
0
    if (result)
226
0
      ul_write_all(pipes[1], result, len);
227
0
    _exit(0);
228
0
  default:
229
0
    break;
230
0
  }
231
232
0
  close(pipes[1]);    /* close unused end */
233
0
  pipes[1] = -1;
234
235
  /* read size or -errno */
236
0
  if (ul_read_all(pipes[0], (char *) &len, sizeof(len)) != sizeof(len))
237
0
    goto done;
238
0
  if (len < 0) {
239
0
    errsv = -len;
240
0
    goto done;
241
0
  }
242
243
0
  result = malloc(len + 1);
244
0
  if (!result) {
245
0
    errsv = ENOMEM;
246
0
    goto done;
247
0
  }
248
  /* read path */
249
0
  if (ul_read_all(pipes[0], result, len) != len) {
250
0
    errsv = errno;
251
0
    goto done;
252
0
  }
253
0
  result[len] = '\0';
254
0
done:
255
0
  if (errsv) {
256
0
    free(result);
257
0
    result = NULL;
258
0
  }
259
0
  close(pipes[0]);
260
261
  /* We make a best effort to reap child */
262
0
  ignore_result( waitpid(pid, NULL, 0) );
263
264
0
  errno = errsv;
265
0
  return result;
266
267
0
}
268
269
int ul_mkdir_p(const char *path, mode_t mode)
270
0
{
271
0
  char *p, *dir;
272
0
  int rc = 0;
273
274
0
  if (!path || !*path)
275
0
    return -EINVAL;
276
277
0
  dir = p = strdup(path);
278
0
  if (!dir)
279
0
    return -ENOMEM;
280
281
0
  if (*p == '/')
282
0
    p++;
283
284
0
  while (p && *p) {
285
0
    char *e = strchr(p, '/');
286
0
    if (e)
287
0
      *e = '\0';
288
0
    if (*p) {
289
0
      rc = mkdir(dir, mode);
290
0
      if (rc && errno != EEXIST)
291
0
        break;
292
0
      rc = 0;
293
0
    }
294
0
    if (!e)
295
0
      break;
296
0
    *e = '/';
297
0
    p = e + 1;
298
0
  }
299
300
0
  free(dir);
301
0
  return rc;
302
0
}
303
304
/* returns basename and keeps dirname in the @path, if @path is "/" (root)
305
 * then returns empty string */
306
char *stripoff_last_component(char *path)
307
0
{
308
0
  char *p = path ? strrchr(path, '/') : NULL;
309
310
0
  if (!p)
311
0
    return NULL;
312
0
  *p = '\0';
313
0
  return p + 1;
314
0
}
315
316
static int copy_file_simple(int from, int to)
317
0
{
318
0
  ssize_t nr;
319
0
  char buf[BUFSIZ];
320
321
0
  while ((nr = ul_read_all(from, buf, sizeof(buf))) > 0)
322
0
    if (ul_write_all(to, buf, nr) == -1)
323
0
      return UL_COPY_WRITE_ERROR;
324
0
  if (nr < 0)
325
0
    return UL_COPY_READ_ERROR;
326
0
#ifdef HAVE_EXPLICIT_BZERO
327
0
  explicit_bzero(buf, sizeof(buf));
328
0
#endif
329
0
  return 0;
330
0
}
331
332
/* Copies the contents of a file. Returns -1 on read error, -2 on write error. */
333
int ul_copy_file(int from, int to)
334
0
{
335
0
#ifdef HAVE_SENDFILE
336
0
  struct stat st;
337
0
  ssize_t nw;
338
339
0
  if (fstat(from, &st) == -1)
340
0
    return UL_COPY_READ_ERROR;
341
0
  if (!S_ISREG(st.st_mode))
342
0
    return copy_file_simple(from, to);
343
0
  if (ul_sendfile_all(to, from, NULL, st.st_size) < 0)
344
0
    return copy_file_simple(from, to);
345
  /* ensure we either get an EOF or an error */
346
0
  while ((nw = ul_sendfile_all(to, from, NULL, 16*1024*1024)) != 0)
347
0
    if (nw < 0)
348
0
      return copy_file_simple(from, to);
349
0
  return 0;
350
#else
351
  return copy_file_simple(from, to);
352
#endif
353
0
}
354
355
/* Composes the /proc/self/fd/<fd> pathname for @fd. The @bufsz has to be at
356
 * least UL_FDPATH_BUFSIZ bytes.
357
 *
358
 * This is the only place where the /proc/self/fd/ pathnames are generated.
359
 *
360
 * Returns @buf, or NULL on error.
361
 */
362
char *ul_fd_mkpath(char *buf, size_t bufsz, int fd)
363
0
{
364
0
  int len;
365
366
0
  if (fd < 0) {
367
0
    errno = EBADF;
368
0
    return NULL;
369
0
  }
370
371
0
  len = snprintf(buf, bufsz, _PATH_PROC_FDDIR "/%d", fd);
372
0
  if (len < 0 || (size_t) len >= bufsz) {
373
0
    errno = ENAMETOOLONG;
374
0
    return NULL;
375
0
  }
376
377
0
  return buf;
378
0
}
379
380
/* Returns the pathname the @fd refers to as used by the kernel, or NULL on
381
 * error. The result has to be deallocated by free().
382
 */
383
char *ul_fd_get_path(int fd)
384
0
{
385
0
  ssize_t ssz;
386
0
  char buf[PATH_MAX];
387
0
  char fdpath[UL_FDPATH_BUFSIZ];
388
389
0
  if (!ul_fd_mkpath(fdpath, sizeof(fdpath), fd))
390
0
    return NULL;
391
392
0
  ssz = readlink(fdpath, buf, sizeof(buf));
393
0
  if (ssz < 0)
394
0
    return NULL;
395
396
  /* readlink() does not terminate the result and it does not report
397
   * truncation; a name we cannot read completely is unusable */
398
0
  if ((size_t) ssz >= sizeof(buf)) {
399
0
    errno = ENAMETOOLONG;
400
0
    return NULL;
401
0
  }
402
403
0
  buf[ssz] = '\0';
404
405
  /* readlink() also succeeds for things without a pathname (pipes,
406
   * sockets, ...) and it returns "<path> (deleted)" for unlinked files;
407
   * refuse all of it rather than return a bogus path.
408
   *
409
   * Note that this also refuses a real file named "foo (deleted)". To
410
   * tell it apart we would have to stat() the name, and such a path
411
   * lookup may trigger an automount or block on an unreachable NFS
412
   * server. */
413
0
  if (*buf != '/' || ul_endswith(buf, PATH_DELETED_SUFFIX)) {
414
0
    errno = ENOENT;
415
0
    return NULL;
416
0
  }
417
418
0
  return strdup(buf);
419
0
}
420
421
int ul_reopen(int fd, int flags)
422
0
{
423
0
  char *path = ul_fd_get_path(fd);
424
0
  int ret;
425
426
0
  if (!path)
427
0
    return -errno;
428
429
0
  ret = open(path, flags);
430
0
  free(path);
431
432
0
  return ret;
433
0
}
434
435
436
/* This is a libc-independent version of basename(), which is necessary to
437
 * maintain functionality across different libc implementations. It was
438
 * inspired by the behavior and implementation of glibc.
439
 */
440
char *ul_basename(char *path)
441
0
{
442
0
  char *p;
443
444
0
  if (!path || !*path)
445
0
    return (char *) "."; /* ugly, static string */
446
447
0
  p = strrchr(path, '/');
448
0
  if (!p)
449
0
    return path;   /* no '/', return original */
450
451
0
  if (*(p + 1) != '\0')
452
0
    return p + 1;   /* begin of the name */
453
454
0
  while (p > path && *(p - 1) == '/')
455
0
    --p;     /* remove trailing '/' */
456
457
0
  if (p > path) {
458
0
    *p-- = '\0';
459
0
    while (p > path && *(p - 1) != '/')
460
0
      --p;   /* move to the beginning of the name */
461
0
  } else while (*(p + 1) != '\0')
462
0
    ++p;
463
464
0
  return p;
465
0
}
466
467
#ifdef HAVE_OPENAT
468
/*
469
 * fopen_at_no_link() - Open a file stream that is not a symbolic/hard link.
470
 *
471
 * This function wraps around openat(2), fstat(2), ftruncate(2) and fdopen(3)
472
 * to create a file stream that is not a symbolic or hard link in a race-free
473
 * manner.
474
 *
475
 * @dir:  dirfd as passed to openat(2), e.g. AT_FDCWD for the calling process
476
 *    current working directory
477
 * @filename: name of the target file
478
 * @flags:  open(2) file creation/status flags, O_NOFOLLOW is implicitly set
479
 * @perm: open(2) file mode, can be bitwise ORed, these are only relevant
480
 *    when O_CREAT is set in @flags, otherwise pass as 0.
481
 * @mode: fopen(3) mode
482
 *
483
 * Return: On success, a valid pointer to a file stream is returned.
484
 *         On failure, NULL is returned and errno is set to indicate the issue.
485
 */
486
FILE *fopen_at_no_link(int dir, const char *filename,
487
                             int flags, mode_t perm, const char *mode)
488
0
{
489
0
  FILE *fp;
490
0
  int fd;
491
0
  struct stat st;
492
493
  /* We temporarily clear the O_TRUNC bit because we do not want
494
   * to accidentally truncate the target file if it is a hard link
495
   * instead of a symbolic one, where the latter is what we are
496
   * guarding against here. The test for the hard link is done below
497
   * with fstat()...
498
   */
499
0
  fd = openat(dir, filename, ((flags & ~O_TRUNC) | O_NOFOLLOW | O_CLOEXEC), perm);
500
0
  if (fd < 0)
501
0
    return NULL;
502
503
0
  if (fstat(fd, &st)) {
504
0
    close(fd);
505
0
    return NULL;
506
0
  }
507
508
0
  if (st.st_nlink > 1) {
509
0
    close(fd);
510
0
    errno = EMLINK;
511
0
    return NULL;
512
0
  }
513
514
0
  if ((flags & O_TRUNC) && ftruncate(fd, 0)) {
515
0
    close(fd);
516
0
    return NULL;
517
0
  }
518
519
0
  fp = fdopen(fd, mode);
520
0
  if (!fp)
521
0
    close(fd);
522
0
  return fp;
523
0
}
524
#endif /* HAVE_OPENAT */
525
526
#if defined(SYS_openat2)
527
int ul_openat_resolve(int dirfd, const char *path, int flags,
528
          mode_t mode, unsigned long long resolve)
529
{
530
  struct open_how how = {
531
    .flags = (__u64) flags,
532
    .mode = (__u64) mode,
533
    .resolve = resolve,
534
  };
535
536
  return syscall(SYS_openat2, dirfd, path, &how, sizeof(how));
537
}
538
#else
539
int ul_openat_resolve(
540
    int dirfd __attribute__((__unused__)),
541
    const char *path __attribute__((__unused__)),
542
    int flags __attribute__((__unused__)),
543
    mode_t mode __attribute__((__unused__)),
544
    unsigned long long resolve __attribute__((__unused__)))
545
0
{
546
0
  errno = ENOSYS;
547
0
  return -1;
548
0
}
549
#endif
550
551
#ifdef __linux__
552
/*
553
 * Fallback for kernels without openat2() (Linux < 5.6).
554
 *
555
 * Open the path and then ask the kernel for the name of the result. If any
556
 * component of the path is a symbolic link then the name reported by the
557
 * kernel differs from the requested path and we refuse the file descriptor.
558
 * A concurrent rename is detected the same way. The name always belongs to
559
 * the file descriptor we return, so there is no time-of-check-to-time-of-use
560
 * window between the check and the use.
561
 *
562
 * Note that the symlink is detected after it has been followed rather than
563
 * refused during the path resolution. The path is opened with O_PATH to keep
564
 * this free of side effects (no device open, no blocking on a FIFO, ...) and
565
 * the caller's flags are applied by re-opening the verified file descriptor.
566
 *
567
 * Returns a file descriptor, or -1 and sets errno to ELOOP when a symlink has
568
 * been detected, or to ENOSYS when the check is not possible.
569
 */
570
static int open_no_symlinks_fallback(const char *path, int flags, mode_t mode)
571
0
{
572
0
  char *abspath = NULL, *kpath = NULL;
573
0
  struct stat st;
574
0
  int fd = -1, errsv;
575
576
  /* we cannot verify a file we have to create first */
577
0
  if (!path || (flags & O_CREAT)) {
578
0
    errno = ENOSYS;
579
0
    return -1;
580
0
  }
581
582
0
  if (ul_is_relative_path(path)) {
583
    /* the kernel reports an absolute pathname; note that
584
     * ul_absolute_path() only prepends the CWD as returned by
585
     * getcwd(), it resolves nothing */
586
0
    abspath = ul_absolute_path(path);
587
0
    if (!abspath)
588
0
      return -1;
589
0
  }
590
591
0
  if (flags & O_PATH)
592
0
    fd = open(path, flags);
593
0
  else
594
0
    fd = open(path, O_PATH | O_CLOEXEC |
595
0
        (flags & (O_NOFOLLOW | O_DIRECTORY)));
596
0
  if (fd < 0)
597
0
    goto fail;
598
599
  /* O_PATH|O_NOFOLLOW returns a FD to the symlink itself */
600
0
  if (fstat(fd, &st) != 0)
601
0
    goto fail;
602
0
  if (S_ISLNK(st.st_mode)) {
603
0
    errno = ELOOP;
604
0
    goto fail;
605
0
  }
606
607
0
  kpath = ul_fd_get_path(fd);
608
0
  if (!kpath) {
609
0
    errno = ENOSYS;   /* no /proc, no verification */
610
0
    goto fail;
611
0
  }
612
613
  /* streq_paths() ignores duplicate and trailing slashes, but "." and
614
   * ".." in the requested path are refused as a symlink */
615
0
  if (streq_paths(abspath ? abspath : path, kpath) != 1) {
616
0
    errno = ELOOP;
617
0
    goto fail;
618
0
  }
619
620
0
  if (!(flags & O_PATH)) {
621
0
    char fdpath[UL_FDPATH_BUFSIZ];
622
0
    int nfd = -1;
623
624
    /* apply the caller's flags; the /proc link refers to the
625
     * verified file, the path is not resolved for the second time */
626
0
    if (ul_fd_mkpath(fdpath, sizeof(fdpath), fd))
627
0
      nfd = open(fdpath, flags, mode);
628
0
    if (nfd < 0)
629
0
      goto fail;
630
0
    close(fd);
631
0
    fd = nfd;
632
0
  }
633
634
0
  free(abspath);
635
0
  free(kpath);
636
0
  return fd;
637
0
fail:
638
0
  errsv = errno;
639
0
  free(abspath);
640
0
  free(kpath);
641
0
  if (fd >= 0)
642
0
    close(fd);
643
0
  errno = errsv;
644
0
  return -1;
645
0
}
646
#else /* !__linux__ */
647
/* O_PATH and the /proc/self/fd/ names are Linux specific */
648
static int open_no_symlinks_fallback(
649
      const char *path __attribute__((__unused__)),
650
      int flags __attribute__((__unused__)),
651
      mode_t mode __attribute__((__unused__)))
652
{
653
  errno = ENOSYS;
654
  return -1;
655
}
656
#endif /* __linux__ */
657
658
/* Opens @path with the guarantee that no component of the path is a symbolic
659
 * link, otherwise it fails with ELOOP.
660
 */
661
int ul_open_no_symlinks(const char *path, int flags, mode_t mode)
662
0
{
663
0
  int fd = ul_openat_resolve(AT_FDCWD, path, flags, mode,
664
0
           RESOLVE_NO_SYMLINKS);
665
666
  /* openat2() is Linux 5.6+ */
667
0
  if (fd < 0 && errno == ENOSYS)
668
0
    fd = open_no_symlinks_fallback(path, flags, mode);
669
670
0
  return fd;
671
0
}
672
673
#ifdef TEST_PROGRAM_FILEUTILS
674
int main(int argc, char *argv[])
675
{
676
  if (argc < 2)
677
    errx(EXIT_FAILURE, "Usage %s --{mkstemp,close-fds,copy-file,open-no-symlinks}",
678
        argv[0]);
679
680
  if (strcmp(argv[1], "--mkstemp") == 0) {
681
    FILE *f;
682
    char *tmpname = NULL;
683
684
    f = xfmkstemp(&tmpname, NULL, "test");
685
    unlink(tmpname);
686
    free(tmpname);
687
    fclose(f);
688
689
  } else if (strcmp(argv[1], "--close-fds") == 0) {
690
    ignore_result( dup(STDIN_FILENO) );
691
    ignore_result( dup(STDIN_FILENO) );
692
    ignore_result( dup(STDIN_FILENO) );
693
694
# ifdef HAVE_CLOSE_RANGE
695
    if (close_range(STDERR_FILENO + 1, ~0U, 0) < 0)
696
# endif
697
      ul_close_all_fds(STDERR_FILENO + 1, ~0U);
698
699
  } else if (strcmp(argv[1], "--copy-file") == 0) {
700
    int ret = ul_copy_file(STDIN_FILENO, STDOUT_FILENO);
701
    if (ret == UL_COPY_READ_ERROR)
702
      err(EXIT_FAILURE, "read");
703
    else if (ret == UL_COPY_WRITE_ERROR)
704
      err(EXIT_FAILURE, "write");
705
706
  } else if (strcmp(argv[1], "--open-no-symlinks") == 0) {
707
#ifdef __linux__
708
    int flags = O_PATH | O_CLOEXEC;
709
#else
710
    int flags = O_RDONLY | O_CLOEXEC;
711
#endif
712
    int fallback = 0;
713
    char *name;
714
    int fd, i;
715
716
    if (argc < 3)
717
      errx(EXIT_FAILURE, "no path specified");
718
719
    for (i = 3; i < argc; i++) {
720
      /* the fallback is used on kernels without openat2()
721
       * only, "--fallback" makes it testable anywhere */
722
      if (strcmp(argv[i], "--fallback") == 0)
723
        fallback = 1;
724
      else if (strcmp(argv[i], "--rdonly") == 0)
725
        flags = O_RDONLY | O_CLOEXEC;
726
    }
727
728
    if (fallback)
729
      fd = open_no_symlinks_fallback(argv[2], flags, 0);
730
    else
731
      fd = ul_open_no_symlinks(argv[2], flags, 0);
732
    if (fd < 0)
733
      err(EXIT_FAILURE, "%s", argv[2]);
734
735
    name = ul_fd_get_path(fd);
736
    printf("%s\n", name);
737
    free(name);
738
    close(fd);
739
  }
740
  return EXIT_SUCCESS;
741
}
742
#endif