]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/import/import-common.c
tree-wide: fix typos reported by Fossies Codespell report
[thirdparty/systemd.git] / src / import / import-common.c
1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3 #include <sched.h>
4 #include <sys/prctl.h>
5 #include <sys/stat.h>
6 #include <unistd.h>
7
8 #include "alloc-util.h"
9 #include "btrfs-util.h"
10 #include "capability-util.h"
11 #include "chattr-util.h"
12 #include "dirent-util.h"
13 #include "fd-util.h"
14 #include "fileio.h"
15 #include "fs-util.h"
16 #include "hostname-util.h"
17 #include "import-common.h"
18 #include "os-util.h"
19 #include "process-util.h"
20 #include "selinux-util.h"
21 #include "signal-util.h"
22 #include "stat-util.h"
23 #include "tmpfile-util.h"
24
25 int import_fork_tar_x(const char *path, pid_t *ret) {
26 _cleanup_close_pair_ int pipefd[2] = PIPE_EBADF;
27 bool use_selinux;
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
37 use_selinux = mac_selinux_use();
38
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);
43 if (r < 0)
44 return r;
45 if (r == 0) {
46 const char *cmdline[] = {
47 "tar",
48 "--ignore-zeros",
49 "--numeric-owner",
50 "-C", path,
51 "-pxf",
52 "-",
53 "--xattrs",
54 "--xattrs-include=*",
55 use_selinux ? "--selinux" : "--no-selinux",
56 NULL
57 };
58
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
69 if (unshare(CLONE_NEWNET) < 0)
70 log_warning_errno(errno, "Failed to lock tar into network namespace, ignoring: %m");
71
72 r = capability_bounding_set_drop(retain, true);
73 if (r < 0)
74 log_warning_errno(r, "Failed to drop capabilities, ignoring: %m");
75
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
85 log_error_errno(errno, "Failed to execute tar: %m");
86 _exit(EXIT_FAILURE);
87 }
88
89 *ret = pid;
90
91 return TAKE_FD(pipefd[1]);
92 }
93
94 int import_fork_tar_c(const char *path, pid_t *ret) {
95 _cleanup_close_pair_ int pipefd[2] = PIPE_EBADF;
96 bool use_selinux;
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
106 use_selinux = mac_selinux_use();
107
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);
112 if (r < 0)
113 return r;
114 if (r == 0) {
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
126 uint64_t retain = (1ULL << CAP_DAC_OVERRIDE);
127
128 /* Child */
129
130 if (unshare(CLONE_NEWNET) < 0)
131 log_error_errno(errno, "Failed to lock tar into network namespace, ignoring: %m");
132
133 r = capability_bounding_set_drop(retain, true);
134 if (r < 0)
135 log_error_errno(r, "Failed to drop capabilities, ignoring: %m");
136
137 execvp("gtar", (char* const*) cmdline);
138 execvp("tar", (char* const*) cmdline);
139
140 log_error_errno(errno, "Failed to execute tar: %m");
141 _exit(EXIT_FAILURE);
142 }
143
144 *ret = pid;
145
146 return TAKE_FD(pipefd[0]);
147 }
148
149 int import_mangle_os_tree(const char *path) {
150 _cleanup_free_ char *child = NULL, *t = NULL, *joined = NULL;
151 _cleanup_closedir_ DIR *d = NULL, *cd = NULL;
152 struct dirent *dent;
153 struct stat st;
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;
176 dent = readdir_no_dot(d);
177 if (!dent) {
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
185 child = strdup(dent->d_name);
186 if (!child)
187 return log_oom();
188
189 errno = 0;
190 dent = readdir_no_dot(d);
191 if (dent) {
192 if (errno != 0)
193 return log_error_errno(errno, "Failed to iterate through directory '%s': %m", path);
194
195 log_debug("Directory '%s' does not look like an OS tree, and has multiple children, leaving as it is.", path);
196 return 0;
197 }
198
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();
210 r = path_is_os_tree(joined);
211 if (r == -ENOTDIR) {
212 log_debug("Directory '%s' does not look like an OS tree, and contains a single regular file only, leaving as it is.", path);
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
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
266 log_info("Successfully rearranged OS tree.");
267
268 return 0;
269 }
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 }