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