Merge branch 'vendor/OPENSSH' (early part)
[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.44 2008/08/21 23:32:27 thomas 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(const char *label);
45 static u_int64_t nowtime(void);
46 static void usage(void);
47
48 static int ForceOpt = 0;
49
50 #define GIG     (1024LL*1024*1024)
51
52 int
53 main(int ac, char **av)
54 {
55         u_int32_t status;
56         off_t total;
57         int ch;
58         int i;
59         const char *label = NULL;
60         struct volume_info *vol;
61         char *fsidstr;
62
63         /*
64          * Sanity check basic filesystem structures.  No cookies for us
65          * if it gets broken!
66          */
67         assert(sizeof(struct hammer_volume_ondisk) <= HAMMER_BUFSIZE);
68         assert(sizeof(struct hammer_blockmap_layer1) == 32);
69         assert(sizeof(struct hammer_blockmap_layer2) == 16);
70
71         /*
72          * Generate a filesystem id and lookup the filesystem type
73          */
74         uuidgen(&Hammer_FSId, 1);
75         uuid_name_lookup(&Hammer_FSType, "DragonFly HAMMER", &status);
76         if (status != uuid_s_ok) {
77                 errx(1, "uuids file does not have the DragonFly "
78                         "HAMMER filesystem type");
79         }
80
81         /*
82          * Parse arguments
83          */
84         while ((ch = getopt(ac, av, "fL:b:m:u:")) != -1) {
85                 switch(ch) {
86                 case 'f':
87                         ForceOpt = 1;
88                         break;
89                 case 'L':
90                         label = optarg;
91                         break;
92                 case 'b':
93                         BootAreaSize = getsize(optarg,
94                                          HAMMER_BUFSIZE,
95                                          HAMMER_BOOT_MAXBYTES, 2);
96                         break;
97                 case 'm':
98                         MemAreaSize = getsize(optarg,
99                                          HAMMER_BUFSIZE,
100                                          HAMMER_MEM_MAXBYTES, 2);
101                         break;
102                 case 'u':
103                         UndoBufferSize = getsize(optarg,
104                                          HAMMER_LARGEBLOCK_SIZE,
105                                          HAMMER_LARGEBLOCK_SIZE *
106                                          HAMMER_UNDO_LAYER2, 2);
107                         if (UndoBufferSize < 100*1024*1024 && ForceOpt == 0)
108                                 errx(1, "The minimum UNDO fifo size is 100M\n");
109                         if (UndoBufferSize < 100*1024*1024) {
110                                 fprintf(stderr, 
111                                         "WARNING: you have specified an UNDO "
112                                         "FIFO size less than 100M, which may\n"
113                                         "lead to VFS panics.\n");
114                         }
115                         break;
116                 default:
117                         usage();
118                         break;
119                 }
120         }
121
122         if (label == NULL) {
123                 fprintf(stderr,
124                         "newfs_hammer: A filesystem label must be specified\n");
125                 exit(1);
126         }
127
128         /*
129          * Collect volume information
130          */
131         ac -= optind;
132         av += optind;
133         NumVolumes = ac;
134         RootVolNo = 0;
135
136         if (NumVolumes == 0) {
137                 fprintf(stderr,
138                         "newfs_hammer: You must specify at least one special file (volume)\n");
139                 exit(1);
140         }
141
142         total = 0;
143         for (i = 0; i < NumVolumes; ++i) {
144                 vol = setup_volume(i, av[i], 1, O_RDWR);
145
146                 /*
147                  * Load up information on the volume and initialize
148                  * its remaining fields.
149                  */
150                 check_volume(vol);
151                 total += vol->size;
152         }
153
154         /*
155          * Calculate defaults for the boot and memory area sizes.
156          */
157         if (BootAreaSize == 0) {
158                 BootAreaSize = HAMMER_BOOT_NOMBYTES;
159                 while (BootAreaSize > total / NumVolumes / 256)
160                         BootAreaSize >>= 1;
161                 if (BootAreaSize < HAMMER_BOOT_MINBYTES)
162                         BootAreaSize = 0;
163         } else if (BootAreaSize < HAMMER_BOOT_MINBYTES) {
164                 BootAreaSize = HAMMER_BOOT_MINBYTES;
165         }
166         if (MemAreaSize == 0) {
167                 MemAreaSize = HAMMER_MEM_NOMBYTES;
168                 while (MemAreaSize > total / NumVolumes / 256)
169                         MemAreaSize >>= 1;
170                 if (MemAreaSize < HAMMER_MEM_MINBYTES)
171                         MemAreaSize = 0;
172         } else if (MemAreaSize < HAMMER_MEM_MINBYTES) {
173                 MemAreaSize = HAMMER_MEM_MINBYTES;
174         }
175
176         /*
177          * Format the volumes.  Format the root volume first so we can
178          * bootstrap the freemap.
179          */
180         format_volume(get_volume(RootVolNo), NumVolumes, label, total);
181         for (i = 0; i < NumVolumes; ++i) {
182                 if (i != RootVolNo)
183                         format_volume(get_volume(i), NumVolumes, label, total);
184         }
185
186         /*
187          * Pre-size the blockmap layer1/layer2 infrastructure to the zone
188          * limit.  If we do this the filesystem does not have to allocate
189          * new layer2 blocks which reduces the chances of the reblocker
190          * having to fallback to an extremely inefficient algorithm.
191          */
192         vol = get_volume(RootVolNo);
193         vol->ondisk->vol0_stat_bigblocks = vol->ondisk->vol0_stat_freebigblocks;
194         vol->cache.modified = 1;
195         uuid_to_string(&Hammer_FSId, &fsidstr, &status);
196
197         printf("---------------------------------------------\n");
198         printf("%d volume%s total size %s\n",
199                 NumVolumes, (NumVolumes == 1 ? "" : "s"), sizetostr(total));
200         printf("boot-area-size:      %s\n", sizetostr(BootAreaSize));
201         printf("memory-log-size:     %s\n", sizetostr(MemAreaSize));
202         printf("undo-buffer-size:    %s\n", sizetostr(UndoBufferSize));
203         printf("total-pre-allocated: %s\n",
204                 sizetostr(vol->vol_free_off & HAMMER_OFF_SHORT_MASK));
205         printf("fsid:                %s\n", fsidstr);
206         printf("\n");
207         printf("NOTE: Please remember that you may have to manually set up a\n"
208                 "cron job to prune and reblock the filesystem regularly.\n"
209                 "By default, the system automatically runs 'hammer cleanup'\n"
210                 "on a nightly basis. The periodic.conf(5) variable\n"
211                 "'daily_clean_hammer_enable' can be unset to disable this.\n"
212                 "Also see 'man hammer' and 'man HAMMER' for more information.\n");
213         if (total < 50*GIG) {
214                 printf("\nWARNING: HAMMER filesystems less than 50G are "
215                         "not recommended!\n"
216                         "You may have to run 'hammer prune-everything' and "
217                         "'hammer reblock'\n"
218                         "quite often, even if using a nohistory mount.\n");
219         }
220         flush_all_volumes();
221         return(0);
222 }
223
224 static
225 void
226 usage(void)
227 {
228         fprintf(stderr,
229                 "newfs_hammer -L label [-b bootsize] [-m savesize] [-u undosize] "
230                         "special ...\n"
231         );
232         exit(1);
233 }
234
235 /*
236  * Convert the size in bytes to a human readable string.
237  */
238 static
239 const char *
240 sizetostr(off_t size)
241 {
242         static char buf[32];
243
244         if (size < 1024 / 2) {
245                 snprintf(buf, sizeof(buf), "%6.2f", (double)size);
246         } else if (size < 1024 * 1024 / 2) {
247                 snprintf(buf, sizeof(buf), "%6.2fKB",
248                         (double)size / 1024);
249         } else if (size < 1024 * 1024 * 1024LL / 2) {
250                 snprintf(buf, sizeof(buf), "%6.2fMB",
251                         (double)size / (1024 * 1024));
252         } else if (size < 1024 * 1024 * 1024LL * 1024LL / 2) {
253                 snprintf(buf, sizeof(buf), "%6.2fGB",
254                         (double)size / (1024 * 1024 * 1024LL));
255         } else {
256                 snprintf(buf, sizeof(buf), "%6.2fTB",
257                         (double)size / (1024 * 1024 * 1024LL * 1024LL));
258         }
259         return(buf);
260 }
261
262 /*
263  * Convert a string to a 64 bit signed integer with various requirements.
264  */
265 static int64_t
266 getsize(const char *str, int64_t minval, int64_t maxval, int powerof2)
267 {
268         int64_t val;
269         char *ptr;
270
271         val = strtoll(str, &ptr, 0);
272         switch(*ptr) {
273         case 't':
274         case 'T':
275                 val *= 1024;
276                 /* fall through */
277         case 'g':
278         case 'G':
279                 val *= 1024;
280                 /* fall through */
281         case 'm':
282         case 'M':
283                 val *= 1024;
284                 /* fall through */
285         case 'k':
286         case 'K':
287                 val *= 1024;
288                 break;
289         default:
290                 errx(1, "Unknown suffix in number '%s'\n", str);
291                 /* not reached */
292         }
293         if (ptr[1]) {
294                 errx(1, "Unknown suffix in number '%s'\n", str);
295                 /* not reached */
296         }
297         if (val < minval) {
298                 errx(1, "Value too small: %s, min is %s\n",
299                      str, sizetostr(minval));
300                 /* not reached */
301         }
302         if (val > maxval) {
303                 errx(1, "Value too large: %s, max is %s\n",
304                      str, sizetostr(maxval));
305                 /* not reached */
306         }
307         if ((powerof2 & 1) && (val ^ (val - 1)) != ((val << 1) - 1)) {
308                 errx(1, "Value not power of 2: %s\n", str);
309                 /* not reached */
310         }
311         if ((powerof2 & 2) && (val & HAMMER_BUFMASK)) {
312                 errx(1, "Value not an integral multiple of %dK: %s", 
313                      HAMMER_BUFSIZE / 1024, str);
314                 /* not reached */
315         }
316         return(val);
317 }
318
319 /*
320  * Generate a transaction id.  Transaction ids are no longer time-based.
321  * Put the nail in the coffin by not making the first one time-based.
322  *
323  * We could start at 1 here but start at 2^32 to reserve a small domain for
324  * possible future use.
325  */
326 static hammer_tid_t
327 createtid(void)
328 {
329         static hammer_tid_t lasttid;
330
331         if (lasttid == 0)
332                 lasttid = 0x0000000100000000ULL;
333         return(lasttid++);
334 }
335
336 static u_int64_t
337 nowtime(void)
338 {
339         struct timeval tv;
340         u_int64_t xtime;
341
342         gettimeofday(&tv, NULL);
343         xtime = tv.tv_sec * 1000000LL + tv.tv_usec;
344         return(xtime);
345 }
346
347 /*
348  * Check basic volume characteristics.  HAMMER filesystems use a minimum
349  * of a 16KB filesystem buffer size.
350  */
351 static
352 void
353 check_volume(struct volume_info *vol)
354 {
355         struct partinfo pinfo;
356         struct stat st;
357
358         /*
359          * Get basic information about the volume
360          */
361         vol->fd = open(vol->name, O_RDWR);
362         if (vol->fd < 0)
363                 err(1, "Unable to open %s R+W", vol->name);
364         if (ioctl(vol->fd, DIOCGPART, &pinfo) < 0) {
365                 /*
366                  * Allow the formatting of regular files as HAMMER volumes
367                  */
368                 if (fstat(vol->fd, &st) < 0)
369                         err(1, "Unable to stat %s", vol->name);
370                 vol->size = st.st_size;
371                 vol->type = "REGFILE";
372         } else {
373                 /*
374                  * When formatting a block device as a HAMMER volume the
375                  * sector size must be compatible.  HAMMER uses 16384 byte
376                  * filesystem buffers.
377                  */
378                 if (pinfo.reserved_blocks) {
379                         errx(1, "HAMMER cannot be placed in a partition "
380                                 "which overlaps the disklabel or MBR");
381                 }
382                 if (pinfo.media_blksize > 16384 ||
383                     16384 % pinfo.media_blksize) {
384                         errx(1, "A media sector size of %d is not supported",
385                              pinfo.media_blksize);
386                 }
387
388                 vol->size = pinfo.media_size;
389                 vol->type = "DEVICE";
390         }
391         printf("Volume %d %s %-15s size %s\n",
392                vol->vol_no, vol->type, vol->name,
393                sizetostr(vol->size));
394
395         /*
396          * Reserve space for (future) header junk, setup our poor-man's
397          * bigblock allocator.
398          */
399         vol->vol_alloc = HAMMER_BUFSIZE * 16;
400 }
401
402 /*
403  * Format a HAMMER volume.  Cluster 0 will be initially placed in volume 0.
404  */
405 static
406 void
407 format_volume(struct volume_info *vol, int nvols, const char *label,
408               off_t total_size __unused)
409 {
410         struct volume_info *root_vol;
411         struct hammer_volume_ondisk *ondisk;
412         int64_t freeblks;
413         int64_t freebytes;
414         int i;
415
416         /*
417          * Initialize basic information in the on-disk volume structure.
418          */
419         ondisk = vol->ondisk;
420
421         ondisk->vol_fsid = Hammer_FSId;
422         ondisk->vol_fstype = Hammer_FSType;
423         snprintf(ondisk->vol_name, sizeof(ondisk->vol_name), "%s", label);
424         ondisk->vol_no = vol->vol_no;
425         ondisk->vol_count = nvols;
426         ondisk->vol_version = HAMMER_VOL_VERSION_DEFAULT;
427
428         ondisk->vol_bot_beg = vol->vol_alloc;
429         vol->vol_alloc += BootAreaSize;
430         ondisk->vol_mem_beg = vol->vol_alloc;
431         vol->vol_alloc += MemAreaSize;
432
433         /*
434          * The remaining area is the zone 2 buffer allocation area.  These
435          * buffers
436          */
437         ondisk->vol_buf_beg = vol->vol_alloc;
438         ondisk->vol_buf_end = vol->size & ~(int64_t)HAMMER_BUFMASK;
439
440         if (ondisk->vol_buf_end < ondisk->vol_buf_beg) {
441                 errx(1, "volume %d %s is too small to hold the volume header",
442                      vol->vol_no, vol->name);
443         }
444
445         ondisk->vol_nblocks = (ondisk->vol_buf_end - ondisk->vol_buf_beg) /
446                               HAMMER_BUFSIZE;
447         ondisk->vol_blocksize = HAMMER_BUFSIZE;
448
449         ondisk->vol_rootvol = RootVolNo;
450         ondisk->vol_signature = HAMMER_FSBUF_VOLUME;
451
452         vol->vol_free_off = HAMMER_ENCODE_RAW_BUFFER(vol->vol_no, 0);
453         vol->vol_free_end = HAMMER_ENCODE_RAW_BUFFER(vol->vol_no, (ondisk->vol_buf_end - ondisk->vol_buf_beg) & ~HAMMER_LARGEBLOCK_MASK64);
454
455         /*
456          * Format the root volume.
457          */
458         if (vol->vol_no == RootVolNo) {
459                 /*
460                  * Starting TID
461                  */
462                 ondisk->vol0_next_tid = createtid();
463
464                 format_freemap(vol,
465                         &ondisk->vol0_blockmap[HAMMER_ZONE_FREEMAP_INDEX]);
466
467                 freeblks = initialize_freemap(vol);
468                 ondisk->vol0_stat_freebigblocks = freeblks;
469
470                 freebytes = freeblks * HAMMER_LARGEBLOCK_SIZE64;
471                 if (freebytes < 1*GIG && ForceOpt == 0) {
472                         errx(1, "Cannot create a HAMMER filesystem less than "
473                                 "1GB unless you use -f.  HAMMER filesystems\n"
474                                 "less than 50G are not recommended\n");
475                 }
476                         
477                 for (i = 8; i < HAMMER_MAX_ZONES; ++i) {
478                         format_blockmap(&ondisk->vol0_blockmap[i],
479                                         HAMMER_ZONE_ENCODE(i, 0));
480                 }
481                 format_undomap(ondisk);
482
483                 ondisk->vol0_btree_root = format_root(label);
484                 ++ondisk->vol0_stat_inodes;     /* root inode */
485         } else {
486                 freeblks = initialize_freemap(vol);
487                 root_vol = get_volume(RootVolNo);
488                 root_vol->cache.modified = 1;
489                 root_vol->ondisk->vol0_stat_freebigblocks += freeblks;
490                 root_vol->ondisk->vol0_stat_bigblocks += freeblks;
491                 rel_volume(root_vol);
492         }
493 }
494
495 /*
496  * Format the root directory.
497  */
498 static
499 hammer_off_t
500 format_root(const char *label)
501 {
502         hammer_off_t btree_off;
503         hammer_off_t pfsd_off;
504         hammer_off_t data_off;
505         hammer_tid_t create_tid;
506         hammer_node_ondisk_t bnode;
507         struct hammer_inode_data *idata;
508         hammer_pseudofs_data_t pfsd;
509         struct buffer_info *data_buffer1 = NULL;
510         struct buffer_info *data_buffer2 = NULL;
511         hammer_btree_elm_t elm;
512         u_int64_t xtime;
513
514         bnode = alloc_btree_element(&btree_off);
515         idata = alloc_data_element(&data_off, sizeof(*idata), &data_buffer1);
516         pfsd = alloc_data_element(&pfsd_off, sizeof(*pfsd), &data_buffer2);
517         create_tid = createtid();
518         xtime = nowtime();
519
520         /*
521          * Populate the inode data and inode record for the root directory.
522          */
523         idata->version = HAMMER_INODE_DATA_VERSION;
524         idata->mode = 0755;
525         idata->ctime = xtime;
526         idata->mtime = xtime;
527         idata->atime = xtime;
528         idata->obj_type = HAMMER_OBJTYPE_DIRECTORY;
529         idata->size = 0;
530         idata->nlinks = 1;
531
532         pfsd->sync_low_tid = 1;
533         pfsd->sync_beg_tid = 0;
534         pfsd->sync_end_tid = 0; /* overriden by vol0_next_tid on pfs0 */
535         pfsd->shared_uuid = Hammer_FSId;
536         pfsd->unique_uuid = Hammer_FSId;
537         pfsd->reserved01 = 0;
538         pfsd->mirror_flags = 0;
539         snprintf(pfsd->label, sizeof(pfsd->label), "%s", label);
540
541         /*
542          * Create the root of the B-Tree.  The root is a leaf node so we
543          * do not have to worry about boundary elements.
544          */
545         bnode->signature = HAMMER_BTREE_SIGNATURE_GOOD;
546         bnode->count = 2;
547         bnode->type = HAMMER_BTREE_TYPE_LEAF;
548
549         elm = &bnode->elms[0];
550         elm->leaf.base.btype = HAMMER_BTREE_TYPE_RECORD;
551         elm->leaf.base.localization = HAMMER_LOCALIZE_INODE;
552         elm->leaf.base.obj_id = HAMMER_OBJID_ROOT;
553         elm->leaf.base.key = 0;
554         elm->leaf.base.create_tid = create_tid;
555         elm->leaf.base.delete_tid = 0;
556         elm->leaf.base.rec_type = HAMMER_RECTYPE_INODE;
557         elm->leaf.base.obj_type = HAMMER_OBJTYPE_DIRECTORY;
558         elm->leaf.create_ts = (u_int32_t)time(NULL);
559
560         elm->leaf.data_offset = data_off;
561         elm->leaf.data_len = sizeof(*idata);
562         elm->leaf.data_crc = crc32(idata, HAMMER_INODE_CRCSIZE);
563
564         elm = &bnode->elms[1];
565         elm->leaf.base.btype = HAMMER_BTREE_TYPE_RECORD;
566         elm->leaf.base.localization = HAMMER_LOCALIZE_MISC;
567         elm->leaf.base.obj_id = HAMMER_OBJID_ROOT;
568         elm->leaf.base.key = 0;
569         elm->leaf.base.create_tid = create_tid;
570         elm->leaf.base.delete_tid = 0;
571         elm->leaf.base.rec_type = HAMMER_RECTYPE_PFS;
572         elm->leaf.base.obj_type = 0;
573         elm->leaf.create_ts = (u_int32_t)time(NULL);
574
575         elm->leaf.data_offset = pfsd_off;
576         elm->leaf.data_len = sizeof(*pfsd);
577         elm->leaf.data_crc = crc32(pfsd, sizeof(*pfsd));
578
579         bnode->crc = crc32(&bnode->crc + 1, HAMMER_BTREE_CRCSIZE);
580
581         return(btree_off);
582 }
583