[PATCH] add a property install-if-hash-different

18 views
Skip to first unread message

Philippe Reynes

unread,
Jul 21, 2026, 11:14:30 AMJul 21
to swup...@googlegroups.com, Philippe Reynes
When the property install-if-hash-different is true,
swupdate checks if the destination already contains
the iexpected data by checking the hash before writting.

Signed-off-by: Philippe Reynes <philipp...@softathome.com>
---
core/cpio_utils.c | 99 +++++++++++++++++++++++++++++++++++
doc/source/sw-description.rst | 5 ++
handlers/raw_handler.c | 1 +
include/swupdate_image.h | 1 +
include/util.h | 2 +
parser/parse_external.c | 7 +++
parser/parser.c | 1 +
7 files changed, 116 insertions(+)

diff --git a/core/cpio_utils.c b/core/cpio_utils.c
index 4a8b2964..689988ff 100644
--- a/core/cpio_utils.c
+++ b/core/cpio_utils.c
@@ -593,6 +593,95 @@ static int hash_compare(void *dgst, unsigned char *hash)
return 0;
}

+static int check_hash(struct swupdate_copy *args)
+{
+ int fdout;
+ void *dgst; /* use a private context for HASH */
+ uint8_t buffer[16384];
+ ssize_t len;
+ long long nbytes;
+ int ret = 0;
+
+ if (!IsValidHash(args->hash)) {
+ ERROR("hash is invalid");
+ ret = -EINVAL;
+ goto out;
+ }
+
+ fdout = (args->out != NULL) ? *(int *)args->out : -1;
+ if (fdout < 0) {
+ ERROR("fdout is invalid");
+ ret = -EINVAL;
+ goto out;
+ }
+
+ if (lseek(fdout, args->seek, SEEK_SET) < 0) {
+ ERROR("lseek has failed");
+ ret = -EIO;
+ goto out;
+ }
+
+ dgst = swupdate_HASH_init(SHA_DEFAULT);
+ if (!dgst) {
+ ERROR("Cannot initialize the hash");
+ ret = -EFAULT;
+ goto out_lseek;
+ }
+
+ nbytes = args->nbytes;
+
+ while (nbytes > 0) {
+ size_t count = sizeof(buffer) < nbytes ? sizeof(buffer) : nbytes;
+ len = read(fdout, buffer, count);
+ if (len < 0) {
+ if (errno == EINTR) {
+ continue;
+ }
+
+ ERROR("Failure in stream %d: %s", fdout, strerror(errno));
+ ret = -EFAULT;
+ goto out_lseek;
+ }
+ if (swupdate_HASH_update(dgst, buffer, len) < 0) {
+ ERROR("Cannot update the hash");
+ ret = -EFAULT;
+ goto out_lseek;
+ }
+
+ nbytes -= len;
+ }
+
+ /*
+ * SHA256_HASH_LENGTH should be enough but openssl might write
+ * up to EVP_MAX_MD_SIZE = 64 bytes (sha512 size)
+ */
+ unsigned char md_value[64];
+ unsigned int md_len = 0;
+
+ if (swupdate_HASH_final(dgst, md_value, &md_len) < 0) {
+ ERROR("Cannot compute final hash");
+ ret = -EFAULT;
+ goto out_lseek;
+ }
+
+ if (md_len != SHA256_HASH_LENGTH || swupdate_HASH_compare(args->hash, md_value)) {
+ TRACE("hash is different");
+ ret = -EFAULT;
+ goto out_lseek;
+ }
+
+ out_lseek:
+ /* come back at the beginning of the file */
+ if (lseek(fdout, 0, SEEK_SET) < 0) {
+ ERROR("lseek to 0 has failed");
+ return -EIO;
+ }
+
+ out:
+
+ return ret;
+}
+
int copyfile(struct swupdate_copy *args)
{
unsigned int percent, prevpercent = 0;
@@ -788,6 +877,15 @@ int copyfile(struct swupdate_copy *args)
}
}

