]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/basic/tmpfile-util.c
shared/user-util: add compat forms of user name checking functions
[thirdparty/systemd.git] / src / basic / tmpfile-util.c
CommitLineData
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
23int 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(). */
60int 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
73int 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
91int 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
126int 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
171int 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
214int 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
244int 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
279int 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
308int 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}