]> CyberLeo.Net >> Repos - FreeBSD/releng/10.2.git/blob - usr.bin/mkimg/vhd.c
- Copy stable/10@285827 to releng/10.2 in preparation for 10.2-RC1
[FreeBSD/releng/10.2.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 uint32_t
163 vhd_timestamp(void)
164 {
165         time_t t;
166
167         if (!unit_testing) {
168                 t = time(NULL);
169                 return (t - 0x386d4380);
170         }
171
172         return (0x01234567);
173 }
174
175 static void
176 vhd_uuid_enc(void *buf, const uuid_t *uuid)
177 {
178         uint8_t *p = buf;
179         int i;
180
181         be32enc(p, uuid->time_low);
182         be16enc(p + 4, uuid->time_mid);
183         be16enc(p + 6, uuid->time_hi_and_version);
184         p[8] = uuid->clock_seq_hi_and_reserved;
185         p[9] = uuid->clock_seq_low;
186         for (i = 0; i < _UUID_NODE_LEN; i++)
187                 p[10 + i] = uuid->node[i];
188 }
189
190 static void
191 vhd_make_footer(struct vhd_footer *footer, uint64_t image_size,
192     uint32_t disk_type, uint64_t data_offset)
193 {
194         uuid_t id;
195
196         memset(footer, 0, sizeof(*footer));
197         be64enc(&footer->cookie, VHD_FOOTER_COOKIE);
198         be32enc(&footer->features, VHD_FEATURES_RESERVED);
199         be32enc(&footer->version, VHD_VERSION);
200         be64enc(&footer->data_offset, data_offset);
201         be32enc(&footer->timestamp, vhd_timestamp());
202         be32enc(&footer->creator_tool, VHD_CREATOR_TOOL);
203         be32enc(&footer->creator_version, VHD_CREATOR_VERSION);
204         be32enc(&footer->creator_os, VHD_CREATOR_OS);
205         be64enc(&footer->original_size, image_size);
206         be64enc(&footer->current_size, image_size);
207         vhd_geometry(image_size, &footer->geometry);
208         be16enc(&footer->geometry.cylinders, footer->geometry.cylinders);
209         be32enc(&footer->disk_type, disk_type);
210         mkimg_uuid(&id);
211         vhd_uuid_enc(&footer->id, &id);
212         be32enc(&footer->checksum, vhd_checksum(footer, sizeof(*footer)));
213 }
214
215 /*
216  * PART 2: Dynamic VHD support
217  *
218  * Notes:
219  * o   File layout:
220  *      copy of disk footer
221  *      dynamic disk header
222  *      block allocation table (BAT)
223  *      data blocks
224  *      disk footer
225  */
226
227 struct vhd_dyn_header {
228         uint64_t        cookie;
229 #define VHD_HEADER_COOKIE       0x6378737061727365
230         uint64_t        data_offset;
231         uint64_t        table_offset;
232         uint32_t        version;
233         uint32_t        max_entries;
234         uint32_t        block_size;
235         uint32_t        checksum;
236         uuid_t          parent_id;
237         uint32_t        parent_timestamp;
238         char            _reserved1[4];
239         uint16_t        parent_name[256];       /* UTF-16 */
240         struct {
241                 uint32_t        code;
242                 uint32_t        data_space;
243                 uint32_t        data_length;
244                 uint32_t        _reserved;
245                 uint64_t        data_offset;
246         } parent_locator[8];
247         char            _reserved2[256];
248 };
249 #if __has_extension(c_static_assert)
250 _Static_assert(sizeof(struct vhd_dyn_header) == VHD_SECTOR_SIZE * 2,
251     "Wrong size for header");
252 #endif
253
254 static int
255 vhd_dyn_resize(lba_t imgsz)
256 {
257         uint64_t imagesz;
258
259         imagesz = imgsz * secsz;
260         imagesz = (imagesz + VHD_BLOCK_SIZE - 1) & ~(VHD_BLOCK_SIZE - 1);
261         return (image_set_size(imagesz / secsz));
262 }
263
264 static int
265 vhd_dyn_write(int fd)
266 {
267         struct vhd_footer footer;
268         struct vhd_dyn_header header;
269         uint64_t imgsz;
270         lba_t blk, blkcnt, nblks;
271         uint32_t *bat;
272         void *bitmap;
273         size_t batsz;
274         uint32_t sector;
275         int bat_entries, error, entry;
276
277         imgsz = image_get_size() * secsz;
278         bat_entries = imgsz / VHD_BLOCK_SIZE;
279
280         vhd_make_footer(&footer, imgsz, VHD_DISK_TYPE_DYNAMIC, sizeof(footer));
281         if (sparse_write(fd, &footer, sizeof(footer)) < 0)
282                 return (errno);
283
284         memset(&header, 0, sizeof(header));
285         be64enc(&header.cookie, VHD_HEADER_COOKIE);
286         be64enc(&header.data_offset, ~0ULL);
287         be64enc(&header.table_offset, sizeof(footer) + sizeof(header));
288         be32enc(&header.version, VHD_VERSION);
289         be32enc(&header.max_entries, bat_entries);
290         be32enc(&header.block_size, VHD_BLOCK_SIZE);
291         be32enc(&header.checksum, vhd_checksum(&header, sizeof(header)));
292         if (sparse_write(fd, &header, sizeof(header)) < 0)
293                 return (errno);
294
295         batsz = bat_entries * sizeof(uint32_t);
296         batsz = (batsz + VHD_SECTOR_SIZE - 1) & ~(VHD_SECTOR_SIZE - 1);
297         bat = malloc(batsz);
298         if (bat == NULL)
299                 return (errno);
300         memset(bat, 0xff, batsz);
301         blkcnt = VHD_BLOCK_SIZE / secsz;
302         sector = (sizeof(footer) + sizeof(header) + batsz) / VHD_SECTOR_SIZE;
303         for (entry = 0; entry < bat_entries; entry++) {
304                 blk = entry * blkcnt;
305                 if (image_data(blk, blkcnt)) {
306                         be32enc(&bat[entry], sector);
307                         sector += (VHD_BLOCK_SIZE / VHD_SECTOR_SIZE) + 1;
308                 }
309         }
310         if (sparse_write(fd, bat, batsz) < 0) {
311                 free(bat);
312                 return (errno);
313         }
314         free(bat);
315
316         bitmap = malloc(VHD_SECTOR_SIZE);
317         if (bitmap == NULL)
318                 return (errno);
319         memset(bitmap, 0xff, VHD_SECTOR_SIZE);
320
321         blk = 0;
322         blkcnt = VHD_BLOCK_SIZE / secsz;
323         error = 0;
324         nblks = image_get_size();
325         while (blk < nblks) {
326                 if (!image_data(blk, blkcnt)) {
327                         blk += blkcnt;
328                         continue;
329                 }
330                 if (sparse_write(fd, bitmap, VHD_SECTOR_SIZE) < 0) {
331                         error = errno;
332                         break;
333                 }
334                 error = image_copyout_region(fd, blk, blkcnt);
335                 if (error)
336                         break;
337                 blk += blkcnt;
338         }
339         free(bitmap);
340         if (blk != nblks)
341                 return (error);
342
343         if (sparse_write(fd, &footer, sizeof(footer)) < 0)
344                 return (errno);
345
346         return (0);
347 }
348
349 static struct mkimg_format vhd_dyn_format = {
350         .name = "vhd",
351         .description = "Virtual Hard Disk",
352         .resize = vhd_dyn_resize,
353         .write = vhd_dyn_write,
354 };
355
356 FORMAT_DEFINE(vhd_dyn_format);
357
358 /*
359  * PART 3: Fixed VHD
360  */
361
362 static int
363 vhd_fix_resize(lba_t imgsz)
364 {
365         struct vhd_geom geom;
366         int64_t imagesz;
367
368         /*
369          * Round the image size to the pre-determined geometry that
370          * matches the image size. This circular dependency implies
371          * that we need to loop to handle boundary conditions.
372          */
373         imgsz *= secsz;
374         imagesz = imgsz;
375         while (1) {
376                 vhd_geometry(imagesz, &geom);
377                 imagesz = (int64_t)geom.cylinders * geom.heads *
378                     geom.sectors * VHD_SECTOR_SIZE;
379                 if (imagesz >= imgsz)
380                         break;
381                 imagesz += geom.heads * geom.sectors * VHD_SECTOR_SIZE;
382         }
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 imgsz;
395         int error;
396
397         error = image_copyout(fd);
398         if (!error) {
399                 imgsz = image_get_size() * secsz;
400                 vhd_make_footer(&footer, imgsz, VHD_DISK_TYPE_FIXED, ~0ULL);
401                 if (sparse_write(fd, &footer, sizeof(footer)) < 0)
402                         error = errno;
403         }
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);