]>
Commit | Line | Data |
---|---|---|
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 | 25 | int 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, | |
e9ccae31 | 42 | FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG_SIGTERM|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 | |
94 | int 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, | |
e9ccae31 | 111 | FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG_SIGTERM|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 | |
149 | int 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 | |
271 | bool 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 | ||
282 | static 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 | ||
288 | int 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 | } |