]>
Commit | Line | Data |
---|---|---|
53e1b683 | 1 | /* SPDX-License-Identifier: LGPL-2.1+ */ |
a2ea3b2f LP |
2 | /*** |
3 | This file is part of systemd. | |
4 | ||
5 | Copyright 2016 Lennart Poettering | |
6 | ||
7 | systemd is free software; you can redistribute it and/or modify it | |
8 | under the terms of the GNU Lesser General Public License as published by | |
9 | the Free Software Foundation; either version 2.1 of the License, or | |
10 | (at your option) any later version. | |
11 | ||
12 | systemd is distributed in the hope that it will be useful, but | |
13 | WITHOUT ANY WARRANTY; without even the implied warranty of | |
14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
15 | Lesser General Public License for more details. | |
16 | ||
17 | You should have received a copy of the GNU Lesser General Public License | |
18 | along with systemd; If not, see <http://www.gnu.org/licenses/>. | |
19 | ***/ | |
20 | ||
21 | #include <fcntl.h> | |
22 | #include <stdio.h> | |
23 | #include <getopt.h> | |
24 | ||
25 | #include "architecture.h" | |
26 | #include "dissect-image.h" | |
4623e8e6 | 27 | #include "hexdecoct.h" |
a2ea3b2f LP |
28 | #include "log.h" |
29 | #include "loop-util.h" | |
30 | #include "string-util.h" | |
31 | #include "util.h" | |
32 | ||
33 | static enum { | |
34 | ACTION_DISSECT, | |
35 | ACTION_MOUNT, | |
36 | } arg_action = ACTION_DISSECT; | |
37 | static const char *arg_image = NULL; | |
38 | static const char *arg_path = NULL; | |
e0f9e7bd | 39 | static DissectImageFlags arg_flags = DISSECT_IMAGE_REQUIRE_ROOT|DISSECT_IMAGE_DISCARD_ON_LOOP; |
4623e8e6 LP |
40 | static void *arg_root_hash = NULL; |
41 | static size_t arg_root_hash_size = 0; | |
a2ea3b2f LP |
42 | |
43 | static void help(void) { | |
44 | printf("%s [OPTIONS...] IMAGE\n" | |
45 | "%s [OPTIONS...] --mount IMAGE PATH\n" | |
46 | "Dissect a file system OS image.\n\n" | |
47 | " -h --help Show this help\n" | |
48 | " --version Show package version\n" | |
49 | " -m --mount Mount the image to the specified directory\n" | |
18b5886e | 50 | " -r --read-only Mount read-only\n" |
4623e8e6 LP |
51 | " --discard=MODE Choose 'discard' mode (disabled, loop, all, crypto)\n" |
52 | " --root-hash=HASH Specify root hash for verity\n", | |
a2ea3b2f LP |
53 | program_invocation_short_name, |
54 | program_invocation_short_name); | |
55 | } | |
56 | ||
57 | static int parse_argv(int argc, char *argv[]) { | |
58 | ||
59 | enum { | |
60 | ARG_VERSION = 0x100, | |
18b5886e | 61 | ARG_DISCARD, |
4623e8e6 | 62 | ARG_ROOT_HASH, |
a2ea3b2f LP |
63 | }; |
64 | ||
65 | static const struct option options[] = { | |
66 | { "help", no_argument, NULL, 'h' }, | |
67 | { "version", no_argument, NULL, ARG_VERSION }, | |
68 | { "mount", no_argument, NULL, 'm' }, | |
69 | { "read-only", no_argument, NULL, 'r' }, | |
18b5886e | 70 | { "discard", required_argument, NULL, ARG_DISCARD }, |
4623e8e6 | 71 | { "root-hash", required_argument, NULL, ARG_ROOT_HASH }, |
a2ea3b2f LP |
72 | {} |
73 | }; | |
74 | ||
4623e8e6 | 75 | int c, r; |
a2ea3b2f LP |
76 | |
77 | assert(argc >= 0); | |
78 | assert(argv); | |
79 | ||
80 | while ((c = getopt_long(argc, argv, "hmr", options, NULL)) >= 0) { | |
81 | ||
82 | switch (c) { | |
83 | ||
84 | case 'h': | |
85 | help(); | |
86 | return 0; | |
87 | ||
88 | case ARG_VERSION: | |
89 | return version(); | |
90 | ||
91 | case 'm': | |
92 | arg_action = ACTION_MOUNT; | |
93 | break; | |
94 | ||
95 | case 'r': | |
18b5886e LP |
96 | arg_flags |= DISSECT_IMAGE_READ_ONLY; |
97 | break; | |
98 | ||
971e2ef0 ZJS |
99 | case ARG_DISCARD: { |
100 | DissectImageFlags flags; | |
101 | ||
18b5886e | 102 | if (streq(optarg, "disabled")) |
971e2ef0 | 103 | flags = 0; |
18b5886e | 104 | else if (streq(optarg, "loop")) |
971e2ef0 | 105 | flags = DISSECT_IMAGE_DISCARD_ON_LOOP; |
18b5886e | 106 | else if (streq(optarg, "all")) |
971e2ef0 | 107 | flags = DISSECT_IMAGE_DISCARD_ON_LOOP | DISSECT_IMAGE_DISCARD; |
18b5886e | 108 | else if (streq(optarg, "crypt")) |
971e2ef0 | 109 | flags = DISSECT_IMAGE_DISCARD_ANY; |
18b5886e LP |
110 | else { |
111 | log_error("Unknown --discard= parameter: %s", optarg); | |
112 | return -EINVAL; | |
113 | } | |
971e2ef0 | 114 | arg_flags = (arg_flags & ~DISSECT_IMAGE_DISCARD_ANY) | flags; |
18b5886e | 115 | |
a2ea3b2f | 116 | break; |
971e2ef0 | 117 | } |
a2ea3b2f | 118 | |
4623e8e6 LP |
119 | case ARG_ROOT_HASH: { |
120 | void *p; | |
121 | size_t l; | |
122 | ||
123 | r = unhexmem(optarg, strlen(optarg), &p, &l); | |
124 | if (r < 0) | |
125 | return log_error_errno(r, "Failed to parse root hash: %s", optarg); | |
126 | if (l < sizeof(sd_id128_t)) { | |
127 | log_error("Root hash must be at least 128bit long: %s", optarg); | |
128 | free(p); | |
129 | return -EINVAL; | |
130 | } | |
131 | ||
132 | free(arg_root_hash); | |
133 | arg_root_hash = p; | |
134 | arg_root_hash_size = l; | |
135 | break; | |
136 | } | |
137 | ||
a2ea3b2f LP |
138 | case '?': |
139 | return -EINVAL; | |
140 | ||
141 | default: | |
142 | assert_not_reached("Unhandled option"); | |
143 | } | |
144 | ||
145 | } | |
146 | ||
147 | switch (arg_action) { | |
148 | ||
149 | case ACTION_DISSECT: | |
150 | if (optind + 1 != argc) { | |
151 | log_error("Expected a file path as only argument."); | |
152 | return -EINVAL; | |
153 | } | |
154 | ||
155 | arg_image = argv[optind]; | |
18b5886e | 156 | arg_flags |= DISSECT_IMAGE_READ_ONLY; |
a2ea3b2f LP |
157 | break; |
158 | ||
159 | case ACTION_MOUNT: | |
160 | if (optind + 2 != argc) { | |
161 | log_error("Expected a file path and mount point path as only arguments."); | |
162 | return -EINVAL; | |
163 | } | |
164 | ||
165 | arg_image = argv[optind]; | |
166 | arg_path = argv[optind + 1]; | |
167 | break; | |
168 | ||
169 | default: | |
170 | assert_not_reached("Unknown action."); | |
171 | } | |
172 | ||
173 | return 1; | |
174 | } | |
175 | ||
176 | int main(int argc, char *argv[]) { | |
177 | _cleanup_(loop_device_unrefp) LoopDevice *d = NULL; | |
18b5886e | 178 | _cleanup_(decrypted_image_unrefp) DecryptedImage *di = NULL; |
a2ea3b2f LP |
179 | _cleanup_(dissected_image_unrefp) DissectedImage *m = NULL; |
180 | int r; | |
181 | ||
182 | log_parse_environment(); | |
183 | log_open(); | |
184 | ||
185 | r = parse_argv(argc, argv); | |
186 | if (r <= 0) | |
187 | goto finish; | |
188 | ||
18b5886e | 189 | r = loop_device_make_by_path(arg_image, (arg_flags & DISSECT_IMAGE_READ_ONLY) ? O_RDONLY : O_RDWR, &d); |
a2ea3b2f LP |
190 | if (r < 0) { |
191 | log_error_errno(r, "Failed to set up loopback device: %m"); | |
192 | goto finish; | |
193 | } | |
194 | ||
78ebe980 LP |
195 | if (!arg_root_hash) { |
196 | r = root_hash_load(arg_image, &arg_root_hash, &arg_root_hash_size); | |
197 | if (r < 0) { | |
198 | log_error_errno(r, "Failed to read root hash file for %s: %m", arg_image); | |
199 | goto finish; | |
200 | } | |
201 | } | |
202 | ||
e0f9e7bd | 203 | r = dissect_image(d->fd, arg_root_hash, arg_root_hash_size, arg_flags, &m); |
a2ea3b2f LP |
204 | if (r == -ENOPKG) { |
205 | log_error_errno(r, "Couldn't identify a suitable partition table or file system in %s.", arg_image); | |
206 | goto finish; | |
207 | } | |
4623e8e6 LP |
208 | if (r == -EADDRNOTAVAIL) { |
209 | log_error_errno(r, "No root partition for specified root hash found in %s.", arg_image); | |
210 | goto finish; | |
211 | } | |
94c4c622 LP |
212 | if (r == -ENOTUNIQ) { |
213 | log_error_errno(r, "Multiple suitable root partitions found in image %s.", arg_image); | |
214 | goto finish; | |
215 | } | |
216 | if (r == -ENXIO) { | |
217 | log_error_errno(r, "No suitable root partition found in image %s.", arg_image); | |
218 | goto finish; | |
219 | } | |
759aaedc LP |
220 | if (r == -EPROTONOSUPPORT) { |
221 | log_error_errno(r, "Device %s is loopback block device with partition scanning turned off, please turn it on.", arg_image); | |
222 | goto finish; | |
223 | } | |
a2ea3b2f LP |
224 | if (r < 0) { |
225 | log_error_errno(r, "Failed to dissect image: %m"); | |
226 | goto finish; | |
227 | } | |
228 | ||
229 | switch (arg_action) { | |
230 | ||
231 | case ACTION_DISSECT: { | |
232 | unsigned i; | |
233 | ||
234 | for (i = 0; i < _PARTITION_DESIGNATOR_MAX; i++) { | |
235 | DissectedPartition *p = m->partitions + i; | |
4623e8e6 | 236 | int k; |
a2ea3b2f LP |
237 | |
238 | if (!p->found) | |
239 | continue; | |
240 | ||
241 | printf("Found %s '%s' partition", | |
242 | p->rw ? "writable" : "read-only", | |
243 | partition_designator_to_string(i)); | |
244 | ||
be30ad41 LP |
245 | if (!sd_id128_is_null(p->uuid)) |
246 | printf(" (UUID " SD_ID128_FORMAT_STR ")", SD_ID128_FORMAT_VAL(p->uuid)); | |
247 | ||
a2ea3b2f LP |
248 | if (p->fstype) |
249 | printf(" of type %s", p->fstype); | |
250 | ||
251 | if (p->architecture != _ARCHITECTURE_INVALID) | |
252 | printf(" for %s", architecture_to_string(p->architecture)); | |
253 | ||
4623e8e6 LP |
254 | k = PARTITION_VERITY_OF(i); |
255 | if (k >= 0) | |
256 | printf(" %s verity", m->partitions[k].found ? "with" : "without"); | |
257 | ||
a2ea3b2f LP |
258 | if (p->partno >= 0) |
259 | printf(" on partition #%i", p->partno); | |
260 | ||
261 | if (p->node) | |
262 | printf(" (%s)", p->node); | |
263 | ||
264 | putchar('\n'); | |
265 | } | |
266 | ||
267 | break; | |
268 | } | |
269 | ||
270 | case ACTION_MOUNT: | |
4623e8e6 | 271 | r = dissected_image_decrypt_interactively(m, NULL, arg_root_hash, arg_root_hash_size, arg_flags, &di); |
18b5886e LP |
272 | if (r < 0) |
273 | goto finish; | |
274 | ||
275 | r = dissected_image_mount(m, arg_path, arg_flags); | |
a2ea3b2f LP |
276 | if (r < 0) { |
277 | log_error_errno(r, "Failed to mount image: %m"); | |
278 | goto finish; | |
279 | } | |
280 | ||
18b5886e LP |
281 | if (di) { |
282 | r = decrypted_image_relinquish(di); | |
283 | if (r < 0) { | |
284 | log_error_errno(r, "Failed to relinquish DM devices: %m"); | |
285 | goto finish; | |
286 | } | |
287 | } | |
288 | ||
a2ea3b2f LP |
289 | loop_device_relinquish(d); |
290 | break; | |
291 | ||
292 | default: | |
293 | assert_not_reached("Unknown action."); | |
294 | } | |
295 | ||
296 | finish: | |
4623e8e6 | 297 | free(arg_root_hash); |
a2ea3b2f LP |
298 | return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; |
299 | } |