Skip to content

Commit

Permalink
Merge pull request rauc#726 from ejoerns/topic/install-hook-image
Browse files Browse the repository at this point in the history
Support Omitting Filename When 'install' Slot Hook is Used
  • Loading branch information
jluebbe authored Nov 8, 2021
2 parents 265d6f5 + 163a1df commit 64468ac
Show file tree
Hide file tree
Showing 8 changed files with 166 additions and 43 deletions.
16 changes: 14 additions & 2 deletions docs/using.rst
Original file line number Diff line number Diff line change
Expand Up @@ -430,8 +430,9 @@ An example on how to use a post-install hook:

The install hook will replace the entire default installation process for the
target slot of the image it was specified for. Note that when having the install
hook enabled, pre- and post-install hooks will *not* be executed.
The install hook allows to fully customize the way an image is installed. This
hook enabled, pre- and post-install hooks will *not* be executed and having
an image (i.e. ``filename`` set) is optional, too!
The install hook allows to fully customize the way a slot is updated. This
allows performing special installation methods that are not natively supported
by RAUC, for example to upgrade the bootloader to a new version while also
migrating configuration settings.
Expand All @@ -447,6 +448,17 @@ migrating configuration settings.
sha256=...
hooks=install
or, without ``filename``:

.. code-block:: cfg
[hooks]
filename=hook
[image.datafs]
hooks=install
Full Custom Update
~~~~~~~~~~~~~~~~~~

