diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..cd368d1 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +*.h linguist-language=C \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1932d84 --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +# Binaries and stuff vvd may generate +vvd +vvd.exe +vdisk.tmp + +# vscode +.vscode + +# Personal stuff +t +NOTES \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..ee8ae9b --- /dev/null +++ b/LICENSE @@ -0,0 +1,19 @@ +Copyright 2019 dd86k + +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. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..eb3a90b --- /dev/null +++ b/README.md @@ -0,0 +1,34 @@ +# vvd, universal vdisk manager + +**NOTE**: This is currently a toy project so don't expect anything from this. +It's also pretty disorganized at the moment, and __dangerous__. + +**CURRENTLY NOT PRODUCTION READY** + +vvd aims to be a simple tool to manage virtual disks as one stand-alone solution. + +**WARNING**: This tool is still in really early development. Keep a backup of +your virtual disks because this thing might explode violently. + +## Goals + +- Usability +- Stability +- Simplicity + +## Useful links + +- [User Manual](#) (Coming soon) +- [Technical Reference Manual](#) (Coming soon) + +# DISCLAIMER + +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. + +For more information, see LICENSE. \ No newline at end of file diff --git a/bin/err.o b/bin/err.o new file mode 100644 index 0000000..41b363e --- /dev/null +++ b/bin/err.o Binary files differ diff --git a/bin/err.obj b/bin/err.obj new file mode 100644 index 0000000..1218bec --- /dev/null +++ b/bin/err.obj Binary files differ diff --git a/bin/fs.o b/bin/fs.o new file mode 100644 index 0000000..464a3a8 --- /dev/null +++ b/bin/fs.o Binary files differ diff --git a/bin/fs.obj b/bin/fs.obj new file mode 100644 index 0000000..0fdf112 --- /dev/null +++ b/bin/fs.obj Binary files differ diff --git a/bin/gpt.o b/bin/gpt.o new file mode 100644 index 0000000..cc0e494 --- /dev/null +++ b/bin/gpt.o Binary files differ diff --git a/bin/gpt.obj b/bin/gpt.obj new file mode 100644 index 0000000..559098f --- /dev/null +++ b/bin/gpt.obj Binary files differ diff --git a/bin/guid.o b/bin/guid.o new file mode 100644 index 0000000..8edb775 --- /dev/null +++ b/bin/guid.o Binary files differ diff --git a/bin/guid.obj b/bin/guid.obj new file mode 100644 index 0000000..3cd5c5b --- /dev/null +++ b/bin/guid.obj Binary files differ diff --git a/bin/main.o b/bin/main.o new file mode 100644 index 0000000..a1403d9 --- /dev/null +++ b/bin/main.o Binary files differ diff --git a/bin/main.obj b/bin/main.obj new file mode 100644 index 0000000..19ed4c5 --- /dev/null +++ b/bin/main.obj Binary files differ diff --git a/bin/mbr.o b/bin/mbr.o new file mode 100644 index 0000000..681cd6f --- /dev/null +++ b/bin/mbr.o Binary files differ diff --git a/bin/mbr.obj b/bin/mbr.obj new file mode 100644 index 0000000..cd31915 --- /dev/null +++ b/bin/mbr.obj Binary files differ diff --git a/bin/mm.o b/bin/mm.o new file mode 100644 index 0000000..181c621 --- /dev/null +++ b/bin/mm.o Binary files differ diff --git a/bin/mm.obj b/bin/mm.obj new file mode 100644 index 0000000..cc48ffd --- /dev/null +++ b/bin/mm.obj Binary files differ diff --git a/bin/utils.o b/bin/utils.o new file mode 100644 index 0000000..4cdbc7e --- /dev/null +++ b/bin/utils.o Binary files differ diff --git a/bin/utils.obj b/bin/utils.obj new file mode 100644 index 0000000..4c3347b --- /dev/null +++ b/bin/utils.obj Binary files differ diff --git a/bin/vdi.o b/bin/vdi.o new file mode 100644 index 0000000..0d4b362 --- /dev/null +++ b/bin/vdi.o Binary files differ diff --git a/bin/vdi.obj b/bin/vdi.obj new file mode 100644 index 0000000..ebf3d82 --- /dev/null +++ b/bin/vdi.obj Binary files differ diff --git a/bin/vdisk.o b/bin/vdisk.o new file mode 100644 index 0000000..b50bdc0 --- /dev/null +++ b/bin/vdisk.o Binary files differ diff --git a/bin/vdisk.obj b/bin/vdisk.obj new file mode 100644 index 0000000..6037426 --- /dev/null +++ b/bin/vdisk.obj Binary files differ diff --git a/bin/vhd.o b/bin/vhd.o new file mode 100644 index 0000000..ac0d5fc --- /dev/null +++ b/bin/vhd.o Binary files differ diff --git a/bin/vhd.obj b/bin/vhd.obj new file mode 100644 index 0000000..b1c3f39 --- /dev/null +++ b/bin/vhd.obj Binary files differ diff --git a/bin/vhdx.o b/bin/vhdx.o new file mode 100644 index 0000000..fa25fae --- /dev/null +++ b/bin/vhdx.o Binary files differ diff --git a/bin/vhdx.obj b/bin/vhdx.obj new file mode 100644 index 0000000..edc085c --- /dev/null +++ b/bin/vhdx.obj Binary files differ diff --git a/bin/vmdk.o b/bin/vmdk.o new file mode 100644 index 0000000..4b1e027 --- /dev/null +++ b/bin/vmdk.o Binary files differ diff --git a/bin/vmdk.obj b/bin/vmdk.obj new file mode 100644 index 0000000..f09eaf1 --- /dev/null +++ b/bin/vmdk.obj Binary files differ diff --git a/bin/vvd.o b/bin/vvd.o new file mode 100644 index 0000000..a412ea5 --- /dev/null +++ b/bin/vvd.o Binary files differ diff --git a/bin/vvd.obj b/bin/vvd.obj new file mode 100644 index 0000000..c7005ac --- /dev/null +++ b/bin/vvd.obj Binary files differ diff --git a/build b/build new file mode 100644 index 0000000..4f90658 --- /dev/null +++ b/build @@ -0,0 +1,83 @@ +#!/bin/sh +#TODO: Function for every file being compiled + +CC=$1 + +show_help() +{ + echo USAGE + echo " ./build ACTION [OPTIONS]" + echo + echo ACTION + echo " Choose a compiler (clang, gcc)" + echo " clean Delete binaries" + exit +} + +b_clean() +{ + rm -r bin/* vvd vvd.exe + exit +} + +b_clang() +{ + CTIME=`date "+%T"` + CDATE=`date "+%F"` + CFLAGS="$CC -D_FILE_OFFSET_BITS=64 -DTIMESTAMP=\"$CDATE $CTIME\" $1 $2 $3 $4 -ferror-limit=2 -std=c99 -fpack-struct=1 -c" + echo [$CC] main.obj + $CFLAGS src/main.c -o bin/main.obj + if [ $? -ne 0 ]; then exit; fi + echo [$CC] guid.obj + $CFLAGS src/guid.c -o bin/guid.obj + if [ $? -ne 0 ]; then exit; fi + echo [$CC] utils.obj + $CFLAGS src/utils.c -o bin/utils.obj + if [ $? -ne 0 ]; then exit; fi + echo [$CC] vdisk.obj + $CFLAGS src/vdisk.c -o bin/vdisk.obj + if [ $? -ne 0 ]; then exit; fi + echo [$CC] vvd.obj + $CFLAGS src/vvd.c -o bin/vvd.obj + if [ $? -ne 0 ]; then exit; fi + # fs + echo [$CC] mbr.obj + $CFLAGS src/fs/mbr.c -o bin/mbr.obj + if [ $? -ne 0 ]; then exit; fi + echo [$CC] gpt.obj + $CFLAGS src/fs/gpt.c -o bin/gpt.obj + if [ $? -ne 0 ]; then exit; fi + # os + echo [$CC] fs.obj + $CFLAGS src/os/fs.c -o bin/fs.obj + if [ $? -ne 0 ]; then exit; fi + echo [$CC] err.obj + $CFLAGS src/os/err.c -o bin/err.obj + if [ $? -ne 0 ]; then exit; fi + # vdisk + echo [$CC] vdi.obj + $CFLAGS src/vdisk/vdi.c -o bin/vdi.obj + if [ $? -ne 0 ]; then exit; fi + echo [$CC] vmdk.obj + $CFLAGS src/vdisk/vmdk.c -o bin/vmdk.obj + if [ $? -ne 0 ]; then exit; fi + echo [$CC] vhd.obj + $CFLAGS src/vdisk/vhd.c -o bin/vhd.obj + if [ $? -ne 0 ]; then exit; fi + echo [$CC] vhdx.obj + $CFLAGS src/vdisk/vhdx.c -o bin/vhdx.obj + if [ $? -ne 0 ]; then exit; fi + # LINK + echo [$CC] Linking vvd + $CC $1 $2 $3 $4 bin/*.obj -o vvd + exit +} + +if [ "$CC" = "clang" ]; then b_clang $2 $3 $4 $5; fi +if [ "$CC" = "clean" ]; then b_clean; fi +if [ -z "$CC" ]; then + CC="clang" + b_clang $2 $3 $4 $5; +fi + +echo "ERROR: Action not found ($CC)" \ No newline at end of file diff --git a/build.cmd b/build.cmd new file mode 100644 index 0000000..7df3014 --- /dev/null +++ b/build.cmd @@ -0,0 +1,95 @@ +@ECHO OFF +:: +:: ** CL CLI reminder ** +:: optimize size: /Os +:: optimize speed: /Ot +:: compile dynamic: /MD +:: compile static: /MT +:: +:: Don't forget setargv_*.obj +:: +::TODO: Function for every file being compiled + +SET CC=%1 +SET CTIME="\"%DATE% %TIME%\"" + +IF /I "%CC%"=="clang-cl" GOTO :CLANGCL +IF /I "%CC%"=="clean" GOTO :CLEAN +IF /I "%CC%"=="help" GOTO :SHOW_HELP +IF /I "%CC%"=="" ( + SET CC=clang-cl + GOTO :CLANGCL +) + +ECHO ERROR: Action not found (%CC%) +GOTO :EOF + +:SHOW_HELP +ECHO USAGE +ECHO build ACTION [OPTIONS] +ECHO. +ECHO ACTION +ECHO ^ Choose a compiler (clang, clang-cl) +ECHO CLEAN Delete binaries +GOTO :EOF + +:: +:: CLEAN +:: + +:CLEAN +DEL /S /Q bin\* vvd vvd.exe +GOTO :EOF + +:: +:: clang-cl +:: + +:CLANGCL +SET CFLAGS=%CC% -c %2 %3 %4 %5 /Zp -DTIMESTAMP=%CTIME% -D_CRT_SECURE_NO_WARNINGS -ferror-limit=2 +ECHO [%CC%] main.o +%CFLAGS% src\main.c -o bin\main.o +IF ERRORLEVEL 1 GOTO :EOF +ECHO [%CC%] guid.o +%CFLAGS% src\guid.c -o bin\guid.o +IF ERRORLEVEL 1 GOTO :EOF +ECHO [%CC%] utils.o +%CFLAGS% src\utils.c -o bin\utils.o +IF ERRORLEVEL 1 GOTO :EOF +ECHO [%CC%] vdisk.o +%CFLAGS% src\vdisk.c -o bin\vdisk.o +IF ERRORLEVEL 1 GOTO :EOF +ECHO [%CC%] vvd.o +%CFLAGS% src\vvd.c -o bin\vvd.o +IF ERRORLEVEL 1 GOTO :EOF +:: os +ECHO [%CC%] fs.o +%CFLAGS% src\os\fs.c -o bin\fs.o +IF ERRORLEVEL 1 GOTO :EOF +ECHO [%CC%] err.o +%CFLAGS% src\os\err.c -o bin\err.o +IF ERRORLEVEL 1 GOTO :EOF +:: fs +ECHO [%CC%] mbr.o +%CFLAGS% src\fs\mbr.c -o bin\mbr.o +IF ERRORLEVEL 1 GOTO :EOF +ECHO [%CC%] gpt.o +%CFLAGS% src\fs\gpt.c -o bin\gpt.o +IF ERRORLEVEL 1 GOTO :EOF +:: vdisk +ECHO [%CC%] vdi.o +%CFLAGS% src\vdisk\vdi.c -o bin\vdi.o +IF ERRORLEVEL 1 GOTO :EOF +ECHO [%CC%] vmdk.o +%CFLAGS% src\vdisk\vmdk.c -o bin\vmdk.o +IF ERRORLEVEL 1 GOTO :EOF +ECHO [%CC%] vhd.o +%CFLAGS% src\vdisk\vhd.c -o bin\vhd.o +IF ERRORLEVEL 1 GOTO :EOF +ECHO [%CC%] vhdx.o +%CFLAGS% src\vdisk\vhdx.c -o bin\vhdx.o +IF ERRORLEVEL 1 GOTO :EOF +:: LINK +ECHO [%CC%] Linking vvd +%CC% %2 %3 %4 %5 bin\*.o -o vvd.exe +GOTO :EOF diff --git a/docs/vvd.1 b/docs/vvd.1 new file mode 100644 index 0000000..44319aa --- /dev/null +++ b/docs/vvd.1 @@ -0,0 +1,138 @@ +." Written by dd86k + +.TH vvd 1 "September 2019" dd86k "User Manual" +.SH NAME +.B vvd +- Universal virtual disk management tool + +.SH SYNOPSIS +.SY vvd +{ +.IR -I +| +.IR -M +| +.IR -N +| +.IR -C +} +.OP rc +.YS +.SY vvd +{ +.IR --help +| +.IR --version +| +.IR --license +} +.YS + +.SH DESCRIPTION + +vvd aims to be a simple to use universal virtual disk management tool. + +.SH OPERATIONS + +One of these must be used to operate with a virtual disk. all virtual disk +file references are referenced by "VDISK". + +.IP -I +Get information on VDISK + +Get VDISK information such as VDISK headers (type, virtual size, etc.), MBR, +EFI, and FS, if available. Supports option +.OP -r + +.IP -M +Get allocation map for VDISK + +Get allocation map for VDISK. If the VDISK type is not dynamic, the operation +is aborted. + +.IP -N +Create new VDISK + +Create a new VDISK with SIZE and optional TYPE specifications. Dynamic disks +are created by default. If +.OP -c +is specified (create as raw), the file is pre-allocated like a fixed VDISK. +.B WARNING: THIS WILL OVERWRITE ANYTHING GIVEN IN PATH! +Here is an example of the format: + +.EX +vvd -N FILE SIZE [TYPE] +.EE + +Where FILE is a path with an extension, which sets the format (.vdi, .vmdk, +etc.), SIZE is a binary size such as "2.50G", and TYPE is an optional type +setting for either "fixed" or "dynamic". + +.IP -C +Compact VDISK + +This will attempt to compact the VDISK. If the VDISK is not of type dynamic, +the operation is canceled. + +.SH OPTIONS + +Some options may be added alongside OPERATION. Here is an example with +.OP -r + +.EX +$ vvd -Ir /dev/sda +.EE + +.IP -r +Open as raw + +Open file, or device, as raw. This bypasses all format and header verifications. + +.IP -c +Create as raw + +Create file as raw. Only used in +.IR -N +, pre-allocates to SIZE. + +.SH EXAMPLES + +.B Get VDISK information + +.EX +$ vvd -I windows10.vhd +.EE + +.B Get disk information + +Open as raw. Requires elevated privileges. + +.EX +$ vvd -Ir /dev/sda +> vvd -Ir \\\\.\\PhysicalDrive0 +.EE + +.B Create fixed VDISK + +.EX +$ vvd -N arch.vdi 4G fixed +.EE + +.SH WARNINGS + +.B This tool is way too young to be called stable. This thing may explode violently. + +This tool may likely change often during its growth. + +.SH AUTHOR +Program and man-gage written by dd86k +.MT dd@dax.moe +.ME + +.UR https://git.dd86k.space/vvd +Homepage: +.UE + +A mirror is available at +.UR https://github.com/dd86k/vvd +.UE diff --git a/src/fs/gpt.c b/src/fs/gpt.c new file mode 100644 index 0000000..edfb0d3 --- /dev/null +++ b/src/fs/gpt.c @@ -0,0 +1,83 @@ +#include +#include +#include // strcpy +#include +#include "gpt.h" // includes guid.h +#include "../utils.h" +#include "../vdisk.h" + +// Check GPT +int gpt_check(GPT *gpt) { + return gpt->sig == EFI_SIG; +} + +void gpt_info(GPT *gpt) { + GUID_TEXT diskguid; + guid_tostr(diskguid, &gpt->guid); + printf( + "\n* GPT v%u.%u (%u B), HDR CRC32 %08X, PT CRC32 %08X\n" + "MAIN LBA %u, BACKUP LBA %u, FIRST LBA %u, LAST LBA %u\n" + "PT LBA %u, %u MAX ENTRIES, ENTRY SIZE %u\n" + "DISK GUID: %s\n", + gpt->majorv, gpt->minorv, gpt->headersize, gpt->crc32, gpt->pt_crc32, + gpt->current.low, gpt->backup.low, gpt->firstlba.low, gpt->lastlba.low, + gpt->pt_location.low, gpt->pt_entries, gpt->pt_esize, + diskguid + ); +} + +void gpt_list_pe_vd(VDISK *vd, GPT *gpt) { + int max = gpt->pt_entries; // maximum limiter + char partname[EFI_PART_NAME_LENGTH]; + GUID_TEXT partguid, typeguid; + GPT_ENTRY entry; + uint32_t lba = 2; + //TODO: Read a few entries per loop to avoid reading too often + +START: + //if (os_read(vd->fd, &entry, sizeof(GPT_ENTRY))) { + if (vdisk_read_lba(vd, &entry, lba)) { + fputs("gpt_list_pe_vd: Could not read GPT_ENTRY", stderr); + return; + } + + if (guid_nil(&entry.type)) + return; + + guid_tostr(typeguid, &entry.type); + guid_tostr(partguid, &entry.part); + wstra(entry.partname, partname, EFI_PART_NAME_LENGTH); + + printf( + "%u. %-36s\n" + " LBA %" PRIu64 " TO %" PRIu64 "\n" + " PART: %s\n" + " TYPE: %s\n" + " FLAGS: %XH, PART FLAGS: %XH\n", + lba - 1, partname, + entry.firstlba.lba, entry.lastlba.lba, + partguid, typeguid, + entry.flags, entry.flags_part + ); + // GPT flags + if (entry.flags & EFI_PE_PLATFORM_REQUIRED) + puts("+ Platform required"); + if (entry.flags & EFI_PE_PLATFORM_REQUIRED) + puts("+ Firmware ignore"); + if (entry.flags & EFI_PE_PLATFORM_REQUIRED) + puts("+ Legacy BIOS bootable"); + // Partition flags + if (entry.flags_part & EFI_PE_SUCCESSFUL_BOOT) + puts("+ (Google) Successful boot"); + if (entry.flags_part & EFI_PE_READ_ONLY) + puts("+ (Microsoft) Read-only"); + if (entry.flags_part & EFI_PE_SHADOW_COPY) + puts("+ (Microsoft) Shadow copy"); + if (entry.flags_part & EFI_PE_HIDDEN) + puts("+ (Microsoft) Hidden"); + + if (max <= 0) + return; + ++lba; --max; + goto START; +} diff --git a/src/fs/gpt.h b/src/fs/gpt.h new file mode 100644 index 0000000..9069a22 --- /dev/null +++ b/src/fs/gpt.h @@ -0,0 +1,119 @@ +#pragma once + +#include +#include "../guid.h" +#include "../vdisk.h" + +// GNU GRUB "Hah!IdontNeedEFI" + +#define EFI_SIG 0x5452415020494645 // "EFI PART" +#define EFI_SIG_LOW 0x20494645 +#define EFI_SIG_HIGH 0x54524150 +#define EFI_PART_NAME_LENGTH 36 + +// +// EFI Parition Entry flags (GPT_ENTRY::flags) +// + +#define EFI_PE_PLATFORM_REQUIRED 1 // bit 0, preserve as-is +#define EFI_PE_EFI_FIRMWARE_IGNORE 2 // bit 1, ignore content of partition +#define EFI_PE_LEGACY_BIOS_BOOTABLE 4 // bit 2 + +// +// EFI Parition Entry flags (GPT_ENTRY::flags_res) +// + +// +// EFI Parition Entry flags (GPT_ENTRY::flags_part) +// + +// Google Chrome OS +// priority[3:0] +// tries remiaining[7:4] +#define EFI_PE_SUCCESSFUL_BOOT 0x10 // bit 8 + +// Microsoft +#define EFI_PE_READ_ONLY 0x1000 // bit 12 +#define EFI_PE_SHADOW_COPY 0x2000 // bit 13 +#define EFI_PE_HIDDEN 0x4000 // bit 14 +#define EFI_PE_NO_DRIVE_LETTER 0x8000 // bit 15 + +typedef struct LBA64 { + union { + uint64_t lba; + struct { + uint32_t low, high; + }; + }; +} LBA64; + +// Protective MBRs have type of EEH, and the header is usually found at LBA 1 +typedef struct GPT { // v1.0 + union { + uint64_t sig; // "EFI PART" + struct { + uint32_t siglow, sighigh; + }; + }; + uint16_t minorv; + uint16_t majorv; + uint32_t headersize; // usually 92 bytes (v1.0) + uint32_t crc32; + uint32_t reserved; // reserved + LBA64 current; + LBA64 backup; + LBA64 firstlba; + LBA64 lastlba; + __GUID guid; // Disk __GUID + LBA64 pt_location; // (partition table) location + uint32_t pt_entries; // (partition table) number of entries + uint32_t pt_esize; // (parition entry) structure size + uint32_t pt_crc32; // (partition table) CRC32 + uint8_t pad[420]; +} GPT; + +// GPT entry structure +typedef struct GPT_ENTRY { + // Unused entry : 00000000-0000-0000-0000-000000000000 + // EFI System Partition: C12A7328-F81F-11D2-BA4B-00A0C93EC93B + // Contains legacy MBR : 024DEE41-33E7-11D3-9D69-0008C781F39F + __GUID type; // Parition type __GUID + __GUID part; // Unique partition __GUID + LBA64 firstlba; + LBA64 lastlba; + union { + uint64_t flagsraw; + struct { + // Bit 0 - Required for platform + // Bit 1 - If on, do not produce EFI_BLOCK_IO_PROTOCOL + // Bit 2 - Legacy PC-AT BIOS bootable + uint32_t flags; // GPT entry flags + uint16_t flags_res; // Reserved + // (Chrome OS) Bit 0:3 - Priority (0: non-boot, 1:lowest, 15:highest) + // (Chrome OS) Bit 4:7 - Tries remaining + // (Chrome OS) Bit 8 - Successful boot + // (Windows) Bit 12 - Read-only + // (Windows) Bit 13 - Shadow partition copy + // (Windows) Bit 14 - Hidden + // (Windows) Bit 15 - No drive letter (no automount) + uint16_t flags_part; // Partition-defined flags + }; + }; + uint16_t partname[36]; // 72 bytes, 32 UTF-16LE characters + uint8_t pad[384]; +} GPT_ENTRY; + +struct VDISK; + +/** + * Verifies GPT partition signature. + */ +int gpt_check(GPT *); +/** + * Prints GPT information to stdout. + */ +void gpt_info(GPT *); +/** + * Run through the list of GPT_ENTRY from a VDISK. + */ +void gpt_list_pe_vd(VDISK *vd, GPT *gpt); diff --git a/src/fs/mbr.c b/src/fs/mbr.c new file mode 100644 index 0000000..258b139 --- /dev/null +++ b/src/fs/mbr.c @@ -0,0 +1,134 @@ +#include +#include +#include "mbr.h" +#include "gpt.h" +#include "../vdisk.h" + +// Maps CHS geometry to an LBA (sector index) +// LBA = (C × HPC + H) × SPT + (S − 1) +// HPC Max heads per cylinders, typically 16 (28-bit LBA) +// SPT Max sectors per strack, typically 63 (28-bit LBA) +uint32_t mbr_lba(CHS_ENTRY *chs) { + uint8_t sector = chs->sector & 0x3F; + uint16_t cylinder = chs->cylinder | ((chs->sector & 0xC0) << 2); + return (cylinder * 16 * chs->head) * 63 + (sector - 1); +} + +// LBA-ASSISTED TRANSLATION for disks under 8032.5 MiB +// SIZE (MiB) S/T H C +// < 504 63 16 63 * H * 512 +// 504-1008 63 32 63 * H * 512 +// 1008-2016 63 64 63 * H * 512 +// 2016-4032 63 128 63 * H * 512 +// 4032-8032.5 63 255 63 * H * 512 +uint32_t mbr_lba_a(CHS_ENTRY *chs, uint64_t bsize) { + uint8_t H; + if (bsize <= 0x1F800000) // 504 MiB + H = 16; + else if (bsize <= 0x3F000000) // 1008 MiB + H = 32; + else if (bsize <= 0x7E000000) // 2016 MiB + H = 64; + else if (bsize <= 0xFC000000) // 4032 MiB + H = 128; + else if (bsize <= 0x1F6080000ULL) // 8032.5 MiB + H = 255; + else + H = 16; //TODO: Verify H when >8.5G + + uint8_t sector = chs->sector & 0x3F; + uint16_t cylinder = chs->cylinder | ((chs->sector & 0xC0) << 2); + return (cylinder * H * chs->head) * 63 + (sector - 1); +} + +// Checks for MBR signature +int mbr_check(MBR *mbr) { + return mbr->sig == MBR_SIG; +} + +// And also checks GPT automatically +void mbr_info_auto(VDISK *vd) { + MBR mbr; + if (vdisk_read_lba(vd, &mbr, 0)) return; + if (mbr_check(&mbr) == 0) return; + mbr_info(&mbr); + // EFI GPT Protective or EFI System Partition + if (mbr.pe1.partition < 0xEE || mbr.pe1.partition > 0xEF) + return; + if (vdisk_read_lba(vd, &mbr, 1)) return; + if (gpt_check((GPT *)&mbr)) { + gpt_info((GPT *)&mbr); + gpt_list_pe_vd(vd, (GPT *)&mbr); + } +} + +void mbr_info(MBR *mbr) { + char size[BIN_FLENGTH]; + uint64_t dtsize = SECTOR_TO_BYTE( + (uint64_t)mbr->pe1.sectors + (uint64_t)mbr->pe2.sectors + + (uint64_t)mbr->pe3.sectors + (uint64_t)mbr->pe4.sectors + ); + fbins(dtsize, size); + printf( + "\n* MBR, SERIAL %08X, USED %s, TYPE %04u\n" + "PARTITIONS STATUS TYPE LBA SIZE C:H:S->C:H:S\n" + "ENTRY 1 %3XH %3XH %9u %9u %u:%u:%u->%u:%u:%u\n" + "ENTRY 2 %3XH %3XH %9u %9u %u:%u:%u->%u:%u:%u\n" + "ENTRY 3 %3XH %3XH %9u %9u %u:%u:%u->%u:%u:%u\n" + "ENTRY 4 %3XH %3XH %9u %9u %u:%u:%u->%u:%u:%u\n" + , + mbr->serial, size, mbr->type, + // PE 1 + mbr->pe1.status, + mbr->pe1.partition, + mbr->pe1.lba, + mbr->pe1.sectors, + mbr->pe1.chsfirst.cylinder | + ((mbr->pe1.chsfirst.sector & 0xC0) << 2), + mbr->pe1.chsfirst.head, + mbr->pe1.chsfirst.sector & 0x3F, + mbr->pe1.chslast.cylinder | + ((mbr->pe1.chslast.sector & 0xC0) << 2), + mbr->pe1.chslast.head, + mbr->pe1.chslast.sector & 0x3F, + // PE 2 + mbr->pe2.status, + mbr->pe2.partition, + mbr->pe2.lba, + mbr->pe2.sectors, + mbr->pe2.chsfirst.cylinder | + ((mbr->pe2.chsfirst.sector & 0xC0) << 2), + mbr->pe2.chsfirst.head, + mbr->pe2.chsfirst.sector & 0x3F, + mbr->pe2.chslast.cylinder | + ((mbr->pe2.chslast.sector & 0xC0) << 2), + mbr->pe2.chslast.head, + mbr->pe2.chslast.sector & 0x3F, + // PE 3 + mbr->pe3.status, + mbr->pe3.partition, + mbr->pe3.lba, + mbr->pe3.sectors, + mbr->pe3.chsfirst.cylinder | + ((mbr->pe3.chsfirst.sector & 0xC0) << 2), + mbr->pe3.chsfirst.head, + mbr->pe3.chsfirst.sector & 0x3F, + mbr->pe3.chslast.cylinder | + ((mbr->pe3.chslast.sector & 0xC0) << 2), + mbr->pe3.chslast.head, + mbr->pe3.chslast.sector & 0x3F, + // PE 4 + mbr->pe4.status, + mbr->pe4.partition, + mbr->pe4.lba, + mbr->pe4.sectors, + mbr->pe4.chsfirst.cylinder | + ((mbr->pe4.chsfirst.sector & 0xC0) << 2), + mbr->pe4.chsfirst.head, + mbr->pe4.chsfirst.sector & 0x3F, + mbr->pe4.chslast.cylinder | + ((mbr->pe4.chslast.sector & 0xC0) << 2), + mbr->pe4.chslast.head, + mbr->pe4.chslast.sector & 0x3F + ); +} diff --git a/src/fs/mbr.h b/src/fs/mbr.h new file mode 100644 index 0000000..e736c7e --- /dev/null +++ b/src/fs/mbr.h @@ -0,0 +1,64 @@ +#pragma once + +#include +#include +#include "../vdisk.h" + +enum { + MBR_SIG = 0xAA55, // MBR signature, LSB +}; + +typedef struct CHS_ENTRY { // Cylinder-Head-Sector + uint8_t head; // HEAD[7:0] + uint8_t sector; // SECTOR[5:0], bits[7:6] for CYLINDER[9:8] + uint8_t cylinder; // CYLINDER[7:0] +} CHS_ENTRY; + +typedef struct MBR_PARTITION_ENTRY { + uint8_t status; + struct CHS_ENTRY chsfirst; // CHS first absolute address + uint8_t partition; // partition type + struct CHS_ENTRY chslast; // CHS last absolute address + uint32_t lba; // LBA of first absolute sector in partition + uint32_t sectors; // number of sectors for parition +} MBR_PARTITION_ENTRY; + +typedef struct MBR { + union { + uint8_t data[512]; + struct { + uint8_t pad[440]; + uint32_t serial; // WindowsNT 3.5+, Linux 2.6+ + // Usually 0000H + // (Windows) 5A5AH if protected + // (UEFI) AA55H if protective MBR + uint16_t type; + MBR_PARTITION_ENTRY pe1; + MBR_PARTITION_ENTRY pe2; + MBR_PARTITION_ENTRY pe3; + MBR_PARTITION_ENTRY pe4; + uint16_t sig; + }; + }; +} MBR; + +/** + * Translate CHS geometry into an LBA. + */ +uint32_t mbr_lba(CHS_ENTRY *); +/** + * Translate CHS geometry into an LBA with BIOS assistance up to 8.5 GiB disks. + */ +uint32_t mbr_lba_a(CHS_ENTRY *, uint64_t); +/** + * Verifies if MBR contains a valid signature. + */ +int mbr_check(MBR *s); +/** + * Print MBR information to stdout. + */ +void mbr_info(MBR *s); +/** + * Print MBR and GPT information if a VDISK contains any. + */ +void mbr_info_auto(VDISK *vd); diff --git a/src/guid.c b/src/guid.c new file mode 100644 index 0000000..c806384 --- /dev/null +++ b/src/guid.c @@ -0,0 +1,57 @@ +#include +#include +#include "guid.h" +#include "utils.h" + +// __GUID to 36-character buffer string +int guid_tostr(char *buffer, __GUID *guid) { + return snprintf(buffer, GUID_TEXT_SIZE, + "%08X-%04X-%04X-%04X-%02X%02X%02X%02X%02X%02X", + guid->time_low, guid->time_mid, guid->time_ver, guid->clock, + guid->data[10], guid->data[11], guid->data[12], + guid->data[13], guid->data[14], guid->data[15] + ); +} + +// __GUID to 36-character buffer string +int uuid_tostr(char *buffer, __GUID *guid) { + return snprintf(buffer, GUID_TEXT_SIZE, + "%08X-%04X-%04X-%04X-%02X%02X%02X%02X%02X%02X", + bswap32(guid->time_low), bswap16(guid->time_mid), + bswap16(guid->time_ver), bswap16(guid->clock), + guid->data[10], guid->data[11], guid->data[12], + guid->data[13], guid->data[14], guid->data[15] + ); +} + +// 36-character string to __GUID, version 4a +int guid_frstr(__GUID *guid, char *buffer) { + assert(0); + return 0; +} + +void guid_swap(__GUID *guid) { + guid->time_low = bswap32(guid->time_low); + guid->time_mid = bswap32(guid->time_mid); + guid->time_ver = bswap32(guid->time_ver); + guid->clock = bswap32(guid->clock); +} + +// "guid_is_not_empty" is an ugly name +int guid_nil(__GUID *guid) { +#if __SIZE_WIDTH__ == 64 + if (guid->u64[0]) return 0; + if (guid->u64[1]) return 0; +#else + if (guid->u32[0]) return 0; + if (guid->u32[1]) return 0; + if (guid->u32[2]) return 0; + if (guid->u32[3]) return 0; +#endif + return 1; +} + +int guid_cmp(__GUID *guid1, __GUID *guid2) { + assert(0); + return 0; +} diff --git a/src/guid.h b/src/guid.h new file mode 100644 index 0000000..37c8f14 --- /dev/null +++ b/src/guid.h @@ -0,0 +1,48 @@ +#pragma once + +#include + +#define GUID_TEXT_SIZE 38 // usually 36 but.. {} and \0 +typedef char GUID_TEXT[GUID_TEXT_SIZE]; +typedef struct __GUID { // GUID/UUID structure + union { + uint8_t data[16]; + uint16_t u16[8]; + uint32_t u32[4]; + uint64_t u64[2]; // Preferred to use when size width = 64 + struct { + uint32_t time_low; + uint16_t time_mid; + uint16_t time_ver; // and time_hi + uint16_t clock; // seq_hi and res_clock_low + uint8_t node[6]; + }; + }; +} __GUID; + +/** + * Format a GUID to a string buffer. + */ +int guid_tostr(char *, __GUID *); +/** + * Format a UUID to a string buffer. + */ +int uuid_tostr(char *, __GUID *); +/** + * Convert a GUID string from string. + */ +int guid_frstr(__GUID *, char *); +/** + * Byte swap GUID/UUID fields to convert a GUID into an UUID or vice-versa. + * Useful when the endianess differs from a machine. GUIDs is usually + * little-endian and UUIDs are usually big-endian. + */ +void guid_swap(__GUID *guid); +/** + * Verifies if a GUID/UUID is nil (null, empty). + */ +int guid_nil(__GUID *); +/** + * Compares two GUIDs or two UUIDs. + */ +int guid_cmp(__GUID *, __GUID *); diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..9cc742c --- /dev/null +++ b/src/main.c @@ -0,0 +1,322 @@ +#ifdef _WIN32 +#include +#include +#include +#include +#else // POSIX + +#endif + +#define VERSION "0.0.0" +#define INCLUDE_TESTS 1 + +#include +#include +#include +#include +#include "utils.h" +#include "vvd.h" +#include "platform.h" + +#ifdef INCLUDE_TESTS +#include +#include "fs/gpt.h" +#include "fs/mbr.h" + +// +// test +// + +void test() { + fputs( + "* Defines\n" +#ifdef __LITTLE_ENDIAN__ + "__LITTLE_ENDIAN__\n" +#endif +#ifdef __BIG_ENDIAN__ + "__BIG_ENDIAN__\n" +#endif +#ifdef __NO_INLINE__ + "__NO_INLINE__\n" +#endif + , stdout); +#ifdef _ILP32 + printf("_ILP32 %u\n", _ILP32); +#endif +#ifdef __SIZE_WIDTH__ + printf("__SIZE_WIDTH__ %u\n", __SIZE_WIDTH__); +#endif + printf( + "sizeof VDISK %u\n" + "sizeof wchar_t %u\n", + (int)sizeof(VDISK), + (int)sizeof(wchar_t) + ); + puts("Running tests..."); + assert(sizeof(MBR) == 512); + assert(sizeof(MBR_PARTITION_ENTRY) == 16); + assert(sizeof(CHS_ENTRY) == 3); + assert(sizeof(GPT) == 512); + assert(sizeof(GPT_ENTRY) == 512); + assert(sizeof(LBA64) == 8); + // VDI + assert(sizeof(VDI_HDR) == 8); + assert(sizeof(VDIDISKGEOMETRY) == 16); + assert(sizeof(VDIHEADER0) == 348); + assert(sizeof(VDIHEADER1) == 400); + // VMDK + assert(sizeof(VMDK_HDR) == 512); + assert(sizeof(VMDK_MARKER) == 512); + // VHD + assert(sizeof(VHD_HDR) == 512); + assert(sizeof(VHD_PARENT_LOCATOR) == 24); + assert(sizeof(VHD_DYN_HDR) == 1024); + // VHDX + assert(sizeof(VHDX_HDR) == 520); + assert(sizeof(VHDX_HEADER1) == 76); + assert(sizeof(VHDX_REGION_HDR) == 16); + assert(sizeof(VHDX_REGION_ENTRY) == 32); + assert(sizeof(VHDX_LOG_HDR) == 64); + assert(sizeof(VHDX_LOG_ZERO) == 32); + assert(sizeof(VDHX_LOG_DESC) == 32); + assert(sizeof(VHDX_LOG_DATA) == 4100); + // utils + assert(bswap16(0xAABB) == 0xBBAA); + assert(bswap32(0xAABBCCDD) == 0xDDCCBBAA); + assert(bswap64(0xAABBCCDD11223344) == 0x44332211DDCCBBAA); +#ifdef _WIN32 + assert(extcmp(L"test.bin", L"bin")); +#else + assert(extcmp("test.bin", "bin")); +#endif + puts("OK"); + exit(EXIT_SUCCESS); +} +#endif // INCLUDE_TESTS + +// +// CLI part +// + +void help() { + puts( + "Manage virtual disks\n" + " Usage: vvd OPERATION FILE [...]\n" + " vvd PAGE\n" + "\nOPERATIONS\n" + " -I Info - Get information\n" + " -N New - Create new empty vdisk\n" + " -M Map - Get allocation map if available\n" + " -C Compact - Save disk space\n" + "\nOPTIONS\n" + " r Open as RAW\n" + "\nPAGE\n" + " --help Print this help screen and quit\n" + " --version Print version screen and quit\n" + " --license Print license screen and quit\n" + ); + exit(EXIT_SUCCESS); +} + +void version(void) { + printf( + "vvd-" PLATFORM " " VERSION +#ifdef TIMESTAMP + " (" TIMESTAMP ")" +#endif + "\n" +#ifdef __VERSION__ + "Compiler: " __VERSION__ "\n" +#endif + "MIT License: Copyright (c) 2019 dd86k\n" + "Project page: \n" + "VDISK OPERATIONS\n" + "VDI I M N C\n" + "VMDK I\n" + "VHD I M\n" + "VHDX \n" + "QED \n" + "QCOW \n" + "VHD \n" + "PHDD \n" + "RAW I\n" + ); + exit(EXIT_SUCCESS); +} + +void license() { + puts( + "Copyright 2019 dd86k\n" + "\n" + "Permission is hereby granted, free of charge, to any person obtaining a copy of\n" + "this software and associated documentation files (the \"Software\"), to deal in\n" + "the Software without restriction, including without limitation the rights to\n" + "use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies\n" + "of the Software, and to permit persons to whom the Software is furnished to do\n" + "so, subject to the following conditions:\n" + "\n" + "The above copyright notice and this permission notice shall be included in all\n" + "copies or substantial portions of the Software.\n" + "\n" + "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n" + "IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n" + "FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n" + "AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n" + "LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n" + "OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n" + "SOFTWARE." + ); + exit(EXIT_SUCCESS); +} + +/** + * Extension vdisk matcher, returns VDISK_FORMAT if matches an extension. + * Otherwise 0. + */ +int vdextauto(_vchar *path) { + if (extcmp(path, EXT_VDI)) return VDISK_FORMAT_VDI; + if (extcmp(path, EXT_VMDK)) return VDISK_FORMAT_VMDK; + if (extcmp(path, EXT_VHD)) return VDISK_FORMAT_VHD; + return 0; +} + +#ifdef _WIN32 +#define MAIN int wmain(int argc, wchar_t **argv) +#else +#define MAIN int main(int argc, char **argv) +#endif + +// +// main +// + +MAIN { + if (argc <= 1) + help(); + + char op_mode; + int oflags = 0; // open file flags + int cflags = 0; // create file flags + + if (argv[1][1] == '-') { // long arguments + _vchar *h = argv[1] + 2; +#ifdef _WIN32 + if (wcscmp(h, L"help") == 0) + help(); + if (wcscmp(h, L"version") == 0) + version(); + if (wcscmp(h, L"license") == 0) + license(); +#ifdef INCLUDE_TESTS + if (wcscmp(h, L"test") == 0) + test(); +#endif // INCLUDE_TESTS + fprintf(stderr, "main: \"%ls\" option unknown, aborting\n", h); +#else + if (strcmp(h, "help") == 0) + help(); + if (strcmp(h, "version") == 0) + version(); + if (strcmp(h, "license") == 0) + license(); +#ifdef INCLUDE_TESTS + if (strcmp(h, "test") == 0) + test(); +#endif // INCLUDE_TESTS + fprintf(stderr, "main: \"%s\" option unknown, aborting\n", h); +#endif + return ECLIARG; + } else if (argv[1][0] == '-') { // short arguments + _vchar *h = argv[1]; + while (*++h) switch (*h) { + case MODE_INFO: // -I + case MODE_MAP: // -M + case MODE_NEW: // -N + case MODE_COMPACT: // -C + op_mode = *h; + break; + case 'r': + oflags |= VDISK_OPEN_RAW; + break; + case 'c': + cflags |= VDISK_OPEN_RAW; + break; + default: + fprintf(stderr, "main: unknown parameter '%c', aborting\n", *h); + return ECLIARG; + } + } + + VDISK vdin; // vdisk IN + VDISK vdout; // vdisk OUT + uint64_t vsize; // virtual disk size, used in -N and -R + + size_t fargi; // File IN argument index + switch (op_mode) { + /*case MODE_RESIZE: + if (argc < 4) + goto L_MISSING_ARGS; + fargi = 3; + break;*/ + case MODE_NEW: + if (argc < 4) // Needs vvd -N TYPE SIZE + goto L_MISSING_ARGS; + + fargi = 2; + oflags |= VDISK_CREATE; + + vdin.format = vdextauto(argv[2]); + if (vdisk_default(&vdin)) { + vdisk_perror(__func__); + return vdisk_errno; + } + + if (sbinf(argv[3], &vsize)) { + fputs("main: Invalid binary size, must be higher than 0\n", stderr); + return ECLIARG; + } + + if (argc > 4) { +#ifdef _WIN32 + if (wcscmp(argv[4], L"fixed") == 0) + oflags |= VDISK_CREATE_FIXED; + else if (wcscmp(argv[4], L"dynamic") == 0) + oflags |= VDISK_CREATE_DYN; +#else + if (strcmp(argv[4], "fixed") == 0) + oflags |= VDISK_CREATE_FIXED; + else if (strcmp(argv[4], "dynamic") == 0) + oflags |= VDISK_CREATE_DYN; +#endif + } // ELSE DEFAULT: dynamic (vdisk_open) + break; + case MODE_COMPACT: + case MODE_INFO: + case MODE_MAP: + if (argc < 3) { +L_MISSING_ARGS: + fputs("main: Missing parameters, aborting\n", stderr); + return ECLIARG; + } + fargi = 2; + break; + default: + fputs("main: Invalid operation mode, aborting\n", stderr); + return ECLIARG; + } + + if (vdisk_open(argv[fargi], &vdin, oflags)) { + vdisk_perror(__func__); + return vdisk_errno; + } + + switch (op_mode) { + case MODE_INFO: return vvd_info(&vdin); + case MODE_MAP: return vvd_map(&vdin); + case MODE_NEW: return vvd_new(&vdin, vsize); + case MODE_COMPACT: return vvd_compact(&vdin); + default: assert(0); + } + + return EXIT_SUCCESS; +} diff --git a/src/os/err.c b/src/os/err.c new file mode 100644 index 0000000..286ea6c --- /dev/null +++ b/src/os/err.c @@ -0,0 +1,27 @@ +#include +#include "os.h" + +void os_perror(const char *func) { + //TODO: Consider os_perror to return last OS error +#ifdef _WIN32 + fprintf(stderr, "%s: ", func); + wchar_t buffer[1024]; + unsigned int e = GetLastError(); + int l = GetLocaleInfoEx( // Recommended over MAKELANGID + LOCALE_NAME_USER_DEFAULT, + LOCALE_ALL, + 0, + 0); + FormatMessageW( + FORMAT_MESSAGE_FROM_SYSTEM, + NULL, + e, + l, + buffer, + 1024, + NULL); + fputws(buffer, stderr); +#else + perror(func); +#endif +} diff --git a/src/os/err.h b/src/os/err.h new file mode 100644 index 0000000..4a5a66b --- /dev/null +++ b/src/os/err.h @@ -0,0 +1 @@ +void os_perror(const char *func); \ No newline at end of file diff --git a/src/os/fs.c b/src/os/fs.c new file mode 100644 index 0000000..cf500fa --- /dev/null +++ b/src/os/fs.c @@ -0,0 +1,122 @@ +#include +#include "os.h" +#include "err.h" + +__OSFILE os_open(_vchar *path) { +#ifdef _WIN32 + __OSFILE fd = CreateFileW( + path, + GENERIC_READ | GENERIC_WRITE, // dwDesiredAccess + 0, // dwShareMode: No sharing + NULL, // lpSecurityAttributes + OPEN_EXISTING, // dwCreationDisposition: Open only if existing + 0, // dwFlagsAndAttributes + NULL // hTemplateFile + ); + if (fd == INVALID_HANDLE_VALUE) { + os_perror(__func__); + return 0; + } +#else + __OSFILE fd = open(path, O_RDWR); + if (fd == -1) { + os_perror(__func__); + return 0; + } +#endif + return fd; +} + +__OSFILE os_create(_vchar *path) { +#ifdef _WIN32 + __OSFILE fd = CreateFileW( + path, + GENERIC_READ | GENERIC_WRITE, // dwDesiredAccess + 0, // dwShareMode: No sharing + NULL, // lpSecurityAttributes + CREATE_ALWAYS, // dwCreationDisposition: Always create + 0, // dwFlagsAndAttributes + NULL // hTemplateFile + ); + if (fd == INVALID_HANDLE_VALUE) { + os_perror(__func__); + return 0; + } +#else + __OSFILE fd = open(path, O_RDWR | O_CREAT | O_TRUNC); + if (fd == -1) { + os_perror(__func__); + return 0; + } +#endif + return fd; +} + +int os_seek(__OSFILE handle, int64_t pos, int flags) { +#ifdef _WIN32 + LARGE_INTEGER a; + a.QuadPart = pos; + if (SetFilePointerEx(handle, a, NULL, flags) == 0) { + os_perror(__func__); + return -1; + } +#else + if (lseek(handle, (off_t)pos, flags) == -1) { + os_perror(__func__); + return -1; + } +#endif + return 0; +} + +int os_read(__OSFILE handle, void *buffer, size_t size) { +#ifdef _WIN32 + DWORD r; + if (ReadFile(handle, buffer, size, &r, NULL) == 0) { + os_perror(__func__); + return -1; + } + if (r != size) { + fprintf(stderr, "os_read: Failed to read %u/%u bytes", + (uint32_t)r, (uint32_t)size); + return -2; + } +#else + ssize_t r; + if ((r = read(handle, buffer, size)) == -1) { + os_perror(__func__); + return -1; + } + if (r != size) { + fprintf(stderr, "os_read: Failed to read %d/%u bytes", + (int32_t)r, (uint32_t)size); + return -2; + } +#endif + return 0; +} + +int os_write(__OSFILE handle, void *buffer, size_t size) { +#ifdef _WIN32 + DWORD r; + if (WriteFile(handle, buffer, size, &r, NULL) == 0) { + os_perror(__func__); + return -1; + } + if (r != size) { + fprintf(stderr, "os_write: Failed to write %u bytes", (unsigned int)r); + return -2; + } +#else + ssize_t r; + if ((r = write(handle, buffer, size)) == -1) { + os_perror(__func__); + return -1; + } + if (r != size) { + fprintf(stderr, "os_write: Failed to read %u bytes", (unsigned int)r); + return -2; + } +#endif + return 0; +} diff --git a/src/os/os.h b/src/os/os.h new file mode 100644 index 0000000..8eff637 --- /dev/null +++ b/src/os/os.h @@ -0,0 +1,47 @@ +#include "../utils.h" +#include "../vdisk.h" + +#ifdef _WIN32 // Windows +#include +typedef HANDLE __OSFILE; + +#else +#include +#include +#include +#include +#include + +#ifndef __OSFILE_H +#define __OSFILE_H +typedef int __OSFILE; +#endif // __OSFILE_H +#endif + +/** + * Open a file stream. File or device must exist. Consult documentation on + * which devices are supported. + */ +__OSFILE os_open(_vchar *path); +/** + * Always create and overwrite a file path. This cannot create devices. + */ +__OSFILE os_create(_vchar *path); +/** + * Seek into a position within the stream. + */ +int os_seek(__OSFILE handle, int64_t position, int flags); +/** + * Read data from stream. + */ +int os_read(__OSFILE handle, void *buffer, size_t size); +/** + * Write data to stream. Overwritting. + */ +int os_write(__OSFILE handle, void *buffer, size_t size); + +/** + * Print last OS error. Usually used within osutils.c and has no purpose outside + * of it. + */ +void os_perror(const char *); \ No newline at end of file diff --git a/src/platform.h b/src/platform.h new file mode 100644 index 0000000..3f230a7 --- /dev/null +++ b/src/platform.h @@ -0,0 +1,48 @@ +/** + * Platform architecture string for printing purposes only + * + * Source: https://sourceforge.net/p/predef/wiki/Architectures/ + */ +#if __amd64 || __amd64__ || __x86_64 || __x86_64__ || _M_AMD64 || _M_X64 + #define PLATFORM "amd64" +#elif i386 || __i386 || __i386__ || _M_IX86 || __X86__ || __THW_INTEL__ || __I86__ || __INTEL__ || __386 + #define PLATFORM "x86" +#elif __aarch64__ + #define PLATFORM "arm64" +#elif __arm__ || __TARGET_ARCH_ARM || _ARM || _M_ARM || __arm + #if __thumb__ || __TARGET_ARCH_THUMB || _M_ARMT + #define PLATFORM "armthumb" + #else + #define PLATFORM "arm" + #endif +#elif _ARCH_PPC64 || __powerpc64__ + #define PLATFORM "powerpc64" +#elif _ARCH_PPC || __powerpc + #define PLATFORM "powerpc" +#elif __ia64__ || __ia64 || _M_IA64 || __itanium__ + #define PLATFORM "ia64" +#elif __370__ || __THW_370__ + #define PLATFORM "system/370" +#elif __s390__ || __s390x__ + #define PLATFORM "system/390" +#elif __zarch__ || __SYSC_ZARCH__ + #define PLATFORM "z/arch" +#elif __sparc__ || __sparc + #define PLATFORM "sparc" +#elif __sh__ + #define PLATFORM "superh" +#elif __alpha__ || __alpha || _M_ALPHA + #define PLATFORM "alpha" +#elif __mips__ || __mips || __MIPS__ + #define PLATFORM "mips" +#elif __m68k__ || M68000 || __MC68K__ + #define PLATFORM "m68k" +#elif __bfin || __BFIN__ + #define PLATFORM "blackfin" +#elif __epiphany__ + #define PLATFORM "epiphany" +#elif __hppa__ || __HPPA__ || __hppa + #define PLATFORM "hp/pa" +#else + #define PLATOFMR "unknown" +#endif \ No newline at end of file diff --git a/src/utils.c b/src/utils.c new file mode 100644 index 0000000..82210c7 --- /dev/null +++ b/src/utils.c @@ -0,0 +1,122 @@ +#include +#include // atof +#include +#include "utils.h" + +void fbins(uint64_t n, char *buf) { // Lazy code 2.0, sorry + float f = n; + char *fs; + if (f >= TB) { + fs = "%.2f TiB"; + f /= TB; + } else if (f >= GB) { + fs = "%.2f GiB"; + f /= GB; + } else if (f >= MB) { + fs = "%.1f MiB"; + f /= MB; + } else if (f >= KB) { + fs = "%.1f KiB"; + f /= KB; + } else + fs = "%g B"; + snprintf(buf, 16, fs, f); +} + +int sbinf(_vchar *input, uint64_t *size) { + float f; + char c; +#ifdef _WIN32 + if (swscanf(input, L"%f%c", &f, &c) != 2) { + return 1; + } +#else + if (sscanf(input, "%f%c", &f, &c) != 2) { + return 1; + } +#endif + if (f <= 0) return 2; + uint64_t u; + switch (c) { + case 'T': case 't': + u = f * 1099511627776; break; + case 'G': case 'g': + u = f * 1073741824; break; + case 'M': case 'm': + u = f * 1048576; break; + case 'K': case 'k': + u = f * 1024; break; + case 'B': case 'b': + u = f; break; + default: return 3; + } + *size = u; + return 0; +} + +uint16_t bswap16(uint16_t s) { + return s >> 8 | s << 8; +} + +uint32_t bswap32(uint32_t s) { + return (s & 0x000000ff) << 24 | (s & 0x0000ff00) << 8 | + (s & 0x00ff0000) >> 8 | (s & 0xff000000) >> 24; +} + +uint64_t bswap64(uint64_t s) { + uint32_t *p = (uint32_t*)&s; + return (uint64_t)bswap32(p[0]) << 32 | (uint64_t)bswap32(p[1]); +} + +void print_a(char *p, uint8_t *a, size_t s) { + size_t i = 0; + printl(p); + while (--s) + printf(" %02X", a[i++]); + putchar('\n'); +} + +int extcmp(_vchar *s1, const _vchar *s2) { +#ifdef _WIN32 + wchar_t *ext = wcsrchr(s1, '.'); + if (ext == NULL) // Not found + return 0; + ++ext; + if (*ext == 0) // Return if no characters after '.' + return 0; + return wcscmp(ext, s2) == 0; +#else + const char *ext = strrchr(s1, '.'); + if (ext == NULL) // Not found + return 0; + ++ext; + if (*ext == 0) // Return if no characters after '.' + return 0; + return strcmp(ext, s2) == 0; +#endif +} + +void printl(const char *s) { + fputs(s, stdout); +} + +int pow2(int n) { + return (n & (n - 1)) == 0; +} + +void wstra(char16 *src, char *dest, int dsize) { + size_t bi = 0; // buffer index + if (src[bi] == 0) { + strcpy(dest, ""); + return; + } + --dsize; // to include null byte later on + while (bi < dsize && src[bi]) { + if (src[bi] >= 0x20 && src[bi] <= 0x7E) + dest[bi] = (char)src[bi]; + else + dest[bi] = '?'; + ++bi; + } + dest[bi] = 0; +} diff --git a/src/utils.h b/src/utils.h new file mode 100644 index 0000000..3c632c5 --- /dev/null +++ b/src/utils.h @@ -0,0 +1,87 @@ +#include +#include + +#ifdef _WIN32 +#define _vchar wchar_t +#else // POSIX +#define _vchar char +#endif + +#ifndef _CHAR16 +#define _CHAR16 +typedef uint16_t char16; +#endif + +// Convert sector number/size to byte offset/size. +#define SECTOR_TO_BYTE(u) ((uint64_t)(u) << 9) + +// Convert byte offset/size to sector number/size. +#define BYTE_TO_SECTOR(u) ((u) >> 9) + +#define TB 1099511627776 +#define GB 1073741824 +#define MB 1048576 +#define KB 1024 + +/** + * Function alias of fputs(*, stdout) to avoid argument bloat. + */ +void printl(const char *s); + +#define BIN_FLENGTH 16 +/** + * Get formatted binary (ISO) size with suffix, buffer fixed at 16 characters + */ +void fbins(uint64_t, char *); +/** + * + */ +int sbinf(_vchar *input, uint64_t *size); + +/** + * Print array with prefix string + */ +void print_a(char *p, uint8_t *a, size_t s); + +/** + * Byte swap a 16-bit (2-Byte) value. + */ +uint16_t bswap16(uint16_t); +/** + * Byte swap a 32-bit (4-Byte) value. + */ +uint32_t bswap32(uint32_t); +/** + * Byte swap a 64-bit (8-Byte) value. + */ +uint64_t bswap64(uint64_t); + +#ifdef _WIN32 + #define EXT_VDI L"vdi" + #define EXT_VMDK L"vmdk" + #define EXT_VHD L"vhd" +#else + #define EXT_VDI "vdi" + #define EXT_VMDK "vmdk" + #define EXT_VHD "vhd" +#endif +/** + * Compare file path with constant extension string. + * + * E.g. `extcmp("test.bin", "bin")` evaluates to non-zero + */ +int extcmp(_vchar *s1, const _vchar *s2); + +/** + * Checks if number is a power of 2. + * + * Returns non-zero if number is a power of 2. + */ +int pow2(int); + +/** + * Convert an UTF-16 string to an ASCII string and returns number of charaters + * copied with the destination's maximum buffer size. This function fills up + * upto dsize-1 characters and inserts a null terminator. + */ +void wstra(char16 *src, char *dest, int dsize); diff --git a/src/vdisk.c b/src/vdisk.c new file mode 100644 index 0000000..f9f2d59 --- /dev/null +++ b/src/vdisk.c @@ -0,0 +1,625 @@ +#include +#include +#include +#include +#include +#include "utils.h" +#include "vdisk.h" + +// +// vdisk_open +// + +int vdisk_open(_vchar *path, VDISK *vd, uint16_t flags) { + vdisk_func = __func__; + + if (flags & VDISK_CREATE || flags & VDISK_CREATE_TEMP) + goto L_CREATE; + + if ((vd->fd = os_open(path)) == 0) { + vdisk_errln = __LINE_BEFORE__; + return (vdisk_errno = EVDOPEN); + } + + if (flags & VDISK_OPEN_RAW) { + vd->format = VDISK_FORMAT_RAW; + vd->offset = 0; + //TODO: Consider getting disk size from OS + return (vdisk_errno = EVDOK); + } + + // + // Format detection + // + // This hints the function to a format and tests both reading and + // seeking capabilities on the file or device. + // + + if (os_read(vd->fd, &vd->format, 4)) { + vdisk_errln = __LINE_BEFORE__; + return (vdisk_errno = EVDREAD); + } + if (os_seek(vd->fd, 0, SEEK_SET)) { + vdisk_errln = __LINE_BEFORE__; + return (vdisk_errno = EVDSEEK); + } + + // + // **************** + // * Disk opening * + // **************** + // + + switch (vd->format) { + // + // VDI + // + case VDISK_FORMAT_VDI: + if (os_seek(vd->fd, 64, SEEK_SET)) { + vdisk_errln = __LINE_BEFORE__; + return (vdisk_errno = EVDSEEK); + } + if (os_read(vd->fd, &vd->vdihdr, sizeof(VDI_HDR))) { + vdisk_errln = __LINE_BEFORE__; + return (vdisk_errno = EVDREAD); + } + if (vd->vdihdr.magic != VDI_HEADER_MAGIC) { + vdisk_errln = __LINE_BEFORE__; + return (vdisk_errno = EVDMAGIC); + } + + switch (vd->vdihdr.majorv) { // Use latest major version natively + case 1: // Includes all minor releases + if (os_read(vd->fd, &vd->vdi, sizeof(VDIHEADER1))) { + vdisk_errln = __LINE_BEFORE__; + return (vdisk_errno = EVDREAD); + } + break; + case 0: { // Or else, translate header + VDIHEADER0 vd0; + if (os_read(vd->fd, &vd0, sizeof(VDIHEADER0))) { + vdisk_errln = __LINE_BEFORE__; + return (vdisk_errno = EVDREAD); + } + vd->vdi.disksize = vd0.disksize; + vd->vdi.type = vd0.type; + vd->vdi.offBlocks = sizeof(VDI_HDR) + sizeof(VDIHEADER0); + vd->vdi.offData = sizeof(VDI_HDR) + sizeof(VDIHEADER0) + + (vd0.totalblocks << 2); // sizeof(uint32_t) -> "* 4" -> "<< 2" + memcpy(&vd->vdi.uuidCreate, &vd0.uuidCreate, 16); + memcpy(&vd->vdi.uuidModify, &vd0.uuidModify, 16); + memcpy(&vd->vdi.uuidLinkage, &vd0.uuidLinkage, 16); + memcpy(&vd->vdi.LegacyGeometry, &vd0.LegacyGeometry, + sizeof(VDIDISKGEOMETRY)); + break; + } + default: + return (vdisk_errno = EVDVERSION); + } + + switch (vd->vdi.type) { + case VDI_DISK_DYN: + case VDI_DISK_FIXED: break; + default: + return (vdisk_errno = EVDTYPE); + } + + // allocation table + if (vd->vdi.blocksize == 0) { + vdisk_errln = __LINE_BEFORE__; + vd->vdi.blocksize = VDI_BLOCKSIZE; + } + //TODO: Consider warning if blocksize != offBlocks? + if (os_seek(vd->fd, vd->vdi.offBlocks, SEEK_SET)) { + vdisk_errln = __LINE_BEFORE__; + return (vdisk_errno = EVDSEEK); + } + int bsize = vd->vdi.totalblocks << 2; // * sizeof(u32) + if ((vd->u32blocks = malloc(bsize)) == NULL) { + vdisk_errln = __LINE_BEFORE__; + return (vdisk_errno = EVDMISC); + } + if (os_read(vd->fd, vd->u32blocks, bsize)) { + vdisk_errln = __LINE_BEFORE__; + return (vdisk_errno = EVDREAD); + } + vd->offset = vd->vdi.offData; + vd->u32nblocks = vd->vdi.totalblocks; + break; // VDISK_FORMAT_VDI + // + // VMDK + // + case VDISK_FORMAT_VMDK: + if (os_read(vd->fd, &vd->vmdk, sizeof(VMDK_HDR))) + return (vdisk_errno = EVDREAD); + if (vd->vmdk.version != 1) + return (vdisk_errno = EVDVERSION); + if (vd->vmdk.grainSize < 1 || vd->vmdk.grainSize > 128 || + pow2(vd->vmdk.grainSize) == 0) + return (vdisk_errno = EVDMISC); + vd->offset = SECTOR_TO_BYTE(vd->vmdk.overHead); + break; // VDISK_FORMAT_VMDK + // + // VHD + // + case VDISK_FORMAT_VHD: + if (os_read(vd->fd, &vd->vhd, sizeof(VHD_HDR))) + return (vdisk_errno = EVDREAD); + if (vd->vhd.magic != VHD_MAGIC) + return (vdisk_errno = EVDMAGIC); +L_VHD_MAGICOK: + vd->vhd.major = bswap16(vd->vhd.major); + if (vd->vhd.major != 1) + return (vdisk_errno = EVDVERSION); + vd->vhd.type = bswap32(vd->vhd.type); + switch (vd->vhd.type) { + case VHD_DISK_DIFF: + case VHD_DISK_DYN: + case VHD_DISK_FIXED: break; + default: + return (vdisk_errno = EVDTYPE); + } + vd->vhd.features = bswap32(vd->vhd.features); + vd->vhd.minor = bswap16(vd->vhd.minor); + vd->vhd.offset = bswap64(vd->vhd.offset); + vd->vhd.timestamp = bswap32(vd->vhd.timestamp); + vd->vhd.creator_major = bswap16(vd->vhd.creator_major); + vd->vhd.creator_minor = bswap16(vd->vhd.creator_minor); +// vd->vhd.creator_os = bswap32(vd->vhd.creator_os); + vd->vhd.size_original = bswap64(vd->vhd.size_original); + vd->vhd.size_current = bswap64(vd->vhd.size_current); + vd->vhd.cylinders = bswap16(vd->vhd.cylinders); + vd->vhd.checksum = bswap32(vd->vhd.checksum); + guid_swap(&vd->vhd.uuid); + if (vd->vhd.type != VHD_DISK_FIXED) { + if (os_seek(vd->fd, vd->vhd.offset, SEEK_SET)) + return (vdisk_errno = EVDSEEK); + if (os_read(vd->fd, &vd->vhddyn, sizeof(VHD_DYN_HDR))) + return (vdisk_errno = EVDREAD); + if (vd->vhddyn.magic != VHD_DYN_MAGIC) + return (vdisk_errno = EVDMAGIC); + vd->vhddyn.data_offset = bswap64(vd->vhddyn.data_offset); + vd->vhddyn.table_offset = bswap64(vd->vhddyn.table_offset); + vd->vhddyn.minor = bswap16(vd->vhddyn.minor); + vd->vhddyn.major = bswap16(vd->vhddyn.major); + vd->vhddyn.max_entries = bswap32(vd->vhddyn.max_entries); + vd->vhddyn.blocksize = bswap32(vd->vhddyn.blocksize); + vd->vhddyn.checksum = bswap32(vd->vhddyn.checksum); + guid_swap(&vd->vhddyn.parent_uuid); + vd->vhddyn.parent_timestamp = bswap32(vd->vhddyn.parent_timestamp); + for (size_t i = 0; i < 8; ++i) { + vd->vhddyn.parent_locator[i].code = bswap32( + vd->vhddyn.parent_locator[i].code); + vd->vhddyn.parent_locator[i].datasize = bswap32( + vd->vhddyn.parent_locator[i].datasize); + vd->vhddyn.parent_locator[i].dataspace = bswap32( + vd->vhddyn.parent_locator[i].dataspace); + vd->vhddyn.parent_locator[i].offset = bswap64( + vd->vhddyn.parent_locator[i].offset); + } + vd->u32nblocks = vd->vhd.size_original / vd->vhddyn.blocksize; + if (vd->u32nblocks <= 0) + return (vdisk_errno = EVDMISC); + if (os_seek(vd->fd, vd->vhddyn.table_offset, SEEK_SET)) + return (vdisk_errno = EVDSEEK); + int batsize = vd->u32nblocks << 2; // "* sizeof(u32)" + if ((vd->u32blocks = malloc(batsize)) == NULL) + return (vdisk_errno = EVDMISC); + if (os_read(vd->fd, vd->u32blocks, batsize)) + return (vdisk_errno = EVDREAD); + for (size_t i = 0; i < vd->u32nblocks; ++i) + vd->u32blocks[i] = bswap32(vd->u32blocks[i]); + vd->offset = SECTOR_TO_BYTE(vd->u32blocks[0]) + 512; + } else { + vd->offset = 0; + } + break; // VDISK_FORMAT_VHD + /*case VDISK_FORMAT_VHDX: + // HDR + if (os_read(vd->fd, &vd->vhdx, sizeof(VHDX_HDR))) + return (vdisk_errno = EVDREAD); + if (vd->vhdx.magic != VHDX_MAGIC) + return (vdisk_errno = EVDMAGIC); + // HEADER1 + if (os_seek(vd->fd, VHDX_HEADER1_LOC, SEEK_SET)) + return (vdisk_errno = EVDSEEK); + if (os_read(vd->fd, &vd->vhdxhdr, sizeof(VHDX_HEADER1))) + return (vdisk_errno = EVDREAD); + if (vd->vhdxhdr.magic != VHDX_HDR1_MAGIC) + return (vdisk_errno = EVDMAGIC); + // REGION + if (os_seek(vd->fd, VHDX_REGION1_LOC, SEEK_SET)) + return (vdisk_errno = EVDSEEK); + if (os_read(vd->fd, &vd->vhdxreg, sizeof(VHDX_REGION_HDR))) + return (vdisk_errno = EVDREAD); + if (vd->vhdxreg.magic != VHDX_REGION_MAGIC) + return (vdisk_errno = EVDMAGIC); + // LOG + // unsupported + // BAT + + // Chunk ratio + //(8388608 * ) / // 8 KiB * 512 + break; // VDISK_FORMAT_VHDX*/ + default: + // Unfortunately CAN NOT simply seek and jump (goto to VDISK + // format case) to the first possibility (e.g. VHD and others + // where header is not at the start of the vdisk) so we have + // to keep the flow here. + + // Try VHD (end of file) for fixed VHDs + if (os_seek(vd->fd, -512, SEEK_END)) + return (vdisk_errno = EVDSEEK); + if (os_read(vd->fd, &vd->vhd, sizeof(VHD_HDR))) + return (vdisk_errno = EVDREAD); + if (vd->vhd.magic == VHD_MAGIC) { + vd->format = VDISK_FORMAT_VHD; + goto L_VHD_MAGICOK; + } + + return (vdisk_errno = EVDFORMAT); + } + + return (vdisk_errno = EVDOK); + + // + // ***************** + // * Disk creation * + // ***************** + // + +L_CREATE: + if (flags & VDISK_CREATE_TEMP) { +#ifdef _WIN32 + path = L"vdisk.tmp"; +#else + path = "vdisk.tmp"; +#endif + } + if (path == NULL) + return (vdisk_errno = EVDMISC); + if (flags & VDISK_OPEN_RAW) + vd->format = VDISK_FORMAT_RAW; + else + switch (vd->format) { + case VDISK_FORMAT_VDI: + if (flags & VDISK_CREATE_FIXED) + vd->vdi.type = VDI_DISK_FIXED; + else + vd->vdi.type = VDI_DISK_DYN; + break; + case VDISK_FORMAT_VMDK: +// vd->type = VMDK_DISK_DYN; + break; + case VDISK_FORMAT_VHD: + if (flags & VDISK_CREATE_FIXED) + vd->vhd.type = VHD_DISK_FIXED; + else + vd->vhd.type = VHD_DISK_DYN; + break; + } + if ((vd->fd = os_create(path)) == 0) + return (vdisk_errno = EVDOPEN); + + return (vdisk_errno = EVDOK); +} + +// +// vdisk_default +// + +int vdisk_default(VDISK *vd) { + vdisk_func = __func__; + + switch (vd->format) { + case VDISK_FORMAT_VDI: + // hdr + vd->vdihdr.magic = VDI_HEADER_MAGIC; + vd->vdihdr.majorv = 1; + vd->vdihdr.minorv = 1; + // struct + vd->vdi.blocksalloc = 0; + vd->vdi.blocksextra = 0; + vd->vdi.blocksize = VDI_BLOCKSIZE; + vd->vdi.cbSector = vd->vdi.LegacyGeometry.cbSector = 512; + vd->vdi.cCylinders = vd->vdi.cHeads = vd->vdi.cSectors = + vd->vdi.LegacyGeometry.cCylinders = + vd->vdi.LegacyGeometry.cHeads = + vd->vdi.LegacyGeometry.cSectors = 0; + vd->vdi.disksize = 0; + vd->vdi.fFlags = 0; + vd->vdi.hdrsize = (uint32_t)sizeof(VDIHEADER1); + vd->vdi.offBlocks = VDI_BLOCKSIZE; + vd->vdi.offData = 2 * VDI_BLOCKSIZE; + vd->vdi.totalblocks = 0; + vd->vdi.type = VDI_DISK_DYN; + vd->vdi.u32Dummy = 0; // Always + memset(vd->vdi.szComment, 0, VDI_COMMENT_SIZE); + memset(&vd->vdi.uuidCreate, 0, 16); + memset(&vd->vdi.uuidLinkage, 0, 16); + memset(&vd->vdi.uuidModify, 0, 16); + memset(&vd->vdi.uuidParentModify, 0, 16); + break; + default: + return (vdisk_errno = EVDFORMAT); + } + + return (vdisk_errno = EVDOK); +} + +// +// vdisk_str +// + +char* vdisk_str(VDISK *vd) { + vdisk_func = __func__; + + switch (vd->format) { + case VDISK_FORMAT_RAW: return "RAW"; + case VDISK_FORMAT_VDI: return "VDI"; + case VDISK_FORMAT_VMDK: return "VMDK"; + case VDISK_FORMAT_VHD: return "VHD"; + case VDISK_FORMAT_VHDX: return "VHDX"; + case VDISK_FORMAT_QED: return "QED"; + case VDISK_FORMAT_QCOW: return "QCOW"; + default: assert(0); return NULL; + } +} + +// +// vdisk_update_headers +// + +int vdisk_update_headers(VDISK *vd) { + vdisk_func = __func__; + + switch (vd->format) { + case VDISK_FORMAT_VDI: + os_seek(vd->fd, 0, SEEK_SET); + os_write(vd->fd, VDI_SIGNATURE, 40); + // skip signature + os_seek(vd->fd, VDI_SIGNATURE_SIZE, SEEK_SET); + os_write(vd->fd, &vd->vdihdr, sizeof(VDI_HDR)); + os_write(vd->fd, &vd->vdi, sizeof(VDIHEADER1)); + // blocks + os_seek(vd->fd, vd->vdi.offBlocks, SEEK_SET); + os_write(vd->fd, vd->u32blocks, vd->u32nblocks * 4); + break; + /*case VDISK_FORMAT_VMDK: + assert(0); + break; + case VDISK_FORMAT_VHD: + assert(0); + break;*/ + default: + return (vdisk_errno = EVDFORMAT); + } + + return (vdisk_errno = EVDOK); +} + +// +// vdisk_read_lba +// + +int vdisk_read_lba(VDISK *vd, void *buffer, uint64_t lba) { + vdisk_func = __func__; + + uint64_t pos; // New file position + uint64_t boff = SECTOR_TO_BYTE(lba); // Byte offset + size_t bi; // Block index + + switch (vd->format) { + case VDISK_FORMAT_VDI: + bi = boff / vd->vdi.blocksize; + if (bi >= vd->vdi.totalblocks) // Over + return (vdisk_errno = EVDMISC); + if (vd->u32blocks[bi] == VDI_BLOCK_UNALLOCATED || + vd->u32blocks[bi] == VDI_BLOCK_FREE) + return (vdisk_errno = EVDMISC); + pos = (vd->u32blocks[bi] * vd->vdi.blocksize) + boff + + vd->offset; + break; + case VDISK_FORMAT_VMDK: + // work vd->vmdk.grainSize directly + assert(0); + break; + case VDISK_FORMAT_VHD: + switch (vd->vhd.type) { + case VHD_DISK_FIXED: + pos = boff; + break; + case VHD_DISK_DYN: + // selected index + bi = boff / vd->vhddyn.blocksize; + if (bi >= vd->u32nblocks) // Over + return EVDMISC; + if (vd->u32blocks[bi] == 0xFFFFFFFF) // Unallocated + return EVDMISC; + pos = SECTOR_TO_BYTE(vd->u32blocks[bi]) + boff + 512; + break; + default: + return (vdisk_errno = EVDTYPE); + } + break; + case VDISK_FORMAT_RAW: + pos = boff; + break; + default: + return (vdisk_errno = EVDFORMAT); + } + + if (os_seek(vd->fd, pos, SEEK_SET)) + return (vdisk_errno = EVDSEEK); + if (os_read(vd->fd, buffer, 512)) + return (vdisk_errno = EVDREAD); + + return (vdisk_errno = EVDOK); +} + +// +// vdisk_read_block +// + +int vdisk_read_block(VDISK *vd, void *buffer, uint64_t index) { + vdisk_func = __func__; + + uint32_t readsize; + uint64_t pos; + + switch (vd->format) { + case VDISK_FORMAT_VDI: + if (index >= vd->u32nblocks) + return (vdisk_errno = EVDMISC); + readsize = vd->vdi.blocksize; + if (vd->u32blocks[index] == VDI_BLOCK_UNALLOCATED) + return (vdisk_errno = EVDUNALLOC); + pos = (vd->u32blocks[index] * vd->vdi.blocksize) + vd->vdi.offData; + break; + case VDISK_FORMAT_VHD: + if (vd->vhd.type != VHD_DISK_DYN) + return (vdisk_errno = EVDTYPE); + readsize = vd->vhddyn.blocksize; + break; + default: + return (vdisk_errno = EVDFORMAT); + } + + if (os_seek(vd->fd, pos, SEEK_SET)) + return (vdisk_errno = EVDSEEK); + if (os_read(vd->fd, buffer, readsize)) + return (vdisk_errno = EVDREAD); + + return (vdisk_errno = EVDOK); +} + +// +// vdisk_write_lba +// + +int vdisk_write_lba(VDISK *vd, void *buffer, uint64_t lba) { + vdisk_func = __func__; + + return EVDOK; +} + +// +// vdisk_write_block +// + +int vdisk_write_block(VDISK *vd, void *buffer, uint64_t index) { + vdisk_func = __func__; + + uint64_t pos; + uint64_t blocksize; + + switch (vd->format) { + case VDISK_FORMAT_VDI: + //TODO: What should we do if we run out of allocation blocks? + if (index >= vd->u32nblocks) + return (vdisk_errno = EVDMISC); + if (vd->u32blocks[index] == VDI_BLOCK_UNALLOCATED) { + pos = vd->nextblock; + vd->u32blocks[index] = ((pos - vd->offset) / vd->vdi.blocksize); + vd->nextblock += vd->vdi.blocksize; + } else { + pos = (vd->u32blocks[index] * vd->vdi.blocksize) + vd->vdi.offData; + } + blocksize = vd->vdi.blocksize; + break; + default: + return (vdisk_errno = EVDFORMAT); + } + + if (os_seek(vd->fd, pos, SEEK_SET)) + return (vdisk_errno = EVDSEEK); + if (os_write(vd->fd, buffer, blocksize)) + return (vdisk_errno = EVDWRITE); + + return (vdisk_errno = EVDOK); +} + +// +// vdisk_write_block_at +// + +int vdisk_write_block_at(VDISK *vd, void *buffer, uint64_t bindex, uint64_t dindex) { + vdisk_func = __func__; + + uint64_t pos; + uint64_t blocksize; + + switch (vd->format) { + case VDISK_FORMAT_VDI: + if (dindex >= vd->u32nblocks) + return (vdisk_errno = EVDMISC); + pos = (dindex * vd->vdi.blocksize) + vd->offset; + blocksize = vd->vdi.blocksize; + vd->u32blocks[bindex] = dindex; + break; + default: + return (vdisk_errno = EVDFORMAT); + } + + if (os_seek(vd->fd, pos, SEEK_SET)) { + return (vdisk_errno = EVDSEEK); + } + if (os_write(vd->fd, buffer, blocksize)) + return (vdisk_errno = EVDWRITE); + + return (vdisk_errno = EVDOK); +} + +// +// vdisk_error +// + +char* vdisk_error() { + switch (vdisk_errno) { + case EVDOK: + return "last operation was successful"; + case EVDOPEN: + return "could not open vdisk"; + case EVDREAD: + return "could not read vdisk"; + case EVDSEEK: + return "could not seek vdisk"; + case EVDWRITE: + return "could not write vdisk"; + case EVDFORMAT: + return "unsupported vdisk format"; + case EVDMAGIC: + return "invalid magic"; + case EVDVERSION: + return "unsupported version"; + case EVDTYPE: + return "invalid disk type for vdisk function"; + case EVDFULL: + return "vdisk is full"; + case EVDUNALLOC: + return "block is unallocated"; + case EVDBOUND: + return "block index is out of bounds"; + case EVDMISC: + return "unknown error happened"; + default: + assert(0); return NULL; + } +} + +// +// vdisk_perror +// + +void vdisk_perror(const char *func) { + fprintf(stderr, "[%s] %s L%u: (%d) %s\n", + func, vdisk_func, vdisk_errln, vdisk_errno, vdisk_error()); +} + +// +// vdisk_last_errno +// + +int vdisk_last_errno() { + return vdisk_errno; +} diff --git a/src/vdisk.h b/src/vdisk.h new file mode 100644 index 0000000..4881751 --- /dev/null +++ b/src/vdisk.h @@ -0,0 +1,220 @@ +#pragma once + +#include "os/os.h" +#include "utils.h" +#include "vdisk/vdi.h" +#include "vdisk/vmdk.h" +#include "vdisk/vhd.h" +#include "vdisk/vhdx.h" + +#define __LINE_BEFORE__ (__LINE__ - 1) +#define DEFAULT_BLOCKSIZE 1048576 + +// +// Global variables +// + +/** + * (Internal) Last error number set by vdisk_* functions. + */ +int vdisk_errno; +/** + * + */ +int vdisk_errln; +/** + * + */ +const char *vdisk_func; + +// +// Enumerations +// + +enum { // DISKFORMAT magical hints (LSB), used for VDISK.format + VDISK_FORMAT_NONE = 0, // No formats has been specificied yet + VDISK_FORMAT_RAW = 0xAAAAAAAA, // Files/Devices + VDISK_FORMAT_VDI = 0x203C3C3C, // "<<< " VirtualBox + VDISK_FORMAT_VMDK = 0x564D444B, // "VMDK" VMware + VDISK_FORMAT_VHD = 0x656E6F63, // "cone" VirtualPC/Hyper-V + VDISK_FORMAT_VHDX = 0x78646876, // "vhdx" Hyper-V + VDISK_FORMAT_QED = 0x00444551, // "QED\0" QEMU Enhanced Disk + VDISK_FORMAT_QCOW = 0xFB494651, // "QFI\xFB" QEMU Copy-On-Write, v1/v2 +// VDISK_FORMAT_DMG = 0x, // "" Apple DMG +// VDISK_FORMAT_PARAHDD = 0x, // "" Parallels HDD +// VDISK_FORMAT_CUE = 0x, // "" Cue/Bin, Disk metadata +}; + +enum { // VDISK flags for vdisk_open + VDISK_OPEN_RAW = 0x1, // Open or create vdisk as raw + VDISK_CREATE = 0x2, // Create a vdisk if it doesn't exist + VDISK_CREATE_TEMP = 0x4, // Create a temporary (random) vdisk file + VDISK_CREATE_INIT = 0x8, // Init disk when creating vdisk + + VDISK_OPEN_VDI_ONLY = 0x100, // Open/create if VDISK is VDI + VDISK_OPEN_VMDK_ONLY = 0x200, // Open/create if VDISK is VMDK + VDISK_OPEN_VHD_ONLY = 0x300, // Open/create if VDISK is VHD + VDISK_OPEN_VHDX_ONLY = 0x400, // Open/create if VDISK is VHDX + VDISK_OPEN_QED_ONLY = 0x500, // Open/create if VDISK is QED + VDISK_OPEN_QCOW_ONLY = 0x600, // Open/create if VDISK is QCOW + + VDISK_CREATE_DYN = 0x1000, // Create a dynamic type VDISK + VDISK_CREATE_FIXED = 0x2000, // Create a fixed type VDISK +}; + +enum { // VDISK error codes + EVDOK = 0, // VDISK OK + EVDOPEN = -1, // VDISK could not be opened nor created + EVDREAD = -2, // Error reading VDISK + EVDSEEK = -3, // Error seeking VDISK + EVDWRITE = -4, // Error seeking VDISK + EVDFORMAT = -5, // Invalid VDISK format + EVDMAGIC = -6, // Invalid VDISK magic signature + EVDVERSION = -7, // Unsupported VDISK version (major) + EVDTYPE = -8, // Unsupported VDISK type + EVDFULL = -9, // VDISK is full and no more data can be allocated + EVDUNALLOC = -10, // Block is unallocated + EVDBOUND = -11, // Index was out of block index bounds + EVDALLOC = -15, // Could not allocate memory + EVDMISC = -16, // Unknown +}; + +// +// Structure definitions +// + +typedef struct VDISK { + uint32_t format; // See VDISKFORMAT, used by vdisk_open + uint32_t flags; // See VDISK_FLAG + uint32_t offset; // Calculated absolute data offset on disk + uint64_t nextblock; // (Internal) Location of new allocation block + // VHDX: + //uint64_t vsize; // Calculated capacity, virtual size + __OSFILE fd; // File descriptor or handle + union { + uint64_t *u64blocks; // 64-bit allocation blocks + uint32_t *u32blocks; // 32-bit allocation blocks + }; + union { + uint64_t u64nblocks; // Total amount of allocated blocks + uint32_t u32nblocks; // Total amount of allocated blocks + }; + //void (*read_lba)(VDISK *, void *, uint64_t); + //void (*write_lba)(VDISK *, void *, uint64_t); + //void (*read_seq)(VDISK *, void *); + //void (*write_seq)(VDISK *, void *); + // To avoid wasting memory space, and since a VDISK can only hold one + // format at a time, all structures are unionized. Version translation + // and header/format validity are done in vdisk_open. + union { + struct { // VDI + VDI_HDR vdihdr; + VDIHEADER1 vdi; + }; + // VMDK + struct VMDK_HDR vmdk; + struct { // VHD + VHD_HDR vhd; + VHD_DYN_HDR vhddyn; + }; + struct { // VHDX + VHDX_HDR vhdx; + VHDX_HEADER1 vhdxhdr; + VHDX_REGION_HDR vhdxreg; + }; + // QED + // QCOW + }; +} VDISK; + +// +// Functions +// + +/** + * Open, or create, a VDISK. + * + * When opening a file, this function verifies the file path, VDISK format, + * header structure, version, and other fields. + * + * When creating a file, the specified file at the file path is overwritten. + * An empty, unallocated VDISK is created. If VDISK_CREATE_TEMP is defined, + * path parameter can be NULL, since the function will create a random + * filename (OS). The fields are NOT populated, to + * + * OPEN VERSIONS + * VDI: 0.0, 1.0, 1.1 + * VMDK: + * + * CREATE VERSIONS + * VDI: 1.1 + * + * Returns error code. Non-zero being an error. + */ +int vdisk_open(_vchar *path, VDISK *vd, uint16_t flags); + +/** + * Initiate VDISK with default/empty structure values. + */ +int vdisk_default(VDISK *vd); + +/** + * + */ +char *vdisk_str(VDISK *vd); + +/** + * Update all headers and allocation tables into file or device. + */ +int vdisk_update_headers(VDISK *vd); + +/** + * Seek and read a sector-size (512 bytes) of data from a sector index (LBA). + * + * This function checks if sector exists on dynamic type disks, and index + * tables such as the BAT on VHDs. + * + * Returns error code. Non-zero being an error. + */ +int vdisk_read_lba(VDISK *vd, void *buffer, uint64_t lba); + +/** + * Seek to a block index and read it. The size of the block depends on the size + * speicified in the VDISK structure. Only certain VDISK types are supported, + * notably dynamic types. If unsupported, returns EVDFORMAT or EVDTYPE. + */ +int vdisk_read_block(VDISK *vd, void *buffer, uint64_t index); + +/** + * + */ +int vdisk_write_lba(VDISK *vd, void *buffer, uint64_t lba); + +/** + * + */ +int vdisk_write_block(VDISK *vd, void *buffer, uint64_t index); + +/** + * + */ +int vdisk_write_block_at(VDISK *vd, void *buffer, uint64_t bindex, uint64_t dindex); + +// +// Error handling +// + +/** + * + */ +char* vdisk_error(); + +/** + * + */ +void vdisk_perror(const char *func); + +/** + * + */ +int vdisk_last_errno(); diff --git a/src/vdisk/vdi.c b/src/vdisk/vdi.c new file mode 100644 index 0000000..603b0dd --- /dev/null +++ b/src/vdisk/vdi.c @@ -0,0 +1,191 @@ +#include +#include // memcpy +#include "../utils.h" +#include "../vdisk.h" + +void vdi_info(VDISK *vd) { + char *type; // vdisk type + switch (vd->vdi.type) { + case VDI_DISK_DYN: type = "dynamic"; break; + case VDI_DISK_FIXED: type = "fixed"; break; + case VDI_DISK_UNDO: type = "undo"; break; + case VDI_DISK_DIFF: type = "diff"; break; + default: type = "type?"; + } + + char disksize[BIN_FLENGTH]; + char bsize[BIN_FLENGTH]; // block size + char create_uuid[GUID_TEXT_SIZE], modify_uuid[GUID_TEXT_SIZE], + link_uuid[GUID_TEXT_SIZE], parent_uuid[GUID_TEXT_SIZE]; + + fbins(vd->vdi.disksize, disksize); + fbins(vd->vdi.blocksize, bsize); + uuid_tostr(create_uuid, &vd->vdi.uuidCreate); + uuid_tostr(modify_uuid, &vd->vdi.uuidModify); + uuid_tostr(link_uuid, &vd->vdi.uuidLinkage); + uuid_tostr(parent_uuid, &vd->vdi.uuidParentModify); + + printf( + "VDI, VirtualBox %s vdisk v%u.%u, %s\n" + "Header size: %u, Flags: %XH, Dummy: %u\n" + "Blocks: %u (allocated: %u, extra: %u), %s size\n" + "Offset to data: %Xh, to alloc blocks: %Xh\n" + "Cylinders: %u (legacy: %u)\n" + "Heads: %u (legacy: %u)\n" + "Sectors: %u (legacy: %u)\n" + "Sector size: %u (legacy: %u)\n" + "Create UUID : %s\n" + "Modifiy UUID: %s\n" + "Linkage UUID: %s\n" + "Parent UUID : %s\n", + type, vd->vdihdr.majorv, vd->vdihdr.minorv, disksize, + vd->vdi.hdrsize, vd->vdi.fFlags, vd->vdi.u32Dummy, + vd->vdi.totalblocks, vd->vdi.blocksalloc, vd->vdi.blocksextra, bsize, + vd->vdi.offData, vd->vdi.offBlocks, + vd->vdi.cCylinders, vd->vdi.LegacyGeometry.cCylinders, + vd->vdi.cHeads, vd->vdi.LegacyGeometry.cHeads, + vd->vdi.cSectors, vd->vdi.LegacyGeometry.cSectors, + vd->vdi.cbSector, vd->vdi.LegacyGeometry.cbSector, + create_uuid, modify_uuid, link_uuid, parent_uuid + ); +} + +int vdi_compact(VDISK *vd) { + if (vd->format != VDISK_FORMAT_VDI) { + fputs("vdi_compact: vdisk not VDI\n", stderr); + return EVDFORMAT; + } + if (vd->vdi.type != VDI_DISK_DYN) { + fputs("vdi_compact: vdisk not dynamic\n", stderr); + return EVDTYPE; + } + + uint8_t *buffer; // Block buffer + + // + // Block buffer for transfer + // + + if ((buffer = malloc(vd->vdi.blocksize)) == NULL) + return EVDALLOC; + + // + // Temporary VDISK + // + // Also assigns the same attributes from the source vdisk + // + + VDISK vdtmp; + if ((vdtmp.u32blocks = malloc(vd->u32nblocks << 2)) == NULL) + return EVDALLOC; + vdtmp.offset = vd->offset; + vdtmp.format = vd->format; + vdtmp.u32nblocks = vd->u32nblocks; + for (size_t i = 0; i < vdtmp.u32nblocks; ++i) + vdtmp.u32blocks[i] = VDI_BLOCK_UNALLOCATED; + memcpy(&vdtmp.vdihdr, &vd->vdihdr, sizeof(VDI_HDR)); + memcpy(&vdtmp.vdi, &vd->vdi, sizeof(VDIHEADER1)); + if (vdisk_open(NULL, &vdtmp, VDISK_CREATE_TEMP)) { + vdisk_perror(__func__); + return vdisk_errno; + } + printf("vdi_compact: %s disk created\n", vdisk_str(&vdtmp)); + + // + // Block tranfer + // + // Status progress + // Original: + // 12,803,112,960 Bytes + // vboxmanage --compact: + // 11,011,096,576 Bytes + // 1. Initial (vdisk_write_block_at): + // 11,917,066,240 Bytes + // 2. Direct write (os_write) + // 11,914,969,088 Bytes + // 3. + // Bytes + // 4. + // Bytes + // 5. + // Bytes + // 6. + // Bytes + // 7. + // Bytes + // 8. + // Bytes + // 9. + // Bytes + // + +/* + +Original: +| 0 | 1 | 4 | - | 3 | <- Block index + | | | /---+ + | | +---|---\ + v v v v +| | | - | | | <- Block data (1 MiB) + +Compacted: +| 0 | 1 | 2 | - | 3 | + | | | +---+ + v v v v +| | | | | - | + +*/ + + // "Optimized" buffer size + size_t oblocksize = vd->vdi.blocksize / sizeof(size_t); + // "Optimized" buffer pointer + size_t *obuffer = (size_t*)buffer; + uint32_t stat_unalloc = 0; // unallocated blocks + uint32_t stat_occupied = 0; // allocated blocks with data + uint32_t stat_zero = 0; // blocks with no data inside + uint32_t stat_alloc = 0; // allocated blocks + char strbsize[BIN_FLENGTH]; + fbins(vd->vdi.blocksize, strbsize); + printf("vdi_compact: Writing (%s blocks, %u checks/%u bytes)...\n", + strbsize, (uint32_t)oblocksize, (uint32_t)sizeof(size_t)); + uint64_t d = 0; // disk block index + os_seek(vd->fd, vd->offset, SEEK_SET); + for (size_t i = 0; i < vd->u32nblocks; ++i) { + if (vd->u32blocks[i] == VDI_BLOCK_UNALLOCATED || + vd->u32blocks[i] == VDI_BLOCK_FREE) { + vdtmp.u32blocks[i] = VDI_BLOCK_UNALLOCATED; + ++stat_unalloc; + continue; + } + int re = vdisk_read_block(vd, buffer, i); + if (re == EVDREAD || re == EVDSEEK) { + fprintf(stderr, "vdi_compact: Couldn't %s disk\n", + re == EVDSEEK ? "seek" : "read"); + return re; + } + // Check if block has data, if so, write the block into tmp VDISK + ++stat_alloc; + for (size_t b = 0; b < oblocksize; ++b) { + if (obuffer[b]) + goto L_HASDATA; + } + ++stat_zero; + continue; +L_HASDATA: + //if (vdisk_write_block_at(&vdtmp, buffer, i, d)) { + // fputs("vdi_compact: Couldn't write to disk\n", stderr); + // return EVDWRITE; + //} + vdtmp.u32blocks[i] = i; + os_write(vdtmp.fd, buffer, vd->vdi.blocksize); + ++stat_occupied; + ++d; + } + vdtmp.vdi.blocksalloc = stat_occupied; + vdisk_update_headers(&vdtmp); + printf( + "vdi_compact: %u/%u blocks written, %u unallocated, %u zero, %u total\n", + stat_occupied, stat_alloc, stat_unalloc, stat_zero, vd->u32nblocks + ); + return 0; +} diff --git a/src/vdisk/vdi.h b/src/vdisk/vdi.h new file mode 100644 index 0000000..40916ac --- /dev/null +++ b/src/vdisk/vdi.h @@ -0,0 +1,91 @@ +/** + * + * + * https://forums.virtualbox.org/viewtopic.php?t=8046 + */ + +#include +#include "../guid.h" + +#define VDI_SIGNATURE "<<< Oracle VM VirtualBox Disk Image >>>\n" +#define VDI_SIGNATURE_OLDER "<<< InnoTek VirtualBox Disk Image >>>\n" +enum { + VDI_HEADER_MAGIC = 0xBEDA107F, + VDI_SIGNATURE_SIZE = 64, + VDI_COMMENT_SIZE = 256, + + VDI_BLOCK_UNUSED = 0, // Usually not used + VDI_BLOCK_UNALLOCATED = -1, // "not on disk" + VDI_BLOCK_FREE = -2, // aka ZERO + + VDI_DISK_DYN = 1, + VDI_DISK_FIXED = 2, + VDI_DISK_UNDO = 3, + VDI_DISK_DIFF = 4, + + VDI_BLOCKSIZE = 1024 * 1024, // Typical block size, 1 MiB +}; + +typedef struct VDIDISKGEOMETRY { + uint32_t cCylinders; + uint32_t cHeads; + uint32_t cSectors; + uint32_t cbSector; +} VDIDISKGEOMETRY; + +typedef struct VDI_HDR { // Excludes char[64] at start + uint32_t magic; + uint16_t majorv; + uint16_t minorv; +} VDI_HDR; + +typedef struct VDIHEADER0 { // v0.0 + uint32_t type; + uint32_t fFlags; + uint8_t szComment[VDI_COMMENT_SIZE]; + VDIDISKGEOMETRY LegacyGeometry; + uint64_t disksize; + uint32_t blocksize; + uint32_t totalblocks; + uint32_t blocksalloc; + __GUID uuidCreate; + __GUID uuidModify; + __GUID uuidLinkage; +} VDIHEADER0; + +typedef struct VDIHEADER1 { // v1.1 + uint32_t hdrsize; + uint32_t type; + uint32_t fFlags; + uint8_t szComment[VDI_COMMENT_SIZE]; + uint32_t offBlocks; // Byte offset to BAT + uint32_t offData; // Byte offset to first block + VDIDISKGEOMETRY LegacyGeometry; + uint32_t u32Dummy; // Used to be translation value for geometry + uint64_t disksize; + uint32_t blocksize; + uint32_t blocksextra; + uint32_t totalblocks; + uint32_t blocksalloc; + __GUID uuidCreate; + __GUID uuidModify; + __GUID uuidLinkage; + __GUID uuidParentModify; + uint32_t cCylinders; // v1.1 + uint32_t cHeads; // v1.1 + uint32_t cSectors; // v1.1 + uint32_t cbSector; // v1.1 +// uint8_t pad[40]; +} VDIHEADER1; + +struct VDISK; + +/** + * Print VDI information to stdout. + */ +void vdi_info(struct VDISK *vd); + +/** + * + */ +int vdi_compact(struct VDISK *vd); diff --git a/src/vdisk/vhd.c b/src/vdisk/vhd.c new file mode 100644 index 0000000..78993ce --- /dev/null +++ b/src/vdisk/vhd.c @@ -0,0 +1,57 @@ +#include +#include // PRIxxx +#include "../vdisk.h" +#include "../fs/mbr.h" +#include "../utils.h" + +void vhd_info(VDISK *vd) { // big-endian + char sizec[BIN_FLENGTH], sizeo[BIN_FLENGTH], uuid[GUID_TEXT_SIZE]; + char *type, *os; + + switch (vd->vhd.type) { + case VHD_DISK_FIXED: type = "fixed"; break; + case VHD_DISK_DYN: type = "dynamic"; break; + case VHD_DISK_DIFF: type = "differencing"; break; + default: + type = vd->vhd.type <= 6 ? "reserved (deprecated)" : "unknown"; + } + + switch (vd->vhd.creator_os) { + case VHD_OS_WIN: os = "Windows"; break; + case VHD_OS_MAC: os = "macOS"; break; + default: os = "unknown"; break; + } + + uuid_tostr(uuid, &vd->vhd.uuid); + fbins(vd->vhd.size_current, sizec); + fbins(vd->vhd.size_original, sizeo); + printf( + "Conectix/Microsoft VHD vdisk v%u.%u, %s %s/%s, %.4s v%u.%u on %s\n" + "Cylinders: %u, Heads: %u, Sectors: %u\n" + "CRC32: %08X, UUID: %s\n" + , + vd->vhd.major, vd->vhd.minor, type, sizec, sizeo, + vd->vhd.creator_app, vd->vhd.creator_major, vd->vhd.creator_minor, os, + vd->vhd.cylinders, vd->vhd.heads, vd->vhd.sectors, + vd->vhd.checksum, + uuid + ); + if (vd->vhd.type != VHD_DISK_FIXED) { + char paruuid[GUID_TEXT_SIZE]; + uuid_tostr(paruuid, &vd->vhddyn.parent_uuid); + printf( + "Dynamic header v%u.%u, data: %" PRIu64 ", table: %" PRIu64 "\n" + "Blocksize: %u, checksum: %08X\n" + "Parent UUID: %s, Parent timestamp: %u\n" + "%u BAT Entries, %u maximum BAT entries\n" + , + vd->vhddyn.minor, vd->vhddyn.major, + vd->vhddyn.data_offset, vd->vhddyn.table_offset, + vd->vhddyn.blocksize, vd->vhddyn.checksum, + paruuid, vd->vhddyn.parent_timestamp, + vd->u32nblocks, vd->vhddyn.max_entries + ); + } + if (vd->vhd.savedState) + puts("+ Saved state"); +} \ No newline at end of file diff --git a/src/vdisk/vhd.h b/src/vdisk/vhd.h new file mode 100644 index 0000000..5c637f9 --- /dev/null +++ b/src/vdisk/vhd.h @@ -0,0 +1,75 @@ +#include + +#define VHDMAGIC "conectix" +#define VHD_MAGIC 0x78697463656E6F63 // "conectix" +#define VHD_DYN_MAGIC 0x6573726170737863 // "cxsparse" +#define VHD_OS_WIN 0x6B326957 // "Wi2k" +#define VHD_OS_MAC 0x2063614D // "Mac " +enum { + VHD_DISK_NONE = 0, + VHD_DISK_RES1 = 1, + VHD_DISK_FIXED = 2, + VHD_DISK_DYN = 3, + VHD_DISK_DIFF = 4, + VHD_DISK_RES2 = 5, + VHD_DISK_RES3 = 6 +}; +enum { + VHD_FEAT_TEMP = 1, + VHD_FEAT_RES = 2 // reserved, but always set +}; + +typedef struct VHD_HDR { // v1 + uint64_t magic; // "conectix" + uint32_t features; + uint16_t major; + uint16_t minor; + uint64_t offset; + uint32_t timestamp; + char creator_app[4]; + uint16_t creator_major; + uint16_t creator_minor; + uint32_t creator_os; + uint64_t size_original; + uint64_t size_current; + uint16_t cylinders; + uint8_t heads; + uint8_t sectors; + uint32_t type; + uint32_t checksum; + __GUID uuid; + uint8_t savedState; + uint8_t reserved[427]; +} VHD_HDR; + +typedef struct VHD_PARENT_LOCATOR { + uint32_t code; + uint32_t dataspace; + uint32_t datasize; + uint32_t res; + uint64_t offset; +} VHD_PARENT_LOCATOR; + +typedef struct VHD_DYN_HDR { // v1 + uint64_t magic; + uint64_t data_offset; + uint64_t table_offset; + uint16_t minor; + uint16_t major; + uint32_t max_entries; // For table + uint32_t blocksize; + uint32_t checksum; + __GUID parent_uuid; // UUID + uint32_t parent_timestamp; + uint32_t res; + uint8_t parent_name[512]; // UTF-16 + VHD_PARENT_LOCATOR parent_locator[8]; + uint8_t res1[256]; +} VHD_DYN_HDR; + +struct VDISK; + +/** + * Print VHD information to stdout. + */ +void vhd_info(struct VDISK *vd); diff --git a/src/vdisk/vhdx.c b/src/vdisk/vhdx.c new file mode 100644 index 0000000..0d39aa5 --- /dev/null +++ b/src/vdisk/vhdx.c @@ -0,0 +1,9 @@ +#include +#include // PRIxxx +#include "../vdisk.h" +#include "../fs/mbr.h" +#include "../utils.h" + +void vhdx_info(VDISK *vd) { + +} \ No newline at end of file diff --git a/src/vdisk/vhdx.h b/src/vdisk/vhdx.h new file mode 100644 index 0000000..5cbb65f --- /dev/null +++ b/src/vdisk/vhdx.h @@ -0,0 +1,127 @@ +/** + * MS-VHDX v20160714 + * + * BAT entry 8-Byte + */ +#include +#include "../guid.h" + +#define VHDX_MAGIC 0x656C696678646876 // "vhdxfile" +#define VHDX_HDR1_MAGIC 0x64616568 // "head" +#define VHDX_REGION_MAGIC 0x69676572 // "regi" +#define VHDX_LOG_HDR_MAGIC 0x65676F6C // "loge" +#define VHDX_LOG_ZERO_MAGIC 0x6F72657A // "zero" +#define VHDX_LOG_DESC_MAGIC 0x63736564 // "desc" +#define VHDX_LOG_DATA_MAGIC 0x61746164 // "data" +#define VHDX_METADATA_MAGIC 0x617461646174656D // "metadata" + +enum { + VHDX_HEADER1_LOC = 64 * 1024, // 64 KiB + VHDX_HEADER2_LOC = VHDX_HEADER1_LOC * 2, // 128 KiB + VHDX_REGION1_LOC = VHDX_HEADER1_LOC * 3, // 192 KiB + VHDX_REGION2_LOC = VHDX_HEADER1_LOC * 4, // 256 KiB + VDHX_LOG_ALIGN = 4 * 1024, // 4 KiB +}; + +typedef struct VHDX_HDR { + uint64_t magic; + uint16_t creator[256]; +} VHDX_HDR; + +typedef struct VHDX_HEADER1 { + uint32_t magic; + uint32_t crc32; + uint32_t seqnumber; + __GUID filewrite; + __GUID datawrite; + __GUID log; + uint16_t logversion; + uint16_t version; + uint32_t logsize; + uint64_t logoffset; + // Rest is reserved (4016 Bytes) +} VHDX_HEADER1; + +typedef struct VHDX_REGION_HDR { + uint32_t magic; + uint32_t crc32; + uint32_t count; + uint32_t res; +} VHDX_REGION_HDR; + +typedef struct VHDX_REGION_ENTRY { + // BAT 2DC27766-F623-4200-9D64-115E9BFD4A08 required + // METADATA 8B7CA206-4790-4B9A-B8FE-575F050F886E required + __GUID guid; + uint64_t offset; + uint32_t length; + uint32_t required; +} VHDX_REGION_ENTRY; + +typedef struct VHDX_LOG_HDR { + uint32_t magic; + uint32_t crc32; + uint32_t count; + uint32_t tail; + uint64_t sequence; + uint32_t desccount; + uint32_t res; + __GUID guid; + uint64_t flushedoffset; + uint64_t lastoffset; +} VHDX_LOG_HDR; + +typedef struct VHDX_LOG_ZERO { + uint32_t magic; + uint32_t res; + uint64_t length; // Multiple of 4 KiB + uint64_t offset; // Multiple of 4 KiB + uint64_t sequence; +} VHDX_LOG_ZERO; + +typedef struct VDHX_LOG_DESC { + uint32_t magic; + uint32_t trail; + uint64_t leading; + uint64_t offset; + uint64_t sequence; +} VDHX_LOG_DESC; + +typedef struct VHDX_LOG_DATA { + uint32_t magic; + union { + uint8_t cdata[4096]; // Cluster + struct { + uint32_t sequenceh; + union { + uint8_t data[4084]; // as per doc + struct { + uint64_t lead; + uint8_t rdata[4068]; // The real data + uint64_t trail; + }; + }; + uint32_t sequencel; + }; + }; +} VHDX_LOG_DATA; + +typedef struct VHDX_METADATA_HDR { + uint64_t magic; + uint16_t res; + uint16_t count; + uint8_t res2[20]; +} VHDX_METADATA_HDR; + +typedef struct VHDX_METADATA_ENTRY { + // File Parameters CAA16737-FA36-4D43-B3B6-33F0AA44E76B + // Virtual Disk Size 2FA54224-CD1B-4876-B211-5DBED83BF4B8 + // Page 83 Data BECA12AB-B2E6-4523-93EF-C309E000C746 + // Logical Sector Size 8141BF1D-A96F-4709-BA47-F233A8FAAB5F + // Logical Sector Size CDA348C7-445D-4471-9CC9-E9885251C556 + // Parent Locator A8D35F2D-B30B-454D-ABF7-D3D84834AB0C + __GUID type; // itemID + uint32_t offset; + uint32_t length; + uint32_t flags; // ...plus 2 bits? what the hell? +} VHDX_METADATA_ENTRY; diff --git a/src/vdisk/vmdk.c b/src/vdisk/vmdk.c new file mode 100644 index 0000000..bf40632 --- /dev/null +++ b/src/vdisk/vmdk.c @@ -0,0 +1,29 @@ +#include +#include // PRIxxx +#include "../vdisk.h" +#include "../fs/mbr.h" +#include "../utils.h" + +void vmdk_info(VDISK *vd) { + char *comp; // compression + //if (h.flags & COMPRESSED) + switch (vd->vmdk.compressAlgorithm) { + case 0: comp = "no"; break; + case 1: comp = "DEFLATE"; break; + default: comp = "?"; + } + + char size[BIN_FLENGTH]; + fbins(SECTOR_TO_BYTE(vd->vmdk.capacity), size); + printf( + "VMDK, VMware vdisk v%u, %s compression, %s\n" + "\nCapacity: %"PRIu64" Sectors\n" + "Overhead: %"PRIu64" Sectors\n" + "Grain size (Raw): %"PRIu64" Sectors\n", + vd->vmdk.version, comp, size, + vd->vmdk.capacity, vd->vmdk.overHead, vd->vmdk.grainSize + ); + + if (vd->vmdk.uncleanShutdown) + printf("+ Unclean shutdown"); +} diff --git a/src/vdisk/vmdk.h b/src/vdisk/vmdk.h new file mode 100644 index 0000000..d90f251 --- /dev/null +++ b/src/vdisk/vmdk.h @@ -0,0 +1,50 @@ +#include + +enum { + VDMK_COMPRESSED = 0x10000, // Flag[BIN_FLENGTH] + VMDK_2G_SPLIT_SIZE = 2047 * 1024 * 1024, // 64K Grain size *512 (2G) +}; +enum { + VMDK_MARKER_EOS = 0, // end-of-stream + VMDK_MARKER_GT = 1, // grain table marker + VMDK_MARKER_GD = 2, // grain directory marker + VMDK_MARKER_FOOTER = 3, // footer marker + + VMDK_DISK_DYN = 1, // (SELF DEFINED) A.K.A. Sparse + VMDK_DISK_FIXED = 2, // (SELF DEFINED) +}; + +typedef struct VMDK_HDR { + uint32_t magicNumber; + uint32_t version; + uint32_t flags; + uint64_t capacity; // in sectors + uint64_t grainSize; + uint64_t descriptorOffset; + uint64_t descriptorSize; + uint32_t numGTEsPerGT; + uint64_t rgdOffset; + uint64_t gdOffset; + uint64_t overHead; + uint8_t uncleanShutdown; // "Bool" + uint8_t singleEndLineChar; // usually '\n' + uint8_t nonEndLineChar; // usually ' ' + uint8_t doubleEndLineChar1; // usually '\r' + uint8_t doubleEndLineChar2; // usually '\n' + uint16_t compressAlgorithm; + uint8_t pad[433]; +} VMDK_HDR; // 512 bytes then 10 KiB of text buffer + +typedef struct VMDK_MARKER { + uint64_t uSector; + uint32_t cbSize; + uint32_t uType; + uint8_t pad[496]; +} VMDK_MARKER; + +struct VDISK; + +/** + * Print VMDK information to stdout. + */ +void vmdk_info(struct VDISK *vd); diff --git a/src/vvd.c b/src/vvd.c new file mode 100644 index 0000000..15eae39 --- /dev/null +++ b/src/vvd.c @@ -0,0 +1,175 @@ +/** + * Main vvd operations + */ +#include +#include // memcpy +#include +#include "vvd.h" +#include "utils.h" +#include "fs/mbr.h" + +// +// vvd_info +// + +int vvd_info(VDISK *vd) { + switch (vd->format) { + case VDISK_FORMAT_VDI: + vdi_info(vd); + break; + case VDISK_FORMAT_VMDK: + vmdk_info(vd); + break; + case VDISK_FORMAT_VHD: + vhd_info(vd); + break; + case VDISK_FORMAT_RAW: break; // No header info + default: + fputs("vvd_info: Format not supported\n", stderr); + return ECLIFORMAT; + } + + mbr_info_auto(vd); + return EVDOK; +} + +// +// vvd_map +// + +int vvd_map(VDISK *vd) { //TODO: Add column width + char bsizestr[BIN_FLENGTH]; // If used + uint64_t blocksize; + uint32_t blocksn = vd->u32nblocks; + uint32_t *blocks = vd->u32blocks; + + switch (vd->format) { + case VDISK_FORMAT_VHD: + if (vd->vhd.type != VHD_DISK_DYN) { + fputs("vvd_map: VHD is not dynamic\n", stderr); + return EVDTYPE; + } + blocksize = vd->vhddyn.blocksize; + break; + case VDISK_FORMAT_VDI: + blocksize = vd->vdi.blocksize; + blocksn = vd->vdi.totalblocks; + break; + default: + fputs("vvd_map: Unsupported format\n", stderr); + return EVDFORMAT; + } + + fbins(blocksize, bsizestr); + printf( + "Allocation map (%u blocks at %s)\n" + " | 0 | 1 | 2 | 3 |" + " 4 | 5 | 6 | 7 |\n" + "----------+----------+----------+----------+----------+" + "----------+----------+----------+----------+\n", + blocksn, bsizestr + ); + size_t i = 0; + size_t bn = blocksn - 8; + for (; i < bn; i += 8) { + printf( + " %8zu | %8X | %8X | %8X | %8X | %8X | %8X | %8X | %8X |\n", + i, + blocks[i], blocks[i + 1], + blocks[i + 2], blocks[i + 3], + blocks[i + 4], blocks[i + 5], + blocks[i + 6], blocks[i + 7] + ); + } + if (blocksn - i > 0) { // Left over + printf(" %8zu |", i); + for (; i < blocksn; ++i) + printf(" %8X |", blocks[i]); + putchar('\n'); + } + return ECLIOK; +} + +// +// vvd_new +// + +int vvd_new(VDISK *vd, uint64_t vsize) { + uint8_t *buffer; + + switch (vd->format) { + case VDISK_FORMAT_VDI: + vd->u32nblocks = vsize / vd->vdi.blocksize; + if (vd->u32nblocks == 0) + vd->u32nblocks = 1; + uint32_t bsize = vd->u32nblocks << 2; + if ((vd->u32blocks = malloc(bsize)) == NULL) + return ECLIALLOC; + vd->vdi.totalblocks = vd->u32nblocks; + vd->vdi.disksize = vsize; + switch (vd->vdi.type) { + case VDI_DISK_DYN: + for (size_t i = 0; i < vd->vdi.totalblocks; ++i) + vd->u32blocks[i] = VDI_BLOCK_UNALLOCATED; + break; + case VDI_DISK_FIXED: + if ((buffer = malloc(vd->vdi.blocksize)) == NULL) + return ECLIALLOC; + os_seek(vd->fd, vd->vdi.offData, SEEK_SET); + for (size_t i = 0; i < vd->vdi.totalblocks; ++i) { + vd->u32blocks[i] = VDI_BLOCK_FREE; + os_write(vd->fd, buffer, vd->vdi.blocksize); + } + break; + default: + fputs("vvd_new: Type currently unsupported", stderr); + return EVDTYPE; + } + break; + default: + fputs("vvd_new: Format currently unsupported", stderr); + return EVDFORMAT; + } + +L_DISKEMPTY: + if (vdisk_update_headers(vd)) { + fputs("vvd_new: Could not write headers\n", stderr); + return 1; + } + return 0; +} + +// +// vvd_compact +// + +int vvd_compact(VDISK *vd) { + if (vd->u32nblocks == 0) { + fputs("vvd_compact: No allocated blocks\n", stderr); + return EVDMISC; + } + + switch (vd->format) { + case VDISK_FORMAT_VDI: return vdi_compact(vd); + case VDISK_FORMAT_RAW: + fputs("vvd_compact: RAW files/devices are not supported\n", + stderr); + return EVDFORMAT; + default: + fputs("vvd_compact: VDISK format not supported\n", + stderr); + return EVDFORMAT; + } + + return 0; +} + +// +// vvd_write (file) +// + +int vvd_write(VDISK *vd, __OSFILE *fd) { + + + return 0; +} diff --git a/src/vvd.h b/src/vvd.h new file mode 100644 index 0000000..0269cd9 --- /dev/null +++ b/src/vvd.h @@ -0,0 +1,48 @@ +#include "vdisk.h" + +enum { // OPERATIONS, main operations from CLI + MODE_INFO = 'I', // Disk information + MODE_STATS = 'S', // Statistics + MODE_MAP = 'M', // Map (print allocation map) + MODE_NEW = 'N', // New empty disk + MODE_COMPACT = 'C', // Compress/compact/trim + MODE_DEFRAG = 'D', // Defragmentation tool + MODE_CONVERT = 'T', // Translate format + MODE_RESIZE = 'R', // Shrink/expand + MODE_PARTITION = 'P', // TUI Partition tool +}; + +enum { // CLI/VVD error codes + ECLIOK = 0, + ECLIARG = 1, // Invalid/missing CLI option + ECLIOPEN = 2, // Could not open or create VDISK + ECLIFORMAT = 3, // Format not supported for OPERATION + ECLIALLOC = 4, // Memory allocation failed +}; + +/** + * MODE_INFO: Print VDISK information to stdout. + * + * This includes information about the VDISK format and type, MBR, GPT, and + * when available, the operating system filesystem. + */ +int vvd_info(VDISK *vd); + +/** + * MODE_MAP: Print VDISK allocation map to stdout. + */ +int vvd_map(VDISK *vd); + +/** + * MODE_NEW: + */ +int vvd_new(VDISK *vd, uint64_t vsize); + +/** + * MODE_COMPACT: Compact a VDISK. + * + * First, the VDISK is checked if the type is dynamic. + * If so, it is defragmented (regarding blocks), then proceeds to remove + * unallocated blocks from the VDISK. + */ +int vvd_compact(VDISK *vd);