Add MiniVHD to 86Box.
This commit is contained in:
parent
48883a46cd
commit
18db2af1eb
1
.gitignore
vendored
1
.gitignore
vendored
@ -9,3 +9,4 @@ src/*.dmp
|
||||
src/NUL
|
||||
src/nvr/
|
||||
src/roms/
|
||||
/.vs
|
||||
|
12
src/disk/minivhd/CREDITS.md
Normal file
12
src/disk/minivhd/CREDITS.md
Normal 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
21
src/disk/minivhd/LICENSE
Normal 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
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
457
src/disk/minivhd/cwalk.h
Normal 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
|
447
src/disk/minivhd/libxml2_encoding.c
Normal file
447
src/disk/minivhd/libxml2_encoding.c
Normal 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. */
|
12
src/disk/minivhd/libxml2_encoding.h
Normal file
12
src/disk/minivhd/libxml2_encoding.h
Normal 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
269
src/disk/minivhd/minivhd.h
Normal 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
|
105
src/disk/minivhd/minivhd_convert.c
Normal file
105
src/disk/minivhd/minivhd_convert.c
Normal 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;
|
||||
}
|
482
src/disk/minivhd/minivhd_create.c
Normal file
482
src/disk/minivhd/minivhd_create.c
Normal 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 */
|
||||
}
|
8
src/disk/minivhd/minivhd_create.h
Normal file
8
src/disk/minivhd/minivhd_create.h
Normal 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
|
96
src/disk/minivhd/minivhd_internal.h
Normal file
96
src/disk/minivhd/minivhd_internal.h
Normal 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
|
276
src/disk/minivhd/minivhd_io.c
Normal file
276
src/disk/minivhd/minivhd_io.c
Normal 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;
|
||||
}
|
132
src/disk/minivhd/minivhd_io.h
Normal file
132
src/disk/minivhd/minivhd_io.h
Normal 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
|
533
src/disk/minivhd/minivhd_manage.c
Normal file
533
src/disk/minivhd/minivhd_manage.c
Normal 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;
|
||||
}
|
165
src/disk/minivhd/minivhd_struct_rw.c
Normal file
165
src/disk/minivhd/minivhd_struct_rw.c
Normal 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);
|
||||
}
|
38
src/disk/minivhd/minivhd_struct_rw.h
Normal file
38
src/disk/minivhd/minivhd_struct_rw.h
Normal 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
|
323
src/disk/minivhd/minivhd_util.c
Normal file
323
src/disk/minivhd/minivhd_util.c
Normal 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
|
||||
}
|
136
src/disk/minivhd/minivhd_util.h
Normal file
136
src/disk/minivhd/minivhd_util.h
Normal 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
|
@ -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)
|
||||
|
Loading…
x
Reference in New Issue
Block a user