Coverage Report

Created: 2026-06-08 06:06

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
#include <sys/syscall.h>
15
#include <string.h>
16
#include <sys/wait.h>
17
#include <fcntl.h>
18
#include <errno.h>
19
20
#ifdef HAVE_LINUX_OPENAT2_H
21
# include <linux/openat2.h>
22
#endif
23
24
#include "c.h"
25
#include "all-io.h"
26
#include "fileutils.h"
27
#include "pathnames.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
    write_all(pipes[1], (char *) &len, sizeof(len));
225
0
    if (result)
226
0
      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 (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 (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
#ifdef TEST_PROGRAM_FILEUTILS
270
int main(int argc, char *argv[])
271
{
272
  if (argc < 2)
273
    errx(EXIT_FAILURE, "Usage %s --{mkstemp,close-fds,copy-file}", argv[0]);
274
275
  if (strcmp(argv[1], "--mkstemp") == 0) {
276
    FILE *f;
277
    char *tmpname = NULL;
278
279
    f = xfmkstemp(&tmpname, NULL, "test");
280
    unlink(tmpname);
281
    free(tmpname);
282
    fclose(f);
283
284
  } else if (strcmp(argv[1], "--close-fds") == 0) {
285
    ignore_result( dup(STDIN_FILENO) );
286
    ignore_result( dup(STDIN_FILENO) );
287
    ignore_result( dup(STDIN_FILENO) );
288
289
# ifdef HAVE_CLOSE_RANGE
290
    if (close_range(STDERR_FILENO + 1, ~0U, 0) < 0)
291
# endif
292
      ul_close_all_fds(STDERR_FILENO + 1, ~0U);
293
294
  } else if (strcmp(argv[1], "--copy-file") == 0) {
295
    int ret = ul_copy_file(STDIN_FILENO, STDOUT_FILENO);
296
    if (ret == UL_COPY_READ_ERROR)
297
      err(EXIT_FAILURE, "read");
298
    else if (ret == UL_COPY_WRITE_ERROR)
299
      err(EXIT_FAILURE, "write");
300
  }
301
  return EXIT_SUCCESS;
302
}
303
#endif
304
305
306
int ul_mkdir_p(const char *path, mode_t mode)
307
0
{
308
0
  char *p, *dir;
309
0
  int rc = 0;
310
311
0
  if (!path || !*path)
312
0
    return -EINVAL;
313
314
0
  dir = p = strdup(path);
315
0
  if (!dir)
316
0
    return -ENOMEM;
317
318
0
  if (*p == '/')
319
0
    p++;
320
321
0
  while (p && *p) {
322
0
    char *e = strchr(p, '/');
323
0
    if (e)
324
0
      *e = '\0';
325
0
    if (*p) {
326
0
      rc = mkdir(dir, mode);
327
0
      if (rc && errno != EEXIST)
328
0
        break;
329
0
      rc = 0;
330
0
    }
331
0
    if (!e)
332
0
      break;
333
0
    *e = '/';
334
0
    p = e + 1;
335
0
  }
336
337
0
  free(dir);
338
0
  return rc;
339
0
}
340
341
/* returns basename and keeps dirname in the @path, if @path is "/" (root)
342
 * then returns empty string */
343
char *stripoff_last_component(char *path)
344
0
{
345
0
  char *p = path ? strrchr(path, '/') : NULL;
346
347
0
  if (!p)
348
0
    return NULL;
349
0
  *p = '\0';
350
0
  return p + 1;
351
0
}
352
353
static int copy_file_simple(int from, int to)
354
0
{
355
0
  ssize_t nr;
356
0
  char buf[BUFSIZ];
357
358
0
  while ((nr = read_all(from, buf, sizeof(buf))) > 0)
359
0
    if (write_all(to, buf, nr) == -1)
360
0
      return UL_COPY_WRITE_ERROR;
361
0
  if (nr < 0)
362
0
    return UL_COPY_READ_ERROR;
363
0
#ifdef HAVE_EXPLICIT_BZERO
364
0
  explicit_bzero(buf, sizeof(buf));
365
0
#endif
366
0
  return 0;
367
0
}
368
369
/* Copies the contents of a file. Returns -1 on read error, -2 on write error. */
370
int ul_copy_file(int from, int to)
371
0
{
372
0
#ifdef HAVE_SENDFILE
373
0
  struct stat st;
374
0
  ssize_t nw;
375
376
0
  if (fstat(from, &st) == -1)
377
0
    return UL_COPY_READ_ERROR;
378
0
  if (!S_ISREG(st.st_mode))
379
0
    return copy_file_simple(from, to);
380
0
  if (sendfile_all(to, from, NULL, st.st_size) < 0)
381
0
    return copy_file_simple(from, to);
382
  /* ensure we either get an EOF or an error */
383
0
  while ((nw = sendfile_all(to, from, NULL, 16*1024*1024)) != 0)
384
0
    if (nw < 0)
385
0
      return copy_file_simple(from, to);
386
0
  return 0;
387
#else
388
  return copy_file_simple(from, to);
389
#endif
390
0
}
391
392
int ul_reopen(int fd, int flags)
393
0
{
394
0
  ssize_t ssz;
395
0
  char buf[PATH_MAX];
396
0
  char fdpath[ sizeof(_PATH_PROC_FDDIR) + sizeof(stringify_value(INT_MAX)) ];
397
398
0
  snprintf(fdpath, sizeof(fdpath), _PATH_PROC_FDDIR "/%d", fd);
399
400
0
  ssz = readlink(fdpath, buf, sizeof(buf) - 1);
401
0
  if (ssz < 0)
402
0
    return -errno;
403
404
0
  assert(ssz > 0);
405
406
0
  buf[ssz] = '\0';
407
408
0
  return open(buf, flags);
409
0
}
410
411
412
/* This is a libc-independent version of basename(), which is necessary to
413
 * maintain functionality across different libc implementations. It was
414
 * inspired by the behavior and implementation of glibc.
415
 */
416
char *ul_basename(char *path)
417
0
{
418
0
  char *p;
419
420
0
  if (!path || !*path)
421
0
    return (char *) "."; /* ugly, static string */
422
423
0
  p = strrchr(path, '/');
424
0
  if (!p)
425
0
    return path;   /* no '/', return original */
426
427
0
  if (*(p + 1) != '\0')
428
0
    return p + 1;   /* begin of the name */
429
430
0
  while (p > path && *(p - 1) == '/')
431
0
    --p;     /* remove trailing '/' */
432
433
0
  if (p > path) {
434
0
    *p-- = '\0';
435
0
    while (p > path && *(p - 1) != '/')
436
0
      --p;   /* move to the beginning of the name */
437
0
  } else while (*(p + 1) != '\0')
438
0
    ++p;
439
440
0
  return p;
441
0
}
442
443
#ifdef HAVE_OPENAT
444
/*
445
 * fopen_at_no_link() - Open a file stream that is not a symbolic/hard link.
446
 *
447
 * This function wraps around openat(2), fstat(2), ftruncate(2) and fdopen(3)
448
 * to create a file stream that is not a symbolic or hard link in a race-free
449
 * manner.
450
 *
451
 * @dir:  dirfd as passed to openat(2), e.g. AT_FDCWD for the calling process
452
 *    current working directory
453
 * @filename: name of the target file
454
 * @flags:  open(2) file creation/status flags, O_NOFOLLOW is implicitly set
455
 * @perm: open(2) file mode, can be bitwise ORed, these are only relevant
456
 *    when O_CREAT is set in @flags, otherwise pass as 0.
457
 * @mode: fopen(3) mode
458
 *
459
 * Return: On success, a valid pointer to a file stream is returned.
460
 *         On failure, NULL is returned and errno is set to indicate the issue.
461
 */
462
FILE *fopen_at_no_link(int dir, const char *filename,
463
                             int flags, mode_t perm, const char *mode)
464
0
{
465
0
  FILE *fp;
466
0
  int fd;
467
0
  struct stat st;
468
469
  /* We temporarily clear the O_TRUNC bit because we do not want
470
   * to accidentally truncate the target file if it is a hard link
471
   * instead of a symbolic one, where the latter is what we are
472
   * guarding against here. The test for the hard link is done below
473
   * with fstat()...
474
   */
475
0
  fd = openat(dir, filename, ((flags & ~O_TRUNC) | O_NOFOLLOW), perm);
476
0
  if (fd < 0)
477
0
    return NULL;
478
479
0
  if (fstat(fd, &st)) {
480
0
    close(fd);
481
0
    return NULL;
482
0
  }
483
484
0
  if (st.st_nlink > 1) {
485
0
    close(fd);
486
0
    errno = EMLINK;
487
0
    return NULL;
488
0
  }
489
490
0
  if ((flags & O_TRUNC) && ftruncate(fd, 0)) {
491
0
    close(fd);
492
0
    return NULL;
493
0
  }
494
495
0
  fp = fdopen(fd, mode);
496
0
  if (!fp)
497
0
    close(fd);
498
0
  return fp;
499
0
}
500
#endif /* HAVE_OPENAT */
501
502
int ul_open_no_symlinks(const char *path, int flags, mode_t mode)
503
0
{
504
#if defined(SYS_openat2) && defined(RESOLVE_NO_SYMLINKS)
505
  struct open_how how = {
506
    .flags = (__u64) flags,
507
    .mode = (__u64) mode,
508
    .resolve = RESOLVE_NO_SYMLINKS,
509
  };
510
  int fd = syscall(SYS_openat2, AT_FDCWD, path, &how, sizeof(how));
511
512
  /* only fall back to O_NOFOLLOW if the syscall is unavailable */
513
  if (fd >= 0 || errno != ENOSYS)
514
    return fd;
515
#endif
516
  return open(path, flags | O_NOFOLLOW, mode);
517
0
}