Add MiniVHD to 86Box.

This commit is contained in:
Stephen McKinney 2020-11-16 23:38:46 -06:00
parent 48883a46cd
commit 18db2af1eb
20 changed files with 4940 additions and 2 deletions

1
.gitignore vendored
View File

@ -9,3 +9,4 @@ src/*.dmp
src/NUL
src/nvr/
src/roms/
/.vs

View File

@ -0,0 +1,12 @@
# Credits
MiniVHD Copyright (c) 2019 Sherman Perry
MiniVHD was made possible with the help of the following projects
### libxml2
**Project Home:** http://www.xmlsoft.org/
**License:** MIT (see src/libxml2_encoding.c for details)
### cwalk
**Project Home:** https://likle.github.io/cwalk/
**Licence:** MIT (https://github.com/likle/cwalk/blob/master/LICENSE.md)

21
src/disk/minivhd/LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2019 Sherman Perry
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

1421
src/disk/minivhd/cwalk.c Normal file

File diff suppressed because it is too large Load Diff

457
src/disk/minivhd/cwalk.h Normal file
View File

@ -0,0 +1,457 @@
#pragma once
#ifndef CWK_LIBRARY_H
#define CWK_LIBRARY_H
#include <stdbool.h>
#include <stddef.h>
/**
* A segment represents a single component of a path. For instance, on linux a
* path might look like this "/var/log/", which consists of two segments "var"
* and "log".
*/
struct cwk_segment
{
const char *path;
const char *segments;
const char *begin;
const char *end;
size_t size;
};
/**
* The segment type can be used to identify whether a segment is a special
* segment or not.
*
* CWK_NORMAL - normal folder or file segment
* CWK_CURRENT - "./" current folder segment
* CWK_BACK - "../" relative back navigation segment
*/
enum cwk_segment_type
{
CWK_NORMAL,
CWK_CURRENT,
CWK_BACK
};
/**
* @brief Determines the style which is used for the path parsing and
* generation.
*/
enum cwk_path_style
{
CWK_STYLE_WINDOWS,
CWK_STYLE_UNIX
};
/**
* @brief Generates an absolute path based on a base.
*
* This function generates an absolute path based on a base path and another
* path. It is guaranteed to return an absolute path. If the second submitted
* path is absolute, it will override the base path. The result will be written
* to a buffer, which might be truncated if the buffer is not large enough to
* hold the full path. However, the truncated result will always be
* null-terminated. The returned value is the amount of characters which the
* resulting path would take if it was not truncated (excluding the
* null-terminating character).
*
* @param base The base path on which the relative path will be applied.
* @param path The relative path which will be applied on the base path.
* @param buffer The buffer where the result will be written to.
* @param buffer_size The size of the result buffer.
* @return Returns the total amount of characters of the new absolute path.
*/
size_t cwk_path_get_absolute(const char *base, const char *path, char *buffer,
size_t buffer_size);
/**
* @brief Generates a relative path based on a base.
*
* This function generates a relative path based on a base path and another
* path. It determines how to get to the submitted path, starting from the base
* directory. The result will be written to a buffer, which might be truncated
* if the buffer is not large enough to hold the full path. However, the
* truncated result will always be null-terminated. The returned value is the
* amount of characters which the resulting path would take if it was not
* truncated (excluding the null-terminating character).
*
* @param base_directory The base path from which the relative path will start.
* @param path The target path where the relative path will point to.
* @param buffer The buffer where the result will be written to.
* @param buffer_size The size of the result buffer.
* @return Returns the total amount of characters of the full path.
*/
size_t cwk_path_get_relative(const char *base_directory, const char *path,
char *buffer, size_t buffer_size);
/**
* @brief Joins two paths together.
*
* This function generates a new path by combining the two submitted paths. It
* will remove double separators, and unlike cwk_path_get_absolute it permits
* the use of two relative paths to combine. The result will be written to a
* buffer, which might be truncated if the buffer is not large enough to hold
* the full path. However, the truncated result will always be null-terminated.
* The returned value is the amount of characters which the resulting path would
* take if it was not truncated (excluding the null-terminating character).
*
* @param path_a The first path which comes first.
* @param path_b The second path which comes after the first.
* @param buffer The buffer where the result will be written to.
* @param buffer_size The size of the result buffer.
* @return Returns the total amount of characters of the full, combined path.
*/
size_t cwk_path_join(const char *path_a, const char *path_b, char *buffer,
size_t buffer_size);
/**
* @brief Joins multiple paths together.
*
* This function generates a new path by joining multiple paths together. It
* will remove double separators, and unlike cwk_path_get_absolute it permits
* the use of multiple relative paths to combine. The last path of the submitted
* string array must be set to NULL. The result will be written to a buffer,
* which might be truncated if the buffer is not large enough to hold the full
* path. However, the truncated result will always be null-terminated. The
* returned value is the amount of characters which the resulting path would
* take if it was not truncated (excluding the null-terminating character).
*
* @param paths An array of paths which will be joined.
* @param buffer The buffer where the result will be written to.
* @param buffer_size The size of the result buffer.
* @return Returns the total amount of characters of the full, combined path.
*/
size_t cwk_path_join_multiple(const char **paths, char *buffer,
size_t buffer_size);
/**
* @brief Determines the root of a path.
*
* This function determines the root of a path by finding it's length. The root
* always starts at the submitted path. If the path has no root, the length will
* be set to zero.
*
* @param path The path which will be inspected.
* @param length The output of the root length.
*/
void cwk_path_get_root(const char *path, size_t *length);
/**
* @brief Changes the root of a path.
*
* This function changes the root of a path. It does not normalize the result.
* The result will be written to a buffer, which might be truncated if the
* buffer is not large enough to hold the full path. However, the truncated
* result will always be null-terminated. The returned value is the amount of
* characters which the resulting path would take if it was not truncated
* (excluding the null-terminating character).
*
* @param path The original path which will get a new root.
* @param new_root The new root which will be placed in the path.
* @param buffer The output buffer where the result is written to.
* @param buffer_size The size of the output buffer where the result is written
* to.
* @return Returns the total amount of characters of the new path.
*/
size_t cwk_path_change_root(const char *path, const char *new_root,
char *buffer, size_t buffer_size);
/**
* @brief Determine whether the path is absolute or not.
*
* This function checks whether the path is an absolute path or not. A path is
* considered to be absolute if the root ends with a separator.
*
* @param path The path which will be checked.
* @return Returns true if the path is absolute or false otherwise.
*/
bool cwk_path_is_absolute(const char *path);
/**
* @brief Determine whether the path is relative or not.
*
* This function checks whether the path is a relative path or not. A path is
* considered to be relative if the root does not end with a separator.
*
* @param path The path which will be checked.
* @return Returns true if the path is relative or false otherwise.
*/
bool cwk_path_is_relative(const char *path);
/**
* @brief Gets the basename of a file path.
*
* This function gets the basename of a file path. A pointer to the beginning of
* the basename will be returned through the basename parameter. This pointer
* will be positioned on the first letter after the separator. The length of the
* file path will be returned through the length parameter. The length will be
* set to zero and the basename to NULL if there is no basename available.
*
* @param path The path which will be inspected.
* @param basename The output of the basename pointer.
* @param length The output of the length of the basename.
*/
void cwk_path_get_basename(const char *path, const char **basename,
size_t *length);
/**
* @brief Changes the basename of a file path.
*
* This function changes the basename of a file path. This function will not
* write out more than the specified buffer can contain. However, the generated
* string is always null-terminated - even if not the whole path is written out.
* The function returns the total number of characters the complete buffer would
* have, even if it was not written out completely. The path may be the same
* memory address as the buffer.
*
* @param path The original path which will be used for the modified path.
* @param new_basename The new basename which will replace the old one.
* @param buffer The buffer where the changed path will be written to.
* @param buffer_size The size of the result buffer where the changed path is
* written to.
* @return Returns the size which the complete new path would have if it was not
* truncated.
*/
size_t cwk_path_change_basename(const char *path, const char *new_basename,
char *buffer, size_t buffer_size);
/**
* @brief Gets the dirname of a file path.
*
* This function determines the dirname of a file path and returns the length up
* to which character is considered to be part of it. If no dirname is found,
* the length will be set to zero. The beginning of the dirname is always equal
* to the submitted path pointer.
*
* @param path The path which will be inspected.
* @param length The length of the dirname.
*/
void cwk_path_get_dirname(const char *path, size_t *length);
/**
* @brief Gets the extension of a file path.
*
* This function extracts the extension portion of a file path. A pointer to
* the beginning of the extension will be returned through the extension
* parameter if an extension is found and true is returned. This pointer will be
* positioned on the dot. The length of the extension name will be returned
* through the length parameter. If no extension is found both parameters won't
* be touched and false will be returned.
*
* @param path The path which will be inspected.
* @param extension The output of the extension pointer.
* @param length The output of the length of the extension.
* @return Returns true if an extension is found or false otherwise.
*/
bool cwk_path_get_extension(const char *path, const char **extension,
size_t *length);
/**
* @brief Determines whether the file path has an extension.
*
* This function determines whether the submitted file path has an extension.
* This will evaluate to true if the last segment of the path contains a dot.
*
* @param path The path which will be inspected.
* @return Returns true if the path has an extension or false otherwise.
*/
bool cwk_path_has_extension(const char *path);
/**
* @brief Changes the extension of a file path.
*
* This function changes the extension of a file name. The function will append
* an extension if the basename does not have an extension, or use the extension
* as a basename if the path does not have a basename. This function will not
* write out more than the specified buffer can contain. However, the generated
* string is always null-terminated - even if not the whole path is written out.
* The function returns the total number of characters the complete buffer would
* have, even if it was not written out completely. The path may be the same
* memory address as the buffer.
*
* @param path The path which will be used to make the change.
* @param new_extension The extension which will be placed within the new path.
* @param buffer The output buffer where the result will be written to.
* @param buffer_size The size of the output buffer where the result will be
* written to.
* @return Returns the total size which the output would have if it was not
* truncated.
*/
size_t cwk_path_change_extension(const char *path, const char *new_extension,
char *buffer, size_t buffer_size);
/**
* @brief Creates a normalized version of the path.
*
* This function creates a normalized version of the path within the specified
* buffer. This function will not write out more than the specified buffer can
* contain. However, the generated string is always null-terminated - even if
* not the whole path is written out. The function returns the total number of
* characters the complete buffer would have, even if it was not written out
* completely. The path may be the same memory address as the buffer.
*
* The following will be true for the normalized path:
* 1) "../" will be resolved.
* 2) "./" will be removed.
* 3) double separators will be fixed with a single separator.
* 4) separator suffixes will be removed.
*
* @param path The path which will be normalized.
* @param buffer The buffer where the new path is written to.
* @param buffer_size The size of the buffer.
* @return The size which the complete normalized path has if it was not
* truncated.
*/
size_t cwk_path_normalize(const char *path, char *buffer, size_t buffer_size);
/**
* @brief Finds common portions in two paths.
*
* This function finds common portions in two paths and returns the number
* characters from the beginning of the base path which are equal to the other
* path.
*
* @param path_base The base path which will be compared with the other path.
* @param path_other The other path which will compared with the base path.
* @return Returns the number of characters which are common in the base path.
*/
size_t cwk_path_get_intersection(const char *path_base, const char *path_other);
/**
* @brief Gets the first segment of a path.
*
* This function finds the first segment of a path. The position of the segment
* is set to the first character after the separator, and the length counts all
* characters until the next separator (excluding the separator).
*
* @param path The path which will be inspected.
* @param segment The segment which will be extracted.
* @return Returns true if there is a segment or false if there is none.
*/
bool cwk_path_get_first_segment(const char *path, struct cwk_segment *segment);
/**
* @brief Gets the last segment of the path.
*
* This function gets the last segment of a path. This function may return false
* if the path doesn't contain any segments, in which case the submitted segment
* parameter is not modified. The position of the segment is set to the first
* character after the separator, and the length counts all characters until the
* end of the path (excluding the separator).
*
* @param path The path which will be inspected.
* @param segment The segment which will be extracted.
* @return Returns true if there is a segment or false if there is none.
*/
bool cwk_path_get_last_segment(const char *path, struct cwk_segment *segment);
/**
* @brief Advances to the next segment.
*
* This function advances the current segment to the next segment. If there are
* no more segments left, the submitted segment structure will stay unchanged
* and false is returned.
*
* @param segment The current segment which will be advanced to the next one.
* @return Returns true if another segment was found or false otherwise.
*/
bool cwk_path_get_next_segment(struct cwk_segment *segment);
/**
* @brief Moves to the previous segment.
*
* This function moves the current segment to the previous segment. If the
* current segment is the first one, the submitted segment structure will stay
* unchanged and false is returned.
*
* @param segment The current segment which will be moved to the previous one.
* @return Returns true if there is a segment before this one or false
* otherwise.
*/
bool cwk_path_get_previous_segment(struct cwk_segment *segment);
/**
* @brief Gets the type of the submitted path segment.
*
* This function inspects the contents of the segment and determines the type of
* it. Currently, there are three types CWK_NORMAL, CWK_CURRENT and CWK_BACK. A
* CWK_NORMAL segment is a normal folder or file entry. A CWK_CURRENT is a "./"
* and a CWK_BACK a "../" segment.
*
* @param segment The segment which will be inspected.
* @return Returns the type of the segment.
*/
enum cwk_segment_type cwk_path_get_segment_type(
const struct cwk_segment *segment);
/**
* @brief Changes the content of a segment.
*
* This function overrides the content of a segment to the submitted value and
* outputs the whole new path to the submitted buffer. The result might require
* less or more space than before if the new value length differs from the
* original length. The output is truncated if the new path is larger than the
* submitted buffer size, but it is always null-terminated. The source of the
* segment and the submitted buffer may be the same.
*
* @param segment The segment which will be modifier.
* @param value The new content of the segment.
* @param buffer The buffer where the modified path will be written to.
* @param buffer_size The size of the output buffer.
* @return Returns the total size which would have been written if the output
* was not truncated.
*/
size_t cwk_path_change_segment(struct cwk_segment *segment, const char *value,
char *buffer, size_t buffer_size);
/**
* @brief Checks whether the submitted pointer points to a separator.
*
* This function simply checks whether the submitted pointer points to a
* separator, which has to be null-terminated (but not necessarily after the
* separator). The function will return true if it is a separator, or false
* otherwise.
*
* @param symbol A pointer to a string.
* @return Returns true if it is a separator, or false otherwise.
*/
bool cwk_path_is_separator(const char *str);
/**
* @brief Guesses the path style.
*
* This function guesses the path style based on a submitted path-string. The
* guessing will look at the root and the type of slashes contained in the path
* and return the style which is more likely used in the path.
*
* @param path The path which will be inspected.
* @return Returns the style which is most likely used for the path.
*/
enum cwk_path_style cwk_path_guess_style(const char *path);
/**
* @brief Configures which path style is used.
*
* This function configures which path style is used. The following styles are
* currently supported.
*
* CWK_STYLE_WINDOWS: Use backslashes as a separator and volume for the root.
* CWK_STYLE_UNIX: Use slashes as a separator and a slash for the root.
*
* @param style The style which will be used from now on.
*/
void cwk_path_set_style(enum cwk_path_style style);
/**
* @brief Gets the path style configuration.
*
* This function gets the style configuration which is currently used for the
* paths. This configuration determines how paths are parsed and generated.
*
* @return Returns the current path style configuration.
*/
enum cwk_path_style cwk_path_get_style(void);
#endif

View File

