Coverage Report

Created: 2026-09-01 06:54

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
#ifdef HAVE_LINUX_OPENAT2_H
23
# include <linux/openat2.h>
24
#endif
25
26
#include "c.h"
27
#include "all-io.h"
28
#include "fileutils.h"
29
#include "pathnames.h"
30
31
int mkstemp_cloexec(char *template)
32
0
{
33
0
#ifdef HAVE_MKOSTEMP
34
0
  return mkostemp(template, O_RDWR|O_CREAT|O_EXCL|O_CLOEXEC);
35
#else
36
  int fd, old_flags, errno_save;
37
38
  fd = mkstemp(template);
39
  if (fd < 0)
40
    return fd;
41
42
  old_flags = fcntl(fd, F_GETFD, 0);
43
  if (old_flags < 0)
44
    goto unwind;
45
  if (fcntl(fd, F_SETFD, old_flags | O_CLOEXEC) < 0)
46
    goto unwind;
47
48
  return fd;
49
50
unwind:
51
  errno_save = errno;
52
  unlink(template);
53
  close(fd);
54
  errno = errno_save;
55
56
  return -1;
57
#endif
58
0
}
59
60
/* Create open temporary file in safe way.  Please notice that the
61
 * file permissions are -rw------- by default. */
62
int xmkstemp(char **tmpname, const char *dir, const char *prefix)
63
0
{
64
0
  char *localtmp;
65
0
  const char *tmpenv;
66
0
  mode_t old_mode;
67
0
  int fd, rc;
68
69
  /* Some use cases must be capable of being moved atomically
70
   * with rename(2), which is the reason why dir is here.  */
71
0
  tmpenv = dir ? dir : getenv("TMPDIR");
72
0
  if (!tmpenv)
73
0
    tmpenv = _PATH_TMP;
74
75
0
  rc = asprintf(&localtmp, "%s/%s.XXXXXX", tmpenv, prefix);
76
0
  if (rc < 0)
77
0
    return -1;
78
79
0
  old_mode = umask(077);
80
0
  fd = mkstemp_cloexec(localtmp);
81
0
  umask(old_mode);
82
0
  if (fd == -1) {
83
0
    free(localtmp);
84
0
    localtmp = NULL;
85
0
  }
86
0
  *tmpname = localtmp;
87
0
  return fd;
88
0
}
89
90
#ifdef F_DUPFD_CLOEXEC
91
int dup_fd_cloexec(int oldfd, int lowfd)
92
#else
93
int dup_fd_cloexec(int oldfd, int lowfd  __attribute__((__unused__)))
94
#endif
95
0
{
96
0
  int fd, flags, errno_save;
97
98
0
#ifdef F_DUPFD_CLOEXEC
99
0
  fd = fcntl(oldfd, F_DUPFD_CLOEXEC, lowfd);
100
0
  if (fd >= 0)
101
0
    return fd;
102
0
#endif
103
104
0
  fd = dup(oldfd);
105
0
  if (fd < 0)
106
0
    return fd;
107
108
0
  flags = fcntl(fd, F_GETFD);
109
0
  if (flags < 0)
110
0
    goto unwind;
111
0
  if (fcntl(fd, F_SETFD, flags | FD_CLOEXEC) < 0)
112
0
    goto unwind;
113
114
0
  return fd;
115
116
0
unwind:
117
0
  errno_save = errno;
118
0
  close(fd);
119
0
  errno = errno_save;
120
121
0
  return -1;
122
0
}
123
124
/*
125
 * portable getdtablesize()
126
 */