+ if (args->check_hash) {
+ TRACE("Checking hash on the destination");
+ if (!check_hash(args)) {
+ TRACE("This file is already on the destination => skipping");
+ ret = 0;
+ goto copyfile_exit;
+ }
+ }
+
if (args->seek) {
int fdout = (args->out != NULL) ? *(int *)args->out : -1;
if (fdout < 0) {
@@ -915,6 +1013,7 @@ int copyimage(void *out, struct img_type *img, writeimage callback)
.offs = (unsigned long*)&img->offset,
.seek = img->seek,
.skip_file = 0,
+ .check_hash = img->install_if_hash_different,
.compressed = img->compressed,
.checksum = &img->checksum,
.hash = img->sha256,
diff --git a/doc/source/sw-description.rst b/doc/source/sw-description.rst
index 2c1e0017..03c4f299 100644
--- a/doc/source/sw-description.rst
+++ b/doc/source/sw-description.rst
@@ -1533,6 +1533,11 @@ There are 4 main sections inside sw-description:
| | | | compared with the entries in |
| | | | sw-versions |
+-------------+----------+------------+---------------------------------------+
+ | install-if\ | bool | images | flag |
+ | -hash-\ | | files | if set, the hash of the image on the |
+ | different | | | board is compared with the hash in |
+ | | | | sw-description |
+ +-------------+----------+------------+---------------------------------------+
| encrypted | string | images | string to indicate the artefact is |
| | | files | encrypted with this cipher. |
| | | scripts | e.g 'encrypted = "aes-cbc"'. |
diff --git a/handlers/raw_handler.c b/handlers/raw_handler.c
index 08912f18..c82d18af 100644
--- a/handlers/raw_handler.c
+++ b/handlers/raw_handler.c
@@ -15,6 +15,7 @@
#include <linux/fs.h>
#endif

+#include "swupdate_crypto.h"
#include "swupdate_image.h"
#include "handler.h"
#include "util.h"
diff --git a/include/swupdate_image.h b/include/swupdate_image.h
index 0d2796a6..cc376b90 100644
--- a/include/swupdate_image.h
+++ b/include/swupdate_image.h
@@ -63,6 +63,7 @@ struct img_type {
char ivt_ascii[33];
char aes_ascii[65]; /* AES_256_KEY_LEN*2+1 */
bool install_directly;
+ bool install_if_hash_different;
int is_script;
int is_partitioner;
struct dict properties;
diff --git a/include/util.h b/include/util.h
index cb90917f..f5e1c7b9 100644
--- a/include/util.h
+++ b/include/util.h
@@ -85,6 +85,8 @@ struct swupdate_copy {
unsigned long long seek;
/* skip callback: only verify input */
int skip_file;
+ /* Check if the output has already the expected data */
+ int check_hash;
/* decompression to use */
enum compression_type compressed;
/* cpio crc checksum */
diff --git a/parser/parse_external.c b/parser/parse_external.c
index e63058f2..58877525 100644
--- a/parser/parse_external.c
+++ b/parser/parse_external.c
@@ -105,6 +105,12 @@ static void sw_set_install_if_higher(struct img_type *img, const char *value)
img->id.install_if_higher = 1;
}

+static void sw_set_install_if_hash_different(struct img_type *img, const char *value)
+{
+ (void)value;
+ img->install_if_hash_different = 1;
+}
+
static const struct stream_handler_entry handlers[] = {
{ "type", sw_set_type },
{ "filename", sw_set_filename },
@@ -125,6 +131,7 @@ static const struct stream_handler_entry handlers[] = {
{ "installed-directly", sw_set_install_directly },
{ "install-if-different", sw_set_install_if_different },
{ "install-if-higher", sw_set_install_if_higher },
+ { "install-if-hash-different", sw_set_install_if_hash_different },
};

static void sw_append_stream(struct img_type *img, const char *key,
diff --git a/parser/parser.c b/parser/parser.c
index 38aba0bc..3a155002 100644
--- a/parser/parser.c
+++ b/parser/parser.c
@@ -519,6 +519,7 @@ static int parse_common_attributes(parsertype p, void *elem, struct img_type *im
GET_FIELD_BOOL(p, elem, "preserve-attributes", &image->preserve_attributes);
GET_FIELD_BOOL(p, elem, "install-if-different", &image->id.install_if_different);
GET_FIELD_BOOL(p, elem, "install-if-higher", &image->id.install_if_higher);
+ GET_FIELD_BOOL(p, elem, "install-if-hash-different", &image->install_if_hash_different);

if ((encrypted = get_field_string(p, elem, "encrypted")) != NULL) {
image->is_encrypted = true;
--
2.43.0

Stefano Babic

unread,
Jul 27, 2026, 10:05:50 AM (9 days ago) Jul 27
to Philippe Reynes, swup...@googlegroups.com
Hi Philippe,
I think it is not the right time to make the check. install_if_different
belongs to the family of install_<condition>_different, and they are
checked at parse time. There are good reasons for it - see
parse_common_attributes().

The reason is to make available the attributes and add logic when/if
this is necessary. For example this works:

filename = ....
install-if-different = true;
hook = "do_something_before_installing";

and in the hook:

function do_something_before_installing(image)
if image.skip
<do something>
else
<do something else>
end
end

Your patch breaks this because the check is done later at the time of
installing and no hook is possible then.

And logically, it belongs to the requirements for installing, and it is
not a parameter of installation itself. So I kindly ask you to move the
check_hash to be called inside the parser, exactly as the other
install_if. IMHO it should be called inside parse_common_attributes()
and sets image->skip = SKIP_SAME if hash matches.

Best regards,
Stefano Babic
_______________________________________________________________________
Nabla Software Engineering GmbH
Hirschstr. 111A | 86156 Augsburg | Tel: +49 821 45592596
Geschäftsführer : Stefano Babic | HRB 40522 Augsburg
E-Mail: sba...@nabladev.com

Philippe Reynes

unread,
Jul 27, 2026, 12:23:34 PM (9 days ago) Jul 27
to Stefano Babic, swup...@googlegroups.com
Hi Stefano,


Le 27/07/2026 à 16:05, Stefano Babic a écrit :
>
> This Mail comes from Outside of SoftAtHome: Do not answer, click links
> or open attachments unless you recognize the sender and know the
> content is safe.
Thanks a lot for the feedback. I have missed this use case.
I'm preparing a v2 with the check in the function parse_common_attributes,
like the function is_image_installed.


>
> Best regards,
> Stefano Babic


Best regards,
Philippe Reynes
> --
> You received this message because you are subscribed to the Google
> Groups "swupdate" group.
> To unsubscribe from this group and stop receiving emails from it, send
> an email to swupdate+u...@googlegroups.com.
> To view this discussion visit
> https://groups.google.com/d/msgid/swupdate/1b1ce0ed-b5fa-4b9a-b853-20a82783558e%40swupdate.org.

Philippe Reynes

unread,
Jul 28, 2026, 1:02:59 PM (8 days ago) Jul 28
to swup...@googlegroups.com, Philippe Reynes
When the property install-if-hash-different is true,
swupdate checks if the destination already contains
the expected data by checking the hash before writting.

For the moment, only raw images are supported.

Signed-off-by: Philippe Reynes <philipp...@softathome.com>
---
history:
v2:
- move the check in the parser (thanks Stefano)
- only support raw images for now


doc/source/sw-description.rst | 5 ++
include/swupdate_image.h | 1 +
include/util.h | 2 +
parser/parse_external.c | 7 +++
parser/parser.c | 92 +++++++++++++++++++++++++++++++++++
5 files changed, 107 insertions(+)

diff --git a/doc/source/sw-description.rst b/doc/source/sw-description.rst
index 2c1e0017..3934994e 100644
--- a/doc/source/sw-description.rst
+++ b/doc/source/sw-description.rst
@@ -1533,6 +1533,11 @@ There are 4 main sections inside sw-description:
| | | | compared with the entries in |
| | | | sw-versions |
+-------------+----------+------------+---------------------------------------+
+ | install-if\ | bool | images | flag |
+ | -hash-\ | | (raw only) | if set, the hash of the image on the |
+ | different | | | board is compared with the hash in |
+ | | | | sw-description |
+ +-------------+----------+------------+---------------------------------------+
| encrypted | string | images | string to indicate the artefact is |
| | | files | encrypted with this cipher. |
| | | scripts | e.g 'encrypted = "aes-cbc"'. |
index 38aba0bc..b2d072b7 100644
--- a/parser/parser.c
+++ b/parser/parser.c
@@ -25,6 +25,7 @@
#include "parsers.h"
#include "swupdate_dict.h"
#include "swupdate_aes.h"
+#include "swupdate_crypto.h"
#include "lua_util.h"

#define MODULE_NAME "PARSER"
@@ -403,6 +404,93 @@ static int is_image_higher(struct swver *sw_ver_list,
return false;
}

+static int is_image_with_same_hash(struct img_type *img)
+{
+ int fdout;
+ void *dgst; /* use a private context for HASH */
+ uint8_t buffer[16384];
+ ssize_t len;
+ long long nbytes;
+ int ret = 0;
+ /*
+ * SHA256_HASH_LENGTH should be enough but openssl might write
+ * up to EVP_MAX_MD_SIZE = 64 bytes (sha512 size)
+ */
+ unsigned char md_value[64];
+ unsigned int md_len = 0;
+
+ if (!img->install_if_hash_different)
+ return 0;
+
+ /* Rigth now, we only manage type raw */
+ if (strncmp(img->type, "raw", strlen("raw"))) {
+ WARN("install-if-hash-different is set but type %s not supported yet", img->type);
+ return 0;
+ }
+
+ if (!img->size) {
+ WARN("install-if-hash-different is set but size is zero");
+ return 0;
+ }
+
+ if (!IsValidHash(img->sha256)) {
+ WARN("install-if-hash-different is set but hash is zero");
+ return 0;
+ }
+
+ fdout = open(img->device, O_RDONLY);
+ if (fdout < 0)
+ return 0;
+
+ if (lseek(fdout, img->seek, SEEK_SET) < 0) {
+ ERROR("lseek has failed");
+ goto out;
+ }
+
+ dgst = swupdate_HASH_init(SHA_DEFAULT);
+ if (!dgst) {
+ ERROR("Cannot initialize the hash");
+ goto out;
+ }
+
+ nbytes = img->size;
+
+ while (nbytes > 0) {
+ size_t count = sizeof(buffer) < nbytes ? sizeof(buffer) : nbytes;
+ len = read(fdout, buffer, count);
+ if (len < 0) {
+ if (errno == EINTR) {
+ continue;
+ }
+
+ ERROR("Failure in stream %d: %s", fdout, strerror(errno));
+ goto out;
+ }
+ if (swupdate_HASH_update(dgst, buffer, len) < 0) {
+ ERROR("Cannot update the hash");
+ goto out;
+ }
+
+ nbytes -= len;
+ }
+
+ if (swupdate_HASH_final(dgst, md_value, &md_len) < 0) {
+ ERROR("Cannot compute final hash");
+ goto out;
+ }
+
+ if (md_len != SHA256_HASH_LENGTH || swupdate_HASH_compare(img->sha256, md_value))
+ goto out;
+
+ TRACE("Found image %s on the device with the same hash => skipping", img->fname);
+ ret = 1;
+
+out:
+ close(fdout);
+
+ return ret;
+}
+
static void set_img_globals(struct img_type *img, struct swupdate_cfg *sw)
{
img->bootloader = &sw->bootloader;
@@ -519,6 +607,7 @@ static int parse_common_attributes(parsertype p, void *elem, struct img_type *im
GET_FIELD_BOOL(p, elem, "preserve-attributes", &image->preserve_attributes);
GET_FIELD_BOOL(p, elem, "install-if-different", &image->id.install_if_different);
GET_FIELD_BOOL(p, elem, "install-if-higher", &image->id.install_if_higher);
+ GET_FIELD_BOOL(p, elem, "install-if-hash-different", &image->install_if_hash_different);

if ((encrypted = get_field_string(p, elem, "encrypted")) != NULL) {
image->is_encrypted = true;
@@ -538,6 +627,9 @@ static int parse_common_attributes(parsertype p, void *elem, struct img_type *im
image->skip = SKIP_NONE;
}

+ if (is_image_with_same_hash(image))
+ image->skip = SKIP_SAME;
+
GET_FIELD_STRING(p, elem, "preinstall", image->lua_fcn_pre);
GET_FIELD_STRING(p, elem, "postinstall", image->lua_fcn_post);

--
2.43.0

Reply all
Reply to author
Forward
0 new messages