@ -0,0 +1,447 @@
/*
* encoding.c : implements the encoding conversion functions needed for XML
*
* Related specs:
* rfc2044 (UTF-8 and UTF-16) F. Yergeau Alis Technologies
* rfc2781 UTF-16, an encoding of ISO 10646, P. Hoffman, F. Yergeau
* [ISO-10646] UTF-8 and UTF-16 in Annexes
* [ISO-8859-1] ISO Latin-1 characters codes.
* [UNICODE] The Unicode Consortium, "The Unicode Standard --
* Worldwide Character Encoding -- Version 1.0", Addison-
* Wesley, Volume 1, 1991, Volume 2, 1992. UTF-8 is
* described in Unicode Technical Report #4.
* [US-ASCII] Coded Character Set--7-bit American Standard Code for
* Information Interchange, ANSI X3.4-1986.
*
* See Copyright for the status of this software.
*
* daniel@veillard.com
*
* Original code for IsoLatin1 and UTF-16 by "Martin J. Duerst" <duerst@w3.org>
*
* Adapted and abridged for MiniVHD by Sherman Perry
*/
#include <stdlib.h>
static int xmlLittleEndian = 1;
/* Note: extracted from original 'void xmlInitCharEncodingHandlers(void)' function */
void xmlEncodingInit(void)
{
unsigned short int tst = 0x1234;
unsigned char *ptr = (unsigned char *) &tst;
if (*ptr == 0x12) xmlLittleEndian = 0;
else if (*ptr == 0x34) xmlLittleEndian = 1;
}
/**
* UTF16LEToUTF8:
* @out: a pointer to an array of bytes to store the result
* @outlen: the length of @out
* @inb: a pointer to an array of UTF-16LE passwd as a byte array
* @inlenb: the length of @in in UTF-16LE chars
*
* Take a block of UTF-16LE ushorts in and try to convert it to an UTF-8
* block of chars out. This function assumes the endian property
* is the same between the native type of this machine and the
* inputed one.
*
* Returns the number of bytes written, or -1 if lack of space, or -2
* if the transcoding fails (if *in is not a valid utf16 string)
* The value of *inlen after return is the number of octets consumed
* if the return value is positive, else unpredictable.
*/
int UTF16LEToUTF8(unsigned char* out, int *outlen,
const unsigned char* inb, int *inlenb)
{
unsigned char* outstart = out;
const unsigned char* processed = inb;
unsigned char* outend = out + *outlen;
unsigned short* in = (unsigned short*) inb;
unsigned short* inend;
unsigned int c, d, inlen;
unsigned char *tmp;
int bits;
if ((*inlenb % 2) == 1)
(*inlenb)--;
inlen = *inlenb / 2;
inend = in + inlen;
while ((in < inend) && (out - outstart + 5 < *outlen)) {
if (xmlLittleEndian) {
c= *in++;
} else {
tmp = (unsigned char *) in;
c = *tmp++;
c = c | (((unsigned int)*tmp) << 8);
in++;
}
if ((c & 0xFC00) == 0xD800) { /* surrogates */
if (in >= inend) { /* (in > inend) shouldn't happens */
break;
}
if (xmlLittleEndian) {
d = *in++;
} else {
tmp = (unsigned char *) in;
d = *tmp++;
d = d | (((unsigned int)*tmp) << 8);
in++;
}
if ((d & 0xFC00) == 0xDC00) {
c &= 0x03FF;
c <<= 10;
c |= d & 0x03FF;
c += 0x10000;
}
else {
*outlen = out - outstart;
*inlenb = processed - inb;
return(-2);
}
}
/* assertion: c is a single UTF-4 value */
if (out >= outend)
break;
if (c < 0x80) { *out++= c; bits= -6; }
else if (c < 0x800) { *out++= ((c >> 6) & 0x1F) | 0xC0; bits= 0; }
else if (c < 0x10000) { *out++= ((c >> 12) & 0x0F) | 0xE0; bits= 6; }
else { *out++= ((c >> 18) & 0x07) | 0xF0; bits= 12; }
for ( ; bits >= 0; bits-= 6) {
if (out >= outend)
break;
*out++= ((c >> bits) & 0x3F) | 0x80;
}
processed = (const unsigned char*) in;
}
*outlen = out - outstart;
*inlenb = processed - inb;
return(*outlen);
}
/**
* UTF8ToUTF16LE:
* @outb: a pointer to an array of bytes to store the result
* @outlen: the length of @outb
* @in: a pointer to an array of UTF-8 chars
* @inlen: the length of @in
*
* Take a block of UTF-8 chars in and try to convert it to an UTF-16LE
* block of chars out.
*
* Returns the number of bytes written, or -1 if lack of space, or -2
* if the transcoding failed.
*/
int UTF8ToUTF16LE(unsigned char* outb, int *outlen,
const unsigned char* in, int *inlen)
{
unsigned short* out = (unsigned short*) outb;
const unsigned char* processed = in;
const unsigned char *const instart = in;
unsigned short* outstart= out;
unsigned short* outend;
const unsigned char* inend;
unsigned int c, d;
int trailing;
unsigned char *tmp;
unsigned short tmp1, tmp2;
/* UTF16LE encoding has no BOM */
if ((out == NULL) || (outlen == NULL) || (inlen == NULL)) return(-1);
if (in == NULL) {
*outlen = 0;
*inlen = 0;
return(0);
}
inend= in + *inlen;
outend = out + (*outlen / 2);
while (in < inend) {
d= *in++;
if (d < 0x80) { c= d; trailing= 0; }
else if (d < 0xC0) {
/* trailing byte in leading position */
*outlen = (out - outstart) * 2;
*inlen = processed - instart;
return(-2);
} else if (d < 0xE0) { c= d & 0x1F; trailing= 1; }
else if (d < 0xF0) { c= d & 0x0F; trailing= 2; }
else if (d < 0xF8) { c= d & 0x07; trailing= 3; }
else {
/* no chance for this in UTF-16 */
*outlen = (out - outstart) * 2;
*inlen = processed - instart;
return(-2);
}
if (inend - in < trailing) {
break;
}
for ( ; trailing; trailing--) {
if ((in >= inend) || (((d= *in++) & 0xC0) != 0x80))
break;
c <<= 6;
c |= d & 0x3F;
}
/* assertion: c is a single UTF-4 value */
if (c < 0x10000) {
if (out >= outend)
break;
if (xmlLittleEndian) {
*out++ = c;
} else {
tmp = (unsigned char *) out;
*tmp = c ;
*(tmp + 1) = c >> 8 ;
out++;
}
}
else if (c < 0x110000) {
if (out+1 >= outend)
break;
c -= 0x10000;
if (xmlLittleEndian) {
*out++ = 0xD800 | (c >> 10);
*out++ = 0xDC00 | (c & 0x03FF);
} else {
tmp1 = 0xD800 | (c >> 10);
tmp = (unsigned char *) out;
*tmp = (unsigned char) tmp1;
*(tmp + 1) = tmp1 >> 8;
out++;
tmp2 = 0xDC00 | (c & 0x03FF);
tmp = (unsigned char *) out;
*tmp = (unsigned char) tmp2;
*(tmp + 1) = tmp2 >> 8;
out++;
}
}
else
break;
processed = in;
}
*outlen = (out - outstart) * 2;
*inlen = processed - instart;
return(*outlen);
}
/**
* UTF16BEToUTF8:
* @out: a pointer to an array of bytes to store the result
* @outlen: the length of @out
* @inb: a pointer to an array of UTF-16 passed as a byte array
* @inlenb: the length of @in in UTF-16 chars
*
* Take a block of UTF-16 ushorts in and try to convert it to an UTF-8
* block of chars out. This function assumes the endian property
* is the same between the native type of this machine and the
* inputed one.
*
* Returns the number of bytes written, or -1 if lack of space, or -2
* if the transcoding fails (if *in is not a valid utf16 string)
* The value of *inlen after return is the number of octets consumed
* if the return value is positive, else unpredictable.
*/
int UTF16BEToUTF8(unsigned char* out, int *outlen,
const unsigned char* inb, int *inlenb)
{
unsigned char* outstart = out;
const unsigned char* processed = inb;
unsigned char* outend = out + *outlen;
unsigned short* in = (unsigned short*) inb;
unsigned short* inend;
unsigned int c, d, inlen;
unsigned char *tmp;
int bits;
if ((*inlenb % 2) == 1)
(*inlenb)--;
inlen = *inlenb / 2;
inend= in + inlen;
while (in < inend) {
if (xmlLittleEndian) {
tmp = (unsigned char *) in;
c = *tmp++;
c = c << 8;
c = c | (unsigned int) *tmp;
in++;
} else {
c= *in++;
}
if ((c & 0xFC00) == 0xD800) { /* surrogates */
if (in >= inend) { /* (in > inend) shouldn't happens */
*outlen = out - outstart;
*inlenb = processed - inb;
return(-2);
}
if (xmlLittleEndian) {
tmp = (unsigned char *) in;
d = *tmp++;
d = d << 8;
d = d | (unsigned int) *tmp;
in++;
} else {
d= *in++;
}
if ((d & 0xFC00) == 0xDC00) {
c &= 0x03FF;
c <<= 10;
c |= d & 0x03FF;
c += 0x10000;
}
else {
*outlen = out - outstart;
*inlenb = processed - inb;
return(-2);
}
}
/* assertion: c is a single UTF-4 value */
if (out >= outend)
break;
if (c < 0x80) { *out++= c; bits= -6; }
else if (c < 0x800) { *out++= ((c >> 6) & 0x1F) | 0xC0; bits= 0; }
else if (c < 0x10000) { *out++= ((c >> 12) & 0x0F) | 0xE0; bits= 6; }
else { *out++= ((c >> 18) & 0x07) | 0xF0; bits= 12; }
for ( ; bits >= 0; bits-= 6) {
if (out >= outend)
break;
*out++= ((c >> bits) & 0x3F) | 0x80;
}
processed = (const unsigned char*) in;
}
*outlen = out - outstart;
*inlenb = processed - inb;
return(*outlen);
}
/**
* UTF8ToUTF16BE:
* @outb: a pointer to an array of bytes to store the result
* @outlen: the length of @outb
* @in: a pointer to an array of UTF-8 chars
* @inlen: the length of @in
*
* Take a block of UTF-8 chars in and try to convert it to an UTF-16BE
* block of chars out.
*
* Returns the number of byte written, or -1 by lack of space, or -2
* if the transcoding failed.
*/
int UTF8ToUTF16BE(unsigned char* outb, int *outlen,
const unsigned char* in, int *inlen)
{
unsigned short* out = (unsigned short*) outb;
const unsigned char* processed = in;
const unsigned char *const instart = in;
unsigned short* outstart= out;
unsigned short* outend;
const unsigned char* inend;
unsigned int c, d;
int trailing;
unsigned char *tmp;
unsigned short tmp1, tmp2;
/* UTF-16BE has no BOM */
if ((outb == NULL) || (outlen == NULL) || (inlen == NULL)) return(-1);
if (in == NULL) {
*outlen = 0;
*inlen = 0;
return(0);
}
inend= in + *inlen;
outend = out + (*outlen / 2);
while (in < inend) {
d= *in++;
if (d < 0x80) { c= d; trailing= 0; }
else if (d < 0xC0) {
/* trailing byte in leading position */
*outlen = out - outstart;
*inlen = processed - instart;
return(-2);
} else if (d < 0xE0) { c= d & 0x1F; trailing= 1; }
else if (d < 0xF0) { c= d & 0x0F; trailing= 2; }
else if (d < 0xF8) { c= d & 0x07; trailing= 3; }
else {
/* no chance for this in UTF-16 */
*outlen = out - outstart;
*inlen = processed - instart;
return(-2);
}
if (inend - in < trailing) {
break;
}
for ( ; trailing; trailing--) {
if ((in >= inend) || (((d= *in++) & 0xC0) != 0x80)) break;
c <<= 6;
c |= d & 0x3F;
}
/* assertion: c is a single UTF-4 value */
if (c < 0x10000) {
if (out >= outend) break;
if (xmlLittleEndian) {
tmp = (unsigned char *) out;
*tmp = c >> 8;
*(tmp + 1) = c;
out++;
} else {
*out++ = c;
}
}
else if (c < 0x110000) {
if (out+1 >= outend) break;
c -= 0x10000;
if (xmlLittleEndian) {
tmp1 = 0xD800 | (c >> 10);
tmp = (unsigned char *) out;
*tmp = tmp1 >> 8;
*(tmp + 1) = (unsigned char) tmp1;
out++;
tmp2 = 0xDC00 | (c & 0x03FF);
tmp = (unsigned char *) out;
*tmp = tmp2 >> 8;
*(tmp + 1) = (unsigned char) tmp2;
out++;
} else {
*out++ = 0xD800 | (c >> 10);
*out++ = 0xDC00 | (c & 0x03FF);
}
}
else
break;
processed = in;
}
*outlen = (out - outstart) * 2;
*inlen = processed - instart;
return(*outlen);
}
/* This file is licenced under the MIT licence as follows:
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is fur-
nished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FIT-
NESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE. */

View File

@ -0,0 +1,12 @@
#ifndef LIBXML2_ENCODING_H
#define LIBXML2_ENCODING_H
#include <stdint.h>
typedef uint16_t mvhd_utf16;
void xmlEncodingInit(void);
int UTF16LEToUTF8(unsigned char* out, int *outlen, const unsigned char* inb, int *inlenb);
int UTF8ToUTF16LE(unsigned char* outb, int *outlen, const unsigned char* in, int *inlen);
int UTF16BEToUTF8(unsigned char* out, int *outlen, const unsigned char* inb, int *inlenb);
int UTF8ToUTF16BE(unsigned char* outb, int *outlen, const unsigned char* in, int *inlen);
#endif

269
src/disk/minivhd/minivhd.h Normal file
View File

@ -0,0 +1,269 @@
#ifndef MINIVHD_H
#define MINIVHD_H
#include <stdio.h>
#include <stdbool.h>
#include <stdint.h>
extern int mvhd_errno;
typedef enum MVHDError {
MVHD_ERR_MEM = -128,
MVHD_ERR_FILE,
MVHD_ERR_NOT_VHD,
MVHD_ERR_TYPE,
MVHD_ERR_FOOTER_CHECKSUM,
MVHD_ERR_SPARSE_CHECKSUM,
MVHD_ERR_UTF_TRANSCODING_FAILED,
MVHD_ERR_UTF_SIZE,
MVHD_ERR_PATH_REL,
MVHD_ERR_PATH_LEN,
MVHD_ERR_PAR_NOT_FOUND,
MVHD_ERR_INVALID_PAR_UUID,
MVHD_ERR_INVALID_GEOM,
MVHD_ERR_INVALID_SIZE,
MVHD_ERR_INVALID_BLOCK_SIZE,
MVHD_ERR_INVALID_PARAMS,
MVHD_ERR_CONV_SIZE,
MVHD_ERR_TIMESTAMP
} MVHDError;
typedef enum MVHDType {
MVHD_TYPE_FIXED = 2,
MVHD_TYPE_DYNAMIC = 3,
MVHD_TYPE_DIFF = 4
} MVHDType;
typedef enum MVHDBlockSize {
MVHD_BLOCK_DEFAULT = 0, /**< 2 MB blocks */
MVHD_BLOCK_SMALL = 1024, /**< 512 KB blocks */
MVHD_BLOCK_LARGE = 4096 /**< 2 MB blocks */
} MVHDBlockSize;
typedef struct MVHDGeom {
uint16_t cyl;
uint8_t heads;
uint8_t spt;
} MVHDGeom;
typedef void (*mvhd_progress_callback)(uint32_t current_sector, uint32_t total_sectors);
typedef struct MVHDCreationOptions {
int type; /** MVHD_TYPE_FIXED, MVHD_TYPE_DYNAMIC, or MVHD_TYPE_DIFF */
char* path; /** Absolute path of the new VHD file */
char* parent_path; /** For MVHD_TYPE_DIFF, this is the absolute path of the VHD's parent. For non-diff VHDs, this should be NULL. */
uint64_t size_in_bytes; /** Total size of the VHD's virtual disk in bytes. Must be a multiple of 512. If 0, the size is auto-calculated from the geometry field. Ignored for MVHD_TYPE_DIFF. */
MVHDGeom geometry; /** The geometry of the VHD. If set to 0, the geometry is auto-calculated from the size_in_bytes field. */
uint32_t block_size_in_sectors; /** MVHD_BLOCK_LARGE or MVHD_BLOCK_SMALL, or 0 for the default value. The number of sectors per block. */
mvhd_progress_callback progress_callback; /** Optional; if not NULL, gets called to indicate progress on the creation operation. Only applies to MVHD_TYPE_FIXED. */
} MVHDCreationOptions;
typedef struct MVHDMeta MVHDMeta;
/**
* \brief Output a string from a MiniVHD error number
*
* \param [in] err is the error number to return string from
*
* \return Error string
*/
const char* mvhd_strerr(MVHDError err);
/**
* \brief A simple test to see if a given file is a VHD
*
* \param [in] f file to test
*
* \retval true if f is a VHD
* \retval false if f is not a VHD
*/
bool mvhd_file_is_vhd(FILE* f);
/**
* \brief Open a VHD image for reading and/or writing
*
* The returned pointer contains all required values and structures (and files) to
* read and write to a VHD file.
*
* Remember to call mvhd_close() when you are finished.
*
* \param [in] Absolute path to VHD file. Relative path will cause issues when opening
* a differencing VHD file
* \param [in] readonly set this to true to open the VHD in a read only manner
* \param [out] err will be set if the VHD fails to open. Value could be one of
* MVHD_ERR_MEM, MVHD_ERR_FILE, MVHD_ERR_NOT_VHD, MVHD_ERR_FOOTER_CHECKSUM, MVHD_ERR_SPARSE_CHECKSUM,
* MVHD_ERR_TYPE, MVHD_ERR_TIMESTAMP
* If MVHD_ERR_FILE is set, mvhd_errno will be set to the appropriate system errno value
*
* \return MVHDMeta pointer. If NULL, check err. err may also be set to MVHD_ERR_TIMESTAMP if
* opening a differencing VHD.
*/
MVHDMeta* mvhd_open(const char* path, bool readonly, int* err);
/**
* \brief Update the parent modified timestamp in the VHD file
*
* Differencing VHD's use a parent last modified timestamp to try and detect if the
* parent has been modified after the child has been created. However, this is rather
* fragile and can be broken by moving/copying the parent. Also, MS DiskPart does not
* set this timestamp in the child :(
*
* Be careful when using this function that you don't update the timestamp after the
* parent actually has been modified.
*
* \param [in] vhdm Differencing VHD to update.
* \param [out] err will be set if the timestamp could not be updated
*
* \return non-zero on error, 0 on success
*/
int mvhd_diff_update_par_timestamp(MVHDMeta* vhdm, int* err);
/**
* \brief Create a fixed VHD image
*
* \param [in] path is the absolute path to the image to create
* \param [in] geom is the HDD geometry of the image to create. Determines final image size
* \param [out] err indicates what error occurred, if any
* \param [out] progress_callback optional; if not NULL, gets called to indicate progress on the creation operation
*
* \retval NULL if an error occurrs. Check value of *err for actual error. Otherwise returns pointer to a MVHDMeta struct
*/
MVHDMeta* mvhd_create_fixed(const char* path, MVHDGeom geom, int* err, mvhd_progress_callback progress_callback);
/**
* \brief Create sparse (dynamic) VHD image.
*
* \param [in] path is the absolute path to the VHD file to create
* \param [in] geom is the HDD geometry of the image to create. Determines final image size
* \param [out] err indicates what error occurred, if any
*
* \return NULL if an error occurrs. Check value of *err for actual error. Otherwise returns pointer to a MVHDMeta struct
*/
MVHDMeta* mvhd_create_sparse(const char* path, MVHDGeom geom, int* err);
/**
* \brief Create differencing VHD imagee.
*
* \param [in] path is the absolute path to the VHD file to create
* \param [in] par_path is the absolute path to a parent image. If NULL, a sparse image is created, otherwise create a differencing image
* \param [out] err indicates what error occurred, if any
*
* \return NULL if an error occurrs. Check value of *err for actual error. Otherwise returns pointer to a MVHDMeta struct
*/
MVHDMeta* mvhd_create_diff(const char* path, const char* par_path, int* err);
/**
* \brief Create a VHD using the provided options
*
* Use mvhd_create_ex if you want more control over the VHD's options. For quick creation, you can use mvhd_create_fixed, mvhd_create_sparse, or mvhd_create_diff.
*
* \param [in] options the VHD creation options.
* \param [out] err indicates what error occurred, if any
*
* \retval NULL if an error occurrs. Check value of *err for actual error. Otherwise returns pointer to a MVHDMeta struct
*/
MVHDMeta* mvhd_create_ex(MVHDCreationOptions options, int* err);
/**
* \brief Safely close a VHD image
*
* \param [in] vhdm MiniVHD data structure to close
*/
void mvhd_close(MVHDMeta* vhdm);
/**
* \brief Calculate hard disk geometry from a provided size
*
* The VHD format uses Cylinder, Heads, Sectors per Track (CHS) when accessing the disk.
* The size of the disk can be determined from C * H * S * sector_size.
*
* Note, maximum geometry size (in bytes) is 65535 * 16 * 255 * 512, which is 127GB.
* However, the maximum VHD size is 2040GB. For VHDs larger than 127GB, the geometry size will be
* smaller than the actual VHD size.
*
* This function determines the appropriate CHS geometry from a provided size in bytes.
* The calculations used are those provided in "Appendix: CHS Calculation" from the document
* "Virtual Hard Disk Image Format Specification" provided by Microsoft.
*
* \param [in] size the desired VHD image size, in bytes
*
* \return MVHDGeom the calculated geometry. This can be used in the appropriate create functions.
*/
MVHDGeom mvhd_calculate_geometry(uint64_t size);
/**
* \brief Convert a raw disk image to a fixed VHD image
*
* \param [in] utf8_raw_path is the path of the raw image to convert
* \param [in] utf8_vhd_path is the path of the VHD to create
* \param [out] err indicates what error occurred, if any
*
* \return NULL if an error occurrs. Check value of *err for actual error. Otherwise returns pointer to a MVHDMeta struct
*/
MVHDMeta* mvhd_convert_to_vhd_fixed(const char* utf8_raw_path, const char* utf8_vhd_path, int* err);
/**
* \brief Convert a raw disk image to a sparse VHD image
*
* \param [in] utf8_raw_path is the path of the raw image to convert
* \param [in] utf8_vhd_path is the path of the VHD to create
* \param [out] err indicates what error occurred, if any
*
* \return NULL if an error occurrs. Check value of *err for actual error. Otherwise returns pointer to a MVHDMeta struct
*/
MVHDMeta* mvhd_convert_to_vhd_sparse(const char* utf8_raw_path, const char* utf8_vhd_path, int* err);
/**
* \brief Convert a VHD image to a raw disk image
*
* \param [in] utf8_vhd_path is the path of the VHD to convert
* \param [in] utf8_raw_path is the path of the raw image to create
* \param [out] err indicates what error occurred, if any
*
* \return NULL if an error occurrs. Check value of *err for actual error. Otherwise returns the raw disk image FILE pointer
*/
FILE* mvhd_convert_to_raw(const char* utf8_vhd_path, const char* utf8_raw_path, int *err);
/**
* \brief Read sectors from VHD file
*
* Read num_sectors, beginning at offset from the VHD file into a buffer
*
* \param [in] vhdm MiniVHD data structure
* \param [in] offset the sector offset from which to start reading from
* \param [in] num_sectors the number of sectors to read
* \param [out] out_buff the buffer to write sector data to
*
* \return the number of sectors that were not read, or zero
*/
int mvhd_read_sectors(MVHDMeta* vhdm, uint32_t offset, int num_sectors, void* out_buff);
/**
* \brief Write sectors to VHD file
*
* Write num_sectors, beginning at offset from a buffer VHD file into the VHD file
*
* \param [in] vhdm MiniVHD data structure
* \param [in] offset the sector offset from which to start writing to
* \param [in] num_sectors the number of sectors to write
* \param [in] in_buffer the buffer to write sector data to
*
* \return the number of sectors that were not written, or zero
*/
int mvhd_write_sectors(MVHDMeta* vhdm, uint32_t offset, int num_sectors, void* in_buff);
/**
* \brief Write zeroed sectors to VHD file
*
* Write num_sectors, beginning at offset, of zero data into the VHD file.
* We reuse the existing write functions, with a preallocated zero buffer as
* our source buffer.
*
* \param [in] vhdm MiniVHD data structure
* \param [in] offset the sector offset from which to start writing to
* \param [in] num_sectors the number of sectors to write
*
* \return the number of sectors that were not written, or zero
*/
int mvhd_format_sectors(MVHDMeta* vhdm, uint32_t offset, int num_sectors);
#endif

