Newer
Older
vvd / src / vvd.c
/**
 * Main vvd operations
 */

#include <assert.h>
#include <string.h> // memcpy
#include <inttypes.h>
#include "vvd.h"
#include "utils.h"
#include "fs/mbr.h"
#include "fs/gpt.h"

//
// Global variables
//
// Since only one instance of any vvd_* is running at a time (seeing that
// they're directly invoked from main()), these variables are useful for
// callbacks to avoid over-implementing the vdisk back-end.
//

// CLI progress bar var
struct progress_t g_progress;
//
uint32_t g_flags;

//
// vvd_cb_progress
//

void vvd_cb_progress(uint32_t type, void *data) {
	switch (type) {
	case VVD_NOTIF_DONE:
		if (g_flags & VVD_PROGRESS)
		if (os_pfinish(&g_progress)) {
			fputs("os_pfinish: Could not finish progress bar", stderr);
			exit(1);
		}
		return;
	case VVD_NOTIF_VDISK_CREATED_TYPE_NAME:
		printf("%s\n", data);
		return;
	case VVD_NOTIF_VDISK_TOTAL_BLOCKS:
	case VVD_NOTIF_VDISK_TOTAL_BLOCKS64:
		if (g_flags & VVD_PROGRESS)
		if (os_pinit(&g_progress, PROG_MODE_POURCENT, 0)) {
			fputs("os_pinit: Could not init progress bar\n", stderr);
			exit(1);
		}
		return;
	case VVD_NOTIF_VDISK_CURRENT_BLOCK:
	case VVD_NOTIF_VDISK_CURRENT_BLOCK64:
		if (g_flags & VVD_PROGRESS)
		if (os_pupdate(&g_progress, 0)) {
			fputs("os_pinit: Could not update progress bar\n", stderr);
			exit(1);
		}
		return;
	}
}

//
// vvd_info_mbr
//

void vvd_info_mbr(MBR *mbr, uint32_t flags) {
	char strsize[BINSTR_LENGTH];
	uint64_t totalsize = SECTOR_TO_BYTE(
		(uint64_t)mbr->pe[0].sectors + (uint64_t)mbr->pe[1].sectors +
		(uint64_t)mbr->pe[2].sectors + (uint64_t)mbr->pe[3].sectors
	);

	bintostr(strsize, totalsize);

	if (flags & VVD_INFO_RAW) {
		printf(
		"\n"
		"disklabel          : MBR\n"
		"serial             : 0x%08X\n"
		"type               : 0x%04X\n",
		mbr->serial, mbr->type
		);
		for (unsigned int i = 0; i < 4; ++i) {
			MBR_PARTITION pe = mbr->pe[i];
			printf(
			"\n"
			"partition          : %u\n"
			"status             : 0x%02X\n"
			"type               : 0x%02X\n"
			"lba                : %u\n"
			"length             : %u sectors\n"
			"chs start          : %u/%u/%u\n"
			"chs end            : %u/%u/%u\n",
			i,
			pe.status,
			pe.type,
			pe.lba,
			pe.sectors,
			// CHS start
			pe.chsfirst.cylinder | ((pe.chsfirst.sector & 0xC0) << 2),
			pe.chsfirst.head, pe.chsfirst.sector & 0x3F,
			// CHS end
			pe.chslast.cylinder | ((pe.chslast.sector & 0xC0) << 2),
			pe.chslast.head, pe.chslast.sector & 0x3F
			);
		}
	} else {
		printf(
		"\n"
		"MBR (DOS) disklabel, %s used\n"
		"   Boot     Start        Size  Type\n", strsize
		);
		for (unsigned int i = 0; i < 4; ++i) {
			MBR_PARTITION pe = mbr->pe[i];
			bintostr(strsize, SECTOR_TO_BYTE(pe.sectors));
			printf(
			"%u. %c  %11u  %10s  %s\n",
			i,
			pe.status >= 0x80 ? '*' : ' ',
			pe.lba,
			strsize,
			mbr_part_type_str(pe.type)
			);
		}
	}
}

//
// vvd_info_gpt
//

