udev: String substitutions can be done in ENV, too
[systemd_ALT.git] / src / basic / tmpfile-util.c
blobe77ca9424892d7a113b258c388a04edabfa9282e
1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
3 #include <sys/mman.h>
5 #include "alloc-util.h"
6 #include "fd-util.h"
7 #include "fileio.h"
8 #include "fs-util.h"
9 #include "hexdecoct.h"
10 #include "macro.h"
11 #include "memfd-util.h"
12 #include "missing_fcntl.h"
13 #include "missing_syscall.h"
14 #include "path-util.h"
15 #include "process-util.h"
16 #include "random-util.h"
17 #include "stat-util.h"
18 #include "stdio-util.h"
19 #include "string-util.h"
20 #include "sync-util.h"
21 #include "tmpfile-util.h"
22 #include "umask-util.h"
24 static int fopen_temporary_internal(int dir_fd, const char *path, FILE **ret_file) {
25 _cleanup_fclose_ FILE *f = NULL;
26 _cleanup_close_ int fd = -EBADF;
27 int r;
29 assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
30 assert(path);
32 fd = openat(dir_fd, path, O_CLOEXEC|O_NOCTTY|O_RDWR|O_CREAT|O_EXCL, 0600);
33 if (fd < 0)
34 return -errno;
36 /* This assumes that returned FILE object is short-lived and used within the same single-threaded
37 * context and never shared externally, hence locking is not necessary. */
39 r = take_fdopen_unlocked(&fd, "w", &f);
40 if (r < 0) {
41 (void) unlinkat(dir_fd, path, 0);
42 return r;
45 if (ret_file)
46 *ret_file = TAKE_PTR(f);
48 return 0;
51 int fopen_temporary_at(int dir_fd, const char *path, FILE **ret_file, char **ret_path) {
52 _cleanup_free_ char *t = NULL;
53 int r;
55 assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
56 assert(path);
58 r = tempfn_random(path, NULL, &t);
59 if (r < 0)
60 return r;
62 r = fopen_temporary_internal(dir_fd, t, ret_file);
63 if (r < 0)
64 return r;
66 if (ret_path)
67 *ret_path = TAKE_PTR(t);
69 return 0;
72 int fopen_temporary_child_at(int dir_fd, const char *path, FILE **ret_file, char **ret_path) {
73 _cleanup_free_ char *t = NULL;
74 int r;
76 assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
78 if (!path) {
79 r = tmp_dir(&path);
80 if (r < 0)
81 return r;
84 r = tempfn_random_child(path, NULL, &t);
85 if (r < 0)
86 return r;
88 r = fopen_temporary_internal(dir_fd, t, ret_file);
89 if (r < 0)
90 return r;
92 if (ret_path)
93 *ret_path = TAKE_PTR(t);
95 return 0;
98 /* This is much like mkostemp() but is subject to umask(). */
99 int mkostemp_safe(char *pattern) {
100 assert(pattern);
101 BLOCK_WITH_UMASK(0077);
102 return RET_NERRNO(mkostemp(pattern, O_CLOEXEC));
105 int fmkostemp_safe(char *pattern, const char *mode, FILE **ret_f) {
106 _cleanup_close_ int fd = -EBADF;
107 FILE *f;
109 fd = mkostemp_safe(pattern);
110 if (fd < 0)
111 return fd;
113 f = take_fdopen(&fd, mode);
114 if (!f)
115 return -errno;
117 *ret_f = f;
118 return 0;
121 static int tempfn_build(const char *p, const char *pre, const char *post, bool child, char **ret) {
122 _cleanup_free_ char *d = NULL, *fn = NULL, *nf = NULL, *result = NULL;
123 size_t len_pre, len_post, len_add;
124 int r;
126 assert(p);
127 assert(ret);
130 * Turns this:
131 * /foo/bar/waldo
133 * Into this :
134 * /foo/bar/waldo/.#<pre><post> (child == true)
135 * /foo/bar/.#<pre>waldo<post> (child == false)
138 if (pre && strchr(pre, '/'))
139 return -EINVAL;
141 if (post && strchr(post, '/'))
142 return -EINVAL;
144 len_pre = strlen_ptr(pre);
145 len_post = strlen_ptr(post);
146 /* NAME_MAX is counted *without* the trailing NUL byte. */
147 if (len_pre > NAME_MAX - STRLEN(".#") ||
148 len_post > NAME_MAX - STRLEN(".#") - len_pre)
149 return -EINVAL;
151 len_add = len_pre + len_post + STRLEN(".#");
153 if (child) {
154 d = strdup(p);
155 if (!d)
156 return -ENOMEM;
157 } else {
158 r = path_extract_directory(p, &d);
159 if (r < 0 && r != -EDESTADDRREQ) /* EDESTADDRREQ → No directory specified, just a filename */
160 return r;
162 r = path_extract_filename(p, &fn);
163 if (r < 0)
164 return r;
166 if (strlen(fn) > NAME_MAX - len_add)
167 /* We cannot simply prepend and append strings to the filename. Let's truncate the filename. */
168 fn[NAME_MAX - len_add] = '\0';
171 nf = strjoin(".#", strempty(pre), strempty(fn), strempty(post));
172 if (!nf)
173 return -ENOMEM;
175 if (d) {
176 if (!path_extend(&d, nf))
177 return -ENOMEM;
179 result = path_simplify(TAKE_PTR(d));
180 } else
181 result = TAKE_PTR(nf);
183 if (!path_is_valid(result)) /* New path is not valid? (Maybe because too long?) Refuse. */
184 return -EINVAL;
186 *ret = TAKE_PTR(result);
187 return 0;
190 int tempfn_xxxxxx(const char *p, const char *extra, char **ret) {
192 * Turns this:
193 * /foo/bar/waldo
195 * Into this:
196 * /foo/bar/.#<extra>waldoXXXXXX
199 return tempfn_build(p, extra, "XXXXXX", /* child = */ false, ret);
202 int tempfn_random(const char *p, const char *extra, char **ret) {
203 _cleanup_free_ char *s = NULL;
205 assert(p);
206 assert(ret);
209 * Turns this:
210 * /foo/bar/waldo
212 * Into this:
213 * /foo/bar/.#<extra>waldobaa2a261115984a9
216 if (asprintf(&s, "%016" PRIx64, random_u64()) < 0)
217 return -ENOMEM;
219 return tempfn_build(p, extra, s, /* child = */ false, ret);
222 int tempfn_random_child(const char *p, const char *extra, char **ret) {
223 _cleanup_free_ char *s = NULL;
224 int r;
226 assert(ret);
228 /* Turns this:
229 * /foo/bar/waldo
230 * Into this:
231 * /foo/bar/waldo/.#<extra>3c2b6219aa75d7d0
234 if (!p) {
235 r = tmp_dir(&p);
236 if (r < 0)
237 return r;
240 if (asprintf(&s, "%016" PRIx64, random_u64()) < 0)
241 return -ENOMEM;
243 return tempfn_build(p, extra, s, /* child = */ true, ret);
246 int open_tmpfile_unlinkable(const char *directory, int flags) {
247 char *p;
248 int fd, r;
250 if (!directory) {
251 r = tmp_dir(&directory);
252 if (r < 0)
253 return r;
254 } else if (isempty(directory))
255 return -EINVAL;
257 /* Returns an unlinked temporary file that cannot be linked into the file system anymore */
259 /* Try O_TMPFILE first, if it is supported */
260 fd = open(directory, flags|O_TMPFILE|O_EXCL, S_IRUSR|S_IWUSR);
261 if (fd >= 0)
262 return fd;
264 /* Fall back to unguessable name + unlinking */
265 p = strjoina(directory, "/systemd-tmp-XXXXXX");
267 fd = mkostemp_safe(p);
268 if (fd < 0)
269 return fd;
271 (void) unlink(p);
273 return fd;
276 int open_tmpfile_linkable_at(int dir_fd, const char *target, int flags, char **ret_path) {
277 _cleanup_free_ char *tmp = NULL;
278 int r, fd;
280 assert(target);
281 assert(ret_path);
283 /* Don't allow O_EXCL, as that has a special meaning for O_TMPFILE */
284 assert((flags & O_EXCL) == 0);
286 /* Creates a temporary file, that shall be renamed to "target" later. If possible, this uses O_TMPFILE – in
287 * which case "ret_path" will be returned as NULL. If not possible the temporary path name used is returned in
288 * "ret_path". Use link_tmpfile() below to rename the result after writing the file in full. */
290 fd = open_parent_at(dir_fd, target, O_TMPFILE|flags, 0640);
291 if (fd >= 0) {
292 *ret_path = NULL;
293 return fd;
296 log_debug_errno(fd, "Failed to use O_TMPFILE for %s: %m", target);
298 r = tempfn_random(target, NULL, &tmp);
299 if (r < 0)
300 return r;
302 fd = openat(dir_fd, tmp, O_CREAT|O_EXCL|O_NOFOLLOW|O_NOCTTY|flags, 0640);
303 if (fd < 0)
304 return -errno;
306 *ret_path = TAKE_PTR(tmp);
308 return fd;
311 int fopen_tmpfile_linkable(const char *target, int flags, char **ret_path, FILE **ret_file) {
312 _cleanup_free_ char *path = NULL;
313 _cleanup_fclose_ FILE *f = NULL;
314 _cleanup_close_ int fd = -EBADF;
316 assert(target);
317 assert(ret_file);
318 assert(ret_path);
320 fd = open_tmpfile_linkable(target, flags, &path);
321 if (fd < 0)
322 return fd;
324 f = take_fdopen(&fd, "w");
325 if (!f)
326 return -ENOMEM;
328 *ret_path = TAKE_PTR(path);
329 *ret_file = TAKE_PTR(f);
330 return 0;
333 static int link_fd(int fd, int newdirfd, const char *newpath) {
334 int r;
336 assert(fd >= 0);
337 assert(newdirfd >= 0 || newdirfd == AT_FDCWD);
338 assert(newpath);
340 /* Try symlinking via /proc/fd/ first. */
341 r = RET_NERRNO(linkat(AT_FDCWD, FORMAT_PROC_FD_PATH(fd), newdirfd, newpath, AT_SYMLINK_FOLLOW));
342 if (r != -ENOENT)
343 return r;
345 /* Fall back to symlinking via AT_EMPTY_PATH as fallback (this requires CAP_DAC_READ_SEARCH and a
346 * more recent kernel, but does not require /proc/ mounted) */
347 if (proc_mounted() != 0)
348 return r;
350 return RET_NERRNO(linkat(fd, "", newdirfd, newpath, AT_EMPTY_PATH));
353 int link_tmpfile_at(int fd, int dir_fd, const char *path, const char *target, LinkTmpfileFlags flags) {
354 _cleanup_free_ char *tmp = NULL;
355 int r;
357 assert(fd >= 0);
358 assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
359 assert(target);
361 /* Moves a temporary file created with open_tmpfile() above into its final place. If "path" is NULL
362 * an fd created with O_TMPFILE is assumed, and linkat() is used. Otherwise it is assumed O_TMPFILE
363 * is not supported on the directory, and renameat2() is used instead. */
365 if (FLAGS_SET(flags, LINK_TMPFILE_SYNC) && fsync(fd) < 0)
366 return -errno;
368 if (path) {
369 if (FLAGS_SET(flags, LINK_TMPFILE_REPLACE))
370 r = RET_NERRNO(renameat(dir_fd, path, dir_fd, target));
371 else
372 r = rename_noreplace(dir_fd, path, dir_fd, target);
373 if (r < 0)
374 return r;
375 } else {
377 r = link_fd(fd, dir_fd, target);
378 if (r != -EEXIST || !FLAGS_SET(flags, LINK_TMPFILE_REPLACE))
379 return r;
381 /* So the target already exists and we were asked to replace it. That sucks a bit, since the kernel's
382 * linkat() logic does not allow that. We work-around this by linking the file to a random name
383 * first, and then renaming that to the final name. This reintroduces the race O_TMPFILE kinda is
384 * trying to fix, but at least the vulnerability window (i.e. where the file is linked into the file
385 * system under a temporary name) is very short. */
387 r = tempfn_random(target, NULL, &tmp);
388 if (r < 0)
389 return r;
391 if (link_fd(fd, dir_fd, tmp) < 0)
392 return -EEXIST; /* propagate original error */
394 r = RET_NERRNO(renameat(dir_fd, tmp, dir_fd, target));
395 if (r < 0) {
396 (void) unlinkat(dir_fd, tmp, 0);
397 return r;
401 if (FLAGS_SET(flags, LINK_TMPFILE_SYNC)) {
402 r = fsync_full(fd);
403 if (r < 0)
404 return r;
407 return 0;
410 int flink_tmpfile(FILE *f, const char *path, const char *target, LinkTmpfileFlags flags) {
411 int fd, r;
413 assert(f);
414 assert(target);
416 fd = fileno(f);
417 if (fd < 0) /* Not all FILE* objects encapsulate fds */
418 return -EBADF;
420 r = fflush_and_check(f);
421 if (r < 0)
422 return r;
424 return link_tmpfile(fd, path, target, flags);
427 int mkdtemp_malloc(const char *template, char **ret) {
428 _cleanup_free_ char *p = NULL;
429 int r;
431 assert(ret);
433 if (template)
434 p = strdup(template);
435 else {
436 const char *tmp;
438 r = tmp_dir(&tmp);
439 if (r < 0)
440 return r;
442 p = path_join(tmp, "XXXXXX");
444 if (!p)
445 return -ENOMEM;
447 if (!mkdtemp(p))
448 return -errno;
450 *ret = TAKE_PTR(p);
451 return 0;
454 int mkdtemp_open(const char *template, int flags, char **ret) {
455 _cleanup_free_ char *p = NULL;
456 int fd, r;
458 r = mkdtemp_malloc(template, &p);
459 if (r < 0)
460 return r;
462 fd = RET_NERRNO(open(p, O_DIRECTORY|O_CLOEXEC|flags));
463 if (fd < 0) {
464 (void) rmdir(p);
465 return fd;
468 if (ret)
469 *ret = TAKE_PTR(p);
471 return fd;