// SPDX-License-Identifier: BSD-3-Clause #define _FILE_OFFSET_BITS 64 #if defined(__APPLE__) #define _DARWIN_C_SOURCE #endif #define _XOPEN_SOURCE 700 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "file.h" #include "firehose.h" #include "list.h" #include "pathbuf.h" #include "program.h" #include "qdl.h" #include "sparse.h" #include "common.h" #define TEST_INCDIR "/mock/incdir" #define TEST_XMLDIR "/mock/xml" #define TEST_PROGRAM_FILE TEST_XMLDIR "/program.xml" #define TEST_PAYLOAD "payload.bin" #define TEST_INCDIR_PAYLOAD TEST_INCDIR "/" TEST_PAYLOAD #define TEST_XMLDIR_PAYLOAD TEST_XMLDIR "/" TEST_PAYLOAD bool qdl_debug; static const char *existing_paths[4]; static void set_existing_paths(const char *path0, const char *path1, const char *path2) { existing_paths[0] = path0; existing_paths[1] = path1; existing_paths[2] = path2; existing_paths[3] = NULL; } static bool mock_path_exists(const char *path) { for (size_t i = 0; existing_paths[i]; i++) { if (!strcmp(path, existing_paths[i])) return true; } return false; } int mock_access(const char *path, int mode) { assert_int_equal(mode, F_OK); if (mock_path_exists(path)) return 0; errno = ENOENT; return -1; } int qdl_file_open(struct qdl_zip *qzip, const char *filename, struct qdl_file *file) { assert_null(qzip); if (!mock_path_exists(filename)) return -1; file->type = QDL_FILE_TYPE_POSIX; file->size = 1; file->fd = -1; file->zip_file = NULL; return 0; } void qdl_file_close(struct qdl_file *file) { file->type = QDL_FILE_TYPE_UNKNOWN; } void *qdl_file_load(struct qdl_file *file, size_t *len) { (void)file; (void)len; return NULL; } size_t qdl_file_getsize(struct qdl_file *file) { return file->size; } off_t qdl_file_seek(struct qdl_file *file, off_t offset, int whence) { (void)file; (void)offset; (void)whence; return -1; } ssize_t qdl_file_read(struct qdl_file *file, void *buf, size_t len) { (void)file; (void)buf; (void)len; return -1; } struct qdl_zip *qdl_zip_get(struct qdl_zip *qzip) { return qzip; } void qdl_zip_put(struct qdl_zip *qzip) { (void)qzip; } int sparse_header_parse(struct qdl_file *file, sparse_header_t *sparse_header) { (void)file; (void)sparse_header; return -1; } int sparse_chunk_header_parse(struct qdl_file *file, sparse_header_t *sparse_header, uint64_t *chunk_size, uint32_t *value, off_t *offset) { (void)file; (void)sparse_header; (void)chunk_size; (void)value; (void)offset; return -1; } void ux_init(void) { } void ux_err(const char *fmt, ...) { (void)fmt; } void ux_info(const char *fmt, ...) { (void)fmt; } void ux_log(const char *fmt, ...) { (void)fmt; } void ux_debug(const char *fmt, ...) { (void)fmt; } void ux_progress(const char *fmt, unsigned int value, unsigned int size, ...) { (void)fmt; (void)value; (void)size; } struct firehose_op *firehose_alloc_op(int type) { struct firehose_op *op; op = calloc(1, sizeof(*op)); if (!op) return NULL; op->type = type; return op; } void firehose_free_ops(struct list_head *ops) { struct firehose_op *next; struct firehose_op *op; list_for_each_entry_safe(op, next, ops, node) { list_del(&op->node); qdl_zip_put(op->zip); free((void *)op->filename); free((void *)op->label); free((void *)op->start_sector); free((void *)op->gpt_partition); free((void *)op->value); free((void *)op->what); free(op); } } int contents_resolve_path(struct contents_filter *filter, const char *filename, struct pathbuf *path) { (void)filter; (void)filename; (void)path; return 0; } static xmlDoc *build_program_doc(const char *filename) { char xml[1024]; int ret; ret = snprintf(xml, sizeof(xml), "" "" "", filename); assert_true(ret > 0 && (size_t)ret < sizeof(xml)); return xmlReadMemory(xml, ret, "program.xml", NULL, 0); } static struct firehose_op *get_only_op(struct list_head *ops) { struct firehose_op *op; struct list_head *it; size_t count = 0; list_for_each(it, ops) count++; if (count != 1) fail_msg("expected exactly one firehose op, got %zu", count); op = list_entry_first(ops, struct firehose_op, node); if (op->type != FIREHOSE_OP_PROGRAM) fail_msg("expected firehose op type PROGRAM, got %d", op->type); return op; } static void test_incdir_takes_precedence(void **state) { struct firehose_op *op; struct list_head ops = LIST_INIT(ops); xmlDoc *doc; int ret; (void)state; set_existing_paths(TEST_INCDIR_PAYLOAD, TEST_XMLDIR_PAYLOAD, NULL); doc = build_program_doc(TEST_PAYLOAD); if (!doc) fail_msg("failed to parse synthetic program XML"); ret = program_load_xml(&ops, doc, NULL, TEST_PROGRAM_FILE, false, false, NULL, TEST_INCDIR); if (ret) fail_msg("incdir payload should be accepted, got %d", ret); op = get_only_op(&ops); if (!op->filename || strcmp(op->filename, TEST_INCDIR_PAYLOAD)) fail_msg("incdir should take precedence, got '%s'", op->filename ? op->filename : "(null)"); firehose_free_ops(&ops); xmlFreeDoc(doc); } static void test_program_xml_directory_is_used(void **state) { struct firehose_op *op; struct list_head ops = LIST_INIT(ops); xmlDoc *doc; int ret; (void)state; set_existing_paths(TEST_XMLDIR_PAYLOAD, NULL, NULL); doc = build_program_doc(TEST_PAYLOAD); if (!doc) fail_msg("failed to parse synthetic program XML"); ret = program_load_xml(&ops, doc, NULL, TEST_PROGRAM_FILE, false, false, NULL, TEST_INCDIR); if (ret) fail_msg("XML-adjacent payload should be accepted, got %d", ret); op = get_only_op(&ops); if (!op->filename || strcmp(op->filename, TEST_XMLDIR_PAYLOAD)) fail_msg("program XML directory should be used, got '%s'", op->filename ? op->filename : "(null)"); firehose_free_ops(&ops); xmlFreeDoc(doc); } static void test_program_xml_directory_takes_precedence_over_current_directory(void **state) { struct firehose_op *op; struct list_head ops = LIST_INIT(ops); xmlDoc *doc; int ret; (void)state; set_existing_paths(TEST_XMLDIR_PAYLOAD, TEST_PAYLOAD, NULL); doc = build_program_doc(TEST_PAYLOAD); if (!doc) fail_msg("failed to parse synthetic program XML"); ret = program_load_xml(&ops, doc, NULL, TEST_PROGRAM_FILE, false, false, NULL, TEST_INCDIR); if (ret) fail_msg("XML-adjacent payload should beat current-directory fallback, got %d", ret); op = get_only_op(&ops); if (!op->filename || strcmp(op->filename, TEST_XMLDIR_PAYLOAD)) fail_msg("program XML directory should take precedence over current directory, got '%s'", op->filename ? op->filename : "(null)"); firehose_free_ops(&ops); xmlFreeDoc(doc); } static void test_current_directory_is_used_when_path_resolution_misses(void **state) { struct firehose_op *op; struct list_head ops = LIST_INIT(ops); xmlDoc *doc; int ret; (void)state; set_existing_paths(TEST_PAYLOAD, NULL, NULL); doc = build_program_doc(TEST_PAYLOAD); if (!doc) fail_msg("failed to parse synthetic program XML"); /* Compatibility fallback: leave the original relative filename intact. */ ret = program_load_xml(&ops, doc, NULL, TEST_PROGRAM_FILE, false, false, NULL, TEST_INCDIR); if (ret) fail_msg("relative fallback payload should be accepted, got %d", ret); op = get_only_op(&ops); if (!op->filename || strcmp(op->filename, TEST_PAYLOAD)) fail_msg("unresolved relative filename should be preserved, got '%s'", op->filename ? op->filename : "(null)"); firehose_free_ops(&ops); xmlFreeDoc(doc); } static void test_missing_file_fails_without_allow_missing(void **state) { struct list_head ops = LIST_INIT(ops); xmlDoc *doc; int ret; (void)state; set_existing_paths(NULL, NULL, NULL); doc = build_program_doc(TEST_PAYLOAD); if (!doc) fail_msg("failed to parse synthetic program XML"); ret = program_load_xml(&ops, doc, NULL, TEST_PROGRAM_FILE, false, false, NULL, TEST_INCDIR); if (ret != -1) fail_msg("missing payload should fail without allow_missing, got %d", ret); if (!list_empty(&ops)) fail_msg("missing payload without allow_missing should not append firehose ops"); xmlFreeDoc(doc); } static void test_missing_file_is_tolerated_with_allow_missing(void **state) { struct firehose_op *op; struct list_head ops = LIST_INIT(ops); xmlDoc *doc; int ret; (void)state; set_existing_paths(NULL, NULL, NULL); doc = build_program_doc(TEST_PAYLOAD); if (!doc) fail_msg("failed to parse synthetic program XML"); ret = program_load_xml(&ops, doc, NULL, TEST_PROGRAM_FILE, false, true, NULL, TEST_INCDIR); if (ret) fail_msg("missing payload should be tolerated with allow_missing, got %d", ret); op = get_only_op(&ops); if (op->filename) fail_msg("allow_missing should clear the missing program filename, got '%s'", op->filename); firehose_free_ops(&ops); xmlFreeDoc(doc); } int main(void) { const struct CMUnitTest tests[] = { cmocka_unit_test(test_incdir_takes_precedence), cmocka_unit_test(test_program_xml_directory_is_used), cmocka_unit_test(test_program_xml_directory_takes_precedence_over_current_directory), cmocka_unit_test(test_current_directory_is_used_when_path_resolution_misses), cmocka_unit_test(test_missing_file_fails_without_allow_missing), cmocka_unit_test(test_missing_file_is_tolerated_with_allow_missing), }; return cmocka_run_group_tests(tests, NULL, NULL); }