void vvd_info_gpt(GPT *gpt, uint32_t flags) {
	char gptsize[BINSTR_LENGTH];
	UID_TEXT diskguid;

	uid_str(diskguid, &gpt->guid, UID_GUID);

	if (flags & VVD_INFO_RAW) {
		printf(
		"\n"
		"disklabel          : GPT\n"
		"version            : %u.%u\n"
		"header size        : %u\n"
		"header crc32       : 0x%08X\n"
		"part. table crc32  : 0x%08X\n"
		"header lba         : %" PRIu64 "\n"
		"backup lba         : %" PRIu64 "\n"
		"lba start          : %" PRIu64 "\n"
		"lba end            : %" PRIu64 "\n"
		"disk guid          : %s\n"
		"part. table lba    : %" PRIu64 "\n"
		"maximum entries    : %u\n"
		"entry size         : %u\n",
		gpt->majorver, gpt->minorver,
		gpt->headersize, gpt->headercrc32,
		gpt->pt_crc32,
		gpt->current.lba, gpt->backup.lba,
		gpt->first.lba, gpt->last.lba,
		diskguid,
		gpt->pt_location.lba, gpt->pt_entries, gpt->pt_esize
		);
	} else {
		bintostr(gptsize, SECTOR_TO_BYTE(gpt->last.lba - gpt->first.lba));

		printf(
		"\n"
		"GPT extended disklabel v%u.%u, %s used\n"
		"Disk GUID: %s\n",
		gpt->majorver, gpt->minorver, gptsize,
		diskguid
		);
	}
}

//
// vvd_info_gpt_entries
//

void vvd_info_gpt_entries(VDISK *vd, GPT *gpt, uint64_t lba, uint32_t flags) {
	int max = gpt->pt_entries;	// maximum limiter, typically 128
	char partname[EFI_PART_NAME_LENGTH];
	char partsize[BINSTR_LENGTH];
	UID_TEXT partguid, typeguid;
	GPT_ENTRY entry; // GPT entry
	uint32_t entrynum = 1;

	if ((flags & VVD_INFO_RAW) == 0)
		puts("Part         Start        Size  Type");

START:
	if (vdisk_read_sector(vd, &entry, lba)) {
		fputs("vvd_info_gpt_entries: Could not read GPT_ENTRY", stderr);
		return;
	}

	if (uid_nil(&entry.type))
		return;

	uid_str(typeguid, &entry.type, UID_GUID);
	uid_str(partguid, &entry.part, UID_GUID);
	int wr = wstra(partname, entry.partname, EFI_PART_NAME_LENGTH);

	if (flags & VVD_INFO_RAW) {
		printf(
		"\n"
		"partition          : %u\n"
		"name               : %-36s\n"
		"part guid          : %s\n"
		"type guid          : %s\n"
		"lba start          : %" PRIu64 "\n"
		"lba end            : %" PRIu64 "\n"
		"flags              : 0x%08X\n"
		"partition flags    : 0x%08X\n",
		entrynum,
		partname,
		partguid,
		typeguid,
		entry.first.lba,
		entry.last.lba,
		entry.flags,
		entry.partflags
		);
	} else {
		bintostr(partsize, SECTOR_TO_BYTE(entry.last.lba - entry.first.lba));
		//TODO: GPT partition type (after name)
		printf(
		"%4u. %12" PRIu64 "%12s  s\n",
		entrynum, entry.first.lba, partsize
		);

		if (wr > 0)
			printf("      Name: %-36s", partname);

		// GPT flags
		if (entry.flags & EFI_PE_PLATFORM_REQUIRED)
			puts("      + Platform required");
		if (entry.flags & EFI_PE_EFI_FIRMWARE_IGNORE)
			puts("      + Firmware ignore");
		if (entry.flags & EFI_PE_LEGACY_BIOS_BOOTABLE)
			puts("      + Legacy BIOS bootable");

		// Partition flags
		if (entry.partflags & EFI_PE_SUCCESSFUL_BOOT)
			puts("      + (Google) Successful boot");
		if (entry.partflags & EFI_PE_READ_ONLY)
			puts("      + (Microsoft) Read-only");
		if (entry.partflags & EFI_PE_SHADOW_COPY)
			puts("      + (Microsoft) Shadow copy");
		if (entry.partflags & EFI_PE_HIDDEN)
			puts("      + (Microsoft) Hidden");
	}

	if (entrynum > gpt->pt_entries)
		return;

	++lba; --max; ++entrynum;
	goto START;
}

