]>
Commit | Line | Data |
---|---|---|
53e1b683 | 1 | /* SPDX-License-Identifier: LGPL-2.1+ */ |
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" |
e21b7229 | 11 | #include "dirent-util.h" |
3ffd4af2 | 12 | #include "fd-util.h" |
e21b7229 LP |
13 | #include "fileio.h" |
14 | #include "fs-util.h" | |
3ffd4af2 | 15 | #include "import-common.h" |
e21b7229 | 16 | #include "os-util.h" |
dccca82b | 17 | #include "process-util.h" |
0a8321d3 | 18 | #include "selinux-util.h" |
24882e06 | 19 | #include "signal-util.h" |
e4de7287 | 20 | #include "tmpfile-util.h" |
618234a5 | 21 | #include "util.h" |
b6e676ce LP |
22 | |
23 | int import_make_read_only_fd(int fd) { | |
24 | int r; | |
25 | ||
26 | assert(fd >= 0); | |
27 | ||
28 | /* First, let's make this a read-only subvolume if it refers | |
29 | * to a subvolume */ | |
30 | r = btrfs_subvol_set_read_only_fd(fd, true); | |
4c701096 | 31 | if (IN_SET(r, -ENOTTY, -ENOTDIR, -EINVAL)) { |
b6e676ce LP |
32 | struct stat st; |
33 | ||
34 | /* This doesn't refer to a subvolume, or the file | |
35 | * system isn't even btrfs. In that, case fall back to | |
36 | * chmod()ing */ | |
37 | ||
38 | r = fstat(fd, &st); | |
39 | if (r < 0) | |
40 | return log_error_errno(errno, "Failed to stat temporary image: %m"); | |
41 | ||
42 | /* Drop "w" flag */ | |
43 | if (fchmod(fd, st.st_mode & 07555) < 0) | |
44 | return log_error_errno(errno, "Failed to chmod() final image: %m"); | |
45 | ||
46 | return 0; | |
47 | ||
48 | } else if (r < 0) | |
49 | return log_error_errno(r, "Failed to make subvolume read-only: %m"); | |
50 | ||
51 | return 0; | |
52 | } | |
53 | ||
54 | int import_make_read_only(const char *path) { | |
55 | _cleanup_close_ int fd = 1; | |
56 | ||
57 | fd = open(path, O_RDONLY|O_NOCTTY|O_CLOEXEC); | |
58 | if (fd < 0) | |
59 | return log_error_errno(errno, "Failed to open %s: %m", path); | |
60 | ||
61 | return import_make_read_only_fd(fd); | |
62 | } | |
63 | ||
587fec42 | 64 | int import_fork_tar_x(const char *path, pid_t *ret) { |
b6e676ce | 65 | _cleanup_close_pair_ int pipefd[2] = { -1, -1 }; |
0a8321d3 | 66 | bool use_selinux; |
b6e676ce LP |
67 | pid_t pid; |
68 | int r; | |
69 | ||
70 | assert(path); | |
71 | assert(ret); | |
72 | ||
73 | if (pipe2(pipefd, O_CLOEXEC) < 0) | |
74 | return log_error_errno(errno, "Failed to create pipe for tar: %m"); | |
75 | ||
0a8321d3 YW |
76 | use_selinux = mac_selinux_use(); |
77 | ||
b6e1fff1 | 78 | r = safe_fork("(tar)", FORK_RESET_SIGNALS|FORK_DEATHSIG|FORK_LOG, &pid); |
4c253ed1 | 79 | if (r < 0) |
b6e1fff1 | 80 | return r; |
4c253ed1 | 81 | if (r == 0) { |
c400d040 LP |
82 | const char *cmdline[] = { |
83 | "tar", | |
84 | "--numeric-owner", | |
85 | "-C", path, | |
86 | "-px", | |
87 | "--xattrs", | |
88 | "--xattrs-include=*", | |
89 | use_selinux ? "--selinux" : "--no-selinux", | |
90 | NULL | |
91 | }; | |
92 | ||
b6e676ce LP |
93 | uint64_t retain = |
94 | (1ULL << CAP_CHOWN) | | |
95 | (1ULL << CAP_FOWNER) | | |
96 | (1ULL << CAP_FSETID) | | |
97 | (1ULL << CAP_MKNOD) | | |
98 | (1ULL << CAP_SETFCAP) | | |
99 | (1ULL << CAP_DAC_OVERRIDE); | |
100 | ||
101 | /* Child */ | |
102 | ||
b6e676ce LP |
103 | pipefd[1] = safe_close(pipefd[1]); |
104 | ||
2b33ab09 | 105 | r = rearrange_stdio(pipefd[0], -1, STDERR_FILENO); |
046a82c1 | 106 | if (r < 0) { |
2b33ab09 | 107 | log_error_errno(r, "Failed to rearrange stdin/stdout: %m"); |
b6e676ce LP |
108 | _exit(EXIT_FAILURE); |
109 | } | |
110 | ||
b6e676ce LP |
111 | if (unshare(CLONE_NEWNET) < 0) |
112 | log_error_errno(errno, "Failed to lock tar into network namespace, ignoring: %m"); | |
113 | ||
a103496c | 114 | r = capability_bounding_set_drop(retain, true); |
b6e676ce LP |
115 | if (r < 0) |
116 | log_error_errno(r, "Failed to drop capabilities, ignoring: %m"); | |
117 | ||
c400d040 LP |
118 | /* Try "gtar" before "tar". We only test things upstream with GNU tar. Some distros appear to |
119 | * install a different implementation as "tar" (in particular some that do not support the | |
120 | * same command line switches), but then provide "gtar" as alias for the real thing, hence | |
121 | * let's prefer that. (Yes, it's a bad idea they do that, given they don't provide equivalent | |
122 | * command line support, but we are not here to argue, let's just expose the same | |
123 | * behaviour/implementation everywhere.) */ | |
124 | execvp("gtar", (char* const*) cmdline); | |
125 | execvp("tar", (char* const*) cmdline); | |
126 | ||
b6e676ce LP |
127 | log_error_errno(errno, "Failed to execute tar: %m"); |
128 | _exit(EXIT_FAILURE); | |
129 | } | |
130 | ||
b6e676ce LP |
131 | *ret = pid; |
132 | ||
c10d6bdb | 133 | return TAKE_FD(pipefd[1]); |
b6e676ce | 134 | } |
587fec42 LP |
135 | |
136 | int import_fork_tar_c(const char *path, pid_t *ret) { | |
137 | _cleanup_close_pair_ int pipefd[2] = { -1, -1 }; | |
0a8321d3 | 138 | bool use_selinux; |
587fec42 LP |
139 | pid_t pid; |
140 | int r; | |
141 | ||
142 | assert(path); | |
143 | assert(ret); | |
144 | ||
145 | if (pipe2(pipefd, O_CLOEXEC) < 0) | |
146 | return log_error_errno(errno, "Failed to create pipe for tar: %m"); | |
147 | ||
0a8321d3 YW |
148 | use_selinux = mac_selinux_use(); |
149 | ||
b6e1fff1 | 150 | r = safe_fork("(tar)", FORK_RESET_SIGNALS|FORK_DEATHSIG|FORK_LOG, &pid); |
4c253ed1 | 151 | if (r < 0) |
b6e1fff1 | 152 | return r; |
4c253ed1 | 153 | if (r == 0) { |
c400d040 LP |
154 | const char *cmdline[] = { |
155 | "tar", | |
156 | "-C", path, | |
157 | "-c", | |
158 | "--xattrs", | |
159 | "--xattrs-include=*", | |
160 | use_selinux ? "--selinux" : "--no-selinux", | |
161 | ".", | |
162 | NULL | |
163 | }; | |
164 | ||
587fec42 LP |
165 | uint64_t retain = (1ULL << CAP_DAC_OVERRIDE); |
166 | ||
167 | /* Child */ | |
168 | ||
587fec42 LP |
169 | pipefd[0] = safe_close(pipefd[0]); |
170 | ||
2b33ab09 | 171 | r = rearrange_stdio(-1, pipefd[1], STDERR_FILENO); |
046a82c1 | 172 | if (r < 0) { |
2b33ab09 | 173 | log_error_errno(r, "Failed to rearrange stdin/stdout: %m"); |
587fec42 LP |
174 | _exit(EXIT_FAILURE); |
175 | } | |
176 | ||
587fec42 LP |
177 | if (unshare(CLONE_NEWNET) < 0) |
178 | log_error_errno(errno, "Failed to lock tar into network namespace, ignoring: %m"); | |
179 | ||
a103496c | 180 | r = capability_bounding_set_drop(retain, true); |
587fec42 LP |
181 | if (r < 0) |
182 | log_error_errno(r, "Failed to drop capabilities, ignoring: %m"); | |
183 | ||
c400d040 LP |
184 | execvp("gtar", (char* const*) cmdline); |
185 | execvp("tar", (char* const*) cmdline); | |
186 | ||
587fec42 LP |
187 | log_error_errno(errno, "Failed to execute tar: %m"); |
188 | _exit(EXIT_FAILURE); | |
189 | } | |
190 | ||
587fec42 LP |
191 | *ret = pid; |
192 | ||
c10d6bdb | 193 | return TAKE_FD(pipefd[0]); |
587fec42 | 194 | } |
e21b7229 LP |
195 | |
196 | int import_mangle_os_tree(const char *path) { | |
197 | _cleanup_closedir_ DIR *d = NULL, *cd = NULL; | |
198 | _cleanup_free_ char *child = NULL, *t = NULL; | |
199 | const char *joined; | |
200 | struct dirent *de; | |
201 | int r; | |
202 | ||
203 | assert(path); | |
204 | ||
205 | /* Some tarballs contain a single top-level directory that contains the actual OS directory tree. Try to | |
206 | * recognize this, and move the tree one level up. */ | |
207 | ||
208 | r = path_is_os_tree(path); | |
209 | if (r < 0) | |
210 | return log_error_errno(r, "Failed to determine whether '%s' is an OS tree: %m", path); | |
211 | if (r > 0) { | |
212 | log_debug("Directory tree '%s' is a valid OS tree.", path); | |
213 | return 0; | |
214 | } | |
215 | ||
216 | log_debug("Directory tree '%s' is not recognizable as OS tree, checking whether to rearrange it.", path); | |
217 | ||
218 | d = opendir(path); | |
219 | if (!d) | |
220 | return log_error_errno(r, "Failed to open directory '%s': %m", path); | |
221 | ||
222 | errno = 0; | |
223 | de = readdir_no_dot(d); | |
224 | if (!de) { | |
225 | if (errno != 0) | |
226 | return log_error_errno(errno, "Failed to iterate through directory '%s': %m", path); | |
227 | ||
228 | log_debug("Directory '%s' is empty, leaving it as it is.", path); | |
229 | return 0; | |
230 | } | |
231 | ||
232 | child = strdup(de->d_name); | |
233 | if (!child) | |
234 | return log_oom(); | |
235 | ||
236 | errno = 0; | |
237 | de = readdir_no_dot(d); | |
238 | if (de) { | |
239 | if (errno != 0) | |
240 | return log_error_errno(errno, "Failed to iterate through directory '%s': %m", path); | |
241 | ||
242 | log_debug("Directory '%s' does not look like a directory tree, and has multiple children, leaving as it is.", path); | |
243 | return 0; | |
244 | } | |
245 | ||
270384b2 | 246 | joined = prefix_roota(path, child); |
e21b7229 LP |
247 | r = path_is_os_tree(joined); |
248 | if (r == -ENOTDIR) { | |
249 | log_debug("Directory '%s' does not look like a directory tree, and contains a single regular file only, leaving as it is.", path); | |
250 | return 0; | |
251 | } | |
252 | if (r < 0) | |
253 | return log_error_errno(r, "Failed to determine whether '%s' is an OS tree: %m", joined); | |
254 | if (r == 0) { | |
255 | log_debug("Neither '%s' nor '%s' is a valid OS tree, leaving them as they are.", path, joined); | |
256 | return 0; | |
257 | } | |
258 | ||
259 | /* Nice, we have checked now: | |
260 | * | |
261 | * 1. The top-level directory does not qualify as OS tree | |
262 | * 1. The top-level directory only contains one item | |
263 | * 2. That item is a directory | |
264 | * 3. And that directory qualifies as OS tree | |
265 | * | |
266 | * Let's now rearrange things, moving everything in the inner directory one level up */ | |
267 | ||
268 | cd = xopendirat(dirfd(d), child, O_NOFOLLOW); | |
269 | if (!cd) | |
270 | return log_error_errno(errno, "Can't open directory '%s': %m", joined); | |
271 | ||
272 | log_info("Rearranging '%s', moving OS tree one directory up.", joined); | |
273 | ||
274 | /* Let's rename the child to an unguessable name so that we can be sure all files contained in it can be | |
275 | * safely moved up and won't collide with the name. */ | |
276 | r = tempfn_random(child, NULL, &t); | |
277 | if (r < 0) | |
278 | return log_oom(); | |
279 | r = rename_noreplace(dirfd(d), child, dirfd(d), t); | |
280 | if (r < 0) | |
281 | return log_error_errno(r, "Unable to rename '%s' to '%s/%s': %m", joined, path, t); | |
282 | ||
283 | FOREACH_DIRENT_ALL(de, cd, return log_error_errno(errno, "Failed to iterate through directory '%s': %m", joined)) { | |
284 | if (dot_or_dot_dot(de->d_name)) | |
285 | continue; | |
286 | ||
287 | r = rename_noreplace(dirfd(cd), de->d_name, dirfd(d), de->d_name); | |
288 | if (r < 0) | |
289 | return log_error_errno(r, "Unable to move '%s/%s/%s' to '%s/%s': %m", path, t, de->d_name, path, de->d_name); | |
290 | } | |
291 | ||
292 | if (unlinkat(dirfd(d), t, AT_REMOVEDIR) < 0) | |
293 | return log_error_errno(errno, "Failed to remove temporary directory '%s/%s': %m", path, t); | |
294 | ||
295 | log_info("Successfully rearranged OS tree."); | |
296 | ||
297 | return 0; | |
298 | } |