1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
5 #include "alloc-util.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
;
29 assert(dir_fd
>= 0 || dir_fd
== AT_FDCWD
);
32 fd
= openat(dir_fd
, path
, O_CLOEXEC
|O_NOCTTY
|O_RDWR
|O_CREAT
|O_EXCL
, 0600);
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
);
41 (void) unlinkat(dir_fd
, path
, 0);
46 *ret_file
= TAKE_PTR(f
);
51 int fopen_temporary_at(int dir_fd
, const char *path
, FILE **ret_file
, char **ret_path
) {
52 _cleanup_free_
char *t
= NULL
;
55 assert(dir_fd
>= 0 || dir_fd
== AT_FDCWD
);
58 r
= tempfn_random(path
, NULL
, &t
);
62 r
= fopen_temporary_internal(dir_fd
, t
, ret_file
);
67 *ret_path
= TAKE_PTR(t
);
72 int fopen_temporary_child_at(int dir_fd
, const char *path
, FILE **ret_file
, char **ret_path
) {
73 _cleanup_free_
char *t
= NULL
;
76 assert(dir_fd
>= 0 || dir_fd
== AT_FDCWD
);
84 r
= tempfn_random_child(path
, NULL
, &t
);
88 r
= fopen_temporary_internal(dir_fd
, t
, ret_file
);
93 *ret_path
= TAKE_PTR(t
);
98 /* This is much like mkostemp() but is subject to umask(). */
99 int mkostemp_safe(char *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
;
109 fd
= mkostemp_safe(pattern
);
113 f
= take_fdopen(&fd
, mode
);
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
;
134 * /foo/bar/waldo/.#<pre><post> (child == true)
135 * /foo/bar/.#<pre>waldo<post> (child == false)
138 if (pre
&& strchr(pre
, '/'))
141 if (post
&& strchr(post
, '/'))
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
)
151 len_add
= len_pre
+ len_post
+ STRLEN(".#");
158 r
= path_extract_directory(p
, &d
);
159 if (r
< 0 && r
!= -EDESTADDRREQ
) /* EDESTADDRREQ → No directory specified, just a filename */
162 r
= path_extract_filename(p
, &fn
);
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
));
176 if (!path_extend(&d
, nf
))
179 result
= path_simplify(TAKE_PTR(d
));
181 result
= TAKE_PTR(nf
);
183 if (!path_is_valid(result
)) /* New path is not valid? (Maybe because too long?) Refuse. */
186 *ret
= TAKE_PTR(result
);
190 int tempfn_xxxxxx(const char *p
, const char *extra
, char **ret
) {
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
;
213 * /foo/bar/.#<extra>waldobaa2a261115984a9
216 if (asprintf(&s
, "%016" PRIx64
, random_u64()) < 0)
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
;
231 * /foo/bar/waldo/.#<extra>3c2b6219aa75d7d0
240 if (asprintf(&s
, "%016" PRIx64
, random_u64()) < 0)
243 return tempfn_build(p
, extra
, s
, /* child = */ true, ret
);
246 int open_tmpfile_unlinkable(const char *directory
, int flags
) {
251 r
= tmp_dir(&directory
);
254 } else if (isempty(directory
))
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
);
264 /* Fall back to unguessable name + unlinking */
265 p
= strjoina(directory
, "/systemd-tmp-XXXXXX");
267 fd
= mkostemp_safe(p
);
276 int open_tmpfile_linkable_at(int dir_fd
, const char *target
, int flags
, char **ret_path
) {
277 _cleanup_free_
char *tmp
= NULL
;
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);
296 log_debug_errno(fd
, "Failed to use O_TMPFILE for %s: %m", target
);
298 r
= tempfn_random(target
, NULL
, &tmp
);
302 fd
= openat(dir_fd
, tmp
, O_CREAT
|O_EXCL
|O_NOFOLLOW
|O_NOCTTY
|flags
, 0640);
306 *ret_path
= TAKE_PTR(tmp
);
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
;
320 fd
= open_tmpfile_linkable(target
, flags
, &path
);
324 f
= take_fdopen(&fd
, "w");
328 *ret_path
= TAKE_PTR(path
);
329 *ret_file
= TAKE_PTR(f
);
333 static int link_fd(int fd
, int newdirfd
, const char *newpath
) {
337 assert(newdirfd
>= 0 || newdirfd
== AT_FDCWD
);
340 /* Try symlinking via /proc/fd/ first. */
341 r
= RET_NERRNO(linkat(AT_FDCWD
, FORMAT_PROC_FD_PATH(fd
), newdirfd
, newpath
, AT_SYMLINK_FOLLOW
));
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)
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
;
358 assert(dir_fd
>= 0 || dir_fd
== AT_FDCWD
);
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)
369 if (FLAGS_SET(flags
, LINK_TMPFILE_REPLACE
))
370 r
= RET_NERRNO(renameat(dir_fd
, path
, dir_fd
, target
));
372 r
= rename_noreplace(dir_fd
, path
, dir_fd
, target
);
377 r
= link_fd(fd
, dir_fd
, target
);
378 if (r
!= -EEXIST
|| !FLAGS_SET(flags
, LINK_TMPFILE_REPLACE
))
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
);
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
));
396 (void) unlinkat(dir_fd
, tmp
, 0);
401 if (FLAGS_SET(flags
, LINK_TMPFILE_SYNC
)) {
410 int flink_tmpfile(FILE *f
, const char *path
, const char *target
, LinkTmpfileFlags flags
) {
417 if (fd
< 0) /* Not all FILE* objects encapsulate fds */
420 r
= fflush_and_check(f
);
424 return link_tmpfile(fd
, path
, target
, flags
);
427 int mkdtemp_malloc(const char *template, char **ret
) {
428 _cleanup_free_
char *p
= NULL
;
434 p
= strdup(template);
442 p
= path_join(tmp
, "XXXXXX");
454 int mkdtemp_open(const char *template, int flags
, char **ret
) {
455 _cleanup_free_
char *p
= NULL
;
458 r
= mkdtemp_malloc(template, &p
);
462 fd
= RET_NERRNO(open(p
, O_DIRECTORY
|O_CLOEXEC
|flags
));