]> CyberLeo.Net >> Repos - FreeBSD/releng/10.3.git/blob - usr.bin/mkimg/vhd.c
- Copy stable/10@296371 to releng/10.3 in preparation for 10.3-RC1
[FreeBSD/releng/10.3.git] / usr.bin / mkimg / vhd.c
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 __FBSDID("$FreeBSD$");
29
30 #include <sys/types.h>
31 #include <sys/endian.h>
32 #include <sys/errno.h>
33 #include <stdlib.h>
34 #include <string.h>
35 #include <time.h>
36 #include <unistd.h>
37 #include <uuid.h>
38
39 #include "image.h"
40 #include "format.h"
41 #include "mkimg.h"
42
43 #ifndef __has_extension
44 #define __has_extension(x)      0
45 #endif
46
47 /*
48  * General notes:
49  * o   File is in network byte order.
50  * o   The timestamp is seconds since 1/1/2000 12:00:00 AM UTC
51  *
52  * This file is divided in 3 parts:
53  * 1.  Common definitions
54  * 2.  Dynamic VHD support
55  * 3.  Fixed VHD support
56  */
57
58 /*
59  * PART 1: Common definitions
60  */
61
62 #define VHD_SECTOR_SIZE 512
63 #define VHD_BLOCK_SIZE  (4096 * VHD_SECTOR_SIZE)        /* 2MB blocks */
64
65 struct vhd_geom {
66         uint16_t        cylinders;
67         uint8_t         heads;
68         uint8_t         sectors;
69 };
70
71 struct vhd_footer {
72         uint64_t        cookie;
73 #define VHD_FOOTER_COOKIE       0x636f6e6563746978
74         uint32_t        features;
75 #define VHD_FEATURES_TEMPORARY  0x01
76 #define VHD_FEATURES_RESERVED   0x02
77         uint32_t        version;
78 #define VHD_VERSION             0x00010000
79         uint64_t        data_offset;
80         uint32_t        timestamp;
81         uint32_t        creator_tool;
82 #define VHD_CREATOR_TOOL        0x2a696d67      /* FreeBSD mkimg */
83         uint32_t        creator_version;
84 #define VHD_CREATOR_VERSION     0x00020000
85         uint32_t        creator_os;
86 #define VHD_CREATOR_OS          0x5769326b      /* Wi2k */
87         uint64_t        original_size;
88         uint64_t        current_size;
89         struct vhd_geom geometry;
90         uint32_t        disk_type;
91 #define VHD_DISK_TYPE_FIXED     2
92 #define VHD_DISK_TYPE_DYNAMIC   3
93 #define VHD_DISK_TYPE_DIFF      4
94         uint32_t        checksum;
95         uuid_t          id;
96         uint8_t         saved_state;
97         uint8_t         _reserved[427];
98 };
99 #if __has_extension(c_static_assert)
100 _Static_assert(sizeof(struct vhd_footer) == VHD_SECTOR_SIZE,
101     "Wrong size for footer");
102 #endif
103
104 static uint32_t
105 vhd_checksum(void *buf, size_t sz)
106 {
107         uint8_t *p = buf;
108         uint32_t sum;
109         size_t ofs;
110
111         sum = 0;
112         for (ofs = 0; ofs < sz; ofs++)
113                 sum += p[ofs];
114         return (~sum);
115 }
116
117 static void
118 vhd_geometry(uint64_t image_size, struct vhd_geom *geom)
119 {
120         lba_t imgsz;
121         long cth;
122
123         imgsz = image_size / VHD_SECTOR_SIZE;
124
125         /* Respect command line options if possible. */
126         if (nheads > 1 && nheads < 256 &&
127             nsecs > 1 && nsecs < 256 &&
128             ncyls < 65536) {
129                 geom->cylinders = (ncyls != 0) ? ncyls :
130                     imgsz / (nheads * nsecs);
131                 geom->heads = nheads;
132                 geom->sectors = nsecs;
133                 return;
134         }
135
136         if (imgsz > 65536 * 16 * 255)
137                 imgsz = 65536 * 16 * 255;
138         if (imgsz >= 65535 * 16 * 63) {
139                 geom->cylinders = imgsz / (16 * 255);
140                 geom->heads = 16;
141                 geom->sectors = 255;
142                 return;
143         }
144         geom->sectors = 17;
145         cth = imgsz / 17;
146         geom->heads = (cth + 1023) / 1024;
147         if (geom->heads < 4)
148                 geom->heads = 4;
149         if (cth >= (geom->heads * 1024) || geom->heads > 16) {
150                 geom->heads = 16;
151                 geom->sectors = 31;
152                 cth = imgsz / 31;
153         }
154         if (cth >= (geom->heads * 1024)) {
155                 geom->heads = 16;
156                 geom->sectors = 63;
157                 cth = imgsz / 63;
158         }
159         geom->cylinders = cth / geom->heads;
160 }
161
162 static uint64_t
163 vhd_resize(uint64_t origsz)
164 {
165         struct vhd_geom geom;
166         uint64_t newsz;
167
168         /*
169          * Round the image size to the pre-determined geometry that
170          * matches the image size. This circular dependency implies
171          * that we need to loop to handle boundary conditions.
172          * The first time, newsz equals origsz and the geometry will
173          * typically yield a new size that's smaller. We keep adding
174          * cylinder's worth of sectors to the new size until its
175          * larger or equal or origsz. But during those iterations,
176          * the geometry can change, so we need to account for that.
177          */
178         newsz = origsz;
179         while (1) {
180                 vhd_geometry(newsz, &geom);
181                 newsz = (int64_t)geom.cylinders * geom.heads *
182                     geom.sectors * VHD_SECTOR_SIZE;
183                 if (newsz >= origsz)
184                         break;
185                 newsz += geom.heads * geom.sectors * VHD_SECTOR_SIZE;
186         }
187         return (newsz);
188 }
189
190 static uint32_t
191 vhd_timestamp(void)
192 {
193         time_t t;
194
195         if (!unit_testing) {
196                 t = time(NULL);
197                 return (t - 0x386d4380);
198         }
199
200         return (0x01234567);
201 }
202
203 static void
204 vhd_uuid_enc(void *buf, const uuid_t *uuid)
205 {
206         uint8_t *p = buf;
207         int i;
208
209         be32enc(p, uuid->time_low);
210         be16enc(p + 4, uuid->time_mid);
211         be16enc(p + 6, uuid->time_hi_and_version);
212         p[8] = uuid->clock_seq_hi_and_reserved;
213         p[9] = uuid->clock_seq_low;
214         for (i = 0; i < _UUID_NODE_LEN; i++)
215                 p[10 + i] = uuid->node[i];
216 }
217
218 static void
219 vhd_make_footer(struct vhd_footer *footer, uint64_t image_size,
220     uint32_t disk_type, uint64_t data_offset)
221 {
222         uuid_t id;
223
224         memset(footer, 0, sizeof(*footer));
225         be64enc(&footer->cookie, VHD_FOOTER_COOKIE);
226         be32enc(&footer->features, VHD_FEATURES_RESERVED);
227         be32enc(&footer->version, VHD_VERSION);
228         be64enc(&footer->data_offset, data_offset);
229         be32enc(&footer->timestamp, vhd_timestamp());
230         be32enc(&footer->creator_tool, VHD_CREATOR_TOOL);
231         be32enc(&footer->creator_version, VHD_CREATOR_VERSION);
232         be32enc(&footer->creator_os, VHD_CREATOR_OS);
233         be64enc(&footer->original_size, image_size);
234         be64enc(&footer->current_size, image_size);
235         vhd_geometry(image_size, &footer->geometry);
236         be16enc(&footer->geometry.cylinders, footer->geometry.cylinders);
237         be32enc(&footer->disk_type, disk_type);
238         mkimg_uuid(&id);
239         vhd_uuid_enc(&footer->id, &id);
240         be32enc(&footer->checksum, vhd_checksum(footer, sizeof(*footer)));
241 }
242
243 /*
244  * PART 2: Dynamic VHD support
245  *
246  * Notes:
247  * o   File layout:
248  *      copy of disk footer
249  *      dynamic disk header
250  *      block allocation table (BAT)
251  *      data blocks
252  *      disk footer
253  */
254
255 struct vhd_dyn_header {
256         uint64_t        cookie;
257 #define VHD_HEADER_COOKIE       0x6378737061727365
258         uint64_t        data_offset;
259         uint64_t        table_offset;
260         uint32_t        version;
261         uint32_t        max_entries;
262         uint32_t        block_size;
263         uint32_t        checksum;
264         uuid_t          parent_id;
265         uint32_t        parent_timestamp;
266         char            _reserved1[4];
267         uint16_t        parent_name[256];       /* UTF-16 */
268         struct {
269                 uint32_t        code;
270                 uint32_t        data_space;
271                 uint32_t        data_length;
272                 uint32_t        _reserved;
273                 uint64_t        data_offset;
274         } parent_locator[8];
275         char            _reserved2[256];
276 };
277 #if __has_extension(c_static_assert)
278 _Static_assert(sizeof(struct vhd_dyn_header) == VHD_SECTOR_SIZE * 2,
279     "Wrong size for header");
280 #endif
281
282 static int
283 vhd_dyn_resize(lba_t imgsz)
284 {
285         uint64_t imagesz;
286
287         imagesz = vhd_resize(imgsz * secsz);
288         return (image_set_size(imagesz / secsz));
289 }
290
291 static int
292 vhd_dyn_write(int fd)
293 {
294         struct vhd_footer footer;
295         struct vhd_dyn_header header;
296         uint64_t imgsz, rawsz;
297         lba_t blk, blkcnt, nblks;
298         uint32_t *bat;
299         void *bitmap;
300         size_t batsz;
301         uint32_t sector;
302         int bat_entries, error, entry;
303
304         rawsz = image_get_size() * secsz;
305         imgsz = (rawsz + VHD_BLOCK_SIZE - 1) & ~(VHD_BLOCK_SIZE - 1);
306
307         vhd_make_footer(&footer, rawsz, VHD_DISK_TYPE_DYNAMIC, sizeof(footer));
308         if (sparse_write(fd, &footer, sizeof(footer)) < 0)
309                 return (errno);
310
311         bat_entries = imgsz / VHD_BLOCK_SIZE;
312         memset(&header, 0, sizeof(header));
313         be64enc(&header.cookie, VHD_HEADER_COOKIE);
314         be64enc(&header.data_offset, ~0ULL);
315         be64enc(&header.table_offset, sizeof(footer) + sizeof(header));
316         be32enc(&header.version, VHD_VERSION);
317         be32enc(&header.max_entries, bat_entries);
318         be32enc(&header.block_size, VHD_BLOCK_SIZE);
319         be32enc(&header.checksum, vhd_checksum(&header, sizeof(header)));
320         if (sparse_write(fd, &header, sizeof(header)) < 0)
321                 return (errno);
322
323         batsz = bat_entries * sizeof(uint32_t);
324         batsz = (batsz + VHD_SECTOR_SIZE - 1) & ~(VHD_SECTOR_SIZE - 1);
325         bat = malloc(batsz);
326         if (bat == NULL)
327                 return (errno);
328         memset(bat, 0xff, batsz);
329         blkcnt = VHD_BLOCK_SIZE / secsz;
330         sector = (sizeof(footer) + sizeof(header) + batsz) / VHD_SECTOR_SIZE;
331         for (entry = 0; entry < bat_entries; entry++) {
332                 blk = entry * blkcnt;
333                 if (image_data(blk, blkcnt)) {
334                         be32enc(&bat[entry], sector);
335                         sector += (VHD_BLOCK_SIZE / VHD_SECTOR_SIZE) + 1;
336                 }
337         }
338         if (sparse_write(fd, bat, batsz) < 0) {
339                 free(bat);
340                 return (errno);
341         }
342         free(bat);
343
344         bitmap = malloc(VHD_SECTOR_SIZE);
345         if (bitmap == NULL)
346                 return (errno);
347         memset(bitmap, 0xff, VHD_SECTOR_SIZE);
348
349         blk = 0;
350         blkcnt = VHD_BLOCK_SIZE / secsz;
351         error = 0;
352         nblks = rawsz / secsz;
353         while (blk < nblks) {
354                 if (!image_data(blk, blkcnt)) {
355                         blk += blkcnt;
356                         continue;
357                 }
358                 if (sparse_write(fd, bitmap, VHD_SECTOR_SIZE) < 0) {
359                         error = errno;
360                         break;
361                 }
362                 /* Handle partial last block */
363                 if (blk + blkcnt > nblks)
364                         blkcnt = nblks - blk;
365                 error = image_copyout_region(fd, blk, blkcnt);
366                 if (error)
367                         break;
368                 blk += blkcnt;
369         }
370         free(bitmap);
371         if (error)
372                 return (error);
373         error = image_copyout_zeroes(fd, imgsz - rawsz);
374         if (error)
375                 return (error);
376         if (sparse_write(fd, &footer, sizeof(footer)) < 0)
377                 return (errno);
378
379         return (0);
380 }
381
382 static struct mkimg_format vhd_dyn_format = {
383         .name = "vhd",
384         .description = "Virtual Hard Disk",
385         .resize = vhd_dyn_resize,
386         .write = vhd_dyn_write,
387 };
388
389 FORMAT_DEFINE(vhd_dyn_format);
390
391 /*
392  * PART 3: Fixed VHD
393  */
394
395 static int
396 vhd_fix_resize(lba_t imgsz)
397 {
398         uint64_t imagesz;
399
400         imagesz = vhd_resize(imgsz * secsz);
401         /*
402          * Azure demands that images are a whole number of megabytes.
403          */
404         imagesz = (imagesz + 0xfffffULL) & ~0xfffffULL;
405         return (image_set_size(imagesz / secsz));
406 }
407
408 static int
409 vhd_fix_write(int fd)
410 {
411         struct vhd_footer footer;
412         uint64_t imagesz;
413         int error;
414
415         error = image_copyout(fd);
416         if (error)
417                 return (error);
418
419         imagesz = image_get_size() * secsz;
420         vhd_make_footer(&footer, imagesz, VHD_DISK_TYPE_FIXED, ~0ULL);
421         error = (sparse_write(fd, &footer, sizeof(footer)) < 0) ? errno : 0;
422         return (error);
423 }
424
425 static struct mkimg_format vhd_fix_format = {
426         .name = "vhdf",
427         .description = "Fixed Virtual Hard Disk",
428         .resize = vhd_fix_resize,
429         .write = vhd_fix_write,
430 };
431
432 FORMAT_DEFINE(vhd_fix_format);