Merge branches 'master' and 'suser_to_priv'
[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 to set up a cron job to prune and\n"
208                 "reblock the filesystem regularly, see 'man hammer' for\n"
209                 "more information.\n");
210         if (total < 50*GIG) {
211                 printf("\nWARNING: HAMMER filesystems less than 50G are "
212                         "not recommended!\n"
213                         "You may have to run 'hammer prune-everything' and "
214                         "'hammer reblock'\n"
215                         "quite often, even if using a nohistory mount.\n");
216         }
217         flush_all_volumes();
218         return(0);
219 }
220
221 static
222 void
223 usage(void)
224 {
225         fprintf(stderr,
226                 "newfs_hammer -L label [-b bootsize] [-m savesize] [-u undosize] "
227                         "special ...\n"
228         );
229         exit(1);
230 }
231
232 /*
233  * Convert the size in bytes to a human readable string.
234  */
235 static
236 const char *
237 sizetostr(off_t size)
238 {
239         static char buf[32];
240
241         if (size < 1024 / 2) {
242                 snprintf(buf, sizeof(buf), "%6.2f", (double)size);
243         } else if (size < 1024 * 1024 / 2) {
244                 snprintf(buf, sizeof(buf), "%6.2fKB",
245                         (double)size / 1024);
246         } else if (size < 1024 * 1024 * 1024LL / 2) {
247                 snprintf(buf, sizeof(buf), "%6.2fMB",
248                         (double)size / (1024 * 1024));
249         } else if (size < 1024 * 1024 * 1024LL * 1024LL / 2) {
250                 snprintf(buf, sizeof(buf), "%6.2fGB",
251                         (double)size / (1024 * 1024 * 1024LL));
252         } else {
253                 snprintf(buf, sizeof(buf), "%6.2fTB",
254                         (double)size / (1024 * 1024 * 1024LL * 1024LL));
255         }
256         return(buf);
257 }
258
259 /*
260  * Convert a string to a 64 bit signed integer with various requirements.
261  */
262 static int64_t
263 getsize(const char *str, int64_t minval, int64_t maxval, int powerof2)
264 {
265         int64_t val;
266         char *ptr;
267
268         val = strtoll(str, &ptr, 0);
269         switch(*ptr) {
270         case 't':
271         case 'T':
272                 val *= 1024;
273                 /* fall through */
274         case 'g':
275         case 'G':
276                 val *= 1024;
277                 /* fall through */
278         case 'm':
279         case 'M':
280                 val *= 1024;
281                 /* fall through */
282         case 'k':
283         case 'K':
284                 val *= 1024;
285                 break;
286         default:
287                 errx(1, "Unknown suffix in number '%s'\n", str);
288                 /* not reached */
289         }
290         if (ptr[1]) {
291                 errx(1, "Unknown suffix in number '%s'\n", str);
292                 /* not reached */
293         }
294         if (val < minval) {
295                 errx(1, "Value too small: %s, min is %s\n",
296                      str, sizetostr(minval));
297                 /* not reached */
298         }
299         if (val > maxval) {
300                 errx(1, "Value too large: %s, max is %s\n",
301                      str, sizetostr(maxval));
302                 /* not reached */
303         }
304         if ((powerof2 & 1) && (val ^ (val - 1)) != ((val << 1) - 1)) {
305                 errx(1, "Value not power of 2: %s\n", str);
306                 /* not reached */
307         }
308         if ((powerof2 & 2) && (val & HAMMER_BUFMASK)) {
309                 errx(1, "Value not an integral multiple of %dK: %s", 
310                      HAMMER_BUFSIZE / 1024, str);
311                 /* not reached */
312         }
313         return(val);
314 }
315
316 /*
317  * Generate a transaction id.  Transaction ids are no longer time-based.
318  * Put the nail in the coffin by not making the first one time-based.
319  *
320  * We could start at 1 here but start at 2^32 to reserve a small domain for
321  * possible future use.
322  */
323 static hammer_tid_t
324 createtid(void)
325 {
326         static hammer_tid_t lasttid;
327
328         if (lasttid == 0)
329                 lasttid = 0x0000000100000000ULL;
330         return(lasttid++);
331 }
332
333 static u_int64_t
334 nowtime(void)
335 {
336         struct timeval tv;
337         u_int64_t xtime;
338
339         gettimeofday(&tv, NULL);
340         xtime = tv.tv_sec * 1000000LL + tv.tv_usec;
341         return(xtime);
342 }
343
344 /*
345  * Check basic volume characteristics.  HAMMER filesystems use a minimum
346  * of a 16KB filesystem buffer size.
347  */
348 static
349 void
350 check_volume(struct volume_info *vol)
351 {
352         struct partinfo pinfo;
353         struct stat st;
354
355         /*
356          * Get basic information about the volume
357          */
358         vol->fd = open(vol->name, O_RDWR);
359         if (vol->fd < 0)
360                 err(1, "Unable to open %s R+W", vol->name);
361         if (ioctl(vol->fd, DIOCGPART, &pinfo) < 0) {
362                 /*
363                  * Allow the formatting of regular files as HAMMER volumes
364                  */
365                 if (fstat(vol->fd, &st) < 0)
366                         err(1, "Unable to stat %s", vol->name);
367                 vol->size = st.st_size;
368                 vol->type = "REGFILE";
369         } else {
370                 /*
371                  * When formatting a block device as a HAMMER volume the
372                  * sector size must be compatible.  HAMMER uses 16384 byte
373                  * filesystem buffers.
374                  */
375                 if (pinfo.reserved_blocks) {
376                         errx(1, "HAMMER cannot be placed in a partition "
377                                 "which overlaps the disklabel or MBR");
378                 }
379                 if (pinfo.media_blksize > 16384 ||
380                     16384 % pinfo.media_blksize) {
381                         errx(1, "A media sector size of %d is not supported",
382                              pinfo.media_blksize);
383                 }
384
385                 vol->size = pinfo.media_size;
386                 vol->type = "DEVICE";
387         }
388         printf("Volume %d %s %-15s size %s\n",
389                vol->vol_no, vol->type, vol->name,
390                sizetostr(vol->size));
391
392         /*
393          * Reserve space for (future) header junk, setup our poor-man's
394          * bigblock allocator.
395          */
396         vol->vol_alloc = HAMMER_BUFSIZE * 16;
397 }
398
399 /*
400  * Format a HAMMER volume.  Cluster 0 will be initially placed in volume 0.
401  */
402 static
403 void
404 format_volume(struct volume_info *vol, int nvols, const char *label,
405               off_t total_size __unused)
406 {
407         struct volume_info *root_vol;
408         struct hammer_volume_ondisk *ondisk;
409         int64_t freeblks;
410         int64_t freebytes;
411         int i;
412
413         /*
414          * Initialize basic information in the on-disk volume structure.
415          */
416         ondisk = vol->ondisk;
417
418         ondisk->vol_fsid = Hammer_FSId;
419         ondisk->vol_fstype = Hammer_FSType;
420         snprintf(ondisk->vol_name, sizeof(ondisk->vol_name), "%s", label);
421         ondisk->vol_no = vol->vol_no;
422         ondisk->vol_count = nvols;
423         ondisk->vol_version = HAMMER_VOL_VERSION_DEFAULT;
424
425         ondisk->vol_bot_beg = vol->vol_alloc;
426         vol->vol_alloc += BootAreaSize;
427         ondisk->vol_mem_beg = vol->vol_alloc;
428         vol->vol_alloc += MemAreaSize;
429
430         /*
431          * The remaining area is the zone 2 buffer allocation area.  These
432          * buffers
433          */
434         ondisk->vol_buf_beg = vol->vol_alloc;
435         ondisk->vol_buf_end = vol->size & ~(int64_t)HAMMER_BUFMASK;
436
437         if (ondisk->vol_buf_end < ondisk->vol_buf_beg) {
438                 errx(1, "volume %d %s is too small to hold the volume header",
439                      vol->vol_no, vol->name);
440         }
441
442         ondisk->vol_nblocks = (ondisk->vol_buf_end - ondisk->vol_buf_beg) /
443                               HAMMER_BUFSIZE;
444         ondisk->vol_blocksize = HAMMER_BUFSIZE;
445
446         ondisk->vol_rootvol = RootVolNo;
447         ondisk->vol_signature = HAMMER_FSBUF_VOLUME;
448
449         vol->vol_free_off = HAMMER_ENCODE_RAW_BUFFER(vol->vol_no, 0);
450         vol->vol_free_end = HAMMER_ENCODE_RAW_BUFFER(vol->vol_no, (ondisk->vol_buf_end - ondisk->vol_buf_beg) & ~HAMMER_LARGEBLOCK_MASK64);
451
452         /*
453          * Format the root volume.
454          */
455         if (vol->vol_no == RootVolNo) {
456                 /*
457                  * Starting TID
458                  */
459                 ondisk->vol0_next_tid = createtid();
460
461                 format_freemap(vol,
462                         &ondisk->vol0_blockmap[HAMMER_ZONE_FREEMAP_INDEX]);
463
464                 freeblks = initialize_freemap(vol);
465                 ondisk->vol0_stat_freebigblocks = freeblks;
466
467                 freebytes = freeblks * HAMMER_LARGEBLOCK_SIZE64;
468                 if (freebytes < 1*GIG && ForceOpt == 0) {
469                         errx(1, "Cannot create a HAMMER filesystem less than "
470                                 "1GB unless you use -f.  HAMMER filesystems\n"
471                                 "less than 50G are not recommended\n");
472                 }
473                         
474                 for (i = 8; i < HAMMER_MAX_ZONES; ++i) {
475                         format_blockmap(&ondisk->vol0_blockmap[i],
476                                         HAMMER_ZONE_ENCODE(i, 0));
477                 }
478                 format_undomap(ondisk);
479
480                 ondisk->vol0_btree_root = format_root(label);
481                 ++ondisk->vol0_stat_inodes;     /* root inode */
482         } else {
483                 freeblks = initialize_freemap(vol);
484                 root_vol = get_volume(RootVolNo);
485                 root_vol->cache.modified = 1;
486                 root_vol->ondisk->vol0_stat_freebigblocks += freeblks;
487                 root_vol->ondisk->vol0_stat_bigblocks += freeblks;
488                 rel_volume(root_vol);
489         }
490 }
491
492 /*
493  * Format the root directory.
494  */
495 static
496 hammer_off_t
497 format_root(const char *label)
498 {
499         hammer_off_t btree_off;
500         hammer_off_t pfsd_off;
501         hammer_off_t data_off;
502         hammer_tid_t create_tid;
503         hammer_node_ondisk_t bnode;
504         struct hammer_inode_data *idata;
505         hammer_pseudofs_data_t pfsd;
506         struct buffer_info *data_buffer1 = NULL;
507         struct buffer_info *data_buffer2 = NULL;
508         hammer_btree_elm_t elm;
509         u_int64_t xtime;
510
511         bnode = alloc_btree_element(&btree_off);
512         idata = alloc_data_element(&data_off, sizeof(*idata), &data_buffer1);
513         pfsd = alloc_data_element(&pfsd_off, sizeof(*pfsd), &data_buffer2);
514         create_tid = createtid();
515         xtime = nowtime();
516
517         /*
518          * Populate the inode data and inode record for the root directory.
519          */
520         idata->version = HAMMER_INODE_DATA_VERSION;
521         idata->mode = 0755;
522         idata->ctime = xtime;
523         idata->mtime = xtime;
524         idata->atime = xtime;
525         idata->obj_type = HAMMER_OBJTYPE_DIRECTORY;
526         idata->size = 0;
527         idata->nlinks = 1;
528
529         pfsd->sync_low_tid = 1;
530         pfsd->sync_beg_tid = 0;
531         pfsd->sync_end_tid = 0; /* overriden by vol0_next_tid on pfs0 */
532         pfsd->shared_uuid = Hammer_FSId;
533         pfsd->unique_uuid = Hammer_FSId;
534         pfsd->reserved01 = 0;
535         pfsd->mirror_flags = 0;
536         snprintf(pfsd->label, sizeof(pfsd->label), "%s", label);
537
538         /*
539          * Create the root of the B-Tree.  The root is a leaf node so we
540          * do not have to worry about boundary elements.
541          */
542         bnode->signature = HAMMER_BTREE_SIGNATURE_GOOD;
543         bnode->count = 2;
544         bnode->type = HAMMER_BTREE_TYPE_LEAF;
545
546         elm = &bnode->elms[0];
547         elm->leaf.base.btype = HAMMER_BTREE_TYPE_RECORD;
548         elm->leaf.base.localization = HAMMER_LOCALIZE_INODE;
549         elm->leaf.base.obj_id = HAMMER_OBJID_ROOT;
550         elm->leaf.base.key = 0;
551         elm->leaf.base.create_tid = create_tid;
552         elm->leaf.base.delete_tid = 0;
553         elm->leaf.base.rec_type = HAMMER_RECTYPE_INODE;
554         elm->leaf.base.obj_type = HAMMER_OBJTYPE_DIRECTORY;
555         elm->leaf.create_ts = (u_int32_t)time(NULL);
556
557         elm->leaf.data_offset = data_off;
558         elm->leaf.data_len = sizeof(*idata);
559         elm->leaf.data_crc = crc32(idata, HAMMER_INODE_CRCSIZE);
560
561         elm = &bnode->elms[1];
562         elm->leaf.base.btype = HAMMER_BTREE_TYPE_RECORD;
563         elm->leaf.base.localization = HAMMER_LOCALIZE_MISC;
564         elm->leaf.base.obj_id = HAMMER_OBJID_ROOT;
565         elm->leaf.base.key = 0;
566         elm->leaf.base.create_tid = create_tid;
567         elm->leaf.base.delete_tid = 0;
568         elm->leaf.base.rec_type = HAMMER_RECTYPE_PFS;
569         elm->leaf.base.obj_type = 0;
570         elm->leaf.create_ts = (u_int32_t)time(NULL);
571
572         elm->leaf.data_offset = pfsd_off;
573         elm->leaf.data_len = sizeof(*pfsd);
574         elm->leaf.data_crc = crc32(pfsd, sizeof(*pfsd));
575
576         bnode->crc = crc32(&bnode->crc + 1, HAMMER_BTREE_CRCSIZE);
577
578         return(btree_off);
579 }
580