View File

@ -0,0 +1,105 @@
#include <stdbool.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include "minivhd_create.h"
#include "minivhd_internal.h"
#include "minivhd_util.h"
#include "minivhd.h"
static FILE* mvhd_open_existing_raw_img(const char* utf8_raw_path, MVHDGeom* geom, int* err);
static FILE* mvhd_open_existing_raw_img(const char* utf8_raw_path, MVHDGeom* geom, int* err) {
FILE *raw_img = mvhd_fopen(utf8_raw_path, "rb", err);
if (raw_img == NULL) {
*err = MVHD_ERR_FILE;
return NULL;
}
if (geom == NULL) {
*err = MVHD_ERR_INVALID_GEOM;
return NULL;
}
mvhd_fseeko64(raw_img, 0, SEEK_END);
uint64_t size_bytes = (uint64_t)mvhd_ftello64(raw_img);
MVHDGeom new_geom = mvhd_calculate_geometry(size_bytes);
if (mvhd_calc_size_bytes(&new_geom) != size_bytes) {
*err = MVHD_ERR_CONV_SIZE;
return NULL;
}
geom->cyl = new_geom.cyl;
geom->heads = new_geom.heads;
geom->spt = new_geom.spt;
mvhd_fseeko64(raw_img, 0, SEEK_SET);
return raw_img;
}
MVHDMeta* mvhd_convert_to_vhd_fixed(const char* utf8_raw_path, const char* utf8_vhd_path, int* err) {
MVHDGeom geom;
FILE *raw_img = mvhd_open_existing_raw_img(utf8_raw_path, &geom, err);
if (raw_img == NULL) {
return NULL;
}
uint64_t size_in_bytes = mvhd_calc_size_bytes(&geom);
MVHDMeta *vhdm = mvhd_create_fixed_raw(utf8_vhd_path, raw_img, size_in_bytes, &geom, err, NULL);
if (vhdm == NULL) {
return NULL;
}
return vhdm;
}
MVHDMeta* mvhd_convert_to_vhd_sparse(const char* utf8_raw_path, const char* utf8_vhd_path, int* err) {
MVHDGeom geom;
MVHDMeta *vhdm = NULL;
FILE *raw_img = mvhd_open_existing_raw_img(utf8_raw_path, &geom, err);
if (raw_img == NULL) {
return NULL;
}
vhdm = mvhd_create_sparse(utf8_vhd_path, geom, err);
if (vhdm == NULL) {
goto end;
}
uint8_t buff[4096] = {0}; // 8 sectors
uint8_t empty_buff[4096] = {0};
int total_sectors = mvhd_calc_size_sectors(&geom);
int copy_sect = 0;
for (int i = 0; i < total_sectors; i += 8) {
copy_sect = 8;
if ((i + 8) >= total_sectors) {
copy_sect = total_sectors - i;
memset(buff, 0, sizeof buff);
}
fread(buff, MVHD_SECTOR_SIZE, copy_sect, raw_img);
/* Only write data if there's data to write, to take advantage of the sparse VHD format */
if (memcmp(buff, empty_buff, sizeof buff) != 0) {
mvhd_write_sectors(vhdm, i, copy_sect, buff);
}
}
end:
fclose(raw_img);
return vhdm;
}
FILE* mvhd_convert_to_raw(const char* utf8_vhd_path, const char* utf8_raw_path, int *err) {
FILE *raw_img = mvhd_fopen(utf8_raw_path, "wb", err);
if (raw_img == NULL) {
return NULL;
}
MVHDMeta *vhdm = mvhd_open(utf8_vhd_path, true, err);
if (vhdm == NULL) {
fclose(raw_img);
return NULL;
}
uint8_t buff[4096] = {0}; // 8 sectors
int total_sectors = mvhd_calc_size_sectors((MVHDGeom*)&vhdm->footer.geom);
int copy_sect = 0;
for (int i = 0; i < total_sectors; i += 8) {
copy_sect = 8;
if ((i + 8) >= total_sectors) {
copy_sect = total_sectors - i;
}
mvhd_read_sectors(vhdm, i, copy_sect, buff);
fwrite(buff, MVHD_SECTOR_SIZE, copy_sect, raw_img);
}
mvhd_close(vhdm);
mvhd_fseeko64(raw_img, 0, SEEK_SET);
return raw_img;
}

View File

