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/subvolumedirectory/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