Expand Down
23 changes: 15 additions & 8 deletions src/install.c
Original file line number Diff line number Diff line change
Expand Up @@ -499,11 +499,11 @@ static void prepare_environment(GSubprocessLauncher *launcher, gchar *update_sou
RaucImage *img = l->data;
if (g_str_equal(slot->sclass, img->slotclass)) {
varname = g_strdup_printf("RAUC_IMAGE_NAME_%i", slotcnt);
g_subprocess_launcher_setenv(launcher, varname, img->filename, TRUE);
g_subprocess_launcher_setenv(launcher, varname, img->filename ?: "", TRUE);
g_clear_pointer(&varname, g_free);

varname = g_strdup_printf("RAUC_IMAGE_DIGEST_%i", slotcnt);
g_subprocess_launcher_setenv(launcher, varname, img->checksum.digest, TRUE);
g_subprocess_launcher_setenv(launcher, varname, img->checksum.digest ?: "", TRUE);
g_clear_pointer(&varname, g_free);

varname = g_strdup_printf("RAUC_IMAGE_CLASS_%i", slotcnt);
Expand Down Expand Up @@ -744,6 +744,10 @@ static gboolean pre_install_checks(gchar* bundledir, GList *install_images, GHas
RaucImage *mfimage = l->data;
RaucSlot *dest_slot = g_hash_table_lookup(target_group, mfimage->slotclass);

/* skip source image checks if filename is not set (install hook) */
if (!mfimage->filename && mfimage->hooks.install)
goto skip_filename_checks;

/* if image filename is relative, make it absolute */
if (!g_path_is_absolute(mfimage->filename)) {
gchar *filename = g_build_filename(bundledir, mfimage->filename, NULL);
Expand All @@ -757,6 +761,7 @@ static gboolean pre_install_checks(gchar* bundledir, GList *install_images, GHas
return FALSE;
}

skip_filename_checks:
if (!g_file_test(dest_slot->device, G_FILE_TEST_EXISTS)) {
g_set_error(error, R_INSTALL_ERROR, R_INSTALL_ERROR_NODST,
"Destination device '%s' not found", dest_slot->device);
Expand Down Expand Up @@ -894,17 +899,19 @@ static gboolean launch_and_wait_default_handler(RaucInstallArgs *args, gchar* bu
g_free(slot_state->status);
slot_state->status = g_strdup("update");

g_message("Slot needs to be updated with %s", mfimage->filename);

r_context_end_step("check_slot", TRUE);

install_args_update(args, g_strdup_printf("Updating slot %s", dest_slot->name));

/* update slot */
if (mfimage->variant)
g_message("Updating %s with %s (variant: %s)", dest_slot->device, mfimage->filename, mfimage->variant);
else
g_message("Updating %s with %s", dest_slot->device, mfimage->filename);
if (mfimage->hooks.install) {
g_message("Updating %s with 'install' slot hook", dest_slot->device);
} else {
if (mfimage->variant)
g_message("Updating %s with %s (variant: %s)", dest_slot->device, mfimage->filename, mfimage->variant);
else
g_message("Updating %s with %s", dest_slot->device, mfimage->filename);
}

r_context_begin_step_formatted("copy_image", 0, "Copying image to %s", dest_slot->name);

Expand Down
36 changes: 20 additions & 16 deletions src/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -658,6 +658,17 @@ static gboolean convert_start(int argc, char **argv)
return TRUE;
}

/* Definition list for terminal colors */
#define KNRM "\x1B[0m"
#define KRED "\x1B[31m"
#define KGRN "\x1B[32m"
#define KYEL "\x1B[33m"
#define KBLU "\x1B[34m"
#define KMAG "\x1B[35m"
#define KCYN "\x1B[36m"
#define KWHT "\x1B[37m"
#define KBLD "\x1B[1m"

/* Takes a shell variable and its desired argument as input and appends it to
* the provided text with taking care of correct shell quoting */
static void formatter_shell_append(GString* text, const gchar* varname, const gchar* argument)
Expand Down Expand Up @@ -762,16 +773,20 @@ static gchar *info_formatter_readable(RaucManifest *manifest)
g_ptr_array_unref(hooks);

cnt = g_list_length(manifest->images);
g_string_append_printf(text, "%d Image%s%s\n", cnt, cnt == 1 ? "" : "s", cnt > 0 ? ":" : "");
g_string_append_printf(text, "\n%d Image%s%s\n", cnt, cnt == 1 ? "" : "s", cnt > 0 ? ":" : "");
cnt = 1;
for (GList *l = manifest->images; l != NULL; l = l->next) {
RaucImage *img = l->data;
g_string_append_printf(text, "(%d)\t%s\n", cnt, img->filename);
g_string_append_printf(text, "\tSlotclass: %s\n", img->slotclass);
g_string_append_printf(text, " "KBLD "[%s]"KNRM "\n", img->slotclass);
if (img->variant)
g_string_append_printf(text, "\tVariant: %s\n", img->variant);
g_string_append_printf(text, "\tChecksum: %s\n", img->checksum.digest);
g_string_append_printf(text, "\tSize: %"G_GOFFSET_FORMAT "\n", img->checksum.size);
if (img->filename) {
g_string_append_printf(text, "\tFilename: %s\n", img->filename);
g_string_append_printf(text, "\tChecksum: %s\n", img->checksum.digest);
g_string_append_printf(text, "\tSize: %"G_GOFFSET_FORMAT "\n", img->checksum.size);
} else {
g_string_append_printf(text, "\t(no image file)\n");
}

hooks = g_ptr_array_new();
if (img->hooks.pre_install == TRUE) {
Expand Down Expand Up @@ -993,17 +1008,6 @@ static void free_status_print(RaucStatusPrint *status)

G_DEFINE_AUTOPTR_CLEANUP_FUNC(RaucStatusPrint, free_status_print);

/* Definition list for terminal colors */
#define KNRM "\x1B[0m"
#define KRED "\x1B[31m"
#define KGRN "\x1B[32m"
#define KYEL "\x1B[33m"
#define KBLU "\x1B[34m"
#define KMAG "\x1B[35m"
#define KCYN "\x1B[36m"
#define KWHT "\x1B[37m"
#define KBLD "\x1B[1m"

static void r_string_append_slot(GString *text, RaucSlot *slot, RaucStatusPrint *status)
{
RaucSlotStatus *slot_state = slot->status;
Expand Down
53 changes: 42 additions & 11 deletions src/manifest.c
Original file line number Diff line number Diff line change
Expand Up @@ -45,14 +45,15 @@ static gboolean parse_image(GKeyFile *key_file, const gchar *group, RaucImage **
iimage->checksum.digest = value;
}
iimage->checksum.size = g_key_file_get_uint64(key_file,
group, "size", NULL);
g_key_file_remove_key(key_file, group, "size", NULL);

iimage->filename = key_file_consume_string(key_file, group, "filename", &ierror);
if (iimage->filename == NULL) {
group, "size", &ierror);
if (g_error_matches(ierror, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_KEY_NOT_FOUND)) {
iimage->checksum.size = -1;
g_clear_error(&ierror);
} else if (ierror) {
g_propagate_error(error, ierror);
goto out;
}
g_key_file_remove_key(key_file, group, "size", NULL);

hooks = g_key_file_get_string_list(key_file, group, "hooks", &entries, NULL);
for (gsize j = 0; j < entries; j++) {
Expand All @@ -70,6 +71,13 @@ static gboolean parse_image(GKeyFile *key_file, const gchar *group, RaucImage **
}
g_key_file_remove_key(key_file, group, "hooks", NULL);

iimage->filename = key_file_consume_string(key_file, group, "filename", &ierror);
/* 'filename' is optional only for 'install' hooks */
if (iimage->filename == NULL && !iimage->hooks.install) {
g_propagate_error(error, ierror);
goto out;
}

if (!check_remaining_keys(key_file, group, &ierror)) {
g_propagate_error(error, ierror);
goto out;
Expand Down Expand Up @@ -277,7 +285,12 @@ static gboolean check_manifest_common(const RaucManifest *mf, GError **error)
RaucImage *image = l->data;

g_assert(image);
g_assert(image->filename);

/* Having no 'filename' set is valid for 'install' hook only.
* This is already ensured during manifest parsing, thus simply
* skip further checks here */
if (!image->filename)
continue;

if (image->checksum.type != G_CHECKSUM_SHA256) {
g_set_error(error, R_MANIFEST_ERROR, R_MANIFEST_CHECK_ERROR, "Unsupported checksum algorithm for image %s", image->filename);
Expand All @@ -287,9 +300,19 @@ static gboolean check_manifest_common(const RaucManifest *mf, GError **error)
g_set_error(error, R_MANIFEST_ERROR, R_MANIFEST_CHECK_ERROR, "Missing digest for image %s", image->filename);
goto out;
}
if (!image->checksum.size) {
g_set_error(error, R_MANIFEST_ERROR, R_MANIFEST_CHECK_ERROR, "Missing size for image %s", image->filename);
goto out;
if (image->checksum.size < 0) {
/* RAUC versions before v1.5 allowed zero-size images but did not handle this explicitly.
* Thus, bundles created did have a valid 'filename=' manifest entry
* but the 'size=' entry was considered as empty and not set at all.
* Retain support for this case, at least for the 'install' per-slot hook use-case
* where an image file can be optional. */
if (image->hooks.install) {
g_message("Missing size parameter for image '%s'", image->filename);
image->checksum.size = 0;
} else {
g_set_error(error, R_MANIFEST_ERROR, R_MANIFEST_CHECK_ERROR, "Missing size for image %s", image->filename);
goto out;
}
}
}

Expand Down Expand Up @@ -507,7 +530,7 @@ static GKeyFile *prepare_manifest(const RaucManifest *mf)

if (image->checksum.type == G_CHECKSUM_SHA256)
g_key_file_set_string(key_file, group, "sha256", image->checksum.digest);
if (image->checksum.size)
if (image->checksum.size >= 0)
g_key_file_set_uint64(key_file, group, "size", image->checksum.size);

if (image->filename)
Expand Down Expand Up @@ -619,7 +642,15 @@ static gboolean update_manifest_checksums(RaucManifest *manifest, const gchar *d

for (GList *elem = manifest->images; elem != NULL; elem = elem->next) {
RaucImage *image = elem->data;
g_autofree gchar *filename = g_build_filename(dir, image->filename, NULL);
g_autofree gchar *filename = NULL;

/* If no filename is set (valid for 'install' hook) explicitly set size to -1 */
if (!image->filename) {
image->checksum.size = -1;
continue;
}

filename = g_build_filename(dir, image->filename, NULL);
res = compute_checksum(&image->checksum, filename, &ierror);
if (!res) {
g_warning("Failed updating checksum: %s", ierror->message);
Expand Down
7 changes: 3 additions & 4 deletions src/update_handler.c
Original file line number Diff line number Diff line change
Expand Up @@ -804,7 +804,7 @@ static gboolean run_slot_hook_extra_env(const gchar *hook_name, const gchar *hoo
g_assert_nonnull(slot->name);
g_assert_nonnull(slot->sclass);

g_message("Running slot hook %s for %s", hook_cmd, slot->name);
g_message("Running slot hook '%s' for %s", hook_cmd, slot->name);

launcher = g_subprocess_launcher_new(G_SUBPROCESS_FLAGS_NONE);

Expand All @@ -829,9 +829,9 @@ static gboolean run_slot_hook_extra_env(const gchar *hook_name, const gchar *hoo
}
if (image) {
image_size = g_strdup_printf("%" G_GOFFSET_FORMAT, image->checksum.size);
g_subprocess_launcher_setenv(launcher, "RAUC_IMAGE_NAME", image->filename, TRUE);
g_subprocess_launcher_setenv(launcher, "RAUC_IMAGE_NAME", image->filename ? image->filename : "", TRUE);
g_subprocess_launcher_setenv(launcher, "RAUC_IMAGE_SIZE", image_size, TRUE);
g_subprocess_launcher_setenv(launcher, "RAUC_IMAGE_DIGEST", image->checksum.digest, TRUE);
g_subprocess_launcher_setenv(launcher, "RAUC_IMAGE_DIGEST", image->checksum.digest ? image->checksum.digest : "", TRUE);
g_subprocess_launcher_setenv(launcher, "RAUC_IMAGE_CLASS", image->slotclass, TRUE);
}
g_subprocess_launcher_setenv(launcher, "RAUC_MOUNT_PREFIX", r_context()->config->mount_prefix, TRUE);
Expand Down Expand Up @@ -1866,7 +1866,6 @@ static gboolean hook_install_handler(RaucImage *image, RaucSlot *dest_slot, cons
gboolean res = FALSE;

/* run slot install hook */
g_message("Running custom slot install hook for %s", dest_slot->name);
res = run_slot_hook(hook_name, R_SLOT_HOOK_INSTALL, image, dest_slot, &ierror);
if (!res) {
g_propagate_error(error, ierror);
Expand Down
2 changes: 1 addition & 1 deletion test/install-content/hook.sh
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ case "$1" in
test -n "$RAUC_SLOT_BOOTNAME" || die_error "missing RAUC_SLOT_BOOTNAME for $RAUC_SLOT_NAME"
fi

echo "RAUC_IMAGE_PATH: $RAUC_IMAGE_PATH"
echo "RAUC_IMAGE_NAME: $RAUC_IMAGE_NAME"
echo "RAUC_SLOT_DEVICE: $RAUC_SLOT_DEVICE"
echo "RAUC_SLOT_MOUNT_POINT: $RAUC_SLOT_MOUNT_POINT"
echo "RAUC_MOUNT_PREFIX: $RAUC_MOUNT_PREFIX"
Expand Down
3 changes: 2 additions & 1 deletion test/install.c
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,8 @@ filename=appfs.ext4";
fixture_helper_set_up_bundle(fixture->tmpdir, manifest_file, &data->manifest_test_options);
}

/* Note: Also ensures having no image in a slot with an 'install' per-slot hook
* is valid. */
static void install_fixture_set_up_bundle_install_hook(InstallFixture *fixture,
gconstpointer user_data)
{
Expand All @@ -122,7 +124,6 @@ filename=rootfs.ext4\n\
hooks=install\n\
\n\
[image.appfs]\n\
filename=appfs.ext4\n\
hooks=install";

fixture->tmpdir = g_dir_make_tmp("rauc-XXXXXX", NULL);
Expand Down
69 changes: 69 additions & 0 deletions test/manifest.c
Original file line number Diff line number Diff line change
Expand Up @@ -353,6 +353,74 @@ hooks=install\n\
g_assert_false(res);
}

/* Check if missing 'size=' parameter in manifest image section causes and error.
* Also check that this does NOT cause an error when using a custom 'install'
* hook (to stay compatible with bundles having zero-size images generated with
* RAUC <1.5)
*/
static void test_manifest_missing_image_size(void)
{
g_autofree gchar *tmpdir;
g_autofree gchar *manifestpath = NULL;
g_autoptr(RaucManifest) rm = NULL;
gboolean res = FALSE;
g_autoptr(GError) error = NULL;
const gchar *mffile_invalid = "\
[update]\n\
compatible=FooCorp Super BarBazzer\n\
version=2015.04-1\n\
\n\
[hooks]\n\
filename=demo.hook\n\
\n\
[image.rootfs]\n\
filename=rootfs-default.ext4\n\
sha256=0815\n\
";
const gchar *mffile_valid = "\
[update]\n\
compatible=FooCorp Super BarBazzer\n\
version=2015.04-1\n\
\n\
[hooks]\n\
filename=demo.hook\n\
\n\
[image.rootfs]\n\
filename=rootfs-default.ext4\n\
sha256=0815\n\
hooks=install\n\
";

tmpdir = g_dir_make_tmp("rauc-XXXXXX", NULL);
g_assert_nonnull(tmpdir);

manifestpath = write_tmp_file(tmpdir, "invalid-manifest.raucm", mffile_invalid, NULL);
g_assert_nonnull(manifestpath);

res = load_manifest_file(manifestpath, &rm, &error);
g_assert_no_error(error);
g_assert_true(res);

res = check_manifest_internal(rm, &error);
g_assert_error(error, R_MANIFEST_ERROR, R_MANIFEST_CHECK_ERROR);
g_assert_false(res);

/* free */
g_free(manifestpath);
g_clear_pointer(&rm, free_manifest);
g_clear_error(&error);

manifestpath = write_tmp_file(tmpdir, "valid-manifest.raucm", mffile_valid, NULL);
g_assert_nonnull(manifestpath);

res = load_manifest_file(manifestpath, &rm, &error);
g_assert_no_error(error);
g_assert_true(res);

res = check_manifest_internal(rm, &error);
g_assert_no_error(error);
g_assert_true(res);
}

/* Test manifest/invalid_data:
*
Expand Down Expand Up @@ -457,6 +525,7 @@ int main(int argc, char *argv[])
g_test_add_func("/manifest/load_variants", test_manifest_load_variants);
g_test_add_func("/manifest/invalid_hook_name", test_manifest_invalid_hook_name);
g_test_add_func("/manifest/missing_hook_name", test_manifest_missing_hook_name);
g_test_add_func("/manifest/missing_image_size", test_manifest_missing_image_size);
g_test_add_func("/manifest/invalid_data", test_invalid_data);

return g_test_run();
Expand Down

0 comments on commit 64468ac

Please sign in to comment.