Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for delta-source-fat-* directives #202

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 69 additions & 0 deletions src/fatfs.c
Original file line number Diff line number Diff line change
Expand Up @@ -425,6 +425,44 @@ int fatfs_truncate(struct block_cache *output, off_t block_offset, const char *f
return 0;
}

/**
* @brief fatfs_pread Read a file
*
* @param fc the current FAT session
* @param filename an existing file
* @param offset offset inside file
* @param size number of bytes to read
* @param buffer buffer where to put read data
* @param br pointer to the variable that receives number of byters actually read
* @return 0 on success
*/
int fatfs_pread(struct block_cache *output, off_t block_offset, const char *filename, int offset, size_t size, void *buffer, size_t *br)
{
// Check if this is the same file as a previous pwrite call
if (current_file_ && strcmp(current_file_, filename) != 0)
close_open_files();

MAYBE_MOUNT(output, block_offset);

if (!current_file_) {
CHECK("fat_read can't open file", filename, f_open(&fil_, filename, FA_READ));
CHECK_SYNC(filename, &fil_);

// Assuming it opens ok, cache the filename for future reads.
current_file_ = strdup(filename);
}

// Check if this pread requires a seek.
FSIZE_t desired_offset = offset;
if (desired_offset != f_tell(&fil_))
CHECK("fat_read can't seek to end of file", filename, f_lseek(&fil_, f_size(&fil_)));

CHECK("fat_read can't read", filename, f_read(&fil_, buffer, (uint)size, (uint*)br));
CHECK_SYNC(filename, &fil_);

return 0;
}

