HAMMER Utilities: Sync with recent changes.
[dragonfly.git] / sbin / newfs_hammer / newfs_hammer.c
1 /*
2  * Copyright (c) 2007 The DragonFly Project.  All rights reserved.
3  * 
4  * This code is derived from software contributed to The DragonFly Project
5  * by Matthew Dillon <dillon@backplane.com>
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  * 
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer.
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer in
15  *    the documentation and/or other materials provided with the
16  *    distribution.
17  * 3. Neither the name of The DragonFly Project nor the names of its
18  *    contributors may be used to endorse or promote products derived
19  *    from this software without specific, prior written permission.
20  * 
21  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
24  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
25  * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
26  * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING,
27  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
28  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
29  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
30  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
31  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
32  * SUCH DAMAGE.
33  * 
34  * $DragonFly: src/sbin/newfs_hammer/newfs_hammer.c,v 1.24 2008/05/06 00:15:35 dillon Exp $
35  */
36
37 #include "newfs_hammer.h"
38
39 static int64_t getsize(const char *str, int64_t minval, int64_t maxval, int pw);
40 static const char *sizetostr(off_t size);
41 static void check_volume(struct volume_info *vol);
42 static void format_volume(struct volume_info *vol, int nvols,const char *label,
43                         off_t total_size);
44 static hammer_off_t format_root(void);
45 static void usage(void);
46
47 int
48 main(int ac, char **av)
49 {
50         int i;
51         int ch;
52         u_int32_t status;
53         off_t total;
54         const char *label = NULL;
55         struct volume_info *vol;
56
57         /*
58          * Sanity check basic filesystem structures.  No cookies for us
59          * if it gets broken!
60          */
61         assert(sizeof(struct hammer_volume_ondisk) <= HAMMER_BUFSIZE);
62         assert(sizeof(union hammer_record_ondisk) == HAMMER_RECORD_SIZE);
63         assert(sizeof(struct hammer_blockmap_layer1) == 32);
64         assert(sizeof(struct hammer_blockmap_layer2) == 16);
65
66         /*
67          * Generate a filesysem id and lookup the filesystem type
68          */
69         uuidgen(&Hammer_FSId, 1);
70         uuid_name_lookup(&Hammer_FSType, "DragonFly HAMMER", &status);
71         if (status != uuid_s_ok) {
72                 errx(1, "uuids file does not have the DragonFly "
73                         "HAMMER filesystem type");
74         }
75
76         /*
77          * Parse arguments
78          */
79         while ((ch = getopt(ac, av, "L:b:m:u:")) != -1) {
80                 switch(ch) {
81                 case 'L':
82                         label = optarg;
83                         break;
84                 case 'b':
85                         BootAreaSize = getsize(optarg,
86                                          HAMMER_BUFSIZE,
87                                          HAMMER_BOOT_MAXBYTES, 2);
88                         break;
89                 case 'm':
90                         MemAreaSize = getsize(optarg,
91                                          HAMMER_BUFSIZE,
92                                          HAMMER_MEM_MAXBYTES, 2);
93                         break;
94                 case 'u':
95                         UndoBufferSize = getsize(optarg,
96                                          HAMMER_LARGEBLOCK_SIZE,
97                                          HAMMER_LARGEBLOCK_SIZE *
98                                          HAMMER_UNDO_LAYER2, 2);
99                         break;
100                 default:
101                         usage();
102                         break;
103                 }
104         }
105
106         if (label == NULL) {
107                 fprintf(stderr,
108                         "newfs_hammer: A filesystem label must be specified\n");
109                 exit(1);
110         }
111
112         /*
113          * Collect volume information
114          */
115         ac -= optind;
116         av += optind;
117         NumVolumes = ac;
118         RootVolNo = 0;
119
120         total = 0;
121         for (i = 0; i < NumVolumes; ++i) {
122                 vol = setup_volume(i, av[i], 1, O_RDWR);
123
124                 /*
125                  * Load up information on the volume and initialize
126                  * its remaining fields.
127                  */
128                 check_volume(vol);
129                 total += vol->size;
130         }
131
132         /*
133          * Calculate defaults for the boot and memory area sizes.
134          */
135         if (BootAreaSize == 0) {
136                 BootAreaSize = HAMMER_BOOT_NOMBYTES;
137                 while (BootAreaSize > total / NumVolumes / 256)
138                         BootAreaSize >>= 1;
139                 if (BootAreaSize < HAMMER_BOOT_MINBYTES)
140                         BootAreaSize = 0;
141         } else if (BootAreaSize < HAMMER_BOOT_MINBYTES) {
142                 BootAreaSize = HAMMER_BOOT_MINBYTES;
143         }
144         if (MemAreaSize == 0) {
145                 MemAreaSize = HAMMER_MEM_NOMBYTES;
146                 while (MemAreaSize > total / NumVolumes / 256)
147                         MemAreaSize >>= 1;
148                 if (MemAreaSize < HAMMER_MEM_MINBYTES)
149                         MemAreaSize = 0;
150         } else if (MemAreaSize < HAMMER_MEM_MINBYTES) {
151                 MemAreaSize = HAMMER_MEM_MINBYTES;
152         }
153
154         /*
155          * Format the volumes.  Format the root volume first so we can
156          * bootstrap the freemap.
157          */
158         format_volume(get_volume(RootVolNo), NumVolumes, label, total);
159         for (i = 0; i < NumVolumes; ++i) {
160                 if (i != RootVolNo)
161                         format_volume(get_volume(i), NumVolumes, label, total);
162         }
163         printf("---------------------------------------------\n");
164         printf("%d volume%s total size %s\n",
165                 NumVolumes, (NumVolumes == 1 ? "" : "s"), sizetostr(total));
166         printf("boot-area-size:      %s\n", sizetostr(BootAreaSize));
167         printf("memory-log-size:     %s\n", sizetostr(MemAreaSize));
168         printf("undo-buffer-size:    %s\n", sizetostr(UndoBufferSize));
169
170         vol = get_volume(RootVolNo);
171         printf("zone-limit:          %s\n", sizetostr(vol->ondisk->vol0_zone_limit));
172         printf("\n");
173
174         flush_all_volumes();
175         return(0);
176 }
177
178 static
179 void
180 usage(void)
181 {
182         fprintf(stderr, "newfs_hammer vol0 [vol1 ...]\n");
183         exit(1);
184 }
185
186 /*
187  * Convert the size in bytes to a human readable string.
188  */
189 static
190 const char *
191 sizetostr(off_t size)
192 {
193         static char buf[32];
194
195         if (size < 1024 / 2) {
196                 snprintf(buf, sizeof(buf), "%6.2f", (double)size);
197         } else if (size < 1024 * 1024 / 2) {
198                 snprintf(buf, sizeof(buf), "%6.2fKB",
199                         (double)size / 1024);
200         } else if (size < 1024 * 1024 * 1024LL / 2) {
201                 snprintf(buf, sizeof(buf), "%6.2fMB",
202                         (double)size / (1024 * 1024));
203         } else if (size < 1024 * 1024 * 1024LL * 1024LL / 2) {
204                 snprintf(buf, sizeof(buf), "%6.2fGB",
205                         (double)size / (1024 * 1024 * 1024LL));
206         } else {
207                 snprintf(buf, sizeof(buf), "%6.2fTB",
208                         (double)size / (1024 * 1024 * 1024LL * 1024LL));
209         }
210         return(buf);
211 }
212
213 /*
214  * Convert a string to a 64 bit signed integer with various requirements.
215  */
216 static int64_t
217 getsize(const char *str, int64_t minval, int64_t maxval, int powerof2)
218 {
219         int64_t val;
220         char *ptr;
221
222         val = strtoll(str, &ptr, 0);
223         switch(*ptr) {
224         case 't':
225         case 'T':
226                 val *= 1024;
227                 /* fall through */
228         case 'g':
229         case 'G':
230                 val *= 1024;
231                 /* fall through */
232         case 'm':
233         case 'M':
234                 val *= 1024;
235                 /* fall through */
236         case 'k':
237         case 'K':
238                 val *= 1024;
239                 break;
240         default:
241                 errx(1, "Unknown suffix in number '%s'\n", str);
242                 /* not reached */
243         }
244         if (ptr[1]) {
245                 errx(1, "Unknown suffix in number '%s'\n", str);
246                 /* not reached */
247         }
248         if (val < minval) {
249                 errx(1, "Value too small: %s, min is %s\n",
250                      str, sizetostr(minval));
251                 /* not reached */
252         }
253         if (val > maxval) {
254                 errx(1, "Value too large: %s, max is %s\n",
255                      str, sizetostr(maxval));
256                 /* not reached */
257         }
258         if ((powerof2 & 1) && (val ^ (val - 1)) != ((val << 1) - 1)) {
259                 errx(1, "Value not power of 2: %s\n", str);
260                 /* not reached */
261         }
262         if ((powerof2 & 2) && (val & HAMMER_BUFMASK)) {
263                 errx(1, "Value not an integral multiple of %dK: %s", 
264                      HAMMER_BUFSIZE / 1024, str);
265                 /* not reached */
266         }
267         return(val);
268 }
269
270 /*
271  * Generate a transaction id
272  */
273 static hammer_tid_t
274 createtid(void)
275 {
276         static hammer_tid_t lasttid;
277         struct timeval tv;
278
279         if (lasttid == 0) {
280                 gettimeofday(&tv, NULL);
281                 lasttid = tv.tv_sec * 1000000000LL +
282                           tv.tv_usec * 1000LL;
283         }
284         return(lasttid++);
285 }
286
287 /*
288  * Check basic volume characteristics.  HAMMER filesystems use a minimum
289  * of a 16KB filesystem buffer size.
290  */
291 static
292 void
293 check_volume(struct volume_info *vol)
294 {
295         struct partinfo pinfo;
296         struct stat st;
297
298         /*
299          * Get basic information about the volume
300          */
301         vol->fd = open(vol->name, O_RDWR);
302         if (vol->fd < 0)
303                 err(1, "Unable to open %s R+W", vol->name);
304         if (ioctl(vol->fd, DIOCGPART, &pinfo) < 0) {
305                 /*
306                  * Allow the formatting of regular filews as HAMMER volumes
307                  */
308                 if (fstat(vol->fd, &st) < 0)
309                         err(1, "Unable to stat %s", vol->name);
310                 vol->size = st.st_size;
311                 vol->type = "REGFILE";
312         } else {
313                 /*
314                  * When formatting a block device as a HAMMER volume the
315                  * sector size must be compatible.  HAMMER uses 16384 byte
316                  * filesystem buffers.
317                  */
318                 if (pinfo.reserved_blocks) {
319                         errx(1, "HAMMER cannot be placed in a partition "
320                                 "which overlaps the disklabel or MBR");
321                 }
322                 if (pinfo.media_blksize > 16384 ||
323                     16384 % pinfo.media_blksize) {
324                         errx(1, "A media sector size of %d is not supported",
325                              pinfo.media_blksize);
326                 }
327
328                 vol->size = pinfo.media_size;
329                 vol->type = "DEVICE";
330         }
331         printf("Volume %d %s %-15s size %s\n",
332                vol->vol_no, vol->type, vol->name,
333                sizetostr(vol->size));
334
335         /*
336          * Reserve space for (future) header junk, setup our poor-man's
337          * bigblock allocator.
338          */
339         vol->vol_alloc = HAMMER_BUFSIZE * 16;
340 }
341
342 /*
343  * Format a HAMMER volume.  Cluster 0 will be initially placed in volume 0.
344  */
345 static
346 void
347 format_volume(struct volume_info *vol, int nvols, const char *label,
348               off_t total_size)
349 {
350         struct volume_info *root_vol;
351         struct hammer_volume_ondisk *ondisk;
352         int64_t freeblks;
353
354         /*
355          * Initialize basic information in the on-disk volume structure.
356          */
357         ondisk = vol->ondisk;
358
359         ondisk->vol_fsid = Hammer_FSId;
360         ondisk->vol_fstype = Hammer_FSType;
361         snprintf(ondisk->vol_name, sizeof(ondisk->vol_name), "%s", label);
362         ondisk->vol_no = vol->vol_no;
363         ondisk->vol_count = nvols;
364         ondisk->vol_version = 1;
365
366         ondisk->vol_bot_beg = vol->vol_alloc;
367         vol->vol_alloc += BootAreaSize;
368         ondisk->vol_mem_beg = vol->vol_alloc;
369         vol->vol_alloc += MemAreaSize;
370
371         /*
372          * The remaining area is the zone 2 buffer allocation area.  These
373          * buffers
374          */
375         ondisk->vol_buf_beg = vol->vol_alloc;
376         ondisk->vol_buf_end = vol->size & ~(int64_t)HAMMER_BUFMASK;
377
378         if (ondisk->vol_buf_end < ondisk->vol_buf_beg) {
379                 errx(1, "volume %d %s is too small to hold the volume header",
380                      vol->vol_no, vol->name);
381         }
382
383         ondisk->vol_nblocks = (ondisk->vol_buf_end - ondisk->vol_buf_beg) /
384                               HAMMER_BUFSIZE;
385         ondisk->vol_blocksize = HAMMER_BUFSIZE;
386
387         ondisk->vol_rootvol = RootVolNo;
388         ondisk->vol_signature = HAMMER_FSBUF_VOLUME;
389
390         vol->vol_free_off = HAMMER_ENCODE_RAW_BUFFER(vol->vol_no, 0);
391         vol->vol_free_end = HAMMER_ENCODE_RAW_BUFFER(vol->vol_no, (ondisk->vol_buf_end - ondisk->vol_buf_beg) & ~HAMMER_LARGEBLOCK_MASK64);
392
393         /*
394          * Format the root volume.
395          */
396         if (vol->vol_no == RootVolNo) {
397                 hammer_off_t zone_limit;
398
399                 /*
400                  * Starting TID
401                  */
402                 ondisk->vol0_next_tid = createtid();
403
404                 /*
405                  * Set the default zone limit to 100x the size of the
406                  * filesystem.  We do not want to create a huge zone limit
407                  * for tiny filesystems because the blockmap could wind up
408                  * getting fragmented and eating a large chunk of the disk
409                  * space.
410                  */
411                 zone_limit = (hammer_off_t)total_size * 100;
412                 zone_limit = (zone_limit + HAMMER_BLOCKMAP_LAYER2_MASK) &
413                              ~HAMMER_BLOCKMAP_LAYER2_MASK;
414                 if (zone_limit < (hammer_off_t)total_size ||
415                     zone_limit > HAMMER_ZONE_LIMIT) {
416                         zone_limit = HAMMER_ZONE_LIMIT;
417                 }
418                 ondisk->vol0_zone_limit = zone_limit;
419
420                 format_freemap(vol,
421                         &ondisk->vol0_blockmap[HAMMER_ZONE_FREEMAP_INDEX]);
422
423                 freeblks = initialize_freemap(vol);
424                 ondisk->vol0_stat_freebigblocks = freeblks;
425                 ondisk->vol0_stat_bigblocks = freeblks;
426
427                 format_blockmap(
428                         &ondisk->vol0_blockmap[HAMMER_ZONE_BTREE_INDEX],
429                         HAMMER_ZONE_BTREE);
430                 format_blockmap(
431                         &ondisk->vol0_blockmap[HAMMER_ZONE_RECORD_INDEX],
432                         HAMMER_ZONE_RECORD);
433                 format_blockmap(
434                         &ondisk->vol0_blockmap[HAMMER_ZONE_LARGE_DATA_INDEX],
435                         HAMMER_ZONE_LARGE_DATA);
436                 format_blockmap(
437                         &ondisk->vol0_blockmap[HAMMER_ZONE_SMALL_DATA_INDEX],
438                         HAMMER_ZONE_SMALL_DATA);
439
440                 format_undomap(ondisk);
441
442                 ondisk->vol0_btree_root = format_root();
443                 ++ondisk->vol0_stat_inodes;     /* root inode */
444         } else {
445                 freeblks = initialize_freemap(vol);
446                 root_vol = get_volume(RootVolNo);
447                 root_vol->cache.modified = 1;
448                 root_vol->ondisk->vol0_stat_freebigblocks += freeblks;
449                 root_vol->ondisk->vol0_stat_bigblocks += freeblks;
450                 rel_volume(root_vol);
451         }
452 }
453
454 /*
455  * Format the root directory.
456  */
457 static
458 hammer_off_t
459 format_root(void)
460 {
461         hammer_off_t btree_off;
462         hammer_off_t rec_off;
463         hammer_node_ondisk_t bnode;
464         hammer_record_ondisk_t rec;
465         struct hammer_inode_data *idata;
466         hammer_btree_elm_t elm;
467
468         bnode = alloc_btree_element(&btree_off);
469         rec = alloc_record_element(&rec_off, sizeof(*idata), (void **)&idata);
470
471         /*
472          * Populate the inode data and inode record for the root directory.
473          */
474         idata->version = HAMMER_INODE_DATA_VERSION;
475         idata->mode = 0755;
476
477         rec->base.base.btype = HAMMER_BTREE_TYPE_RECORD;
478         rec->base.base.obj_id = HAMMER_OBJID_ROOT;
479         rec->base.base.key = 0;
480         rec->base.base.create_tid = createtid();
481         rec->base.base.delete_tid = 0;
482         rec->base.base.rec_type = HAMMER_RECTYPE_INODE;
483         rec->base.base.obj_type = HAMMER_OBJTYPE_DIRECTORY;
484         /* rec->base.data_offset - initialized by alloc_record_element */
485         /* rec->base.data_len    - initialized by alloc_record_element */
486         rec->base.data_crc = crc32(idata, sizeof(*idata));
487         rec->inode.ino_atime  = rec->base.base.create_tid;
488         rec->inode.ino_mtime  = rec->base.base.create_tid;
489         rec->inode.ino_size   = 0;
490         rec->inode.ino_nlinks = 1;
491         rec->base.signature = HAMMER_RECORD_SIGNATURE_GOOD;
492         rec->base.rec_crc = crc32(&rec->base.rec_crc + 1,
493                                 HAMMER_RECORD_SIZE - sizeof(rec->base.rec_crc));
494
495         /*
496          * Create the root of the B-Tree.  The root is a leaf node so we
497          * do not have to worry about boundary elements.
498          */
499         bnode->signature = HAMMER_BTREE_SIGNATURE_GOOD;
500         bnode->count = 1;
501         bnode->type = HAMMER_BTREE_TYPE_LEAF;
502
503         elm = &bnode->elms[0];
504         elm->base = rec->base.base;
505         elm->leaf.rec_offset = rec_off;
506         elm->leaf.data_offset = rec->base.data_off;
507         elm->leaf.data_len = rec->base.data_len;
508         elm->leaf.data_crc = rec->base.data_crc;
509
510         bnode->crc = crc32(&bnode->crc + 1,
511                            sizeof(*bnode) - sizeof(bnode->crc));
512
513         return(btree_off);
514 }
515