Files

433 lines
9.6 KiB
C
Raw Permalink Normal View History

2026-04-23 19:57:05 +00:00
// SPDX-License-Identifier: BSD-3-Clause
#define _FILE_OFFSET_BITS 64
#if defined(__APPLE__)
#define _DARWIN_C_SOURCE
#endif
#define _XOPEN_SOURCE 700
#include <errno.h>
#include <limits.h>
#include <setjmp.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <cmocka.h>
#include <libxml/parser.h>
#include <libxml/tree.h>
#include "file.h"
#include "firehose.h"
#include "list.h"
#include "pathbuf.h"
2026-04-23 19:57:05 +00:00
#include "program.h"
#include "qdl.h"
#include "sparse.h"
#include "common.h"
2026-04-23 19:57:05 +00:00
#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;
}
2026-04-23 19:57:05 +00:00
static xmlDoc *build_program_doc(const char *filename)
{
char xml[1024];
int ret;
ret = snprintf(xml, sizeof(xml),
"<data>"
"<program "
"SECTOR_SIZE_IN_BYTES=\"512\" "
"filename=\"%s\" "
"label=\"test\" "
"num_partition_sectors=\"1\" "
"physical_partition_number=\"0\" "
"start_sector=\"0\" "
"file_sector_offset=\"0\""
"/>"
"</data>",
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);
2026-04-23 19:57:05 +00:00
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);
2026-04-23 19:57:05 +00:00
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);
2026-04-23 19:57:05 +00:00
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);
2026-04-23 19:57:05 +00:00
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);
2026-04-23 19:57:05 +00:00
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);
2026-04-23 19:57:05 +00:00
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);
}