+static int
+hasher_continue_blob(const struct blob_descriptor *blob, u64 offset,
+ const void *chunk, size_t size, void *_ctx)
+{
+ struct hasher_context *ctx = _ctx;
+
+ sha1_update(&ctx->sha_ctx, chunk, size);
+
+ return call_continue_blob(blob, offset, chunk, size, &ctx->cbs);
+}
+
+static int
+report_sha1_mismatch_error(const struct blob_descriptor *blob,
+ const u8 actual_hash[SHA1_HASH_SIZE])
+{
+ tchar expected_hashstr[SHA1_HASH_SIZE * 2 + 1];
+ tchar actual_hashstr[SHA1_HASH_SIZE * 2 + 1];
+
+ wimlib_assert(blob->blob_location != BLOB_NONEXISTENT);
+ wimlib_assert(blob->blob_location != BLOB_IN_ATTACHED_BUFFER);
+
+ sprint_hash(blob->hash, expected_hashstr);
+ sprint_hash(actual_hash, actual_hashstr);
+
+ if (blob_is_in_file(blob)) {
+ ERROR("A file was concurrently modified!\n"
+ " Path: \"%"TS"\"\n"
+ " Expected SHA-1: %"TS"\n"
+ " Actual SHA-1: %"TS"\n",
+ blob_file_path(blob), expected_hashstr, actual_hashstr);
+ return WIMLIB_ERR_CONCURRENT_MODIFICATION_DETECTED;
+ } else if (blob->blob_location == BLOB_IN_WIM) {
+ const struct wim_resource_descriptor *rdesc = blob->rdesc;
+ ERROR("A WIM resource is corrupted!\n"
+ " WIM file: \"%"TS"\"\n"
+ " Blob uncompressed size: %"PRIu64"\n"
+ " Resource offset in WIM: %"PRIu64"\n"
+ " Resource uncompressed size: %"PRIu64"\n"
+ " Resource size in WIM: %"PRIu64"\n"
+ " Resource flags: 0x%x%"TS"\n"
+ " Resource compression type: %"TS"\n"
+ " Resource compression chunk size: %"PRIu32"\n"
+ " Expected SHA-1: %"TS"\n"
+ " Actual SHA-1: %"TS"\n",
+ rdesc->wim->filename,
+ blob->size,
+ rdesc->offset_in_wim,
+ rdesc->uncompressed_size,
+ rdesc->size_in_wim,
+ (unsigned int)rdesc->flags,
+ (rdesc->is_pipable ? T(", pipable") : T("")),
+ wimlib_get_compression_type_string(
+ rdesc->compression_type),
+ rdesc->chunk_size,
+ expected_hashstr, actual_hashstr);
+ return WIMLIB_ERR_INVALID_RESOURCE_HASH;
+ } else {
+ ERROR("File data was concurrently modified!\n"
+ " Location ID: %d\n"
+ " Expected SHA-1: %"TS"\n"
+ " Actual SHA-1: %"TS"\n",
+ (int)blob->blob_location,
+ expected_hashstr, actual_hashstr);
+ return WIMLIB_ERR_CONCURRENT_MODIFICATION_DETECTED;
+ }
+}
+
+/* Callback for finishing reading a blob while calculating its SHA-1 message
+ * digest. */
+static int
+hasher_end_blob(struct blob_descriptor *blob, int status, void *_ctx)
+{
+ struct hasher_context *ctx = _ctx;
+ u8 hash[SHA1_HASH_SIZE];
+ int ret;
+
+ if (unlikely(status)) {
+ /* Error occurred; the full blob may not have been read. */
+ ret = status;
+ goto out_next_cb;
+ }
+
+ /* Retrieve the final SHA-1 message digest. */
+ sha1_final(hash, &ctx->sha_ctx);
+
+ /* Set the SHA-1 message digest of the blob, or compare the calculated
+ * value with stored value. */
+ if (blob->unhashed) {
+ if (ctx->flags & COMPUTE_MISSING_BLOB_HASHES)
+ copy_hash(blob->hash, hash);
+ } else if ((ctx->flags & VERIFY_BLOB_HASHES) &&
+ unlikely(!hashes_equal(hash, blob->hash)))
+ {
+ ret = report_sha1_mismatch_error(blob, hash);
+ goto out_next_cb;
+ }
+ ret = 0;
+out_next_cb:
+ return call_end_blob(blob, ret, &ctx->cbs);
+}
+
+/* Read the full data of the specified blob, passing the data into the specified
+ * callbacks (all of which are optional) and either checking or computing the
+ * SHA-1 message digest of the blob. */
+int
+read_blob_with_sha1(struct blob_descriptor *blob,
+ const struct read_blob_callbacks *cbs)