Newer
Older
vvd / src / main.c
#ifdef _WIN32 // Windows
#include <Windows.h>
#include <WinBase.h>
#include <tchar.h>
#include <wchar.h>
#else // POSIX

#endif

#define __DATETIME__ __DATE__ " " __TIME__
#define PROJECT_VERSION	"0.0.0"
#define COPYRIGHT "Copyright (c) 2019-2021 dd86k <dd@dax.moe>"

#include <stdio.h>
#include <stdlib.h>
#include <inttypes.h>
#include <string.h>
#include "utils/bin.h"
#include "utils/platform.h"
#include "utils/hash.h"
#include "vvd.h"

//TODO: Move "unit" tests into its own compilation unit
#ifdef DEBUG
#include <assert.h>
#include "fs/gpt.h"
#include "fs/mbr.h"

//
// test
//

void test() {
	//TODO: Move this to its own (tests/init.c)
	//	With others like tests/vdisk_qed.c
	fputs(
	"* Defines\n"
#ifdef ENDIAN_LITTLE
	"ENDIAN_LITTLE\n"
#endif
#ifdef ENDIAN_BIG
	"ENDIAN_BIG\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	VDI_HDR		%u+%u\n"
	"sizeof	VMDK_HDR	%u\n"
	"sizeof	VHD_HDR		%u\n"
	"sizeof	VHDX_HDR	%u+%u\n"
	"sizeof	QED_HDR		%u\n"
	"sizeof	QCOW_HDR	%u\n"
	"sizeof	PHDD_HDR	%u\n"
	"sizeof	wchar_t		%u\n"
	"Running tests...\n",
	(int)sizeof(VDISK),
	(int)sizeof(VDI_HDR), (int)sizeof(VDI_HEADERv1),
	(int)sizeof(VMDK_HDR),
	(int)sizeof(VHD_HDR),
	(int)sizeof(VHDX_HDR), (int)sizeof(VHDX_HEADER1),
	(int)sizeof(QED_HDR),
	(int)sizeof(QCOW_HDR),
	(int)sizeof(PHDD_HDR),
	(int)sizeof(wchar_t)
	);
	assert(sizeof(MBR) == 512);
	assert(sizeof(MBR_PARTITION) == 16);
	assert(sizeof(CHS) == 3);
	assert(sizeof(GPT) == 512);
	assert(sizeof(GPT_ENTRY) == 512);
	assert(sizeof(LBA64) == 8);
	// VDI
	assert(sizeof(VDI_HDR) == 8);
	assert(sizeof(VDI_DISKGEOMETRY) == 16);
	assert(sizeof(VDI_HEADERv0) == 348);
	assert(sizeof(VDI_HEADERv1) == 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
//

static void help() {
	puts(
	"Manage virtual disks\n"
	"  Usage: vvd OPERATION [FILE] [OPTIONS]\n"
	"         vvd PAGE\n"
	"         vvd {--help|--version|--license}\n"
	"\n"
	"OPERATION\n"
	"  info       Get vdisk image information\n"
	"  new        Create new empty vdisk\n"
	"  map        Show allocation map\n"
	"  compact    Compact vdisk image\n"
	"\n"
	"PAGES\n"
	"  help       Show help page and exit\n"
	"  ver        Only show version and exit\n"
	"  version    Show version page and exit\n"
	"  license    Show license page and exit\n"
	"\n"
	"OPTIONS\n"
	"  --raw           Open as RAW\n"
	"  --create-raw    Create as RAW\n"
	"  --create-dyn    Create vdisk as dynamic\n"
	"  --create-fixed  Create vdisk as fixed\n"
	);
	exit(EXIT_SUCCESS);
}

static void version(void) {
	printf(
	"vvd-" __PLATFORM__ " " PROJECT_VERSION " (compiled " __DATETIME__ ")\n"
#ifdef __VERSION__
	"Compiler: " __VERSION__ "\n"
#endif
	"MIT License: " COPYRIGHT "\n"
	"Project page: <https://github.com/dd86k/vvd>\n"
	"Defines: "
#ifdef DEBUG
	"DEBUG "
#endif
#ifdef TRACE
	"TRACE "
#endif
	"\n\n"
	"FORMAT	OPERATIONS\n"
	"VDI	info, map, new, compact\n"
	"VMDK	info\n"
	"VHD	info, map\n"
	"VHDX	\n"
	"QED	info, map\n"
	"QCOW	\n"
	"PHDD	\n"
	"RAW	info\n"
	);
	exit(EXIT_SUCCESS);
}

static void license() {
	puts(
	COPYRIGHT "\n"
	"\n"
	"Permission to use, copy, modify, and/or distribute this software for any\n"
	"purpose with or without fee is hereby granted, provided that the above\n"
	"copyright notice and this permission notice appear in all copies.\n"
	"\n"
	"THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH\n"
	"REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND\n"
	"FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,\n"
	"INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM\n"
	"LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR\n"
	"OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR\n"
	"PERFORMANCE OF THIS SOFTWARE."
	);
	exit(EXIT_SUCCESS);
}

#ifdef _WIN32
//TODO: Check if UNICODE is defined
#define MAIN wmain(int argc, wchar_t **argv)
#else
#define MAIN main(int argc, char **argv)
#endif

// NOTICE: Hashes may be cute, but for the command-line interface (CLI), that
//         would require both UTF-8 and UTF-16LE versions for all hashes (and
//         possibly more encodings), so no thanks. Besides, I'm lazy, and this
//         is obviously not critical code.

/**
 * Match a patch to an exception with the VDISK_FORMAT_* enum.
 * 
 * \returns VDISK_FORMAT_* enumeration value
 */
static int vdextauto(const oschar *path) {
	if (extcmp(path, osstr("vdi")))  return VDISK_FORMAT_VDI;
	if (extcmp(path, osstr("vmdk"))) return VDISK_FORMAT_VMDK;
	if (extcmp(path, osstr("vhd")))  return VDISK_FORMAT_VHD;
	if (extcmp(path, osstr("vhdx"))) return VDISK_FORMAT_VHDX;
	if (extcmp(path, osstr("qed")))  return VDISK_FORMAT_QED;
	if (extcmp(path, osstr("qcow")) || extcmp(path, osstr("qcow2")))
		return VDISK_FORMAT_QCOW;
	if (extcmp(path, osstr("hdd")))  return VDISK_FORMAT_PHDD;
	return VDISK_FORMAT_NONE;
}

// Main entry point. This only performs intepreting the command-line options
// for the core functions in vvd.c.
int MAIN {
	if (argc <= 1)
		help();

	uint32_t mflags = 0;	// main: Command-line flags
	uint32_t oflags = 0;	// vdisk_open: file flags
	uint32_t cflags = 0;	// vdisk_create: file flags
	VDISK vdin;	// vdisk IN
	VDISK vdout;	// vdisk OUT
	uint64_t vsize = 0;	// virtual disk size, used in 'new' and 'resize'
	const oschar *defopt = NULL;	// Default option for input file

	// Additional arguments are processed first, since they're simpler
	//TODO: --verbose: prints those extra lines (>v0.10.0)
	//TODO: --verify-repair
	for (size_t argi = 2; argi < argc; ++argi) {
		const oschar *arg = argv[argi];
		//
		// Generic
		//
		if (oscmp(arg, osstr("--size")) == 0) {
			if (argi + 1 >= argc) {
				fputs("main: missing argument for --size\n", stderr);
				return EXIT_FAILURE;
			}
			if (strtobin(&vsize, argv[++argi])) {
				fputs("main: failed to convert binary number\n", stderr);
				return EXIT_FAILURE;
			}
			continue;
		}
		if (oscmp(arg, osstr("--size")) == 0) {
			mflags |= VVD_PROGRESS;
			continue;
		}
		//
		// vdisk_open flags
		//
		if (oscmp(arg, osstr("--raw")) == 0) {
			oflags |= VDISK_RAW;
			continue;
		}
		//
		// vdisk_create flags
		//
		if (oscmp(arg, osstr("--create-raw")) == 0) {
			cflags |= VDISK_RAW;
			continue;
		}
		if (oscmp(arg, osstr("--create-dynamic")) == 0) {
			cflags |= VDISK_CREATE_TYPE_DYNAMIC;
			continue;
		}
		if (oscmp(arg, osstr("--create-fixed")) == 0) {
			cflags |= VDISK_CREATE_TYPE_FIXED;
			continue;
		}
		//
		// vvd_info flags
		//
		if (oscmp(arg, osstr("--info-full")) == 0) {
			mflags |= VVD_INFO_RAW;
			continue;
		}
		//
		// Default argument
		//
		if (defopt == NULL) {
			defopt = arg;
			continue;
		}

		fprintf(stderr, "main: '" OSCHARFMT "' unknown option\n", arg);
		return EXIT_FAILURE;
	}

	const oschar *action = argv[1];

	//
	// Main operation actions
	//

	if (oscmp(action, osstr("info")) == 0) {
		if (defopt == NULL) {
			fputs("main: missing vdisk\n", stderr);
			return EXIT_FAILURE;
		}
		if (vdisk_open(&vdin, defopt, oflags)) {
			vvd_perror(&vdin);
			return vdin.err.num;
		}
		return vvd_info(&vdin, mflags);
	}

	if (oscmp(action, osstr("map")) == 0) {
		if (defopt == NULL) {
			fputs("main: missing vdisk\n", stderr);
			return EXIT_FAILURE;
		}
		if (vdisk_open(&vdin, defopt, oflags)) {
			vvd_perror(&vdin);
			return vdin.err.num;
		}
		return vvd_map(&vdin, 0);
	}

	if (oscmp(action, osstr("compact")) == 0) {
		if (defopt == NULL) {
			fputs("main: missing vdisk\n", stderr);
			return EXIT_FAILURE;
		}
		if (vdisk_open(&vdin, defopt, 0)) {
			vvd_perror(&vdin);
			return vdin.err.num;
		}
		if (vvd_compact(&vdin, 0)) {
			vvd_perror(&vdin);
			return vdin.err.num;
		}
		return EXIT_FAILURE;
	}

	if (oscmp(action, osstr("defrag")) == 0) {
		fputs("main: not implemented\n", stderr);
		return EXIT_FAILURE;
	}

	if (oscmp(action, osstr("new")) == 0) {
		if (defopt == NULL) {
			fputs("main: missing path specifier\n", stderr);
			return EXIT_FAILURE;
		}
		if (vsize == 0) {
			fputs("main: capacity cannot be zero\n", stderr);
			return EXIT_FAILURE;
		}

		// Get vdisk type out of extension name
		int format = vdextauto(defopt);
		if (format == VDISK_FORMAT_NONE) {
			fputs("main: unknown extension\n", stderr);
			return EXIT_FAILURE;
		}

		return vvd_new(defopt, format, vsize, cflags);
	}

	if (oscmp(action, osstr("resize")) == 0) {
		fputs("main: not implemented\n", stderr);
		return EXIT_FAILURE;
	}

	if (oscmp(action, osstr("verify")) == 0) {
		fputs("main: not implemented\n", stderr);
		return EXIT_FAILURE;
	}

	if (oscmp(action, osstr("convert")) == 0) {
		fputs("main: not implemented\n", stderr);
		return EXIT_FAILURE;
	}

	if (oscmp(action, osstr("upgrade")) == 0) {
		fputs("main: not implemented\n", stderr);
		return EXIT_FAILURE;
	}

	if (oscmp(action, osstr("cleanfs")) == 0) {
		fputs("main: not implemented\n", stderr);
		return EXIT_FAILURE;
	}

	//
	// Internal things
	//

	if (oscmp(action, osstr("internalhash")) == 0) {
		if (defopt == NULL) {
			fputs("main: missing argument\n", stderr);
			return EXIT_FAILURE;
		}
#ifdef _WIN32
		char buffer[200];
		wcstombs(buffer, defopt, 200);
		printf("%08X\n", hash_string((char*)buffer));
#else
		printf("%08X\n", hash_string((char*)defopt));
#endif
		return EXIT_SUCCESS;
	}

	if (oscmp(action, osstr("internalguidhash")) == 0) {
		if (defopt == NULL) {
			fputs("main: missing argument\n", stderr);
			return EXIT_FAILURE;
		}
		UID uid;
#ifdef _WIN32
		char buffer[200];
		wcstombs(buffer, defopt, 200);
		int r = uid_parse(&uid, buffer, UID_GUID);
#else
		int r = uid_parse(&uid, defopt, UID_GUID);
#endif
		if (r) {
			if (r < 0)
				perror("main");
			else
				printf("main: failed to parse UID, got %d items\n", r);
			return EXIT_FAILURE;
		}
		printf("%08X\n", hash_compute((const char*)&uid, 16));
		return EXIT_SUCCESS;
	}

	//TODO: internalmbrtype <hexcode>
	//TODO: internalgpttype <GUID>

	//
	// Pages
	//

	if (oscmp(action, osstr("ver")) == 0) {
		puts(PROJECT_VERSION);
		return EXIT_SUCCESS;
	}
	if (oscmp(action, osstr("version")) == 0 || oscmp(action, osstr("--version")) == 0)
		version();
	//TODO: Help system in its own module
	if (oscmp(action, osstr("help")) == 0 || oscmp(action, osstr("--help")) == 0)
		help();
	if (oscmp(action, osstr("license")) == 0 || oscmp(action, osstr("--license")) == 0)
		license();
#ifdef DEBUG
	if (oscmp(action, osstr("--test")) == 0)
		test();
#endif

	fprintf(stderr, "main: '" OSCHARFMT "' unknown operation, see 'vvd help'\n", action);
	return EXIT_FAILURE;
}