]>
Commit | Line | Data |
---|---|---|
43cc7a3e LP |
1 | /* SPDX-License-Identifier: LGPL-2.1-or-later */ |
2 | ||
3 | #include <fcntl.h> | |
4 | #include <sys/stat.h> | |
5 | #include <unistd.h> | |
6 | ||
7 | #include "alloc-util.h" | |
8 | #include "blockdev-util.h" | |
9 | #include "chase-symlinks.h" | |
ca822829 | 10 | #include "device-util.h" |
f8a7112c | 11 | #include "devnum-util.h" |
43cc7a3e LP |
12 | #include "dirent-util.h" |
13 | #include "env-util.h" | |
14 | #include "fd-util.h" | |
15 | #include "fileio.h" | |
16 | #include "glyph-util.h" | |
17 | #include "gpt.h" | |
18 | #include "hexdecoct.h" | |
19 | #include "import-util.h" | |
20 | #include "macro.h" | |
21 | #include "process-util.h" | |
22 | #include "sort-util.h" | |
43cc7a3e LP |
23 | #include "string-table.h" |
24 | #include "sysupdate-cache.h" | |
25 | #include "sysupdate-instance.h" | |
26 | #include "sysupdate-pattern.h" | |
27 | #include "sysupdate-resource.h" | |
28 | #include "sysupdate.h" | |
29 | #include "utf8.h" | |
30 | ||
31 | void resource_destroy(Resource *rr) { | |
32 | assert(rr); | |
33 | ||
34 | free(rr->path); | |
35 | strv_free(rr->patterns); | |
36 | ||
37 | for (size_t i = 0; i < rr->n_instances; i++) | |
38 | instance_free(rr->instances[i]); | |
39 | free(rr->instances); | |
40 | } | |
41 | ||
42 | static int resource_add_instance( | |
43 | Resource *rr, | |
44 | const char *path, | |
45 | const InstanceMetadata *f, | |
46 | Instance **ret) { | |
47 | ||
48 | Instance *i; | |
49 | int r; | |
50 | ||
51 | assert(rr); | |
52 | assert(path); | |
53 | assert(f); | |
54 | assert(f->version); | |
55 | ||
56 | if (!GREEDY_REALLOC(rr->instances, rr->n_instances + 1)) | |
57 | return log_oom(); | |
58 | ||
59 | r = instance_new(rr, path, f, &i); | |
60 | if (r < 0) | |
61 | return r; | |
62 | ||
63 | rr->instances[rr->n_instances++] = i; | |
64 | ||
65 | if (ret) | |
66 | *ret = i; | |
67 | ||
68 | return 0; | |
69 | } | |
70 | ||
71 | static int resource_load_from_directory( | |
72 | Resource *rr, | |
73 | mode_t m) { | |
74 | ||
75 | _cleanup_(closedirp) DIR *d = NULL; | |
76 | int r; | |
77 | ||
78 | assert(rr); | |
79 | assert(IN_SET(rr->type, RESOURCE_TAR, RESOURCE_REGULAR_FILE, RESOURCE_DIRECTORY, RESOURCE_SUBVOLUME)); | |
80 | assert(IN_SET(m, S_IFREG, S_IFDIR)); | |
81 | ||
82 | d = opendir(rr->path); | |
83 | if (!d) { | |
84 | if (errno == ENOENT) { | |
85 | log_debug("Directory %s does not exist, not loading any resources.", rr->path); | |
86 | return 0; | |
87 | } | |
88 | ||
89 | return log_error_errno(errno, "Failed to open directory '%s': %m", rr->path); | |
90 | } | |
91 | ||
92 | for (;;) { | |
93 | _cleanup_(instance_metadata_destroy) InstanceMetadata extracted_fields = INSTANCE_METADATA_NULL; | |
94 | _cleanup_free_ char *joined = NULL; | |
95 | Instance *instance; | |
96 | struct dirent *de; | |
97 | struct stat st; | |
98 | ||
99 | errno = 0; | |
100 | de = readdir_no_dot(d); | |
101 | if (!de) { | |
102 | if (errno != 0) | |
103 | return log_error_errno(errno, "Failed to read directory '%s': %m", rr->path); | |
104 | break; | |
105 | } | |
106 | ||
107 | switch (de->d_type) { | |
108 | ||
109 | case DT_UNKNOWN: | |
110 | break; | |
111 | ||
112 | case DT_DIR: | |
113 | if (m != S_IFDIR) | |
114 | continue; | |
115 | ||
116 | break; | |
117 | ||
118 | case DT_REG: | |
119 | if (m != S_IFREG) | |
120 | continue; | |
121 | break; | |
122 | ||
123 | default: | |
124 | continue; | |
125 | } | |
126 | ||
127 | if (fstatat(dirfd(d), de->d_name, &st, AT_NO_AUTOMOUNT) < 0) { | |
128 | if (errno == ENOENT) /* Gone by now? */ | |
129 | continue; | |
130 | ||
131 | return log_error_errno(errno, "Failed to stat %s/%s: %m", rr->path, de->d_name); | |
132 | } | |
133 | ||
134 | if ((st.st_mode & S_IFMT) != m) | |
135 | continue; | |
136 | ||
137 | r = pattern_match_many(rr->patterns, de->d_name, &extracted_fields); | |
138 | if (r < 0) | |
139 | return log_error_errno(r, "Failed to match pattern: %m"); | |
140 | if (r == 0) | |
141 | continue; | |
142 | ||
143 | joined = path_join(rr->path, de->d_name); | |
144 | if (!joined) | |
145 | return log_oom(); | |
146 | ||
147 | r = resource_add_instance(rr, joined, &extracted_fields, &instance); | |
148 | if (r < 0) | |
149 | return r; | |
150 | ||
151 | /* Inherit these from the source, if not explicitly overwritten */ | |
152 | if (instance->metadata.mtime == USEC_INFINITY) | |
153 | instance->metadata.mtime = timespec_load(&st.st_mtim) ?: USEC_INFINITY; | |
154 | ||
155 | if (instance->metadata.mode == MODE_INVALID) | |
156 | instance->metadata.mode = st.st_mode & 0775; /* mask out world-writability and suid and stuff, for safety */ | |
157 | } | |
158 | ||
159 | return 0; | |
160 | } | |
161 | ||
162 | static int resource_load_from_blockdev(Resource *rr) { | |
163 | _cleanup_(fdisk_unref_contextp) struct fdisk_context *c = NULL; | |
164 | _cleanup_(fdisk_unref_tablep) struct fdisk_table *t = NULL; | |
165 | size_t n_partitions; | |
166 | int r; | |
167 | ||
168 | assert(rr); | |
169 | ||
170 | c = fdisk_new_context(); | |
171 | if (!c) | |
172 | return log_oom(); | |
173 | ||
174 | r = fdisk_assign_device(c, rr->path, /* readonly= */ true); | |
175 | if (r < 0) | |
176 | return log_error_errno(r, "Failed to open device '%s': %m", rr->path); | |
177 | ||
178 | if (!fdisk_is_labeltype(c, FDISK_DISKLABEL_GPT)) | |
179 | return log_error_errno(SYNTHETIC_ERRNO(EHWPOISON), "Disk %s has no GPT disk label, not suitable.", rr->path); | |
180 | ||
181 | r = fdisk_get_partitions(c, &t); | |
182 | if (r < 0) | |
183 | return log_error_errno(r, "Failed to acquire partition table: %m"); | |
184 | ||
185 | n_partitions = fdisk_table_get_nents(t); | |
186 | for (size_t i = 0; i < n_partitions; i++) { | |
187 | _cleanup_(instance_metadata_destroy) InstanceMetadata extracted_fields = INSTANCE_METADATA_NULL; | |
188 | _cleanup_(partition_info_destroy) PartitionInfo pinfo = PARTITION_INFO_NULL; | |
189 | Instance *instance; | |
190 | ||
191 | r = read_partition_info(c, t, i, &pinfo); | |
192 | if (r < 0) | |
193 | return r; | |
194 | if (r == 0) /* not assigned */ | |
195 | continue; | |
196 | ||
197 | /* Check if partition type matches */ | |
22e932f4 | 198 | if (rr->partition_type_set && !sd_id128_equal(pinfo.type, rr->partition_type.uuid)) |
43cc7a3e LP |
199 | continue; |
200 | ||
201 | /* A label of "_empty" means "not used so far" for us */ | |
202 | if (streq_ptr(pinfo.label, "_empty")) { | |
203 | rr->n_empty++; | |
204 | continue; | |
205 | } | |
206 | ||
207 | r = pattern_match_many(rr->patterns, pinfo.label, &extracted_fields); | |
208 | if (r < 0) | |
209 | return log_error_errno(r, "Failed to match pattern: %m"); | |
210 | if (r == 0) | |
211 | continue; | |
212 | ||
213 | r = resource_add_instance(rr, pinfo.device, &extracted_fields, &instance); | |
214 | if (r < 0) | |
215 | return r; | |
216 | ||
217 | instance->partition_info = pinfo; | |
218 | pinfo = (PartitionInfo) PARTITION_INFO_NULL; | |
219 | ||
220 | /* Inherit data from source if not configured explicitly */ | |
221 | if (!instance->metadata.partition_uuid_set) { | |
222 | instance->metadata.partition_uuid = instance->partition_info.uuid; | |
223 | instance->metadata.partition_uuid_set = true; | |
224 | } | |
225 | ||
226 | if (!instance->metadata.partition_flags_set) { | |
227 | instance->metadata.partition_flags = instance->partition_info.flags; | |
228 | instance->metadata.partition_flags_set = true; | |
229 | } | |
230 | ||
231 | if (instance->metadata.read_only < 0) | |
232 | instance->metadata.read_only = instance->partition_info.read_only; | |
233 | } | |
234 | ||
235 | return 0; | |
236 | } | |
237 | ||
238 | static int download_manifest( | |
239 | const char *url, | |
240 | bool verify_signature, | |
241 | char **ret_buffer, | |
242 | size_t *ret_size) { | |
243 | ||
244 | _cleanup_free_ char *buffer = NULL, *suffixed_url = NULL; | |
19ee48a6 | 245 | _cleanup_(close_pairp) int pfd[2] = PIPE_EBADF; |
43cc7a3e LP |
246 | _cleanup_fclose_ FILE *manifest = NULL; |
247 | size_t size = 0; | |
248 | pid_t pid; | |
249 | int r; | |
250 | ||
251 | assert(url); | |
252 | assert(ret_buffer); | |
253 | assert(ret_size); | |
254 | ||
255 | /* Download a SHA256SUMS file as manifest */ | |
256 | ||
257 | r = import_url_append_component(url, "SHA256SUMS", &suffixed_url); | |
258 | if (r < 0) | |
259 | return log_error_errno(r, "Failed to append SHA256SUMS to URL: %m"); | |
260 | ||
261 | if (pipe2(pfd, O_CLOEXEC) < 0) | |
262 | return log_error_errno(errno, "Failed to allocate pipe: %m"); | |
263 | ||
28e5e1e9 DT |
264 | log_info("%s Acquiring manifest file %s%s", special_glyph(SPECIAL_GLYPH_DOWNLOAD), |
265 | suffixed_url, special_glyph(SPECIAL_GLYPH_ELLIPSIS)); | |
43cc7a3e LP |
266 | |
267 | r = safe_fork("(sd-pull)", FORK_RESET_SIGNALS|FORK_DEATHSIG|FORK_LOG, &pid); | |
268 | if (r < 0) | |
269 | return r; | |
270 | if (r == 0) { | |
271 | /* Child */ | |
272 | ||
273 | const char *cmdline[] = { | |
274 | "systemd-pull", | |
275 | "raw", | |
276 | "--direct", /* just download the specified URL, don't download anything else */ | |
277 | "--verify", verify_signature ? "signature" : "no", /* verify the manifest file */ | |
278 | suffixed_url, | |
279 | "-", /* write to stdout */ | |
280 | NULL | |
281 | }; | |
282 | ||
283 | pfd[0] = safe_close(pfd[0]); | |
284 | ||
285 | r = rearrange_stdio(-1, pfd[1], STDERR_FILENO); | |
286 | if (r < 0) { | |
287 | log_error_errno(r, "Failed to rearrange stdin/stdout: %m"); | |
288 | _exit(EXIT_FAILURE); | |
289 | } | |
290 | ||
291 | (void) unsetenv("NOTIFY_SOCKET"); | |
292 | execv(pull_binary_path(), (char *const*) cmdline); | |
293 | log_error_errno(errno, "Failed to execute %s tool: %m", pull_binary_path()); | |
294 | _exit(EXIT_FAILURE); | |
295 | }; | |
296 | ||
297 | pfd[1] = safe_close(pfd[1]); | |
298 | ||
299 | /* We'll first load the entire manifest into memory before parsing it. That's because the | |
300 | * systemd-pull tool can validate the download only after its completion, but still pass the data to | |
301 | * us as it runs. We thus need to check the return value of the process *before* parsing, to be | |
302 | * reasonably safe. */ | |
303 | ||
304 | manifest = fdopen(pfd[0], "r"); | |
305 | if (!manifest) | |
306 | return log_error_errno(errno, "Failed allocate FILE object for manifest file: %m"); | |
307 | ||
308 | TAKE_FD(pfd[0]); | |
309 | ||
310 | r = read_full_stream(manifest, &buffer, &size); | |
311 | if (r < 0) | |
312 | return log_error_errno(r, "Failed to read manifest file from child: %m"); | |
313 | ||
314 | manifest = safe_fclose(manifest); | |
315 | ||
316 | r = wait_for_terminate_and_check("(sd-pull)", pid, WAIT_LOG); | |
317 | if (r < 0) | |
318 | return r; | |
319 | if (r != 0) | |
320 | return -EPROTO; | |
321 | ||
322 | *ret_buffer = TAKE_PTR(buffer); | |
323 | *ret_size = size; | |
324 | ||
325 | return 0; | |
326 | } | |
327 | ||
328 | static int resource_load_from_web( | |
329 | Resource *rr, | |
330 | bool verify, | |
331 | Hashmap **web_cache) { | |
332 | ||
333 | size_t manifest_size = 0, left = 0; | |
334 | _cleanup_free_ char *buf = NULL; | |
335 | const char *manifest, *p; | |
336 | size_t line_nr = 1; | |
337 | WebCacheItem *ci; | |
338 | int r; | |
339 | ||
340 | assert(rr); | |
341 | ||
342 | ci = web_cache ? web_cache_get_item(*web_cache, rr->path, verify) : NULL; | |
343 | if (ci) { | |
344 | log_debug("Manifest web cache hit for %s.", rr->path); | |
345 | ||
346 | manifest = (char*) ci->data; | |
347 | manifest_size = ci->size; | |
348 | } else { | |
349 | log_debug("Manifest web cache miss for %s.", rr->path); | |
350 | ||
351 | r = download_manifest(rr->path, verify, &buf, &manifest_size); | |
352 | if (r < 0) | |
353 | return r; | |
354 | ||
355 | manifest = buf; | |
356 | } | |
357 | ||
358 | if (memchr(manifest, 0, manifest_size)) | |
359 | return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Manifest file has embedded NUL byte, refusing."); | |
360 | if (!utf8_is_valid_n(manifest, manifest_size)) | |
361 | return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Manifest file is not valid UTF-8, refusing."); | |
362 | ||
363 | p = manifest; | |
364 | left = manifest_size; | |
365 | ||
366 | while (left > 0) { | |
367 | _cleanup_(instance_metadata_destroy) InstanceMetadata extracted_fields = INSTANCE_METADATA_NULL; | |
368 | _cleanup_free_ char *fn = NULL; | |
369 | _cleanup_free_ void *h = NULL; | |
370 | Instance *instance; | |
371 | const char *e; | |
372 | size_t hlen; | |
373 | ||
374 | /* 64 character hash + separator + filename + newline */ | |
375 | if (left < 67) | |
376 | return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Corrupt manifest at line %zu, refusing.", line_nr); | |
377 | ||
378 | if (p[0] == '\\') | |
379 | return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "File names with escapes not supported in manifest at line %zu, refusing.", line_nr); | |
380 | ||
381 | r = unhexmem(p, 64, &h, &hlen); | |
382 | if (r < 0) | |
383 | return log_error_errno(r, "Failed to parse digest at manifest line %zu, refusing.", line_nr); | |
384 | ||
385 | p += 64, left -= 64; | |
386 | ||
387 | if (*p != ' ') | |
388 | return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Missing space separator at manifest line %zu, refusing.", line_nr); | |
389 | p++, left--; | |
390 | ||
391 | if (!IN_SET(*p, '*', ' ')) | |
392 | return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Missing binary/text input marker at manifest line %zu, refusing.", line_nr); | |
393 | p++, left--; | |
394 | ||
395 | e = memchr(p, '\n', left); | |
396 | if (!e) | |
397 | return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Truncated manifest file at line %zu, refusing.", line_nr); | |
398 | if (e == p) | |
399 | return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Empty filename specified at manifest line %zu, refusing.", line_nr); | |
400 | ||
401 | fn = strndup(p, e - p); | |
402 | if (!fn) | |
403 | return log_oom(); | |
404 | ||
405 | if (!filename_is_valid(fn)) | |
406 | return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid filename specified at manifest line %zu, refusing.", line_nr); | |
407 | if (string_has_cc(fn, NULL)) | |
408 | return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Filename contains control characters at manifest line %zu, refusing.", line_nr); | |
409 | ||
410 | r = pattern_match_many(rr->patterns, fn, &extracted_fields); | |
411 | if (r < 0) | |
412 | return log_error_errno(r, "Failed to match pattern: %m"); | |
413 | if (r > 0) { | |
414 | _cleanup_free_ char *path = NULL; | |
415 | ||
416 | r = import_url_append_component(rr->path, fn, &path); | |
417 | if (r < 0) | |
418 | return log_error_errno(r, "Failed to build instance URL: %m"); | |
419 | ||
420 | r = resource_add_instance(rr, path, &extracted_fields, &instance); | |
421 | if (r < 0) | |
422 | return r; | |
423 | ||
424 | assert(hlen == sizeof(instance->metadata.sha256sum)); | |
425 | ||
426 | if (instance->metadata.sha256sum_set) { | |
427 | if (memcmp(instance->metadata.sha256sum, h, hlen) != 0) | |
428 | return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "SHA256 sum parsed from filename and manifest don't match at line %zu, refusing.", line_nr); | |
429 | } else { | |
430 | memcpy(instance->metadata.sha256sum, h, hlen); | |
431 | instance->metadata.sha256sum_set = true; | |
432 | } | |
433 | } | |
434 | ||
435 | left -= (e - p) + 1; | |
436 | p = e + 1; | |
437 | ||
438 | line_nr++; | |
439 | } | |
440 | ||
441 | if (!ci && web_cache) { | |
442 | r = web_cache_add_item(web_cache, rr->path, verify, manifest, manifest_size); | |
443 | if (r < 0) | |
444 | log_debug_errno(r, "Failed to add manifest '%s' to cache, ignoring: %m", rr->path); | |
445 | else | |
446 | log_debug("Added manifest '%s' to cache.", rr->path); | |
447 | } | |
448 | ||
449 | return 0; | |
450 | } | |
451 | ||
452 | static int instance_cmp(Instance *const*a, Instance *const*b) { | |
453 | int r; | |
454 | ||
455 | assert(a); | |
456 | assert(b); | |
457 | assert(*a); | |
458 | assert(*b); | |
459 | assert((*a)->metadata.version); | |
460 | assert((*b)->metadata.version); | |
461 | ||
462 | /* Newest version at the beginning */ | |
463 | r = strverscmp_improved((*a)->metadata.version, (*b)->metadata.version); | |
464 | if (r != 0) | |
465 | return -r; | |
466 | ||
467 | /* Instances don't have to be uniquely named (uniqueness on partition tables is not enforced at all, | |
468 | * and since we allow multiple matching patterns not even in directories they are unique). Hence | |
469 | * let's order by path as secondary ordering key. */ | |
470 | return path_compare((*a)->path, (*b)->path); | |
471 | } | |
472 | ||
473 | int resource_load_instances(Resource *rr, bool verify, Hashmap **web_cache) { | |
474 | int r; | |
475 | ||
476 | assert(rr); | |
477 | ||
478 | switch (rr->type) { | |
479 | ||
480 | case RESOURCE_TAR: | |
481 | case RESOURCE_REGULAR_FILE: | |
482 | r = resource_load_from_directory(rr, S_IFREG); | |
483 | break; | |
484 | ||
485 | case RESOURCE_DIRECTORY: | |
486 | case RESOURCE_SUBVOLUME: | |
487 | r = resource_load_from_directory(rr, S_IFDIR); | |
488 | break; | |
489 | ||
490 | case RESOURCE_PARTITION: | |
491 | r = resource_load_from_blockdev(rr); | |
492 | break; | |
493 | ||
494 | case RESOURCE_URL_FILE: | |
495 | case RESOURCE_URL_TAR: | |
496 | r = resource_load_from_web(rr, verify, web_cache); | |
497 | break; | |
498 | ||
499 | default: | |
500 | assert_not_reached(); | |
501 | } | |
502 | if (r < 0) | |
503 | return r; | |
504 | ||
505 | typesafe_qsort(rr->instances, rr->n_instances, instance_cmp); | |
506 | return 0; | |
507 | } | |
508 | ||
509 | Instance* resource_find_instance(Resource *rr, const char *version) { | |
510 | Instance key = { | |
511 | .metadata.version = (char*) version, | |
512 | }, *k = &key; | |
513 | ||
514 | return typesafe_bsearch(&k, rr->instances, rr->n_instances, instance_cmp); | |
515 | } | |
516 | ||
517 | int resource_resolve_path( | |
518 | Resource *rr, | |
519 | const char *root, | |
520 | const char *node) { | |
521 | ||
522 | _cleanup_free_ char *p = NULL; | |
523 | dev_t d; | |
524 | int r; | |
525 | ||
526 | assert(rr); | |
527 | ||
528 | if (rr->path_auto) { | |
f8a7112c | 529 | struct stat orig_root_stats; |
43cc7a3e | 530 | |
f8a7112c | 531 | /* NB: If the root mount has been replaced by some form of volatile file system (overlayfs), |
532 | * the original root block device node is symlinked in /run/systemd/volatile-root. Let's | |
533 | * follow that link here. If that doesn't exist, we check the backing device of "/usr". We | |
534 | * don't actually check the backing device of the root fs "/", in order to support | |
535 | * environments where the root fs is a tmpfs, and the OS itself placed exclusively in | |
536 | * /usr/. */ | |
43cc7a3e LP |
537 | |
538 | if (rr->type != RESOURCE_PARTITION) | |
539 | return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), | |
540 | "Automatic root path discovery only supported for partition resources."); | |
541 | ||
542 | if (node) { /* If --image= is specified, directly use the loopback device */ | |
543 | r = free_and_strdup_warn(&rr->path, node); | |
544 | if (r < 0) | |
545 | return r; | |
546 | ||
547 | return 0; | |
548 | } | |
549 | ||
550 | if (root) | |
551 | return log_error_errno(SYNTHETIC_ERRNO(EPERM), | |
552 | "Block device is not allowed when using --root= mode."); | |
553 | ||
f8a7112c | 554 | r = stat("/run/systemd/volatile-root", &orig_root_stats); |
555 | if (r < 0) { | |
556 | if (errno == -ENOENT) /* volatile-root not found */ | |
557 | r = get_block_device_harder("/usr/", &d); | |
558 | else | |
559 | return log_error_errno(r, "Failed to stat /run/systemd/volatile-root: %m"); | |
560 | } else if (!S_ISBLK(orig_root_stats.st_mode)) /* symlink was present but not block device */ | |
561 | return log_error_errno(SYNTHETIC_ERRNO(ENOTBLK), "/run/systemd/volatile-root is not linked to a block device."); | |
562 | else /* symlink was present and a block device */ | |
563 | d = orig_root_stats.st_rdev; | |
43cc7a3e LP |
564 | |
565 | } else if (rr->type == RESOURCE_PARTITION) { | |
254d1313 | 566 | _cleanup_close_ int fd = -EBADF, real_fd = -EBADF; |
43cc7a3e LP |
567 | _cleanup_free_ char *resolved = NULL; |
568 | struct stat st; | |
569 | ||
570 | r = chase_symlinks(rr->path, root, CHASE_PREFIX_ROOT, &resolved, &fd); | |
571 | if (r < 0) | |
572 | return log_error_errno(r, "Failed to resolve '%s': %m", rr->path); | |
573 | ||
574 | if (fstat(fd, &st) < 0) | |
7a692931 | 575 | return log_error_errno(errno, "Failed to stat '%s': %m", resolved); |
43cc7a3e LP |
576 | |
577 | if (S_ISBLK(st.st_mode) && root) | |
578 | return log_error_errno(SYNTHETIC_ERRNO(EPERM), "When using --root= or --image= access to device nodes is prohibited."); | |
579 | ||
580 | if (S_ISREG(st.st_mode) || S_ISBLK(st.st_mode)) { | |
581 | /* Not a directory, hence no need to find backing block device for the path */ | |
582 | free_and_replace(rr->path, resolved); | |
583 | return 0; | |
584 | } | |
585 | ||
586 | if (!S_ISDIR(st.st_mode)) | |
587 | return log_error_errno(SYNTHETIC_ERRNO(ENOTDIR), "Target path '%s' does not refer to regular file, directory or block device, refusing.", rr->path); | |
588 | ||
589 | if (node) { /* If --image= is specified all file systems are backed by the same loopback device, hence shortcut things. */ | |
590 | r = free_and_strdup_warn(&rr->path, node); | |
591 | if (r < 0) | |
592 | return r; | |
593 | ||
594 | return 0; | |
595 | } | |
596 | ||
597 | real_fd = fd_reopen(fd, O_RDONLY|O_CLOEXEC|O_DIRECTORY); | |
598 | if (real_fd < 0) | |
599 | return log_error_errno(real_fd, "Failed to convert O_PATH file descriptor for %s to regular file descriptor: %m", rr->path); | |
600 | ||
601 | r = get_block_device_harder_fd(fd, &d); | |
602 | ||
603 | } else if (RESOURCE_IS_FILESYSTEM(rr->type) && root) { | |
604 | _cleanup_free_ char *resolved = NULL; | |
605 | ||
606 | r = chase_symlinks(rr->path, root, CHASE_PREFIX_ROOT, &resolved, NULL); | |
607 | if (r < 0) | |
608 | return log_error_errno(r, "Failed to resolve '%s': %m", rr->path); | |
609 | ||
610 | free_and_replace(rr->path, resolved); | |
611 | return 0; | |
612 | } else | |
613 | return 0; /* Otherwise assume there's nothing to resolve */ | |
614 | ||
615 | if (r < 0) | |
616 | return log_error_errno(r, "Failed to determine block device of file system: %m"); | |
617 | ||
618 | r = block_get_whole_disk(d, &d); | |
619 | if (r < 0) | |
620 | return log_error_errno(r, "Failed to find whole disk device for partition backing file system: %m"); | |
621 | if (r == 0) | |
622 | return log_error_errno(SYNTHETIC_ERRNO(EINVAL), | |
623 | "File system is not placed on a partition block device, cannot determine whole block device backing root file system."); | |
624 | ||
4fe46c34 | 625 | r = devname_from_devnum(S_IFBLK, d, &p); |
43cc7a3e LP |
626 | if (r < 0) |
627 | return r; | |
628 | ||
629 | if (rr->path) | |
630 | log_info("Automatically discovered block device '%s' from '%s'.", p, rr->path); | |
631 | else | |
632 | log_info("Automatically discovered root block device '%s'.", p); | |
633 | ||
634 | free_and_replace(rr->path, p); | |
635 | return 1; | |
636 | } | |
637 | ||
638 | static const char *resource_type_table[_RESOURCE_TYPE_MAX] = { | |
639 | [RESOURCE_URL_FILE] = "url-file", | |
640 | [RESOURCE_URL_TAR] = "url-tar", | |
641 | [RESOURCE_TAR] = "tar", | |
642 | [RESOURCE_PARTITION] = "partition", | |
643 | [RESOURCE_REGULAR_FILE] = "regular-file", | |
644 | [RESOURCE_DIRECTORY] = "directory", | |
645 | [RESOURCE_SUBVOLUME] = "subvolume", | |
646 | }; | |
647 | ||
648 | DEFINE_STRING_TABLE_LOOKUP(resource_type, ResourceType); |