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