mirror of
https://github.com/ventoy/Ventoy.git
synced 2025-03-18 17:58:32 -04:00
550 lines
14 KiB
C
550 lines
14 KiB
C
/*
|
|
* Copyright (C) 2014 Michael Brown <mbrown@fensystems.co.uk>.
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License as
|
|
* published by the Free Software Foundation; either version 2 of the
|
|
* License, or (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful, but
|
|
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
|
* 02110-1301, USA.
|
|
*/
|
|
|
|
/**
|
|
* @file
|
|
*
|
|
* WIM images
|
|
*
|
|
*/
|
|
|
|
#include <stddef.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <wchar.h>
|
|
#include <assert.h>
|
|
#include "wimboot.h"
|
|
#include "vdisk.h"
|
|
#include "lzx.h"
|
|
#include "xca.h"
|
|
#include "wim.h"
|
|
|
|
/** WIM chunk buffer */
|
|
static struct wim_chunk_buffer wim_chunk_buffer;
|
|
|
|
/**
|
|
* Get WIM header
|
|
*
|
|
* @v file Virtual file
|
|
* @v header WIM header to fill in
|
|
* @ret rc Return status code
|
|
*/
|
|
int wim_header ( struct vdisk_file *file, struct wim_header *header ) {
|
|
|
|
/* Sanity check */
|
|
if ( sizeof ( *header ) > file->len ) {
|
|
DBG ( "WIM file too short (%#zx bytes)\n", file->len );
|
|
return -1;
|
|
}
|
|
|
|
/* Read WIM header */
|
|
file->read ( file, header, 0, sizeof ( *header ) );
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Get compressed chunk offset
|
|
*
|
|
* @v file Virtual file
|
|
* @v resource Resource
|
|
* @v chunk Chunk number
|
|
* @v offset Offset to fill in
|
|
* @ret rc Return status code
|
|
*/
|
|
static int wim_chunk_offset ( struct vdisk_file *file,
|
|
struct wim_resource_header *resource,
|
|
unsigned int chunk, size_t *offset ) {
|
|
size_t zlen = ( resource->zlen__flags & WIM_RESHDR_ZLEN_MASK );
|
|
unsigned int chunks;
|
|
size_t offset_offset;
|
|
size_t offset_len;
|
|
size_t chunks_len;
|
|
union {
|
|
uint32_t offset_32;
|
|
uint64_t offset_64;
|
|
} u;
|
|
|
|
/* Special case: zero-length files have no chunks */
|
|
if ( ! resource->len ) {
|
|
*offset = 0;
|
|
return 0;
|
|
}
|
|
|
|
/* Calculate chunk parameters */
|
|
chunks = ( ( resource->len + WIM_CHUNK_LEN - 1 ) / WIM_CHUNK_LEN );
|
|
offset_len = ( ( resource->len > 0xffffffffULL ) ?
|
|
sizeof ( u.offset_64 ) : sizeof ( u.offset_32 ) );
|
|
chunks_len = ( ( chunks - 1 ) * offset_len );
|
|
|
|
/* Sanity check */
|
|
if ( chunks_len > zlen ) {
|
|
DBG ( "Resource too short for %d chunks\n", chunks );
|
|
return -1;
|
|
}
|
|
|
|
/* Special case: chunk 0 has no offset field */
|
|
if ( ! chunk ) {
|
|
*offset = chunks_len;
|
|
return 0;
|
|
}
|
|
|
|
/* Treat out-of-range chunks as being at the end of the
|
|
* resource, to allow for length calculation on the final
|
|
* chunk.
|
|
*/
|
|
if ( chunk >= chunks ) {
|
|
*offset = zlen;
|
|
return 0;
|
|
}
|
|
|
|
/* Otherwise, read the chunk offset */
|
|
offset_offset = ( ( chunk - 1 ) * offset_len );
|
|
file->read ( file, &u, ( resource->offset + offset_offset ),
|
|
offset_len );
|
|
*offset = ( chunks_len + ( ( offset_len == sizeof ( u.offset_64 ) ) ?
|
|
u.offset_64 : u.offset_32 ) );
|
|
if ( *offset > zlen ) {
|
|
DBG ( "Chunk %d offset lies outside resource\n", chunk );
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Read chunk from a compressed resource
|
|
*
|
|
* @v file Virtual file
|
|
* @v header WIM header
|
|
* @v resource Resource
|
|
* @v chunk Chunk number
|
|
* @v buf Chunk buffer
|
|
* @ret rc Return status code
|
|
*/
|
|
static int wim_chunk ( struct vdisk_file *file, struct wim_header *header,
|
|
struct wim_resource_header *resource,
|
|
unsigned int chunk, struct wim_chunk_buffer *buf ) {
|
|
ssize_t ( * decompress ) ( const void *data, size_t len, void *buf );
|
|
unsigned int chunks;
|
|
size_t offset;
|
|
size_t next_offset;
|
|
size_t len;
|
|
size_t expected_out_len;
|
|
ssize_t out_len;
|
|
int rc;
|
|
|
|
/* Get chunk compressed data offset and length */
|
|
if ( ( rc = wim_chunk_offset ( file, resource, chunk,
|
|
&offset ) ) != 0 )
|
|
return rc;
|
|
if ( ( rc = wim_chunk_offset ( file, resource, ( chunk + 1 ),
|
|
&next_offset ) ) != 0 )
|
|
return rc;
|
|
len = ( next_offset - offset );
|
|
|
|
/* Calculate uncompressed length */
|
|
assert ( resource->len > 0 );
|
|
chunks = ( ( resource->len + WIM_CHUNK_LEN - 1 ) / WIM_CHUNK_LEN );
|
|
expected_out_len = WIM_CHUNK_LEN;
|
|
if ( chunk >= ( chunks - 1 ) )
|
|
expected_out_len -= ( -resource->len & ( WIM_CHUNK_LEN - 1 ) );
|
|
|
|
/* Read possibly-compressed data */
|
|
if ( len == expected_out_len ) {
|
|
|
|
/* Chunk did not compress; read raw data */
|
|
file->read ( file, buf->data, ( resource->offset + offset ),
|
|
len );
|
|
|
|
} else {
|
|
uint8_t zbuf[len];
|
|
|
|
/* Read compressed data into a temporary buffer */
|
|
file->read ( file, zbuf, ( resource->offset + offset ), len );
|
|
|
|
/* Identify decompressor */
|
|
if ( header->flags & WIM_HDR_LZX ) {
|
|
decompress = lzx_decompress;
|
|
} else if (header->flags & WIM_HDR_XPRESS) {
|
|
decompress = xca_decompress;
|
|
} else {
|
|
DBG ( "Can't handle unknown compression scheme %#08x "
|
|
"for %#llx chunk %d at [%#llx+%#llx)\n",
|
|
header->flags, resource->offset,
|
|
chunk, ( resource->offset + offset ),
|
|
( resource->offset + offset + len ) );
|
|
return -1;
|
|
}
|
|
|
|
/* Decompress data */
|
|
out_len = decompress ( zbuf, len, NULL );
|
|
if ( out_len < 0 )
|
|
return out_len;
|
|
if ( ( ( size_t ) out_len ) != expected_out_len ) {
|
|
DBG ( "Unexpected output length %#lx (expected %#zx)\n",
|
|
out_len, expected_out_len );
|
|
return -1;
|
|
}
|
|
decompress ( zbuf, len, buf->data );
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Read from a (possibly compressed) resource
|
|
*
|
|
* @v file Virtual file
|
|
* @v header WIM header
|
|
* @v resource Resource
|
|
* @v data Data buffer
|
|
* @v offset Starting offset
|
|
* @v len Length
|
|
* @ret rc Return status code
|
|
*/
|
|
int wim_read ( struct vdisk_file *file, struct wim_header *header,
|
|
struct wim_resource_header *resource, void *data,
|
|
size_t offset, size_t len ) {
|
|
static struct vdisk_file *cached_file;
|
|
static size_t cached_resource_offset;
|
|
static unsigned int cached_chunk;
|
|
size_t zlen = ( resource->zlen__flags & WIM_RESHDR_ZLEN_MASK );
|
|
unsigned int chunk;
|
|
size_t skip_len;
|
|
size_t frag_len;
|
|
int rc;
|
|
|
|
/* Sanity checks */
|
|
if ( ( offset + len ) > resource->len ) {
|
|
DBG ( "Resource too short (%#llx bytes)\n", resource->len );
|
|
return -1;
|
|
}
|
|
if ( ( resource->offset + zlen ) > file->len ) {
|
|
DBG ( "Resource exceeds length of file\n" );
|
|
return -1;
|
|
}
|
|
|
|
/* If resource is uncompressed, just read the raw data */
|
|
if ( ! ( resource->zlen__flags & ( WIM_RESHDR_COMPRESSED |
|
|
WIM_RESHDR_PACKED_STREAMS ) ) ) {
|
|
file->read ( file, data, ( resource->offset + offset ), len );
|
|
return 0;
|
|
}
|
|
|
|
/* Read from each chunk overlapping the target region */
|
|
while ( len ) {
|
|
|
|
/* Calculate chunk number */
|
|
chunk = ( offset / WIM_CHUNK_LEN );
|
|
|
|
/* Read chunk, if not already cached */
|
|
if ( ( file != cached_file ) ||
|
|
( resource->offset != cached_resource_offset ) ||
|
|
( chunk != cached_chunk ) ) {
|
|
|
|
/* Read chunk */
|
|
if ( ( rc = wim_chunk ( file, header, resource, chunk,
|
|
&wim_chunk_buffer ) ) != 0 )
|
|
return rc;
|
|
|
|
/* Update cache */
|
|
cached_file = file;
|
|
cached_resource_offset = resource->offset;
|
|
cached_chunk = chunk;
|
|
}
|
|
|
|
/* Copy fragment from this chunk */
|
|
skip_len = ( offset % WIM_CHUNK_LEN );
|
|
frag_len = ( WIM_CHUNK_LEN - skip_len );
|
|
if ( frag_len > len )
|
|
frag_len = len;
|
|
memcpy ( data, ( wim_chunk_buffer.data + skip_len ), frag_len );
|
|
|
|
/* Move to next chunk */
|
|
data += frag_len;
|
|
offset += frag_len;
|
|
len -= frag_len;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Get number of images
|
|
*
|
|
* @v file Virtual file
|
|
* @v header WIM header
|
|
* @v count Count of images to fill in
|
|
* @ret rc Return status code
|
|
*/
|
|
int wim_count ( struct vdisk_file *file, struct wim_header *header,
|
|
unsigned int *count ) {
|
|
struct wim_lookup_entry entry;
|
|
size_t offset;
|
|
int rc;
|
|
|
|
/* Count metadata entries */
|
|
for ( offset = 0 ; ( offset + sizeof ( entry ) ) <= header->lookup.len ;
|
|
offset += sizeof ( entry ) ) {
|
|
|
|
/* Read entry */
|
|
if ( ( rc = wim_read ( file, header, &header->lookup, &entry,
|
|
offset, sizeof ( entry ) ) ) != 0 )
|
|
return rc;
|
|
|
|
/* Check for metadata entries */
|
|
if ( entry.resource.zlen__flags & WIM_RESHDR_METADATA ) {
|
|
(*count)++;
|
|
DBG2 ( "...found image %d metadata at +%#zx\n",
|
|
*count, offset );
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Get WIM image metadata
|
|
*
|
|
* @v file Virtual file
|
|
* @v header WIM header
|
|
* @v index Image index, or 0 to use boot image
|
|
* @v meta Metadata to fill in
|
|
* @ret rc Return status code
|
|
*/
|
|
int wim_metadata ( struct vdisk_file *file, struct wim_header *header,
|
|
unsigned int index, struct wim_resource_header *meta ) {
|
|
struct wim_lookup_entry entry;
|
|
size_t offset;
|
|
unsigned int found = 0;
|
|
int rc;
|
|
|
|
/* If no image index is specified, just use the boot metadata */
|
|
if ( index == 0 ) {
|
|
memcpy ( meta, &header->boot, sizeof ( *meta ) );
|
|
return 0;
|
|
}
|
|
|
|
/* Look for metadata entry */
|
|
for ( offset = 0 ; ( offset + sizeof ( entry ) ) <= header->lookup.len ;
|
|
offset += sizeof ( entry ) ) {
|
|
|
|
/* Read entry */
|
|
if ( ( rc = wim_read ( file, header, &header->lookup, &entry,
|
|
offset, sizeof ( entry ) ) ) != 0 )
|
|
return rc;
|
|
|
|
/* Look for our target entry */
|
|
if ( entry.resource.zlen__flags & WIM_RESHDR_METADATA ) {
|
|
found++;
|
|
DBG2 ( "...found image %d metadata at +%#zx\n",
|
|
found, offset );
|
|
if ( found == index ) {
|
|
memcpy ( meta, &entry.resource,
|
|
sizeof ( *meta ) );
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Fail if index was not found */
|
|
DBG ( "Cannot find WIM image index %d in %s\n", index, file->name );
|
|
return -1;
|
|
}
|
|
|
|
/**
|
|
* Get directory entry
|
|
*
|
|
* @v file Virtual file
|
|
* @v header WIM header
|
|
* @v meta Metadata
|
|
* @v name Name
|
|
* @v offset Directory offset (will be updated)
|
|
* @v direntry Directory entry to fill in
|
|
* @ret rc Return status code
|
|
*/
|
|
static int wim_direntry ( struct vdisk_file *file, struct wim_header *header,
|
|
struct wim_resource_header *meta,
|
|
const wchar_t *name, size_t *offset,
|
|
struct wim_directory_entry *direntry ) {
|
|
wchar_t name_buf[ wcslen ( name ) + 1 /* NUL */ ];
|
|
int rc;
|
|
|
|
/* Search directory */
|
|
for ( ; ; *offset += direntry->len ) {
|
|
|
|
/* Read length field */
|
|
if ( ( rc = wim_read ( file, header, meta, direntry, *offset,
|
|
sizeof ( direntry->len ) ) ) != 0 )
|
|
return rc;
|
|
|
|
/* Check for end of this directory */
|
|
if ( ! direntry->len ) {
|
|
DBG ( "...directory entry \"%ls\" not found\n", name );
|
|
return -1;
|
|
}
|
|
|
|
/* Read fixed-length portion of directory entry */
|
|
if ( ( rc = wim_read ( file, header, meta, direntry, *offset,
|
|
sizeof ( *direntry ) ) ) != 0 )
|
|
return rc;
|
|
|
|
/* Check name length */
|
|
if ( direntry->name_len > sizeof ( name_buf ) )
|
|
continue;
|
|
|
|
/* Read name */
|
|
if ( ( rc = wim_read ( file, header, meta, &name_buf,
|
|
( *offset + sizeof ( *direntry ) ),
|
|
sizeof ( name_buf ) ) ) != 0 )
|
|
return rc;
|
|
|
|
/* Check name */
|
|
if ( wcscasecmp ( name, name_buf ) != 0 )
|
|
continue;
|
|
|
|
DBG2 ( "...found entry \"%ls\"\n", name );
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get directory entry for a path
|
|
*
|
|
* @v file Virtual file
|
|
* @v header WIM header
|
|
* @v meta Metadata
|
|
* @v path Path to file/directory
|
|
* @v offset Directory entry offset to fill in
|
|
* @v direntry Directory entry to fill in
|
|
* @ret rc Return status code
|
|
*/
|
|
int wim_path ( struct vdisk_file *file, struct wim_header *header,
|
|
struct wim_resource_header *meta, const wchar_t *path,
|
|
size_t *offset, struct wim_directory_entry *direntry ) {
|
|
wchar_t path_copy[ wcslen ( path ) + 1 /* WNUL */ ];
|
|
struct wim_security_header security;
|
|
wchar_t *name;
|
|
wchar_t *next;
|
|
int rc;
|
|
|
|
/* Read security data header */
|
|
if ( ( rc = wim_read ( file, header, meta, &security, 0,
|
|
sizeof ( security ) ) ) != 0 )
|
|
return rc;
|
|
|
|
/* Get root directory offset */
|
|
if (security.len > 0)
|
|
direntry->subdir = ( ( security.len + sizeof ( uint64_t ) - 1 ) & ~( sizeof ( uint64_t ) - 1 ) );
|
|
else
|
|
direntry->subdir = security.len + 8;
|
|
|
|
/* Find directory entry */
|
|
name = memcpy ( path_copy, path, sizeof ( path_copy ) );
|
|
do {
|
|
next = wcschr ( name, L'\\' );
|
|
if ( next )
|
|
*next = L'\0';
|
|
*offset = direntry->subdir;
|
|
if ( ( rc = wim_direntry ( file, header, meta, name, offset,
|
|
direntry ) ) != 0 )
|
|
return rc;
|
|
name = ( next + 1 );
|
|
} while ( next );
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Get file resource
|
|
*
|
|
* @v file Virtual file
|
|
* @v header WIM header
|
|
* @v meta Metadata
|
|
* @v path Path to file
|
|
* @v resource File resource to fill in
|
|
* @ret rc Return status code
|
|
*/
|
|
int wim_file ( struct vdisk_file *file, struct wim_header *header,
|
|
struct wim_resource_header *meta, const wchar_t *path,
|
|
struct wim_resource_header *resource ) {
|
|
struct wim_directory_entry direntry;
|
|
struct wim_lookup_entry entry;
|
|
size_t offset;
|
|
int rc;
|
|
|
|
/* Find directory entry */
|
|
if ( ( rc = wim_path ( file, header, meta, path, &offset,
|
|
&direntry ) ) != 0 )
|
|
return rc;
|
|
|
|
/* File matching file entry */
|
|
for ( offset = 0 ; ( offset + sizeof ( entry ) ) <= header->lookup.len ;
|
|
offset += sizeof ( entry ) ) {
|
|
|
|
/* Read entry */
|
|
if ( ( rc = wim_read ( file, header, &header->lookup, &entry,
|
|
offset, sizeof ( entry ) ) ) != 0 )
|
|
return rc;
|
|
|
|
/* Look for our target entry */
|
|
if ( memcmp ( &entry.hash, &direntry.hash,
|
|
sizeof ( entry.hash ) ) == 0 ) {
|
|
DBG ( "...found file \"%ls\"\n", path );
|
|
memcpy ( resource, &entry.resource,
|
|
sizeof ( *resource ) );
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
DBG ( "Cannot find file %ls\n", path );
|
|
return -1;
|
|
}
|
|
|
|
/**
|
|
* Get length of a directory
|
|
*
|
|
* @v file Virtual file
|
|
* @v header WIM header
|
|
* @v meta Metadata
|
|
* @v offset Directory offset
|
|
* @v len Directory length to fill in (excluding terminator)
|
|
* @ret rc Return status code
|
|
*/
|
|
int wim_dir_len ( struct vdisk_file *file, struct wim_header *header,
|
|
struct wim_resource_header *meta, size_t offset,
|
|
size_t *len ) {
|
|
struct wim_directory_entry direntry;
|
|
int rc;
|
|
|
|
/* Search directory */
|
|
for ( *len = 0 ; ; *len += direntry.len ) {
|
|
|
|
/* Read length field */
|
|
if ( ( rc = wim_read ( file, header, meta, &direntry,
|
|
( offset + *len ),
|
|
sizeof ( direntry.len ) ) ) != 0 )
|
|
return rc;
|
|
|
|
/* Check for end of this directory */
|
|
if ( ! direntry.len )
|
|
return 0;
|
|
}
|
|
}
|