]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/basic/tmpfile-util.c
tree-wide: use -EBADF for fd initialization
[thirdparty/systemd.git] / src / basic / tmpfile-util.c
1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3 #include <sys/mman.h>
4
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 "stdio-util.h"
18 #include "string-util.h"
19 #include "tmpfile-util.h"
20 #include "umask-util.h"
21
22 static int fopen_temporary_internal(int dir_fd, const char *path, FILE **ret_file) {
23 _cleanup_fclose_ FILE *f = NULL;
24 _cleanup_close_ int fd = -EBADF;
25 int r;
26
27 assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
28 assert(path);
29
30 fd = openat(dir_fd, path, O_CLOEXEC|O_NOCTTY|O_RDWR|O_CREAT|O_EXCL, 0600);
31 if (fd < 0)
32 return -errno;
33
34 /* This assumes that returned FILE object is short-lived and used within the same single-threaded
35 * context and never shared externally, hence locking is not necessary. */
36
37 r = take_fdopen_unlocked(&fd, "w", &f);
38 if (r < 0) {
39 (void) unlinkat(dir_fd, path, 0);
40 return r;
41 }
42
43 if (ret_file)
44 *ret_file = TAKE_PTR(f);
45
46 return 0;
47 }
48
49 int fopen_temporary_at(int dir_fd, const char *path, FILE **ret_file, char **ret_path) {
50 _cleanup_free_ char *t = NULL;
51 int r;
52
53 assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
54 assert(path);
55
56 r = tempfn_random(path, NULL, &t);
57 if (r < 0)
58 return r;
59
60 r = fopen_temporary_internal(dir_fd, t, ret_file);
61 if (r < 0)
62 return r;
63
64 if (ret_path)
65 *ret_path = TAKE_PTR(t);
66
67 return 0;
68 }
69
70 int fopen_temporary_child_at(int dir_fd, const char *path, FILE **ret_file, char **ret_path) {
71 _cleanup_free_ char *t = NULL;
72 int r;
73
74 assert(dir_fd >= 0 || dir_fd == AT_FDCWD);
75
76 if (!path) {
77 r = tmp_dir(&path);
78 if (r < 0)
79 return r;
80 }
81
82 r = tempfn_random_child(path, NULL, &t);
83 if (r < 0)
84 return r;
85
86 r = fopen_temporary_internal(dir_fd, t, ret_file);
87 if (r < 0)
88 return r;
89
90 if (ret_path)
91 *ret_path = TAKE_PTR(t);
92
93 return 0;
94 }
95
96 /* This is much like mkostemp() but is subject to umask(). */
97 int mkostemp_safe(char *pattern) {
98 assert(pattern);
99 BLOCK_WITH_UMASK(0077);
100 return RET_NERRNO(mkostemp(pattern, O_CLOEXEC));
101 }
102
103 int fmkostemp_safe(char *pattern, const char *mode, FILE **ret_f) {
104 _cleanup_close_ int fd = -EBADF;
105 FILE *f;
106
107 fd = mkostemp_safe(pattern);
108 if (fd < 0)
109 return fd;
110
111 f = take_fdopen(&fd, mode);
112 if (!f)
113 return -errno;
114
115 *ret_f = f;
116 return 0;
117 }
118
119 static int tempfn_build(const char *p, const char *pre, const char *post, bool child, char **ret) {
120 _cleanup_free_ char *d = NULL, *fn = NULL, *nf = NULL, *result = NULL;
121 size_t len_pre, len_post, len_add;
122 int r;
123
124 assert(p);
125 assert(ret);
126
127 /*
128 * Turns this:
129 * /foo/bar/waldo
130 *
131 * Into this :
132 * /foo/bar/waldo/.#<pre><post> (child == true)
133 * /foo/bar/.#<pre>waldo<post> (child == false)
134 */
135
136 if (pre && strchr(pre, '/'))
137 return -EINVAL;
138
139 if (post && strchr(post, '/'))
140 return -EINVAL;
141
142 len_pre = strlen_ptr(pre);
143 len_post = strlen_ptr(post);
144 /* NAME_MAX is counted *without* the trailing NUL byte. */
145 if (len_pre > NAME_MAX - STRLEN(".#") ||
146 len_post > NAME_MAX - STRLEN(".#") - len_pre)
147 return -EINVAL;
148
149 len_add = len_pre + len_post + STRLEN(".#");
150
151 if (child) {
152 d = strdup(p);
153 if (!d)
154 return -ENOMEM;
155 } else {
156 r = path_extract_directory(p, &d);
157 if (r < 0 && r != -EDESTADDRREQ) /* EDESTADDRREQ → No directory specified, just a filename */
158 return r;
159
160 r = path_extract_filename(p, &fn);
161 if (r < 0)
162 return r;
163
164 if (strlen(fn) > NAME_MAX - len_add)
165 /* We cannot simply prepend and append strings to the filename. Let's truncate the filename. */
166 fn[NAME_MAX - len_add] = '\0';
167 }
168
169 nf = strjoin(".#", strempty(pre), strempty(fn), strempty(post));
170 if (!nf)
171 return -ENOMEM;
172
173 if (d) {
174 if (!path_extend(&d, nf))
175 return -ENOMEM;
176
177 result = path_simplify(TAKE_PTR(d));
178 } else
179 result = TAKE_PTR(nf);
180
181 if (!path_is_valid(result)) /* New path is not valid? (Maybe because too long?) Refuse. */
182 return -EINVAL;
183
184 *ret = TAKE_PTR(result);
185 return 0;
186 }
187
188 int tempfn_xxxxxx(const char *p, const char *extra, char **ret) {
189 /*
190 * Turns this:
191 * /foo/bar/waldo
192 *
193 * Into this:
194 * /foo/bar/.#<extra>waldoXXXXXX
195 */
196
197 return tempfn_build(p, extra, "XXXXXX", /* child = */ false, ret);
198 }
199
200 int tempfn_random(const char *p, const char *extra, char **ret) {
201 _cleanup_free_ char *s = NULL;
202
203 assert(p);
204 assert(ret);
205
206 /*
207 * Turns this:
208 * /foo/bar/waldo
209 *
210 * Into this:
211 * /foo/bar/.#<extra>waldobaa2a261115984a9
212 */
213
214 if (asprintf(&s, "%016" PRIx64, random_u64()) < 0)
215 return -ENOMEM;
216
217 return tempfn_build(p, extra, s, /* child = */ false, ret);
218 }
219
220 int tempfn_random_child(const char *p, const char *extra, char **ret) {
221 _cleanup_free_ char *s = NULL;
222 int r;
223
224 assert(ret);
225
226 /* Turns this:
227 * /foo/bar/waldo
228 * Into this:
229 * /foo/bar/waldo/.#<extra>3c2b6219aa75d7d0
230 */
231
232 if (!p) {
233 r = tmp_dir(&p);
234 if (r < 0)
235 return r;
236 }
237
238 if (asprintf(&s, "%016" PRIx64, random_u64()) < 0)
239 return -ENOMEM;
240
241 return tempfn_build(p, extra, s, /* child = */ true, ret);
242 }
243
244 int open_tmpfile_unlinkable(const char *directory, int flags) {
245 char *p;
246 int fd, r;
247
248 if (!directory) {
249 r = tmp_dir(&directory);
250 if (r < 0)
251 return r;
252 } else if (isempty(directory))
253 return -EINVAL;
254
255 /* Returns an unlinked temporary file that cannot be linked into the file system anymore */
256
257 /* Try O_TMPFILE first, if it is supported */
258 fd = open(directory, flags|O_TMPFILE|O_EXCL, S_IRUSR|S_IWUSR);
259 if (fd >= 0)
260 return fd;
261
262 /* Fall back to unguessable name + unlinking */
263 p = strjoina(directory, "/systemd-tmp-XXXXXX");
264
265 fd = mkostemp_safe(p);
266 if (fd < 0)
267 return fd;
268
269 (void) unlink(p);
270
271 return fd;
272 }
273
274 int open_tmpfile_linkable(const char *target, int flags, char **ret_path) {
275 _cleanup_free_ char *tmp = NULL;
276 int r, fd;
277
278 assert(target);
279 assert(ret_path);
280
281 /* Don't allow O_EXCL, as that has a special meaning for O_TMPFILE */
282 assert((flags & O_EXCL) == 0);
283
284 /* Creates a temporary file, that shall be renamed to "target" later. If possible, this uses O_TMPFILE – in
285 * which case "ret_path" will be returned as NULL. If not possible the temporary path name used is returned in
286 * "ret_path". Use link_tmpfile() below to rename the result after writing the file in full. */
287
288 fd = open_parent(target, O_TMPFILE|flags, 0640);
289 if (fd >= 0) {
290 *ret_path = NULL;
291 return fd;
292 }
293
294 log_debug_errno(fd, "Failed to use O_TMPFILE for %s: %m", target);
295
296 r = tempfn_random(target, NULL, &tmp);
297 if (r < 0)
298 return r;
299
300 fd = open(tmp, O_CREAT|O_EXCL|O_NOFOLLOW|O_NOCTTY|flags, 0640);
301 if (fd < 0)
302 return -errno;
303
304 *ret_path = TAKE_PTR(tmp);
305
306 return fd;
307 }
308
309 int fopen_tmpfile_linkable(const char *target, int flags, char **ret_path, FILE **ret_file) {
310 _cleanup_free_ char *path = NULL;
311 _cleanup_fclose_ FILE *f = NULL;
312 _cleanup_close_ int fd = -EBADF;
313
314 assert(target);
315 assert(ret_file);
316 assert(ret_path);
317
318 fd = open_tmpfile_linkable(target, flags, &path);
319 if (fd < 0)
320 return fd;
321
322 f = take_fdopen(&fd, "w");
323 if (!f)
324 return -ENOMEM;
325
326 *ret_path = TAKE_PTR(path);
327 *ret_file = TAKE_PTR(f);
328 return 0;
329 }
330
331 int link_tmpfile(int fd, const char *path, const char *target) {
332 assert(fd >= 0);
333 assert(target);
334
335 /* Moves a temporary file created with open_tmpfile() above into its final place. if "path" is NULL an fd
336 * created with O_TMPFILE is assumed, and linkat() is used. Otherwise it is assumed O_TMPFILE is not supported
337 * on the directory, and renameat2() is used instead.
338 *
339 * Note that in both cases we will not replace existing files. This is because linkat() does not support this
340 * operation currently (renameat2() does), and there is no nice way to emulate this. */
341
342 if (path)
343 return rename_noreplace(AT_FDCWD, path, AT_FDCWD, target);
344
345 return RET_NERRNO(linkat(AT_FDCWD, FORMAT_PROC_FD_PATH(fd), AT_FDCWD, target, AT_SYMLINK_FOLLOW));
346 }
347
348 int flink_tmpfile(FILE *f, const char *path, const char *target) {
349 int fd, r;
350
351 assert(f);
352 assert(target);
353
354 fd = fileno(f);
355 if (fd < 0) /* Not all FILE* objects encapsulate fds */
356 return -EBADF;
357
358 r = fflush_sync_and_check(f);
359 if (r < 0)
360 return r;
361
362 return link_tmpfile(fd, path, target);
363 }
364
365 int mkdtemp_malloc(const char *template, char **ret) {
366 _cleanup_free_ char *p = NULL;
367 int r;
368
369 assert(ret);
370
371 if (template)
372 p = strdup(template);
373 else {
374 const char *tmp;
375
376 r = tmp_dir(&tmp);
377 if (r < 0)
378 return r;
379
380 p = path_join(tmp, "XXXXXX");
381 }
382 if (!p)
383 return -ENOMEM;
384
385 if (!mkdtemp(p))
386 return -errno;
387
388 *ret = TAKE_PTR(p);
389 return 0;
390 }
391
392 int mkdtemp_open(const char *template, int flags, char **ret) {
393 _cleanup_free_ char *p = NULL;
394 int fd, r;
395
396 r = mkdtemp_malloc(template, &p);
397 if (r < 0)
398 return r;
399
400 fd = RET_NERRNO(open(p, O_DIRECTORY|O_CLOEXEC|flags));
401 if (fd < 0) {
402 (void) rmdir(p);
403 return fd;
404 }
405
406 if (ret)
407 *ret = TAKE_PTR(p);
408
409 return fd;
410 }