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 
584