]> wimlib.net Git - wimlib/blob - src/integrity.c
Rework WIM writing code
[wimlib] / src / integrity.c
1 /*
2  * integrity.c
3  *
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.
7  */
8
9 /*
10  * Copyright (C) 2012 Eric Biggers
11  *
12  * This file is part of wimlib, a library for working with WIM files.
13  *
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)
17  * any later version.
18  *
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
22  * details.
23  *
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/.
26  */
27
28 #include "wimlib_internal.h"
29 #include "io.h"
30 #include "sha1.h"
31
32 /* Size, in bytes, of each SHA1-summed chunk, when wimlib writes integrity
33  * information. */
34 #define INTEGRITY_CHUNK_SIZE 10485760
35
36 /*
37  * Verifies the integrity of a WIM.
38  *
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
44  *                      chunk.
45  */
46 static int verify_integrity(FILE *fp, u64 num_bytes, u32 chunk_size,
47                             const u8 *sha1sums, int show_progress)
48 {
49         u8    *chunk_buf;
50         u8     resblock[SHA1_HASH_SIZE];
51         u64    bytes_remaining;
52         size_t bytes_to_read;
53         uint   percent_done;
54         int    ret;
55
56         chunk_buf = MALLOC(chunk_size);
57         if (!chunk_buf) {
58                 ERROR("Failed to allocate %u byte buffer for integrity chunks",
59                       chunk_size);
60                 return WIMLIB_ERR_NOMEM;
61         }
62         bytes_remaining = num_bytes;
63         while (bytes_remaining != 0) {
64                 if (show_progress) {
65                         percent_done = (num_bytes - bytes_remaining) * 100 /
66                                         num_bytes;
67                         printf("Verifying integrity of WIM (%"PRIu64" bytes "
68                                         "remaining, %u%% done)       \r",
69                                         bytes_remaining, percent_done);
70                         fflush(stdout);
71                 }
72                 bytes_to_read = min(chunk_size, bytes_remaining);
73                 if (fread(chunk_buf, 1, bytes_to_read, fp) != bytes_to_read) {
74                         if (feof(fp)) {
75                                 ERROR("Unexpected EOF while verifying "
76                                       "integrity of WIM");
77                         } else {
78                                 ERROR_WITH_ERRNO("File stream error while "
79                                                  "verifying integrity of WIM");
80                         }
81                         ret = WIMLIB_ERR_READ;
82                         goto out;
83                 }
84                 sha1_buffer(chunk_buf, bytes_to_read, resblock);
85                 if (!hashes_equal(resblock, sha1sums)) {
86                         ret = WIM_INTEGRITY_NOT_OK;
87                         goto out;
88                 }
89                 sha1sums += SHA1_HASH_SIZE;
90                 bytes_remaining -= bytes_to_read;
91         }
92         ret = WIM_INTEGRITY_OK;
93 out:
94         FREE(chunk_buf);
95         if (show_progress)
96                 putchar('\n');
97         return ret;
98 }
99
100 /*
101  * Verifies the integrity of the WIM.
102  */
103 int check_wim_integrity(WIMStruct *w, int show_progress)
104 {
105
106         struct resource_entry *res_entry;
107         u8 *buf = NULL;
108         int ret;
109         u32 integrity_table_size;
110         u32 num_entries;
111         u32 chunk_size;
112         const u8 *p;
113         u64 expected_size;
114         u64 end_lookup_table_offset;
115         u64 bytes_to_check;
116         u64 expected_num_entries;
117
118         res_entry = &w->hdr.integrity;
119         if (res_entry->size == 0) {
120                 DEBUG("No integrity information.");
121                 return WIM_INTEGRITY_NONEXISTENT;
122         }
123         if (res_entry->original_size < 12) {
124                 ERROR("Integrity table is too short");
125                 return WIMLIB_ERR_INVALID_INTEGRITY_TABLE;
126         }
127         if (res_entry->flags & WIM_RESHDR_FLAG_COMPRESSED) {
128                 ERROR("Didn't expect a compressed integrity table");
129                 return WIMLIB_ERR_INVALID_INTEGRITY_TABLE;
130         }
131
132         /* Read the integrity table into memory. */
133         if ((sizeof(size_t) < sizeof(u64)
134             && res_entry->original_size > ~(size_t)0)
135             || ((buf = MALLOC(res_entry->original_size)) == NULL))
136         {
137                 ERROR("Out of memory (needed %zu bytes for integrity table)",
138                       (size_t)res_entry->original_size);
139                 ret = WIMLIB_ERR_NOMEM;
140                 goto out;
141         }
142         ret = read_uncompressed_resource(w->fp, res_entry->offset,
143                                          res_entry->original_size, buf);
144         if (ret != 0) {
145                 ERROR("Failed to read integrity table (size = %"PRIu64", "
146                       "original_size = %"PRIu64", offset = "
147                       "%"PRIu64")",
148                       (u64)res_entry->size, res_entry->original_size,
149                       res_entry->offset);
150                 goto out;
151         }
152
153         p = get_u32(buf, &integrity_table_size);
154         p = get_u32(p, &num_entries);
155         p = get_u32(p, &chunk_size);
156
157         /* p now points to the array of SHA1 message digests for the WIM. */
158
159         /* Make sure the integrity table is the right size. */
160         if (integrity_table_size != res_entry->original_size) {
161                 ERROR("Inconsistent integrity table sizes: header says %u "
162                       "bytes but resource entry says "
163                       "%"PRIu64" bytes",
164                       integrity_table_size, res_entry->original_size);
165                 ret = WIMLIB_ERR_INVALID_INTEGRITY_TABLE;
166                 goto out;
167         }
168
169         DEBUG("integrity_table_size = %u, num_entries = %u, chunk_size = %u",
170               integrity_table_size, num_entries, chunk_size);
171
172
173         expected_size = num_entries * SHA1_HASH_SIZE + 12;
174
175         if (integrity_table_size != expected_size) {
176                 ERROR("Integrity table is %u bytes, but expected %"PRIu64" "
177                       "bytes to hold %u entries",
178                       integrity_table_size, expected_size, num_entries);
179                 ret = WIMLIB_ERR_INVALID_INTEGRITY_TABLE;
180                 goto out;
181         }
182
183         if (chunk_size == 0) {
184                 ERROR("Cannot use integrity chunk size of 0");
185                 ret = WIMLIB_ERR_INVALID_INTEGRITY_TABLE;
186                 goto out;
187         }
188
189         end_lookup_table_offset = w->hdr.lookup_table_res_entry.offset +
190                                   w->hdr.lookup_table_res_entry.size;
191
192         if (end_lookup_table_offset < WIM_HEADER_DISK_SIZE) {
193                 ERROR("WIM lookup table ends before WIM header ends???");
194                 ret = WIMLIB_ERR_INVALID_INTEGRITY_TABLE;
195                 goto out;
196         }
197
198         bytes_to_check = end_lookup_table_offset - WIM_HEADER_DISK_SIZE;
199
200         expected_num_entries = (bytes_to_check + chunk_size - 1) / chunk_size;
201
202         if (num_entries != expected_num_entries) {
203                 ERROR("%"PRIu64" entries would be required to checksum "
204                       "the %"PRIu64" bytes from the end of the header to the",
205                       expected_num_entries, bytes_to_check);
206                 ERROR("end of the lookup table with a chunk size of %u, but "
207                       "there were only %u entries", chunk_size, num_entries);
208                 ret = WIMLIB_ERR_INVALID_INTEGRITY_TABLE;
209                 goto out;
210         }
211
212         /* The integrity checking starts after the header, so seek to the offset
213          * in the WIM after the header. */
214
215         if (fseeko(w->fp, WIM_HEADER_DISK_SIZE, SEEK_SET) != 0) {
216                 ERROR_WITH_ERRNO("Failed to seek to byte %u of WIM to check "
217                                  "integrity", WIM_HEADER_DISK_SIZE);
218                 ret = WIMLIB_ERR_READ;
219                 goto out;
220         }
221         /* call verify_integrity(), which does the actual checking of the SHA1
222          * message digests. */
223         ret = verify_integrity(w->fp, bytes_to_check, chunk_size, p,
224                                show_progress);
225 out:
226         FREE(buf);
227         return ret;
228 }
229
230 /*
231  * Writes integrity information to the output stream for a WIM file being
232  * written.
233  *
234  * @end_header_offset is the offset of the byte after the header, which is the
235  *      beginning of the region that is checksummed.
236  *
237  * @end_lookup_table_offset is the offset of the byte after the lookup table,
238  *      which is the end of the region that is checksummed.
239  */
240 int write_integrity_table(FILE *out, u64 end_header_offset,
241                           u64 end_lookup_table_offset, int show_progress,
242                           struct resource_entry *out_res_entry)
243 {
244         u64  bytes_to_check;
245         u64  bytes_remaining;
246         u8  *buf;
247         u8  *p;
248         u8  *chunk_buf;
249         u32  num_entries;
250         u32  integrity_table_size;
251         int  ret;
252         off_t start_offset;
253
254         start_offset = ftello(out);
255         if (start_offset == -1)
256                 return WIMLIB_ERR_WRITE;
257
258         DEBUG("Calculating integrity table");
259         if (fseeko(out, end_header_offset, SEEK_SET) != 0) {
260                 ERROR_WITH_ERRNO("Failed to seek to byte %"PRIu64" of WIM to "
261                                  "calculate integrity data", end_header_offset);
262                 return WIMLIB_ERR_WRITE;
263         }
264
265         bytes_to_check = end_lookup_table_offset - end_header_offset;
266         num_entries = (bytes_to_check + INTEGRITY_CHUNK_SIZE - 1) /
267                         INTEGRITY_CHUNK_SIZE;
268         integrity_table_size = num_entries * SHA1_HASH_SIZE + 3 * sizeof(u32);
269
270         DEBUG("integrity_table_size = %u", integrity_table_size);
271
272         buf = MALLOC(integrity_table_size);
273         if (!buf) {
274                 ERROR("Failed to allocate %u bytes for integrity table",
275                       integrity_table_size);
276                 return WIMLIB_ERR_NOMEM;
277         }
278
279         p = put_u32(buf, integrity_table_size);
280         p = put_u32(p, num_entries);
281         p = put_u32(p, INTEGRITY_CHUNK_SIZE);
282
283         chunk_buf = MALLOC(INTEGRITY_CHUNK_SIZE);
284         if (!chunk_buf) {
285                 ERROR("Failed to allocate %u bytes for integrity chunk buffer",
286                       INTEGRITY_CHUNK_SIZE);
287                 ret = WIMLIB_ERR_NOMEM;
288                 goto out_free_buf;
289         }
290
291         bytes_remaining = bytes_to_check;
292
293         DEBUG("Bytes to check = %"PRIu64, bytes_to_check);
294
295         while (bytes_remaining != 0) {
296
297                 uint percent_done = (bytes_to_check - bytes_remaining) *
298                                     100 / bytes_to_check;
299
300                 if (show_progress) {
301                         printf("Calculating integrity checksums for WIM "
302                                         "(%"PRIu64" bytes remaining, %u%% "
303                                         "done)      \r",
304                                         bytes_remaining, percent_done);
305                         fflush(stdout);
306                 }
307
308
309                 size_t bytes_to_read = min(INTEGRITY_CHUNK_SIZE, bytes_remaining);
310                 size_t bytes_read = fread(chunk_buf, 1, bytes_to_read, out);
311                 if (bytes_read != bytes_to_read) {
312                         if (feof(out)) {
313                                 ERROR("Unexpected EOF while calculating "
314                                       "integrity checksums");
315                         } else {
316                                 ERROR_WITH_ERRNO("File stream error while "
317                                                  "calculating integrity "
318                                                  "checksums");
319                         }
320                         ret = WIMLIB_ERR_READ;
321                         goto out_free_chunk_buf;
322                 }
323                 sha1_buffer(chunk_buf, bytes_read, p);
324                 p += SHA1_HASH_SIZE;
325                 bytes_remaining -= bytes_read;
326         }
327         if (show_progress)
328                 puts("Calculating integrity checksums for WIM "
329                                 "(0 bytes remaining, 100% done)"
330                                 "                       ");
331
332         if (fseeko(out, start_offset, SEEK_SET) != 0) {
333                 ERROR_WITH_ERRNO("Failed to seek to end of WIM");
334                 ret = WIMLIB_ERR_WRITE;
335                 goto out_free_chunk_buf;
336         }
337
338         if (fwrite(buf, 1, integrity_table_size, out) != integrity_table_size) {
339                 ERROR_WITH_ERRNO("Failed to write integrity table to end of "
340                                  "WIM");
341                 ret = WIMLIB_ERR_WRITE;
342                 goto out_free_chunk_buf;
343         }
344
345         out_res_entry->offset        = start_offset;
346         out_res_entry->size          = integrity_table_size;
347         out_res_entry->original_size = integrity_table_size;
348         out_res_entry->flags         = 0;
349         ret = 0;
350 out_free_chunk_buf:
351         FREE(chunk_buf);
352 out_free_buf:
353         FREE(buf);
354         return ret;
355 }