]>
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) { | |
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 | } |