1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3 #include "sd-id128.h"
4
5 #include "alloc-util.h"
6 #include "blockdev-util.h"
7 #include "chase-symlinks.h"
8 #include "conf-parser.h"
9 #include "dirent-util.h"
10 #include "fd-util.h"
11 #include "glyph-util.h"
12 #include "gpt.h"
13 #include "hexdecoct.h"
14 #include "install-file.h"
15 #include "parse-helpers.h"
16 #include "parse-util.h"
17 #include "process-util.h"
18 #include "rm-rf.h"
19 #include "specifier.h"
20 #include "stat-util.h"
21 #include "stdio-util.h"
22 #include "strv.h"
23 #include "sync-util.h"
24 #include "sysupdate-pattern.h"
25 #include "sysupdate-resource.h"
26 #include "sysupdate-transfer.h"
27 #include "sysupdate-util.h"
28 #include "sysupdate.h"
29 #include "tmpfile-util.h"
30 #include "web-util.h"
31
32 /* Default value for InstancesMax= for fs object targets */
33 #define DEFAULT_FILE_INSTANCES_MAX 3
34
transfer_free(Transfer * t)35 Transfer *transfer_free(Transfer *t) {
36 if (!t)
37 return NULL;
38
39 t->temporary_path = rm_rf_subvolume_and_free(t->temporary_path);
40
41 free(t->definition_path);
42 free(t->min_version);
43 strv_free(t->protected_versions);
44 free(t->current_symlink);
45 free(t->final_path);
46
47 partition_info_destroy(&t->partition_info);
48
49 resource_destroy(&t->source);
50 resource_destroy(&t->target);
51
52 return mfree(t);
53 }
54
transfer_new(void)55 Transfer *transfer_new(void) {
56 Transfer *t;
57
58 t = new(Transfer, 1);
59 if (!t)
60 return NULL;
61
62 *t = (Transfer) {
63 .source.type = _RESOURCE_TYPE_INVALID,
64 .target.type = _RESOURCE_TYPE_INVALID,
65 .remove_temporary = true,
66 .mode = MODE_INVALID,
67 .tries_left = UINT64_MAX,
68 .tries_done = UINT64_MAX,
69 .verify = true,
70
71 /* the three flags, as configured by the user */
72 .no_auto = -1,
73 .read_only = -1,
74 .growfs = -1,
75
76 /* the read only flag, as ultimately determined */
77 .install_read_only = -1,
78
79 .partition_info = PARTITION_INFO_NULL,
80 };
81
82 return t;
83 }
84
85 static const Specifier specifier_table[] = {
86 COMMON_SYSTEM_SPECIFIERS,
87 COMMON_TMP_SPECIFIERS,
88 {}
89 };
90
config_parse_protect_version(const char * unit,const char * filename,unsigned line,const char * section,unsigned section_line,const char * lvalue,int ltype,const char * rvalue,void * data,void * userdata)91 static int config_parse_protect_version(
92 const char *unit,
93 const char *filename,
94 unsigned line,
95 const char *section,
96 unsigned section_line,
97 const char *lvalue,
98 int ltype,
99 const char *rvalue,
100 void *data,
101 void *userdata) {
102
103 _cleanup_free_ char *resolved = NULL;
104 char ***protected_versions = data;
105 int r;
106
107 assert(rvalue);
108 assert(data);
109
110 r = specifier_printf(rvalue, NAME_MAX, specifier_table, arg_root, NULL, &resolved);
111 if (r < 0) {
112 log_syntax(unit, LOG_WARNING, filename, line, r,
113 "Failed to expand specifiers in ProtectVersion=, ignoring: %s", rvalue);
114 return 0;
115 }
116
117 if (!version_is_valid(resolved)) {
118 log_syntax(unit, LOG_WARNING, filename, line, 0,
119 "ProtectVersion= string is not valid, ignoring: %s", resolved);
120 return 0;
121 }
122
123 r = strv_extend(protected_versions, resolved);
124 if (r < 0)
125 return log_oom();
126
127 return 0;
128 }
129
config_parse_min_version(const char * unit,const char * filename,unsigned line,const char * section,unsigned section_line,const char * lvalue,int ltype,const char * rvalue,void * data,void * userdata)130 static int config_parse_min_version(
131 const char *unit,
132 const char *filename,
133 unsigned line,
134 const char *section,
135 unsigned section_line,
136 const char *lvalue,
137 int ltype,
138 const char *rvalue,
139 void *data,
140 void *userdata) {
141
142 _cleanup_free_ char *resolved = NULL;
143 char **version = data;
144 int r;
145
146 assert(rvalue);
147 assert(data);
148
149 r = specifier_printf(rvalue, NAME_MAX, specifier_table, arg_root, NULL, &resolved);
150 if (r < 0) {
151 log_syntax(unit, LOG_WARNING, filename, line, r,
152 "Failed to expand specifiers in MinVersion=, ignoring: %s", rvalue);
153 return 0;
154 }
155
156 if (!version_is_valid(rvalue)) {
157 log_syntax(unit, LOG_WARNING, filename, line, 0,
158 "MinVersion= string is not valid, ignoring: %s", resolved);
159 return 0;
160 }
161
162 return free_and_replace(*version, resolved);
163 }
164
config_parse_current_symlink(const char * unit,const char * filename,unsigned line,const char * section,unsigned section_line,const char * lvalue,int ltype,const char * rvalue,void * data,void * userdata)165 static int config_parse_current_symlink(
166 const char *unit,
167 const char *filename,
168 unsigned line,
169 const char *section,
170 unsigned section_line,
171 const char *lvalue,
172 int ltype,
173 const char *rvalue,
174 void *data,
175 void *userdata) {
176
177 _cleanup_free_ char *resolved = NULL;
178 char **current_symlink = data;
179 int r;
180
181 assert(rvalue);
182 assert(data);
183
184 r = specifier_printf(rvalue, NAME_MAX, specifier_table, arg_root, NULL, &resolved);
185 if (r < 0) {
186 log_syntax(unit, LOG_WARNING, filename, line, r,
187 "Failed to expand specifiers in CurrentSymlink=, ignoring: %s", rvalue);
188 return 0;
189 }
190
191 r = path_simplify_and_warn(resolved, 0, unit, filename, line, lvalue);
192 if (r < 0)
193 return 0;
194
195 return free_and_replace(*current_symlink, resolved);
196 }
197
config_parse_instances_max(const char * unit,const char * filename,unsigned line,const char * section,unsigned section_line,const char * lvalue,int ltype,const char * rvalue,void * data,void * userdata)198 static int config_parse_instances_max(
199 const char *unit,
200 const char *filename,
201 unsigned line,
202 const char *section,
203 unsigned section_line,
204 const char *lvalue,
205 int ltype,
206 const char *rvalue,
207 void *data,
208 void *userdata) {
209
210 uint64_t *instances_max = data, i;
211 int r;
212
213 assert(rvalue);
214 assert(data);
215
216 if (isempty(rvalue)) {
217 *instances_max = 0; /* Revert to default logic, see transfer_read_definition() */
218 return 0;
219 }
220
221 r = safe_atou64(rvalue, &i);
222 if (r < 0) {
223 log_syntax(unit, LOG_WARNING, filename, line, r,
224 "Failed to parse InstancesMax= value, ignoring: %s", rvalue);
225 return 0;
226 }
227
228 if (i < 2) {
229 log_syntax(unit, LOG_WARNING, filename, line, 0,
230 "InstancesMax= value must be at least 2, bumping: %s", rvalue);
231 *instances_max = 2;
232 } else
233 *instances_max = i;
234
235 return 0;
236 }
237
config_parse_resource_pattern(const char * unit,const char * filename,unsigned line,const char * section,unsigned section_line,const char * lvalue,int ltype,const char * rvalue,void * data,void * userdata)238 static int config_parse_resource_pattern(
239 const char *unit,
240 const char *filename,
241 unsigned line,
242 const char *section,
243 unsigned section_line,
244 const char *lvalue,
245 int ltype,
246 const char *rvalue,
247 void *data,
248 void *userdata) {
249
250 char ***patterns = data;
251 int r;
252
253 assert(rvalue);
254 assert(data);
255
256 if (isempty(rvalue)) {
257 *patterns = strv_free(*patterns);
258 return 0;
259 }
260
261 for (;;) {
262 _cleanup_free_ char *word = NULL, *resolved = NULL;
263
264 r = extract_first_word(&rvalue, &word, NULL, EXTRACT_CUNESCAPE|EXTRACT_UNESCAPE_RELAX);
265 if (r < 0) {
266 log_syntax(unit, LOG_WARNING, filename, line, r,
267 "Failed to extract first pattern from MatchPattern=, ignoring: %s", rvalue);
268 return 0;
269 }
270 if (r == 0)
271 break;
272
273 r = specifier_printf(word, NAME_MAX, specifier_table, arg_root, NULL, &resolved);
274 if (r < 0) {
275 log_syntax(unit, LOG_WARNING, filename, line, r,
276 "Failed to expand specifiers in MatchPattern=, ignoring: %s", rvalue);
277 return 0;
278 }
279
280 if (!pattern_valid(resolved))
281 return log_syntax(unit, LOG_ERR, filename, line, SYNTHETIC_ERRNO(EINVAL),
282 "MatchPattern= string is not valid, refusing: %s", resolved);
283
284 r = strv_consume(patterns, TAKE_PTR(resolved));
285 if (r < 0)
286 return log_oom();
287 }
288
289 strv_uniq(*patterns);
290 return 0;
291 }
292
config_parse_resource_path(const char * unit,const char * filename,unsigned line,const char * section,unsigned section_line,const char * lvalue,int ltype,const char * rvalue,void * data,void * userdata)293 static int config_parse_resource_path(
294 const char *unit,
295 const char *filename,
296 unsigned line,
297 const char *section,
298 unsigned section_line,
299 const char *lvalue,
300 int ltype,
301 const char *rvalue,
302 void *data,
303 void *userdata) {
304
305 _cleanup_free_ char *resolved = NULL;
306 Resource *rr = data;
307 int r;
308
309 assert(rvalue);
310 assert(data);
311
312 if (streq(rvalue, "auto")) {
313 rr->path_auto = true;
314 rr->path = mfree(rr->path);
315 return 0;
316 }
317
318 r = specifier_printf(rvalue, PATH_MAX-1, specifier_table, arg_root, NULL, &resolved);
319 if (r < 0) {
320 log_syntax(unit, LOG_WARNING, filename, line, r,
321 "Failed to expand specifiers in Path=, ignoring: %s", rvalue);
322 return 0;
323 }
324
325 /* Note that we don't validate the path as being absolute or normalized. We'll do that in
326 * transfer_read_definition() as we might not know yet whether Path refers to an URL or a file system
327 * path. */
328
329 rr->path_auto = false;
330 return free_and_replace(rr->path, resolved);
331 }
332
333 static DEFINE_CONFIG_PARSE_ENUM(config_parse_resource_type, resource_type, ResourceType, "Invalid resource type");
334
config_parse_resource_ptype(const char * unit,const char * filename,unsigned line,const char * section,unsigned section_line,const char * lvalue,int ltype,const char * rvalue,void * data,void * userdata)335 static int config_parse_resource_ptype(
336 const char *unit,
337 const char *filename,
338 unsigned line,
339 const char *section,
340 unsigned section_line,
341 const char *lvalue,
342 int ltype,
343 const char *rvalue,
344 void *data,
345 void *userdata) {
346
347 Resource *rr = data;
348 int r;
349
350 assert(rvalue);
351 assert(data);
352
353 r = gpt_partition_type_uuid_from_string(rvalue, &rr->partition_type);
354 if (r < 0) {
355 log_syntax(unit, LOG_WARNING, filename, line, r,
356 "Failed parse partition type, ignoring: %s", rvalue);
357 return 0;
358 }
359
360 rr->partition_type_set = true;
361 return 0;
362 }
363
config_parse_partition_uuid(const char * unit,const char * filename,unsigned line,const char * section,unsigned section_line,const char * lvalue,int ltype,const char * rvalue,void * data,void * userdata)364 static int config_parse_partition_uuid(
365 const char *unit,
366 const char *filename,
367 unsigned line,
368 const char *section,
369 unsigned section_line,
370 const char *lvalue,
371 int ltype,
372 const char *rvalue,
373 void *data,
374 void *userdata) {
375
376 Transfer *t = data;
377 int r;
378
379 assert(rvalue);
380 assert(data);
381
382 r = sd_id128_from_string(rvalue, &t->partition_uuid);
383 if (r < 0) {
384 log_syntax(unit, LOG_WARNING, filename, line, r,
385 "Failed parse partition UUID, ignoring: %s", rvalue);
386 return 0;
387 }
388
389 t->partition_uuid_set = true;
390 return 0;
391 }
392
config_parse_partition_flags(const char * unit,const char * filename,unsigned line,const char * section,unsigned section_line,const char * lvalue,int ltype,const char * rvalue,void * data,void * userdata)393 static int config_parse_partition_flags(
394 const char *unit,
395 const char *filename,
396 unsigned line,
397 const char *section,
398 unsigned section_line,
399 const char *lvalue,
400 int ltype,
401 const char *rvalue,
402 void *data,
403 void *userdata) {
404
405 Transfer *t = data;
406 int r;
407
408 assert(rvalue);
409 assert(data);
410
411 r = safe_atou64(rvalue, &t->partition_flags);
412 if (r < 0) {
413 log_syntax(unit, LOG_WARNING, filename, line, r,
414 "Failed parse partition flags, ignoring: %s", rvalue);
415 return 0;
416 }
417
418 t->partition_flags_set = true;
419 return 0;
420 }
421
transfer_read_definition(Transfer * t,const char * path)422 int transfer_read_definition(Transfer *t, const char *path) {
423 int r;
424
425 assert(t);
426 assert(path);
427
428 ConfigTableItem table[] = {
429 { "Transfer", "MinVersion", config_parse_min_version, 0, &t->min_version },
430 { "Transfer", "ProtectVersion", config_parse_protect_version, 0, &t->protected_versions },
431 { "Transfer", "Verify", config_parse_bool, 0, &t->verify },
432 { "Source", "Type", config_parse_resource_type, 0, &t->source.type },
433 { "Source", "Path", config_parse_resource_path, 0, &t->source },
434 { "Source", "MatchPattern", config_parse_resource_pattern, 0, &t->source.patterns },
435 { "Target", "Type", config_parse_resource_type, 0, &t->target.type },
436 { "Target", "Path", config_parse_resource_path, 0, &t->target },
437 { "Target", "MatchPattern", config_parse_resource_pattern, 0, &t->target.patterns },
438 { "Target", "MatchPartitionType", config_parse_resource_ptype, 0, &t->target },
439 { "Target", "PartitionUUID", config_parse_partition_uuid, 0, t },
440 { "Target", "PartitionFlags", config_parse_partition_flags, 0, t },
441 { "Target", "PartitionNoAuto", config_parse_tristate, 0, &t->no_auto },
442 { "Target", "PartitionGrowFileSystem", config_parse_tristate, 0, &t->growfs },
443 { "Target", "ReadOnly", config_parse_tristate, 0, &t->read_only },
444 { "Target", "Mode", config_parse_mode, 0, &t->mode },
445 { "Target", "TriesLeft", config_parse_uint64, 0, &t->tries_left },
446 { "Target", "TriesDone", config_parse_uint64, 0, &t->tries_done },
447 { "Target", "InstancesMax", config_parse_instances_max, 0, &t->instances_max },
448 { "Target", "RemoveTemporary", config_parse_bool, 0, &t->remove_temporary },
449 { "Target", "CurrentSymlink", config_parse_current_symlink, 0, &t->current_symlink },
450 {}
451 };
452
453 r = config_parse(NULL, path, NULL,
454 "Transfer\0"
455 "Source\0"
456 "Target\0",
457 config_item_table_lookup, table,
458 CONFIG_PARSE_WARN,
459 t,
460 NULL);
461 if (r < 0)
462 return r;
463
464 if (!RESOURCE_IS_SOURCE(t->source.type))
465 return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL),
466 "Source Type= must be one of url-file, url-tar, tar, regular-file, directory, subvolume.");
467
468 if (t->target.type < 0) {
469 switch (t->source.type) {
470
471 case RESOURCE_URL_FILE:
472 case RESOURCE_REGULAR_FILE:
473 t->target.type =
474 t->target.path && path_startswith(t->target.path, "/dev/") ?
475 RESOURCE_PARTITION : RESOURCE_REGULAR_FILE;
476 break;
477
478 case RESOURCE_URL_TAR:
479 case RESOURCE_TAR:
480 case RESOURCE_DIRECTORY:
481 t->target.type = RESOURCE_DIRECTORY;
482 break;
483
484 case RESOURCE_SUBVOLUME:
485 t->target.type = RESOURCE_SUBVOLUME;
486 break;
487
488 default:
489 assert_not_reached();
490 }
491 }
492
493 if (!RESOURCE_IS_TARGET(t->target.type))
494 return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL),
495 "Target Type= must be one of partition, regular-file, directory, subvolume.");
496
497 if ((IN_SET(t->source.type, RESOURCE_URL_FILE, RESOURCE_PARTITION, RESOURCE_REGULAR_FILE) &&
498 !IN_SET(t->target.type, RESOURCE_PARTITION, RESOURCE_REGULAR_FILE)) ||
499 (IN_SET(t->source.type, RESOURCE_URL_TAR, RESOURCE_TAR, RESOURCE_DIRECTORY, RESOURCE_SUBVOLUME) &&
500 !IN_SET(t->target.type, RESOURCE_DIRECTORY, RESOURCE_SUBVOLUME)))
501 return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL),
502 "Target type '%s' is incompatible with source type '%s', refusing.",
503 resource_type_to_string(t->source.type), resource_type_to_string(t->target.type));
504
505 if (!t->source.path && !t->source.path_auto)
506 return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL),
507 "Source specification lacks Path=.");
508
509 if (t->source.path) {
510 if (RESOURCE_IS_FILESYSTEM(t->source.type) || t->source.type == RESOURCE_PARTITION)
511 if (!path_is_absolute(t->source.path) || !path_is_normalized(t->source.path))
512 return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL),
513 "Source path is not a normalized, absolute path: %s", t->source.path);
514
515 /* We unofficially support file:// in addition to http:// and https:// for url
516 * sources. That's mostly for testing, since it relieves us from having to set up a HTTP
517 * server, and CURL abstracts this away from us thankfully. */
518 if (RESOURCE_IS_URL(t->source.type))
519 if (!http_url_is_valid(t->source.path) && !file_url_is_valid(t->source.path))
520 return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL),
521 "Source path is not a valid HTTP or HTTPS URL: %s", t->source.path);
522 }
523
524 if (strv_isempty(t->source.patterns))
525 return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL),
526 "Source specification lacks MatchPattern=.");
527
528 if (!t->target.path && !t->target.path_auto)
529 return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL),
530 "Target specification lacks Path= field.");
531
532 if (t->target.path &&
533 (!path_is_absolute(t->target.path) || !path_is_normalized(t->target.path)))
534 return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL),
535 "Target path is not a normalized, absolute path: %s", t->target.path);
536
537 if (strv_isempty(t->target.patterns)) {
538 strv_free(t->target.patterns);
539 t->target.patterns = strv_copy(t->source.patterns);
540 if (!t->target.patterns)
541 return log_oom();
542 }
543
544 if (t->current_symlink && !RESOURCE_IS_FILESYSTEM(t->target.type) && !path_is_absolute(t->current_symlink))
545 return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL),
546 "Current symlink must be absolute path if target is partition: %s", t->current_symlink);
547
548 /* When no instance limit is set, use all available partition slots in case of partitions, or 3 in case of fs objects */
549 if (t->instances_max == 0)
550 t->instances_max = t->target.type == RESOURCE_PARTITION ? UINT64_MAX : DEFAULT_FILE_INSTANCES_MAX;
551
552 return 0;
553 }
554
transfer_resolve_paths(Transfer * t,const char * root,const char * node)555 int transfer_resolve_paths(
556 Transfer *t,
557 const char *root,
558 const char *node) {
559
560 int r;
561
562 /* If Path=auto is used in [Source] or [Target] sections, let's automatically detect the path of the
563 * block device to use. Moreover, if this path points to a directory but we need a block device,
564 * automatically determine the backing block device, so that users can reference block devices by
565 * mount point. */
566
567 assert(t);
568
569 r = resource_resolve_path(&t->source, root, node);
570 if (r < 0)
571 return r;
572
573 r = resource_resolve_path(&t->target, root, node);
574 if (r < 0)
575 return r;
576
577 return 0;
578 }
579
transfer_remove_temporary(Transfer * t)580 static void transfer_remove_temporary(Transfer *t) {
581 _cleanup_(closedirp) DIR *d = NULL;
582 int r;
583
584 assert(t);
585
586 if (!t->remove_temporary)
587 return;
588
589 if (!IN_SET(t->target.type, RESOURCE_REGULAR_FILE, RESOURCE_DIRECTORY, RESOURCE_SUBVOLUME))
590 return;
591
592 /* Removes all temporary files/dirs from previous runs in the target directory, i.e. all those starting with '.#' */
593
594 d = opendir(t->target.path);
595 if (!d) {
596 if (errno == ENOENT)
597 return;
598
599 log_debug_errno(errno, "Failed to open target directory '%s', ignoring: %m", t->target.path);
600 return;
601 }
602
603 for (;;) {
604 struct dirent *de;
605
606 errno = 0;
607 de = readdir_no_dot(d);
608 if (!de) {
609 if (errno != 0)
610 log_debug_errno(errno, "Failed to read target directory '%s', ignoring: %m", t->target.path);
611 break;
612 }
613
614 if (!startswith(de->d_name, ".#"))
615 continue;
616
617 r = rm_rf_child(dirfd(d), de->d_name, REMOVE_PHYSICAL|REMOVE_SUBVOLUME|REMOVE_CHMOD);
618 if (r == -ENOENT)
619 continue;
620 if (r < 0) {
621 log_warning_errno(r, "Failed to remove temporary resource instance '%s/%s', ignoring: %m", t->target.path, de->d_name);
622 continue;
623 }
624
625 log_debug("Removed temporary resource instance '%s/%s'.", t->target.path, de->d_name);
626 }
627 }
628
transfer_vacuum(Transfer * t,uint64_t space,const char * extra_protected_version)629 int transfer_vacuum(
630 Transfer *t,
631 uint64_t space,
632 const char *extra_protected_version) {
633
634 uint64_t instances_max, limit;
635 int r, count = 0;
636
637 assert(t);
638
639 transfer_remove_temporary(t);
640
641 /* First, calculate how many instances to keep, based on the instance limit — but keep at least one */
642
643 instances_max = arg_instances_max != UINT64_MAX ? arg_instances_max : t->instances_max;
644 assert(instances_max >= 1);
645 if (instances_max == UINT64_MAX) /* Keep infinite instances? */
646 limit = UINT64_MAX;
647 else if (space > instances_max)
648 return log_error_errno(SYNTHETIC_ERRNO(ENOSPC),
649 "Asked to delete more instances than total maximum allowed number of instances, refusing.");
650 else if (space == instances_max)
651 return log_error_errno(SYNTHETIC_ERRNO(ENOSPC),
652 "Asked to delete all possible instances, can't allow that. One instance must always remain.");
653 else
654 limit = instances_max - space;
655
656 if (t->target.type == RESOURCE_PARTITION) {
657 uint64_t rm, remain;
658
659 /* If we are looking at a partition table, we also have to take into account how many
660 * partition slots of the right type are available */
661
662 if (t->target.n_empty + t->target.n_instances < 2)
663 return log_error_errno(SYNTHETIC_ERRNO(ENOSPC),
664 "Partition table has less than two partition slots of the right type " SD_ID128_UUID_FORMAT_STR " (%s), refusing.",
665 SD_ID128_FORMAT_VAL(t->target.partition_type),
666 gpt_partition_type_uuid_to_string(t->target.partition_type));
667 if (space > t->target.n_empty + t->target.n_instances)
668 return log_error_errno(SYNTHETIC_ERRNO(ENOSPC),
669 "Partition table does not have enough partition slots of right type " SD_ID128_UUID_FORMAT_STR " (%s) for operation.",
670 SD_ID128_FORMAT_VAL(t->target.partition_type),
671 gpt_partition_type_uuid_to_string(t->target.partition_type));
672 if (space == t->target.n_empty + t->target.n_instances)
673 return log_error_errno(SYNTHETIC_ERRNO(ENOSPC),
674 "Asked to empty all partition table slots of the right type " SD_ID128_UUID_FORMAT_STR " (%s), can't allow that. One instance must always remain.",
675 SD_ID128_FORMAT_VAL(t->target.partition_type),
676 gpt_partition_type_uuid_to_string(t->target.partition_type));
677
678 rm = LESS_BY(space, t->target.n_empty);
679 remain = LESS_BY(t->target.n_instances, rm);
680 limit = MIN(limit, remain);
681 }
682
683 while (t->target.n_instances > limit) {
684 Instance *oldest;
685 size_t p = t->target.n_instances - 1;
686
687 for (;;) {
688 oldest = t->target.instances[p];
689 assert(oldest);
690
691 /* If this is listed among the protected versions, then let's not remove it */
692 if (!strv_contains(t->protected_versions, oldest->metadata.version) &&
693 (!extra_protected_version || !streq(extra_protected_version, oldest->metadata.version)))
694 break;
695
696 log_debug("Version '%s' is protected, not removing.", oldest->metadata.version);
697 if (p == 0) {
698 oldest = NULL;
699 break;
700 }
701
702 p--;
703 }
704
705 if (!oldest) /* Nothing more to remove */
706 break;
707
708 assert(oldest->resource);
709
710 log_info("%s Removing old '%s' (%s).", special_glyph(SPECIAL_GLYPH_RECYCLING), oldest->path, resource_type_to_string(oldest->resource->type));
711
712 switch (t->target.type) {
713
714 case RESOURCE_REGULAR_FILE:
715 case RESOURCE_DIRECTORY:
716 case RESOURCE_SUBVOLUME:
717 r = rm_rf(oldest->path, REMOVE_ROOT|REMOVE_PHYSICAL|REMOVE_SUBVOLUME|REMOVE_MISSING_OK|REMOVE_CHMOD);
718 if (r < 0 && r != -ENOENT)
719 return log_error_errno(r, "Failed to make room, deleting '%s' failed: %m", oldest->path);
720
721 break;
722
723 case RESOURCE_PARTITION: {
724 PartitionInfo pinfo = oldest->partition_info;
725
726 /* label "_empty" means "no contents" for our purposes */
727 pinfo.label = (char*) "_empty";
728
729 r = patch_partition(t->target.path, &pinfo, PARTITION_LABEL);
730 if (r < 0)
731 return r;
732
733 t->target.n_empty++;
734 break;
735 }
736
737 default:
738 assert_not_reached();
739 break;
740 }
741
742 instance_free(oldest);
743 memmove(t->target.instances + p, t->target.instances + p + 1, (t->target.n_instances - p - 1) * sizeof(Instance*));
744 t->target.n_instances--;
745
746 count++;
747 }
748
749 return count;
750 }
751
compile_pattern_fields(const Transfer * t,const Instance * i,InstanceMetadata * ret)752 static void compile_pattern_fields(
753 const Transfer *t,
754 const Instance *i,
755 InstanceMetadata *ret) {
756
757 assert(t);
758 assert(i);
759 assert(ret);
760
761 *ret = (InstanceMetadata) {
762 .version = i->metadata.version,
763
764 /* We generally prefer explicitly configured values for the transfer over those automatically
765 * derived from the source instance. Also, if the source is a tar archive, then let's not
766 * patch mtime/mode and use the one embedded in the tar file */
767 .partition_uuid = t->partition_uuid_set ? t->partition_uuid : i->metadata.partition_uuid,
768 .partition_uuid_set = t->partition_uuid_set || i->metadata.partition_uuid_set,
769 .partition_flags = t->partition_flags_set ? t->partition_flags : i->metadata.partition_flags,
770 .partition_flags_set = t->partition_flags_set || i->metadata.partition_flags_set,
771 .mtime = RESOURCE_IS_TAR(i->resource->type) ? USEC_INFINITY : i->metadata.mtime,
772 .mode = t->mode != MODE_INVALID ? t->mode : (RESOURCE_IS_TAR(i->resource->type) ? MODE_INVALID : i->metadata.mode),
773 .size = i->metadata.size,
774 .tries_done = t->tries_done != UINT64_MAX ? t->tries_done :
775 i->metadata.tries_done != UINT64_MAX ? i->metadata.tries_done : 0,
776 .tries_left = t->tries_left != UINT64_MAX ? t->tries_left :
777 i->metadata.tries_left != UINT64_MAX ? i->metadata.tries_left : 3,
778 .no_auto = t->no_auto >= 0 ? t->no_auto : i->metadata.no_auto,
779 .read_only = t->read_only >= 0 ? t->read_only : i->metadata.read_only,
780 .growfs = t->growfs >= 0 ? t->growfs : i->metadata.growfs,
781 .sha256sum_set = i->metadata.sha256sum_set,
782 };
783
784 memcpy(ret->sha256sum, i->metadata.sha256sum, sizeof(ret->sha256sum));
785 }
786
run_helper(const char * name,const char * path,const char * const cmdline[])787 static int run_helper(
788 const char *name,
789 const char *path,
790 const char * const cmdline[]) {
791
792 int r;
793
794 assert(name);
795 assert(path);
796 assert(cmdline);
797
798 r = safe_fork(name, FORK_RESET_SIGNALS|FORK_DEATHSIG|FORK_LOG|FORK_WAIT, NULL);
799 if (r < 0)
800 return r;
801 if (r == 0) {
802 /* Child */
803
804 (void) unsetenv("NOTIFY_SOCKET");
805 execv(path, (char *const*) cmdline);
806 log_error_errno(errno, "Failed to execute %s tool: %m", path);
807 _exit(EXIT_FAILURE);
808 }
809
810 return 0;
811 }
812
transfer_acquire_instance(Transfer * t,Instance * i)813 int transfer_acquire_instance(Transfer *t, Instance *i) {
814 _cleanup_free_ char *formatted_pattern = NULL, *digest = NULL;
815 char offset[DECIMAL_STR_MAX(uint64_t)+1], max_size[DECIMAL_STR_MAX(uint64_t)+1];
816 const char *where = NULL;
817 InstanceMetadata f;
818 Instance *existing;
819 int r;
820
821 assert(t);
822 assert(i);
823 assert(i->resource);
824 assert(t == container_of(i->resource, Transfer, source));
825
826 /* Does this instance already exist in the target? Then we don't need to acquire anything */
827 existing = resource_find_instance(&t->target, i->metadata.version);
828 if (existing) {
829 log_info("No need to acquire '%s', already installed.", i->path);
830 return 0;
831 }
832
833 assert(!t->final_path);
834 assert(!t->temporary_path);
835 assert(!strv_isempty(t->target.patterns));
836
837 /* Format the target name using the first pattern specified */
838 compile_pattern_fields(t, i, &f);
839 r = pattern_format(t->target.patterns[0], &f, &formatted_pattern);
840 if (r < 0)
841 return log_error_errno(r, "Failed to format target pattern: %m");
842
843 if (RESOURCE_IS_FILESYSTEM(t->target.type)) {
844
845 if (!filename_is_valid(formatted_pattern))
846 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Formatted pattern is not suitable as file name, refusing: %s", formatted_pattern);
847
848 t->final_path = path_join(t->target.path, formatted_pattern);
849 if (!t->final_path)
850 return log_oom();
851
852 r = tempfn_random(t->final_path, "sysupdate", &t->temporary_path);
853 if (r < 0)
854 return log_error_errno(r, "Failed to generate temporary target path: %m");
855
856 where = t->final_path;
857 }
858
859 if (t->target.type == RESOURCE_PARTITION) {
860 r = gpt_partition_label_valid(formatted_pattern);
861 if (r < 0)
862 return log_error_errno(r, "Failed to determine if formatted pattern is suitable as GPT partition label: %s", formatted_pattern);
863 if (!r)
864 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Formatted pattern is not suitable as GPT partition label, refusing: %s", formatted_pattern);
865
866 r = find_suitable_partition(
867 t->target.path,
868 i->metadata.size,
869 t->target.partition_type_set ? &t->target.partition_type : NULL,
870 &t->partition_info);
871 if (r < 0)
872 return r;
873
874 xsprintf(offset, "%" PRIu64, t->partition_info.start);
875 xsprintf(max_size, "%" PRIu64, t->partition_info.size);
876
877 where = t->partition_info.device;
878 }
879
880 assert(where);
881
882 log_info("%s Acquiring %s %s %s...", special_glyph(SPECIAL_GLYPH_DOWNLOAD), i->path, special_glyph(SPECIAL_GLYPH_ARROW_RIGHT), where);
883
884 if (RESOURCE_IS_URL(i->resource->type)) {
885 /* For URL sources we require the SHA256 sum to be known so that we can validate the
886 * download. */
887
888 if (!i->metadata.sha256sum_set)
889 return log_error_errno(r, "SHA256 checksum not known for download '%s', refusing.", i->path);
890
891 digest = hexmem(i->metadata.sha256sum, sizeof(i->metadata.sha256sum));
892 if (!digest)
893 return log_oom();
894 }
895
896 switch (i->resource->type) { /* Source */
897
898 case RESOURCE_REGULAR_FILE:
899
900 switch (t->target.type) { /* Target */
901
902 case RESOURCE_REGULAR_FILE:
903
904 /* regular file → regular file (why fork off systemd-import for such a simple file
905 * copy case? implicit decompression mostly, and thus also sandboxing. Also, the
906 * importer has some tricks up its sleeve, such as sparse file generation, which we
907 * want to take benefit of, too.) */
908
909 r = run_helper("(sd-import-raw)",
910 import_binary_path(),
911 (const char* const[]) {
912 "systemd-import",
913 "raw",
914 "--direct", /* just copy/unpack the specified file, don't do anything else */
915 arg_sync ? "--sync=yes" : "--sync=no",
916 i->path,
917 t->temporary_path,
918 NULL
919 });
920 break;
921
922 case RESOURCE_PARTITION:
923
924 /* regular file → partition */
925
926 r = run_helper("(sd-import-raw)",
927 import_binary_path(),
928 (const char* const[]) {
929 "systemd-import",
930 "raw",
931 "--direct", /* just copy/unpack the specified file, don't do anything else */
932 "--offset", offset,
933 "--size-max", max_size,
934 arg_sync ? "--sync=yes" : "--sync=no",
935 i->path,
936 t->target.path,
937 NULL
938 });
939 break;
940
941 default:
942 assert_not_reached();
943 }
944
945 break;
946
947 case RESOURCE_DIRECTORY:
948 case RESOURCE_SUBVOLUME:
949 assert(IN_SET(t->target.type, RESOURCE_DIRECTORY, RESOURCE_SUBVOLUME));
950
951 /* directory/subvolume → directory/subvolume */
952
953 r = run_helper("(sd-import-fs)",
954 import_fs_binary_path(),
955 (const char* const[]) {
956 "systemd-import-fs",
957 "run",
958 "--direct", /* just untar the specified file, don't do anything else */
959 arg_sync ? "--sync=yes" : "--sync=no",
960 t->target.type == RESOURCE_SUBVOLUME ? "--btrfs-subvol=yes" : "--btrfs-subvol=no",
961 i->path,
962 t->temporary_path,
963 NULL
964 });
965 break;
966
967 case RESOURCE_TAR:
968 assert(IN_SET(t->target.type, RESOURCE_DIRECTORY, RESOURCE_SUBVOLUME));
969
970 /* tar → directory/subvolume */
971
972 r = run_helper("(sd-import-tar)",
973 import_binary_path(),
974 (const char* const[]) {
975 "systemd-import",
976 "tar",
977 "--direct", /* just untar the specified file, don't do anything else */
978 arg_sync ? "--sync=yes" : "--sync=no",
979 t->target.type == RESOURCE_SUBVOLUME ? "--btrfs-subvol=yes" : "--btrfs-subvol=no",
980 i->path,
981 t->temporary_path,
982 NULL
983 });
984 break;
985
986 case RESOURCE_URL_FILE:
987
988 switch (t->target.type) {
989
990 case RESOURCE_REGULAR_FILE:
991
992 /* url file → regular file */
993
994 r = run_helper("(sd-pull-raw)",
995 pull_binary_path(),
996 (const char* const[]) {
997 "systemd-pull",
998 "raw",
999 "--direct", /* just download the specified URL, don't download anything else */
1000 "--verify", digest, /* validate by explicit SHA256 sum */
1001 arg_sync ? "--sync=yes" : "--sync=no",
1002 i->path,
1003 t->temporary_path,
1004 NULL
1005 });
1006 break;
1007
1008 case RESOURCE_PARTITION:
1009
1010 /* url file → partition */
1011
1012 r = run_helper("(sd-pull-raw)",
1013 pull_binary_path(),
1014 (const char* const[]) {
1015 "systemd-pull",
1016 "raw",
1017 "--direct", /* just download the specified URL, don't download anything else */
1018 "--verify", digest, /* validate by explicit SHA256 sum */
1019 "--offset", offset,
1020 "--size-max", max_size,
1021 arg_sync ? "--sync=yes" : "--sync=no",
1022 i->path,
1023 t->target.path,
1024 NULL
1025 });
1026 break;
1027
1028 default:
1029 assert_not_reached();
1030 }
1031
1032 break;
1033
1034 case RESOURCE_URL_TAR:
1035 assert(IN_SET(t->target.type, RESOURCE_DIRECTORY, RESOURCE_SUBVOLUME));
1036
1037 r = run_helper("(sd-pull-tar)",
1038 pull_binary_path(),
1039 (const char*const[]) {
1040 "systemd-pull",
1041 "tar",
1042 "--direct", /* just download the specified URL, don't download anything else */
1043 "--verify", digest, /* validate by explicit SHA256 sum */
1044 t->target.type == RESOURCE_SUBVOLUME ? "--btrfs-subvol=yes" : "--btrfs-subvol=no",
1045 arg_sync ? "--sync=yes" : "--sync=no",
1046 i->path,
1047 t->temporary_path,
1048 NULL
1049 });
1050 break;
1051
1052 default:
1053 assert_not_reached();
1054 }
1055 if (r < 0)
1056 return r;
1057
1058 if (RESOURCE_IS_FILESYSTEM(t->target.type)) {
1059 bool need_sync = false;
1060 assert(t->temporary_path);
1061
1062 /* Apply file attributes if set */
1063 if (f.mtime != USEC_INFINITY) {
1064 struct timespec ts;
1065
1066 timespec_store(&ts, f.mtime);
1067
1068 if (utimensat(AT_FDCWD, t->temporary_path, (struct timespec[2]) { ts, ts }, AT_SYMLINK_NOFOLLOW) < 0)
1069 return log_error_errno(errno, "Failed to adjust mtime of '%s': %m", t->temporary_path);
1070
1071 need_sync = true;
1072 }
1073
1074 if (f.mode != MODE_INVALID) {
1075 /* Try with AT_SYMLINK_NOFOLLOW first, because it's the safe thing to do. Older
1076 * kernels don't support that however, in that case we fall back to chmod(). Not as
1077 * safe, but shouldn't be a problem, given that we don't create symlinks here. */
1078 if (fchmodat(AT_FDCWD, t->temporary_path, f.mode, AT_SYMLINK_NOFOLLOW) < 0 &&
1079 (!ERRNO_IS_NOT_SUPPORTED(errno) || chmod(t->temporary_path, f.mode) < 0))
1080 return log_error_errno(errno, "Failed to adjust mode of '%s': %m", t->temporary_path);
1081
1082 need_sync = true;
1083 }
1084
1085 /* Synchronize */
1086 if (arg_sync && need_sync) {
1087 if (t->target.type == RESOURCE_REGULAR_FILE)
1088 r = fsync_path_and_parent_at(AT_FDCWD, t->temporary_path);
1089 else {
1090 assert(IN_SET(t->target.type, RESOURCE_DIRECTORY, RESOURCE_SUBVOLUME));
1091 r = syncfs_path(AT_FDCWD, t->temporary_path);
1092 }
1093 if (r < 0)
1094 return log_error_errno(r, "Failed to synchronize file system backing '%s': %m", t->temporary_path);
1095 }
1096
1097 t->install_read_only = f.read_only;
1098 }
1099
1100 if (t->target.type == RESOURCE_PARTITION) {
1101 free_and_replace(t->partition_info.label, formatted_pattern);
1102 t->partition_change = PARTITION_LABEL;
1103
1104 if (f.partition_uuid_set) {
1105 t->partition_info.uuid = f.partition_uuid;
1106 t->partition_change |= PARTITION_UUID;
1107 }
1108
1109 if (f.partition_flags_set) {
1110 t->partition_info.flags = f.partition_flags;
1111 t->partition_change |= PARTITION_FLAGS;
1112 }
1113
1114 if (f.no_auto >= 0) {
1115 t->partition_info.no_auto = f.no_auto;
1116 t->partition_change |= PARTITION_NO_AUTO;
1117 }
1118
1119 if (f.read_only >= 0) {
1120 t->partition_info.read_only = f.read_only;
1121 t->partition_change |= PARTITION_READ_ONLY;
1122 }
1123
1124 if (f.growfs >= 0) {
1125 t->partition_info.growfs = f.growfs;
1126 t->partition_change |= PARTITION_GROWFS;
1127 }
1128 }
1129
1130 /* For regular file cases the only step left is to install the file in place, which install_file()
1131 * will do via rename(). For partition cases the only step left is to update the partition table,
1132 * which is done at the same place. */
1133
1134 log_info("Successfully acquired '%s'.", i->path);
1135 return 0;
1136 }
1137
transfer_install_instance(Transfer * t,Instance * i,const char * root)1138 int transfer_install_instance(
1139 Transfer *t,
1140 Instance *i,
1141 const char *root) {
1142
1143 int r;
1144
1145 assert(t);
1146 assert(i);
1147 assert(i->resource);
1148 assert(t == container_of(i->resource, Transfer, source));
1149
1150 if (t->temporary_path) {
1151 assert(RESOURCE_IS_FILESYSTEM(t->target.type));
1152 assert(t->final_path);
1153
1154 r = install_file(AT_FDCWD, t->temporary_path,
1155 AT_FDCWD, t->final_path,
1156 INSTALL_REPLACE|
1157 (t->install_read_only > 0 ? INSTALL_READ_ONLY : 0)|
1158 (t->target.type == RESOURCE_REGULAR_FILE ? INSTALL_FSYNC_FULL : INSTALL_SYNCFS));
1159 if (r < 0)
1160 return log_error_errno(r, "Failed to move '%s' into place: %m", t->final_path);
1161
1162 log_info("Successfully installed '%s' (%s) as '%s' (%s).",
1163 i->path,
1164 resource_type_to_string(i->resource->type),
1165 t->final_path,
1166 resource_type_to_string(t->target.type));
1167
1168 t->temporary_path = mfree(t->temporary_path);
1169 }
1170
1171 if (t->partition_change != 0) {
1172 assert(t->target.type == RESOURCE_PARTITION);
1173
1174 r = patch_partition(
1175 t->target.path,
1176 &t->partition_info,
1177 t->partition_change);
1178 if (r < 0)
1179 return r;
1180
1181 log_info("Successfully installed '%s' (%s) as '%s' (%s).",
1182 i->path,
1183 resource_type_to_string(i->resource->type),
1184 t->partition_info.device,
1185 resource_type_to_string(t->target.type));
1186 }
1187
1188 if (t->current_symlink) {
1189 _cleanup_free_ char *buf = NULL, *parent = NULL, *relative = NULL, *resolved = NULL;
1190 const char *link_path, *link_target;
1191 bool resolve_link_path = false;
1192
1193 if (RESOURCE_IS_FILESYSTEM(t->target.type)) {
1194
1195 assert(t->target.path);
1196
1197 if (path_is_absolute(t->current_symlink)) {
1198 link_path = t->current_symlink;
1199 resolve_link_path = true;
1200 } else {
1201 buf = path_make_absolute(t->current_symlink, t->target.path);
1202 if (!buf)
1203 return log_oom();
1204
1205 link_path = buf;
1206 }
1207
1208 link_target = t->final_path;
1209
1210 } else if (t->target.type == RESOURCE_PARTITION) {
1211
1212 assert(path_is_absolute(t->current_symlink));
1213
1214 link_path = t->current_symlink;
1215 link_target = t->partition_info.device;
1216
1217 resolve_link_path = true;
1218 } else
1219 assert_not_reached();
1220
1221 if (resolve_link_path && root) {
1222 r = chase_symlinks(link_path, root, CHASE_PREFIX_ROOT|CHASE_NONEXISTENT, &resolved, NULL);
1223 if (r < 0)
1224 return log_error_errno(r, "Failed to resolve current symlink path '%s': %m", link_path);
1225
1226 link_path = resolved;
1227 }
1228
1229 if (link_target) {
1230 r = path_extract_directory(link_path, &parent);
1231 if (r < 0)
1232 return log_error_errno(r, "Failed to extract directory of target path '%s': %m", link_path);
1233
1234 r = path_make_relative(parent, link_target, &relative);
1235 if (r < 0)
1236 return log_error_errno(r, "Failed to make symlink path '%s' relative to '%s': %m", link_target, parent);
1237
1238 r = symlink_atomic(relative, link_path);
1239 if (r < 0)
1240 return log_error_errno(r, "Failed to update current symlink '%s' → '%s': %m", link_path, relative);
1241
1242 log_info("Updated symlink '%s' → '%s'.", link_path, relative);
1243 }
1244 }
1245
1246 return 0;
1247 }
1248