@ -0,0 +1,482 @@
#include <assert.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include "cwalk.h"
#include "libxml2_encoding.h"
#include "minivhd_internal.h"
#include "minivhd_util.h"
#include "minivhd_struct_rw.h"
#include "minivhd_io.h"
#include "minivhd_create.h"
#include "minivhd.h"
static void mvhd_gen_footer(MVHDFooter* footer, uint64_t size_in_bytes, MVHDGeom* geom, MVHDType type, uint64_t sparse_header_off);
static void mvhd_gen_sparse_header(MVHDSparseHeader* header, uint32_t num_blks, uint64_t bat_offset, uint32_t block_size_in_sectors);
static int mvhd_gen_par_loc(MVHDSparseHeader* header,
const char* child_path,
const char* par_path,
uint64_t start_offset,
mvhd_utf16* w2ku_path_buff,
mvhd_utf16* w2ru_path_buff,
MVHDError* err);
static MVHDMeta* mvhd_create_sparse_diff(const char* path, const char* par_path, uint64_t size_in_bytes, MVHDGeom* geom, uint32_t block_size_in_sectors, int* err);
/**
* \brief Populate a VHD footer
*
* \param [in] footer to populate
* \param [in] size_in_bytes is the total size of the virtual hard disk in bytes
* \param [in] geom to use
* \param [in] type of HVD that is being created
* \param [in] sparse_header_off, an absolute file offset to the sparse header. Not used for fixed VHD images
*/
static void mvhd_gen_footer(MVHDFooter* footer, uint64_t size_in_bytes, MVHDGeom* geom, MVHDType type, uint64_t sparse_header_off) {
memcpy(footer->cookie, "conectix", sizeof footer->cookie);
footer->features = 0x00000002;
footer->fi_fmt_vers = 0x00010000;
footer->data_offset = (type == MVHD_TYPE_DIFF || type == MVHD_TYPE_DYNAMIC) ? sparse_header_off : 0xffffffffffffffff;
footer->timestamp = vhd_calc_timestamp();
memcpy(footer->cr_app, "mvhd", sizeof footer->cr_app);
footer->cr_vers = 0x000e0000;
memcpy(footer->cr_host_os, "Wi2k", sizeof footer->cr_host_os);
footer->orig_sz = footer->curr_sz = size_in_bytes;
footer->geom.cyl = geom->cyl;
footer->geom.heads = geom->heads;
footer->geom.spt = geom->spt;
footer->disk_type = type;
mvhd_generate_uuid(footer->uuid);
footer->checksum = mvhd_gen_footer_checksum(footer);
}
/**
* \brief Populate a VHD sparse header
*
* \param [in] header for sparse and differencing images
* \param [in] num_blks is the number of data blocks that the image contains
* \param [in] bat_offset is the absolute file offset for start of the Block Allocation Table
* \param [in] block_size_in_sectors is the block size in sectors.
*/
static void mvhd_gen_sparse_header(MVHDSparseHeader* header, uint32_t num_blks, uint64_t bat_offset, uint32_t block_size_in_sectors) {
memcpy(header->cookie, "cxsparse", sizeof header->cookie);
header->data_offset = 0xffffffffffffffff;
header->bat_offset = bat_offset;
header->head_vers = 0x00010000;
header->max_bat_ent = num_blks;
header->block_sz = block_size_in_sectors * (uint32_t)MVHD_SECTOR_SIZE;
header->checksum = mvhd_gen_sparse_checksum(header);
}
/**
* \brief Generate parent locators for differencing VHD images
*
* \param [in] header the sparse header to populate with parent locator entries
* \param [in] child_path is the full path to the VHD being created
* \param [in] par_path is the full path to the parent image
* \param [in] start_offset is the absolute file offset from where to start storing the entries themselves. Must be sector aligned.
* \param [out] w2ku_path_buff is a buffer containing the full path to the parent, encoded as UTF16-LE
* \param [out] w2ru_path_buff is a buffer containing the relative path to the parent, encoded as UTF16-LE
* \param [out] err indicates what error occurred, if any
*
* \retval 0 if success
* \retval < 0 if an error occurrs. Check value of *err for actual error
*/
static int mvhd_gen_par_loc(MVHDSparseHeader* header,
const char* child_path,
const char* par_path,
uint64_t start_offset,
mvhd_utf16* w2ku_path_buff,
mvhd_utf16* w2ru_path_buff,
MVHDError* err) {
/* Get our paths to store in the differencing VHD. We want both the absolute path to the parent,
as well as the relative path from the child VHD */
int rv = 0;
char* par_filename;
size_t par_fn_len;
char rel_path[MVHD_MAX_PATH_BYTES] = {0};
char child_dir[MVHD_MAX_PATH_BYTES] = {0};
size_t child_dir_len;
if (strlen(child_path) < sizeof child_dir) {
strcpy(child_dir, child_path);
} else {
*err = MVHD_ERR_PATH_LEN;
rv = -1;
goto end;
}
cwk_path_get_basename(par_path, (const char**)&par_filename, &par_fn_len);
cwk_path_get_dirname(child_dir, &child_dir_len);
child_dir[child_dir_len] = '\0';
size_t rel_len = cwk_path_get_relative(child_dir, par_path, rel_path, sizeof rel_path);
if (rel_len > sizeof rel_path) {
*err = MVHD_ERR_PATH_LEN;
rv = -1;
goto end;
}
/* We have our paths, now store the parent filename directly in the sparse header. */
int outlen = sizeof header->par_utf16_name;
int utf_ret;
utf_ret = UTF8ToUTF16BE((unsigned char*)header->par_utf16_name, &outlen, (const unsigned char*)par_filename, (int*)&par_fn_len);
if (utf_ret < 0) {
mvhd_set_encoding_err(utf_ret, (int*)err);
rv = -1;
goto end;
}
/* And encode the paths to UTF16-LE */
size_t par_path_len = strlen(par_path);
outlen = sizeof *w2ku_path_buff * MVHD_MAX_PATH_CHARS;
utf_ret = UTF8ToUTF16LE((unsigned char*)w2ku_path_buff, &outlen, (const unsigned char*)par_path, (int*)&par_path_len);
if (utf_ret < 0) {
mvhd_set_encoding_err(utf_ret, (int*)err);
rv = -1;
goto end;
}
int w2ku_len = utf_ret;
outlen = sizeof *w2ru_path_buff * MVHD_MAX_PATH_CHARS;
utf_ret = UTF8ToUTF16LE((unsigned char*)w2ru_path_buff, &outlen, (const unsigned char*)rel_path, (int*)&rel_len);
if (utf_ret < 0) {
mvhd_set_encoding_err(utf_ret, (int*)err);
rv = -1;
goto end;
}
int w2ru_len = utf_ret;
/**
* Finally populate the parent locaters in the sparse header.
* This is the information needed to find the paths saved elsewhere
* in the VHD image
*/
/* Note about the plat_data_space field: The VHD spec says this field stores the number of sectors needed to store the locator path.
* However, Hyper-V and VPC store the number of bytes, not the number of sectors, and will refuse to open VHDs which have the
* number of sectors in this field.
* See https://stackoverflow.com/questions/40760181/mistake-in-virtual-hard-disk-image-format-specification
*/
header->par_loc_entry[0].plat_code = MVHD_DIF_LOC_W2KU;
header->par_loc_entry[0].plat_data_len = (uint32_t)w2ku_len;
header->par_loc_entry[0].plat_data_offset = (uint64_t)start_offset;
header->par_loc_entry[0].plat_data_space = ((header->par_loc_entry[0].plat_data_len / MVHD_SECTOR_SIZE) + 1) * MVHD_SECTOR_SIZE;
header->par_loc_entry[1].plat_code = MVHD_DIF_LOC_W2RU;
header->par_loc_entry[1].plat_data_len = (uint32_t)w2ru_len;
header->par_loc_entry[1].plat_data_offset = (uint64_t)start_offset + ((uint64_t)header->par_loc_entry[0].plat_data_space);
header->par_loc_entry[1].plat_data_space = ((header->par_loc_entry[1].plat_data_len / MVHD_SECTOR_SIZE) + 1) * MVHD_SECTOR_SIZE;
goto end;
end:
return rv;
}
MVHDMeta* mvhd_create_fixed(const char* path, MVHDGeom geom, int* err, mvhd_progress_callback progress_callback) {
uint64_t size_in_bytes = mvhd_calc_size_bytes(&geom);
return mvhd_create_fixed_raw(path, NULL, size_in_bytes, &geom, err, progress_callback);
}
/**
* \brief internal function that implements public mvhd_create_fixed() functionality
*
* Contains one more parameter than the public function, to allow using an existing
* raw disk image as the data source for the new fixed VHD.
*
* \param [in] raw_image file handle to a raw disk image to populate VHD
*/
MVHDMeta* mvhd_create_fixed_raw(const char* path, FILE* raw_img, uint64_t size_in_bytes, MVHDGeom* geom, int* err, mvhd_progress_callback progress_callback) {
uint8_t img_data[MVHD_SECTOR_SIZE] = {0};
uint8_t footer_buff[MVHD_FOOTER_SIZE] = {0};
MVHDMeta* vhdm = calloc(1, sizeof *vhdm);
if (vhdm == NULL) {
*err = MVHD_ERR_MEM;
goto end;
}
if (geom == NULL || (geom->cyl == 0 || geom->heads == 0 || geom->spt == 0)) {
*err = MVHD_ERR_INVALID_GEOM;
goto cleanup_vhdm;
}
FILE* f = mvhd_fopen(path, "wb+", err);
if (f == NULL) {
goto cleanup_vhdm;
}
mvhd_fseeko64(f, 0, SEEK_SET);
uint32_t size_sectors = (uint32_t)(size_in_bytes / MVHD_SECTOR_SIZE);
uint32_t s;
if (progress_callback)
progress_callback(0, size_sectors);
if (raw_img != NULL) {
mvhd_fseeko64(raw_img, 0, SEEK_END);
uint64_t raw_size = (uint64_t)mvhd_ftello64(raw_img);
MVHDGeom raw_geom = mvhd_calculate_geometry(raw_size);
if (mvhd_calc_size_bytes(&raw_geom) != raw_size) {
*err = MVHD_ERR_CONV_SIZE;
goto cleanup_vhdm;
}
mvhd_gen_footer(&vhdm->footer, raw_size, geom, MVHD_TYPE_FIXED, 0);
mvhd_fseeko64(raw_img, 0, SEEK_SET);
for (s = 0; s < size_sectors; s++) {
fread(img_data, sizeof img_data, 1, raw_img);
fwrite(img_data, sizeof img_data, 1, f);
if (progress_callback)
progress_callback(s + 1, size_sectors);
}
} else {
mvhd_gen_footer(&vhdm->footer, size_in_bytes, geom, MVHD_TYPE_FIXED, 0);
for (s = 0; s < size_sectors; s++) {
fwrite(img_data, sizeof img_data, 1, f);
if (progress_callback)
progress_callback(s + 1, size_sectors);
}
}
mvhd_footer_to_buffer(&vhdm->footer, footer_buff);
fwrite(footer_buff, sizeof footer_buff, 1, f);
fclose(f);
f = NULL;
free(vhdm);
vhdm = mvhd_open(path, false, err);
goto end;
cleanup_vhdm:
free(vhdm);
vhdm = NULL;
end:
return vhdm;
}
/**
* \brief Create sparse or differencing VHD image.
*
* \param [in] path is the absolute path to the VHD file to create
* \param [in] par_path is the absolute path to a parent image. If NULL, a sparse image is created, otherwise create a differencing image
* \param [in] size_in_bytes is the total size in bytes of the virtual hard disk image
* \param [in] geom is the HDD geometry of the image to create. Determines final image size
* \param [in] block_size_in_sectors is the block size in sectors
* \param [out] err indicates what error occurred, if any
*
* \return NULL if an error occurrs. Check value of *err for actual error. Otherwise returns pointer to a MVHDMeta struct
*/
static MVHDMeta* mvhd_create_sparse_diff(const char* path, const char* par_path, uint64_t size_in_bytes, MVHDGeom* geom, uint32_t block_size_in_sectors, int* err) {
uint8_t footer_buff[MVHD_FOOTER_SIZE] = {0};
uint8_t sparse_buff[MVHD_SPARSE_SIZE] = {0};
uint8_t bat_sect[MVHD_SECTOR_SIZE];
MVHDGeom par_geom = {0};
memset(bat_sect, 0xffffffff, sizeof bat_sect);
MVHDMeta* vhdm = NULL;
MVHDMeta* par_vhdm = NULL;
mvhd_utf16* w2ku_path_buff = NULL;
mvhd_utf16* w2ru_path_buff = NULL;
uint32_t par_mod_timestamp = 0;
if (par_path != NULL) {
par_mod_timestamp = mvhd_file_mod_timestamp(par_path, err);
if (*err != 0) {
goto end;
}
par_vhdm = mvhd_open(par_path, true, err);
if (par_vhdm == NULL) {
goto end;
}
}
vhdm = calloc(1, sizeof *vhdm);
if (vhdm == NULL) {
*err = MVHD_ERR_MEM;
goto cleanup_par_vhdm;
}
if (par_vhdm != NULL) {
/* We use the geometry from the parent VHD, not what was passed in */
par_geom.cyl = par_vhdm->footer.geom.cyl;
par_geom.heads = par_vhdm->footer.geom.heads;
par_geom.spt = par_vhdm->footer.geom.spt;
geom = &par_geom;
size_in_bytes = par_vhdm->footer.curr_sz;
} else if (geom == NULL || (geom->cyl == 0 || geom->heads == 0 || geom->spt == 0)) {
*err = MVHD_ERR_INVALID_GEOM;
goto cleanup_vhdm;
}
FILE* f = mvhd_fopen(path, "wb+", err);
if (f == NULL) {
goto cleanup_vhdm;
}
mvhd_fseeko64(f, 0, SEEK_SET);
/* Note, the sparse header follows the footer copy at the beginning of the file */
if (par_path == NULL) {
mvhd_gen_footer(&vhdm->footer, size_in_bytes, geom, MVHD_TYPE_DYNAMIC, MVHD_FOOTER_SIZE);
} else {
mvhd_gen_footer(&vhdm->footer, size_in_bytes, geom, MVHD_TYPE_DIFF, MVHD_FOOTER_SIZE);
}
mvhd_footer_to_buffer(&vhdm->footer, footer_buff);
/* As mentioned, start with a copy of the footer */
fwrite(footer_buff, sizeof footer_buff, 1, f);
/**
* Calculate the number of (2MB or 512KB) data blocks required to store the entire
* contents of the disk image, followed by the number of sectors the
* BAT occupies in the image. Note, the BAT is sector aligned, and is padded
* to the next sector boundary
* */
uint32_t size_in_sectors = (uint32_t)(size_in_bytes / MVHD_SECTOR_SIZE);
uint32_t num_blks = size_in_sectors / block_size_in_sectors;
if (size_in_sectors % block_size_in_sectors != 0) {
num_blks += 1;
}
uint32_t num_bat_sect = num_blks / MVHD_BAT_ENT_PER_SECT;
if (num_blks % MVHD_BAT_ENT_PER_SECT != 0) {
num_bat_sect += 1;
}
/* Storing the BAT directly following the footer and header */
uint64_t bat_offset = MVHD_FOOTER_SIZE + MVHD_SPARSE_SIZE;
uint64_t par_loc_offset = 0;
/**
* If creating a differencing VHD, populate the sparse header with additional
* data about the parent image, and where to find it, and it's last modified timestamp
* */
if (par_vhdm != NULL) {
/**
* Create output buffers to encode paths into.
* The paths are not stored directly in the sparse header, hence the need to
* store them in buffers to be written to the VHD image later
*/
w2ku_path_buff = calloc(MVHD_MAX_PATH_CHARS, sizeof * w2ku_path_buff);
if (w2ku_path_buff == NULL) {
*err = MVHD_ERR_MEM;
goto end;
}
w2ru_path_buff = calloc(MVHD_MAX_PATH_CHARS, sizeof * w2ru_path_buff);
if (w2ru_path_buff == NULL) {
*err = MVHD_ERR_MEM;
goto end;
}
memcpy(vhdm->sparse.par_uuid, par_vhdm->footer.uuid, sizeof vhdm->sparse.par_uuid);
par_loc_offset = bat_offset + ((uint64_t)num_bat_sect * MVHD_SECTOR_SIZE) + (5 * MVHD_SECTOR_SIZE);
if (mvhd_gen_par_loc(&vhdm->sparse, path, par_path, par_loc_offset, w2ku_path_buff, w2ru_path_buff, (MVHDError*)err) < 0) {
goto cleanup_vhdm;
}
vhdm->sparse.par_timestamp = par_mod_timestamp;
}
mvhd_gen_sparse_header(&vhdm->sparse, num_blks, bat_offset, block_size_in_sectors);
mvhd_header_to_buffer(&vhdm->sparse, sparse_buff);
fwrite(sparse_buff, sizeof sparse_buff, 1, f);
/* The BAT sectors need to be filled with 0xffffffff */
for (uint32_t i = 0; i < num_bat_sect; i++) {
fwrite(bat_sect, sizeof bat_sect, 1, f);
}
mvhd_write_empty_sectors(f, 5);
/**
* If creating a differencing VHD, the paths to the parent image need to be written
* tp the file. Both absolute and relative paths are written
* */
if (par_vhdm != NULL) {
uint64_t curr_pos = (uint64_t)mvhd_ftello64(f);
/* Double check my sums... */
assert(curr_pos == par_loc_offset);
/* Fill the space required for location data with zero */
uint8_t empty_sect[MVHD_SECTOR_SIZE] = {0};
for (int i = 0; i < 2; i++) {
for (uint32_t j = 0; j < (vhdm->sparse.par_loc_entry[i].plat_data_space / MVHD_SECTOR_SIZE); j++) {
fwrite(empty_sect, sizeof empty_sect, 1, f);
}
}
/* Now write the location entries */
mvhd_fseeko64(f, vhdm->sparse.par_loc_entry[0].plat_data_offset, SEEK_SET);
fwrite(w2ku_path_buff, vhdm->sparse.par_loc_entry[0].plat_data_len, 1, f);
mvhd_fseeko64(f, vhdm->sparse.par_loc_entry[1].plat_data_offset, SEEK_SET);
fwrite(w2ru_path_buff, vhdm->sparse.par_loc_entry[1].plat_data_len, 1, f);
/* and reset the file position to continue */
mvhd_fseeko64(f, vhdm->sparse.par_loc_entry[1].plat_data_offset + vhdm->sparse.par_loc_entry[1].plat_data_space, SEEK_SET);
mvhd_write_empty_sectors(f, 5);
}
/* And finish with the footer */
fwrite(footer_buff, sizeof footer_buff, 1, f);
fclose(f);
f = NULL;
free(vhdm);
vhdm = mvhd_open(path, false, err);
goto end;
cleanup_vhdm:
free(vhdm);
vhdm = NULL;
cleanup_par_vhdm:
if (par_vhdm != NULL) {
mvhd_close(par_vhdm);
}
end:
free(w2ku_path_buff);
free(w2ru_path_buff);
return vhdm;
}
MVHDMeta* mvhd_create_sparse(const char* path, MVHDGeom geom, int* err) {
uint64_t size_in_bytes = mvhd_calc_size_bytes(&geom);
return mvhd_create_sparse_diff(path, NULL, size_in_bytes, &geom, MVHD_BLOCK_LARGE, err);
}
MVHDMeta* mvhd_create_diff(const char* path, const char* par_path, int* err) {
return mvhd_create_sparse_diff(path, par_path, 0, NULL, MVHD_BLOCK_LARGE, err);
}
MVHDMeta* mvhd_create_ex(MVHDCreationOptions options, int* err) {
uint32_t geom_sector_size;
switch (options.type)
{
case MVHD_TYPE_FIXED:
case MVHD_TYPE_DYNAMIC:
geom_sector_size = mvhd_calc_size_sectors(&(options.geometry));
if ((options.size_in_bytes > 0 && (options.size_in_bytes % MVHD_SECTOR_SIZE) > 0)
|| (options.size_in_bytes > MVHD_MAX_SIZE_IN_BYTES)
|| (options.size_in_bytes == 0 && geom_sector_size == 0))
{
*err = MVHD_ERR_INVALID_SIZE;
return NULL;
}
if (options.size_in_bytes > 0 && ((uint64_t)geom_sector_size * MVHD_SECTOR_SIZE) > options.size_in_bytes)
{
*err = MVHD_ERR_INVALID_GEOM;
return NULL;
}
if (options.size_in_bytes == 0)
options.size_in_bytes = (uint64_t)geom_sector_size * MVHD_SECTOR_SIZE;
if (geom_sector_size == 0)
options.geometry = mvhd_calculate_geometry(options.size_in_bytes);
break;
case MVHD_TYPE_DIFF:
if (options.parent_path == NULL)
{
*err = MVHD_ERR_FILE;
return NULL;
}
break;
default:
*err = MVHD_ERR_TYPE;
return NULL;
}
if (options.path == NULL)
{
*err = MVHD_ERR_FILE;
return NULL;
}
if (options.type != MVHD_TYPE_FIXED)
{
if (options.block_size_in_sectors == MVHD_BLOCK_DEFAULT)
options.block_size_in_sectors = MVHD_BLOCK_LARGE;
if (options.block_size_in_sectors != MVHD_BLOCK_LARGE && options.block_size_in_sectors != MVHD_BLOCK_SMALL)
{
*err = MVHD_ERR_INVALID_BLOCK_SIZE;
return NULL;
}
}
switch (options.type)
{
case MVHD_TYPE_FIXED:
return mvhd_create_fixed_raw(options.path, NULL, options.size_in_bytes, &(options.geometry), err, options.progress_callback);
case MVHD_TYPE_DYNAMIC:
return mvhd_create_sparse_diff(options.path, NULL, options.size_in_bytes, &(options.geometry), options.block_size_in_sectors, err);
case MVHD_TYPE_DIFF:
return mvhd_create_sparse_diff(options.path, options.parent_path, 0, NULL, options.block_size_in_sectors, err);
}
return NULL; /* Make the compiler happy */
}

View File

@ -0,0 +1,8 @@
#ifndef MINIVHD_CREATE_H
#define MINIVHD_CREATE_H
#include <stdio.h>
#include "minivhd.h"
MVHDMeta* mvhd_create_fixed_raw(const char* path, FILE* raw_img, uint64_t size_in_bytes, MVHDGeom* geom, int* err, mvhd_progress_callback progress_callback);
#endif

View File

@ -0,0 +1,96 @@
#ifndef MINIVHD_INTERNAL_H
#define MINIVHD_INTERNAL_H
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#define MVHD_FOOTER_SIZE 512
#define MVHD_SPARSE_SIZE 1024
#define MVHD_SECTOR_SIZE 512
#define MVHD_BAT_ENT_PER_SECT 128
#define MVHD_MAX_SIZE_IN_BYTES 0x1fe00000000
#define MVHD_SPARSE_BLK 0xffffffff
/* For simplicity, we don't handle paths longer than this
* Note, this is the max path in characters, as that is what
* Windows uses
*/
#define MVHD_MAX_PATH_CHARS 260
#define MVHD_MAX_PATH_BYTES 1040
#define MVHD_DIF_LOC_W2RU 0x57327275
#define MVHD_DIF_LOC_W2KU 0x57326B75
typedef struct MVHDSectorBitmap {
uint8_t* curr_bitmap;
int sector_count;
int curr_block;
} MVHDSectorBitmap;
typedef struct MVHDFooter {
uint8_t cookie[8];
uint32_t features;
uint32_t fi_fmt_vers;
uint64_t data_offset;
uint32_t timestamp;
uint8_t cr_app[4];
uint32_t cr_vers;
uint8_t cr_host_os[4];
uint64_t orig_sz;
uint64_t curr_sz;
struct {
uint16_t cyl;
uint8_t heads;
uint8_t spt;
} geom;
uint32_t disk_type;
uint32_t checksum;
uint8_t uuid[16];
uint8_t saved_st;
uint8_t reserved[427];
} MVHDFooter;
typedef struct MVHDSparseHeader {
uint8_t cookie[8];
uint64_t data_offset;
uint64_t bat_offset;
uint32_t head_vers;
uint32_t max_bat_ent;
uint32_t block_sz;
uint32_t checksum;
uint8_t par_uuid[16];
uint32_t par_timestamp;
uint32_t reserved_1;
uint8_t par_utf16_name[512];
struct {
uint32_t plat_code;
uint32_t plat_data_space;
uint32_t plat_data_len;
uint32_t reserved;
uint64_t plat_data_offset;
} par_loc_entry[8];
uint8_t reserved_2[256];
} MVHDSparseHeader;
typedef struct MVHDMeta MVHDMeta;
struct MVHDMeta {
FILE* f;
bool readonly;
char filename[MVHD_MAX_PATH_BYTES];
struct MVHDMeta* parent;
MVHDFooter footer;
MVHDSparseHeader sparse;
uint32_t* block_offset;
int sect_per_block;
MVHDSectorBitmap bitmap;
int (*read_sectors)(MVHDMeta*, uint32_t, int, void*);
int (*write_sectors)(MVHDMeta*, uint32_t, int, void*);
struct {
uint8_t* zero_data;
int sector_count;
} format_buffer;
};
#endif

View File

