]> glassweightruler.freedombox.rocks Git - Ventoy.git/blob - wimboot/wimboot-2.7.3/src/wim.c
keep up with 1.0.67 (#1464)
[Ventoy.git] / wimboot / wimboot-2.7.3 / src / wim.c
1 /*
2 * Copyright (C) 2014 Michael Brown <mbrown@fensystems.co.uk>.
3 *
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License as
6 * published by the Free Software Foundation; either version 2 of the
7 * License, or (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful, but
10 * WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
17 * 02110-1301, USA.
18 */
19
20 /**
21 * @file
22 *
23 * WIM images
24 *
25 */
26
27 #include <stddef.h>
28 #include <stdio.h>
29 #include <string.h>
30 #include <wchar.h>
31 #include <assert.h>
32 #include "wimboot.h"
33 #include "vdisk.h"
34 #include "lzx.h"
35 #include "wim.h"
36
37 /** WIM chunk buffer */
38 static struct wim_chunk_buffer wim_chunk_buffer;
39
40 /**
41 * Get WIM header
42 *
43 * @v file Virtual file
44 * @v header WIM header to fill in
45 * @ret rc Return status code
46 */
47 int wim_header ( struct vdisk_file *file, struct wim_header *header ) {
48
49 /* Sanity check */
50 if ( sizeof ( *header ) > file->len ) {
51 DBG ( "WIM file too short (%#zx bytes)\n", file->len );
52 return -1;
53 }
54
55 /* Read WIM header */
56 file->read ( file, header, 0, sizeof ( *header ) );
57
58 return 0;
59 }
60
61 /**
62 * Get compressed chunk offset
63 *
64 * @v file Virtual file
65 * @v resource Resource
66 * @v chunk Chunk number
67 * @v offset Offset to fill in
68 * @ret rc Return status code
69 */
70 static int wim_chunk_offset ( struct vdisk_file *file,
71 struct wim_resource_header *resource,
72 unsigned int chunk, size_t *offset ) {
73 size_t zlen = ( resource->zlen__flags & WIM_RESHDR_ZLEN_MASK );
74 unsigned int chunks;
75 size_t offset_offset;
76 size_t offset_len;
77 size_t chunks_len;
78 union {
79 uint32_t offset_32;
80 uint64_t offset_64;
81 } u;
82
83 /* Special case: zero-length files have no chunks */
84 if ( ! resource->len ) {
85 *offset = 0;
86 return 0;
87 }
88
89 /* Calculate chunk parameters */
90 chunks = ( ( resource->len + WIM_CHUNK_LEN - 1 ) / WIM_CHUNK_LEN );
91 offset_len = ( ( resource->len > 0xffffffffULL ) ?
92 sizeof ( u.offset_64 ) : sizeof ( u.offset_32 ) );
93 chunks_len = ( ( chunks - 1 ) * offset_len );
94
95 /* Sanity check */
96 if ( chunks_len > zlen ) {
97 DBG ( "Resource too short for %d chunks\n", chunks );
98 return -1;
99 }
100
101 /* Special case: chunk 0 has no offset field */
102 if ( ! chunk ) {
103 *offset = chunks_len;
104 return 0;
105 }
106
107 /* Treat out-of-range chunks as being at the end of the
108 * resource, to allow for length calculation on the final
109 * chunk.
110 */
111 if ( chunk >= chunks ) {
112 *offset = zlen;
113 return 0;
114 }
115
116 /* Otherwise, read the chunk offset */
117 offset_offset = ( ( chunk - 1 ) * offset_len );
118 file->read ( file, &u, ( resource->offset + offset_offset ),
119 offset_len );
120 *offset = ( chunks_len + ( ( offset_len == sizeof ( u.offset_64 ) ) ?
121 u.offset_64 : u.offset_32 ) );
122 if ( *offset > zlen ) {
123 DBG ( "Chunk %d offset lies outside resource\n", chunk );
124 return -1;
125 }
126 return 0;
127 }
128
129 /**
130 * Read chunk from a compressed resource
131 *
132 * @v file Virtual file
133 * @v header WIM header
134 * @v resource Resource
135 * @v chunk Chunk number
136 * @v buf Chunk buffer
137 * @ret rc Return status code
138 */
139 static int wim_chunk ( struct vdisk_file *file, struct wim_header *header,
140 struct wim_resource_header *resource,
141 unsigned int chunk, struct wim_chunk_buffer *buf ) {
142 ssize_t ( * decompress ) ( const void *data, size_t len, void *buf );
143 unsigned int chunks;
144 size_t offset;
145 size_t next_offset;
146 size_t len;
147 size_t expected_out_len;
148 ssize_t out_len;
149 int rc;
150
151 /* Get chunk compressed data offset and length */
152 if ( ( rc = wim_chunk_offset ( file, resource, chunk,
153 &offset ) ) != 0 )
154 return rc;
155 if ( ( rc = wim_chunk_offset ( file, resource, ( chunk + 1 ),
156 &next_offset ) ) != 0 )
157 return rc;
158 len = ( next_offset - offset );
159
160 /* Calculate uncompressed length */
161 assert ( resource->len > 0 );
162 chunks = ( ( resource->len + WIM_CHUNK_LEN - 1 ) / WIM_CHUNK_LEN );
163 expected_out_len = WIM_CHUNK_LEN;
164 if ( chunk >= ( chunks - 1 ) )
165 expected_out_len -= ( -resource->len & ( WIM_CHUNK_LEN - 1 ) );
166
167 /* Read possibly-compressed data */
168 if ( len == expected_out_len ) {
169
170 /* Chunk did not compress; read raw data */
171 file->read ( file, buf->data, ( resource->offset + offset ),
172 len );
173
174 } else {
175 uint8_t zbuf[len];
176
177 /* Read compressed data into a temporary buffer */
178 file->read ( file, zbuf, ( resource->offset + offset ), len );
179
180 /* Identify decompressor */
181 if ( header->flags & WIM_HDR_LZX ) {
182 decompress = lzx_decompress;
183 } else {
184 DBG ( "Can't handle unknown compression scheme %#08x "
185 "for %#llx chunk %d at [%#llx+%#llx)\n",
186 header->flags, resource->offset,
187 chunk, ( resource->offset + offset ),
188 ( resource->offset + offset + len ) );
189 return -1;
190 }
191
192 /* Decompress data */
193 out_len = decompress ( zbuf, len, NULL );
194 if ( out_len < 0 )
195 return out_len;
196 if ( ( ( size_t ) out_len ) != expected_out_len ) {
197 DBG ( "Unexpected output length %#lx (expected %#zx)\n",
198 out_len, expected_out_len );
199 return -1;
200 }
201 decompress ( zbuf, len, buf->data );
202 }
203
204 return 0;
205 }
206
207 /**
208 * Read from a (possibly compressed) resource
209 *
210 * @v file Virtual file
211 * @v header WIM header
212 * @v resource Resource
213 * @v data Data buffer
214 * @v offset Starting offset
215 * @v len Length
216 * @ret rc Return status code
217 */
218 int wim_read ( struct vdisk_file *file, struct wim_header *header,
219 struct wim_resource_header *resource, void *data,
220 size_t offset, size_t len ) {
221 static struct vdisk_file *cached_file;
222 static size_t cached_resource_offset;
223 static unsigned int cached_chunk;
224 size_t zlen = ( resource->zlen__flags & WIM_RESHDR_ZLEN_MASK );
225 unsigned int chunk;
226 size_t skip_len;
227 size_t frag_len;
228 int rc;
229
230 /* Sanity checks */
231 if ( ( offset + len ) > resource->len ) {
232 DBG ( "Resource too short (%#llx bytes)\n", resource->len );
233 return -1;
234 }
235 if ( ( resource->offset + zlen ) > file->len ) {
236 DBG ( "Resource exceeds length of file\n" );
237 return -1;
238 }
239
240 /* If resource is uncompressed, just read the raw data */
241 if ( ! ( resource->zlen__flags & ( WIM_RESHDR_COMPRESSED |
242 WIM_RESHDR_PACKED_STREAMS ) ) ) {
243 file->read ( file, data, ( resource->offset + offset ), len );
244 return 0;
245 }
246
247 /* Read from each chunk overlapping the target region */
248 while ( len ) {
249
250 /* Calculate chunk number */
251 chunk = ( offset / WIM_CHUNK_LEN );
252
253 /* Read chunk, if not already cached */
254 if ( ( file != cached_file ) ||
255 ( resource->offset != cached_resource_offset ) ||
256 ( chunk != cached_chunk ) ) {
257
258 /* Read chunk */
259 if ( ( rc = wim_chunk ( file, header, resource, chunk,
260 &wim_chunk_buffer ) ) != 0 )
261 return rc;
262
263 /* Update cache */
264 cached_file = file;
265 cached_resource_offset = resource->offset;
266 cached_chunk = chunk;
267 }
268
269 /* Copy fragment from this chunk */
270 skip_len = ( offset % WIM_CHUNK_LEN );
271 frag_len = ( WIM_CHUNK_LEN - skip_len );
272 if ( frag_len > len )
273 frag_len = len;
274 memcpy ( data, ( wim_chunk_buffer.data + skip_len ), frag_len );
275
276 /* Move to next chunk */
277 data += frag_len;
278 offset += frag_len;
279 len -= frag_len;
280 }
281
282 return 0;
283 }
284
285 /**
286 * Get number of images
287 *
288 * @v file Virtual file
289 * @v header WIM header
290 * @v count Count of images to fill in
291 * @ret rc Return status code
292 */
293 int wim_count ( struct vdisk_file *file, struct wim_header *header,
294 unsigned int *count ) {
295 struct wim_lookup_entry entry;
296 size_t offset;
297 int rc;
298
299 /* Count metadata entries */
300 for ( offset = 0 ; ( offset + sizeof ( entry ) ) <= header->lookup.len ;
301 offset += sizeof ( entry ) ) {
302
303 /* Read entry */
304 if ( ( rc = wim_read ( file, header, &header->lookup, &entry,
305 offset, sizeof ( entry ) ) ) != 0 )
306 return rc;
307
308 /* Check for metadata entries */
309 if ( entry.resource.zlen__flags & WIM_RESHDR_METADATA ) {
310 (*count)++;
311 DBG2 ( "...found image %d metadata at +%#zx\n",
312 *count, offset );
313 }
314 }
315
316 return 0;
317 }
318
319 /**
320 * Get WIM image metadata
321 *
322 * @v file Virtual file
323 * @v header WIM header
324 * @v index Image index, or 0 to use boot image
325 * @v meta Metadata to fill in
326 * @ret rc Return status code
327 */
328 int wim_metadata ( struct vdisk_file *file, struct wim_header *header,
329 unsigned int index, struct wim_resource_header *meta ) {
330 struct wim_lookup_entry entry;
331 size_t offset;
332 unsigned int found = 0;
333 int rc;
334
335 /* If no image index is specified, just use the boot metadata */
336 if ( index == 0 ) {
337 memcpy ( meta, &header->boot, sizeof ( *meta ) );
338 return 0;
339 }
340
341 /* Look for metadata entry */
342 for ( offset = 0 ; ( offset + sizeof ( entry ) ) <= header->lookup.len ;
343 offset += sizeof ( entry ) ) {
344
345 /* Read entry */
346 if ( ( rc = wim_read ( file, header, &header->lookup, &entry,
347 offset, sizeof ( entry ) ) ) != 0 )
348 return rc;
349
350 /* Look for our target entry */
351 if ( entry.resource.zlen__flags & WIM_RESHDR_METADATA ) {
352 found++;
353 DBG2 ( "...found image %d metadata at +%#zx\n",
354 found, offset );
355 if ( found == index ) {
356 memcpy ( meta, &entry.resource,
357 sizeof ( *meta ) );
358 return 0;
359 }
360 }
361 }
362
363 /* Fail if index was not found */
364 DBG ( "Cannot find WIM image index %d in %s\n", index, file->name );
365 return -1;
366 }
367
368 /**
369 * Get directory entry
370 *
371 * @v file Virtual file
372 * @v header WIM header
373 * @v meta Metadata
374 * @v name Name
375 * @v offset Directory offset (will be updated)
376 * @v direntry Directory entry to fill in
377 * @ret rc Return status code
378 */
379 static int wim_direntry ( struct vdisk_file *file, struct wim_header *header,
380 struct wim_resource_header *meta,
381 const wchar_t *name, size_t *offset,
382 struct wim_directory_entry *direntry ) {
383 wchar_t name_buf[ wcslen ( name ) + 1 /* NUL */ ];
384 int rc;
385
386 /* Search directory */
387 for ( ; ; *offset += direntry->len ) {
388
389 /* Read length field */
390 if ( ( rc = wim_read ( file, header, meta, direntry, *offset,
391 sizeof ( direntry->len ) ) ) != 0 )
392 return rc;
393
394 /* Check for end of this directory */
395 if ( ! direntry->len ) {
396 DBG ( "...directory entry \"%ls\" not found\n", name );
397 return -1;
398 }
399
400 /* Read fixed-length portion of directory entry */
401 if ( ( rc = wim_read ( file, header, meta, direntry, *offset,
402 sizeof ( *direntry ) ) ) != 0 )
403 return rc;
404
405 /* Check name length */
406 if ( direntry->name_len > sizeof ( name_buf ) )
407 continue;
408
409 /* Read name */
410 if ( ( rc = wim_read ( file, header, meta, &name_buf,
411 ( *offset + sizeof ( *direntry ) ),
412 sizeof ( name_buf ) ) ) != 0 )
413 return rc;
414
415 /* Check name */
416 if ( wcscasecmp ( name, name_buf ) != 0 )
417 continue;
418
419 DBG2 ( "...found entry \"%ls\"\n", name );
420 return 0;
421 }
422 }
423
424 /**
425 * Get directory entry for a path
426 *
427 * @v file Virtual file
428 * @v header WIM header
429 * @v meta Metadata
430 * @v path Path to file/directory
431 * @v offset Directory entry offset to fill in
432 * @v direntry Directory entry to fill in
433 * @ret rc Return status code
434 */
435 int wim_path ( struct vdisk_file *file, struct wim_header *header,
436 struct wim_resource_header *meta, const wchar_t *path,
437 size_t *offset, struct wim_directory_entry *direntry ) {
438 wchar_t path_copy[ wcslen ( path ) + 1 /* WNUL */ ];
439 struct wim_security_header security;
440 wchar_t *name;
441 wchar_t *next;
442 int rc;
443
444 /* Read security data header */
445 if ( ( rc = wim_read ( file, header, meta, &security, 0,
446 sizeof ( security ) ) ) != 0 )
447 return rc;
448
449 /* Get root directory offset */
450 direntry->subdir = ( ( security.len + sizeof ( uint64_t ) - 1 ) &
451 ~( sizeof ( uint64_t ) - 1 ) );
452
453 /* Find directory entry */
454 name = memcpy ( path_copy, path, sizeof ( path_copy ) );
455 do {
456 next = wcschr ( name, L'\\' );
457 if ( next )
458 *next = L'\0';
459 *offset = direntry->subdir;
460 if ( ( rc = wim_direntry ( file, header, meta, name, offset,
461 direntry ) ) != 0 )
462 return rc;
463 name = ( next + 1 );
464 } while ( next );
465
466 return 0;
467 }
468
469 /**
470 * Get file resource
471 *
472 * @v file Virtual file
473 * @v header WIM header
474 * @v meta Metadata
475 * @v path Path to file
476 * @v resource File resource to fill in
477 * @ret rc Return status code
478 */
479 int wim_file ( struct vdisk_file *file, struct wim_header *header,
480 struct wim_resource_header *meta, const wchar_t *path,
481 struct wim_resource_header *resource ) {
482 struct wim_directory_entry direntry;
483 struct wim_lookup_entry entry;
484 size_t offset;
485 int rc;
486
487 /* Find directory entry */
488 if ( ( rc = wim_path ( file, header, meta, path, &offset,
489 &direntry ) ) != 0 )
490 return rc;
491
492 /* File matching file entry */
493 for ( offset = 0 ; ( offset + sizeof ( entry ) ) <= header->lookup.len ;
494 offset += sizeof ( entry ) ) {
495
496 /* Read entry */
497 if ( ( rc = wim_read ( file, header, &header->lookup, &entry,
498 offset, sizeof ( entry ) ) ) != 0 )
499 return rc;
500
501 /* Look for our target entry */
502 if ( memcmp ( &entry.hash, &direntry.hash,
503 sizeof ( entry.hash ) ) == 0 ) {
504 DBG ( "...found file \"%ls\"\n", path );
505 memcpy ( resource, &entry.resource,
506 sizeof ( *resource ) );
507 return 0;
508 }
509 }
510
511 DBG ( "Cannot find file %ls\n", path );
512 return -1;
513 }
514
515 /**
516 * Get length of a directory
517 *
518 * @v file Virtual file
519 * @v header WIM header
520 * @v meta Metadata
521 * @v offset Directory offset
522 * @v len Directory length to fill in (excluding terminator)
523 * @ret rc Return status code
524 */
525 int wim_dir_len ( struct vdisk_file *file, struct wim_header *header,
526 struct wim_resource_header *meta, size_t offset,
527 size_t *len ) {
528 struct wim_directory_entry direntry;
529 int rc;
530
531 /* Search directory */
532 for ( *len = 0 ; ; *len += direntry.len ) {
533
534 /* Read length field */
535 if ( ( rc = wim_read ( file, header, meta, &direntry,
536 ( offset + *len ),
537 sizeof ( direntry.len ) ) ) != 0 )
538 return rc;
539
540 /* Check for end of this directory */
541 if ( ! direntry.len )
542 return 0;
543 }
544 }