int fatfs_pwrite(struct block_cache *output, off_t block_offset, const char *filename, int offset, const char *buffer, off_t size)
{
// Check if this is the same file as a previous pwrite call
Expand Down Expand Up @@ -476,6 +514,37 @@ int fatfs_pwrite(struct block_cache *output, off_t block_offset, const char *fil
return 0;
}

/**
* @brief fatfs_size read file size
*
* @param fc the current FAT session
* @param filename an existing file
* @param size pointer to the variable that receives file size
* @return 0 on success
*/
int fatfs_size(struct block_cache *output, off_t block_offset, const char *filename, size_t *size)
{
// Check if this is the same file as a previous size call
if (current_file_ && strcmp(current_file_, filename) != 0)
close_open_files();

MAYBE_MOUNT(output, block_offset);

if (!current_file_) {
CHECK("fat_size can't open file", filename, f_open(&fil_, filename, FA_READ));
CHECK_SYNC(filename, &fil_);

// Assuming it opens ok, cache the filename for future calls.
current_file_ = strdup(filename);
}

size = malloc(sizeof(off_t));
*size = (size_t)f_size(&fil_);
CHECK_SYNC(filename, &fil_);

return 0;
}

void fatfs_closefs()
{
if (output_) {
Expand Down
2 changes: 2 additions & 0 deletions src/fatfs.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,13 @@ int fatfs_setlabel(struct block_cache *output, off_t block_offset, const char *l
int fatfs_mv(struct block_cache *output, off_t block_offset, const char *cmd, const char *from_name, const char *to_name, bool force);
int fatfs_rm(struct block_cache *output, off_t block_offset, const char *cmd, const char *filename, bool file_must_exist);
int fatfs_truncate(struct block_cache *output, off_t block_offset, const char *filename);
int fatfs_pread(struct block_cache *output, off_t block_offset, const char *filename, int offset, size_t size, void *buffer, size_t *br);
int fatfs_pwrite(struct block_cache *output, off_t block_offset, const char *filename, int offset, const char *buffer, off_t size);
int fatfs_cp(struct block_cache *output, off_t block_offset, const char *from_name, const char *to_name);
int fatfs_touch(struct block_cache *output, off_t block_offset, const char *filename);
int fatfs_exists(struct block_cache *output, off_t block_offset, const char *filename);
int fatfs_file_matches(struct block_cache *output, off_t block_offset, const char *filename, const char *pattern);
int fatfs_size(struct block_cache *output, off_t block_offset, const char *filename, size_t *size);
void fatfs_closefs();

#endif // FATFS_H
1 change: 1 addition & 0 deletions src/functions.h
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ struct fun_context {
struct xdelta_state *xd;
off_t xd_source_offset;
size_t xd_source_count;
const char *xd_source_path;

void *cookie;
};
Expand Down
27 changes: 26 additions & 1 deletion src/fwup_apply.c
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,17 @@ static int xdelta_read_source_callback(void *cookie, void *buf, size_t count, of
return block_cache_pread(fctx->output, buf, count, fctx->xd_source_offset + offset);
}

static int xdelta_read_fat_callback(void *cookie, void *buf, size_t count, off_t offset)
{
struct fun_context *fctx = (struct fun_context *) cookie;
size_t br;

if (offset < 0)
ERR_RETURN("xdelta tried to load outside of allowed byte range (0-%" PRId64 ")", fctx->xd_source_count);

return fatfs_pread(fctx->output, fctx->xd_source_offset, fctx->xd_source_path, offset, count, buf, &br);
}

static int read_callback_xdelta(struct fun_context *fctx, const void **buffer, size_t *len, off_t *offset)
{
struct fwup_apply_data *p = (struct fwup_apply_data *) fctx->cookie;
Expand Down Expand Up @@ -387,17 +398,31 @@ static int run_task(struct fun_context *fctx, struct fwup_apply_data *pd)
off_t expected_size_in_archive = sparse_file_data_size(&pd->sfm);

if (pd->sfm.map_len == 1 && archive_entry_size_is_set(ae) && size_in_archive != expected_size_in_archive) {
// Size in archive is different from expected size
const char *source_raw_offset_str = cfg_getstr(on_resource, "delta-source-raw-offset");
int source_raw_count = cfg_getint(on_resource, "delta-source-raw-count");

const char *source_fat_offset_str = cfg_getstr(on_resource, "delta-source-fat-offset");
const char *source_fat_path = cfg_getstr(on_resource, "delta-source-fat-path");

if (source_raw_count > 0 && source_raw_offset_str != NULL) {
// Found delta-source-raw-offset and delta-source-raw-count directives
off_t source_raw_offset = strtoul(source_raw_offset_str, NULL, 0);

fctx->xd = malloc(sizeof(struct xdelta_state));
xdelta_init(fctx->xd, xdelta_read_patch_callback, xdelta_read_source_callback, fctx);
fctx->xd_source_offset = source_raw_offset * FWUP_BLOCK_SIZE;
fctx->xd_source_count = source_raw_count * FWUP_BLOCK_SIZE;
} else if (source_fat_offset_str != NULL && source_fat_path != NULL) {
// Found delta-source-fat-offset and delta-source-fat-path directives
off_t source_fat_offset = strtoul(source_fat_offset_str, NULL, 0);

fctx->xd = malloc(sizeof(struct xdelta_state));
xdelta_init(fctx->xd, xdelta_read_patch_callback, xdelta_read_fat_callback, fctx);
fctx->xd_source_offset = source_fat_offset;
fctx->xd_source_path = source_fat_path;
} else {
ERR_CLEANUP_MSG("File '%s' isn't expected size (%d vs %d) and xdelta3 patch support not enabled on it. (Add delta-source-raw-offset or delta-source-raw-count at least)", resource_name, (int) size_in_archive, (int) expected_size_in_archive);
ERR_CLEANUP_MSG("File '%s' isn't expected size (%d vs %d) and xdelta3 patch support not enabled on it. (Add delta-source-raw-offset / delta-source-raw-count or delta-source-fat-offset / delta-source-fat-path)", resource_name, (int) size_in_archive, (int) expected_size_in_archive);
}
}
}
Expand Down
74 changes: 74 additions & 0 deletions tests/192_delta_fat_upgrade.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
#!/bin/sh

#
# Write a firmware image and then test upgrading it with
# an xdelta3-encoded update, on FAT-stored files.
#
# brew install xdelta
#

. "$(cd "$(dirname "$0")" && pwd)/common.sh"

FWFILE2="${WORK}/fwup2.fw"

cat >"$CONFIG" <<EOF
define(BOOT_PART_OFFSET, 63)
define(BOOT_PART_COUNT, 77238)

file-resource 1k.cur {
host-path = "${TESTFILE_1K}"
}
file-resource 1k.new {
host-path = "${TESTFILE_1K}"
}
mbr mbr-a {
partition 0 {
block-offset = \${BOOT_PART_OFFSET}
block-count = \${BOOT_PART_COUNT}
type = 0xc # FAT32
boot = true
}
}
task complete {
on-init {
mbr_write(mbr-a)
fat_mkfs(\${BOOT_PART_OFFSET}, \${BOOT_PART_COUNT})
}
on-resource 1k.cur { fat_write(\${BOOT_PART_OFFSET}, "1k.cur") }
}
task upgrade {
on-resource 1k.new {
delta-source-fat-offset=\${BOOT_PART_OFFSET}
delta-source-fat-path="1k.cur"
fat_write(\${BOOT_PART_OFFSET}, "1k.new")
}
}
EOF

offset_bytes=$(( 63*512 ))

# Create the firmware file, then "burn it"
$FWUP_CREATE -c -f "$CONFIG" -o "$FWFILE"
$FWUP_APPLY -a -d "$IMGFILE" -i "$FWFILE" -t complete

# Check the content of current firmware content
mcopy -n -i "${IMGFILE}@@${offset_bytes}" ::/1k.cur "${WORK}/1k.cur"
diff "${TESTFILE_1K}" "${WORK}/1k.cur"

# Manually create the delta upgrade by replacing 1k.new
# with the delta3 version
mkdir -p "$WORK/data"
xdelta3 -A -S -f -s "$TESTFILE_1K" "$TESTFILE_1K" "$WORK/data/1k.new"
cp "$FWFILE" "$FWFILE2"
(cd "$WORK" && zip "$FWFILE2" data/1k.new)

# Now upgrade the IMGFILE file
$FWUP_APPLY -a -d "$IMGFILE" -i "$FWFILE2" -t upgrade

# Check the content of new firmware content
mcopy -i "${IMGFILE}@@${offset_bytes}" ::/1k.new "${WORK}/1k.new"
diff "${TESTFILE_1K}" "${WORK}/1k.new"

# Check that the verify logic works on both files
$FWUP_VERIFY -V -i "$FWFILE"
$FWUP_VERIFY -V -i "$FWFILE2"
3 changes: 2 additions & 1 deletion tests/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,7 @@ TESTS = 001_simple_fw.test \
188_uboot_redundant_bad_param.test \
189_uboot_redundant_recover.test \
190_one_metadata_cmdline.test \
191_disk_crypto_via_env.test
191_disk_crypto_via_env.test \
192_delta_fat_upgrade.test

EXTRA_DIST = $(TESTS) common.sh 1K.bin 1K-corrupt.bin 150K.bin