@ -0,0 +1,276 @@
/**
* \file
* \brief Sector reading and writing implementations
*/
#include <stdlib.h>
#include <string.h>
#include "minivhd_internal.h"
#include "minivhd_util.h"
/* The following bit array macros adapted from
http://www.mathcs.emory.edu/~cheung/Courses/255/Syllabus/1-C-intro/bit-array.html */
#define VHD_SETBIT(A,k) ( A[(k/8)] |= (0x80 >> (k%8)) )
#define VHD_CLEARBIT(A,k) ( A[(k/8)] &= ~(0x80 >> (k%8)) )
#define VHD_TESTBIT(A,k) ( A[(k/8)] & (0x80 >> (k%8)) )
static inline void mvhd_check_sectors(uint32_t offset, int num_sectors, uint32_t total_sectors, int* transfer_sect, int* trunc_sect);
static void mvhd_read_sect_bitmap(MVHDMeta* vhdm, int blk);
static void mvhd_write_bat_entry(MVHDMeta* vhdm, int blk);
static void mvhd_create_block(MVHDMeta* vhdm, int blk);
static void mvhd_write_curr_sect_bitmap(MVHDMeta* vhdm);
/**
* \brief Check that we will not be overflowing buffers
*
* \param [in] offset The offset from which we are beginning from
* \param [in] num_sectors The number of sectors which we desire to read/write
* \param [in] total_sectors The total number of sectors available
* \param [out] transfer_sect The number of sectors to actually write.
* This may be lower than num_sectors if offset + num_sectors >= total_sectors
* \param [out] trunc_sectors The number of sectors truncated if transfer_sectors < num_sectors
*/
static inline void mvhd_check_sectors(uint32_t offset, int num_sectors, uint32_t total_sectors, int* transfer_sect, int* trunc_sect) {
*transfer_sect = num_sectors;
*trunc_sect = 0;
if ((total_sectors - offset) < (uint32_t)*transfer_sect) {
*transfer_sect = total_sectors - offset;
*trunc_sect = num_sectors - *transfer_sect;
}
}
void mvhd_write_empty_sectors(FILE* f, int sector_count) {
uint8_t zero_bytes[MVHD_SECTOR_SIZE] = {0};
for (int i = 0; i < sector_count; i++) {
fwrite(zero_bytes, sizeof zero_bytes, 1, f);
}
}
/**
* \brief Read the sector bitmap for a block.
*
* If the block is sparse, the sector bitmap in memory will be
* zeroed. Otherwise, the sector bitmap is read from the VHD file.
*
* \param [in] vhdm MiniVHD data structure
* \param [in] blk The block for which to read the sector bitmap from
*/
static void mvhd_read_sect_bitmap(MVHDMeta* vhdm, int blk) {
if (vhdm->block_offset[blk] != MVHD_SPARSE_BLK) {
mvhd_fseeko64(vhdm->f, (uint64_t)vhdm->block_offset[blk] * MVHD_SECTOR_SIZE, SEEK_SET);
fread(vhdm->bitmap.curr_bitmap, vhdm->bitmap.sector_count * MVHD_SECTOR_SIZE, 1, vhdm->f);
} else {
memset(vhdm->bitmap.curr_bitmap, 0, vhdm->bitmap.sector_count * MVHD_SECTOR_SIZE);
}
vhdm->bitmap.curr_block = blk;
}
/**
* \brief Write the current sector bitmap in memory to file
*
* \param [in] vhdm MiniVHD data structure
*/
static void mvhd_write_curr_sect_bitmap(MVHDMeta* vhdm) {
if (vhdm->bitmap.curr_block >= 0) {
int64_t abs_offset = (int64_t)vhdm->block_offset[vhdm->bitmap.curr_block] * MVHD_SECTOR_SIZE;
mvhd_fseeko64(vhdm->f, abs_offset, SEEK_SET);
fwrite(vhdm->bitmap.curr_bitmap, MVHD_SECTOR_SIZE, vhdm->bitmap.sector_count, vhdm->f);
}
}
/**
* \brief Write block offset from memory into file
*
* \param [in] vhdm MiniVHD data structure
* \param [in] blk The block for which to write the offset for
*/
static void mvhd_write_bat_entry(MVHDMeta* vhdm, int blk) {
uint64_t table_offset = vhdm->sparse.bat_offset + ((uint64_t)blk * sizeof *vhdm->block_offset);
uint32_t offset = mvhd_to_be32(vhdm->block_offset[blk]);
mvhd_fseeko64(vhdm->f, table_offset, SEEK_SET);
fwrite(&offset, sizeof offset, 1, vhdm->f);
}
/**
* \brief Create an empty block in a sparse or differencing VHD image
*
* VHD images store data in blocks, which are typically 4096 sectors in size
* (~2MB). These blocks may be stored on disk in any order. Blocks are created
* on demand when required.
*
* This function creates new, empty blocks, by replacing the footer at the end of the file
* and then re-inserting the footer at the new file end. The BAT table entry for the
* new block is updated with the new offset.
*
* \param [in] vhdm MiniVHD data structure
* \param [in] blk The block number to create
*/
static void mvhd_create_block(MVHDMeta* vhdm, int blk) {
uint8_t footer[MVHD_FOOTER_SIZE];
/* Seek to where the footer SHOULD be */
mvhd_fseeko64(vhdm->f, -MVHD_FOOTER_SIZE, SEEK_END);
fread(footer, sizeof footer, 1, vhdm->f);
mvhd_fseeko64(vhdm->f, -MVHD_FOOTER_SIZE, SEEK_END);
if (!mvhd_is_conectix_str(footer)) {
/* Oh dear. We use the header instead, since something has gone wrong at the footer */
mvhd_fseeko64(vhdm->f, 0, SEEK_SET);
fread(footer, sizeof footer, 1, vhdm->f);
mvhd_fseeko64(vhdm->f, 0, SEEK_END);
}
int64_t abs_offset = mvhd_ftello64(vhdm->f);
if (abs_offset % MVHD_SECTOR_SIZE != 0) {
/* Yikes! We're supposed to be on a sector boundary. Add some padding */
int64_t padding_amount = (int64_t)MVHD_SECTOR_SIZE - (abs_offset % MVHD_SECTOR_SIZE);
uint8_t zero_byte = 0;
for (int i = 0; i < padding_amount; i++) {
fwrite(&zero_byte, sizeof zero_byte, 1, vhdm->f);
}
abs_offset += padding_amount;
}
uint32_t sect_offset = (uint32_t)(abs_offset / MVHD_SECTOR_SIZE);
int blk_size_sectors = vhdm->sparse.block_sz / MVHD_SECTOR_SIZE;
mvhd_write_empty_sectors(vhdm->f, vhdm->bitmap.sector_count + blk_size_sectors);
/* Add a bit of padding. That's what Windows appears to do, although it's not strictly necessary... */
mvhd_write_empty_sectors(vhdm->f, 5);
/* And we finish with the footer */
fwrite(footer, sizeof footer, 1, vhdm->f);
/* We no longer have a sparse block. Update that BAT! */
vhdm->block_offset[blk] = sect_offset;
mvhd_write_bat_entry(vhdm, blk);
}
int mvhd_fixed_read(MVHDMeta* vhdm, uint32_t offset, int num_sectors, void* out_buff) {
int64_t addr;
int transfer_sectors, truncated_sectors;
uint32_t total_sectors = (uint32_t)(vhdm->footer.curr_sz / MVHD_SECTOR_SIZE);
mvhd_check_sectors(offset, num_sectors, total_sectors, &transfer_sectors, &truncated_sectors);
addr = (int64_t)offset * MVHD_SECTOR_SIZE;
mvhd_fseeko64(vhdm->f, addr, SEEK_SET);
fread(out_buff, transfer_sectors*MVHD_SECTOR_SIZE, 1, vhdm->f);
return truncated_sectors;
}
int mvhd_sparse_read(MVHDMeta* vhdm, uint32_t offset, int num_sectors, void* out_buff) {
int transfer_sectors, truncated_sectors;
uint32_t total_sectors = (uint32_t)(vhdm->footer.curr_sz / MVHD_SECTOR_SIZE);
mvhd_check_sectors(offset, num_sectors, total_sectors, &transfer_sectors, &truncated_sectors);
uint8_t* buff = (uint8_t*)out_buff;
int64_t addr;
uint32_t s, ls;
int blk, prev_blk, sib;
ls = offset + transfer_sectors;
prev_blk = -1;
for (s = offset; s < ls; s++) {
blk = s / vhdm->sect_per_block;
sib = s % vhdm->sect_per_block;
if (blk != prev_blk) {
prev_blk = blk;
if (vhdm->bitmap.curr_block != blk) {
mvhd_read_sect_bitmap(vhdm, blk);
mvhd_fseeko64(vhdm->f, (uint64_t)sib * MVHD_SECTOR_SIZE, SEEK_CUR);
} else {
addr = ((int64_t)vhdm->block_offset[blk] + vhdm->bitmap.sector_count + sib) * MVHD_SECTOR_SIZE;
mvhd_fseeko64(vhdm->f, addr, SEEK_SET);
}
}
if (VHD_TESTBIT(vhdm->bitmap.curr_bitmap, sib)) {
fread(buff, MVHD_SECTOR_SIZE, 1, vhdm->f);
} else {
memset(buff, 0, MVHD_SECTOR_SIZE);
mvhd_fseeko64(vhdm->f, MVHD_SECTOR_SIZE, SEEK_CUR);
}
buff += MVHD_SECTOR_SIZE;
}
return truncated_sectors;
}
int mvhd_diff_read(MVHDMeta* vhdm, uint32_t offset, int num_sectors, void* out_buff) {
int transfer_sectors, truncated_sectors;
uint32_t total_sectors = (uint32_t)(vhdm->footer.curr_sz / MVHD_SECTOR_SIZE);
mvhd_check_sectors(offset, num_sectors, total_sectors, &transfer_sectors, &truncated_sectors);
uint8_t* buff = (uint8_t*)out_buff;
MVHDMeta* curr_vhdm = vhdm;
uint32_t s, ls;
int blk, sib;
ls = offset + transfer_sectors;
for (s = offset; s < ls; s++) {
while (curr_vhdm->footer.disk_type == MVHD_TYPE_DIFF) {
blk = s / curr_vhdm->sect_per_block;
sib = s % curr_vhdm->sect_per_block;
if (curr_vhdm->bitmap.curr_block != blk) {
mvhd_read_sect_bitmap(curr_vhdm, blk);
}
if (!VHD_TESTBIT(curr_vhdm->bitmap.curr_bitmap, sib)) {
curr_vhdm = curr_vhdm->parent;
} else { break; }
}
/* We handle actual sector reading using the fixed or sparse functions,
as a differencing VHD is also a sparse VHD */
if (curr_vhdm->footer.disk_type == MVHD_TYPE_DIFF || curr_vhdm->footer.disk_type == MVHD_TYPE_DYNAMIC) {
mvhd_sparse_read(curr_vhdm, s, 1, buff);
} else {
mvhd_fixed_read(curr_vhdm, s, 1, buff);
}
curr_vhdm = vhdm;
buff += MVHD_SECTOR_SIZE;
}
return truncated_sectors;
}
int mvhd_fixed_write(MVHDMeta* vhdm, uint32_t offset, int num_sectors, void* in_buff) {
int64_t addr;
int transfer_sectors, truncated_sectors;
uint32_t total_sectors = (uint32_t)(vhdm->footer.curr_sz / MVHD_SECTOR_SIZE);
mvhd_check_sectors(offset, num_sectors, total_sectors, &transfer_sectors, &truncated_sectors);
addr = (int64_t)offset * MVHD_SECTOR_SIZE;
mvhd_fseeko64(vhdm->f, addr, SEEK_SET);
fwrite(in_buff, transfer_sectors*MVHD_SECTOR_SIZE, 1, vhdm->f);
return truncated_sectors;
}
int mvhd_sparse_diff_write(MVHDMeta* vhdm, uint32_t offset, int num_sectors, void* in_buff) {
int transfer_sectors, truncated_sectors;
uint32_t total_sectors = (uint32_t)(vhdm->footer.curr_sz / MVHD_SECTOR_SIZE);
mvhd_check_sectors(offset, num_sectors, total_sectors, &transfer_sectors, &truncated_sectors);
uint8_t* buff = (uint8_t*)in_buff;
int64_t addr;
uint32_t s, ls;
int blk, prev_blk, sib;
ls = offset + transfer_sectors;
prev_blk = -1;
for (s = offset; s < ls; s++) {
blk = s / vhdm->sect_per_block;
sib = s % vhdm->sect_per_block;
if (vhdm->block_offset[blk] == MVHD_SPARSE_BLK) {
/* "read" the sector bitmap first, before creating a new block, as the bitmap will be
zero either way */
mvhd_read_sect_bitmap(vhdm, blk);
mvhd_create_block(vhdm, blk);
}
if (blk != prev_blk) {
if (vhdm->bitmap.curr_block != blk) {
if (prev_blk >= 0) {
/* Write the sector bitmap for the previous block, before we replace it. */
mvhd_write_curr_sect_bitmap(vhdm);
}
mvhd_read_sect_bitmap(vhdm, blk);
mvhd_fseeko64(vhdm->f, (uint64_t)sib * MVHD_SECTOR_SIZE, SEEK_CUR);
} else {
addr = ((int64_t)vhdm->block_offset[blk] + vhdm->bitmap.sector_count + sib) * MVHD_SECTOR_SIZE;
mvhd_fseeko64(vhdm->f, addr, SEEK_SET);
}
prev_blk = blk;
}
fwrite(buff, MVHD_SECTOR_SIZE, 1, vhdm->f);
VHD_SETBIT(vhdm->bitmap.curr_bitmap, sib);
buff += MVHD_SECTOR_SIZE;
}
/* And write the sector bitmap for the last block we visited to disk */
mvhd_write_curr_sect_bitmap(vhdm);
return truncated_sectors;
}
int mvhd_noop_write(MVHDMeta* vhdm, uint32_t offset, int num_sectors, void* in_buff) {
return 0;
}

View File

@ -0,0 +1,132 @@
#ifndef MINIVHD_IO_H
#define MINIVHD_IO_H
#include "minivhd.h"
/**
* \brief Write zero filled sectors to file.
*
* Note, the caller should set the file position before calling this
* function for correct operation.
*
* \param [in] f File to write sectors to
* \param [in] sector_count The number of sectors to write
*/
void mvhd_write_empty_sectors(FILE* f, int sector_count);
/**
* \brief Read a fixed VHD image
*
* Fixed VHD images are essentially raw image files with a footer tacked on
* the end. They are therefore straightforward to write
*
* \param [in] vhdm MiniVHD data structure
* \param [in] offset Sector offset to read from
* \param [in] num_sectors The desired number of sectors to read
* \param [out] out_buff An output buffer to store read sectors. Must be
* large enough to hold num_sectors worth of sectors.
*
* \retval 0 num_sectors were read from file
* \retval >0 < num_sectors were read from file
*/
int mvhd_fixed_read(MVHDMeta* vhdm, uint32_t offset, int num_sectors, void* out_buff);
/**
* \brief Read a sparse VHD image
*
* Sparse, or dynamic images are VHD images that grow as data is written to them.
*
* This function implements the logic to read sectors from the file, taking into
* account the fact that blocks may be stored on disk in any order, and that the
* read could cross block boundaries.
*
* \param [in] vhdm MiniVHD data structure
* \param [in] offset Sector offset to read from
* \param [in] num_sectors The desired number of sectors to read
* \param [out] out_buff An output buffer to store read sectors. Must be
* large enough to hold num_sectors worth of sectors.
*
* \retval 0 num_sectors were read from file
* \retval >0 < num_sectors were read from file
*/
int mvhd_sparse_read(MVHDMeta* vhdm, uint32_t offset, int num_sectors, void* out_buff);
/**
* \brief Read a differencing VHD image
*
* Differencing images are a variant of a sparse image. They contain the grow-on-demand
* properties of sparse images, but also reference a parent image. Data is read from the
* child image only if it is newer than the data stored in the parent image.
*
* This function implements the logic to read sectors from the child, or a parent image.
* Differencing images may have a differencing image as a parent, creating a chain of images.
* There is no theoretical chain length limit, although I do not consider long chains to be
* advisable. Verifying the parent-child relationship is not very robust.
*
* \param [in] vhdm MiniVHD data structure
* \param [in] offset Sector offset to read from
* \param [in] num_sectors The desired number of sectors to read
* \param [out] out_buff An output buffer to store read sectors. Must be
* large enough to hold num_sectors worth of sectors.
*
* \retval 0 num_sectors were read from file
* \retval >0 < num_sectors were read from file
*/
int mvhd_diff_read(MVHDMeta* vhdm, uint32_t offset, int num_sectors, void* out_buff);
/**
* \brief Write to a fixed VHD image
*
* Fixed VHD images are essentially raw image files with a footer tacked on
* the end. They are therefore straightforward to write
*
* \param [in] vhdm MiniVHD data structure
* \param [in] offset Sector offset to write to
* \param [in] num_sectors The desired number of sectors to write
* \param [in] in_buff A source buffer to write sectors from. Must be
* large enough to hold num_sectors worth of sectors.
*
* \retval 0 num_sectors were written to file
* \retval >0 < num_sectors were written to file
*/
int mvhd_fixed_write(MVHDMeta* vhdm, uint32_t offset, int num_sectors, void* in_buff);
/**
* \brief Write to a sparse or differencing VHD image
*
* Sparse, or dynamic images are VHD images that grow as data is written to them.
*
* Differencing images are a variant of a sparse image. They contain the grow-on-demand
* properties of sparse images, but also reference a parent image. Data is always written
* to the child image. This makes writing to differencing images essentially identical to
* writing to sparse images, hence they use the same function.
*
* This function implements the logic to write sectors to the file, taking into
* account the fact that blocks may be stored on disk in any order, and that the
* write operation could cross block boundaries.
*
* \param [in] vhdm MiniVHD data structure
* \param [in] offset Sector offset to write to
* \param [in] num_sectors The desired number of sectors to write
* \param [in] in_buff A source buffer to write sectors from. Must be
* large enough to hold num_sectors worth of sectors.
*
* \retval 0 num_sectors were written to file
* \retval >0 < num_sectors were written to file
*/
int mvhd_sparse_diff_write(MVHDMeta* vhdm, uint32_t offset, int num_sectors, void* in_buff);
/**
* \brief A no-op function to "write" to read-only VHD images
*
* \param [in] vhdm MiniVHD data structure
* \param [in] offset Sector offset to write to
* \param [in] num_sectors The desired number of sectors to write
* \param [in] in_buff A source buffer to write sectors from. Must be
* large enough to hold num_sectors worth of sectors.
*
* \retval 0 num_sectors were written to file
* \retval >0 < num_sectors were written to file
*/
int mvhd_noop_write(MVHDMeta* vhdm, uint32_t offset, int num_sectors, void* in_buff);
#endif

