Coverage Report

Created: 2025-09-05 06:58

/src/util-linux/lib/fileutils.c
Line
Count
Source (jump to first uncovered line)
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 <string.h>
15
#include <sys/wait.h>
16
17
#include "c.h"
18
#include "all-io.h"
19
#include "fileutils.h"
20
#include "pathnames.h"
21
22
int mkstemp_cloexec(char *template)
23
1.16k
{
24
1.16k
#ifdef HAVE_MKOSTEMP
25
1.16k
  return mkostemp(template, O_RDWR|O_CREAT|O_EXCL|O_CLOEXEC);
26
#else
27
  int fd, old_flags, errno_save;
28
29
  fd = mkstemp(template);
30
  if (fd < 0)
31
    return fd;
32
33
  old_flags = fcntl(fd, F_GETFD, 0);
34
  if (old_flags < 0)
35
    goto unwind;
36
  if (fcntl(fd, F_SETFD, old_flags | O_CLOEXEC) < 0)
37
    goto unwind;
38
39
  return fd;
40
41
unwind:
42
  errno_save = errno;
43
  unlink(template);
44
  close(fd);
45
  errno = errno_save;
46
47
  return -1;
48
#endif
49
1.16k
}
50
51
/* Create open temporary file in safe way.  Please notice that the
52
 * file permissions are -rw------- by default. */
53
int xmkstemp(char **tmpname, const char *dir, const char *prefix)
54
0
{
55
0
  char *localtmp;
56
0
  const char *tmpenv;
57
0
  mode_t old_mode;
58
0
  int fd, rc;
59
60
  /* Some use cases must be capable of being moved atomically
61
   * with rename(2), which is the reason why dir is here.  */
62
0
  tmpenv = dir ? dir : getenv("TMPDIR");
63
0
  if (!tmpenv)
64
0
    tmpenv = _PATH_TMP;
65
66
0
  rc = asprintf(&localtmp, "%s/%s.XXXXXX", tmpenv, prefix);
67
0
  if (rc < 0)
68
0
    return -1;
69
70
0
  old_mode = umask(077);
71
0
  fd = mkstemp_cloexec(localtmp);
72
0
  umask(old_mode);
73
0
  if (fd == -1) {
74
0
    free(localtmp);
75
0
    localtmp = NULL;
76
0
  }
77
0
  *tmpname = localtmp;
78
0
  return fd;
79
0
}
80
81
#ifdef F_DUPFD_CLOEXEC
82
int dup_fd_cloexec(int oldfd, int lowfd)
83
#else
84
int dup_fd_cloexec(int oldfd, int lowfd  __attribute__((__unused__)))
85
#endif
86
0
{
87
0
  int fd, flags, errno_save;
88
89
0
#ifdef F_DUPFD_CLOEXEC
90
0
  fd = fcntl(oldfd, F_DUPFD_CLOEXEC, lowfd);
91
0
  if (fd >= 0)
92
0
    return fd;
93
0
#endif
94
95
0
  fd = dup(oldfd);
96
0
  if (fd < 0)
97
0
    return fd;
98
99
0
  flags = fcntl(fd, F_GETFD);
100
0
  if (flags < 0)
101
0
    goto unwind;
102
0
  if (fcntl(fd, F_SETFD, flags | FD_CLOEXEC) < 0)
103
0
    goto unwind;
104
105
0
  return fd;
106
107
0
unwind:
108
0
  errno_save = errno;
109
0
  close(fd);
110
0
  errno = errno_save;
111
112
0
  return -1;
113
0
}
114
115
/*
116
 * portable getdtablesize()
117
 */
118
unsigned int get_fd_tabsize(void)
119
0
{
120
0
  int m;
121
122
0
#if defined(HAVE_GETDTABLESIZE)
123
0
  m = getdtablesize();
124
#elif defined(HAVE_GETRLIMIT) && defined(RLIMIT_NOFILE)
125
  struct rlimit rl;
126
127
  getrlimit(RLIMIT_NOFILE, &rl);
128
  m = rl.rlim_cur;
129
#elif defined(HAVE_SYSCONF) && defined(_SC_OPEN_MAX)
130
  m = sysconf(_SC_OPEN_MAX);
131
#else
132
  m = OPEN_MAX;
133
#endif
134
0
  return m;
135
0
}
136
137
void ul_close_all_fds(unsigned int first, unsigned int last)
138
0
{
139
0
  struct dirent *d;
140
0
  DIR *dir;
141
142
0
  dir = opendir(_PATH_PROC_FDDIR);
143
0
  if (dir) {
144
0
    while ((d = xreaddir(dir))) {
145
0
      char *end;
146
0
      unsigned int fd;
147
0
      int dfd;
148
149
0
      errno = 0;
150
0
      fd = strtoul(d->d_name, &end, 10);
151
152
0
      if (errno || end == d->d_name || !end || *end)
153
0
        continue;
154
0
      dfd = dirfd(dir);
155
0
      if (dfd < 0)
156
0
        continue;
157
0
      if ((unsigned int)dfd == fd)
158
0
        continue;
159
0
      if (fd < first || last < fd)
160
0
        continue;
161
0
      close(fd);
162
0
    }
163
0
    closedir(dir);
164
0
  } else {
165
0
    unsigned fd, tbsz = get_fd_tabsize();
166
167
0
    for (fd = 0; fd < tbsz; fd++) {
168
0
      if (first <= fd && fd <= last)
169
0
        close(fd);
170
0
    }
171
0
  }
172
0
}
173
174
/*
175
 * Fork, drop permissions, and call oper() and return result.
176
 */
