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
38 /** WIM chunk buffer */
39 static struct wim_chunk_buffer wim_chunk_buffer
;
44 * @v file Virtual file
45 * @v header WIM header to fill in
46 * @ret rc Return status code
48 int wim_header ( struct vdisk_file
*file
, struct wim_header
*header
) {
51 if ( sizeof ( *header
) > file
->len
) {
52 DBG ( "WIM file too short (%#zx bytes)\n", file
->len
);
57 file
->read ( file
, header
, 0, sizeof ( *header
) );
63 * Get compressed chunk offset
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
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
);
84 /* Special case: zero-length files have no chunks */
85 if ( ! resource
->len
) {
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
);
97 if ( chunks_len
> zlen
) {
98 DBG ( "Resource too short for %d chunks\n", chunks
);
102 /* Special case: chunk 0 has no offset field */
104 *offset
= chunks_len
;
108 /* Treat out-of-range chunks as being at the end of the
109 * resource, to allow for length calculation on the final
112 if ( chunk
>= chunks
) {
117 /* Otherwise, read the chunk offset */
118 offset_offset
= ( ( chunk
- 1 ) * offset_len
);
119 file
->read ( file
, &u
, ( resource
->offset
+ offset_offset
),
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
);
131 * Read chunk from a compressed resource
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
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
);
148 size_t expected_out_len
;
152 /* Get chunk compressed data offset and length */
153 if ( ( rc
= wim_chunk_offset ( file
, resource
, chunk
,
156 if ( ( rc
= wim_chunk_offset ( file
, resource
, ( chunk
+ 1 ),
157 &next_offset
) ) != 0 )
159 len
= ( next_offset
- offset
);
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 ) );
168 /* Read possibly-compressed data */
169 if ( len
== expected_out_len
) {
171 /* Chunk did not compress; read raw data */
172 file
->read ( file
, buf
->data
, ( resource
->offset
+ offset
),
178 /* Read compressed data into a temporary buffer */
179 file
->read ( file
, zbuf
, ( resource
->offset
+ offset
), len
);
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
;
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
) );
195 /* Decompress data */
196 out_len
= decompress ( zbuf
, len
, NULL
);
199 if ( ( ( size_t ) out_len
) != expected_out_len
) {
200 DBG ( "Unexpected output length %#lx (expected %#zx)\n",
201 out_len
, expected_out_len
);
204 decompress ( zbuf
, len
, buf
->data
);
211 * Read from a (possibly compressed) resource
213 * @v file Virtual file
214 * @v header WIM header
215 * @v resource Resource
216 * @v data Data buffer
217 * @v offset Starting offset
219 * @ret rc Return status code
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
);
234 if ( ( offset
+ len
) > resource
->len
) {
235 DBG ( "Resource too short (%#llx bytes)\n", resource
->len
);
238 if ( ( resource
->offset
+ zlen
) > file
->len
) {
239 DBG ( "Resource exceeds length of file\n" );
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
);
250 /* Read from each chunk overlapping the target region */
253 /* Calculate chunk number */
254 chunk
= ( offset
/ WIM_CHUNK_LEN
);
256 /* Read chunk, if not already cached */
257 if ( ( file
!= cached_file
) ||
258 ( resource
->offset
!= cached_resource_offset
) ||
259 ( chunk
!= cached_chunk
) ) {
262 if ( ( rc
= wim_chunk ( file
, header
, resource
, chunk
,
263 &wim_chunk_buffer
) ) != 0 )
268 cached_resource_offset
= resource
->offset
;
269 cached_chunk
= chunk
;
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
)
277 memcpy ( data
, ( wim_chunk_buffer
.data
+ skip_len
), frag_len
);
279 /* Move to next chunk */
289 * Get number of images
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
296 int wim_count ( struct vdisk_file
*file
, struct wim_header
*header
,
297 unsigned int *count
) {
298 struct wim_lookup_entry entry
;
302 /* Count metadata entries */
303 for ( offset
= 0 ; ( offset
+ sizeof ( entry
) ) <= header
->lookup
.len
;
304 offset
+= sizeof ( entry
) ) {
307 if ( ( rc
= wim_read ( file
, header
, &header
->lookup
, &entry
,
308 offset
, sizeof ( entry
) ) ) != 0 )
311 /* Check for metadata entries */
312 if ( entry
.resource
.zlen__flags
& WIM_RESHDR_METADATA
) {
314 DBG2 ( "...found image %d metadata at +%#zx\n",
323 * Get WIM image metadata
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
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
;
335 unsigned int found
= 0;
338 /* If no image index is specified, just use the boot metadata */
340 memcpy ( meta
, &header
->boot
, sizeof ( *meta
) );
344 /* Look for metadata entry */
345 for ( offset
= 0 ; ( offset
+ sizeof ( entry
) ) <= header
->lookup
.len
;
346 offset
+= sizeof ( entry
) ) {
349 if ( ( rc
= wim_read ( file
, header
, &header
->lookup
, &entry
,
350 offset
, sizeof ( entry
) ) ) != 0 )
353 /* Look for our target entry */
354 if ( entry
.resource
.zlen__flags
& WIM_RESHDR_METADATA
) {
356 DBG2 ( "...found image %d metadata at +%#zx\n",
358 if ( found
== index
) {
359 memcpy ( meta
, &entry
.resource
,
366 /* Fail if index was not found */
367 DBG ( "Cannot find WIM image index %d in %s\n", index
, file
->name
);
372 * Get directory entry
374 * @v file Virtual file
375 * @v header WIM header
378 * @v offset Directory offset (will be updated)
379 * @v direntry Directory entry to fill in
380 * @ret rc Return status code
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 */ ];
389 /* Search directory */
390 for ( ; ; *offset
+= direntry
->len
) {
392 /* Read length field */
393 if ( ( rc
= wim_read ( file
, header
, meta
, direntry
, *offset
,
394 sizeof ( direntry
->len
) ) ) != 0 )
397 /* Check for end of this directory */
398 if ( ! direntry
->len
) {
399 DBG ( "...directory entry \"%ls\" not found\n", name
);
403 /* Read fixed-length portion of directory entry */
404 if ( ( rc
= wim_read ( file
, header
, meta
, direntry
, *offset
,
405 sizeof ( *direntry
) ) ) != 0 )
408 /* Check name length */
409 if ( direntry
->name_len
> sizeof ( name_buf
) )
413 if ( ( rc
= wim_read ( file
, header
, meta
, &name_buf
,
414 ( *offset
+ sizeof ( *direntry
) ),
415 sizeof ( name_buf
) ) ) != 0 )
419 if ( wcscasecmp ( name
, name_buf
) != 0 )
422 DBG2 ( "...found entry \"%ls\"\n", name
);
428 * Get directory entry for a path
430 * @v file Virtual file
431 * @v header WIM header
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
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
;
447 /* Read security data header */
448 if ( ( rc
= wim_read ( file
, header
, meta
, &security
, 0,
449 sizeof ( security
) ) ) != 0 )
452 /* Get root directory offset */
453 if (security
.len
> 0)
454 direntry
->subdir
= ( ( security
.len
+ sizeof ( uint64_t ) - 1 ) & ~( sizeof ( uint64_t ) - 1 ) );
456 direntry
->subdir
= security
.len
+ 8;
458 /* Find directory entry */
459 name
= memcpy ( path_copy
, path
, sizeof ( path_copy
) );
461 next
= wcschr ( name
, L
'\\' );
464 *offset
= direntry
->subdir
;
465 if ( ( rc
= wim_direntry ( file
, header
, meta
, name
, offset
,
477 * @v file Virtual file
478 * @v header WIM header
480 * @v path Path to file
481 * @v resource File resource to fill in
482 * @ret rc Return status code
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
;
492 /* Find directory entry */
493 if ( ( rc
= wim_path ( file
, header
, meta
, path
, &offset
,
497 /* File matching file entry */
498 for ( offset
= 0 ; ( offset
+ sizeof ( entry
) ) <= header
->lookup
.len
;
499 offset
+= sizeof ( entry
) ) {
502 if ( ( rc
= wim_read ( file
, header
, &header
->lookup
, &entry
,
503 offset
, sizeof ( entry
) ) ) != 0 )
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
) );
516 DBG ( "Cannot find file %ls\n", path
);
521 * Get length of a directory
523 * @v file Virtual file
524 * @v header WIM header
526 * @v offset Directory offset
527 * @v len Directory length to fill in (excluding terminator)
528 * @ret rc Return status code
530 int wim_dir_len ( struct vdisk_file
*file
, struct wim_header
*header
,
531 struct wim_resource_header
*meta
, size_t offset
,
533 struct wim_directory_entry direntry
;
536 /* Search directory */
537 for ( *len
= 0 ; ; *len
+= direntry
.len
) {
539 /* Read length field */
540 if ( ( rc
= wim_read ( file
, header
, meta
, &direntry
,
542 sizeof ( direntry
.len
) ) ) != 0 )
545 /* Check for end of this directory */
546 if ( ! direntry
.len
)