Branch data 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 : 20 : int fopen_temporary(const char *path, FILE **_f, char **_temp_path) {
24 : : FILE *f;
25 : : char *t;
26 : : int r, fd;
27 : :
28 [ - + ]: 20 : assert(path);
29 [ - + ]: 20 : assert(_f);
30 [ - + ]: 20 : assert(_temp_path);
31 : :
32 : 20 : r = tempfn_xxxxxx(path, NULL, &t);
33 [ - + ]: 20 : if (r < 0)
34 : 0 : return r;
35 : :
36 : 20 : fd = mkostemp_safe(t);
37 [ - + ]: 20 : 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 : 20 : r = fdopen_unlocked(fd, "w", &f);
46 [ - + ]: 20 : if (r < 0) {
47 : 0 : unlink(t);
48 : 0 : free(t);
49 : 0 : safe_close(fd);
50 : 0 : return r;
51 : : }
52 : :
53 : 20 : *_f = f;
54 : 20 : *_temp_path = t;
55 : :
56 : 20 : return 0;
57 : : }
58 : :
59 : : /* This is much like mkostemp() but is subject to umask(). */
60 : 492 : int mkostemp_safe(char *pattern) {
61 : 984 : _unused_ _cleanup_umask_ mode_t u = umask(0077);
62 : : int fd;
63 : :
64 [ - + ]: 492 : assert(pattern);
65 : :
66 : 492 : fd = mkostemp(pattern, O_CLOEXEC);
67 [ - + ]: 492 : if (fd < 0)
68 : 0 : return -errno;
69 : :
70 : 492 : return fd;
71 : : }
72 : :
73 : 116 : int fmkostemp_safe(char *pattern, const char *mode, FILE **ret_f) {
74 : : int fd;
75 : : FILE *f;
76 : :
77 : 116 : fd = mkostemp_safe(pattern);
78 [ - + ]: 116 : if (fd < 0)
79 : 0 : return fd;
80 : :
81 : 116 : f = fdopen(fd, mode);
82 [ - + ]: 116 : if (!f) {
83 : 0 : safe_close(fd);
84 : 0 : return -errno;
85 : : }
86 : :
87 : 116 : *ret_f = f;
88 : 116 : return 0;
89 : : }
90 : :
91 : 28 : int tempfn_xxxxxx(const char *p, const char *extra, char **ret) {
92 : : const char *fn;
93 : : char *t;
94 : :
95 [ - + ]: 28 : assert(ret);
96 : :
97 [ - + ]: 28 : if (isempty(p))
98 : 0 : return -EINVAL;
99 [ - + ]: 28 : 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 : 28 : fn = basename(p);
111 [ - + ]: 28 : if (!filename_is_valid(fn))
112 : 0 : return -EINVAL;
113 : :
114 : 28 : extra = strempty(extra);
115 : :
116 : 28 : t = new(char, strlen(p) + 2 + strlen(extra) + 6 + 1);
117 [ - + ]: 28 : if (!t)
118 : 0 : return -ENOMEM;
119 : :
120 : 28 : strcpy(stpcpy(stpcpy(stpcpy(mempcpy(t, p, fn - p), ".#"), extra), fn), "XXXXXX");
121 : :
122 : 28 : *ret = path_simplify(t, false);
123 : 28 : return 0;
124 : : }
125 : :
126 : 16 : 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 [ - + ]: 16 : assert(ret);
133 : :
134 [ - + ]: 16 : if (isempty(p))
135 : 0 : return -EINVAL;
136 [ - + ]: 16 : 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 : 16 : fn = basename(p);
148 [ - + ]: 16 : if (!filename_is_valid(fn))
149 : 0 : return -EINVAL;
150 : :
151 : 16 : extra = strempty(extra);
152 : :
153 : 16 : t = new(char, strlen(p) + 2 + strlen(extra) + 16 + 1);
154 [ - + ]: 16 : if (!t)
155 : 0 : return -ENOMEM;
156 : :
157 : 16 : x = stpcpy(stpcpy(stpcpy(mempcpy(t, p, fn - p), ".#"), extra), fn);
158 : :
159 : 16 : u = random_u64();
160 [ + + ]: 272 : for (i = 0; i < 16; i++) {
161 : 256 : *(x++) = hexchar(u & 0xF);
162 : 256 : u >>= 4;
163 : : }
164 : :
165 : 16 : *x = 0;
166 : :
167 : 16 : *ret = path_simplify(t, false);
168 : 16 : return 0;
169 : : }
170 : :
171 : 12 : 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 [ - + ]: 12 : assert(ret);
178 : :
179 : : /* Turns this:
180 : : * /foo/bar/waldo
181 : : * Into this:
182 : : * /foo/bar/waldo/.#<extra>3c2b6219aa75d7d0
183 : : */
184 : :
185 [ + + ]: 12 : if (!p) {
186 : 4 : r = tmp_dir(&p);
187 [ - + ]: 4 : if (r < 0)
188 : 0 : return r;
189 : : }
190 : :
191 : 12 : extra = strempty(extra);
192 : :
193 : 12 : t = new(char, strlen(p) + 3 + strlen(extra) + 16 + 1);
194 [ - + ]: 12 : if (!t)
195 : 0 : return -ENOMEM;
196 : :
197 [ - + ]: 12 : if (isempty(p))
198 : 0 : x = stpcpy(stpcpy(t, ".#"), extra);
199 : : else
200 : 12 : x = stpcpy(stpcpy(stpcpy(t, p), "/.#"), extra);
201 : :
202 : 12 : u = random_u64();
203 [ + + ]: 204 : for (i = 0; i < 16; i++) {
204 : 192 : *(x++) = hexchar(u & 0xF);
205 : 192 : u >>= 4;
206 : : }
207 : :
208 : 12 : *x = 0;
209 : :
210 : 12 : *ret = path_simplify(t, false);
211 : 12 : return 0;
212 : : }
213 : :
214 : 36 : int open_tmpfile_unlinkable(const char *directory, int flags) {
215 : : char *p;
216 : : int fd, r;
217 : :
218 [ + + ]: 36 : if (!directory) {
219 : 12 : r = tmp_dir(&directory);
220 [ - + ]: 12 : if (r < 0)
221 : 0 : return r;
222 [ - + ]: 24 : } 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 : 36 : fd = open(directory, flags|O_TMPFILE|O_EXCL, S_IRUSR|S_IWUSR);
229 [ + - ]: 36 : if (fd >= 0)
230 : 36 : 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 : 12 : int open_tmpfile_linkable(const char *target, int flags, char **ret_path) {
245 : 12 : _cleanup_free_ char *tmp = NULL;
246 : : int r, fd;
247 : :
248 [ - + ]: 12 : assert(target);
249 [ - + ]: 12 : assert(ret_path);
250 : :
251 : : /* Don't allow O_EXCL, as that has a special meaning for O_TMPFILE */
252 [ - + ]: 12 : 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 : 12 : fd = open_parent(target, O_TMPFILE|flags, 0640);
259 [ + - ]: 12 : if (fd >= 0) {
260 : 12 : *ret_path = NULL;
261 : 12 : 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 : 16 : int link_tmpfile(int fd, const char *path, const char *target) {
280 : : int r;
281 : :
282 [ - + ]: 16 : assert(fd >= 0);
283 [ - + ]: 16 : 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 [ - + ]: 16 : 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 [ - + ]: 16 : xsprintf(proc_fd_path, "/proc/self/fd/%i", fd);
300 : :
301 [ + + ]: 16 : if (linkat(AT_FDCWD, proc_fd_path, AT_FDCWD, target, AT_SYMLINK_FOLLOW) < 0)
302 : 8 : return -errno;
303 : : }
304 : :
305 : 8 : return 0;
306 : : }
307 : :
308 : 84 : int mkdtemp_malloc(const char *template, char **ret) {
309 : 84 : _cleanup_free_ char *p = NULL;
310 : : int r;
311 : :
312 [ - + ]: 84 : assert(ret);
313 : :
314 [ + + ]: 84 : if (template)
315 : 76 : p = strdup(template);
316 : : else {
317 : : const char *tmp;
318 : :
319 : 8 : r = tmp_dir(&tmp);
320 [ - + ]: 8 : if (r < 0)
321 : 0 : return r;
322 : :
323 : 8 : p = path_join(tmp, "XXXXXX");
324 : : }
325 [ - + ]: 84 : if (!p)
326 : 0 : return -ENOMEM;
327 : :
328 [ - + ]: 84 : if (!mkdtemp(p))
329 : 0 : return -errno;
330 : :
331 : 84 : *ret = TAKE_PTR(p);
332 : 84 : return 0;
333 : : }
|