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