127
unsigned int get_fd_tabsize(void)
128
0
{
129
0
  int m;
130
131
0
#if defined(HAVE_GETDTABLESIZE)
132
0
  m = getdtablesize();
133
#elif defined(HAVE_GETRLIMIT) && defined(RLIMIT_NOFILE)
134
  struct rlimit rl;
135
136
  getrlimit(RLIMIT_NOFILE, &rl);
137
  m = rl.rlim_cur;
138
#elif defined(HAVE_SYSCONF) && defined(_SC_OPEN_MAX)
139
  m = sysconf(_SC_OPEN_MAX);
140
#else
141
  m = OPEN_MAX;
142
#endif
143
0
  return m;
144
0
}
145
146
void ul_close_all_fds(unsigned int first, unsigned int last)
147
0
{
148
0
  struct dirent *d;
149
0
  DIR *dir;
150
151
0
  dir = opendir(_PATH_PROC_FDDIR);
152
0
  if (dir) {
153
0
    while ((d = xreaddir(dir))) {
154
0
      char *end;
155
0
      unsigned int fd;
156
0
      int dfd;
157
158
0
      errno = 0;
159
0
      fd = strtoul(d->d_name, &end, 10);
160
161
0
      if (errno || end == d->d_name || !end || *end)
162
0
        continue;
163
0
      dfd = dirfd(dir);
164
0
      if (dfd < 0)
165
0
        continue;
166
0
      if ((unsigned int)dfd == fd)
167
0
        continue;
168
0
      if (fd < first || last < fd)
169
0
        continue;
170
0
      close(fd);
171
0
    }
172
0
    closedir(dir);
173
0
  } else {
174
0
    unsigned fd, tbsz = get_fd_tabsize();
175
176
0
    for (fd = 0; fd < tbsz; fd++) {
177
0
      if (first <= fd && fd <= last)
178
0
        close(fd);
179
0
    }
180
0
  }
181
0
}
182
183
/*
184
 * Fork, drop permissions, and call oper() and return result.
185
 */
186
char *ul_restricted_path_oper(const char *path,
187
      int (*oper)(const char *path, char **result, void *data),
188
      void *data)
189
0
{
190
0
  char *result = NULL;
191
0
  int errsv = 0;
192
0
  int pipes[2];
193
0
  ssize_t len;
194
0
  pid_t pid;
195
196
0
  if (!path || !*path)
197
0
    return NULL;
198
199
0
  if (pipe(pipes) != 0)
200
0
    return NULL;
201
  /*
202
   * To accurately assume identity of getuid() we must use setuid()
203
   * but if we do that, we lose ability to reassume euid of 0, so
204
   * we fork to do the check to keep euid intact.
205
   */
206
0
  pid = fork();
207
0
  switch (pid) {
208
0
  case -1:
209
0
    close(pipes[0]);
210
0
    close(pipes[1]);
211
0
    return NULL;     /* fork error */
212
0
  case 0:
213
0
    close(pipes[0]);    /* close unused end */
214
0
    pipes[0] = -1;
215
0
    errno = 0;
216
217
0
    if (drop_permissions() != 0)
218
0
      result = NULL; /* failed */
219
0
    else
220
0
      oper(path, &result, data);
221
222
0
    len = result ? (ssize_t) strlen(result) :
223
0
              errno ? -errno : -EINVAL;
224
225
    /* send length or errno */
226
0
    ul_write_all(pipes[1], (char *) &len, sizeof(len));
227
0
    if (result)
228
0
      ul_write_all(pipes[1], result, len);
229
0
    _exit(0);
230
0
  default:
231
0
    break;
232
0
  }
233
234
0
  close(pipes[1]);    /* close unused end */
235
0
  pipes[1] = -1;
236
237
  /* read size or -errno */
238
0
  if (ul_read_all(pipes[0], (char *) &len, sizeof(len)) != sizeof(len))
239
0
    goto done;
240
0
  if (len < 0) {
241
0
    errsv = -len;
242
0
    goto done;
243
0
  }
244
245
0
  result = malloc(len + 1);
246
0
  if (!result) {
247
0
    errsv = ENOMEM;
248
0
    goto done;
249
0
  }
250
  /* read path */
251
0
  if (ul_read_all(pipes[0], result, len) != len) {
252
0
    errsv = errno;
253
0
    goto done;
254
0
  }
255
0
  result[len] = '\0';
256
0
done:
257
0
  if (errsv) {
258
0
    free(result);
259
0
    result = NULL;
260
0
  }
261
0
  close(pipes[0]);
262
263
  /* We make a best effort to reap child */
264
0
  ignore_result( waitpid(pid, NULL, 0) );
265
266
0
  errno = errsv;
267
0
  return result;
268
269
0
}
270
271
#ifdef TEST_PROGRAM_FILEUTILS
272
int main(int argc, char *argv[])
273
{
274
  if (argc < 2)
275
    errx(EXIT_FAILURE, "Usage %s --{mkstemp,close-fds,copy-file}", argv[0]);
276
277
  if (strcmp(argv[1], "--mkstemp") == 0) {
278
    FILE *f;
279
    char *tmpname = NULL;
280
281
    f = xfmkstemp(&tmpname, NULL, "test");
282
    unlink(tmpname);
283
    free(tmpname);
284
    fclose(f);
285
286
  } else if (strcmp(argv[1], "--close-fds") == 0) {
287
    ignore_result( dup(STDIN_FILENO) );
288
    ignore_result( dup(STDIN_FILENO) );
289
    ignore_result( dup(STDIN_FILENO) );
290
291
# ifdef HAVE_CLOSE_RANGE
292
    if (close_range(STDERR_FILENO + 1, ~0U, 0) < 0)
293
# endif
294
      ul_close_all_fds(STDERR_FILENO + 1, ~0U);
295
296
  } else if (strcmp(argv[1], "--copy-file") == 0) {
297
    int ret = ul_copy_file(STDIN_FILENO, STDOUT_FILENO);
298
    if (ret == UL_COPY_READ_ERROR)
299
      err(EXIT_FAILURE, "read");
300
    else if (ret == UL_COPY_WRITE_ERROR)
301
      err(EXIT_FAILURE, "write");
302
  }
303
  return EXIT_SUCCESS;
304
}
305
#endif
306
307
308
int ul_mkdir_p(const char *path, mode_t mode)
309
0
{
310
0
  char *p, *dir;
311
0
  int rc = 0;
312
313
0
  if (!path || !*path)
314
0
    return -EINVAL;
315
316
0
  dir = p = strdup(path);
317
0
  if (!dir)
318
0
    return -ENOMEM;
319
320
0
  if (*p == '/')
321
0
    p++;
322
323
0
  while (p && *p) {
324
0
    char *e = strchr(p, '/');
325
0
    if (e)
326
0
      *e = '\0';
327
0
    if (*p) {
328
0
      rc = mkdir(dir, mode);
329
0
      if (rc && errno != EEXIST)
330
0
        break;
331
0
      rc = 0;
332
0
    }
333
0
    if (!e)
334
0
      break;
335
0
    *e = '/';
336
0
    p = e + 1;
337
0
  }
338
339
0
  free(dir);
340
0
  return rc;
341
0
}
342
343
/* returns basename and keeps dirname in the @path, if @path is "/" (root)
344
 * then returns empty string */
