]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/basic/tmpfile-util.c
Make fopen_temporary and fopen_temporary_label unlocked
[thirdparty/systemd.git] / src / basic / tmpfile-util.c
1 /* SPDX-License-Identifier: LGPL-2.1+ */
2
3 #include <stdio.h>
4 #include <stdio_ext.h>
5 #include <sys/mman.h>
6
7 #include "alloc-util.h"
8 #include "fd-util.h"
9 #include "fs-util.h"
10 #include "hexdecoct.h"
11 #include "macro.h"
12 #include "memfd-util.h"
13 #include "missing_fcntl.h"
14 #include "missing_syscall.h"
15 #include "path-util.h"
16 #include "process-util.h"
17 #include "random-util.h"
18 #include "stdio-util.h"
19 #include "string-util.h"
20 #include "tmpfile-util.h"
21 #include "umask-util.h"
22
23 int fopen_temporary(const char *path, FILE **_f, char **_temp_path) {
24 FILE *f;
25 char *t;
26 int r, fd;
27
28 assert(path);
29 assert(_f);
30 assert(_temp_path);
31
32 r = tempfn_xxxxxx(path, NULL, &t);
33 if (r < 0)
34 return r;
35
36 fd = mkostemp_safe(t);
37 if (fd < 0) {
38 free(t);
39 return -errno;
40 }
41
42 /* This assumes that returned FILE object is short-lived and used within the same single-threaded
43 * context and never shared externally, hence locking is not necessary. */
44
45 f = fdopen(fd, "w");
46 if (!f) {
47 unlink_noerrno(t);
48 free(t);
49 safe_close(fd);
50 return -errno;
51 }
52
53 (void) __fsetlocking(f, FSETLOCKING_BYCALLER);
54
55 *_f = f;
56 *_temp_path = t;
57
58 return 0;
59 }
60
61 /* This is much like mkostemp() but is subject to umask(). */
62 int mkostemp_safe(char *pattern) {
63 _cleanup_umask_ mode_t u = 0;
64 int fd;
65
66 assert(pattern);
67
68 u = umask(077);
69
70 fd = mkostemp(pattern, O_CLOEXEC);
71 if (fd < 0)
72 return -errno;
73
74 return fd;
75 }
76
77 int fmkostemp_safe(char *pattern, const char *mode, FILE **ret_f) {
78 int fd;
79 FILE *f;
80
81 fd = mkostemp_safe(pattern);
82 if (fd < 0)
83 return fd;
84
85 f = fdopen(fd, mode);
86 if (!f) {
87 safe_close(fd);
88 return -errno;
89 }
90
91 *ret_f = f;
92 return 0;
93 }
94
95 int tempfn_xxxxxx(const char *p, const char *extra, char **ret) {
96 const char *fn;
97 char *t;
98
99 assert(ret);
100
101 if (isempty(p))
102 return -EINVAL;
103 if (path_equal(p, "/"))
104 return -EINVAL;
105
106 /*
107 * Turns this:
108 * /foo/bar/waldo
109 *
110 * Into this:
111 * /foo/bar/.#<extra>waldoXXXXXX
112 */
113
114 fn = basename(p);
115 if (!filename_is_valid(fn))
116 return -EINVAL;
117
118 extra = strempty(extra);
119
120 t = new(char, strlen(p) + 2 + strlen(extra) + 6 + 1);
121 if (!t)
122 return -ENOMEM;
123
124 strcpy(stpcpy(stpcpy(stpcpy(mempcpy(t, p, fn - p), ".#"), extra), fn), "XXXXXX");
125
126 *ret = path_simplify(t, false);
127 return 0;
128 }
129
130 int tempfn_random(const char *p, const char *extra, char **ret) {
131 const char *fn;
132 char *t, *x;
133 uint64_t u;
134 unsigned i;
135
136 assert(ret);
137
138 if (isempty(p))
139 return -EINVAL;
140 if (path_equal(p, "/"))
141 return -EINVAL;
142
143 /*
144 * Turns this:
145 * /foo/bar/waldo
146 *
147 * Into this:
148 * /foo/bar/.#<extra>waldobaa2a261115984a9
149 */
150
151 fn = basename(p);
152 if (!filename_is_valid(fn))
153 return -EINVAL;
154
155 extra = strempty(extra);
156
157 t = new(char, strlen(p) + 2 + strlen(extra) + 16 + 1);
158 if (!t)
159 return -ENOMEM;
160
161 x = stpcpy(stpcpy(stpcpy(mempcpy(t, p, fn - p), ".#"), extra), fn);
162
163 u = random_u64();
164 for (i = 0; i < 16; i++) {
165 *(x++) = hexchar(u & 0xF);
166 u >>= 4;
167 }
168
169 *x = 0;
170
171 *ret = path_simplify(t, false);
172 return 0;
173 }
174
175 int tempfn_random_child(const char *p, const char *extra, char **ret) {
176 char *t, *x;
177 uint64_t u;
178 unsigned i;
179 int r;
180
181 assert(ret);
182
183 /* Turns this:
184 * /foo/bar/waldo
185 * Into this:
186 * /foo/bar/waldo/.#<extra>3c2b6219aa75d7d0
187 */
188
189 if (!p) {
190 r = tmp_dir(&p);
191 if (r < 0)
192 return r;
193 }
194
195 extra = strempty(extra);
196
197 t = new(char, strlen(p) + 3 + strlen(extra) + 16 + 1);
198 if (!t)
199 return -ENOMEM;
200
201 if (isempty(p))
202 x = stpcpy(stpcpy(t, ".#"), extra);
203 else
204 x = stpcpy(stpcpy(stpcpy(t, p), "/.#"), extra);
205
206 u = random_u64();
207 for (i = 0; i < 16; i++) {
208 *(x++) = hexchar(u & 0xF);
209 u >>= 4;
210 }
211
212 *x = 0;
213
214 *ret = path_simplify(t, false);
215 return 0;
216 }
217
218 int open_tmpfile_unlinkable(const char *directory, int flags) {
219 char *p;
220 int fd, r;
221
222 if (!directory) {
223 r = tmp_dir(&directory);
224 if (r < 0)
225 return r;
226 } else if (isempty(directory))
227 return -EINVAL;
228
229 /* Returns an unlinked temporary file that cannot be linked into the file system anymore */
230
231 /* Try O_TMPFILE first, if it is supported */
232 fd = open(directory, flags|O_TMPFILE|O_EXCL, S_IRUSR|S_IWUSR);
233 if (fd >= 0)
234 return fd;
235
236 /* Fall back to unguessable name + unlinking */
237 p = strjoina(directory, "/systemd-tmp-XXXXXX");
238
239 fd = mkostemp_safe(p);
240 if (fd < 0)
241 return fd;
242
243 (void) unlink(p);
244
245 return fd;
246 }
247
248 int open_tmpfile_linkable(const char *target, int flags, char **ret_path) {
249 _cleanup_free_ char *tmp = NULL;
250 int r, fd;
251
252 assert(target);
253 assert(ret_path);
254
255 /* Don't allow O_EXCL, as that has a special meaning for O_TMPFILE */
256 assert((flags & O_EXCL) == 0);
257
258 /* Creates a temporary file, that shall be renamed to "target" later. If possible, this uses O_TMPFILE – in
259 * which case "ret_path" will be returned as NULL. If not possible a the tempoary path name used is returned in
260 * "ret_path". Use link_tmpfile() below to rename the result after writing the file in full. */
261
262 fd = open_parent(target, O_TMPFILE|flags, 0640);
263 if (fd >= 0) {
264 *ret_path = NULL;
265 return fd;
266 }
267
268 log_debug_errno(fd, "Failed to use O_TMPFILE for %s: %m", target);
269
270 r = tempfn_random(target, NULL, &tmp);
271 if (r < 0)
272 return r;
273
274 fd = open(tmp, O_CREAT|O_EXCL|O_NOFOLLOW|O_NOCTTY|flags, 0640);
275 if (fd < 0)
276 return -errno;
277
278 *ret_path = TAKE_PTR(tmp);
279
280 return fd;
281 }
282
283 int link_tmpfile(int fd, const char *path, const char *target) {
284 int r;
285
286 assert(fd >= 0);
287 assert(target);
288
289 /* Moves a temporary file created with open_tmpfile() above into its final place. if "path" is NULL an fd
290 * created with O_TMPFILE is assumed, and linkat() is used. Otherwise it is assumed O_TMPFILE is not supported
291 * on the directory, and renameat2() is used instead.
292 *
293 * Note that in both cases we will not replace existing files. This is because linkat() does not support this
294 * operation currently (renameat2() does), and there is no nice way to emulate this. */
295
296 if (path) {
297 r = rename_noreplace(AT_FDCWD, path, AT_FDCWD, target);
298 if (r < 0)
299 return r;
300 } else {
301 char proc_fd_path[STRLEN("/proc/self/fd/") + DECIMAL_STR_MAX(fd) + 1];
302
303 xsprintf(proc_fd_path, "/proc/self/fd/%i", fd);
304
305 if (linkat(AT_FDCWD, proc_fd_path, AT_FDCWD, target, AT_SYMLINK_FOLLOW) < 0)
306 return -errno;
307 }
308
309 return 0;
310 }
311
312 int mkdtemp_malloc(const char *template, char **ret) {
313 _cleanup_free_ char *p = NULL;
314 int r;
315
316 assert(ret);
317
318 if (template)
319 p = strdup(template);
320 else {
321 const char *tmp;
322
323 r = tmp_dir(&tmp);
324 if (r < 0)
325 return r;
326
327 p = strjoin(tmp, "/XXXXXX");
328 }
329 if (!p)
330 return -ENOMEM;
331
332 if (!mkdtemp(p))
333 return -errno;
334
335 *ret = TAKE_PTR(p);
336 return 0;
337 }