View File

@ -0,0 +1,533 @@
/**
* \file
* \brief VHD management functions (open, close, read write etc)
*/
#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include "cwalk.h"
#include "libxml2_encoding.h"
#include "minivhd_internal.h"
#include "minivhd_io.h"
#include "minivhd_util.h"
#include "minivhd_struct_rw.h"
#include "minivhd.h"
int mvhd_errno = 0;
static char tmp_open_path[MVHD_MAX_PATH_BYTES] = {0};
struct MVHDPaths {
char dir_path[MVHD_MAX_PATH_BYTES];
char file_name[MVHD_MAX_PATH_BYTES];
char w2ku_path[MVHD_MAX_PATH_BYTES];
char w2ru_path[MVHD_MAX_PATH_BYTES];
char joined_path[MVHD_MAX_PATH_BYTES];
uint16_t tmp_src_path[MVHD_MAX_PATH_CHARS];
};
static void mvhd_read_footer(MVHDMeta* vhdm);
static void mvhd_read_sparse_header(MVHDMeta* vhdm);
static bool mvhd_footer_checksum_valid(MVHDMeta* vhdm);
static bool mvhd_sparse_checksum_valid(MVHDMeta* vhdm);
static int mvhd_read_bat(MVHDMeta *vhdm, MVHDError* err);
static void mvhd_calc_sparse_values(MVHDMeta* vhdm);
static int mvhd_init_sector_bitmap(MVHDMeta* vhdm, MVHDError* err);
/**
* \brief Populate data stuctures with content from a VHD footer
*
* \param [in] vhdm MiniVHD data structure
*/
static void mvhd_read_footer(MVHDMeta* vhdm) {
uint8_t buffer[MVHD_FOOTER_SIZE];
mvhd_fseeko64(vhdm->f, -MVHD_FOOTER_SIZE, SEEK_END);
fread(buffer, sizeof buffer, 1, vhdm->f);
mvhd_buffer_to_footer(&vhdm->footer, buffer);
}
/**
* \brief Populate data stuctures with content from a VHD sparse header
*
* \param [in] vhdm MiniVHD data structure
*/
static void mvhd_read_sparse_header(MVHDMeta* vhdm) {
uint8_t buffer[MVHD_SPARSE_SIZE];
mvhd_fseeko64(vhdm->f, vhdm->footer.data_offset, SEEK_SET);
fread(buffer, sizeof buffer, 1, vhdm->f);
mvhd_buffer_to_header(&vhdm->sparse, buffer);
}
/**
* \brief Validate VHD footer checksum
*
* This works by generating a checksum from the footer, and comparing it against the stored checksum.
*
* \param [in] vhdm MiniVHD data structure
*/
static bool mvhd_footer_checksum_valid(MVHDMeta* vhdm) {
return vhdm->footer.checksum == mvhd_gen_footer_checksum(&vhdm->footer);
}
/**
* \brief Validate VHD sparse header checksum
*
* This works by generating a checksum from the sparse header, and comparing it against the stored checksum.
*
* \param [in] vhdm MiniVHD data structure
*/
static bool mvhd_sparse_checksum_valid(MVHDMeta* vhdm) {
return vhdm->sparse.checksum == mvhd_gen_sparse_checksum(&vhdm->sparse);
}
/**
* \brief Read BAT into MiniVHD data structure
*
* The Block Allocation Table (BAT) is the structure in a sparse and differencing VHD which stores
* the 4-byte sector offsets for each data block. This function allocates enough memory to contain
* the entire BAT, and then reads the contents of the BAT into the buffer.
*
* \param [in] vhdm MiniVHD data structure
* \param [out] err this is populated with MVHD_ERR_MEM if the calloc fails
*
* \retval -1 if an error occurrs. Check value of err in this case
* \retval 0 if the function call succeeds
*/
static int mvhd_read_bat(MVHDMeta *vhdm, MVHDError* err) {
vhdm->block_offset = calloc(vhdm->sparse.max_bat_ent, sizeof *vhdm->block_offset);
if (vhdm->block_offset == NULL) {
*err = MVHD_ERR_MEM;
return -1;
}
mvhd_fseeko64(vhdm->f, vhdm->sparse.bat_offset, SEEK_SET);
for (uint32_t i = 0; i < vhdm->sparse.max_bat_ent; i++) {
fread(&vhdm->block_offset[i], sizeof *vhdm->block_offset, 1, vhdm->f);
vhdm->block_offset[i] = mvhd_from_be32(vhdm->block_offset[i]);
}
return 0;
}
/**
* \brief Perform a one-time calculation of some sparse VHD values
*
* \param [in] vhdm MiniVHD data structure
*/
static void mvhd_calc_sparse_values(MVHDMeta* vhdm) {
vhdm->sect_per_block = vhdm->sparse.block_sz / MVHD_SECTOR_SIZE;
int bm_bytes = vhdm->sect_per_block / 8;
vhdm->bitmap.sector_count = bm_bytes / MVHD_SECTOR_SIZE;
if (bm_bytes % MVHD_SECTOR_SIZE > 0) {
vhdm->bitmap.sector_count++;
}
}
/**
* \brief Allocate memory for a sector bitmap.
*
* Each data block is preceded by a sector bitmap. Each bit indicates whether the corresponding sector
* is considered 'clean' or 'dirty' (for sparse VHD images), or whether to read from the parent or current
* image (for differencing images).
*
* \param [in] vhdm MiniVHD data structure
* \param [out] err this is populated with MVHD_ERR_MEM if the calloc fails
*
* \retval -1 if an error occurrs. Check value of err in this case
* \retval 0 if the function call succeeds
*/
static int mvhd_init_sector_bitmap(MVHDMeta* vhdm, MVHDError* err) {
vhdm->bitmap.curr_bitmap = calloc(vhdm->bitmap.sector_count, MVHD_SECTOR_SIZE);
if (vhdm->bitmap.curr_bitmap == NULL) {
*err = MVHD_ERR_MEM;
return -1;
}
vhdm->bitmap.curr_block = -1;
return 0;
}
/**
* \brief Check if the path for a given platform code exists
*
* From the available paths, both relative and absolute, construct a full path
* and attempt to open a file at that path.
*
* Note, this function makes no attempt to verify that the path is the correct
* VHD image, or even a VHD image at all.
*
* \param [in] paths a struct containing all available paths to work with
* \param [in] the platform code to try and obtain a path for. Setting this to zero
* will try using the directory of the child image
*
* \retval true if a file is found
* \retval false if a file is not found
*/
static bool mvhd_parent_path_exists(struct MVHDPaths* paths, uint32_t plat_code) {
memset(paths->joined_path, 0, sizeof paths->joined_path);
FILE* f;
int cwk_ret, ferr;
enum cwk_path_style style = cwk_path_guess_style((const char*)paths->dir_path);
cwk_path_set_style(style);
cwk_ret = 1;
if (plat_code == MVHD_DIF_LOC_W2RU && *paths->w2ru_path) {
cwk_ret = cwk_path_join((const char*)paths->dir_path, (const char*)paths->w2ru_path, paths->joined_path, sizeof paths->joined_path);
} else if (plat_code == MVHD_DIF_LOC_W2KU && *paths->w2ku_path) {
memcpy(paths->joined_path, paths->w2ku_path, (sizeof paths->joined_path) - 1);
cwk_ret = 0;
} else if (plat_code == 0) {
cwk_ret = cwk_path_join((const char*)paths->dir_path, (const char*)paths->file_name, paths->joined_path, sizeof paths->joined_path);
}
if (cwk_ret > MVHD_MAX_PATH_BYTES) {
return false;
}
f = mvhd_fopen((const char*)paths->joined_path, "rb", &ferr);
if (f != NULL) {
/* We found a file at the requested path! */
memcpy(tmp_open_path, paths->joined_path, (sizeof paths->joined_path) - 1);
tmp_open_path[sizeof tmp_open_path - 1] = '\0';
fclose(f);
return true;
} else {
return false;
}
}
/**
* \brief attempt to obtain a file path to a file that may be a valid VHD image
*
* Differential VHD images store both a UTF-16BE file name (or path), and up to
* eight "parent locator" entries. Using this information, this function tries to
* find a parent image.
*
* This function does not verify if the path returned is a valid parent image.
*
* \param [in] vhdm current MiniVHD data structure
* \param [out] err any errors that may occurr. Check this if NULL is returned
*
* \return a pointer to the global string `tmp_open_path`, or NULL if a path could
* not be found, or some error occurred
*/
static char* mvhd_get_diff_parent_path(MVHDMeta* vhdm, int* err) {
int utf_outlen, utf_inlen, utf_ret;
char* par_fp = NULL;
/* We can't resolve relative paths if we don't have an absolute
path to work with */
if (!cwk_path_is_absolute((const char*)vhdm->filename)) {
*err = MVHD_ERR_PATH_REL;
goto end;
}
struct MVHDPaths* paths = calloc(1, sizeof *paths);
if (paths == NULL) {
*err = MVHD_ERR_MEM;
goto end;
}
size_t dirlen;
cwk_path_get_dirname((const char*)vhdm->filename, &dirlen);
if (dirlen >= sizeof paths->dir_path) {
*err = MVHD_ERR_PATH_LEN;
goto paths_cleanup;
}
memcpy(paths->dir_path, vhdm->filename, dirlen);
/* Get the filename field from the sparse header. */
utf_outlen = (int)sizeof paths->file_name;
utf_inlen = (int)sizeof vhdm->sparse.par_utf16_name;
utf_ret = UTF16BEToUTF8((unsigned char*)paths->file_name, &utf_outlen, (const unsigned char*)vhdm->sparse.par_utf16_name, &utf_inlen);
if (utf_ret < 0) {
mvhd_set_encoding_err(utf_ret, err);
goto paths_cleanup;
}
/* Now read the parent locator entries, both relative and absolute, if they exist */
unsigned char* loc_path;
for (int i = 0; i < 8; i++) {
utf_outlen = MVHD_MAX_PATH_BYTES - 1;
if (vhdm->sparse.par_loc_entry[i].plat_code == MVHD_DIF_LOC_W2RU) {
loc_path = (unsigned char*)paths->w2ru_path;
} else if (vhdm->sparse.par_loc_entry[i].plat_code == MVHD_DIF_LOC_W2KU) {
loc_path = (unsigned char*)paths->w2ku_path;
} else {
continue;
}
utf_inlen = vhdm->sparse.par_loc_entry[i].plat_data_len;
if (utf_inlen > MVHD_MAX_PATH_BYTES) {
*err = MVHD_ERR_PATH_LEN;
goto paths_cleanup;
}
mvhd_fseeko64(vhdm->f, vhdm->sparse.par_loc_entry[i].plat_data_offset, SEEK_SET);
fread(paths->tmp_src_path, sizeof (uint8_t), utf_inlen, vhdm->f);
/* Note, the W2*u parent locators are UTF-16LE, unlike the filename field previously obtained,
which is UTF-16BE */
utf_ret = UTF16LEToUTF8(loc_path, &utf_outlen, (const unsigned char*)paths->tmp_src_path, &utf_inlen);
if (utf_ret < 0) {
mvhd_set_encoding_err(utf_ret, err);
goto paths_cleanup;
}
}
/* We have paths in UTF-8. We should have enough info to try and find the parent VHD */
/* Does the relative path exist? */
if (mvhd_parent_path_exists(paths, MVHD_DIF_LOC_W2RU)) {
par_fp = tmp_open_path;
goto paths_cleanup;
}
/* What about trying the child directory? */
if (mvhd_parent_path_exists(paths, 0)) {
par_fp = tmp_open_path;
goto paths_cleanup;
}
/* Well, all else fails, try the stored absolute path, if it exists */
if (mvhd_parent_path_exists(paths, MVHD_DIF_LOC_W2KU)) {
par_fp = tmp_open_path;
goto paths_cleanup;
}
/* If we reach this point, we could not find a path with a valid file */
par_fp = NULL;
*err = MVHD_ERR_PAR_NOT_FOUND;
paths_cleanup:
free(paths);
paths = NULL;
end:
return par_fp;
}
/**
* \brief Attach the read/write function pointers to read/write functions
*
* Depending on the VHD type, different sector reading and writing functions are used.
* The functions are called via function pointers stored in the vhdm struct.
*
* \param [in] vhdm MiniVHD data structure
*/
static void mvhd_assign_io_funcs(MVHDMeta* vhdm) {
switch (vhdm->footer.disk_type) {
case MVHD_TYPE_FIXED:
vhdm->read_sectors = mvhd_fixed_read;
vhdm->write_sectors = mvhd_fixed_write;
break;
case MVHD_TYPE_DYNAMIC:
vhdm->read_sectors = mvhd_sparse_read;
vhdm->write_sectors = mvhd_sparse_diff_write;
break;
case MVHD_TYPE_DIFF:
vhdm->read_sectors = mvhd_diff_read;
vhdm->write_sectors = mvhd_sparse_diff_write;
break;
}
if (vhdm->readonly) {
vhdm->write_sectors = mvhd_noop_write;
}
}
bool mvhd_file_is_vhd(FILE* f) {
if (f) {
uint8_t con_str[8];
mvhd_fseeko64(f, -MVHD_FOOTER_SIZE, SEEK_END);
fread(con_str, sizeof con_str, 1, f);
return mvhd_is_conectix_str(con_str);
} else {
return false;
}
}
MVHDGeom mvhd_calculate_geometry(uint64_t size) {
MVHDGeom chs;
uint32_t ts = (uint32_t)(size / MVHD_SECTOR_SIZE);
uint32_t spt, heads, cyl, cth;
if (ts > 65535 * 16 * 255) {
ts = 65535 * 16 * 255;
}
if (ts >= 65535 * 16 * 63) {
spt = 255;
heads = 16;
cth = ts / spt;
} else {
spt = 17;
cth = ts / spt;
heads = (cth + 1023) / 1024;
if (heads < 4) {
heads = 4;
}
if (cth >= (heads * 1024) || heads > 16) {
spt = 31;
heads = 16;
cth = ts / spt;
}
if (cth >= (heads * 1024)) {
spt = 63;
heads = 16;
cth = ts / spt;
}
}
cyl = cth / heads;
chs.heads = heads;
chs.spt = spt;
chs.cyl = cyl;
return chs;
}
MVHDMeta* mvhd_open(const char* path, bool readonly, int* err) {
MVHDError open_err;
MVHDMeta *vhdm = calloc(sizeof *vhdm, 1);
if (vhdm == NULL) {
*err = MVHD_ERR_MEM;
goto end;
}
if (strlen(path) >= sizeof vhdm->filename) {
*err = MVHD_ERR_PATH_LEN;
goto cleanup_vhdm;
}
//This is safe, as we've just checked for potential overflow above
strcpy(vhdm->filename, path);
vhdm->f = readonly ? mvhd_fopen((const char*)vhdm->filename, "rb", err) : mvhd_fopen((const char*)vhdm->filename, "rb+", err);
if (vhdm->f == NULL) {
/* note, mvhd_fopen sets err for us */
goto cleanup_vhdm;
}
vhdm->readonly = readonly;
if (!mvhd_file_is_vhd(vhdm->f)) {
*err = MVHD_ERR_NOT_VHD;
goto cleanup_file;
}
mvhd_read_footer(vhdm);
if (!mvhd_footer_checksum_valid(vhdm)) {
*err = MVHD_ERR_FOOTER_CHECKSUM;
goto cleanup_file;
}
if (vhdm->footer.disk_type == MVHD_TYPE_DIFF || vhdm->footer.disk_type == MVHD_TYPE_DYNAMIC) {
mvhd_read_sparse_header(vhdm);
if (!mvhd_sparse_checksum_valid(vhdm)) {
*err = MVHD_ERR_SPARSE_CHECKSUM;
goto cleanup_file;
}
if (mvhd_read_bat(vhdm, &open_err) == -1) {
*err = open_err;
goto cleanup_file;
}
mvhd_calc_sparse_values(vhdm);
if (mvhd_init_sector_bitmap(vhdm, &open_err) == -1) {
*err = open_err;
goto cleanup_bat;
}
} else if (vhdm->footer.disk_type != MVHD_TYPE_FIXED) {
*err = MVHD_ERR_TYPE;
goto cleanup_bitmap;
}
mvhd_assign_io_funcs(vhdm);
vhdm->format_buffer.zero_data = calloc(64, MVHD_SECTOR_SIZE);
if (vhdm->format_buffer.zero_data == NULL) {
*err = MVHD_ERR_MEM;
goto cleanup_bitmap;
}
vhdm->format_buffer.sector_count = 64;
if (vhdm->footer.disk_type == MVHD_TYPE_DIFF) {
char* par_path = mvhd_get_diff_parent_path(vhdm, err);
if (par_path == NULL) {
goto cleanup_format_buff;
}
uint32_t par_mod_ts = mvhd_file_mod_timestamp(par_path, err);
if (*err != 0) {
goto cleanup_format_buff;
}
if (vhdm->sparse.par_timestamp != par_mod_ts) {
/* The last-modified timestamp is to fragile to make this a fatal error.
Instead, we inform the caller of the potential problem. */
*err = MVHD_ERR_TIMESTAMP;
}
vhdm->parent = mvhd_open(par_path, true, err);
if (vhdm->parent == NULL) {
goto cleanup_format_buff;
}
if (memcmp(vhdm->sparse.par_uuid, vhdm->parent->footer.uuid, sizeof vhdm->sparse.par_uuid) != 0) {
*err = MVHD_ERR_INVALID_PAR_UUID;
goto cleanup_format_buff;
}
}
/* If we've reached this point, we are good to go, so skip the cleanup steps */
goto end;
cleanup_format_buff:
free(vhdm->format_buffer.zero_data);
vhdm->format_buffer.zero_data = NULL;
cleanup_bitmap:
free(vhdm->bitmap.curr_bitmap);
vhdm->bitmap.curr_bitmap = NULL;
cleanup_bat:
free(vhdm->block_offset);
vhdm->block_offset = NULL;
cleanup_file:
fclose(vhdm->f);
vhdm->f = NULL;
cleanup_vhdm:
free(vhdm);
vhdm = NULL;
end:
return vhdm;
}
void mvhd_close(MVHDMeta* vhdm) {
if (vhdm != NULL) {
if (vhdm->parent != NULL) {
mvhd_close(vhdm->parent);
}
fclose(vhdm->f);
if (vhdm->block_offset != NULL) {
free(vhdm->block_offset);
vhdm->block_offset = NULL;
}
if (vhdm->bitmap.curr_bitmap != NULL) {
free(vhdm->bitmap.curr_bitmap);
vhdm->bitmap.curr_bitmap = NULL;
}
if (vhdm->format_buffer.zero_data != NULL) {
free(vhdm->format_buffer.zero_data);
vhdm->format_buffer.zero_data = NULL;
}
free(vhdm);
vhdm = NULL;
}
}
int mvhd_diff_update_par_timestamp(MVHDMeta* vhdm, int* err) {
uint8_t sparse_buff[1024];
if (vhdm == NULL || err == NULL) {
*err = MVHD_ERR_INVALID_PARAMS;
return -1;
}
if (vhdm->footer.disk_type != MVHD_TYPE_DIFF) {
*err = MVHD_ERR_TYPE;
return -1;
}
char* par_path = mvhd_get_diff_parent_path(vhdm, err);
if (par_path == NULL) {
return -1;
}
uint32_t par_mod_ts = mvhd_file_mod_timestamp(par_path, err);
if (*err != 0) {
return -1;
}
/* Update the timestamp and sparse header checksum */
vhdm->sparse.par_timestamp = par_mod_ts;
vhdm->sparse.checksum = mvhd_gen_sparse_checksum(&vhdm->sparse);
/* Generate and write the updated sparse header */
mvhd_header_to_buffer(&vhdm->sparse, sparse_buff);
mvhd_fseeko64(vhdm->f, (int64_t)vhdm->footer.data_offset, SEEK_SET);
fwrite(sparse_buff, sizeof sparse_buff, 1, vhdm->f);
return 0;
}
int mvhd_read_sectors(MVHDMeta* vhdm, uint32_t offset, int num_sectors, void* out_buff) {
return vhdm->read_sectors(vhdm, offset, num_sectors, out_buff);
}
int mvhd_write_sectors(MVHDMeta* vhdm, uint32_t offset, int num_sectors, void* in_buff) {
return vhdm->write_sectors(vhdm, offset, num_sectors, in_buff);
}
int mvhd_format_sectors(MVHDMeta* vhdm, uint32_t offset, int num_sectors) {
int num_full = num_sectors / vhdm->format_buffer.sector_count;
int remain = num_sectors % vhdm->format_buffer.sector_count;
for (int i = 0; i < num_full; i++) {
vhdm->write_sectors(vhdm, offset, vhdm->format_buffer.sector_count, vhdm->format_buffer.zero_data);
offset += vhdm->format_buffer.sector_count;
}
vhdm->write_sectors(vhdm, offset, remain, vhdm->format_buffer.zero_data);
return 0;
}