177
char *ul_restricted_path_oper(const char *path,
178
      int (*oper)(const char *path, char **result, void *data),
179
      void *data)
180
0
{
181
0
  char *result = NULL;
182
0
  int errsv = 0;
183
0
  int pipes[2];
184
0
  ssize_t len;
185
0
  pid_t pid;
186
187
0
  if (!path || !*path)
188
0
    return NULL;
189
190
0
  if (pipe(pipes) != 0)
191
0
    return NULL;
192
  /*
193
   * To accurately assume identity of getuid() we must use setuid()
194
   * but if we do that, we lose ability to reassume euid of 0, so
195
   * we fork to do the check to keep euid intact.
196
   */
197
0
  pid = fork();
198
0
  switch (pid) {
199
0
  case -1:
200
0
    close(pipes[0]);
201
0
    close(pipes[1]);
202
0
    return NULL;     /* fork error */
203
0
  case 0:
204
0
    close(pipes[0]);    /* close unused end */
205
0
    pipes[0] = -1;
206
0
    errno = 0;
207
208
0
    if (drop_permissions() != 0)
209
0
      result = NULL; /* failed */
210
0
    else
211
0
      oper(path, &result, data);
212
213
0
    len = result ? (ssize_t) strlen(result) :
214
0
              errno ? -errno : -EINVAL;
215
216
    /* send length or errno */
217
0
    write_all(pipes[1], (char *) &len, sizeof(len));
218
0
    if (result)
219
0
      write_all(pipes[1], result, len);
220
0
    _exit(0);
221
0
  default:
222
0
    break;
223
0
  }
224
225
0
  close(pipes[1]);    /* close unused end */
226
0
  pipes[1] = -1;
227
228
  /* read size or -errno */
229
0
  if (read_all(pipes[0], (char *) &len, sizeof(len)) != sizeof(len))
230
0
    goto done;
231
0
  if (len < 0) {
232
0
    errsv = -len;
233
0
    goto done;
234
0
  }
235
236
0
  result = malloc(len + 1);
237
0
  if (!result) {
238
0
    errsv = ENOMEM;
239
0
    goto done;
240
0
  }
241
  /* read path */
242
0
  if (read_all(pipes[0], result, len) != len) {
243
0
    errsv = errno;
244
0
    goto done;
245
0
  }
246
0
  result[len] = '\0';
247
0
done:
248
0
  if (errsv) {
249
0
    free(result);
250
0
    result = NULL;
251
0
  }
252
0
  close(pipes[0]);
253
254
  /* We make a best effort to reap child */
255
0
  ignore_result( waitpid(pid, NULL, 0) );
256
257
0
  errno = errsv;
258
0
  return result;
259
260
0
}
261
262
#ifdef TEST_PROGRAM_FILEUTILS
263
int main(int argc, char *argv[])
264
{
265
  if (argc < 2)
266
    errx(EXIT_FAILURE, "Usage %s --{mkstemp,close-fds,copy-file}", argv[0]);
267
268
  if (strcmp(argv[1], "--mkstemp") == 0) {
269
    FILE *f;
270
    char *tmpname = NULL;
271
272
    f = xfmkstemp(&tmpname, NULL, "test");
273
    unlink(tmpname);
274
    free(tmpname);
275
    fclose(f);
276
277
  } else if (strcmp(argv[1], "--close-fds") == 0) {
278
    ignore_result( dup(STDIN_FILENO) );
279
    ignore_result( dup(STDIN_FILENO) );
280
    ignore_result( dup(STDIN_FILENO) );
281
282
# ifdef HAVE_CLOSE_RANGE
283
    if (close_range(STDERR_FILENO + 1, ~0U, 0) < 0)
284
# endif
285
      ul_close_all_fds(STDERR_FILENO + 1, ~0U);
286
287
  } else if (strcmp(argv[1], "--copy-file") == 0) {
288
    int ret = ul_copy_file(STDIN_FILENO, STDOUT_FILENO);
289
    if (ret == UL_COPY_READ_ERROR)
290
      err(EXIT_FAILURE, "read");
291
    else if (ret == UL_COPY_WRITE_ERROR)
292
      err(EXIT_FAILURE, "write");
293
  }
294
  return EXIT_SUCCESS;
295
}
296
#endif
297
298
299
int ul_mkdir_p(const char *path, mode_t mode)
300
0
{
301
0
  char *p, *dir;
302
0
  int rc = 0;
303
304
0
  if (!path || !*path)
305
0
    return -EINVAL;
306
307
0
  dir = p = strdup(path);
308
0
  if (!dir)
309
0
    return -ENOMEM;
310
311
0
  if (*p == '/')
312
0
    p++;
313
314
0
  while (p && *p) {
315
0
    char *e = strchr(p, '/');
316
0
    if (e)
317
0
      *e = '\0';
318
0
    if (*p) {
319
0
      rc = mkdir(dir, mode);
320
0
      if (rc && errno != EEXIST)
321
0
        break;
322
0
      rc = 0;
323
0
    }
324
0
    if (!e)
325
0
      break;
326
0
    *e = '/';
327
0
    p = e + 1;
328
0
  }
329
330
0
  free(dir);
331
0
  return rc;
332
0
}
333
334
/* returns basename and keeps dirname in the @path, if @path is "/" (root)
335
 * then returns empty string */
