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 
581