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