View File

@ -0,0 +1,165 @@
/**
* \file
* \brief Header and footer serialize/deserialize functions
*/
#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include "minivhd_util.h"
#include "minivhd_internal.h"
/* Read data from footer into the struct members, swapping endian where necessary
Note: order matters here! We must read each field in the order the struct is in.
Doing this may be less elegant than performing a memcpy to a packed struct, but
it avoids potential data alignment issues, and the endian swapping allows us to
use the fields directly. */
static void mvhd_next_buffer_to_struct(void* struct_memb, size_t memb_size, bool req_endian, uint8_t** buffer);
static void mvhd_next_struct_to_buffer(void* struct_memb, size_t memb_size, bool req_endian, uint8_t** buffer);
/**
* \brief Get the next field from a buffer and store it in a struct member, converting endian if necessary
*
* \param [out] struct_memb struct member to save the field to
* \param [in] memb_size the size of struct_memb, in bytes
* \param [in] req_endian is the field a value that requires endian conversion (eg: uint16, uint32)
* \param [in] buffer the buffer from which fields are read from. Will be advanced at the end of the function call
*/
static void mvhd_next_buffer_to_struct(void* struct_memb, size_t memb_size, bool req_endian, uint8_t** buffer) {
memcpy(struct_memb, *buffer, memb_size);
if (req_endian) {
switch (memb_size) {
case 2:
*(uint16_t*)(struct_memb) = mvhd_from_be16(*(uint16_t*)(struct_memb));
break;
case 4:
*(uint32_t*)(struct_memb) = mvhd_from_be32(*(uint32_t*)(struct_memb));
break;
case 8:
*(uint64_t*)(struct_memb) = mvhd_from_be64(*(uint64_t*)(struct_memb));
break;
}
}
*buffer += memb_size;
}
/**
* \brief Save a struct member into a buffer, converting endian if necessary
*
* \param [in] struct_memb struct member read from
* \param [in] memb_size the size of struct_memb, in bytes
* \param [in] req_endian is the field a value that requires endian conversion (eg: uint16, uint32)
* \param [out] buffer the buffer from which struct member is saved to. Will be advanced at the end of the function call
*/
static void mvhd_next_struct_to_buffer(void* struct_memb, size_t memb_size, bool req_endian, uint8_t** buffer) {
uint8_t *buf_ptr = *buffer;
memcpy(buf_ptr, struct_memb, memb_size);
if (req_endian) {
switch (memb_size) {
case 2:
*((uint16_t*)buf_ptr) = mvhd_to_be16(*(uint16_t*)(struct_memb));
break;
case 4:
*((uint32_t*)buf_ptr) = mvhd_to_be32(*(uint32_t*)(struct_memb));
break;
case 8:
*((uint64_t*)buf_ptr) = mvhd_to_be64(*(uint64_t*)(struct_memb));
break;
}
}
buf_ptr += memb_size;
*buffer = buf_ptr;
}
void mvhd_buffer_to_footer(MVHDFooter* footer, uint8_t* buffer) {
uint8_t* buff_ptr = buffer;
mvhd_next_buffer_to_struct(&footer->cookie, sizeof footer->cookie, false, &buff_ptr);
mvhd_next_buffer_to_struct(&footer->features, sizeof footer->features, true, &buff_ptr);
mvhd_next_buffer_to_struct(&footer->fi_fmt_vers, sizeof footer->fi_fmt_vers, true, &buff_ptr);
mvhd_next_buffer_to_struct(&footer->data_offset, sizeof footer->data_offset, true, &buff_ptr);
mvhd_next_buffer_to_struct(&footer->timestamp, sizeof footer->timestamp, true, &buff_ptr);
mvhd_next_buffer_to_struct(&footer->cr_app, sizeof footer->cr_app, false, &buff_ptr);
mvhd_next_buffer_to_struct(&footer->cr_vers, sizeof footer->cr_vers, true, &buff_ptr);
mvhd_next_buffer_to_struct(&footer->cr_host_os, sizeof footer->cr_host_os, false, &buff_ptr);
mvhd_next_buffer_to_struct(&footer->orig_sz, sizeof footer->orig_sz, true, &buff_ptr);
mvhd_next_buffer_to_struct(&footer->curr_sz, sizeof footer->curr_sz, true, &buff_ptr);
mvhd_next_buffer_to_struct(&footer->geom.cyl, sizeof footer->geom.cyl, true, &buff_ptr);
mvhd_next_buffer_to_struct(&footer->geom.heads, sizeof footer->geom.heads, false, &buff_ptr);
mvhd_next_buffer_to_struct(&footer->geom.spt, sizeof footer->geom.spt, false, &buff_ptr);
mvhd_next_buffer_to_struct(&footer->disk_type, sizeof footer->disk_type, true, &buff_ptr);
mvhd_next_buffer_to_struct(&footer->checksum, sizeof footer->checksum, true, &buff_ptr);
mvhd_next_buffer_to_struct(&footer->uuid, sizeof footer->uuid, false, &buff_ptr);
mvhd_next_buffer_to_struct(&footer->saved_st, sizeof footer->saved_st, false, &buff_ptr);
mvhd_next_buffer_to_struct(&footer->reserved, sizeof footer->reserved, false, &buff_ptr);
}
void mvhd_footer_to_buffer(MVHDFooter* footer, uint8_t* buffer) {
uint8_t* buff_ptr = buffer;
mvhd_next_struct_to_buffer(&footer->cookie, sizeof footer->cookie, false, &buff_ptr);
mvhd_next_struct_to_buffer(&footer->features, sizeof footer->features, true, &buff_ptr);
mvhd_next_struct_to_buffer(&footer->fi_fmt_vers, sizeof footer->fi_fmt_vers, true, &buff_ptr);
mvhd_next_struct_to_buffer(&footer->data_offset, sizeof footer->data_offset, true, &buff_ptr);
mvhd_next_struct_to_buffer(&footer->timestamp, sizeof footer->timestamp, true, &buff_ptr);
mvhd_next_struct_to_buffer(&footer->cr_app, sizeof footer->cr_app, false, &buff_ptr);
mvhd_next_struct_to_buffer(&footer->cr_vers, sizeof footer->cr_vers, true, &buff_ptr);
mvhd_next_struct_to_buffer(&footer->cr_host_os, sizeof footer->cr_host_os, false, &buff_ptr);
mvhd_next_struct_to_buffer(&footer->orig_sz, sizeof footer->orig_sz, true, &buff_ptr);
mvhd_next_struct_to_buffer(&footer->curr_sz, sizeof footer->curr_sz, true, &buff_ptr);
mvhd_next_struct_to_buffer(&footer->geom.cyl, sizeof footer->geom.cyl, true, &buff_ptr);
mvhd_next_struct_to_buffer(&footer->geom.heads, sizeof footer->geom.heads, false, &buff_ptr);
mvhd_next_struct_to_buffer(&footer->geom.spt, sizeof footer->geom.spt, false, &buff_ptr);
mvhd_next_struct_to_buffer(&footer->disk_type, sizeof footer->disk_type, true, &buff_ptr);
mvhd_next_struct_to_buffer(&footer->checksum, sizeof footer->checksum, true, &buff_ptr);
mvhd_next_struct_to_buffer(&footer->uuid, sizeof footer->uuid, false, &buff_ptr);
mvhd_next_struct_to_buffer(&footer->saved_st, sizeof footer->saved_st, false, &buff_ptr);
mvhd_next_struct_to_buffer(&footer->reserved, sizeof footer->reserved, false, &buff_ptr);
}
void mvhd_buffer_to_header(MVHDSparseHeader* header, uint8_t* buffer) {
uint8_t* buff_ptr = buffer;
mvhd_next_buffer_to_struct(&header->cookie, sizeof header->cookie, false, &buff_ptr);
mvhd_next_buffer_to_struct(&header->data_offset, sizeof header->data_offset, true, &buff_ptr);
mvhd_next_buffer_to_struct(&header->bat_offset, sizeof header->bat_offset, true, &buff_ptr);
mvhd_next_buffer_to_struct(&header->head_vers, sizeof header->head_vers, true, &buff_ptr);
mvhd_next_buffer_to_struct(&header->max_bat_ent, sizeof header->max_bat_ent, true, &buff_ptr);
mvhd_next_buffer_to_struct(&header->block_sz, sizeof header->block_sz, true, &buff_ptr);
mvhd_next_buffer_to_struct(&header->checksum, sizeof header->checksum, true, &buff_ptr);
mvhd_next_buffer_to_struct(&header->par_uuid, sizeof header->par_uuid, false, &buff_ptr);
mvhd_next_buffer_to_struct(&header->par_timestamp, sizeof header->par_timestamp, true, &buff_ptr);
mvhd_next_buffer_to_struct(&header->reserved_1, sizeof header->reserved_1, true, &buff_ptr);
mvhd_next_buffer_to_struct(&header->par_utf16_name, sizeof header->par_utf16_name, false, &buff_ptr);
for (int i = 0; i < 8; i++) {
mvhd_next_buffer_to_struct(&header->par_loc_entry[i].plat_code, sizeof header->par_loc_entry[i].plat_code, true, &buff_ptr);
mvhd_next_buffer_to_struct(&header->par_loc_entry[i].plat_data_space, sizeof header->par_loc_entry[i].plat_data_space, true, &buff_ptr);
mvhd_next_buffer_to_struct(&header->par_loc_entry[i].plat_data_len, sizeof header->par_loc_entry[i].plat_data_len, true, &buff_ptr);
mvhd_next_buffer_to_struct(&header->par_loc_entry[i].reserved, sizeof header->par_loc_entry[i].reserved, true, &buff_ptr);
mvhd_next_buffer_to_struct(&header->par_loc_entry[i].plat_data_offset, sizeof header->par_loc_entry[i].plat_data_offset, true, &buff_ptr);
}
mvhd_next_buffer_to_struct(&header->reserved_2, sizeof header->reserved_2, false, &buff_ptr);
}
void mvhd_header_to_buffer(MVHDSparseHeader* header, uint8_t* buffer) {
uint8_t* buff_ptr = buffer;
mvhd_next_struct_to_buffer(&header->cookie, sizeof header->cookie, false, &buff_ptr);
mvhd_next_struct_to_buffer(&header->data_offset, sizeof header->data_offset, true, &buff_ptr);
mvhd_next_struct_to_buffer(&header->bat_offset, sizeof header->bat_offset, true, &buff_ptr);
mvhd_next_struct_to_buffer(&header->head_vers, sizeof header->head_vers, true, &buff_ptr);
mvhd_next_struct_to_buffer(&header->max_bat_ent, sizeof header->max_bat_ent, true, &buff_ptr);
mvhd_next_struct_to_buffer(&header->block_sz, sizeof header->block_sz, true, &buff_ptr);
mvhd_next_struct_to_buffer(&header->checksum, sizeof header->checksum, true, &buff_ptr);
mvhd_next_struct_to_buffer(&header->par_uuid, sizeof header->par_uuid, false, &buff_ptr);
mvhd_next_struct_to_buffer(&header->par_timestamp, sizeof header->par_timestamp, true, &buff_ptr);
mvhd_next_struct_to_buffer(&header->reserved_1, sizeof header->reserved_1, true, &buff_ptr);
mvhd_next_struct_to_buffer(&header->par_utf16_name, sizeof header->par_utf16_name, false, &buff_ptr);
for (int i = 0; i < 8; i++) {
mvhd_next_struct_to_buffer(&header->par_loc_entry[i].plat_code, sizeof header->par_loc_entry[i].plat_code, true, &buff_ptr);
mvhd_next_struct_to_buffer(&header->par_loc_entry[i].plat_data_space, sizeof header->par_loc_entry[i].plat_data_space, true, &buff_ptr);
mvhd_next_struct_to_buffer(&header->par_loc_entry[i].plat_data_len, sizeof header->par_loc_entry[i].plat_data_len, true, &buff_ptr);
mvhd_next_struct_to_buffer(&header->par_loc_entry[i].reserved, sizeof header->par_loc_entry[i].reserved, true, &buff_ptr);
mvhd_next_struct_to_buffer(&header->par_loc_entry[i].plat_data_offset, sizeof header->par_loc_entry[i].plat_data_offset, true, &buff_ptr);
}
mvhd_next_struct_to_buffer(&header->reserved_2, sizeof header->reserved_2, false, &buff_ptr);
}

View File

@ -0,0 +1,38 @@
#ifndef MINIVHD_STRUCT_RW_H
#define minivhd_struct_rw
#include "minivhd_internal.h"
/**
* \brief Save the contents of a VHD footer from a buffer to a struct
*
* \param [out] footer save contents of buffer into footer
* \param [in] buffer VHD footer in raw bytes
*/
void mvhd_buffer_to_footer(MVHDFooter* footer, uint8_t* buffer);
/**
* \brief Save the contents of a VHD sparse header from a buffer to a struct
*
* \param [out] header save contents of buffer into header
* \param [in] buffer VHD header in raw bytes
*/
void mvhd_buffer_to_header(MVHDSparseHeader* header, uint8_t* buffer);
/**
* \brief Save the contents of a VHD footer struct to a buffer
*
* \param [in] footer save contents of struct into buffer
* \param [out] buffer VHD footer in raw bytes
*/
void mvhd_footer_to_buffer(MVHDFooter* footer, uint8_t* buffer);
/**
* \brief Save the contents of a VHD sparse header struct to a buffer
*
* \param [in] header save contents of struct into buffer
* \param [out] buffer VHD sparse header in raw bytes
*/
void mvhd_header_to_buffer(MVHDSparseHeader* header, uint8_t* buffer);
#endif

View File