336
char *stripoff_last_component(char *path)
337
0
{
338
0
  char *p = path ? strrchr(path, '/') : NULL;
339
340
0
  if (!p)
341
0
    return NULL;
342
0
  *p = '\0';
343
0
  return p + 1;
344
0
}
345
346
static int copy_file_simple(int from, int to)
347
0
{
348
0
  ssize_t nr;
349
0
  char buf[BUFSIZ];
350
351
0
  while ((nr = read_all(from, buf, sizeof(buf))) > 0)
352
0
    if (write_all(to, buf, nr) == -1)
353
0
      return UL_COPY_WRITE_ERROR;
354
0
  if (nr < 0)
355
0
    return UL_COPY_READ_ERROR;
356
0
#ifdef HAVE_EXPLICIT_BZERO
357
0
  explicit_bzero(buf, sizeof(buf));
358
0
#endif
359
0
  return 0;
360
0
}
361
362
/* Copies the contents of a file. Returns -1 on read error, -2 on write error. */
363
int ul_copy_file(int from, int to)
364
0
{
365
0
#ifdef HAVE_SENDFILE
366
0
  struct stat st;
367
0
  ssize_t nw;
368
369
0
  if (fstat(from, &st) == -1)
370
0
    return UL_COPY_READ_ERROR;
371
0
  if (!S_ISREG(st.st_mode))
372
0
    return copy_file_simple(from, to);
373
0
  if (sendfile_all(to, from, NULL, st.st_size) < 0)
374
0
    return copy_file_simple(from, to);
375
  /* ensure we either get an EOF or an error */
376
0
  while ((nw = sendfile_all(to, from, NULL, 16*1024*1024)) != 0)
377
0
    if (nw < 0)
378
0
      return copy_file_simple(from, to);
379
0
  return 0;
380
#else
381
  return copy_file_simple(from, to);
382
#endif
383
0
}
384
385
int ul_reopen(int fd, int flags)
386
0
{
387
0
  ssize_t ssz;
388
0
  char buf[PATH_MAX];
389
0
  char fdpath[ sizeof(_PATH_PROC_FDDIR) + sizeof(stringify_value(INT_MAX)) ];
390
391
0
  snprintf(fdpath, sizeof(fdpath), _PATH_PROC_FDDIR "/%d", fd);
392
393
0
  ssz = readlink(fdpath, buf, sizeof(buf) - 1);
394
0
  if (ssz < 0)
395
0
    return -errno;
396
397
0
  assert(ssz > 0);
398
399
0
  buf[ssz] = '\0';
400
401
0
  return open(buf, flags);
402
0
}
403
404
405
/* This is a libc-independent version of basename(), which is necessary to
406
 * maintain functionality across different libc implementations. It was
407
 * inspired by the behavior and implementation of glibc.
408
 */
409
char *ul_basename(char *path)
410
0
{
411
0
  char *p;
412
413
0
  if (!path || !*path)
414
0
    return (char *) "."; /* ugly, static string */
415
416
0
  p = strrchr(path, '/');
417
0
  if (!p)
418
0
    return path;   /* no '/', return original */
419
420
0
  if (*(p + 1) != '\0')
421
0
    return p + 1;   /* begin of the name */
422
423
0
  while (p > path && *(p - 1) == '/')
424
0
    --p;     /* remove trailing '/' */
425
426
0
  if (p > path) {
427
0
    *p-- = '\0';
428
0
    while (p > path && *(p - 1) != '/')
429
0
      --p;   /* move to the beginning of the name */
430
0
  } else while (*(p + 1) != '\0')
431
0
    ++p;
432
433
0
  return p;
434
0
}