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