]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/import/import-common.c
fd-uitl: rename PIPE_EBADF → EBADF_PAIR, and add EBADF_TRIPLET
[thirdparty/systemd.git] / src / import / import-common.c
CommitLineData
db9ecf05 1/* SPDX-License-Identifier: LGPL-2.1-or-later */
b6e676ce 2
618234a5 3#include <sched.h>
b6e676ce
LP
4#include <sys/prctl.h>
5#include <sys/stat.h>
6#include <unistd.h>
7
e21b7229 8#include "alloc-util.h"
b6e676ce 9#include "btrfs-util.h"
430f0182 10#include "capability-util.h"
d8f9686c 11#include "chattr-util.h"
e21b7229 12#include "dirent-util.h"
3ffd4af2 13#include "fd-util.h"
e21b7229
LP
14#include "fileio.h"
15#include "fs-util.h"
d32a5841 16#include "hostname-util.h"
3ffd4af2 17#include "import-common.h"
e21b7229 18#include "os-util.h"
dccca82b 19#include "process-util.h"
0a8321d3 20#include "selinux-util.h"
24882e06 21#include "signal-util.h"
0ca04e5f 22#include "stat-util.h"
e4de7287 23#include "tmpfile-util.h"
b6e676ce 24
587fec42 25int import_fork_tar_x(const char *path, pid_t *ret) {
71136404 26 _cleanup_close_pair_ int pipefd[2] = EBADF_PAIR;
0a8321d3 27 bool use_selinux;
b6e676ce
LP
28 pid_t pid;
29 int r;
30
31 assert(path);
32 assert(ret);
33
34 if (pipe2(pipefd, O_CLOEXEC) < 0)
35 return log_error_errno(errno, "Failed to create pipe for tar: %m");
36
0a8321d3
YW
37 use_selinux = mac_selinux_use();
38
0c2aedb4
YW
39 r = safe_fork_full("(tar)",
40 (int[]) { pipefd[0], -EBADF, STDERR_FILENO },
41 NULL, 0,
42 FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG|FORK_REARRANGE_STDIO|FORK_LOG, &pid);
4c253ed1 43 if (r < 0)
b6e1fff1 44 return r;
4c253ed1 45 if (r == 0) {
c400d040
LP
46 const char *cmdline[] = {
47 "tar",
e4ec7820 48 "--ignore-zeros",
c400d040
LP
49 "--numeric-owner",
50 "-C", path,
181eea67
AA
51 "-pxf",
52 "-",
c400d040
LP
53 "--xattrs",
54 "--xattrs-include=*",
55 use_selinux ? "--selinux" : "--no-selinux",
56 NULL
57 };
58
b6e676ce
LP
59 uint64_t retain =
60 (1ULL << CAP_CHOWN) |
61 (1ULL << CAP_FOWNER) |
62 (1ULL << CAP_FSETID) |
63 (1ULL << CAP_MKNOD) |
64 (1ULL << CAP_SETFCAP) |
65 (1ULL << CAP_DAC_OVERRIDE);
66
67 /* Child */
68
b6e676ce 69 if (unshare(CLONE_NEWNET) < 0)
6a117acf 70 log_warning_errno(errno, "Failed to lock tar into network namespace, ignoring: %m");
b6e676ce 71
a103496c 72 r = capability_bounding_set_drop(retain, true);
b6e676ce 73 if (r < 0)
6a117acf 74 log_warning_errno(r, "Failed to drop capabilities, ignoring: %m");
b6e676ce 75
c400d040
LP
76 /* Try "gtar" before "tar". We only test things upstream with GNU tar. Some distros appear to
77 * install a different implementation as "tar" (in particular some that do not support the
78 * same command line switches), but then provide "gtar" as alias for the real thing, hence
79 * let's prefer that. (Yes, it's a bad idea they do that, given they don't provide equivalent
80 * command line support, but we are not here to argue, let's just expose the same
81 * behaviour/implementation everywhere.) */
82 execvp("gtar", (char* const*) cmdline);
83 execvp("tar", (char* const*) cmdline);
84
b6e676ce
LP
85 log_error_errno(errno, "Failed to execute tar: %m");
86 _exit(EXIT_FAILURE);
87 }
88
b6e676ce
LP
89 *ret = pid;
90
c10d6bdb 91 return TAKE_FD(pipefd[1]);
b6e676ce 92}
587fec42
LP
93
94int import_fork_tar_c(const char *path, pid_t *ret) {
71136404 95 _cleanup_close_pair_ int pipefd[2] = EBADF_PAIR;
0a8321d3 96 bool use_selinux;
587fec42
LP
97 pid_t pid;
98 int r;
99
100 assert(path);
101 assert(ret);
102
103 if (pipe2(pipefd, O_CLOEXEC) < 0)
104 return log_error_errno(errno, "Failed to create pipe for tar: %m");
105
0a8321d3
YW
106 use_selinux = mac_selinux_use();
107
0c2aedb4
YW
108 r = safe_fork_full("(tar)",
109 (int[]) { -EBADF, pipefd[1], STDERR_FILENO },
110 NULL, 0,
111 FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG|FORK_REARRANGE_STDIO|FORK_LOG, &pid);
4c253ed1 112 if (r < 0)
b6e1fff1 113 return r;
4c253ed1 114 if (r == 0) {
c400d040
LP
115 const char *cmdline[] = {
116 "tar",
117 "-C", path,
118 "-c",
119 "--xattrs",
120 "--xattrs-include=*",
121 use_selinux ? "--selinux" : "--no-selinux",
122 ".",
123 NULL
124 };
125
587fec42
LP
126 uint64_t retain = (1ULL << CAP_DAC_OVERRIDE);
127
128 /* Child */
129
587fec42
LP
130 if (unshare(CLONE_NEWNET) < 0)
131 log_error_errno(errno, "Failed to lock tar into network namespace, ignoring: %m");
132
a103496c 133 r = capability_bounding_set_drop(retain, true);
587fec42
LP
134 if (r < 0)
135 log_error_errno(r, "Failed to drop capabilities, ignoring: %m");
136
c400d040
LP
137 execvp("gtar", (char* const*) cmdline);
138 execvp("tar", (char* const*) cmdline);
139
587fec42
LP
140 log_error_errno(errno, "Failed to execute tar: %m");
141 _exit(EXIT_FAILURE);
142 }
143
587fec42
LP
144 *ret = pid;
145
c10d6bdb 146 return TAKE_FD(pipefd[0]);
587fec42 147}
e21b7229
LP
148
149int import_mangle_os_tree(const char *path) {
0ca04e5f 150 _cleanup_free_ char *child = NULL, *t = NULL, *joined = NULL;
e21b7229 151 _cleanup_closedir_ DIR *d = NULL, *cd = NULL;
c7f0d9e5 152 struct dirent *dent;
0ca04e5f 153 struct stat st;
e21b7229
LP
154 int r;
155
156 assert(path);
157
158 /* Some tarballs contain a single top-level directory that contains the actual OS directory tree. Try to
159 * recognize this, and move the tree one level up. */
160
161 r = path_is_os_tree(path);
162 if (r < 0)
163 return log_error_errno(r, "Failed to determine whether '%s' is an OS tree: %m", path);
164 if (r > 0) {
165 log_debug("Directory tree '%s' is a valid OS tree.", path);
166 return 0;
167 }
168
169 log_debug("Directory tree '%s' is not recognizable as OS tree, checking whether to rearrange it.", path);
170
171 d = opendir(path);
172 if (!d)
173 return log_error_errno(r, "Failed to open directory '%s': %m", path);
174
175 errno = 0;
c7f0d9e5
ZJS
176 dent = readdir_no_dot(d);
177 if (!dent) {
e21b7229
LP
178 if (errno != 0)
179 return log_error_errno(errno, "Failed to iterate through directory '%s': %m", path);
180
181 log_debug("Directory '%s' is empty, leaving it as it is.", path);
182 return 0;
183 }
184
c7f0d9e5 185 child = strdup(dent->d_name);
e21b7229
LP
186 if (!child)
187 return log_oom();
188
189 errno = 0;
c7f0d9e5
ZJS
190 dent = readdir_no_dot(d);
191 if (dent) {
e21b7229
LP
192 if (errno != 0)
193 return log_error_errno(errno, "Failed to iterate through directory '%s': %m", path);
194
dda859f8 195 log_debug("Directory '%s' does not look like an OS tree, and has multiple children, leaving as it is.", path);
e21b7229
LP
196 return 0;
197 }
198
0ca04e5f
LP
199 if (fstatat(dirfd(d), child, &st, AT_SYMLINK_NOFOLLOW) < 0)
200 return log_debug_errno(errno, "Failed to stat file '%s/%s': %m", path, child);
201 r = stat_verify_directory(&st);
202 if (r < 0) {
203 log_debug_errno(r, "Child '%s' of directory '%s' is not a directory, leaving things as they are.", child, path);
204 return 0;
205 }
206
207 joined = path_join(path, child);
208 if (!joined)
209 return log_oom();
e21b7229
LP
210 r = path_is_os_tree(joined);
211 if (r == -ENOTDIR) {
dda859f8 212 log_debug("Directory '%s' does not look like an OS tree, and contains a single regular file only, leaving as it is.", path);
e21b7229
LP
213 return 0;
214 }
215 if (r < 0)
216 return log_error_errno(r, "Failed to determine whether '%s' is an OS tree: %m", joined);
217 if (r == 0) {
218 log_debug("Neither '%s' nor '%s' is a valid OS tree, leaving them as they are.", path, joined);
219 return 0;
220 }
221
222 /* Nice, we have checked now:
223 *
224 * 1. The top-level directory does not qualify as OS tree
225 * 1. The top-level directory only contains one item
226 * 2. That item is a directory
227 * 3. And that directory qualifies as OS tree
228 *
229 * Let's now rearrange things, moving everything in the inner directory one level up */
230
231 cd = xopendirat(dirfd(d), child, O_NOFOLLOW);
232 if (!cd)
233 return log_error_errno(errno, "Can't open directory '%s': %m", joined);
234
235 log_info("Rearranging '%s', moving OS tree one directory up.", joined);
236
237 /* Let's rename the child to an unguessable name so that we can be sure all files contained in it can be
238 * safely moved up and won't collide with the name. */
239 r = tempfn_random(child, NULL, &t);
240 if (r < 0)
241 return log_oom();
242 r = rename_noreplace(dirfd(d), child, dirfd(d), t);
243 if (r < 0)
244 return log_error_errno(r, "Unable to rename '%s' to '%s/%s': %m", joined, path, t);
245
246 FOREACH_DIRENT_ALL(de, cd, return log_error_errno(errno, "Failed to iterate through directory '%s': %m", joined)) {
247 if (dot_or_dot_dot(de->d_name))
248 continue;
249
250 r = rename_noreplace(dirfd(cd), de->d_name, dirfd(d), de->d_name);
251 if (r < 0)
252 return log_error_errno(r, "Unable to move '%s/%s/%s' to '%s/%s': %m", path, t, de->d_name, path, de->d_name);
253 }
254
255 if (unlinkat(dirfd(d), t, AT_REMOVEDIR) < 0)
256 return log_error_errno(errno, "Failed to remove temporary directory '%s/%s': %m", path, t);
257
0ca04e5f
LP
258 r = futimens(dirfd(d), (struct timespec[2]) { st.st_atim, st.st_mtim });
259 if (r < 0)
260 log_debug_errno(r, "Failed to adjust top-level timestamps '%s', ignoring: %m", path);
261
262 r = fchmod_and_chown(dirfd(d), st.st_mode, st.st_uid, st.st_gid);
263 if (r < 0)
264 return log_error_errno(r, "Failed to adjust top-level directory mode/ownership '%s': %m", path);
265
e21b7229
LP
266 log_info("Successfully rearranged OS tree.");
267
268 return 0;
269}
d32a5841
LP
270
271bool import_validate_local(const char *name, ImportFlags flags) {
272
273 /* By default we insist on a valid hostname for naming images. But optionally we relax that, in which
274 * case it can be any path name */
275
276 if (FLAGS_SET(flags, IMPORT_DIRECT))
277 return path_is_valid(name);
278
279 return hostname_is_valid(name, 0);
280}
281
282static int interrupt_signal_handler(sd_event_source *s, const struct signalfd_siginfo *si, void *userdata) {
283 log_notice("Transfer aborted.");
284 sd_event_exit(sd_event_source_get_event(s), EINTR);
285 return 0;
286}
287
288int import_allocate_event_with_signals(sd_event **ret) {
289 _cleanup_(sd_event_unrefp) sd_event *event = NULL;
290 int r;
291
292 assert(ret);
293
294 r = sd_event_default(&event);
295 if (r < 0)
296 return log_error_errno(r, "Failed to allocate event loop: %m");
297
298 assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGTERM, SIGINT, -1) >= 0);
299 (void) sd_event_add_signal(event, NULL, SIGTERM, interrupt_signal_handler, NULL);
300 (void) sd_event_add_signal(event, NULL, SIGINT, interrupt_signal_handler, NULL);
301
302 *ret = TAKE_PTR(event);
303 return 0;
304}