1 /*-
2 * Copyright (c) 2014, 2015 Marcel Moolenaar
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24 * SUCH DAMAGE.
25 */
26
27 #include <sys/cdefs.h>
28 #include <sys/errno.h>
29 #include <stdlib.h>
30 #include <string.h>
31 #include <time.h>
32
33 #include "endian.h"
34 #include "image.h"
35 #include "format.h"
36 #include "mkimg.h"
37
38 #ifndef __has_extension
39 #define __has_extension(x) 0
40 #endif
41
42 /*
43 * General notes:
44 * o File is in network byte order.
45 * o The timestamp is seconds since 1/1/2000 12:00:00 AM UTC
46 *
47 * This file is divided in 3 parts:
48 * 1. Common definitions
49 * 2. Dynamic VHD support
50 * 3. Fixed VHD support
51 */
52
53 /*
54 * PART 1: Common definitions
55 */
56
57 #define VHD_SECTOR_SIZE 512
58 #define VHD_BLOCK_SIZE (4096 * VHD_SECTOR_SIZE) /* 2MB blocks */
59
60 struct vhd_geom {
61 uint16_t cylinders;
62 uint8_t heads;
63 uint8_t sectors;
64 };
65
66 struct vhd_footer {
67 uint64_t cookie;
68 #define VHD_FOOTER_COOKIE 0x636f6e6563746978ULL
69 uint32_t features;
70 #define VHD_FEATURES_TEMPORARY 0x01
71 #define VHD_FEATURES_RESERVED 0x02
72 uint32_t version;
73 #define VHD_VERSION 0x00010000
74 uint64_t data_offset;
75 uint32_t timestamp;
76 uint32_t creator_tool;
77 #define VHD_CREATOR_TOOL 0x2a696d67 /* FreeBSD mkimg */
78 uint32_t creator_version;
79 #define VHD_CREATOR_VERSION 0x00020000
80 uint32_t creator_os;
81 #define VHD_CREATOR_OS 0x5769326b /* Wi2k */
82 uint64_t original_size;
83 uint64_t current_size;
84 struct vhd_geom geometry;
85 uint32_t disk_type;
86 #define VHD_DISK_TYPE_FIXED 2
87 #define VHD_DISK_TYPE_DYNAMIC 3
88 #define VHD_DISK_TYPE_DIFF 4
89 uint32_t checksum;
90 mkimg_uuid_t id;
91 uint8_t saved_state;
92 uint8_t _reserved[427];
93 };
94 #if __has_extension(c_static_assert)
95 _Static_assert(sizeof(struct vhd_footer) == VHD_SECTOR_SIZE,
96 "Wrong size for footer");
97 #endif
98
99 static uint32_t
vhd_checksum(void * buf,size_t sz)100 vhd_checksum(void *buf, size_t sz)
101 {
102 uint8_t *p = buf;
103 uint32_t sum;
104 size_t ofs;
105
106 sum = 0;
107 for (ofs = 0; ofs < sz; ofs++)
108 sum += p[ofs];
109 return (~sum);
110 }
111
112 static void
vhd_geometry(uint64_t image_size,struct vhd_geom * geom)113 vhd_geometry(uint64_t image_size, struct vhd_geom *geom)
114 {
115 lba_t imgsz;
116 long cth;
117
118 imgsz = image_size / VHD_SECTOR_SIZE;
119
120 /* Respect command line options if possible. */
121 if (nheads > 1 && nheads < 256 &&
122 nsecs > 1 && nsecs < 256 &&
123 ncyls < 65536) {
124 geom->cylinders = (ncyls != 0) ? ncyls :
125 imgsz / (nheads * nsecs);
126 geom->heads = nheads;
127 geom->sectors = nsecs;
128 return;
129 }
130
131 if (imgsz > 65536 * 16 * 255)
132 imgsz = 65536 * 16 * 255;
133 if (imgsz >= 65535 * 16 * 63) {
134 geom->cylinders = imgsz / (16 * 255);
135 geom->heads = 16;
136 geom->sectors = 255;
137 return;
138 }
139 geom->sectors = 17;
140 cth = imgsz / 17;
141 geom->heads = (cth + 1023) / 1024;
142 if (geom->heads < 4)
143 geom->heads = 4;
144 if (cth >= (geom->heads * 1024) || geom->heads > 16) {
145 geom->heads = 16;
146 geom->sectors = 31;
147 cth = imgsz / 31;
148 }
149 if (cth >= (geom->heads * 1024)) {
150 geom->heads = 16;
151 geom->sectors = 63;
152 cth = imgsz / 63;
153 }
154 geom->cylinders = cth / geom->heads;
155 }
156
157 static uint64_t
vhd_resize(uint64_t origsz)158 vhd_resize(uint64_t origsz)
159 {
160 struct vhd_geom geom;
161 uint64_t newsz;
162
163 /*
164 * Round the image size to the pre-determined geometry that
165 * matches the image size. This circular dependency implies
166 * that we need to loop to handle boundary conditions.
167 * The first time, newsz equals origsz and the geometry will
168 * typically yield a new size that's smaller. We keep adding
169 * cylinder's worth of sectors to the new size until its
170 * larger or equal or origsz. But during those iterations,
171 * the geometry can change, so we need to account for that.
172 */
173 newsz = origsz;
174 while (1) {
175 vhd_geometry(newsz, &geom);
176 newsz = (int64_t)geom.cylinders * geom.heads *
177 geom.sectors * VHD_SECTOR_SIZE;
178 if (newsz >= origsz)
179 break;
180 newsz += geom.heads * geom.sectors * VHD_SECTOR_SIZE;
181 }
182 return (newsz);
183 }
184
185 static uint32_t
vhd_timestamp(void)186 vhd_timestamp(void)
187 {
188 time_t t;
189
190 if (!unit_testing) {
191 t = time(NULL);
192 return (t - 0x386d4380);
193 }
194
195 return (0x01234567);
196 }
197
198 static void
vhd_make_footer(struct vhd_footer * footer,uint64_t image_size,uint32_t disk_type,uint64_t data_offset)199 vhd_make_footer(struct vhd_footer *footer, uint64_t image_size,
200 uint32_t disk_type, uint64_t data_offset)
201 {
202 mkimg_uuid_t id;
203
204 memset(footer, 0, sizeof(*footer));
205 be64enc(&footer->cookie, VHD_FOOTER_COOKIE);
206 be32enc(&footer->features, VHD_FEATURES_RESERVED);
207 be32enc(&footer->version, VHD_VERSION);
208 be64enc(&footer->data_offset, data_offset);
209 be32enc(&footer->timestamp, vhd_timestamp());
210 be32enc(&footer->creator_tool, VHD_CREATOR_TOOL);
211 be32enc(&footer->creator_version, VHD_CREATOR_VERSION);
212 be32enc(&footer->creator_os, VHD_CREATOR_OS);
213 be64enc(&footer->original_size, image_size);
214 be64enc(&footer->current_size, image_size);
215 vhd_geometry(image_size, &footer->geometry);
216 be16enc(&footer->geometry.cylinders, footer->geometry.cylinders);
217 be32enc(&footer->disk_type, disk_type);
218 mkimg_uuid(&id);
219 mkimg_uuid_enc(&footer->id, &id);
220 be32enc(&footer->checksum, vhd_checksum(footer, sizeof(*footer)));
221 }
222
223 /*
224 * PART 2: Dynamic VHD support
225 *
226 * Notes:
227 * o File layout:
228 * copy of disk footer
229 * dynamic disk header
230 * block allocation table (BAT)
231 * data blocks
232 * disk footer
233 */
234
235 struct vhd_dyn_header {
236 uint64_t cookie;
237 #define VHD_HEADER_COOKIE 0x6378737061727365ULL
238 uint64_t data_offset;
239 uint64_t table_offset;
240 uint32_t version;
241 uint32_t max_entries;
242 uint32_t block_size;
243 uint32_t checksum;
244 mkimg_uuid_t parent_id;
245 uint32_t parent_timestamp;
246 char _reserved1[4];
247 uint16_t parent_name[256]; /* UTF-16 */
248 struct {
249 uint32_t code;
250 uint32_t data_space;
251 uint32_t data_length;
252 uint32_t _reserved;
253 uint64_t data_offset;
254 } parent_locator[8];
255 char _reserved2[256];
256 };
257 #if __has_extension(c_static_assert)
258 _Static_assert(sizeof(struct vhd_dyn_header) == VHD_SECTOR_SIZE * 2,
259 "Wrong size for header");
260 #endif
261
262 static int
vhd_dyn_resize(lba_t imgsz)263 vhd_dyn_resize(lba_t imgsz)
264 {
265 uint64_t imagesz;
266
267 imagesz = vhd_resize(imgsz * secsz);
268 return (image_set_size(imagesz / secsz));
269 }
270
271 static int
vhd_dyn_write(int fd)272 vhd_dyn_write(int fd)
273 {
274 struct vhd_footer footer;
275 struct vhd_dyn_header header;
276 uint64_t imgsz, rawsz;
277 lba_t blk, blkcnt, nblks;
278 uint32_t *bat;
279 void *bitmap;
280 size_t batsz;
281 uint32_t sector;
282 int bat_entries, error, entry;
283
284 rawsz = image_get_size() * secsz;
285 imgsz = (rawsz + VHD_BLOCK_SIZE - 1) & ~(VHD_BLOCK_SIZE - 1);
286
287 vhd_make_footer(&footer, rawsz, VHD_DISK_TYPE_DYNAMIC, sizeof(footer));
288 if (sparse_write(fd, &footer, sizeof(footer)) < 0)
289 return (errno);
290
291 bat_entries = imgsz / VHD_BLOCK_SIZE;
292 memset(&header, 0, sizeof(header));
293 be64enc(&header.cookie, VHD_HEADER_COOKIE);
294 be64enc(&header.data_offset, ~0ULL);
295 be64enc(&header.table_offset, sizeof(footer) + sizeof(header));
296 be32enc(&header.version, VHD_VERSION);
297 be32enc(&header.max_entries, bat_entries);
298 be32enc(&header.block_size, VHD_BLOCK_SIZE);
299 be32enc(&header.checksum, vhd_checksum(&header, sizeof(header)));
300 if (sparse_write(fd, &header, sizeof(header)) < 0)
301 return (errno);
302
303 batsz = bat_entries * sizeof(uint32_t);
304 batsz = (batsz + VHD_SECTOR_SIZE - 1) & ~(VHD_SECTOR_SIZE - 1);
305 bat = malloc(batsz);
306 if (bat == NULL)
307 return (errno);
308 memset(bat, 0xff, batsz);
309 blkcnt = VHD_BLOCK_SIZE / secsz;
310 sector = (sizeof(footer) + sizeof(header) + batsz) / VHD_SECTOR_SIZE;
311 for (entry = 0; entry < bat_entries; entry++) {
312 blk = entry * blkcnt;
313 if (image_data(blk, blkcnt)) {
314 be32enc(&bat[entry], sector);
315 sector += (VHD_BLOCK_SIZE / VHD_SECTOR_SIZE) + 1;
316 }
317 }
318 if (sparse_write(fd, bat, batsz) < 0) {
319 free(bat);
320 return (errno);
321 }
322 free(bat);
323
324 bitmap = malloc(VHD_SECTOR_SIZE);
325 if (bitmap == NULL)
326 return (errno);
327 memset(bitmap, 0xff, VHD_SECTOR_SIZE);
328
329 blk = 0;
330 blkcnt = VHD_BLOCK_SIZE / secsz;
331 error = 0;
332 nblks = rawsz / secsz;
333 while (blk < nblks) {
334 if (!image_data(blk, blkcnt)) {
335 blk += blkcnt;
336 continue;
337 }
338 if (sparse_write(fd, bitmap, VHD_SECTOR_SIZE) < 0) {
339 error = errno;
340 break;
341 }
342 /* Handle partial last block */
343 if (blk + blkcnt > nblks)
344 blkcnt = nblks - blk;
345 error = image_copyout_region(fd, blk, blkcnt);
346 if (error)
347 break;
348 blk += blkcnt;
349 }
350 free(bitmap);
351 if (error)
352 return (error);
353 error = image_copyout_zeroes(fd, imgsz - rawsz);
354 if (error)
355 return (error);
356 if (sparse_write(fd, &footer, sizeof(footer)) < 0)
357 return (errno);
358
359 return (0);
360 }
361
362 static struct mkimg_format vhd_dyn_format = {
363 .name = "vhd",
364 .description = "Virtual Hard Disk",
365 .resize = vhd_dyn_resize,
366 .write = vhd_dyn_write,
367 };
368
369 FORMAT_DEFINE(vhd_dyn_format);
370
371 /*
372 * PART 3: Fixed VHD
373 */
374
375 static int
vhd_fix_resize(lba_t imgsz)376 vhd_fix_resize(lba_t imgsz)
377 {
378 uint64_t imagesz;
379
380 imagesz = vhd_resize(imgsz * secsz);
381 /*
382 * Azure demands that images are a whole number of megabytes.
383 */
384 imagesz = (imagesz + 0xfffffULL) & ~0xfffffULL;
385 return (image_set_size(imagesz / secsz));
386 }
387
388 static int
vhd_fix_write(int fd)389 vhd_fix_write(int fd)
390 {
391 struct vhd_footer footer;
392 uint64_t imagesz;
393 int error;
394
395 error = image_copyout(fd);
396 if (error)
397 return (error);
398
399 imagesz = image_get_size() * secsz;
400 vhd_make_footer(&footer, imagesz, VHD_DISK_TYPE_FIXED, ~0ULL);
401 error = (sparse_write(fd, &footer, sizeof(footer)) < 0) ? errno : 0;
402 return (error);
403 }
404
405 static struct mkimg_format vhd_fix_format = {
406 .name = "vhdf",
407 .description = "Fixed Virtual Hard Disk",
408 .resize = vhd_fix_resize,
409 .write = vhd_fix_write,
410 };
411
412 FORMAT_DEFINE(vhd_fix_format);
413