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