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