345
char *stripoff_last_component(char *path)
346
0
{
347
0
  char *p = path ? strrchr(path, '/') : NULL;
348
349
0
  if (!p)
350
0
    return NULL;
351
0
  *p = '\0';
352
0
  return p + 1;
353
0
}
354
355
static int copy_file_simple(int from, int to)
356
0
{
357
0
  ssize_t nr;
358
0
  char buf[BUFSIZ];
359
360
0
  while ((nr = ul_read_all(from, buf, sizeof(buf))) > 0)
361
0
    if (ul_write_all(to, buf, nr) == -1)
362
0
      return UL_COPY_WRITE_ERROR;
363
0
  if (nr < 0)
364
0
    return UL_COPY_READ_ERROR;
365
0
#ifdef HAVE_EXPLICIT_BZERO
366
0
  explicit_bzero(buf, sizeof(buf));
367
0
#endif
368
0
  return 0;
369
0
}
370
371
/* Copies the contents of a file. Returns -1 on read error, -2 on write error. */
372
int ul_copy_file(int from, int to)
373
0
{
374
0
#ifdef HAVE_SENDFILE
375
0
  struct stat st;
376
0
  ssize_t nw;
377
378
0
  if (fstat(from, &st) == -1)
379
0
    return UL_COPY_READ_ERROR;
380
0
  if (!S_ISREG(st.st_mode))
381
0
    return copy_file_simple(from, to);
382
0
  if (ul_sendfile_all(to, from, NULL, st.st_size) < 0)
383
0
    return copy_file_simple(from, to);
384
  /* ensure we either get an EOF or an error */
385
0
  while ((nw = ul_sendfile_all(to, from, NULL, 16*1024*1024)) != 0)
386
0
    if (nw < 0)
387
0
      return copy_file_simple(from, to);
388
0
  return 0;
389
#else
390
  return copy_file_simple(from, to);
391
#endif
392
0
}
393
394
int ul_reopen(int fd, int flags)
395
0
{
396
0
  ssize_t ssz;
397
0
  char buf[PATH_MAX];
398
0
  char fdpath[ sizeof(_PATH_PROC_FDDIR) + sizeof(stringify_value(INT_MAX)) ];
399
400
0
  snprintf(fdpath, sizeof(fdpath), _PATH_PROC_FDDIR "/%d", fd);
401
402
0
  ssz = readlink(fdpath, buf, sizeof(buf) - 1);
403
0
  if (ssz < 0)
404
0
    return -errno;
405
406
0
  assert(ssz > 0);
407
408
0
  buf[ssz] = '\0';
409
410
0
  return open(buf, flags);
411
0
}
412
413
414
/* This is a libc-independent version of basename(), which is necessary to
415
 * maintain functionality across different libc implementations. It was
416
 * inspired by the behavior and implementation of glibc.
417
 */
