]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - usr.bin/mkimg/vhd.c
Add two missing eventhandler.h headers
[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 <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
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
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
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
188 vhd_timestamp(void)
189 {
190         time_t t;
191
192         if (!unit_testing) {
193                 t = time(NULL);
194                 return (t - 0x386d4380);
195         }
196
197         return (0x01234567);
198 }
199
200 static void
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
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
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
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
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);