diff --git a/.github/workflows/system-tests.yml b/.github/workflows/system-tests.yml index 27779d12..650e368c 100644 --- a/.github/workflows/system-tests.yml +++ b/.github/workflows/system-tests.yml @@ -36,6 +36,8 @@ jobs: layout.yaml random.bin lorem.txt lorem.tar root.ext4 echo "Show package content" sudo G_DEBUG=fatal-warnings partup -s show pkg.partup + echo "Validate package" + sudo G_DEBUG=fatal-warnings partup -s --size=128MiB validate pkg.partup echo "Install package to loop device" sudo G_DEBUG=fatal-warnings partup -d install pkg.partup ${{ env.loop_dev }} echo "Show loop device info" diff --git a/doc/usage.rst b/doc/usage.rst index a53cbe6e..fb78ff5f 100644 --- a/doc/usage.rst +++ b/doc/usage.rst @@ -37,8 +37,6 @@ Commands install [OPTION…] *PACKAGE* *DEVICE* Install a partup PACKAGE to DEVICE - -s, --skip-checksums Skip checksum verification for all input files - package [OPTION…] *PACKAGE* *FILES…* Create a partup PACKAGE with the contents FILES @@ -50,6 +48,17 @@ show [OPTION…] *PACKAGE* -s, --size Print the size of each file +validate [OPTION…] *PACKAGE* + Validate a package against a virtual or physical device + + -s, --skip-checksums Skip checksum verification for all input files + --size=SIZE Size of virtual device. Default is 1GB + -w, --write-image Install package to virtual device as an additional validation step + -b, --build-image=FILENAME Build a bootable image from the package. Implies --write-image + --device=DEVICE Validate against provided device + + Available since: :ref:`release-2.0.0` + version Print the program version diff --git a/meson.build b/meson.build index e37590f4..22d20e89 100644 --- a/meson.build +++ b/meson.build @@ -39,7 +39,9 @@ src = [ 'src/pu-flash.c', 'src/pu-glib-compat.c', 'src/pu-hashtable.c', + 'src/pu-input.c', 'src/pu-log.c', + 'src/pu-loopdev.c', 'src/pu-mount.c', 'src/pu-package.c', 'src/pu-utils.c' diff --git a/src/pu-emmc.c b/src/pu-emmc.c index 1384b5a9..d954d795 100644 --- a/src/pu-emmc.c +++ b/src/pu-emmc.c @@ -13,18 +13,11 @@ #include "pu-mount.h" #include "pu-utils.h" #include "pu-emmc.h" +#include "pu-input.h" #define PARTITION_TABLE_SIZE_MSDOS 1 #define PARTITION_TABLE_SIZE_GPT 34 -typedef struct _PuEmmcInput { - gchar *filename; - gchar *md5sum; - gchar *sha256sum; - - /* Internal members */ - gsize _size; -} PuEmmcInput; typedef struct _PuEmmcPartition { gchar *label; gchar *partuuid; @@ -41,7 +34,7 @@ typedef struct _PuEmmcPartition { typedef struct _PuEmmcBinary { PedSector input_offset; PedSector output_offset; - PuEmmcInput *input; + PuInput *input; } PuEmmcBinary; typedef struct _PuEmmcBootPartitions { guint enable; @@ -67,6 +60,7 @@ struct _PuEmmc { PedDisk *disk; PedDiskType *disktype; + GList *input_files; GList *partitions; GList *clean; GList *raw; @@ -169,6 +163,113 @@ emmc_create_partition(PuEmmc *self, return TRUE; } +static gboolean +pu_emmc_check_raw_overwrite(PuEmmc *emmc, + GError **error) +{ + g_return_val_if_fail(emmc != NULL, FALSE); + g_return_val_if_fail(error == NULL || *error == NULL, FALSE); + + if (emmc->partitions && emmc->raw) { + PuEmmcPartition *part = emmc->partitions->data; + gsize part_start = part->offset * emmc->device->sector_size; + gsize raw_start; + gsize raw_end; + + for (GList *b = emmc->raw; b != NULL; b = b->next) { + PuEmmcBinary *bin = b->data; + + raw_start = bin->output_offset * emmc->device->sector_size; + raw_end = bin->input->_size; + raw_end += raw_start; + raw_end -= bin->input_offset * emmc->device->sector_size; + + if (raw_end > part_start) { + g_set_error(error, PU_ERROR, PU_ERROR_FAILED, + "Raw binary overlaps with first partition"); + return FALSE; + } + + /* Check against all binaries after the current one */ + for (GList *i = b->next; i != NULL; i = i->next) { + PuEmmcBinary *bin2 = i->data; + + gsize raw2_start = bin2->output_offset * emmc->device->sector_size; + gsize raw2_end; + raw2_end = bin2->input->_size; + raw2_end += raw2_start; + raw2_end -= bin2->input_offset * emmc->device->sector_size; + + if (!(raw_end < raw2_start || raw_start > raw2_end)) { + g_set_error(error, PU_ERROR, PU_ERROR_FAILED, + "Raw binary overlaps with other raw binary"); + return FALSE; + } + } + } + } + + return TRUE; +} + +static gboolean +pu_emmc_validate_config(PuFlash *flash, + GError **error) +{ + PuEmmc *self = PU_EMMC(flash); + gboolean skip_checksums = FALSE; + + g_return_val_if_fail(flash != NULL, FALSE); + g_return_val_if_fail(error == NULL || *error == NULL, FALSE); + + g_object_get(flash, + "skip-checksums", &skip_checksums, + NULL); + + if (!skip_checksums) { + for (GList *i = self->input_files; i != NULL; i = i->next) { + PuInput *input = i->data; + if (!pu_input_validate_file(input, error)) + return FALSE; + } + } + + for (GList *i = self->input_files; i != NULL; i = i->next) { + PuInput *input = i->data; + if (!pu_input_get_size(input, error)) + return FALSE; + } + + if (!pu_emmc_check_raw_overwrite(self, error)) + return FALSE; + + // check if input files fit in partition + for (GList *p = self->partitions; p != NULL; p = p->next) { + PuEmmcPartition *part = p->data; + gsize input_size = 0; + gsize part_size = 0; + + if (part->expand) + part_size = self->expanded_part_size * self->device->sector_size; + else + part_size = part->size * self->device->sector_size; + + for (GList *i = part->input; i != NULL; i = i->next) { + PuInput *input = i->data; + input_size += input->_size; + } + g_debug("input size: %ld part size: %ld", input_size, part_size); + + if (input_size > part_size) { + g_set_error(error, PU_ERROR, PU_ERROR_FAILED, + "Input files are larger than partition"); + return FALSE; + } + } + + return TRUE; +} + static gboolean pu_emmc_init_device(PuFlash *flash, GError **error) @@ -252,19 +353,13 @@ pu_emmc_write_data(PuFlash *flash, PuEmmc *self = PU_EMMC(flash); guint i = 0; gboolean first_logical_part = FALSE; - gboolean skip_checksums = FALSE; g_autofree gchar *part_path = NULL; g_autofree gchar *part_mount = NULL; - g_autofree gchar *prefix = NULL; + gchar *path; g_return_val_if_fail(flash != NULL, FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); - g_object_get(flash, - "prefix", &prefix, - "skip-checksums", &skip_checksums, - NULL); - g_message("Writing data to MMC"); for (GList *p = self->partitions; p != NULL; p = p->next) { @@ -307,27 +402,8 @@ pu_emmc_write_data(PuFlash *flash, return FALSE; for (GList *i = part->input; i != NULL; i = i->next) { - PuEmmcInput *input = i->data; - g_autofree gchar *path = NULL; - - path = pu_path_from_filename(input->filename, prefix, error); - if (path == NULL) { - g_prefix_error(error, "Failed parsing input filename for partition: "); - return FALSE; - } - - if (!g_str_equal(input->md5sum, "") && !skip_checksums) { - g_debug("Checking MD5 sum of input file '%s'", path); - if (!pu_checksum_verify_file(path, input->md5sum, - G_CHECKSUM_MD5, error)) - return FALSE; - } - if (!g_str_equal(input->sha256sum, "") && !skip_checksums) { - g_debug("Checking SHA256 sum of input file '%s'", path); - if (!pu_checksum_verify_file(path, input->sha256sum, - G_CHECKSUM_SHA256, error)) - return FALSE; - } + PuInput *input = i->data; + path = input->filename; if (g_regex_match_simple(".tar", path, G_REGEX_CASELESS, 0)) { if (!pu_mount(part_path, part_mount, NULL, NULL, error)) @@ -374,32 +450,8 @@ pu_emmc_write_data(PuFlash *flash, for (GList *b = self->raw; b != NULL; b = b->next) { PuEmmcBinary *bin = b->data; - PuEmmcInput *input = bin->input; - g_autofree gchar *path = NULL; - - path = pu_path_from_filename(input->filename, prefix, error); - if (path == NULL) { - g_prefix_error(error, "Failed parsing input filename for binary: "); - return FALSE; - } - - if (g_str_equal(path, "")) { - g_warning("No input specified for binary"); - continue; - } - - if (!g_str_equal(input->md5sum, "") && !skip_checksums) { - g_debug("Checking MD5 sum of input file '%s'", path); - if (!pu_checksum_verify_file(path, input->md5sum, - G_CHECKSUM_MD5, error)) - return FALSE; - } - if (!g_str_equal(input->sha256sum, "") && !skip_checksums) { - g_debug("Checking SHA256 sum of input file '%s'", path); - if (!pu_checksum_verify_file(path, input->sha256sum, - G_CHECKSUM_SHA256, error)) - return FALSE; - } + PuInput *input = bin->input; + path = input->filename; g_debug("Writing raw data: filename=%s input_offset=%lld output_offset=%lld", input->filename, bin->input_offset, bin->output_offset); @@ -422,32 +474,7 @@ pu_emmc_write_data(PuFlash *flash, for (GList *i = input; i != NULL; i = i->next) { PuEmmcBinary *bin = i->data; - g_autofree gchar *path = NULL; - - path = pu_path_from_filename(bin->input->filename, prefix, error); - if (path == NULL) { - g_prefix_error(error, "Failed parsing input filename for eMMC boot partition: "); - return FALSE; - } - - if (g_str_equal(path, "")) { - g_set_error(error, PU_ERROR, PU_ERROR_FLASH_DATA, - "No input specified for eMMC boot partition"); - return FALSE; - } - - if (!g_str_equal(bin->input->md5sum, "") && !skip_checksums) { - g_debug("Checking MD5 sum of input file '%s'", path); - if (!pu_checksum_verify_file(path, bin->input->md5sum, - G_CHECKSUM_MD5, error)) - return FALSE; - } - if (!g_str_equal(bin->input->sha256sum, "") && !skip_checksums) { - g_debug("Checking SHA256 sum of input file '%s'", path); - if (!pu_checksum_verify_file(path, bin->input->sha256sum, - G_CHECKSUM_SHA256, error)) - return FALSE; - } + path = bin->input->filename; g_debug("Writing eMMC boot partitions: filename=%s input_offset=%lld output_offset=%lld", bin->input->filename, bin->input_offset, @@ -486,7 +513,7 @@ pu_emmc_class_finalize(GObject *object) g_free(part->mkfs_extra_args); g_list_free(g_steal_pointer(&part->flags)); for (GList *i = part->input; i != NULL; i = i->next) { - PuEmmcInput *in = i->data; + PuInput *in = i->data; g_free(in->filename); g_free(in->md5sum); g_free(in->sha256sum); @@ -525,6 +552,8 @@ pu_emmc_class_finalize(GObject *object) g_free(emmc->mmc_controls); } + g_list_free(g_steal_pointer(&emmc->input_files)); + if (emmc->disk) ped_disk_destroy(emmc->disk); if (emmc->device) @@ -539,6 +568,7 @@ pu_emmc_class_init(PuEmmcClass *class) PuFlashClass *flash_class = PU_FLASH_CLASS(class); GObjectClass *object_class = G_OBJECT_CLASS(class); + flash_class->validate_config = pu_emmc_validate_config; flash_class->init_device = pu_emmc_init_device; flash_class->setup_layout = pu_emmc_setup_layout; flash_class->write_data = pu_emmc_write_data; @@ -629,7 +659,7 @@ pu_emmc_parse_mmc_controls(PuEmmc *emmc, return FALSE; } - PuEmmcInput *input = g_new0(PuEmmcInput, 1); + PuInput *input = g_new0(PuInput, 1); input->filename = pu_hash_table_lookup_string(value_input->data.mapping, "filename", ""); input->md5sum = pu_hash_table_lookup_string(value_input->data.mapping, "md5sum", ""); input->sha256sum = pu_hash_table_lookup_string(value_input->data.mapping, "sha256sum", ""); @@ -694,17 +724,11 @@ pu_emmc_parse_raw(PuEmmc *emmc, { PuConfigValue *value_raw = g_hash_table_lookup(root, "raw"); GList *raw; - g_autofree gchar *path = NULL; - g_autofree gchar *prefix = NULL; g_return_val_if_fail(emmc != NULL, FALSE); g_return_val_if_fail(root != NULL, FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); - g_object_get(emmc, - "prefix", &prefix, - NULL); - if (!value_raw) { g_debug("No entry 'raw' found. Skipping..."); return TRUE; @@ -735,19 +759,11 @@ pu_emmc_parse_raw(PuEmmc *emmc, "'input' of binary does not contain a mapping"); return FALSE; } - PuEmmcInput *input = g_new0(PuEmmcInput, 1); + PuInput *input = g_new0(PuInput, 1); input->filename = pu_hash_table_lookup_string(value_input->data.mapping, "filename", ""); input->md5sum = pu_hash_table_lookup_string(value_input->data.mapping, "md5sum", ""); input->sha256sum = pu_hash_table_lookup_string(value_input->data.mapping, "sha256sum", ""); - path = pu_path_from_filename(input->filename, prefix, error); - if (path == NULL) - return FALSE; - - input->_size = pu_get_file_size(path, error); - if (!input->_size) - return FALSE; - bin->input = input; g_debug("Parsed raw input: filename=%s md5sum=%s sha256sum=%s", input->filename, input->md5sum, input->sha256sum); @@ -762,6 +778,7 @@ pu_emmc_parse_raw(PuEmmc *emmc, } emmc->raw = g_list_prepend(emmc->raw, bin); + emmc->input_files = g_list_prepend(emmc->input_files, input); } emmc->raw = g_list_reverse(emmc->raw); @@ -881,11 +898,12 @@ pu_emmc_parse_partitions(PuEmmc *emmc, return FALSE; } - PuEmmcInput *input = g_new0(PuEmmcInput, 1); + PuInput *input = g_new0(PuInput, 1); input->filename = pu_hash_table_lookup_string(iv->data.mapping, "filename", ""); input->md5sum = pu_hash_table_lookup_string(iv->data.mapping, "md5sum", ""); input->sha256sum = pu_hash_table_lookup_string(iv->data.mapping, "sha256sum", ""); part->input = g_list_prepend(part->input, input); + emmc->input_files = g_list_prepend(emmc->input_files, input); g_debug("Parsed partition input: filename=%s md5sum=%s sha256sum=%s", input->filename, input->md5sum, input->sha256sum); @@ -912,6 +930,12 @@ pu_emmc_parse_partitions(PuEmmc *emmc, emmc->partitions = g_list_reverse(emmc->partitions); + if (fixed_parts_size > emmc->device->length) { + g_set_error(error, PU_ERROR, PU_ERROR_EMMC_PARSE, + "Combined partition size is larger than device"); + return FALSE; + } + if (emmc->num_expanded_parts > 0) { emmc->expanded_part_size = emmc->device->length - fixed_parts_size - 2 * emmc->num_logical_parts; @@ -929,55 +953,6 @@ pu_emmc_parse_partitions(PuEmmc *emmc, return TRUE; } -static gboolean -pu_emmc_check_raw_overwrite(PuEmmc *emmc, - GError **error) -{ - g_return_val_if_fail(emmc != NULL, FALSE); - g_return_val_if_fail(error == NULL || *error == NULL, FALSE); - - if (emmc->partitions && emmc->raw) { - PuEmmcPartition *part = emmc->partitions->data; - gsize part_start = part->offset * emmc->device->sector_size; - gsize raw_start; - gsize raw_end; - - for (GList *b = emmc->raw; b != NULL; b = b->next) { - PuEmmcBinary *bin = b->data; - - raw_start = bin->output_offset * emmc->device->sector_size; - raw_end = bin->input->_size; - raw_end += raw_start; - raw_end -= bin->input_offset * emmc->device->sector_size; - - if (raw_end > part_start) { - g_set_error(error, PU_ERROR, PU_ERROR_FAILED, - "Raw binary overlaps with first partition"); - return FALSE; - } - - /* Check against all binaries after the current one */ - for (GList *i = b->next; i != NULL; i = i->next) { - PuEmmcBinary *bin2 = i->data; - - gsize raw2_start = bin2->output_offset * emmc->device->sector_size; - gsize raw2_end; - raw2_end = bin2->input->_size; - raw2_end += raw2_start; - raw2_end -= bin2->input_offset * emmc->device->sector_size; - - if (!(raw_end < raw2_start || raw_start > raw2_end)) { - g_set_error(error, PU_ERROR, PU_ERROR_FAILED, - "Raw binary overlaps with other raw binary"); - return FALSE; - } - } - } - } - - return TRUE; -} - PuEmmc * pu_emmc_new(const gchar *device_path, PuConfig *config, @@ -1028,8 +1003,11 @@ pu_emmc_new(const gchar *device_path, if (!pu_emmc_parse_clean(self, root, error)) return NULL; - if (!pu_emmc_check_raw_overwrite(self, error)) - return NULL; + for (GList *i = self->input_files; i != NULL; i = i->next) { + PuInput *input = i->data; + if (!pu_input_prefix_filename(input, prefix, error)) + return NULL; + } return g_steal_pointer(&self); } diff --git a/src/pu-flash.c b/src/pu-flash.c index 1eefff2d..6d54a65e 100644 --- a/src/pu-flash.c +++ b/src/pu-flash.c @@ -27,6 +27,16 @@ static GParamSpec *props[NUM_PROPS] = { NULL }; G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE(PuFlash, pu_flash, G_TYPE_OBJECT) +static gboolean +pu_flash_default_validate_config(PuFlash *self, + G_GNUC_UNUSED GError **error) +{ + g_critical("Flash of type '%s' does not implement PuFlash::validate_config", + G_OBJECT_TYPE_NAME(self)); + + return FALSE; +} + static gboolean pu_flash_default_init_device(PuFlash *self, G_GNUC_UNUSED GError **error) @@ -120,6 +130,7 @@ pu_flash_class_init(PuFlashClass *class) { GObjectClass *object_class = G_OBJECT_CLASS(class); + class->validate_config = pu_flash_default_validate_config; class->init_device = pu_flash_default_init_device; class->setup_layout = pu_flash_default_setup_layout; class->write_data = pu_flash_default_write_data; @@ -159,6 +170,13 @@ pu_flash_init(G_GNUC_UNUSED PuFlash *self) { } +gboolean +pu_flash_validate_config(PuFlash *self, + GError **error) +{ + return PU_FLASH_GET_CLASS(self)->validate_config(self, error); +} + gboolean pu_flash_init_device(PuFlash *self, GError **error) diff --git a/src/pu-flash.h b/src/pu-flash.h index 8d688c1d..ae843610 100644 --- a/src/pu-flash.h +++ b/src/pu-flash.h @@ -21,16 +21,18 @@ * PuFlash is the base object used for specific implementations of flash * devices. An example can be found for eMMC flash with PuEmmc. * - * Implementations of PuFlash use three different functions representing the - * different stages of initializing, formatting and writing a flash device. The - * implementation of these functions are flash device specific and may vary - * depending on its type. + * Implementations of PuFlash use four different functions representing the + * different stages of validating, initializing, formatting and writing a flash + * device. The implementation of these functions are flash device specific and + * may vary depending on its type. */ G_DECLARE_DERIVABLE_TYPE(PuFlash, pu_flash, PU, FLASH, GObject) struct _PuFlashClass { GObjectClass parent_class; + gboolean (*validate_config)(PuFlash *self, + GError **error); gboolean (*init_device)(PuFlash *self, GError **error); gboolean (*setup_layout)(PuFlash *self, @@ -41,6 +43,20 @@ struct _PuFlashClass { gpointer padding[8]; }; +/** + * Validate the config for the flash device. + * + * Validate the config, e.g. check if input files exist and the checksum is + * correct. + * + * @param self the PuFlash instance. + * @param error a GError used for error handling. + * + * @return TRUE on success or FALSE if an error occurred. + */ +gboolean pu_flash_validate_config(PuFlash *self, + GError **error); + /** * Initialize the flash device. * diff --git a/src/pu-input.c b/src/pu-input.c new file mode 100644 index 00000000..bd5cc6a3 --- /dev/null +++ b/src/pu-input.c @@ -0,0 +1,101 @@ +/* + * SPDX-License-Identifier: GPL-3.0-or-later + * Copyright (c) 2023 PHYTEC Messtechnik GmbH + */ + +#define G_LOG_DOMAIN "partup-input" + +#include "pu-checksum.h" +#include "pu-glib-compat.h" +#include "pu-input.h" +#include "pu-utils.h" + +G_DEFINE_QUARK(pu-input-context-error-quark, pu_input_error) + +gboolean +pu_input_validate_file(PuInput *input, + GError **error) +{ + gboolean validated = FALSE; + gchar *path; + + g_return_val_if_fail(input != NULL, FALSE); + g_return_val_if_fail(error == NULL || *error == NULL, FALSE); + + path = input->filename; + + if (!g_file_test(path, G_FILE_TEST_IS_REGULAR)) { + g_set_error(error, PU_INPUT_ERROR, PU_INPUT_ERROR_FILE_NOT_FOUND, + "Input file '%s' does not exist", path); + return FALSE; + } + + if (!g_str_equal(input->md5sum, "")) { + g_debug("Checking MD5 sum of input file '%s'", path); + if (!pu_checksum_verify_file(path, input->md5sum, G_CHECKSUM_MD5, error)) + return FALSE; + validated = TRUE; + } + + if (!g_str_equal(input->sha256sum, "")) { + g_debug("Checking SHA256 sum of input file '%s'", path); + if (!pu_checksum_verify_file(path, input->sha256sum, G_CHECKSUM_SHA256, error)) + return FALSE; + validated = TRUE; + } + + if (!validated) { + g_set_error(error, PU_INPUT_ERROR, PU_INPUT_ERROR_NO_CHECKSUM, + "No checksum provided for '%s'", path); + return FALSE; + } + + return TRUE; +} + +gboolean +pu_input_prefix_filename(PuInput *input, + const gchar *prefix, + GError **error) +{ + g_autofree gchar *path = NULL; + + g_return_val_if_fail(input != NULL, FALSE); + g_return_val_if_fail(prefix != NULL, FALSE); + g_return_val_if_fail(error == NULL || *error == NULL, FALSE); + + path = pu_path_from_filename(input->filename, prefix, error); + if (path == NULL) { + g_prefix_error(error, "Failed parsing input filename: "); + return FALSE; + } + + g_free(input->filename); + input->filename = g_steal_pointer(&path); + + return TRUE; +} + +gboolean +pu_input_get_size(PuInput *input, + GError **error) +{ + g_autofree gchar *cmd = NULL; + g_autofree gchar *out = NULL; + + g_return_val_if_fail(input != NULL, FALSE); + g_return_val_if_fail(error == NULL || *error == NULL, FALSE); + + if (g_regex_match_simple(".tar", input->filename, G_REGEX_CASELESS, 0)) { + cmd = g_strdup_printf("sh -c 'tar -xf %s -O | wc -c'", input->filename); + if (!pu_spawn_command_line_sync_result(cmd, &out, error)) + return FALSE; + input->_size = (gsize) g_ascii_strtoull(out, NULL, 10); + } else { + input->_size = pu_get_file_size(input->filename, error); + if (!input->_size) + return FALSE; + } + + return TRUE; +} diff --git a/src/pu-input.h b/src/pu-input.h new file mode 100644 index 00000000..facf77f2 --- /dev/null +++ b/src/pu-input.h @@ -0,0 +1,35 @@ +/* + * SPDX-License-Identifier: GPL-3.0-or-later + * Copyright (c) 2023 PHYTEC Messtechnik GmbH + */ + +#ifndef PARTUP_INPUT_H +#define PARTUP_INPUT_H + +#define PU_INPUT_ERROR (pu_input_error_quark()) + +GQuark pu_input_error_quark(void); + +typedef enum { + PU_INPUT_ERROR_NO_CHECKSUM, + PU_INPUT_ERROR_FILE_NOT_FOUND +} PuInputError; + +typedef struct { + gchar *filename; + gchar *md5sum; + gchar *sha256sum; + + /* Internal members */ + gsize _size; +} PuInput; + +gboolean pu_input_validate_file(PuInput *input, + GError **error); +gboolean pu_input_prefix_filename(PuInput *input, + const gchar *prefix, + GError **error); +gboolean pu_input_get_size(PuInput *input, + GError **error); + +#endif /* PARTUP_INPUT_H */ diff --git a/src/pu-log.c b/src/pu-log.c index 4f3beb94..f6284d70 100644 --- a/src/pu-log.c +++ b/src/pu-log.c @@ -7,7 +7,7 @@ #include #include "pu-log.h" -#define PU_LOG_DOMAINS "partup partup-config partup-emmc partup-mount partup-package partup-utils" +#define PU_LOG_DOMAINS "partup partup-config partup-emmc partup-mount partup-package partup-utils partup-input" GLogLevelFlags log_output_level = G_LOG_LEVEL_INFO; diff --git a/src/pu-loopdev.c b/src/pu-loopdev.c new file mode 100644 index 00000000..665560cf --- /dev/null +++ b/src/pu-loopdev.c @@ -0,0 +1,86 @@ +/* + * SPDX-License-Identifier: GPL-3.0-or-later + * Copyright (c) 2023 PHYTEC Messtechnik GmbH + */ + +#define G_LOG_DOMAIN "partup-loopdev" + +#include +#include "pu-error.h" +#include "pu-loopdev.h" +#include "pu-utils.h" + +PuLoopdev * +pu_loopdev_new(gsize size, + GError **error) +{ + g_return_val_if_fail(error == NULL || *error == NULL, FALSE); + + g_autofree gchar *buffer = NULL; + g_autoptr(GFileIOStream) iostream = NULL; + + PuLoopdev *loopdev = g_new0(PuLoopdev, 1); + loopdev->file = g_file_new_tmp("partup-loopdev-XXXXXX", &iostream, error); + if (!loopdev->file) + return NULL; + + buffer = g_new0(gchar, size); + if (!buffer) { + g_set_error(error, PU_ERROR, PU_ERROR_FAILED, + "Failed creating a new loopdev"); + return NULL; + } + + if (!g_file_replace_contents(loopdev->file, buffer, size, NULL, FALSE, + 0, NULL, NULL, error)) + return NULL; + + return loopdev; +} + +gboolean +pu_loopdev_attach(PuLoopdev *loopdev, + GError **error) +{ + g_return_val_if_fail(loopdev, FALSE); + g_return_val_if_fail(error == NULL || *error == NULL, FALSE); + + g_autofree gchar *cmd = NULL; + g_autofree gchar *path = NULL; + + cmd = g_strdup_printf("losetup -f --show -P %s", g_file_get_path(loopdev->file)); + if (!pu_spawn_command_line_sync_result(cmd, &path, error)) { + return FALSE; + } + + loopdev->device = g_strdup(g_strstrip(path)); + return TRUE; +} + +gboolean +pu_loopdev_detach(PuLoopdev *loopdev, + GError **error) +{ + g_return_val_if_fail(loopdev, FALSE); + g_return_val_if_fail(error == NULL || *error == NULL, FALSE); + + g_autofree gchar *cmd = NULL; + + cmd = g_strdup_printf("losetup -d %s", loopdev->device); + if (!pu_spawn_command_line_sync(cmd, error)) + return FALSE; + + g_free(loopdev->device); + return TRUE; +} + +void +pu_loopdev_free(PuLoopdev *loopdev) +{ + if (!loopdev) + return; + + g_file_delete(loopdev->file, NULL, NULL); + g_object_unref(loopdev->file); + g_free(loopdev); +} diff --git a/src/pu-loopdev.h b/src/pu-loopdev.h new file mode 100644 index 00000000..2d96b6e2 --- /dev/null +++ b/src/pu-loopdev.h @@ -0,0 +1,30 @@ +/* + * SPDX-License-Identifier: GPL-3.0-or-later + * Copyright (c) 2023 PHYTEC Messtechnik GmbH + */ + +#ifndef PARTUP_LOOPDEV_H +#define PARTUP_LOOPDEV_H + +#define PU_LOOPDEV_ERROR (pu_loopdev_error_quark()) + +GQuark pu_loopdev_error_quark(void); + +typedef enum { + PU_LOOPDEV_ERROR_FAILED +} PutLoopdevError; + +typedef struct { + GFile *file; + gchar *device; +} PuLoopdev; + +PuLoopdev * pu_loopdev_new(gsize size, + GError **error); +gboolean pu_loopdev_attach(PuLoopdev *loopdev, + GError **error); +gboolean pu_loopdev_detach(PuLoopdev *loopdev, + GError **error); +void pu_loopdev_free(PuLoopdev *loopdev); + +#endif /* PARTUP_LOOPDEV_H */ diff --git a/src/pu-main.c b/src/pu-main.c index b63dd778..f1eded44 100644 --- a/src/pu-main.c +++ b/src/pu-main.c @@ -7,6 +7,7 @@ #include #include +#include #include #include #include @@ -16,6 +17,7 @@ #include "pu-error.h" #include "pu-flash.h" #include "pu-log.h" +#include "pu-loopdev.h" #include "pu-mount.h" #include "pu-package.h" #include "pu-utils.h" @@ -24,10 +26,14 @@ static gboolean arg_debug = FALSE; static gchar *arg_debug_domains = NULL; static gboolean arg_quiet = FALSE; -static gboolean arg_install_skip_checksums = FALSE; static gchar *arg_package_directory = NULL; static gboolean arg_package_force = FALSE; static gboolean arg_show_size = FALSE; +static gboolean arg_validate_skip_checksums = FALSE; +static gboolean arg_validate_write_image = FALSE; +static gchar *arg_validate_file_size = "1GB"; +static gchar *arg_validate_build_image = NULL; +static gchar *arg_validate_device = NULL; static gchar **arg_remaining = NULL; static inline gboolean @@ -45,6 +51,14 @@ error_out(const gchar *mountpoint) return FALSE; } +static inline gboolean +error_out_loopdev(PuLoopdev *loopdev, + const gchar *mountpoint) +{ + pu_loopdev_free(loopdev); + return error_out(mountpoint); +} + static gboolean cmd_install(PuCommandContext *context, GError **error) @@ -101,7 +115,7 @@ cmd_install(PuCommandContext *context, return error_out(mount_path); emmc = pu_emmc_new(device_path, config, mount_path, - arg_install_skip_checksums, error); + TRUE, error); if (emmc == NULL) { g_prefix_error(error, "Failed parsing eMMC info from config: "); return error_out(mount_path); @@ -162,6 +176,108 @@ cmd_show(PuCommandContext *context, return pu_package_show_contents(args[0], arg_show_size, error); } +static gboolean +cmd_validate(PuCommandContext *context, + GError **error) +{ + g_autoptr(PuConfig) config = NULL; + g_autoptr(PuEmmc) emmc = NULL; + g_autofree gchar *mount_path = NULL; + g_autofree gchar *config_path = NULL; + gchar *device; + gchar **args; + gsize device_size; + PuLoopdev *loopdev; + + if (getuid() != 0) + return error_not_root(error); + + args = pu_command_context_get_args(context); + + if (!pu_package_mount(args[0], &mount_path, &config_path, error)) + return FALSE; + + config = pu_config_new_from_file(config_path, error); + if (config == NULL) { + g_prefix_error(error, "Failed creating configuration object for file '%s': ", + config_path); + return error_out(mount_path); + } + if (!pu_config_is_version_compatible(config, PARTUP_VERSION_MAJOR, error)) + return error_out(mount_path); + + if (arg_validate_device) { + device = arg_validate_device; + } else { + /* No device provided, use loopdev instead */ + if (!pu_parse_size(arg_validate_file_size, &device_size, error)) + return error_out(mount_path); + + if (device_size < 128 * PED_MEBIBYTE_SIZE) { + g_set_error(error, PU_ERROR, PU_ERROR_FAILED, + "File size '%s' is too small", arg_validate_file_size); + return error_out(mount_path); + } + + loopdev = pu_loopdev_new(device_size, error); + if (!loopdev) + return error_out(mount_path); + + if (!pu_loopdev_attach(loopdev, error)) { + return error_out_loopdev(loopdev, mount_path); + } + + device = loopdev->device; + } + + emmc = pu_emmc_new(device, config, mount_path, + arg_validate_skip_checksums, error); + if (emmc == NULL) { + g_prefix_error(error, "Failed parsing eMMC info from config: "); + return error_out_loopdev(loopdev, mount_path); + } + + if (!pu_flash_validate_config(PU_FLASH(emmc), error)) { + g_prefix_error(error, "Failed validating package: "); + return error_out_loopdev(loopdev, mount_path); + } + + if (!arg_validate_device) { + if (arg_validate_write_image || arg_validate_build_image) { + /* write data to loop device */ + if (!pu_flash_init_device(PU_FLASH(emmc), error)) { + g_prefix_error(error, "Failed initializing device: "); + return error_out_loopdev(loopdev, mount_path); + } + if (!pu_flash_setup_layout(PU_FLASH(emmc), error)) { + g_prefix_error(error, "Failed setting up layout on device: "); + return error_out_loopdev(loopdev, mount_path); + } + if (!pu_flash_write_data(PU_FLASH(emmc), error)) { + g_prefix_error(error, "Failed writing data to device: "); + pu_umount_all(device, NULL); + return error_out_loopdev(loopdev, mount_path); + } + } + + if (!pu_loopdev_detach(loopdev, error)) + return error_out_loopdev(loopdev, mount_path); + + if (arg_validate_build_image) { + if (g_file_test(arg_validate_build_image, G_FILE_TEST_IS_REGULAR)) + g_remove(arg_validate_build_image); + + if (!pu_file_copy(g_file_get_path(loopdev->file), + arg_validate_build_image, error)) + return error_out_loopdev(loopdev, mount_path); + } + + pu_loopdev_free(loopdev); + } + + return pu_package_umount(mount_path, error); +} + static gboolean cmd_version(G_GNUC_UNUSED PuCommandContext *context, G_GNUC_UNUSED GError **error) @@ -183,8 +299,6 @@ static GOptionEntry option_entries_main[] = { }; static GOptionEntry option_entries_install[] = { - { "skip-checksums", 's', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, - &arg_install_skip_checksums, "Skip checksum verification for all input files", NULL }, { G_OPTION_REMAINING, 0, G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING_ARRAY, &arg_remaining, NULL, "install PACKAGE DEVICE" }, { NULL } @@ -214,6 +328,26 @@ static GOptionEntry option_entries_version[] = { { NULL } }; +static GOptionEntry option_entries_validate[] = { + { "skip-checksums", 's', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, + &arg_validate_skip_checksums, "Skip checksum verification for all input files", NULL }, + { "size", 0, G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING, + &arg_validate_file_size, "Size of virtual device. Default is 1G", "SIZE" }, + { "write-image", 'w', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, + &arg_validate_write_image, + "Install package to virtual device as an additional validation step", + NULL }, + { "build-image", 'b', G_OPTION_FLAG_NONE, G_OPTION_ARG_FILENAME, + &arg_validate_build_image, "Build a bootable image from the package. Implies --write-image", + "FILENAME" }, + { "device", 0, G_OPTION_FLAG_NONE, G_OPTION_ARG_FILENAME, + &arg_validate_device, "Validate against provided device", + "DEVICE" }, + { G_OPTION_REMAINING, 0, G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING_ARRAY, + &arg_remaining, NULL, "validate PACKAGE" }, + { NULL } +}; + static PuCommandEntry command_entries[] = { { "install", PU_COMMAND_ARG_FILENAME_ARRAY, cmd_install, "Install a package to a device", option_entries_install }, @@ -221,6 +355,9 @@ static PuCommandEntry command_entries[] = { "Create a package from files", option_entries_package }, { "show", PU_COMMAND_ARG_FILENAME, cmd_show, "List the contents of a package", option_entries_show }, + { "validate", PU_COMMAND_ARG_FILENAME, cmd_validate, + "Validate a package against a virtual or physical device", + option_entries_validate }, { "version", PU_COMMAND_ARG_NONE, cmd_version, "Print the program version", option_entries_version }, PU_COMMAND_ENTRY_NULL diff --git a/src/pu-utils.c b/src/pu-utils.c index 6a96fde4..b5e7d5bf 100644 --- a/src/pu-utils.c +++ b/src/pu-utils.c @@ -21,8 +21,9 @@ #define UDEVADM_SETTLE_TIMEOUT 10 gboolean -pu_spawn_command_line_sync(const gchar *command_line, - GError **error) +pu_spawn_command_line_sync_result(const gchar *command_line, + gchar **result, + GError **error) { GSpawnFlags spawn_flags; gchar **argv = NULL; @@ -36,9 +37,11 @@ pu_spawn_command_line_sync(const gchar *command_line, if (!g_shell_parse_argv(command_line, NULL, &argv, error)) return FALSE; - spawn_flags = G_SPAWN_SEARCH_PATH | - G_SPAWN_STDOUT_TO_DEV_NULL; - if (!g_spawn_sync(NULL, argv, NULL, spawn_flags, NULL, NULL, NULL, &errmsg, + spawn_flags = G_SPAWN_SEARCH_PATH; + if (!result) + spawn_flags |= G_SPAWN_STDOUT_TO_DEV_NULL; + + if (!g_spawn_sync(NULL, argv, NULL, spawn_flags, NULL, NULL, result, &errmsg, &wait_status, error)) { g_prefix_error(error, "Failed spawning process: "); g_strfreev(argv); @@ -56,6 +59,13 @@ pu_spawn_command_line_sync(const gchar *command_line, return TRUE; } +gboolean +pu_spawn_command_line_sync(const gchar *command_line, + GError **error) +{ + return pu_spawn_command_line_sync_result(command_line, NULL, error); +} + gboolean pu_file_copy(const gchar *src, const gchar *dest, @@ -71,8 +81,12 @@ pu_file_copy(const gchar *src, g_debug("Copying '%s' to '%s'", src, dest); + if (g_file_test(dest, G_FILE_TEST_IS_DIR)) + out_path = g_build_filename(dest, g_path_get_basename(src), NULL); + else + out_path = g_strdup(dest); + in = g_file_new_for_path(src); - out_path = g_build_filename(dest, g_path_get_basename(src), NULL); out = g_file_new_for_path(out_path); return g_file_copy(in, out, G_FILE_COPY_NONE, NULL, NULL, NULL, error); @@ -542,3 +556,66 @@ pu_str_pre_remove(gchar *string, return string; } + +gboolean +pu_parse_size(const gchar *string, + gsize *size, + GError **error) +{ + gchar *unit; + gsize unit_factor = 0; + + g_return_val_if_fail(g_strcmp0(string, "") > 0, FALSE); + g_return_val_if_fail(size, FALSE); + g_return_val_if_fail(error == NULL || *error == NULL, FALSE); + + *size = g_ascii_strtoull(string, &unit, 10); + + if (strlen(unit) > 1 && g_ascii_tolower(unit[1]) == 'i') { + switch (g_ascii_tolower(unit[0])) { + case 'k': + unit_factor = PED_KIBIBYTE_SIZE; + break; + case 'm': + unit_factor = PED_MEBIBYTE_SIZE; + break; + case 'g': + unit_factor = PED_GIBIBYTE_SIZE; + break; + case 't': + unit_factor = PED_TEBIBYTE_SIZE; + } + } else if (strlen(unit) > 0) { + switch (g_ascii_tolower(unit[0])) { + case 's': + unit_factor = PED_SECTOR_SIZE_DEFAULT; + break; + case 'b': + unit_factor = 1; + break; + case 'k': + unit_factor = PED_KILOBYTE_SIZE; + break; + case 'm': + unit_factor = PED_MEGABYTE_SIZE; + break; + case 'g': + unit_factor = PED_GIGABYTE_SIZE; + break; + case 't': + unit_factor = PED_TERABYTE_SIZE; + } + } else { + unit_factor = 1; + } + + if (unit_factor == 0) { + g_set_error(error, PU_ERROR, PU_ERROR_FAILED, + "Size '%s' could not be parsed, unit '%s' is unknown", + string, unit); + return FALSE; + } + + *size *= unit_factor; + return TRUE; +} diff --git a/src/pu-utils.h b/src/pu-utils.h index 89731abd..dac7a126 100644 --- a/src/pu-utils.h +++ b/src/pu-utils.h @@ -9,6 +9,9 @@ #include #include +gboolean pu_spawn_command_line_sync_result(const gchar *command_line, + gchar **result, + GError **error); gboolean pu_spawn_command_line_sync(const gchar *command_line, GError **error); gboolean pu_file_copy(const gchar *src, @@ -63,5 +66,8 @@ gchar * pu_device_get_partition_pattern(const gchar *device, GError **error); gchar * pu_str_pre_remove(gchar *string, guint n); +gboolean pu_parse_size(const gchar *string, + gsize *size, + GError **error); #endif /* PARTUP_UTILS_H */ diff --git a/tests/config/checksums.yaml b/tests/config/checksums.yaml new file mode 100644 index 00000000..a7400f3e --- /dev/null +++ b/tests/config/checksums.yaml @@ -0,0 +1,25 @@ +api-version: 0 +disklabel: gpt + +raw: + - input-offset: 0 + output-offset: 33KiB + input: + filename: random.bin + md5sum: 5b41daa7a946650a4e4ee1a24b5f1f78 + +partitions: + - type: primary + filesystem: fat32 + size: 16MiB + offset: 16MiB + input: + - filename: lorem.txt + sha256sum: 25623b53e0984428da972f4c635706d32d01ec92dcd2ab39066082e0b9488c9d + - type: primary + filesystem: NULL + size: 32MiB + input: + - filename: root.ext4 + md5sum: 75866bf838812780c94706ce27ce67f8 + sha256sum: bcd0b814381a47095a18f302a8ec1b76fcdb9161a0af8c2aea56fc8d67d9a489 diff --git a/tests/config/partition-too-small.yaml b/tests/config/partition-too-small.yaml new file mode 100644 index 00000000..bc231ff7 --- /dev/null +++ b/tests/config/partition-too-small.yaml @@ -0,0 +1,11 @@ +api-version: 0 +disklabel: gpt + +partitions: + - type: primary + filesystem: fat32 + size: 10KB + offset: 1MiB + input: + - filename: lorem.txt + - filename: random.bin diff --git a/tests/config/raw-non-overlap.yaml b/tests/config/raw-non-overlap.yaml index 5289500c..c72814c1 100644 --- a/tests/config/raw-non-overlap.yaml +++ b/tests/config/raw-non-overlap.yaml @@ -14,5 +14,5 @@ raw: partitions: - type: primary filesystem: fat32 - size: 128MiB + size: 64MiB offset: 16MiB diff --git a/tests/config/raw-overlap-partition-table.yaml b/tests/config/raw-overlap-partition-table.yaml index 4a712f2e..4d8fd82f 100644 --- a/tests/config/raw-overlap-partition-table.yaml +++ b/tests/config/raw-overlap-partition-table.yaml @@ -14,5 +14,5 @@ raw: partitions: - type: primary filesystem: fat32 - size: 128MiB + size: 64MiB offset: 16MiB diff --git a/tests/config/raw-overlap-partition.yaml b/tests/config/raw-overlap-partition.yaml index 71352a61..f869f5cb 100644 --- a/tests/config/raw-overlap-partition.yaml +++ b/tests/config/raw-overlap-partition.yaml @@ -14,5 +14,5 @@ raw: partitions: - type: primary filesystem: fat32 - size: 128MiB + size: 64MiB offset: 16MiB diff --git a/tests/config/raw-overlap-raw.yaml b/tests/config/raw-overlap-raw.yaml index c5f013ec..b97ef770 100644 --- a/tests/config/raw-overlap-raw.yaml +++ b/tests/config/raw-overlap-raw.yaml @@ -14,5 +14,5 @@ raw: partitions: - type: primary filesystem: fat32 - size: 128MiB + size: 64MiB offset: 16MiB diff --git a/tests/emmc.c b/tests/emmc.c index c52a6264..18a126bb 100644 --- a/tests/emmc.c +++ b/tests/emmc.c @@ -10,6 +10,7 @@ #include "helper.h" #include "pu-emmc.h" #include "pu-error.h" +#include "pu-input.h" static void emmc_simple(void) @@ -53,10 +54,13 @@ test_raw_overwrite_pass(EmptyFileFixture *fixture, g_assert_no_error(fixture->error); g_assert_nonnull(config); - emmc = pu_emmc_new(g_file_get_path(fixture->file), config, "data", FALSE, + emmc = pu_emmc_new(g_file_get_path(fixture->file), config, "data", TRUE, &fixture->error); g_assert_no_error(fixture->error); g_assert_nonnull(emmc); + + g_assert_true(pu_flash_validate_config(PU_FLASH(emmc), &fixture->error)); + g_assert_no_error(fixture->error); } static void @@ -71,7 +75,7 @@ test_raw_overwrite_fail_partition_table(EmptyFileFixture *fixture, g_assert_no_error(fixture->error); g_assert_nonnull(config); - emmc = pu_emmc_new(g_file_get_path(fixture->file), config, "data", FALSE, + emmc = pu_emmc_new(g_file_get_path(fixture->file), config, "data", TRUE, &fixture->error); g_assert_error(fixture->error, PU_ERROR, PU_ERROR_EMMC_PARSE); g_assert_null(emmc); @@ -91,10 +95,13 @@ test_raw_overwrite_fail_partition(EmptyFileFixture *fixture, g_assert_no_error(fixture->error); g_assert_nonnull(config); - emmc = pu_emmc_new(g_file_get_path(fixture->file), config, "data", FALSE, + emmc = pu_emmc_new(g_file_get_path(fixture->file), config, "data", TRUE, &fixture->error); + g_assert_no_error(fixture->error); + g_assert_nonnull(emmc); + + g_assert_false(pu_flash_validate_config(PU_FLASH(emmc), &fixture->error)); g_assert_error(fixture->error, PU_ERROR, PU_ERROR_FAILED); - g_assert_null(emmc); g_clear_error(&fixture->error); } @@ -110,10 +117,79 @@ test_raw_overwrite_fail_raw(EmptyFileFixture *fixture, g_assert_no_error(fixture->error); g_assert_nonnull(config); - emmc = pu_emmc_new(g_file_get_path(fixture->file), config, "data", FALSE, + emmc = pu_emmc_new(g_file_get_path(fixture->file), config, "data", TRUE, &fixture->error); + g_assert_no_error(fixture->error); + g_assert_nonnull(emmc); + + g_assert_false(pu_flash_validate_config(PU_FLASH(emmc), &fixture->error)); + g_assert_error(fixture->error, PU_ERROR, PU_ERROR_FAILED); + + g_clear_error(&fixture->error); +} + +static void +test_no_checksums(EmptyFileFixture *fixture, + G_GNUC_UNUSED gconstpointer user_data) +{ + g_autoptr(PuConfig) config = NULL; + g_autoptr(PuEmmc) emmc = NULL; + + config = pu_config_new_from_file("config/raw-non-overlap.yaml", &fixture->error); + g_assert_no_error(fixture->error); + g_assert_nonnull(config); + + emmc = pu_emmc_new(g_file_get_path(fixture->file), config, "data", false, + &fixture->error); + g_assert_no_error(fixture->error); + g_assert_nonnull(emmc); + + g_assert_false(pu_flash_validate_config(PU_FLASH(emmc), &fixture->error)); + g_assert_error(fixture->error, PU_INPUT_ERROR, PU_INPUT_ERROR_NO_CHECKSUM); + + g_clear_error(&fixture->error); +} + +static void +test_checksums(EmptyFileFixture *fixture, + G_GNUC_UNUSED gconstpointer user_data) +{ + g_autoptr(PuConfig) config = NULL; + g_autoptr(PuEmmc) emmc = NULL; + + config = pu_config_new_from_file("config/checksums.yaml", &fixture->error); + g_assert_no_error(fixture->error); + g_assert_nonnull(config); + + emmc = pu_emmc_new(g_file_get_path(fixture->file), config, "data", false, + &fixture->error); + g_assert_no_error(fixture->error); + g_assert_nonnull(emmc); + + g_assert_true(pu_flash_validate_config(PU_FLASH(emmc), &fixture->error)); + g_assert_no_error(fixture->error); + + g_clear_error(&fixture->error); +} + +static void +test_partition_too_small(EmptyFileFixture *fixture, + G_GNUC_UNUSED gconstpointer user_data) +{ + g_autoptr(PuConfig) config = NULL; + g_autoptr(PuEmmc) emmc = NULL; + + config = pu_config_new_from_file("config/partition-too-small.yaml", &fixture->error); + g_assert_no_error(fixture->error); + g_assert_nonnull(config); + + emmc = pu_emmc_new(g_file_get_path(fixture->file), config, "data", true, + &fixture->error); + g_assert_no_error(fixture->error); + g_assert_nonnull(emmc); + + g_assert_false(pu_flash_validate_config(PU_FLASH(emmc), &fixture->error)); g_assert_error(fixture->error, PU_ERROR, PU_ERROR_FAILED); - g_assert_null(emmc); g_clear_error(&fixture->error); } @@ -140,6 +216,15 @@ main(int argc, g_test_add("/emmc/raw_overwrite_fail_raw", EmptyFileFixture, "mmcblk0", empty_file_set_up, test_raw_overwrite_fail_raw, empty_file_tear_down); + g_test_add("/emmc/no_checksums", EmptyFileFixture, "mmcblk0", + empty_file_set_up, test_no_checksums, + empty_file_tear_down); + g_test_add("/emmc/checksums", EmptyFileFixture, "mmcblk0", + empty_file_set_up, test_checksums, + empty_file_tear_down); + g_test_add("/emmc/partition_too_small", EmptyFileFixture, "mmcblk0", + empty_file_set_up, test_partition_too_small, + empty_file_tear_down); return g_test_run(); } diff --git a/tests/input.c b/tests/input.c new file mode 100644 index 00000000..e28b639e --- /dev/null +++ b/tests/input.c @@ -0,0 +1,101 @@ +/* + * SPDX-License-Identifier: GPL-3.0-or-later + * Copyright (c) 2023 PHYTEC Messtechnik GmbH + */ + +#include +#include +#include + +#define LOREM_TXT_SHA256SUM "25623b53e0984428da972f4c635706d32d01ec92dcd2ab39066082e0b9488c9d" +#define LOREM_TXT_MD5SUM "3bc34a45d26784b5bea8529db533ae84" +#define LOREM_TXT_FILENAME "lorem.txt" +#define LOREM_TAR_FILENAME "lorem.tar" +#define DATA_DIR "data" +#define LOREM_TXT_PATH DATA_DIR "/" LOREM_TXT_FILENAME +#define LOREM_TAR_PATH DATA_DIR "/" LOREM_TAR_FILENAME +#define LOREM_TXT_SIZE 12 + +static void +test_prefix_filename(void) +{ + g_autoptr(GError) error = NULL; + PuInput input; + input.filename = g_strdup(LOREM_TXT_FILENAME); + g_assert_true(pu_input_prefix_filename(&input, DATA_DIR, &error)); + g_assert_no_error(error); + g_assert_cmpstr(input.filename, ==, LOREM_TXT_PATH); +} + +static void +test_validate_file(void) +{ + g_autoptr(GError) error = NULL; + PuInput input; + input.filename = g_strdup(LOREM_TXT_PATH); + input.sha256sum = LOREM_TXT_SHA256SUM; + input.md5sum = LOREM_TXT_MD5SUM; + + g_assert_true(pu_input_validate_file(&input, &error)); + g_assert_no_error(error); +} + +static void +test_validate_file_no_checksum(void) +{ + g_autoptr(GError) error = NULL; + PuInput input; + input.filename = g_strdup(LOREM_TXT_PATH); + input.sha256sum = ""; + input.md5sum = ""; + + g_assert_false(pu_input_validate_file(&input, &error)); + g_assert_error(error, PU_INPUT_ERROR, PU_INPUT_ERROR_NO_CHECKSUM); +} + +static void +test_input_get_size(void) +{ + g_autoptr(GError) error = NULL; + PuInput input; + input.filename = g_strdup(LOREM_TXT_PATH); + input.sha256sum = ""; + input.md5sum = ""; + + g_assert_true(pu_input_get_size(&input, &error)); + g_assert_no_error(error); + g_assert_cmpuint(input._size, ==, LOREM_TXT_SIZE); +} + +static void +test_input_get_size_tar(void) +{ + g_autoptr(GError) error = NULL; + PuInput input; + input.filename = g_strdup(LOREM_TAR_PATH); + input.sha256sum = ""; + input.md5sum = ""; + + g_assert_true(pu_input_get_size(&input, &error)); + g_assert_no_error(error); + g_assert_cmpuint(input._size, ==, LOREM_TXT_SIZE); +} + +int +main(int argc, + char *argv[]) +{ + g_test_init(&argc, &argv, NULL); + +#ifdef PARTUP_TEST_SRCDIR + g_chdir(PARTUP_TEST_SRCDIR); +#endif + + g_test_add_func("/input/prefix_filename", test_prefix_filename); + g_test_add_func("/input/validate_file", test_validate_file); + g_test_add_func("/input/validate_file_no_checksum", test_validate_file_no_checksum); + g_test_add_func("/input/input_get_size", test_input_get_size); + g_test_add_func("/input/input_get_size_tar", test_input_get_size_tar); + + return g_test_run(); +} diff --git a/tests/loopdev-root.c b/tests/loopdev-root.c new file mode 100644 index 00000000..8f9213be --- /dev/null +++ b/tests/loopdev-root.c @@ -0,0 +1,51 @@ +/* + * SPDX-License-Identifier: GPL-3.0-or-later + * Copyright (c) 2023 PHYTEC Messtechnik GmbH + */ + +#include +#include +#include +#include "pu-error.h" +#include "pu-loopdev.h" + +#define LOOPDEV_SIZE 1 * 1024 * 1024 + +static void +test_loopdev(void) +{ + g_autoptr(GError) error = NULL; + PuLoopdev *loopdev; + + loopdev = pu_loopdev_new(LOOPDEV_SIZE, &error); + g_assert_no_error(error); + g_assert_nonnull(loopdev); + g_assert_true(g_file_test(g_file_get_path(loopdev->file), G_FILE_TEST_IS_REGULAR)); + + g_assert_true(pu_loopdev_attach(loopdev, &error)); + g_assert_no_error(error); + + g_assert_true(pu_loopdev_detach(loopdev, &error)); + g_assert_no_error(error); + + pu_loopdev_free(loopdev); +} + +int +main(int argc, + char *argv[]) +{ + /* Skip tests when not run as root */ + if (getuid() != 0) + return 77; + + g_test_init(&argc, &argv, NULL); + +#ifdef PARTUP_TEST_SRCDIR + g_chdir(PARTUP_TEST_SRCDIR); +#endif + + g_test_add_func("/loopdev/loopdev", test_loopdev); + + return g_test_run(); +} diff --git a/tests/meson.build b/tests/meson.build index 06cf46dc..7923d7cb 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -3,11 +3,13 @@ tests = [ 'command', 'config', 'emmc', + 'input', 'package', 'utils' ] tests_root = [ + 'loopdev-root', 'mount-root', 'package-root', 'utils-root' diff --git a/tests/utils.c b/tests/utils.c index 1e89a201..f0272ead 100644 --- a/tests/utils.c +++ b/tests/utils.c @@ -6,6 +6,7 @@ #include #include #include +#include #include "helper.h" #include "pu-glib-compat.h" #include "pu-utils.h" @@ -243,6 +244,35 @@ test_device_get_partition_pattern(void) g_assert_false(g_regex_match_simple(pattern, "/dev/sdb1", 0, 0)); } +static void +test_spawn_command_line_sync_result(void) +{ + g_autoptr(GError) error = NULL; + g_autofree gchar *result = NULL; + + g_assert_true(pu_spawn_command_line_sync_result("echo 123", &result, &error)); + g_assert_no_error(error); + g_assert_nonnull(strstr(result, "123")); +} + +static void +test_parse_size(void) +{ + g_autoptr(GError) error = NULL; + gsize size; + + g_assert_true(pu_parse_size("1M", &size, &error)); + g_assert_no_error(error); + g_assert_cmpuint(size, ==, PED_MEGABYTE_SIZE); + + g_assert_true(pu_parse_size("128KiB", &size, &error)); + g_assert_no_error(error); + g_assert_cmpuint(size, ==, 128 * PED_KIBIBYTE_SIZE); + + g_assert_false(pu_parse_size("1x", &size, &error)); + g_assert_error(error, PU_ERROR, PU_ERROR_FAILED); +} + int main(int argc, char *argv[]) @@ -276,6 +306,8 @@ main(int argc, test_get_file_size); g_test_add_func("/utils/str_pre_remove", test_str_pre_remove); g_test_add_func("/utils/device_get_partition_pattern", test_device_get_partition_pattern); + g_test_add_func("/utils/spawn_command_line_sync_result", test_spawn_command_line_sync_result); + g_test_add_func("/utils/parse_size", test_parse_size); return g_test_run(); }