2 * Copyright (C) 2014 Michael Brown <mbrown@fensystems.co.uk>.
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.
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.
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
37 /** WIM chunk buffer */
38 static struct wim_chunk_buffer wim_chunk_buffer
;
43 * @v file Virtual file
44 * @v header WIM header to fill in
45 * @ret rc Return status code
47 int wim_header ( struct vdisk_file
*file
, struct wim_header
*header
) {
50 if ( sizeof ( *header
) > file
->len
) {
51 DBG ( "WIM file too short (%#zx bytes)\n", file
->len
);
56 file
->read ( file
, header
, 0, sizeof ( *header
) );
62 * Get compressed chunk offset
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
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
);
83 /* Special case: zero-length files have no chunks */
84 if ( ! resource
->len
) {
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
);
96 if ( chunks_len
> zlen
) {
97 DBG ( "Resource too short for %d chunks\n", chunks
);
101 /* Special case: chunk 0 has no offset field */
103 *offset
= chunks_len
;
107 /* Treat out-of-range chunks as being at the end of the
108 * resource, to allow for length calculation on the final
111 if ( chunk
>= chunks
) {
116 /* Otherwise, read the chunk offset */
117 offset_offset
= ( ( chunk
- 1 ) * offset_len
);
118 file
->read ( file
, &u
, ( resource
->offset
+ offset_offset
),
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
);
130 * Read chunk from a compressed resource
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
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
);
147 size_t expected_out_len
;
151 /* Get chunk compressed data offset and length */
152 if ( ( rc
= wim_chunk_offset ( file
, resource
, chunk
,
155 if ( ( rc
= wim_chunk_offset ( file
, resource
, ( chunk
+ 1 ),
156 &next_offset
) ) != 0 )
158 len
= ( next_offset
- offset
);
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 ) );
167 /* Read possibly-compressed data */
168 if ( len
== expected_out_len
) {
170 /* Chunk did not compress; read raw data */
171 file
->read ( file
, buf
->data
, ( resource
->offset
+ offset
),
177 /* Read compressed data into a temporary buffer */
178 file
->read ( file
, zbuf
, ( resource
->offset
+ offset
), len
);
180 /* Identify decompressor */
181 if ( header
->flags
& WIM_HDR_LZX
) {
182 decompress
= lzx_decompress
;
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
) );
192 /* Decompress data */
193 out_len
= decompress ( zbuf
, len
, NULL
);
196 if ( ( ( size_t ) out_len
) != expected_out_len
) {
197 DBG ( "Unexpected output length %#lx (expected %#zx)\n",
198 out_len
, expected_out_len
);
201 decompress ( zbuf
, len
, buf
->data
);
208 * Read from a (possibly compressed) resource
210 * @v file Virtual file
211 * @v header WIM header
212 * @v resource Resource
213 * @v data Data buffer
214 * @v offset Starting offset
216 * @ret rc Return status code
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
);
231 if ( ( offset
+ len
) > resource
->len
) {
232 DBG ( "Resource too short (%#llx bytes)\n", resource
->len
);
235 if ( ( resource
->offset
+ zlen
) > file
->len
) {
236 DBG ( "Resource exceeds length of file\n" );
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
);
247 /* Read from each chunk overlapping the target region */
250 /* Calculate chunk number */
251 chunk
= ( offset
/ WIM_CHUNK_LEN
);
253 /* Read chunk, if not already cached */
254 if ( ( file
!= cached_file
) ||
255 ( resource
->offset
!= cached_resource_offset
) ||
256 ( chunk
!= cached_chunk
) ) {
259 if ( ( rc
= wim_chunk ( file
, header
, resource
, chunk
,
260 &wim_chunk_buffer
) ) != 0 )
265 cached_resource_offset
= resource
->offset
;
266 cached_chunk
= chunk
;
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
)
274 memcpy ( data
, ( wim_chunk_buffer
.data
+ skip_len
), frag_len
);
276 /* Move to next chunk */
286 * Get number of images
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
293 int wim_count ( struct vdisk_file
*file
, struct wim_header
*header
,
294 unsigned int *count
) {
295 struct wim_lookup_entry entry
;
299 /* Count metadata entries */
300 for ( offset
= 0 ; ( offset
+ sizeof ( entry
) ) <= header
->lookup
.len
;
301 offset
+= sizeof ( entry
) ) {
304 if ( ( rc
= wim_read ( file
, header
, &header
->lookup
, &entry
,
305 offset
, sizeof ( entry
) ) ) != 0 )
308 /* Check for metadata entries */
309 if ( entry
.resource
.zlen__flags
& WIM_RESHDR_METADATA
) {
311 DBG2 ( "...found image %d metadata at +%#zx\n",
320 * Get WIM image metadata
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
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
;
332 unsigned int found
= 0;
335 /* If no image index is specified, just use the boot metadata */
337 memcpy ( meta
, &header
->boot
, sizeof ( *meta
) );
341 /* Look for metadata entry */
342 for ( offset
= 0 ; ( offset
+ sizeof ( entry
) ) <= header
->lookup
.len
;
343 offset
+= sizeof ( entry
) ) {
346 if ( ( rc
= wim_read ( file
, header
, &header
->lookup
, &entry
,
347 offset
, sizeof ( entry
) ) ) != 0 )
350 /* Look for our target entry */
351 if ( entry
.resource
.zlen__flags
& WIM_RESHDR_METADATA
) {
353 DBG2 ( "...found image %d metadata at +%#zx\n",
355 if ( found
== index
) {
356 memcpy ( meta
, &entry
.resource
,
363 /* Fail if index was not found */
364 DBG ( "Cannot find WIM image index %d in %s\n", index
, file
->name
);
369 * Get directory entry
371 * @v file Virtual file
372 * @v header WIM header
375 * @v offset Directory offset (will be updated)
376 * @v direntry Directory entry to fill in
377 * @ret rc Return status code
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 */ ];
386 /* Search directory */
387 for ( ; ; *offset
+= direntry
->len
) {
389 /* Read length field */
390 if ( ( rc
= wim_read ( file
, header
, meta
, direntry
, *offset
,
391 sizeof ( direntry
->len
) ) ) != 0 )
394 /* Check for end of this directory */
395 if ( ! direntry
->len
) {
396 DBG ( "...directory entry \"%ls\" not found\n", name
);
400 /* Read fixed-length portion of directory entry */
401 if ( ( rc
= wim_read ( file
, header
, meta
, direntry
, *offset
,
402 sizeof ( *direntry
) ) ) != 0 )
405 /* Check name length */
406 if ( direntry
->name_len
> sizeof ( name_buf
) )
410 if ( ( rc
= wim_read ( file
, header
, meta
, &name_buf
,
411 ( *offset
+ sizeof ( *direntry
) ),
412 sizeof ( name_buf
) ) ) != 0 )
416 if ( wcscasecmp ( name
, name_buf
) != 0 )
419 DBG2 ( "...found entry \"%ls\"\n", name
);
425 * Get directory entry for a path
427 * @v file Virtual file
428 * @v header WIM header
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
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
;
444 /* Read security data header */
445 if ( ( rc
= wim_read ( file
, header
, meta
, &security
, 0,
446 sizeof ( security
) ) ) != 0 )
449 /* Get root directory offset */
450 direntry
->subdir
= ( ( security
.len
+ sizeof ( uint64_t ) - 1 ) &
451 ~( sizeof ( uint64_t ) - 1 ) );
453 /* Find directory entry */
454 name
= memcpy ( path_copy
, path
, sizeof ( path_copy
) );
456 next
= wcschr ( name
, L
'\\' );
459 *offset
= direntry
->subdir
;
460 if ( ( rc
= wim_direntry ( file
, header
, meta
, name
, offset
,
472 * @v file Virtual file
473 * @v header WIM header
475 * @v path Path to file
476 * @v resource File resource to fill in
477 * @ret rc Return status code
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
;
487 /* Find directory entry */
488 if ( ( rc
= wim_path ( file
, header
, meta
, path
, &offset
,
492 /* File matching file entry */
493 for ( offset
= 0 ; ( offset
+ sizeof ( entry
) ) <= header
->lookup
.len
;
494 offset
+= sizeof ( entry
) ) {
497 if ( ( rc
= wim_read ( file
, header
, &header
->lookup
, &entry
,
498 offset
, sizeof ( entry
) ) ) != 0 )
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
) );
511 DBG ( "Cannot find file %ls\n", path
);
516 * Get length of a directory
518 * @v file Virtual file
519 * @v header WIM header
521 * @v offset Directory offset
522 * @v len Directory length to fill in (excluding terminator)
523 * @ret rc Return status code
525 int wim_dir_len ( struct vdisk_file
*file
, struct wim_header
*header
,
526 struct wim_resource_header
*meta
, size_t offset
,
528 struct wim_directory_entry direntry
;
531 /* Search directory */
532 for ( *len
= 0 ; ; *len
+= direntry
.len
) {
534 /* Read length field */
535 if ( ( rc
= wim_read ( file
, header
, meta
, &direntry
,
537 sizeof ( direntry
.len
) ) ) != 0 )
540 /* Check for end of this directory */
541 if ( ! direntry
.len
)