4 * WIM files can optionally contain an array of SHA1 message digests at the end,
5 * one digest for each 1 MB of the file. This file implements the checking of
6 * the digests, and the writing of the digests for new WIM files.
10 * Copyright (C) 2012 Eric Biggers
12 * This file is part of wimlib, a library for working with WIM files.
14 * wimlib is free software; you can redistribute it and/or modify it under the
15 * terms of the GNU General Public License as published by the Free
16 * Software Foundation; either version 3 of the License, or (at your option)
19 * wimlib is distributed in the hope that it will be useful, but WITHOUT ANY
20 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
21 * A PARTICULAR PURPOSE. See the GNU General Public License for more
24 * You should have received a copy of the GNU General Public License
25 * along with wimlib; if not, see http://www.gnu.org/licenses/.
28 #include "wimlib_internal.h"
32 /* Size, in bytes, of each SHA1-summed chunk, when wimlib writes integrity
34 #define INTEGRITY_CHUNK_SIZE 10485760
37 * Verifies the integrity of a WIM.
39 * @fp: FILE* of the WIM, currently positioned at the end of the header.
40 * @num_bytes: Number of bytes to verify the integrity of.
41 * @chunk_size: Chunk size per SHA1 message digest.
42 * @sha1sums: Array of SHA1 message digests; 20 bytes each, one per chunk.
43 * @show_progress: Nonzero if the percent complete is to be printed after every
45 * @status: On success, set to WIM_INTEGRITY_OK or WIM_INTEGRITY_NOT_OK
46 * based on whether the WIM is intact or not.
48 static int verify_integrity(FILE *fp, u64 num_bytes, u32 chunk_size,
49 const u8 *sha1sums, int show_progress,
53 u8 resblock[SHA1_HASH_SIZE];
59 chunk_buf = MALLOC(chunk_size);
61 ERROR("Failed to allocate %u byte buffer for integrity chunks",
63 return WIMLIB_ERR_NOMEM;
65 bytes_remaining = num_bytes;
66 while (bytes_remaining != 0) {
68 percent_done = (num_bytes - bytes_remaining) * 100 /
70 printf("Verifying integrity of WIM (%"PRIu64" bytes "
71 "remaining, %u%% done) \r",
72 bytes_remaining, percent_done);
75 bytes_to_read = min(chunk_size, bytes_remaining);
76 if (fread(chunk_buf, 1, bytes_to_read, fp) != bytes_to_read) {
78 ERROR("Unexpected EOF while verifying "
81 ERROR_WITH_ERRNO("File stream error while "
82 "verifying integrity of WIM");
84 ret = WIMLIB_ERR_READ;
85 goto verify_integrity_error;
87 sha1_buffer(chunk_buf, bytes_to_read, resblock);
88 if (!hashes_equal(resblock, sha1sums)) {
89 *status = WIM_INTEGRITY_NOT_OK;
90 goto verify_integrity_done;
92 sha1sums += SHA1_HASH_SIZE;
93 bytes_remaining -= bytes_to_read;
95 *status = WIM_INTEGRITY_OK;
96 verify_integrity_done:
98 verify_integrity_error:
106 * Verifies the integrity of the WIM.
108 * @show_progress: Nonzero if the percent complete is to be printed after every
110 * @status: On success, set to WIM_INTEGRITY_OK, WIM_INTEGRITY_NOT_OK,
111 * or WIM_INTEGRITY_NONEXISTENT.
113 * Returns: 0, WIMLIB_ERR_INVALID_INTEGRITY_TABLE, WIMLIB_ERR_NOMEM, or
114 * WIMLIB_ERR_READ. If nonzero, the boolean pointed to by @ok is not changed.
116 int check_wim_integrity(WIMStruct *w, int show_progress, int *status)
119 struct resource_entry *res_entry;
122 u32 integrity_table_size;
127 u64 end_lookup_table_offset;
129 u64 expected_num_entries;
131 res_entry = &w->hdr.integrity;
132 if (res_entry->size == 0) {
133 DEBUG("No integrity information.");
134 *status = WIM_INTEGRITY_NONEXISTENT;
137 if (res_entry->original_size < 12) {
138 ERROR("Integrity table is too short");
139 return WIMLIB_ERR_INVALID_INTEGRITY_TABLE;
141 if (res_entry->flags & WIM_RESHDR_FLAG_COMPRESSED) {
142 ERROR("Didn't expect a compressed integrity table");
143 return WIMLIB_ERR_INVALID_INTEGRITY_TABLE;
146 /* Read the integrity table into memory. */
147 if ((sizeof(size_t) < sizeof(u64)
148 && res_entry->original_size > ~(size_t)0)
149 || ((buf = MALLOC(res_entry->original_size)) == NULL))
151 ERROR("Out of memory (needed %zu bytes for integrity table)",
152 (size_t)res_entry->original_size);
153 ret = WIMLIB_ERR_NOMEM;
156 ret = read_uncompressed_resource(w->fp, res_entry->offset,
157 res_entry->original_size, buf);
159 ERROR("Failed to read integrity table (size = %"PRIu64", "
160 "original_size = %"PRIu64", offset = "
162 (u64)res_entry->size, res_entry->original_size,
167 p = get_u32(buf, &integrity_table_size);
168 p = get_u32(p, &num_entries);
169 p = get_u32(p, &chunk_size);
171 /* p now points to the array of SHA1 message digests for the WIM. */
173 /* Make sure the integrity table is the right size. */
174 if (integrity_table_size != res_entry->original_size) {
175 ERROR("Inconsistent integrity table sizes: header says %u "
176 "bytes but resource entry says "
178 integrity_table_size, res_entry->original_size);
179 ret = WIMLIB_ERR_INVALID_INTEGRITY_TABLE;
183 DEBUG("integrity_table_size = %u, num_entries = %u, chunk_size = %u",
184 integrity_table_size, num_entries, chunk_size);
187 expected_size = num_entries * SHA1_HASH_SIZE + 12;
189 if (integrity_table_size != expected_size) {
190 ERROR("Integrity table is %u bytes, but expected %"PRIu64" "
191 "bytes to hold %u entries",
192 integrity_table_size, expected_size, num_entries);
193 ret = WIMLIB_ERR_INVALID_INTEGRITY_TABLE;
197 if (chunk_size == 0) {
198 ERROR("Cannot use integrity chunk size of 0");
199 ret = WIMLIB_ERR_INVALID_INTEGRITY_TABLE;
203 end_lookup_table_offset = w->hdr.lookup_table_res_entry.offset +
204 w->hdr.lookup_table_res_entry.size;
206 if (end_lookup_table_offset < WIM_HEADER_DISK_SIZE) {
207 ERROR("WIM lookup table ends before WIM header ends???");
208 ret = WIMLIB_ERR_INVALID_INTEGRITY_TABLE;
212 bytes_to_check = end_lookup_table_offset - WIM_HEADER_DISK_SIZE;
214 expected_num_entries = (bytes_to_check + chunk_size - 1) / chunk_size;
216 if (num_entries != expected_num_entries) {
217 ERROR("%"PRIu64" entries would be required to checksum "
218 "the %"PRIu64" bytes from the end of the header to the",
219 expected_num_entries, bytes_to_check);
220 ERROR("end of the lookup table with a chunk size of %u, but "
221 "there were only %u entries", chunk_size, num_entries);
222 ret = WIMLIB_ERR_INVALID_INTEGRITY_TABLE;
226 /* The integrity checking starts after the header, so seek to the offset
227 * in the WIM after the header. */
229 if (fseeko(w->fp, WIM_HEADER_DISK_SIZE, SEEK_SET) != 0) {
230 ERROR_WITH_ERRNO("Failed to seek to byte %u of WIM to check "
231 "integrity", WIM_HEADER_DISK_SIZE);
232 ret = WIMLIB_ERR_READ;
235 /* call verify_integrity(), which does the actual checking of the SHA1
236 * message digests. */
237 ret = verify_integrity(w->fp, bytes_to_check, chunk_size, p,
238 show_progress, status);
245 * Writes integrity information to the output stream for a WIM file being
248 * @end_header_offset is the offset of the byte after the header, which is the
249 * beginning of the region that is checksummed.
251 * @end_lookup_table_offset is the offset of the byte after the lookup table,
252 * which is the end of the region that is checksummed.
254 int write_integrity_table(FILE *out, u64 end_header_offset,
255 u64 end_lookup_table_offset, int show_progress)
263 u32 integrity_table_size;
266 DEBUG("Calculating integrity table");
267 if (fseeko(out, end_header_offset, SEEK_SET) != 0) {
268 ERROR_WITH_ERRNO("Failed to seek to byte %"PRIu64" of WIM to "
269 "calculate integrity data", end_header_offset);
270 return WIMLIB_ERR_WRITE;
273 bytes_to_check = end_lookup_table_offset - end_header_offset;
274 num_entries = (bytes_to_check + INTEGRITY_CHUNK_SIZE - 1) /
275 INTEGRITY_CHUNK_SIZE;
276 integrity_table_size = num_entries * SHA1_HASH_SIZE + 3 * sizeof(u32);
278 DEBUG("integrity_table_size = %u", integrity_table_size);
280 buf = MALLOC(integrity_table_size);
282 ERROR("Failed to allocate %u bytes for integrity table",
283 integrity_table_size);
284 return WIMLIB_ERR_NOMEM;
287 p = put_u32(buf, integrity_table_size);
288 p = put_u32(p, num_entries);
289 p = put_u32(p, INTEGRITY_CHUNK_SIZE);
291 chunk_buf = MALLOC(INTEGRITY_CHUNK_SIZE);
293 ERROR("Failed to allocate %u bytes for integrity chunk buffer",
294 INTEGRITY_CHUNK_SIZE);
295 ret = WIMLIB_ERR_NOMEM;
299 bytes_remaining = bytes_to_check;
301 DEBUG("Bytes to check = %"PRIu64, bytes_to_check);
303 while (bytes_remaining != 0) {
305 uint percent_done = (bytes_to_check - bytes_remaining) *
306 100 / bytes_to_check;
309 printf("Calculating integrity checksums for WIM "
310 "(%"PRIu64" bytes remaining, %u%% "
312 bytes_remaining, percent_done);
317 size_t bytes_to_read = min(INTEGRITY_CHUNK_SIZE, bytes_remaining);
318 size_t bytes_read = fread(chunk_buf, 1, bytes_to_read, out);
319 if (bytes_read != bytes_to_read) {
321 ERROR("Unexpected EOF while calculating "
322 "integrity checksums");
324 ERROR_WITH_ERRNO("File stream error while "
325 "calculating integrity "
328 ret = WIMLIB_ERR_READ;
329 goto out_free_chunk_buf;
331 sha1_buffer(chunk_buf, bytes_read, p);
333 bytes_remaining -= bytes_read;
336 puts("Calculating integrity checksums for WIM "
337 "(0 bytes remaining, 100% done)"
340 if (fseeko(out, 0, SEEK_END) != 0) {
341 ERROR_WITH_ERRNO("Failed to seek to end of WIM to write "
343 ret = WIMLIB_ERR_WRITE;
344 goto out_free_chunk_buf;
347 if (fwrite(buf, 1, integrity_table_size, out) != integrity_table_size) {
348 ERROR_WITH_ERRNO("Failed to write integrity table to end of "
350 ret = WIMLIB_ERR_WRITE;
351 goto out_free_chunk_buf;