]> wimlib.net Git - wimlib/blob - src/integrity.c
Empty file fix
[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  * Copyright (C) 2012 Eric Biggers
9  *
10  * wimlib - Library for working with WIM files 
11  *
12  * This library is free software; you can redistribute it and/or modify it under
13  * the terms of the GNU Lesser General Public License as published by the Free
14  * Software Foundation; either version 2.1 of the License, or (at your option) any
15  * later version.
16  *
17  * This library is distributed in the hope that it will be useful, but WITHOUT ANY
18  * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
19  * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
20  *
21  * You should have received a copy of the GNU Lesser General Public License along
22  * with this library; if not, write to the Free Software Foundation, Inc., 59
23  * Temple Place, Suite 330, Boston, MA 02111-1307 USA 
24  */
25
26 #include "wimlib_internal.h"
27 #include "io.h"
28 #include "sha1.h"
29
30 /* Size, in bytes, of each SHA1-summed chunk, when wimlib writes integrity
31  * information. */
32 #define INTEGRITY_CHUNK_SIZE 10485760
33
34 /*
35  * Verifies the integrity of a WIM. 
36  *
37  * @fp:            FILE* of the WIM, currently positioned at the end of the header. 
38  * @num_bytes:     Number of bytes to verify the integrity of.
39  * @chunk_size:    Chunk size per SHA1 message digest.
40  * @sha1sums:      Array of SHA1 message digests; 20 bytes each, one per chunk.
41  * @show_progress: Nonzero if the percent complete is to be printed after every
42  *                      chunk.
43  * @status:        On success, set to WIM_INTEGRITY_OK or WIM_INTEGRITY_NOT_OK 
44  *                      based on whether the WIM is intact or not.
45  */
46 static int verify_integrity(FILE *fp, u64 num_bytes, u32 chunk_size, 
47                             const u8 *sha1sums, int show_progress,
48                             int *status)
49 {
50         char  *chunk_buf;
51         u8     resblock[WIM_HASH_SIZE];
52         u64    bytes_remaining;
53         size_t bytes_to_read;
54         uint   percent_done;
55         int    ret;
56
57         chunk_buf = MALLOC(chunk_size);
58         if (!chunk_buf) {
59                 ERROR("Failed to allocate %u byte buffer for integrity "
60                                 "chunks\n", chunk_size);
61                 return WIMLIB_ERR_NOMEM;
62         }
63         bytes_remaining = num_bytes;
64         while (bytes_remaining != 0) {
65                 if (show_progress) {
66                         percent_done = (num_bytes - bytes_remaining) * 100 / 
67                                         num_bytes;
68                         printf("Verifying integrity of WIM (%"PRIu64" bytes "
69                                         "remaining, %u%% done)       \r", 
70                                         bytes_remaining, percent_done);
71                         fflush(stdout);
72                 }
73                 bytes_to_read = min(chunk_size, bytes_remaining);
74                 if (fread(chunk_buf, 1, bytes_to_read, fp) != bytes_to_read) {
75                         if (feof(fp)) {
76                                 ERROR("Unexpected EOF while verifying "
77                                                 "integrity of WIM!\n");
78                         } else {
79                                 ERROR("File stream error while verifying "
80                                                 "integrity of WIM: %m\n");
81                         }
82                         ret = WIMLIB_ERR_READ;
83                         goto verify_integrity_error;
84                 }
85                 sha1_buffer(chunk_buf, bytes_to_read, resblock);
86                 if (memcmp(resblock, sha1sums, WIM_HASH_SIZE) != 0) {
87                         *status = WIM_INTEGRITY_NOT_OK;
88                         goto verify_integrity_done;
89                 }
90                 sha1sums += WIM_HASH_SIZE;
91                 bytes_remaining -= bytes_to_read;
92         }
93         *status = WIM_INTEGRITY_OK;
94 verify_integrity_done:
95         ret = 0;
96 verify_integrity_error:
97         FREE(chunk_buf);
98         if (show_progress)
99                 putchar('\n');
100         return ret;
101 }
102
103 /*
104  * Verifies the integrity of the WIM. 
105  *
106  * @show_progress: Nonzero if the percent complete is to be printed after every
107  *                      chunk.
108  * @status:        On success, set to WIM_INTEGRITY_OK, WIM_INTEGRITY_NOT_OK,
109  *                      or WIM_INTEGRITY_NONEXISTENT.
110  *
111  * Returns: 0, WIMLIB_ERR_INVALID_INTEGRITY_TABLE, WIMLIB_ERR_NOMEM, or
112  * WIMLIB_ERR_READ.  If nonzero, the boolean pointed to by @ok is not changed.
113  */
114 int check_wim_integrity(WIMStruct *w, int show_progress, int *status)
115 {
116
117         struct resource_entry *res_entry;
118         int ctype;
119         u8 *buf = NULL;
120         int ret;
121         u32 integrity_table_size;
122         u32 num_entries;
123         u32 chunk_size;
124         const u8 *p;
125         u64 expected_size;
126         u64 end_lookup_table_offset;
127         u64 bytes_to_check;
128         u64 expected_num_entries;
129
130         res_entry = &w->hdr.integrity;
131         if (res_entry->size == 0) {
132                 DEBUG("No integrity information.\n");
133                 *status = WIM_INTEGRITY_NONEXISTENT;
134                 return 0;
135         }
136         ctype = wim_resource_compression_type(w, res_entry);
137         if (res_entry->original_size < 12) {
138                 ERROR("Integrity table resource is too short!\n");
139                 return WIMLIB_ERR_INVALID_INTEGRITY_TABLE;
140         }
141
142         /* Read the integrity table into memory. */
143         buf = MALLOC(res_entry->original_size);
144         if (!buf) {
145                 ERROR("Out of memory (needed %zu bytes for integrity table)!\n",
146                                                 res_entry->original_size);
147                 ret = WIMLIB_ERR_NOMEM;
148                 goto check_integrity_error;
149         }
150         ret = read_full_resource(w->fp, res_entry->size, res_entry->original_size,
151                                  res_entry->offset, ctype, buf);
152         if (ret != 0) {
153                 ERROR("Failed to read integrity table (size = %"PRIu64", "
154                                 "original_size = %"PRIu64", offset = "
155                                 "%"PRIu64", ctype = %d\n",
156                                 (u64)res_entry->size, res_entry->original_size,
157                                 res_entry->offset, ctype);
158                 goto check_integrity_error;
159         }
160
161         p = get_u32(buf, &integrity_table_size);
162         p = get_u32(p, &num_entries);
163         p = get_u32(p, &chunk_size);
164
165         /* p now points to the array of SHA1 message digests for the WIM. */
166
167         /* Make sure the integrity table is the right size. */
168         if (integrity_table_size != res_entry->original_size) {
169                 ERROR("Inconsistent integrity table sizes: header says %u "
170                                 "bytes but resource entry says "
171                                 "%"PRIu64" bytes\n", integrity_table_size, 
172                                 res_entry->original_size);
173
174                 ret = WIMLIB_ERR_INVALID_INTEGRITY_TABLE;
175                 goto check_integrity_error;
176         }
177
178         DEBUG("integrity_table_size = %u, num_entries = %u, chunk_size = %u\n",
179                         integrity_table_size, num_entries, chunk_size);
180
181
182         expected_size = num_entries * WIM_HASH_SIZE + 12;
183
184         if (integrity_table_size != expected_size) {
185                 ERROR("Integrity table is %u bytes, but expected %"PRIu64" "
186                                 "bytes to hold %u entries!\n", 
187                                 integrity_table_size,
188                                 expected_size, num_entries);
189                 ret = WIMLIB_ERR_INVALID_INTEGRITY_TABLE;
190                 goto check_integrity_error;
191         }
192
193         end_lookup_table_offset = w->hdr.lookup_table_res_entry.offset +
194                                   w->hdr.lookup_table_res_entry.size;
195
196         bytes_to_check = end_lookup_table_offset - WIM_HEADER_DISK_SIZE;
197
198         expected_num_entries = bytes_to_check / chunk_size + 
199                                (bytes_to_check % chunk_size != 0);
200
201         if (num_entries != expected_num_entries) {
202                 ERROR("%"PRIu64 " entries would be required to checksum "
203                         "the %"PRIu64" bytes from the end of the header to the\n"
204                         "end of the lookup table with a chunk size of %u, but "
205                         "there were only %u entries!\n", 
206                         expected_num_entries, bytes_to_check, chunk_size,
207                         num_entries);
208                 ret = WIMLIB_ERR_INVALID_INTEGRITY_TABLE;
209                 goto check_integrity_error;
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("Failed to seek to byte %u of WIM to check "
217                                 "integrity: %m\n", WIM_HEADER_DISK_SIZE);
218                 ret = WIMLIB_ERR_READ;
219                 goto check_integrity_error;
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, status);
225 check_integrity_error:
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 {
243         u64   bytes_to_check;
244         u64   bytes_remaining;
245         u8   *buf;
246         u8   *p;
247         char *chunk_buf;
248         u32   num_entries;
249         u32   integrity_table_size;
250         int   ret;
251
252         DEBUG("Writing integrity table\n");
253         if (fseeko(out, end_header_offset, SEEK_SET) != 0) {
254                 ERROR("Failed to seek to byte %"PRIu64" of WIM "
255                                 "to calculate integrity data: %m\n",
256                                 end_header_offset);
257                 return WIMLIB_ERR_WRITE;
258         }
259
260         bytes_to_check = end_lookup_table_offset - end_header_offset;
261         num_entries = bytes_to_check / INTEGRITY_CHUNK_SIZE +
262                         (bytes_to_check % INTEGRITY_CHUNK_SIZE != 0);
263         integrity_table_size = num_entries * WIM_HASH_SIZE + 3 * sizeof(u32);
264
265         DEBUG("integrity table size = %u\n", integrity_table_size);
266
267
268         buf = MALLOC(integrity_table_size);
269         if (!buf) {
270                 ERROR("Failed to allocate %u bytes for integrity table!\n",
271                                 integrity_table_size);
272                 return WIMLIB_ERR_NOMEM;
273         }
274
275         p = put_u32(buf, integrity_table_size);
276         p = put_u32(p, num_entries);
277         p = put_u32(p, INTEGRITY_CHUNK_SIZE);
278
279         chunk_buf = MALLOC(INTEGRITY_CHUNK_SIZE);
280         if (!chunk_buf) {
281                 ERROR("Failed to allocate %u bytes for integrity chunk "
282                                 "buffer!\n", INTEGRITY_CHUNK_SIZE);
283                 ret = WIMLIB_ERR_NOMEM;
284                 goto err2;
285         }
286
287         bytes_remaining = bytes_to_check;
288
289         DEBUG("Bytes to check = %"PRIu64"\n", bytes_to_check);
290
291         while (bytes_remaining != 0) {
292
293                 uint percent_done = (bytes_to_check - bytes_remaining) * 
294                                     100 / bytes_to_check;
295
296                 if (show_progress) {
297                         printf("Calculating integrity checksums for WIM "
298                                         "(%"PRIu64" bytes remaining, %u%% "
299                                         "done)      \r", 
300                                         bytes_remaining, percent_done);
301                         fflush(stdout);
302                 }
303
304
305                 size_t bytes_to_read = min(INTEGRITY_CHUNK_SIZE, bytes_remaining);
306                 size_t bytes_read = fread(chunk_buf, 1, bytes_to_read, out);
307                 if (bytes_read != bytes_to_read) {
308                         if (feof(out)) {
309                                 ERROR("Unexpected EOF while calculating "
310                                                 "integrity checksums!\n");
311                         } else {
312                                 ERROR("File stream error while calculating "
313                                                 "integrity checksums: %m\n");
314                         }
315                         ret = WIMLIB_ERR_READ;
316                         goto err2;
317                 }
318                 sha1_buffer(chunk_buf, bytes_read, p);
319                 p += WIM_HASH_SIZE;
320                 bytes_remaining -= bytes_read;
321         }
322         if (show_progress)
323                 puts("Calculating integrity checksums for WIM "
324                                 "(0 bytes remaining, 100% done)"
325                                 "                       ");
326
327         if (fseeko(out, 0, SEEK_END) != 0) {
328                 ERROR("Failed to seek to end of WIM to write integrity "
329                                 "table: %m\n");
330                 ret = WIMLIB_ERR_WRITE;
331                 goto err1;
332         }
333
334         if (fwrite(buf, 1, integrity_table_size, out) != integrity_table_size) {
335                 ERROR("Failed to write integrity table to end of WIM: %m\n");
336                 ret = WIMLIB_ERR_WRITE;
337                 goto err1;
338         }
339         ret = 0;
340 err1:
341         FREE(chunk_buf);
342 err2:
343         FREE(buf);
344         return ret;
345 }