//
// vvd_info
//

int vvd_info(VDISK *vd, uint32_t flags) {
	const char *type;	// vdisk type
	char disksize[BINSTR_LENGTH], blocksize[BINSTR_LENGTH];
	char uid1[UID_LENGTH], uid2[UID_LENGTH], uid3[UID_LENGTH], uid4[UID_LENGTH];

	switch (vd->format) {
	//
	// VDI
	//
	case VDISK_FORMAT_VDI: {
		switch (vd->vdi->v1.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?";
		}

		bintostr(disksize, vd->vdi->v1.capacity);

		if (flags & VVD_INFO_RAW) {
			char create_uuid[UID_LENGTH], modify_uuid[UID_LENGTH],
				link_uuid[UID_LENGTH], parent_uuid[UID_LENGTH];

			bintostr(blocksize, vd->vdi->v1.blk_size);
			uid_str(uid1, &vd->vdi->v1.uuidCreate, UID_UUID);
			uid_str(uid2, &vd->vdi->v1.uuidModify, UID_UUID);
			uid_str(uid3, &vd->vdi->v1.uuidLinkage, UID_UUID);
			uid_str(uid4, &vd->vdi->v1.uuidParentModify, UID_UUID);

			printf(
			"disk format        : VDI\n"
			"version            : %u.%u\n"
			"type               : %s\n"
			"capacity           : %s (%"PRIu64")\n"
			"header size        : %u\n"
			"flags              : 0x%08X\n"
			"dummy              : 0x%08X\n"
			"block size         : %s (%u)\n"
			"blocks total       : %u\n"
			"blocks alloc       : %u\n"
			"blocks extra       : %u\n"
			"offset table       : %u\n"
			"offset data        : %u\n"
			"chs                : %u/%u/%u\n"
			"chs legacy         : %u/%u/%u\n"
			"sector size        : %u\n"
			"sector size legacy : %u\n"
			"create uuid        : %s\n"
			"modify uuid        : %s\n"
			"link uuid          : %s\n"
			"parent uuid        : %s\n",
			vd->vdi->hdr.majorver, vd->vdi->hdr.minorver,
			type,
			disksize, vd->vdi->v1.capacity,
			vd->vdi->v1.hdrsize,
			vd->vdi->v1.fFlags,
			vd->vdi->v1.u32Dummy,
			blocksize, vd->vdi->v1.blk_size,
			vd->vdi->v1.blk_total,
			vd->vdi->v1.blk_alloc,
			vd->vdi->v1.blk_extra,
			vd->vdi->v1.offBlocks,
			vd->vdi->v1.offData,
			vd->vdi->v1.cCylinders,
			vd->vdi->v1.cHeads,
			vd->vdi->v1.cSectors,
			vd->vdi->v1.LegacyGeometry.cCylinders,
			vd->vdi->v1.LegacyGeometry.cHeads,
			vd->vdi->v1.LegacyGeometry.cSectors,
			vd->vdi->v1.cbSector,
			vd->vdi->v1.LegacyGeometry.cbSector,
			uid1, uid2, uid3, uid4
			);
		} else {
			printf(
			"VirtualBox VDI %s disk v%u.%u, %s\n",
			type, vd->vdi->hdr.majorver, vd->vdi->hdr.minorver, disksize
			);
			//TODO: Interpret flags
		}
	}
		break;
	//
	// VMDK
	//
	case VDISK_FORMAT_VMDK: {
		const char *comp; // compression
		//if (h.flags & COMPRESSED)
		switch (vd->vmdk->hdr.compressAlgorithm) {
		case 0:  comp = "no"; break;
		case 1:  comp = "DEFLATE"; break;
		default: comp = "?";
		}

		if (flags & VVD_INFO_RAW) {
			printf(
			"disk format        : VMDK\n"
			"version            : %u\n"
			"flags              : 0x%08X\n"
			"capacity           : %" PRIu64 " sectors\n"
			"overhead           : %" PRIu64 " sectors\n"
			"grain size         : %" PRIu64 " sectors\n"
			"descriptor offset  : %" PRIu64 " sectors\n"
			"descriptor size    : %" PRIu64 " sectors\n"
			"table entries      : %u\n"
			"backup l0 offset   : %" PRIu64 " sectors\n"
			"l0 offset          : %" PRIu64 " sectors\n"
			"overhead           : %" PRIu64 " sectors\n"
			"unclean shutdown   : %u\n"
			"single nl char     : 0x%02X\n"
			"non-end line char  : 0x%02X\n"
			"double nl char 1   : 0x%02X\n"
			"double nl char 2   : 0x%02X\n"
			"compression        : 0x%02X\n",
			vd->vmdk->hdr.version,
			vd->vmdk->hdr.flags,
			vd->vmdk->hdr.capacity,
			vd->vmdk->hdr.overHead,
			vd->vmdk->hdr.grainSize,
			vd->vmdk->hdr.descriptorOffset,
			vd->vmdk->hdr.descriptorSize,
			vd->vmdk->hdr.numGTEsPerGT,
			vd->vmdk->hdr.rgdOffset,
			vd->vmdk->hdr.gdOffset,
			vd->vmdk->hdr.overHead,
			vd->vmdk->hdr.uncleanShutdown,
			vd->vmdk->hdr.singleEndLineChar,
			vd->vmdk->hdr.nonEndLineChar,
			vd->vmdk->hdr.doubleEndLineChar1,
			vd->vmdk->hdr.doubleEndLineChar2,
			vd->vmdk->hdr.compressAlgorithm
			);
		} else {
			bintostr(disksize, vd->capacity);
			printf(
			"VMware VMDK disk v%u, %s compression, %s\n",
			vd->vmdk->hdr.version, comp, disksize
			);

			if (vd->vmdk->hdr.flags & VMDK_F_VALID_NL)
				puts("+ Valid newline detection");
			if (vd->vmdk->hdr.flags & VMDK_F_REDUNDANT_TABLE)
				puts("+ Redundant grain table used");
			if (vd->vmdk->hdr.flags & VMDK_F_ZEROED_GTE)
				puts("+ Zeroed-grain GTE used");
			if (vd->vmdk->hdr.flags & VDMK_F_COMPRESSED)
				puts("+ Grains are compressed");
			if (vd->vmdk->hdr.flags & VMDK_F_MARKERS)
				puts("+ Markers used");
			if (vd->vmdk->hdr.uncleanShutdown)
				puts("+ Unclean shutdown");
		}
	}
		break;
	//
	// VHD
	//
	case VDISK_FORMAT_VHD: {
		char sizecur[BINSTR_LENGTH];
		const char *byos;

		switch (vd->vhd->hdr.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->hdr.type <= 6 ? "reserved (deprecated)" : "unknown";
		}

		switch (vd->vhd->hdr.creator_os) {
		case VHD_OS_WIN:	byos = "Windows"; break;
		case VHD_OS_MAC:	byos = "macOS"; break;
		default:	byos = "unknown"; break;
		}

		uid_str(uid1, &vd->vhd->hdr.uuid, UID_ASIS);
		str_s(vd->vhd->hdr.creator_app, 4);

		if (flags & VVD_INFO_RAW) {
			printf(
			"disk format        : VHD\n"
			"version            : %u.%u\n"
			"type               : %s\n"
			"current size       : %" PRIu64 "\n"
			"capacity           : %" PRIu64 "\n"
			"created by         : %.4s\n"
			"created by ver.    : %u.%u\n"
			"created on os      : %s\n"
			"chs                : %u/%u/%u\n"
			"checksum           : 0x%08X\n"
			"uuid               : %s\n",
			vd->vhd->hdr.major, vd->vhd->hdr.minor,
			type,
			vd->vhd->hdr.size_current,
			vd->vhd->hdr.size_original,
			vd->vhd->hdr.creator_app,
			vd->vhd->hdr.creator_major, vd->vhd->hdr.creator_minor,
			byos,
			vd->vhd->hdr.cylinders, vd->vhd->hdr.heads, vd->vhd->hdr.sectors,
			vd->vhd->hdr.checksum,
			uid1
			);
			if (vd->vhd->hdr.type != VHD_DISK_FIXED) {
				char paruuid[UID_LENGTH];
				uid_str(paruuid, &vd->vhd->dyn.parent_uuid, UID_ASIS);
				printf(
				"dyn. header ver.   : %u.%u\n"
				"offset table       : %" PRIu64 "\n"
				"offset data        : %" PRIu64 "\n"
				"block size         : %u\n"
				"dyn. checksum      : 0x%08X\n"
				"parent uuid        : %s\n"
				"parent timestamp   : %u\n"
				"bat entries        : %u\n",
				vd->vhd->dyn.minor, vd->vhd->dyn.major,
				vd->vhd->dyn.table_offset,
				vd->vhd->dyn.data_offset,
				vd->vhd->dyn.blocksize,
				vd->vhd->dyn.checksum,
				paruuid,
				vd->vhd->dyn.parent_timestamp,
				vd->vhd->dyn.max_entries
				);
			}
		} else {
			bintostr(sizecur, vd->vhd->hdr.size_current);
			bintostr(disksize, vd->vhd->hdr.size_original);

			printf(
			"Connectix/Microsoft VHD %s disk v%u.%u, %s/%s\n",
			type, vd->vhd->hdr.major, vd->vhd->hdr.minor, sizecur, disksize
			);
		}

		if (vd->vhd->hdr.savedState)
			puts("+ Saved state");
	}
		break;
//	case VDISK_FORMAT_VHDX:
	case VDISK_FORMAT_QED:
		if (flags & VVD_INFO_RAW) {
			printf(
			"disk format        : QED\n"
			"cluster size       : %u\n"
			"table size         : %u\n"
			"header size        : %u\n"
			"features           : 0x%" PRIX64 "\n"
			"compact features   : 0x%" PRIX64 "\n"
			"autoclear features : 0x%" PRIX64 "\n"
			"L1 offset          : 0x%" PRIX64 "\n",
			vd->qed->hdr.cluster_size,
			vd->qed->hdr.table_size,
			vd->qed->hdr.header_size,
			vd->qed->hdr.features,
			vd->qed->hdr.compat_features,
			vd->qed->hdr.autoclear_features,
			vd->qed->hdr.l1_offset
			);
			if (vd->qed->hdr.features & QED_F_BACKING_FILE) {
				printf(
				"back. name offset  : %u\n"
				"back. name size    : %u\n",
				vd->qed->hdr.backup_name_offset,
				vd->qed->hdr.backup_name_size
				);
			}
		} else {
			bintostr(disksize, vd->capacity);
			printf("QEMU Enhanced Disk, %s\n", disksize);
		}
		break;
	case VDISK_FORMAT_RAW: break; // No header info
	default:
		fputs("vvd_info: Format not supported\n", stderr);
		return VVD_EVDFORMAT;
	}

	//TODO: BSD disklabel detection
	//TODO: SGI disklabel detection

	//
	// MBR detection
	//

	MBR mbr;
	if (vdisk_read_sector(vd, &mbr, 0)) return EXIT_SUCCESS;
	if (mbr.sig != MBR_SIG) return EXIT_SUCCESS;
	vvd_info_mbr(&mbr, flags);

	//
	// Extended MBR detection (EBR)
	//

	uint64_t ebrlba;
	for (int i = 0; i < 4; ++i) {
		switch (mbr.pe[i].type) {
		case 0xEE: // EFI GPT Protective
		case 0xEF: // EFI System Partition
			// Start of disk
			if (vdisk_read_sector(vd, &mbr, 1)) return VVD_EOK;
			if (((GPT*)&mbr)->sig == EFI_SIG) {
				ebrlba = 2;
				goto L_GPT_RDY;
			}
			// End of disk
			ebrlba = BYTE_TO_SECTOR(vd->capacity) - 1;
			if (vdisk_read_sector(vd, &mbr, ebrlba)) return VVD_EOK;
			if (((GPT*)&mbr)->sig == EFI_SIG) {
				ebrlba -= ((GPT*)&mbr)->pt_entries; // typically 128
				goto L_GPT_RDY;
			}
			continue;
		L_GPT_RDY:
			vvd_info_gpt((GPT*)&mbr, flags);
			vvd_info_gpt_entries(vd, (GPT*)&mbr, ebrlba, flags);
			continue;
		}
	}

	return EXIT_SUCCESS;
}

