1/*
2 * Copyright 2007, Marcus Overhagen. All Rights Reserved.
3 * Distributed under the terms of the MIT License.
4 */
5
6
7#include <errno.h>
8#include <fcntl.h>
9#include <getopt.h>
10#include <limits.h>
11#include <stdarg.h>
12#include <stdio.h>
13#include <stdlib.h>
14#include <string.h>
15#include <sys/types.h>
16#include <sys/stat.h>
17#include <unistd.h>
18
19#include <vmdk.h>
20
21
22#if defined(__BEOS__) && !defined(__HAIKU__)
23#define pread(_fd, _buf, _count, _pos) read_pos(_fd, _pos, _buf, _count)
24#define realpath(x, y)	NULL
25#endif
26
27
28static void
29print_usage()
30{
31	printf("\n");
32	printf("vmdkimage\n");
33	printf("\n");
34	printf("usage: vmdkimage -i <imagesize> -h <headersize> [-c] [-H] "
35		"[-u <uuid>] [-f] <file>\n");
36	printf("   or: vmdkimage -d <file>\n");
37	printf("       -d, --dump         dumps info for the image file\n");
38	printf("       -i, --imagesize    size of raw partition image file\n");
39	printf("       -h, --headersize   size of the vmdk header to write\n");
40	printf("       -f, --file         the raw partition image file\n");
41	printf("       -u, --uuid         UUID for the image instead of a computed "
42		"one\n");
43	printf("       -c, --clear-image  set the image content to zero\n");
44	printf("       -H, --header-only  write only the header\n");
45	exit(EXIT_FAILURE);
46}
47
48
49static void
50dump_image_info(const char *filename)
51{
52	int image = open(filename, O_RDONLY);
53	if (image < 0) {
54		fprintf(stderr, "Error: couldn't open file %s (%s)\n", filename,
55			strerror(errno));
56		exit(EXIT_FAILURE);
57	}
58
59	SparseExtentHeader header;
60	if (read(image, &header, 512) != 512) {
61		fprintf(stderr, "Error: couldn't read header: %s\n", strerror(errno));
62		exit(EXIT_FAILURE);
63	}
64
65	if (header.magicNumber != VMDK_SPARSE_MAGICNUMBER) {
66		fprintf(stderr, "Error: invalid header magic.\n");
67		exit(EXIT_FAILURE);
68	}
69
70	printf("--------------- Header ---------------\n");
71	printf("  version:             %d\n", (int)header.version);
72	printf("  flags:               %d\n", (int)header.flags);
73	printf("  capacity:            %d\n", (int)header.capacity);
74	printf("  grainSize:           %lld\n", (long long)header.grainSize);
75	printf("  descriptorOffset:    %lld\n", (long long)header.descriptorOffset);
76	printf("  descriptorSize:      %lld\n", (long long)header.descriptorSize);
77	printf("  numGTEsPerGT:        %u\n", (unsigned int)header.numGTEsPerGT);
78	printf("  rgdOffset:           %lld\n", (long long)header.rgdOffset);
79	printf("  gdOffset:            %lld\n", (long long)header.gdOffset);
80	printf("  overHead:            %lld\n", (long long)header.overHead);
81	printf("  uncleanShutdown:     %s\n",
82		header.uncleanShutdown ? "yes" : "no");
83	printf("  singleEndLineChar:   '%c'\n", header.singleEndLineChar);
84	printf("  nonEndLineChar:      '%c'\n", header.nonEndLineChar);
85	printf("  doubleEndLineChar1:  '%c'\n", header.doubleEndLineChar1);
86	printf("  doubleEndLineChar2:  '%c'\n", header.doubleEndLineChar2);
87
88	if (header.descriptorOffset != 0) {
89		printf("\n--------------- Descriptor ---------------\n");
90		size_t descriptorSize = header.descriptorSize * 512 * 2;
91		char *descriptor = (char *)malloc(descriptorSize);
92		if (descriptor == NULL) {
93			fprintf(stderr, "Error: cannot allocate descriptor size %u.\n",
94				(unsigned int)descriptorSize);
95			exit(EXIT_FAILURE);
96		}
97
98		if (pread(image, descriptor, descriptorSize,
99				header.descriptorOffset * 512) != (ssize_t)descriptorSize) {
100			fprintf(stderr, "Error: couldn't read header: %s\n",
101				strerror(errno));
102			exit(EXIT_FAILURE);
103		}
104
105		puts(descriptor);
106		putchar('\n');
107		free(descriptor);
108	}
109
110	close(image);
111}
112
113
114static uint64_t
115hash_string(const char *string)
116{
117	uint64_t hash = 0;
118	char c;
119
120	while ((c = *string++) != 0) {
121		hash = c + (hash << 6) + (hash << 16) - hash;
122	}
123
124	return hash;
125}
126
127
128static bool
129is_valid_uuid(const char *uuid)
130{
131	const char *kHex = "0123456789abcdef";
132	for (int i = 0; i < 36; i++) {
133		if (!uuid[i])
134			return false;
135		if (i == 8 || i == 13 || i == 18 || i == 23) {
136			if (uuid[i] != '-')
137				return false;
138			continue;
139		}
140		if (strchr(kHex, uuid[i]) == NULL)
141			return false;
142	}
143
144	return uuid[36] == '\0';
145}
146
147
148int
149main(int argc, char *argv[])
150{
151	uint64_t headerSize = 0;
152	uint64_t imageSize = 0;
153	const char *file = NULL;
154	const char *uuid = NULL;
155	bool headerOnly = false;
156	bool clearImage = false;
157	bool dumpOnly = false;
158
159	if (sizeof(SparseExtentHeader) != 512) {
160		fprintf(stderr, "compilation error: struct size is %u byte\n",
161			(unsigned)sizeof(SparseExtentHeader));
162		exit(EXIT_FAILURE);
163	}
164
165	while (1) {
166		int c;
167		static struct option long_options[] = {
168			{"dump", no_argument, 0, 'd'},
169		 	{"headersize", required_argument, 0, 'h'},
170			{"imagesize", required_argument, 0, 'i'},
171			{"file", required_argument, 0, 'f'},
172			{"uuid", required_argument, 0, 'u'},
173			{"clear-image", no_argument, 0, 'c'},
174			{"header-only", no_argument, 0, 'H'},
175			{0, 0, 0, 0}
176		};
177
178		opterr = 0; /* don't print errors */
179		c = getopt_long(argc, argv, "dh:i:u:cHf:", long_options, NULL);
180		if (c == -1)
181			break;
182
183		switch (c) {
184			case 'd':
185				dumpOnly = true;
186				break;
187
188			case 'h':
189				headerSize = strtoull(optarg, NULL, 10);
190				if (strchr(optarg, 'G') || strchr(optarg, 'g'))
191					headerSize *= 1024 * 1024 * 1024;
192				else if (strchr(optarg, 'M') || strchr(optarg, 'm'))
193					headerSize *= 1024 * 1024;
194				else if (strchr(optarg, 'K') || strchr(optarg, 'k'))
195					headerSize *= 1024;
196				break;
197
198			case 'i':
199				imageSize = strtoull(optarg, NULL, 10);
200				if (strchr(optarg, 'G') || strchr(optarg, 'g'))
201					imageSize *= 1024 * 1024 * 1024;
202				else if (strchr(optarg, 'M') || strchr(optarg, 'm'))
203					imageSize *= 1024 * 1024;
204				else if (strchr(optarg, 'K') || strchr(optarg, 'k'))
205					imageSize *= 1024;
206				break;
207
208			case 'u':
209				uuid = optarg;
210				if (!is_valid_uuid(uuid)) {
211					fprintf(stderr, "Error: invalid UUID given (use "
212						"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx format only).\n");
213					exit(EXIT_FAILURE);
214				}
215				break;
216
217			case 'f':
218				file = optarg;
219				break;
220
221			case 'c':
222				clearImage = true;
223				break;
224
225			case 'H':
226				headerOnly = true;
227				break;
228
229			default:
230				print_usage();
231		}
232	}
233
234	if (file == NULL && optind == argc - 1)
235		file = argv[optind];
236
237	if (dumpOnly && file != NULL) {
238		dump_image_info(file);
239		return 0;
240	}
241
242	if (!headerSize || !imageSize || !file)
243		print_usage();
244
245	char desc[1024];
246	SparseExtentHeader header;
247
248	if (headerSize < sizeof(desc) + sizeof(header)) {
249		fprintf(stderr, "Error: header size must be at least %u byte\n",
250			(unsigned)(sizeof(desc) + sizeof(header)));
251		exit(EXIT_FAILURE);
252	}
253
254	if (headerSize % 512) {
255		fprintf(stderr, "Error: header size must be a multiple of 512 bytes\n");
256		exit(EXIT_FAILURE);
257	}
258
259	if (imageSize % 512) {
260		fprintf(stderr, "Error: image size must be a multiple of 512 bytes\n");
261		exit(EXIT_FAILURE);
262	}
263
264	// arbitrary 1 GB limitation
265	if (headerSize > 0x40000000ULL) {
266		fprintf(stderr, "Error: header size too large\n");
267		exit(EXIT_FAILURE);
268	}
269
270	// arbitrary 160 GB limitation
271	if (imageSize > 0x2800000000ULL) {
272		fprintf(stderr, "Error: image size too large\n");
273		exit(EXIT_FAILURE);
274	}
275
276	const char *name = strrchr(file, '/');
277	name = name ? (name + 1) : file;
278
279//	printf("headerSize %llu\n", headerSize);
280//	printf("imageSize %llu\n", imageSize);
281//	printf("file %s\n", file);
282
283	uint64_t sectors;
284	uint64_t heads;
285	uint64_t cylinders;
286
287	// TODO: fixme!
288	sectors = 63;
289	heads = 16;
290	cylinders = imageSize / (sectors * heads * 512);
291	while (cylinders > 1024) {
292		cylinders /= 2;
293		heads *= 2;
294	}
295	off_t actualImageSize = (off_t)cylinders * sectors * heads * 512;
296
297	memset(desc, 0, sizeof(desc));
298	memset(&header, 0, sizeof(header));
299
300	header.magicNumber = VMDK_SPARSE_MAGICNUMBER;
301	header.version = VMDK_SPARSE_VERSION;
302	header.flags = 1;
303	header.capacity = 0;
304	header.grainSize = 16;
305	header.descriptorOffset = 1;
306	header.descriptorSize = (sizeof(desc) + 511) / 512;
307	header.numGTEsPerGT = 512;
308	header.rgdOffset = 0;
309	header.gdOffset = 0;
310	header.overHead = headerSize / 512;
311	header.uncleanShutdown = 0;
312	header.singleEndLineChar = '\n';
313	header.nonEndLineChar = ' ';
314	header.doubleEndLineChar1 = '\r';
315	header.doubleEndLineChar2 = '\n';
316
317	// Generate UUID for the image by hashing its full path
318	uint64_t uuid1 = 0, uuid2 = 0, uuid3 = 0, uuid4 = 0, uuid5 = 0;
319	if (uuid == NULL) {
320		char fullPath[PATH_MAX + 6];
321		strcpy(fullPath, "Haiku");
322
323		if (realpath(file, fullPath + 5) == NULL)
324			strncpy(fullPath + 5, file, sizeof(fullPath) - 5);
325
326		size_t pathLength = strlen(fullPath);
327		for (size_t i = pathLength; i < 42; i++) {
328			// fill rest with some numbers
329			fullPath[i] = i % 10 + '0';
330		}
331		if (pathLength < 42)
332			fullPath[42] = '\0';
333
334		uuid1 = hash_string(fullPath);
335		uuid2 = hash_string(fullPath + 5);
336		uuid3 = hash_string(fullPath + 13);
337		uuid4 = hash_string(fullPath + 19);
338		uuid5 = hash_string(fullPath + 29);
339	}
340
341	// Create embedded descriptor
342	strcat(desc,
343		"# Disk Descriptor File\n"
344		"version=1\n"
345		"CID=fffffffe\n"
346		"parentCID=ffffffff\n"
347		"createType=\"monolithicFlat\"\n");
348	sprintf(desc + strlen(desc),
349		"# Extent Description\n"
350		"RW %llu FLAT \"%s\" %llu\n",
351		(unsigned long long)actualImageSize / 512, name,
352		(unsigned long long)headerSize / 512);
353	sprintf(desc + strlen(desc),
354		"# Disk Data Base\n"
355		"ddb.toolsVersion = \"0\"\n"
356		"ddb.virtualHWVersion = \"3\"\n"
357		"ddb.geometry.sectors = \"%llu\"\n"
358		"ddb.adapterType = \"ide\"\n"
359		"ddb.geometry.heads = \"%llu\"\n"
360		"ddb.geometry.cylinders = \"%llu\"\n",
361		(unsigned long long)sectors, (unsigned long long)heads,
362		(unsigned long long)cylinders);
363
364	if (uuid == NULL) {
365		sprintf(desc + strlen(desc),
366			"ddb.uuid.image=\"%08llx-%04llx-%04llx-%04llx-%012llx\"\n",
367			uuid1 & 0xffffffffLL, uuid2 & 0xffffLL, uuid3 & 0xffffLL,
368			uuid4 & 0xffffLL, uuid5 & 0xffffffffffffLL);
369	} else
370		sprintf(desc + strlen(desc), "ddb.uuid.image=\"%s\"\n", uuid);
371
372	int fd = open(file, O_RDWR | O_CREAT, 0666);
373	if (fd < 0) {
374		fprintf(stderr, "Error: couldn't open file %s (%s)\n", file,
375			strerror(errno));
376		exit(EXIT_FAILURE);
377	}
378	if (write(fd, &header, sizeof(header)) != sizeof(header))
379		goto write_err;
380
381	if (write(fd, desc, sizeof(desc)) != sizeof(desc))
382		goto write_err;
383
384	if ((uint64_t)lseek(fd, headerSize - 1, SEEK_SET) != headerSize - 1)
385		goto write_err;
386
387	if (1 != write(fd, "", 1))
388		goto write_err;
389
390	if (!headerOnly) {
391		if ((clearImage && ftruncate(fd, headerSize) != 0)
392			|| ftruncate(fd, actualImageSize + headerSize) != 0) {
393			fprintf(stderr, "Error: resizing file %s failed (%s)\n", file,
394				strerror(errno));
395			exit(EXIT_FAILURE);
396		}
397	}
398
399	close(fd);
400	return 0;
401
402write_err:
403	fprintf(stderr, "Error: writing file %s failed (%s)\n", file,
404		strerror(errno));
405	close(fd);
406	exit(EXIT_FAILURE);
407}
408