xref: /haiku/src/tools/vmdkimage/vmdkimage.cpp (revision a30a4a41f948ebb03b95dab065a27a584ac0c97a)
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 
28 static void
29 print_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 
49 static void
50 dump_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 
114 static uint64_t
115 hash_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 
128 static bool
129 is_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 
148 int
149 main(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 
402 write_err:
403 	fprintf(stderr, "Error: writing file %s failed (%s)\n", file,
404 		strerror(errno));
405 	close(fd);
406 	exit(EXIT_FAILURE);
407 }
408