int install_bundles(struct list *bundles, int current_version, struct manifest *mom) { int ret; struct file *file; struct list *iter; struct list *to_install_bundles, *to_install_files; /* step 1: check bundle args are valid if so populate subs struct */ ret = add_subscriptions(bundles, current_version, mom); if (ret) { if (ret == 1) { printf("bundle(s) already installed, exiting now\n"); } ret = EBUNDLE_INSTALL; goto out; } subscription_versions_from_MoM(mom, 0); to_install_bundles = recurse_manifest(mom, NULL); if (!to_install_bundles) { printf("Error: Cannot load to install bundles\n"); ret = ERECURSE_MANIFEST; goto out; } to_install_files = files_from_bundles(to_install_bundles); to_install_files = consolidate_files(to_install_files); /* step 2: download neccessary packs */ (void)rm_staging_dir_contents("download"); printf("Downloading packs...\n"); (void)download_subscribed_packs(true); /* step 3: Add tracked bundles */ read_subscriptions_alt(); subscription_versions_from_MoM(mom, 0); mom->submanifests = recurse_manifest(mom, NULL); if (!mom->submanifests) { printf("Error: Cannot load installed bundles\n"); ret = ERECURSE_MANIFEST; goto out; } mom->files = files_from_bundles(mom->submanifests); mom->files = consolidate_files(mom->files); /* step 4: Install all bundle(s) files into the fs */ printf("Installing bundle(s) files...\n"); iter = list_head(to_install_files); while (iter) { file = iter->data; iter = iter->next; if (file->is_deleted || file->do_not_update || ignore(file)) { continue; } ret = do_staging(file, mom); if (ret) { ret = verify_fix_path(file->filename, mom); } if (ret) { ret = EBUNDLE_INSTALL; goto out; } } iter = list_head(to_install_files); while (iter) { file = iter->data; iter = iter->next; if (file->is_deleted || file->do_not_update || ignore(file)) { continue; } /* This was staged by verify_fix_path */ if (!file->staging) { file = search_file_in_manifest(mom, file->filename); } rename_staged_file_to_final(file); } sync(); /* step 5: Run any scripts that are needed to complete update */ run_scripts(); ret = 0; printf("Bundle(s) installation done.\n"); out: free_subscriptions(); return ret; }
/* Consider adding a remove_leftovers() that runs in verify/fix in order to * allow this function to mkdtemp create folders for parallel build */ int do_staging(struct file *file, struct manifest *MoM) { char *statfile = NULL, *tmp = NULL, *tmp2 = NULL; char *dir, *base, *rel_dir; char *tarcommand = NULL; char *original = NULL; char *target = NULL; char *targetpath = NULL; char *rename_target = NULL; char *rename_tmpdir = NULL; int ret; struct stat s; tmp = strdup(file->filename); tmp2 = strdup(file->filename); dir = dirname(tmp); base = basename(tmp2); rel_dir = dir; if (*dir == '/') { rel_dir = dir + 1; } string_or_die(&original, "%s/staged/%s", state_dir, file->hash); string_or_die(&targetpath, "%s%s", path_prefix, rel_dir); ret = stat(targetpath, &s); if ((ret == -1) && (errno == ENOENT)) { fprintf(stderr, "Update target directory does not exist: %s. Trying to fix it\n", targetpath); verify_fix_path(dir, MoM); } else if (!S_ISDIR(s.st_mode)) { fprintf(stderr, "Error: Update target exists but is NOT a directory: %s\n", targetpath); } free_string(&targetpath); string_or_die(&target, "%s%s/.update.%s", path_prefix, rel_dir, base); ret = swupd_rm(target); if (ret < 0 && ret != -ENOENT) { fprintf(stderr, "Error: Failed to remove %s\n", target); } string_or_die(&statfile, "%s%s", path_prefix, file->filename); memset(&s, 0, sizeof(struct stat)); ret = lstat(statfile, &s); if (ret == 0) { if ((file->is_dir && !S_ISDIR(s.st_mode)) || (file->is_link && !S_ISLNK(s.st_mode)) || (file->is_file && !S_ISREG(s.st_mode))) { //file type changed, move old out of the way for new ret = swupd_rm(statfile); if (ret < 0) { ret = -ETYPE_CHANGED_FILE_RM; goto out; } } } free_string(&statfile); if (file->is_dir || S_ISDIR(s.st_mode)) { /* In the btrfs only scenario there was an implicit * "create_or_update_dir()" via un-tar-ing a directory.tar after * download and the untar happens in the staging subvolume which * then gets promoted to a "real" usable subvolume. But for * a live rootfs the directory needs copied out of staged * and into the rootfs. Tar is a way to copy with * attributes and it includes internal logic that does the * right thing to overlay a directory onto something * pre-existing: */ /* In order to avoid tar transforms with directories, rename * the directory before and after the tar command */ string_or_die(&rename_tmpdir, "%s/tmprenamedir", state_dir); ret = create_staging_renamedir(rename_tmpdir); if (ret) { goto out; } string_or_die(&rename_target, "%s/%s", rename_tmpdir, base); if (rename(original, rename_target)) { ret = -errno; goto out; } string_or_die(&tarcommand, TAR_COMMAND " -C '%s' " TAR_PERM_ATTR_ARGS " -cf - './%s' 2> /dev/null | " TAR_COMMAND " -C '%s%s' " TAR_PERM_ATTR_ARGS " -xf - 2> /dev/null", rename_tmpdir, base, path_prefix, rel_dir); ret = system(tarcommand); if (WIFEXITED(ret)) { ret = WEXITSTATUS(ret); } free_string(&tarcommand); if (rename(rename_target, original)) { ret = -errno; goto out; } if (ret < 0) { ret = -EDIR_OVERWRITE; goto out; } } else { /* (!file->is_dir && !S_ISDIR(stat.st_mode)) */ /* can't naively hard link(): Non-read-only files with same hash must remain * separate copies otherwise modifications to one instance of the file * propagate to all instances of the file perhaps causing subtle data corruption from * a user's perspective. In practice the rootfs is stateless and owned by us. * Additionally cross-mount hardlinks fail and it's hard to know what an admin * might have for overlaid mounts. The use of tar is a simple way to copy, but * inefficient. So prefer hardlink and fall back if needed: */ ret = -1; if (!file->is_config && !file->is_state && !file->use_xattrs) { ret = link(original, target); } if (ret < 0) { /* either the hardlink failed, or it was undesirable (config), do a tar-tar dance */ /* In order to avoid tar transforms, rename the file * before and after the tar command */ string_or_die(&rename_target, "%s/staged/.update.%s", state_dir, base); ret = rename(original, rename_target); if (ret) { ret = -errno; goto out; } string_or_die(&tarcommand, TAR_COMMAND " -C '%s/staged' " TAR_PERM_ATTR_ARGS " -cf - '.update.%s' 2> /dev/null | " TAR_COMMAND " -C '%s%s' " TAR_PERM_ATTR_ARGS " -xf - 2> /dev/null", state_dir, base, path_prefix, rel_dir); ret = system(tarcommand); if (WIFEXITED(ret)) { ret = WEXITSTATUS(ret); } free_string(&tarcommand); ret = rename(rename_target, original); if (ret) { ret = -errno; goto out; } } struct stat buf; int err; free_string(&file->staging); string_or_die(&file->staging, "%s%s/.update.%s", path_prefix, rel_dir, base); err = lstat(file->staging, &buf); if (err != 0) { free_string(&file->staging); ret = -EDOTFILE_WRITE; goto out; } } out: free_string(&target); free_string(&original); free_string(&rename_target); free_string(&rename_tmpdir); free_string(&tmp); free_string(&tmp2); return ret; }