1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2 
3 #include <sys/mman.h>
4 
5 #include "alloc-util.h"
6 #include "fd-util.h"
7 #include "fileio.h"
8 #include "fs-util.h"
9 #include "hexdecoct.h"
10 #include "macro.h"
11 #include "memfd-util.h"
12 #include "missing_fcntl.h"
13 #include "missing_syscall.h"
14 #include "path-util.h"
15 #include "process-util.h"
16 #include "random-util.h"
17 #include "stdio-util.h"
18 #include "string-util.h"
19 #include "tmpfile-util.h"
20 #include "umask-util.h"
21 
fopen_temporary(const char * path,FILE ** ret_f,char ** ret_temp_path)22 int fopen_temporary(const char *path, FILE **ret_f, char **ret_temp_path) {
23         _cleanup_fclose_ FILE *f = NULL;
24         _cleanup_free_ char *t = NULL;
25         _cleanup_close_ int fd = -1;
26         int r;
27 
28         if (path) {
29                 r = tempfn_xxxxxx(path, NULL, &t);
30                 if (r < 0)
31                         return r;
32         } else {
33                 const char *d;
34 
35                 r = tmp_dir(&d);
36                 if (r < 0)
37                         return r;
38 
39                 t = path_join(d, "XXXXXX");
40                 if (!t)
41                         return -ENOMEM;
42         }
43 
44         fd = mkostemp_safe(t);
45         if (fd < 0)
46                 return -errno;
47 
48         /* This assumes that returned FILE object is short-lived and used within the same single-threaded
49          * context and never shared externally, hence locking is not necessary. */
50 
51         r = take_fdopen_unlocked(&fd, "w", &f);
52         if (r < 0) {
53                 (void) unlink(t);
54                 return r;
55         }
56 
57         if (ret_f)
58                 *ret_f = TAKE_PTR(f);
59 
60         if (ret_temp_path)
61                 *ret_temp_path = TAKE_PTR(t);
62 
63         return 0;
64 }
65 
66 /* This is much like mkostemp() but is subject to umask(). */
mkostemp_safe(char * pattern)67 int mkostemp_safe(char *pattern) {
68         assert(pattern);
69         BLOCK_WITH_UMASK(0077);
70         return RET_NERRNO(mkostemp(pattern, O_CLOEXEC));
71 }
72 
fmkostemp_safe(char * pattern,const char * mode,FILE ** ret_f)73 int fmkostemp_safe(char *pattern, const char *mode, FILE **ret_f) {
74         _cleanup_close_ int fd = -1;
75         FILE *f;
76 
77         fd = mkostemp_safe(pattern);
78         if (fd < 0)
79                 return fd;
80 
81         f = take_fdopen(&fd, mode);
82         if (!f)
83                 return -errno;
84 
85         *ret_f = f;
86         return 0;
87 }
88 
tempfn_xxxxxx(const char * p,const char * extra,char ** ret)89 int tempfn_xxxxxx(const char *p, const char *extra, char **ret) {
90         _cleanup_free_ char *d = NULL, *fn = NULL, *nf = NULL;
91         int r;
92 
93         assert(ret);
94 
95         /*
96          * Turns this:
97          *         /foo/bar/waldo
98          *
99          * Into this:
100          *         /foo/bar/.#<extra>waldoXXXXXX
101          */
102 
103         r = path_extract_directory(p, &d);
104         if (r < 0 && r != -EDESTADDRREQ) /* EDESTADDRREQ → No directory specified, just a filename */
105                 return r;
106 
107         r = path_extract_filename(p, &fn);
108         if (r < 0)
109                 return r;
110 
111         nf = strjoin(".#", strempty(extra), fn, "XXXXXX");
112         if (!nf)
113                 return -ENOMEM;
114 
115         if (!filename_is_valid(nf)) /* New name is not valid? (Maybe because too long?) Refuse. */
116                 return -EINVAL;
117 
118         if (d)  {
119                 if (!path_extend(&d, nf))
120                         return -ENOMEM;
121 
122                 *ret = path_simplify(TAKE_PTR(d));
123         } else
124                 *ret = TAKE_PTR(nf);
125 
126         return 0;
127 }
128 
tempfn_random(const char * p,const char * extra,char ** ret)129 int tempfn_random(const char *p, const char *extra, char **ret) {
130         _cleanup_free_ char *d = NULL, *fn = NULL, *nf = NULL;
131         int r;
132 
133         assert(ret);
134 
135         /*
136          * Turns this:
137          *         /foo/bar/waldo
138          *
139          * Into this:
140          *         /foo/bar/.#<extra>waldobaa2a261115984a9
141          */
142 
143         r = path_extract_directory(p, &d);
144         if (r < 0 && r != -EDESTADDRREQ) /* EDESTADDRREQ → No directory specified, just a filename */
145                 return r;
146 
147         r = path_extract_filename(p, &fn);
148         if (r < 0)
149                 return r;
150 
151         if (asprintf(&nf, ".#%s%s%016" PRIx64,
152                      strempty(extra),
153                      fn,
154                      random_u64()) < 0)
155                 return -ENOMEM;
156 
157         if (!filename_is_valid(nf)) /* Not valid? (maybe because too long now?) — refuse early */
158                 return -EINVAL;
159 
160         if (d) {
161                 if (!path_extend(&d, nf))
162                         return -ENOMEM;
163 
164                 *ret = path_simplify(TAKE_PTR(d));
165         } else
166                 *ret = TAKE_PTR(nf);
167 
168         return 0;
169 }
170 
tempfn_random_child(const char * p,const char * extra,char ** ret)171 int tempfn_random_child(const char *p, const char *extra, char **ret) {
172         char *t, *x;
173         uint64_t u;
174         int r;
175 
176         assert(ret);
177 
178         /* Turns this:
179          *         /foo/bar/waldo
180          * Into this:
181          *         /foo/bar/waldo/.#<extra>3c2b6219aa75d7d0
182          */
183 
184         if (!p) {
185                 r = tmp_dir(&p);
186                 if (r < 0)
187                         return r;
188         }
189 
190         extra = strempty(extra);
191 
192         t = new(char, strlen(p) + 3 + strlen(extra) + 16 + 1);
193         if (!t)
194                 return -ENOMEM;
195 
196         if (isempty(p))
197                 x = stpcpy(stpcpy(t, ".#"), extra);
198         else
199                 x = stpcpy(stpcpy(stpcpy(t, p), "/.#"), extra);
200 
201         u = random_u64();
202         for (unsigned i = 0; i < 16; i++) {
203                 *(x++) = hexchar(u & 0xF);
204                 u >>= 4;
205         }
206 
207         *x = 0;
208 
209         *ret = path_simplify(t);
210         return 0;
211 }
212 
open_tmpfile_unlinkable(const char * directory,int flags)213 int open_tmpfile_unlinkable(const char *directory, int flags) {
214         char *p;
215         int fd, r;
216 
217         if (!directory) {
218                 r = tmp_dir(&directory);
219                 if (r < 0)
220                         return r;
221         } else if (isempty(directory))
222                 return -EINVAL;
223 
224         /* Returns an unlinked temporary file that cannot be linked into the file system anymore */
225 
226         /* Try O_TMPFILE first, if it is supported */
227         fd = open(directory, flags|O_TMPFILE|O_EXCL, S_IRUSR|S_IWUSR);
228         if (fd >= 0)
229                 return fd;
230 
231         /* Fall back to unguessable name + unlinking */
232         p = strjoina(directory, "/systemd-tmp-XXXXXX");
233 
234         fd = mkostemp_safe(p);
235         if (fd < 0)
236                 return fd;
237 
238         (void) unlink(p);
239 
240         return fd;
241 }
242 
open_tmpfile_linkable(const char * target,int flags,char ** ret_path)243 int open_tmpfile_linkable(const char *target, int flags, char **ret_path) {
244         _cleanup_free_ char *tmp = NULL;
245         int r, fd;
246 
247         assert(target);
248         assert(ret_path);
249 
250         /* Don't allow O_EXCL, as that has a special meaning for O_TMPFILE */
251         assert((flags & O_EXCL) == 0);
252 
253         /* Creates a temporary file, that shall be renamed to "target" later. If possible, this uses O_TMPFILE – in
254          * which case "ret_path" will be returned as NULL. If not possible the temporary path name used is returned in
255          * "ret_path". Use link_tmpfile() below to rename the result after writing the file in full. */
256 
257         fd = open_parent(target, O_TMPFILE|flags, 0640);
258         if (fd >= 0) {
259                 *ret_path = NULL;
260                 return fd;
261         }
262 
263         log_debug_errno(fd, "Failed to use O_TMPFILE for %s: %m", target);
264 
265         r = tempfn_random(target, NULL, &tmp);
266         if (r < 0)
267                 return r;
268 
269         fd = open(tmp, O_CREAT|O_EXCL|O_NOFOLLOW|O_NOCTTY|flags, 0640);
270         if (fd < 0)
271                 return -errno;
272 
273         *ret_path = TAKE_PTR(tmp);
274 
275         return fd;
276 }
277 
fopen_tmpfile_linkable(const char * target,int flags,char ** ret_path,FILE ** ret_file)278 int fopen_tmpfile_linkable(const char *target, int flags, char **ret_path, FILE **ret_file) {
279         _cleanup_free_ char *path = NULL;
280         _cleanup_fclose_ FILE *f = NULL;
281         _cleanup_close_ int fd = -1;
282 
283         assert(target);
284         assert(ret_file);
285         assert(ret_path);
286 
287         fd = open_tmpfile_linkable(target, flags, &path);
288         if (fd < 0)
289                 return fd;
290 
291         f = take_fdopen(&fd, "w");
292         if (!f)
293                 return -ENOMEM;
294 
295         *ret_path = TAKE_PTR(path);
296         *ret_file = TAKE_PTR(f);
297         return 0;
298 }
299 
link_tmpfile(int fd,const char * path,const char * target)300 int link_tmpfile(int fd, const char *path, const char *target) {
301         assert(fd >= 0);
302         assert(target);
303 
304         /* Moves a temporary file created with open_tmpfile() above into its final place. if "path" is NULL an fd
305          * created with O_TMPFILE is assumed, and linkat() is used. Otherwise it is assumed O_TMPFILE is not supported
306          * on the directory, and renameat2() is used instead.
307          *
308          * Note that in both cases we will not replace existing files. This is because linkat() does not support this
309          * operation currently (renameat2() does), and there is no nice way to emulate this. */
310 
311         if (path)
312                 return rename_noreplace(AT_FDCWD, path, AT_FDCWD, target);
313 
314         return RET_NERRNO(linkat(AT_FDCWD, FORMAT_PROC_FD_PATH(fd), AT_FDCWD, target, AT_SYMLINK_FOLLOW));
315 }
316 
flink_tmpfile(FILE * f,const char * path,const char * target)317 int flink_tmpfile(FILE *f, const char *path, const char *target) {
318         int fd, r;
319 
320         assert(f);
321         assert(target);
322 
323         fd = fileno(f);
324         if (fd < 0) /* Not all FILE* objects encapsulate fds */
325                 return -EBADF;
326 
327         r = fflush_sync_and_check(f);
328         if (r < 0)
329                 return r;
330 
331         return link_tmpfile(fd, path, target);
332 }
333 
mkdtemp_malloc(const char * template,char ** ret)334 int mkdtemp_malloc(const char *template, char **ret) {
335         _cleanup_free_ char *p = NULL;
336         int r;
337 
338         assert(ret);
339 
340         if (template)
341                 p = strdup(template);
342         else {
343                 const char *tmp;
344 
345                 r = tmp_dir(&tmp);
346                 if (r < 0)
347                         return r;
348 
349                 p = path_join(tmp, "XXXXXX");
350         }
351         if (!p)
352                 return -ENOMEM;
353 
354         if (!mkdtemp(p))
355                 return -errno;
356 
357         *ret = TAKE_PTR(p);
358         return 0;
359 }
360