]>
Commit | Line | Data |
---|---|---|
e4de7287 LP |
1 | /* SPDX-License-Identifier: LGPL-2.1+ */ |
2 | ||
41f6e627 | 3 | #include <stdio.h> |
e4de7287 LP |
4 | #include <sys/mman.h> |
5 | ||
6 | #include "alloc-util.h" | |
7 | #include "fd-util.h" | |
02e23d1a | 8 | #include "fileio.h" |
e4de7287 LP |
9 | #include "fs-util.h" |
10 | #include "hexdecoct.h" | |
11 | #include "macro.h" | |
12 | #include "memfd-util.h" | |
3ee57870 | 13 | #include "missing_fcntl.h" |
e4de7287 LP |
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 | ||
41f6e627 ZJS |
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 | ||
02e23d1a ZJS |
45 | r = fdopen_unlocked(fd, "w", &f); |
46 | if (r < 0) { | |
47 | unlink(t); | |
e4de7287 LP |
48 | free(t); |
49 | safe_close(fd); | |
02e23d1a | 50 | return r; |
e4de7287 LP |
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) { | |
f8a8579e | 61 | _unused_ _cleanup_umask_ mode_t u = umask(0077); |
e4de7287 LP |
62 | int fd; |
63 | ||
64 | assert(pattern); | |
65 | ||
e4de7287 LP |
66 | fd = mkostemp(pattern, O_CLOEXEC); |
67 | if (fd < 0) | |
68 | return -errno; | |
69 | ||
70 | return fd; | |
71 | } | |
72 | ||
73 | int fmkostemp_safe(char *pattern, const char *mode, FILE **ret_f) { | |
74 | int fd; | |
75 | FILE *f; | |
76 | ||
77 | fd = mkostemp_safe(pattern); | |
78 | if (fd < 0) | |
79 | return fd; | |
80 | ||
81 | f = fdopen(fd, mode); | |
82 | if (!f) { | |
83 | safe_close(fd); | |
84 | return -errno; | |
85 | } | |
86 | ||
87 | *ret_f = f; | |
88 | return 0; | |
89 | } | |
90 | ||
91 | int tempfn_xxxxxx(const char *p, const char *extra, char **ret) { | |
92 | const char *fn; | |
93 | char *t; | |
94 | ||
95 | assert(ret); | |
96 | ||
97 | if (isempty(p)) | |
98 | return -EINVAL; | |
99 | if (path_equal(p, "/")) | |
100 | return -EINVAL; | |
101 | ||
102 | /* | |
103 | * Turns this: | |
104 | * /foo/bar/waldo | |
105 | * | |
106 | * Into this: | |
107 | * /foo/bar/.#<extra>waldoXXXXXX | |
108 | */ | |
109 | ||
110 | fn = basename(p); | |
111 | if (!filename_is_valid(fn)) | |
112 | return -EINVAL; | |
113 | ||
114 | extra = strempty(extra); | |
115 | ||
116 | t = new(char, strlen(p) + 2 + strlen(extra) + 6 + 1); | |
117 | if (!t) | |
118 | return -ENOMEM; | |
119 | ||
120 | strcpy(stpcpy(stpcpy(stpcpy(mempcpy(t, p, fn - p), ".#"), extra), fn), "XXXXXX"); | |
121 | ||
122 | *ret = path_simplify(t, false); | |
123 | return 0; | |
124 | } | |
125 | ||
126 | 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 | assert(ret); | |
133 | ||
134 | if (isempty(p)) | |
135 | return -EINVAL; | |
136 | if (path_equal(p, "/")) | |
137 | return -EINVAL; | |
138 | ||
139 | /* | |
140 | * Turns this: | |
141 | * /foo/bar/waldo | |
142 | * | |
143 | * Into this: | |
144 | * /foo/bar/.#<extra>waldobaa2a261115984a9 | |
145 | */ | |
146 | ||
147 | fn = basename(p); | |
148 | if (!filename_is_valid(fn)) | |
149 | return -EINVAL; | |
150 | ||
151 | extra = strempty(extra); | |
152 | ||
153 | t = new(char, strlen(p) + 2 + strlen(extra) + 16 + 1); | |
154 | if (!t) | |
155 | return -ENOMEM; | |
156 | ||
157 | x = stpcpy(stpcpy(stpcpy(mempcpy(t, p, fn - p), ".#"), extra), fn); | |
158 | ||
159 | u = random_u64(); | |
160 | for (i = 0; i < 16; i++) { | |
161 | *(x++) = hexchar(u & 0xF); | |
162 | u >>= 4; | |
163 | } | |
164 | ||
165 | *x = 0; | |
166 | ||
167 | *ret = path_simplify(t, false); | |
168 | return 0; | |
169 | } | |
170 | ||
171 | 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 | assert(ret); | |
178 | ||
179 | /* Turns this: | |
180 | * /foo/bar/waldo | |
181 | * Into this: | |
182 | * /foo/bar/waldo/.#<extra>3c2b6219aa75d7d0 | |
183 | */ | |
184 | ||
185 | if (!p) { | |
186 | r = tmp_dir(&p); | |
187 | if (r < 0) | |
188 | return r; | |
189 | } | |
190 | ||
191 | extra = strempty(extra); | |
192 | ||
193 | t = new(char, strlen(p) + 3 + strlen(extra) + 16 + 1); | |
194 | if (!t) | |
195 | return -ENOMEM; | |
196 | ||
197 | if (isempty(p)) | |
198 | x = stpcpy(stpcpy(t, ".#"), extra); | |
199 | else | |
200 | x = stpcpy(stpcpy(stpcpy(t, p), "/.#"), extra); | |
201 | ||
202 | u = random_u64(); | |
203 | for (i = 0; i < 16; i++) { | |
204 | *(x++) = hexchar(u & 0xF); | |
205 | u >>= 4; | |
206 | } | |
207 | ||
208 | *x = 0; | |
209 | ||
210 | *ret = path_simplify(t, false); | |
211 | return 0; | |
212 | } | |
213 | ||
214 | int open_tmpfile_unlinkable(const char *directory, int flags) { | |
215 | char *p; | |
216 | int fd, r; | |
217 | ||
218 | if (!directory) { | |
219 | r = tmp_dir(&directory); | |
220 | if (r < 0) | |
221 | return r; | |
222 | } else if (isempty(directory)) | |
223 | 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 | fd = open(directory, flags|O_TMPFILE|O_EXCL, S_IRUSR|S_IWUSR); | |
229 | if (fd >= 0) | |
230 | return fd; | |
231 | ||
232 | /* Fall back to unguessable name + unlinking */ | |
233 | p = strjoina(directory, "/systemd-tmp-XXXXXX"); | |
234 | ||
235 | fd = mkostemp_safe(p); | |
236 | if (fd < 0) | |
237 | return fd; | |
238 | ||
239 | (void) unlink(p); | |
240 | ||
241 | return fd; | |
242 | } | |
243 | ||
244 | int open_tmpfile_linkable(const char *target, int flags, char **ret_path) { | |
245 | _cleanup_free_ char *tmp = NULL; | |
246 | int r, fd; | |
247 | ||
248 | assert(target); | |
249 | assert(ret_path); | |
250 | ||
251 | /* Don't allow O_EXCL, as that has a special meaning for O_TMPFILE */ | |
252 | 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 | fd = open_parent(target, O_TMPFILE|flags, 0640); | |
259 | if (fd >= 0) { | |
260 | *ret_path = NULL; | |
261 | return fd; | |
262 | } | |
263 | ||
264 | log_debug_errno(fd, "Failed to use O_TMPFILE for %s: %m", target); | |
265 | ||
266 | r = tempfn_random(target, NULL, &tmp); | |
267 | if (r < 0) | |
268 | return r; | |
269 | ||
270 | fd = open(tmp, O_CREAT|O_EXCL|O_NOFOLLOW|O_NOCTTY|flags, 0640); | |
271 | if (fd < 0) | |
272 | return -errno; | |
273 | ||
274 | *ret_path = TAKE_PTR(tmp); | |
275 | ||
276 | return fd; | |
277 | } | |
278 | ||
279 | int link_tmpfile(int fd, const char *path, const char *target) { | |
280 | int r; | |
281 | ||
282 | assert(fd >= 0); | |
283 | 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 | if (path) { | |
293 | r = rename_noreplace(AT_FDCWD, path, AT_FDCWD, target); | |
294 | if (r < 0) | |
295 | return r; | |
296 | } else { | |
297 | char proc_fd_path[STRLEN("/proc/self/fd/") + DECIMAL_STR_MAX(fd) + 1]; | |
298 | ||
299 | xsprintf(proc_fd_path, "/proc/self/fd/%i", fd); | |
300 | ||
301 | if (linkat(AT_FDCWD, proc_fd_path, AT_FDCWD, target, AT_SYMLINK_FOLLOW) < 0) | |
302 | return -errno; | |
303 | } | |
304 | ||
305 | return 0; | |
306 | } | |
307 | ||
308 | int mkdtemp_malloc(const char *template, char **ret) { | |
309 | _cleanup_free_ char *p = NULL; | |
310 | int r; | |
311 | ||
312 | assert(ret); | |
313 | ||
314 | if (template) | |
315 | p = strdup(template); | |
316 | else { | |
317 | const char *tmp; | |
318 | ||
319 | r = tmp_dir(&tmp); | |
320 | if (r < 0) | |
321 | return r; | |
322 | ||
657ee2d8 | 323 | p = path_join(tmp, "XXXXXX"); |
e4de7287 LP |
324 | } |
325 | if (!p) | |
326 | return -ENOMEM; | |
327 | ||
328 | if (!mkdtemp(p)) | |
329 | return -errno; | |
330 | ||
331 | *ret = TAKE_PTR(p); | |
332 | return 0; | |
333 | } |