//
// vvd_map
//

int vvd_map(VDISK *vd, uint32_t flags) {
	char bsizestr[BINSTR_LENGTH]; // If used

	puts("vvd_map: to be implemented");

	/*switch (vd->format) {
	case VDISK_FORMAT_VDI:
		bcount = vd->vdiv1.blk_total;
		bsize = vd->vdiv1.blocksize;
		break;
	case VDISK_FORMAT_VHD:
		if (vd->vhdhdr.type != VHD_DISK_DYN) {
			fputs("vvd_map: vdisk is not dynamic\n", stderr);
			return VVD_EVDTYPE;
		}
		bcount = vd->u32blockcount;
		bsize = vd->vhddyn.blocksize;
		break;
	case VDISK_FORMAT_QED:
		index64 = 1;
		bcount = vd->u32blockcount;
		bsize = vd->qedhdr.cluster_size;
		break;
	default:
		fputs("vvd_map: unsupported format\n", stderr);
		return VVD_EVDFORMAT;
	}

	bintostr(bsizestr, bsize);
	size_t i = 0;
	size_t bn;
	if (index64) {
		printf(
		"Allocation map: %u blocks to %s blocks\n"
		" offset d |                0 |                1 |"
			"                2 |                3 |\n"
		"----------+------------------+------------------+"
			"------------------+------------------+\n",
		bcount, bsizestr
		);
		bn = bcount - 4;
		for (; i < bn; i += 4) {
			printf(
			" %8zu | %16"PRIX64" | %16"PRIX64" | %16"PRIX64" | %16"PRIX64" |\n",
			i,
			vd->u64block[i],     vd->u64block[i + 1],
			vd->u64block[i + 2], vd->u64block[i + 3]
			);
		}
		if (bcount - i > 0) { // Left over
			printf(" %8zu |", i);
			for (; i < bcount; ++i)
				printf(" %16"PRIX64" |", vd->u64block[i]);
			putchar('\n');
		}
	} else {
		printf(
		"Allocation map: %u blocks of %s each\n"
		" offset d |        0 |        1 |        2 |        3 |"
			"        4 |        5 |        6 |        7 |\n"
		"----------+----------+----------+----------+----------+"
			"----------+----------+----------+----------+\n",
		bcount, bsizestr
		);
		bn = bcount - 8;
		for (; i < bn; i += 8) {
			printf(
			" %8zu | %8X | %8X | %8X | %8X | %8X | %8X | %8X | %8X |\n",
			i,
			vd->u32block[i],     vd->u32block[i + 1],
			vd->u32block[i + 2], vd->u32block[i + 3],
			vd->u32block[i + 4], vd->u32block[i + 5],
			vd->u32block[i + 6], vd->u32block[i + 7]
			);
		}
		if (bcount - i > 0) { // Left over
			printf(" %8zu |", i);
			for (; i < bcount; ++i)
				printf(" %8X |", vd->u32block[i]);
			putchar('\n');
		}
	}*/

	return EXIT_SUCCESS;
}

//
// vvd_new
//

int vvd_new(const oschar *path, uint32_t format, uint64_t capacity, uint32_t flags) {
	VDISK vd;
	if (vdisk_create(&vd, path, format, capacity, flags)) {
		vdisk_perror(&vd);
		return vd.err.num;
	}
	printf("vvd_new: %s disk created successfully\n", vdisk_str(&vd));
	return EXIT_SUCCESS;
}

//
// vvd_compact
//

int vvd_compact(VDISK *vd, uint32_t flags) {
	puts("vvd_compact: [warning] This function is still work in progress");
	g_flags = flags;
	if (vdisk_op_compact(vd, vvd_cb_progress)) {
		vdisk_perror(vd);
		return vd->err.num;
	}
	return EXIT_SUCCESS;
}