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