418
char *ul_basename(char *path)
419
0
{
420
0
  char *p;
421
422
0
  if (!path || !*path)
423
0
    return (char *) "."; /* ugly, static string */
424
425
0
  p = strrchr(path, '/');
426
0
  if (!p)
427
0
    return path;   /* no '/', return original */
428
429
0
  if (*(p + 1) != '\0')
430
0
    return p + 1;   /* begin of the name */
431
432
0
  while (p > path && *(p - 1) == '/')
433
0
    --p;     /* remove trailing '/' */
434
435
0
  if (p > path) {
436
0
    *p-- = '\0';
437
0
    while (p > path && *(p - 1) != '/')
438
0
      --p;   /* move to the beginning of the name */
439
0
  } else while (*(p + 1) != '\0')
440
0
    ++p;
441
442
0
  return p;
443
0
}
444
445
#ifdef HAVE_OPENAT
446
/*
447
 * fopen_at_no_link() - Open a file stream that is not a symbolic/hard link.
448
 *
449
 * This function wraps around openat(2), fstat(2), ftruncate(2) and fdopen(3)
450
 * to create a file stream that is not a symbolic or hard link in a race-free
451
 * manner.
452
 *
453
 * @dir:  dirfd as passed to openat(2), e.g. AT_FDCWD for the calling process
454
 *    current working directory
455
 * @filename: name of the target file
456
 * @flags:  open(2) file creation/status flags, O_NOFOLLOW is implicitly set
457
 * @perm: open(2) file mode, can be bitwise ORed, these are only relevant
458
 *    when O_CREAT is set in @flags, otherwise pass as 0.
459
 * @mode: fopen(3) mode
460
 *
461
 * Return: On success, a valid pointer to a file stream is returned.
462
 *         On failure, NULL is returned and errno is set to indicate the issue.
463
 */
464
FILE *fopen_at_no_link(int dir, const char *filename,
465
                             int flags, mode_t perm, const char *mode)
466
0
{
467
0
  FILE *fp;
468
0
  int fd;
469
0
  struct stat st;
470
471
  /* We temporarily clear the O_TRUNC bit because we do not want
472
   * to accidentally truncate the target file if it is a hard link
473
   * instead of a symbolic one, where the latter is what we are
474
   * guarding against here. The test for the hard link is done below
475
   * with fstat()...
476
   */
477
0
  fd = openat(dir, filename, ((flags & ~O_TRUNC) | O_NOFOLLOW), perm);
478
0
  if (fd < 0)
479
0
    return NULL;
480
481
0
  if (fstat(fd, &st)) {
482
0
    close(fd);
483
0
    return NULL;
484
0
  }
485
486
0
  if (st.st_nlink > 1) {
487
0
    close(fd);
488
0
    errno = EMLINK;
489
0
    return NULL;
490
0
  }
491
492
0
  if ((flags & O_TRUNC) && ftruncate(fd, 0)) {
493
0
    close(fd);
494
0
    return NULL;
495
0
  }
496
497
0
  fp = fdopen(fd, mode);
498
0
  if (!fp)
499
0
    close(fd);
500
0
  return fp;
501
0
}
502
#endif /* HAVE_OPENAT */
503
504
int ul_open_no_symlinks(const char *path, int flags, mode_t mode)
505
0
{
506
#if defined(SYS_openat2) && defined(RESOLVE_NO_SYMLINKS)
507
  struct open_how how = {
508
    .flags = (__u64) flags,
509
    .mode = (__u64) mode,
510
    .resolve = RESOLVE_NO_SYMLINKS,
511
  };
512
  int fd = syscall(SYS_openat2, AT_FDCWD, path, &how, sizeof(how));
513
514
  /* only fall back to O_NOFOLLOW if the syscall is unavailable */
515
  if (fd >= 0 || errno != ENOSYS)
516
    return fd;
517
#endif
518
  return open(path, flags | O_NOFOLLOW, mode);
519
0
}