@ -0,0 +1,323 @@
/**
* \file
* \brief Utility functions
*/
#include <errno.h>
#include <stdlib.h>
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
#include <time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include "libxml2_encoding.h"
#include "minivhd_internal.h"
#include "minivhd_util.h"
const char MVHD_CONECTIX_COOKIE[] = "conectix";
const char MVHD_CREATOR[] = "pcem";
const char MVHD_CREATOR_HOST_OS[] = "Wi2k";
const char MVHD_CXSPARSE_COOKIE[] = "cxsparse";
uint16_t mvhd_from_be16(uint16_t val) {
uint8_t *tmp = (uint8_t*)&val;
uint16_t ret = 0;
ret |= (uint16_t)tmp[0] << 8;
ret |= (uint16_t)tmp[1] << 0;
return ret;
}
uint32_t mvhd_from_be32(uint32_t val) {
uint8_t *tmp = (uint8_t*)&val;
uint32_t ret = 0;
ret = (uint32_t)tmp[0] << 24;
ret |= (uint32_t)tmp[1] << 16;
ret |= (uint32_t)tmp[2] << 8;
ret |= (uint32_t)tmp[3] << 0;
return ret;
}
uint64_t mvhd_from_be64(uint64_t val) {
uint8_t *tmp = (uint8_t*)&val;
uint64_t ret = 0;
ret = (uint64_t)tmp[0] << 56;
ret |= (uint64_t)tmp[1] << 48;
ret |= (uint64_t)tmp[2] << 40;
ret |= (uint64_t)tmp[3] << 32;
ret |= (uint64_t)tmp[4] << 24;
ret |= (uint64_t)tmp[5] << 16;
ret |= (uint64_t)tmp[6] << 8;
ret |= (uint64_t)tmp[7] << 0;
return ret;
}
uint16_t mvhd_to_be16(uint16_t val) {
uint16_t ret = 0;
uint8_t *tmp = (uint8_t*)&ret;
tmp[0] = (val & 0xff00) >> 8;
tmp[1] = (val & 0x00ff) >> 0;
return ret;
}
uint32_t mvhd_to_be32(uint32_t val) {
uint32_t ret = 0;
uint8_t *tmp = (uint8_t*)&ret;
tmp[0] = (val & 0xff000000) >> 24;
tmp[1] = (val & 0x00ff0000) >> 16;
tmp[2] = (val & 0x0000ff00) >> 8;
tmp[3] = (val & 0x000000ff) >> 0;
return ret;
}
uint64_t mvhd_to_be64(uint64_t val) {
uint64_t ret = 0;
uint8_t *tmp = (uint8_t*)&ret;
tmp[0] = (uint8_t)((val & 0xff00000000000000) >> 56);
tmp[1] = (uint8_t)((val & 0x00ff000000000000) >> 48);
tmp[2] = (uint8_t)((val & 0x0000ff0000000000) >> 40);
tmp[3] = (uint8_t)((val & 0x000000ff00000000) >> 32);
tmp[4] = (uint8_t)((val & 0x00000000ff000000) >> 24);
tmp[5] = (uint8_t)((val & 0x0000000000ff0000) >> 16);
tmp[6] = (uint8_t)((val & 0x000000000000ff00) >> 8);
tmp[7] = (uint8_t)((val & 0x00000000000000ff) >> 0);
return ret;
}
bool mvhd_is_conectix_str(const void* buffer) {
if (strncmp(buffer, MVHD_CONECTIX_COOKIE, strlen(MVHD_CONECTIX_COOKIE)) == 0) {
return true;
} else {
return false;
}
}
void mvhd_generate_uuid(uint8_t* uuid)
{
/* We aren't doing crypto here, so using system time as seed should be good enough */
srand((unsigned int)time(0));
for (int n = 0; n < 16; n++) {
uuid[n] = rand();
}
uuid[6] &= 0x0F;
uuid[6] |= 0x40; /* Type 4 */
uuid[8] &= 0x3F;
uuid[8] |= 0x80; /* Variant 1 */
}
uint32_t vhd_calc_timestamp(void)
{
time_t start_time;
time_t curr_time;
double vhd_time;
start_time = MVHD_START_TS; /* 1 Jan 2000 00:00 */
curr_time = time(NULL);
vhd_time = difftime(curr_time, start_time);
return (uint32_t)vhd_time;
}
uint32_t mvhd_epoch_to_vhd_ts(time_t ts) {
time_t start_time = MVHD_START_TS;
if (ts < start_time) {
return start_time;
}
double vhd_time = difftime(ts, start_time);
return (uint32_t)vhd_time;
}
time_t vhd_get_created_time(MVHDMeta *vhdm)
{
time_t vhd_time = (time_t)vhdm->footer.timestamp;
time_t vhd_time_unix = MVHD_START_TS + vhd_time;
return vhd_time_unix;
}
FILE* mvhd_fopen(const char* path, const char* mode, int* err) {
FILE* f = NULL;
#ifdef _WIN32
size_t path_len = strlen(path);
size_t mode_len = strlen(mode);
mvhd_utf16 new_path[260] = {0};
int new_path_len = (sizeof new_path) - 2;
mvhd_utf16 mode_str[5] = {0};
int new_mode_len = (sizeof mode_str) - 2;
int path_res = UTF8ToUTF16LE((unsigned char*)new_path, &new_path_len, (const unsigned char*)path, (int*)&path_len);
int mode_res = UTF8ToUTF16LE((unsigned char*)mode_str, &new_mode_len, (const unsigned char*)mode, (int*)&mode_len);
if (path_res > 0 && mode_res > 0) {
f = _wfopen(new_path, mode_str);
if (f == NULL) {
mvhd_errno = errno;
*err = MVHD_ERR_FILE;
}
} else {
if (path_res == -1 || mode_res == -1) {
*err = MVHD_ERR_UTF_SIZE;
} else if (path_res == -2 || mode_res == -2) {
*err = MVHD_ERR_UTF_TRANSCODING_FAILED;
}
}
#else
f = fopen64(path, mode);
if (f == NULL) {
mvhd_errno = errno;
*err = MVHD_ERR_FILE;
}
#endif
return f;
}
void mvhd_set_encoding_err(int encoding_retval, int* err) {
if (encoding_retval == -1) {
*err = MVHD_ERR_UTF_SIZE;
} else if (encoding_retval == -2) {
*err = MVHD_ERR_UTF_TRANSCODING_FAILED;
}
}
uint64_t mvhd_calc_size_bytes(MVHDGeom *geom) {
uint64_t img_size = (uint64_t)geom->cyl * (uint64_t)geom->heads * (uint64_t)geom->spt * (uint64_t)MVHD_SECTOR_SIZE;
return img_size;
}
uint32_t mvhd_calc_size_sectors(MVHDGeom *geom) {
uint32_t sector_size = (uint32_t)geom->cyl * (uint32_t)geom->heads * (uint32_t)geom->spt;
return sector_size;
}
MVHDGeom mvhd_get_geometry(MVHDMeta* vhdm) {
MVHDGeom geometry = { .cyl = vhdm->footer.geom.cyl, .heads = vhdm->footer.geom.heads, .spt = vhdm->footer.geom.spt };
return geometry;
}
uint32_t mvhd_gen_footer_checksum(MVHDFooter* footer) {
uint32_t new_chk = 0;
uint32_t orig_chk = footer->checksum;
footer->checksum = 0;
uint8_t* footer_bytes = (uint8_t*)footer;
for (size_t i = 0; i < sizeof *footer; i++) {
new_chk += footer_bytes[i];
}
footer->checksum = orig_chk;
return ~new_chk;
}
uint32_t mvhd_gen_sparse_checksum(MVHDSparseHeader* header) {
uint32_t new_chk = 0;
uint32_t orig_chk = header->checksum;
header->checksum = 0;
uint8_t* sparse_bytes = (uint8_t*)header;
for (size_t i = 0; i < sizeof *header; i++) {
new_chk += sparse_bytes[i];
}
header->checksum = orig_chk;
return ~new_chk;
}
const char* mvhd_strerr(MVHDError err) {
switch (err) {
case MVHD_ERR_MEM:
return "memory allocation error";
case MVHD_ERR_FILE:
return "file error";
case MVHD_ERR_NOT_VHD:
return "file is not a VHD image";
case MVHD_ERR_TYPE:
return "unsupported VHD image type";
case MVHD_ERR_FOOTER_CHECKSUM:
return "invalid VHD footer checksum";
case MVHD_ERR_SPARSE_CHECKSUM:
return "invalid VHD sparse header checksum";
case MVHD_ERR_UTF_TRANSCODING_FAILED:
return "error converting path encoding";
case MVHD_ERR_UTF_SIZE:
return "buffer size mismatch when converting path encoding";
case MVHD_ERR_PATH_REL:
return "relative path detected where absolute path expected";
case MVHD_ERR_PATH_LEN:
return "path length exceeds MVHD_MAX_PATH";
case MVHD_ERR_PAR_NOT_FOUND:
return "parent VHD image not found";
case MVHD_ERR_INVALID_PAR_UUID:
return "UUID mismatch between child and parent VHD";
case MVHD_ERR_INVALID_GEOM:
return "invalid geometry detected";
case MVHD_ERR_INVALID_SIZE:
return "invalid size";
case MVHD_ERR_INVALID_BLOCK_SIZE:
return "invalid block size";
case MVHD_ERR_INVALID_PARAMS:
return "invalid parameters passed to function";
case MVHD_ERR_CONV_SIZE:
return "error converting image. Size mismatch detechted";
default:
return "unknown error";
}
}
int64_t mvhd_ftello64(FILE* stream)
{
#ifdef _MSC_VER
return _ftelli64(stream);
#else
return ftello64(stream);
#endif
}
int mvhd_fseeko64(FILE* stream, int64_t offset, int origin)
{
#ifdef _MSC_VER
return _fseeki64(stream, offset, origin);
#else
return fseeko64(stream, offset, origin);
#endif
}
uint32_t mvhd_crc32_for_byte(uint32_t r) {
for (int j = 0; j < 8; ++j)
r = (r & 1 ? 0 : (uint32_t)0xEDB88320L) ^ r >> 1;
return r ^ (uint32_t)0xFF000000L;
}
uint32_t mvhd_crc32(const void* data, size_t n_bytes) {
static uint32_t table[0x100];
if (!*table)
for (size_t i = 0; i < 0x100; ++i)
table[i] = mvhd_crc32_for_byte(i);
uint32_t crc = 0;
for (size_t i = 0; i < n_bytes; ++i)
crc = table[(uint8_t)crc ^ ((uint8_t*)data)[i]] ^ crc >> 8;
return crc;
}
uint32_t mvhd_file_mod_timestamp(const char* path, int *err) {
*err = 0;
#ifdef _WIN32
struct _stat file_stat;
size_t path_len = strlen(path);
mvhd_utf16 new_path[260] = {0};
int new_path_len = (sizeof new_path) - 2;
int path_res = UTF8ToUTF16LE((unsigned char*)new_path, &new_path_len, (const unsigned char*)path, (int*)&path_len);
if (path_res > 0) {
int stat_res = _wstat(new_path, &file_stat);
if (stat_res != 0) {
mvhd_errno = errno;
*err = MVHD_ERR_FILE;
return 0;
}
return mvhd_epoch_to_vhd_ts(file_stat.st_mtime);
} else {
if (path_res == -1) {
*err = MVHD_ERR_UTF_SIZE;
} else if (path_res == -2) {
*err = MVHD_ERR_UTF_TRANSCODING_FAILED;
}
return 0;
}
#else
struct stat file_stat;
int stat_res = stat(path, &file_stat);
if (stat_res != 0) {
mvhd_errno = errno;
*err = MVHD_ERR_FILE;
return 0;
}
return mvhd_epoch_to_vhd_ts(file_stat.st_mtime);
#endif
}

View File

@ -0,0 +1,136 @@
#ifndef MINIVHD_UTIL_H
#define MINIVHD_UTIL_H
#include <stdint.h>
#include <stdio.h>
#include <time.h>
#include "minivhd_internal.h"
#include "minivhd.h"
#define MVHD_START_TS 946684800
/**
* Functions to deal with endian issues
*/
uint16_t mvhd_from_be16(uint16_t val);
uint32_t mvhd_from_be32(uint32_t val);
uint64_t mvhd_from_be64(uint64_t val);
uint16_t mvhd_to_be16(uint16_t val);
uint32_t mvhd_to_be32(uint32_t val);
uint64_t mvhd_to_be64(uint64_t val);
/**
* \brief Check if provided buffer begins with the string "conectix"
*
* \param [in] buffer The buffer to compare. Must be at least 8 bytes in length
*
* \return true if the buffer begins with "conectix"
* \return false if the buffer does not begin with "conectix"
*/
bool mvhd_is_conectix_str(const void* buffer);
/**
* \brief Generate a raw 16 byte UUID
*
* \param [out] uuid A 16 byte buffer in which the generated UUID will be stored to
*/
void mvhd_generate_uuid(uint8_t *uuid);
/**
* \brief Calculate a VHD formatted timestamp from the current time
*/
uint32_t vhd_calc_timestamp(void);
/**
* \brief Convert an epoch timestamp to a VHD timestamp
*
* \param [in] ts epoch timestamp to convert.
*
* \return The adjusted timestamp, or 0 if the input timestamp is
* earlier that 1 Janurary 2000
*/
uint32_t mvhd_epoch_to_vhd_ts(time_t ts);
/**
* \brief Return the created time from a VHD image
*
* \param [in] vhdm Pointer to the MiniVHD metadata structure
*
* \return The created time, as a Unix timestamp
*/
time_t vhd_get_created_time(MVHDMeta *vhdm);
/**
* \brief Cross platform, unicode filepath opening
*
* This function accounts for the fact that fopen() handles file paths differently compared to other
* operating systems. Windows version of fopen() will not handle multi byte encoded text like UTF-8.
*
* Unicode filepath support on Windows requires using the _wfopen() function, which expects UTF-16LE
* encoded path and modestring.
*
* \param [in] path The filepath to open as a UTF-8 string
* \param [in] mode The mode string to use (eg: "rb+"")
* \param [out] err The error value, if an error occurrs
*
* \return a FILE pointer if successful, NULL otherwise. If NULL, check the value of err
*/
FILE* mvhd_fopen(const char* path, const char* mode, int* err);
void mvhd_set_encoding_err(int encoding_retval, int* err);
uint64_t mvhd_calc_size_bytes(MVHDGeom *geom);
uint32_t mvhd_calc_size_sectors(MVHDGeom *geom);
MVHDGeom mvhd_get_geometry(MVHDMeta* vhdm);
/**
* \brief Generate VHD footer checksum
*
* \param [in] vhdm MiniVHD data structure
*/
uint32_t mvhd_gen_footer_checksum(MVHDFooter* footer);
/**
* \brief Generate VHD sparse header checksum
*
* \param [in] vhdm MiniVHD data structure
*/
uint32_t mvhd_gen_sparse_checksum(MVHDSparseHeader* header);
/**
* \brief Get current position in file stream
*
* This is a portable version of the POSIX ftello64(). *
*/
int64_t mvhd_ftello64(FILE* stream);
/**
* \brief Reposition the file stream's position
*
* This is a portable version of the POSIX fseeko64(). *
*/
int mvhd_fseeko64(FILE* stream, int64_t offset, int origin);
/**
* \brief Calculate the CRC32 of a data buffer.
*
* This function can be used for verifying data integrity.
*
* \param [in] data The data buffer
* \param [in] n_bytes The size of the data buffer in bytes
*
* \return The CRC32 of the data buffer
*/
uint32_t mvhd_crc32(const void* data, size_t n_bytes);
/**
* \brief Calculate the file modification timestamp.
*
* This function is primarily to help protect differencing VHD's
*
* \param [in] path the UTF-8 file path
* \param [out] err The error value, if an error occurrs
*
* \return The file modified timestamp, in VHD compatible timestamp.
* 'err' will be set to non-zero on error
*/
uint32_t mvhd_file_mod_timestamp(const char* path, int *err);
#endif

View File

@ -303,7 +303,7 @@ endif
# Nothing should need changing from here on.. #
#########################################################################
VPATH := $(EXPATH) . $(CODEGEN) cpu \
cdrom chipset device disk floppy \
cdrom chipset device disk disk/minivhd floppy \
game machine mem printer \
sio sound \
sound/munt sound/munt/c_interface sound/munt/sha1 \
@ -690,6 +690,10 @@ HDDOBJ := hdd.o \
hdc_xtide.o hdc_ide.o \
hdc_ide_opti611.o \
hdc_ide_cmd640.o hdc_ide_sff8038i.o
MINIVHDOBJ := cwalk.o libxml2_encoding.o minivhd_convert.o \
minivhd_create.o minivhd_io.o minivhd_manage.o \
minivhd_struct_rw.o minivhd_util.o
CDROMOBJ := cdrom.o \
cdrom_image_backend.o cdrom_image.o
@ -789,7 +793,7 @@ else
endif
OBJ := $(MAINOBJ) $(CPUOBJ) $(CHIPSETOBJ) $(MCHOBJ) $(DEVOBJ) $(MEMOBJ) \
$(FDDOBJ) $(GAMEOBJ) $(CDROMOBJ) $(ZIPOBJ) $(MOOBJ) $(HDDOBJ) \
$(FDDOBJ) $(GAMEOBJ) $(CDROMOBJ) $(ZIPOBJ) $(MOOBJ) $(HDDOBJ) $(MINIVHDOBJ) \
$(NETOBJ) $(PRINTOBJ) $(SCSIOBJ) $(SIOOBJ) $(SNDOBJ) $(VIDOBJ) \
$(PLATOBJ) $(UIOBJ) $(FSYNTHOBJ) $(MUNTOBJ) $(DEVBROBJ) \
$(DISCORDOBJ)