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