Use newly available libc and system calls related to statvfs to make df
[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.26 2008/05/18 01:49:44 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(struct hammer_blockmap_layer1) == 32);
63         assert(sizeof(struct hammer_blockmap_layer2) == 16);
64
65         /*
66          * Generate a filesysem id and lookup the filesystem type
67          */
68         uuidgen(&Hammer_FSId, 1);
69         uuid_name_lookup(&Hammer_FSType, "DragonFly HAMMER", &status);
70         if (status != uuid_s_ok) {
71                 errx(1, "uuids file does not have the DragonFly "
72                         "HAMMER filesystem type");
73         }
74
75         /*
76          * Parse arguments
77          */
78         while ((ch = getopt(ac, av, "L:b:m:u:")) != -1) {
79                 switch(ch) {
80                 case 'L':
81                         label = optarg;
82                         break;
83                 case 'b':
84                         BootAreaSize = getsize(optarg,
85                                          HAMMER_BUFSIZE,
86                                          HAMMER_BOOT_MAXBYTES, 2);
87                         break;
88                 case 'm':
89                         MemAreaSize = getsize(optarg,
90                                          HAMMER_BUFSIZE,
91                                          HAMMER_MEM_MAXBYTES, 2);
92                         break;
93                 case 'u':
94                         UndoBufferSize = getsize(optarg,
95                                          HAMMER_LARGEBLOCK_SIZE,
96                                          HAMMER_LARGEBLOCK_SIZE *
97                                          HAMMER_UNDO_LAYER2, 2);
98                         break;
99                 default:
100                         usage();
101                         break;
102                 }
103         }
104
105         if (label == NULL) {
106                 fprintf(stderr,
107                         "newfs_hammer: A filesystem label must be specified\n");
108                 exit(1);
109         }
110
111         /*
112          * Collect volume information
113          */
114         ac -= optind;
115         av += optind;
116         NumVolumes = ac;
117         RootVolNo = 0;
118
119         total = 0;
120         for (i = 0; i < NumVolumes; ++i) {
121                 vol = setup_volume(i, av[i], 1, O_RDWR);
122
123                 /*
124                  * Load up information on the volume and initialize
125                  * its remaining fields.
126                  */
127                 check_volume(vol);
128                 total += vol->size;
129         }
130
131         /*
132          * Calculate defaults for the boot and memory area sizes.
133          */
134         if (BootAreaSize == 0) {
135                 BootAreaSize = HAMMER_BOOT_NOMBYTES;
136                 while (BootAreaSize > total / NumVolumes / 256)
137                         BootAreaSize >>= 1;
138                 if (BootAreaSize < HAMMER_BOOT_MINBYTES)
139                         BootAreaSize = 0;
140         } else if (BootAreaSize < HAMMER_BOOT_MINBYTES) {
141                 BootAreaSize = HAMMER_BOOT_MINBYTES;
142         }
143         if (MemAreaSize == 0) {
144                 MemAreaSize = HAMMER_MEM_NOMBYTES;
145                 while (MemAreaSize > total / NumVolumes / 256)
146                         MemAreaSize >>= 1;
147                 if (MemAreaSize < HAMMER_MEM_MINBYTES)
148                         MemAreaSize = 0;
149         } else if (MemAreaSize < HAMMER_MEM_MINBYTES) {
150                 MemAreaSize = HAMMER_MEM_MINBYTES;
151         }
152
153         /*
154          * Format the volumes.  Format the root volume first so we can
155          * bootstrap the freemap.
156          */
157         format_volume(get_volume(RootVolNo), NumVolumes, label, total);
158         for (i = 0; i < NumVolumes; ++i) {
159                 if (i != RootVolNo)
160                         format_volume(get_volume(i), NumVolumes, label, total);
161         }
162         printf("---------------------------------------------\n");
163         printf("%d volume%s total size %s\n",
164                 NumVolumes, (NumVolumes == 1 ? "" : "s"), sizetostr(total));
165         printf("boot-area-size:      %s\n", sizetostr(BootAreaSize));
166         printf("memory-log-size:     %s\n", sizetostr(MemAreaSize));
167         printf("undo-buffer-size:    %s\n", sizetostr(UndoBufferSize));
168
169         vol = get_volume(RootVolNo);
170         printf("zone-limit:          %s\n", sizetostr(vol->ondisk->vol0_zone_limit));
171         printf("\n");
172
173         flush_all_volumes();
174         return(0);
175 }
176
177 static
178 void
179 usage(void)
180 {
181         fprintf(stderr, "newfs_hammer vol0 [vol1 ...]\n");
182         exit(1);
183 }
184
185 /*
186  * Convert the size in bytes to a human readable string.
187  */
188 static
189 const char *
190 sizetostr(off_t size)
191 {
192         static char buf[32];
193
194         if (size < 1024 / 2) {
195                 snprintf(buf, sizeof(buf), "%6.2f", (double)size);
196         } else if (size < 1024 * 1024 / 2) {
197                 snprintf(buf, sizeof(buf), "%6.2fKB",
198                         (double)size / 1024);
199         } else if (size < 1024 * 1024 * 1024LL / 2) {
200                 snprintf(buf, sizeof(buf), "%6.2fMB",
201                         (double)size / (1024 * 1024));
202         } else if (size < 1024 * 1024 * 1024LL * 1024LL / 2) {
203                 snprintf(buf, sizeof(buf), "%6.2fGB",
204                         (double)size / (1024 * 1024 * 1024LL));
205         } else {
206                 snprintf(buf, sizeof(buf), "%6.2fTB",
207                         (double)size / (1024 * 1024 * 1024LL * 1024LL));
208         }
209         return(buf);
210 }
211
212 /*
213  * Convert a string to a 64 bit signed integer with various requirements.
214  */
215 static int64_t
216 getsize(const char *str, int64_t minval, int64_t maxval, int powerof2)
217 {
218         int64_t val;
219         char *ptr;
220
221         val = strtoll(str, &ptr, 0);
222         switch(*ptr) {
223         case 't':
224         case 'T':
225                 val *= 1024;
226                 /* fall through */
227         case 'g':
228         case 'G':
229                 val *= 1024;
230                 /* fall through */
231         case 'm':
232         case 'M':
233                 val *= 1024;
234                 /* fall through */
235         case 'k':
236         case 'K':
237                 val *= 1024;
238                 break;
239         default:
240                 errx(1, "Unknown suffix in number '%s'\n", str);
241                 /* not reached */
242         }
243         if (ptr[1]) {
244                 errx(1, "Unknown suffix in number '%s'\n", str);
245                 /* not reached */
246         }
247         if (val < minval) {
248                 errx(1, "Value too small: %s, min is %s\n",
249                      str, sizetostr(minval));
250                 /* not reached */
251         }
252         if (val > maxval) {
253                 errx(1, "Value too large: %s, max is %s\n",
254                      str, sizetostr(maxval));
255                 /* not reached */
256         }
257         if ((powerof2 & 1) && (val ^ (val - 1)) != ((val << 1) - 1)) {
258                 errx(1, "Value not power of 2: %s\n", str);
259                 /* not reached */
260         }
261         if ((powerof2 & 2) && (val & HAMMER_BUFMASK)) {
262                 errx(1, "Value not an integral multiple of %dK: %s", 
263                      HAMMER_BUFSIZE / 1024, str);
264                 /* not reached */
265         }
266         return(val);
267 }
268
269 /*
270  * Generate a transaction id
271  */
272 static hammer_tid_t
273 createtid(void)
274 {
275         static hammer_tid_t lasttid;
276         struct timeval tv;
277
278         if (lasttid == 0) {
279                 gettimeofday(&tv, NULL);
280                 lasttid = tv.tv_sec * 1000000000LL +
281                           tv.tv_usec * 1000LL;
282         }
283         return(lasttid++);
284 }
285
286 /*
287  * Check basic volume characteristics.  HAMMER filesystems use a minimum
288  * of a 16KB filesystem buffer size.
289  */
290 static
291 void
292 check_volume(struct volume_info *vol)
293 {
294         struct partinfo pinfo;
295         struct stat st;
296
297         /*
298          * Get basic information about the volume
299          */
300         vol->fd = open(vol->name, O_RDWR);
301         if (vol->fd < 0)
302                 err(1, "Unable to open %s R+W", vol->name);
303         if (ioctl(vol->fd, DIOCGPART, &pinfo) < 0) {
304                 /*
305                  * Allow the formatting of regular filews as HAMMER volumes
306                  */
307                 if (fstat(vol->fd, &st) < 0)
308                         err(1, "Unable to stat %s", vol->name);
309                 vol->size = st.st_size;
310                 vol->type = "REGFILE";
311         } else {
312                 /*
313                  * When formatting a block device as a HAMMER volume the
314                  * sector size must be compatible.  HAMMER uses 16384 byte
315                  * filesystem buffers.
316                  */
317                 if (pinfo.reserved_blocks) {
318                         errx(1, "HAMMER cannot be placed in a partition "
319                                 "which overlaps the disklabel or MBR");
320                 }
321                 if (pinfo.media_blksize > 16384 ||
322                     16384 % pinfo.media_blksize) {
323                         errx(1, "A media sector size of %d is not supported",
324                              pinfo.media_blksize);
325                 }
326
327                 vol->size = pinfo.media_size;
328                 vol->type = "DEVICE";
329         }
330         printf("Volume %d %s %-15s size %s\n",
331                vol->vol_no, vol->type, vol->name,
332                sizetostr(vol->size));
333
334         /*
335          * Reserve space for (future) header junk, setup our poor-man's
336          * bigblock allocator.
337          */
338         vol->vol_alloc = HAMMER_BUFSIZE * 16;
339 }
340
341 /*
342  * Format a HAMMER volume.  Cluster 0 will be initially placed in volume 0.
343  */
344 static
345 void
346 format_volume(struct volume_info *vol, int nvols, const char *label,
347               off_t total_size)
348 {
349         struct volume_info *root_vol;
350         struct hammer_volume_ondisk *ondisk;
351         int64_t freeblks;
352
353         /*
354          * Initialize basic information in the on-disk volume structure.
355          */
356         ondisk = vol->ondisk;
357
358         ondisk->vol_fsid = Hammer_FSId;
359         ondisk->vol_fstype = Hammer_FSType;
360         snprintf(ondisk->vol_name, sizeof(ondisk->vol_name), "%s", label);
361         ondisk->vol_no = vol->vol_no;
362         ondisk->vol_count = nvols;
363         ondisk->vol_version = 1;
364
365         ondisk->vol_bot_beg = vol->vol_alloc;
366         vol->vol_alloc += BootAreaSize;
367         ondisk->vol_mem_beg = vol->vol_alloc;
368         vol->vol_alloc += MemAreaSize;
369
370         /*
371          * The remaining area is the zone 2 buffer allocation area.  These
372          * buffers
373          */
374         ondisk->vol_buf_beg = vol->vol_alloc;
375         ondisk->vol_buf_end = vol->size & ~(int64_t)HAMMER_BUFMASK;
376
377         if (ondisk->vol_buf_end < ondisk->vol_buf_beg) {
378                 errx(1, "volume %d %s is too small to hold the volume header",
379                      vol->vol_no, vol->name);
380         }
381
382         ondisk->vol_nblocks = (ondisk->vol_buf_end - ondisk->vol_buf_beg) /
383                               HAMMER_BUFSIZE;
384         ondisk->vol_blocksize = HAMMER_BUFSIZE;
385
386         ondisk->vol_rootvol = RootVolNo;
387         ondisk->vol_signature = HAMMER_FSBUF_VOLUME;
388
389         vol->vol_free_off = HAMMER_ENCODE_RAW_BUFFER(vol->vol_no, 0);
390         vol->vol_free_end = HAMMER_ENCODE_RAW_BUFFER(vol->vol_no, (ondisk->vol_buf_end - ondisk->vol_buf_beg) & ~HAMMER_LARGEBLOCK_MASK64);
391
392         /*
393          * Format the root volume.
394          */
395         if (vol->vol_no == RootVolNo) {
396                 hammer_off_t zone_limit;
397
398                 /*
399                  * Starting TID
400                  */
401                 ondisk->vol0_next_tid = createtid();
402
403                 /*
404                  * Set the default zone limit to 100x the size of the
405                  * filesystem.  We do not want to create a huge zone limit
406                  * for tiny filesystems because the blockmap could wind up
407                  * getting fragmented and eating a large chunk of the disk
408                  * space.
409                  */
410                 zone_limit = (hammer_off_t)total_size * 100;
411                 zone_limit = (zone_limit + HAMMER_BLOCKMAP_LAYER2_MASK) &
412                              ~HAMMER_BLOCKMAP_LAYER2_MASK;
413                 if (zone_limit < (hammer_off_t)total_size ||
414                     zone_limit > HAMMER_ZONE_LIMIT) {
415                         zone_limit = HAMMER_ZONE_LIMIT;
416                 }
417                 ondisk->vol0_zone_limit = zone_limit;
418
419                 format_freemap(vol,
420                         &ondisk->vol0_blockmap[HAMMER_ZONE_FREEMAP_INDEX]);
421
422                 freeblks = initialize_freemap(vol);
423                 ondisk->vol0_stat_freebigblocks = freeblks;
424                 ondisk->vol0_stat_bigblocks = freeblks;
425
426                 format_blockmap(
427                         &ondisk->vol0_blockmap[HAMMER_ZONE_BTREE_INDEX],
428                         HAMMER_ZONE_BTREE);
429 #if 0
430                 format_blockmap(
431                         &ondisk->vol0_blockmap[HAMMER_ZONE_RECORD_INDEX],
432                         HAMMER_ZONE_RECORD);
433 #endif
434                 format_blockmap(
435                         &ondisk->vol0_blockmap[HAMMER_ZONE_LARGE_DATA_INDEX],
436                         HAMMER_ZONE_LARGE_DATA);
437                 format_blockmap(
438                         &ondisk->vol0_blockmap[HAMMER_ZONE_SMALL_DATA_INDEX],
439                         HAMMER_ZONE_SMALL_DATA);
440
441                 format_undomap(ondisk);
442
443                 ondisk->vol0_btree_root = format_root();
444                 ++ondisk->vol0_stat_inodes;     /* root inode */
445         } else {
446                 freeblks = initialize_freemap(vol);
447                 root_vol = get_volume(RootVolNo);
448                 root_vol->cache.modified = 1;
449                 root_vol->ondisk->vol0_stat_freebigblocks += freeblks;
450                 root_vol->ondisk->vol0_stat_bigblocks += freeblks;
451                 rel_volume(root_vol);
452         }
453 }
454
455 /*
456  * Format the root directory.
457  */
458 static
459 hammer_off_t
460 format_root(void)
461 {
462         hammer_off_t btree_off;
463         hammer_off_t data_off;
464         hammer_tid_t create_tid;
465         hammer_node_ondisk_t bnode;
466         struct hammer_inode_data *idata;
467         struct buffer_info *data_buffer = NULL;
468         hammer_btree_elm_t elm;
469
470         bnode = alloc_btree_element(&btree_off);
471         idata = alloc_data_element(&data_off, sizeof(*idata), &data_buffer);
472         create_tid = createtid();
473
474         /*
475          * Populate the inode data and inode record for the root directory.
476          */
477         idata->version = HAMMER_INODE_DATA_VERSION;
478         idata->mode = 0755;
479         idata->mtime = create_tid;
480         idata->obj_type = HAMMER_OBJTYPE_DIRECTORY;
481         idata->size = 0;
482         idata->nlinks = 1;
483
484         /*
485          * Create the root of the B-Tree.  The root is a leaf node so we
486          * do not have to worry about boundary elements.
487          */
488         bnode->signature = HAMMER_BTREE_SIGNATURE_GOOD;
489         bnode->count = 1;
490         bnode->type = HAMMER_BTREE_TYPE_LEAF;
491
492         elm = &bnode->elms[0];
493         elm->leaf.base.btype = HAMMER_BTREE_TYPE_RECORD;
494         elm->leaf.base.localization = HAMMER_LOCALIZE_INODE;
495         elm->leaf.base.obj_id = HAMMER_OBJID_ROOT;
496         elm->leaf.base.key = 0;
497         elm->leaf.base.create_tid = create_tid;
498         elm->leaf.base.delete_tid = 0;
499         elm->leaf.base.rec_type = HAMMER_RECTYPE_INODE;
500         elm->leaf.base.obj_type = HAMMER_OBJTYPE_DIRECTORY;
501
502         elm->leaf.atime = create_tid;
503         elm->leaf.data_offset = data_off;
504         elm->leaf.data_len = sizeof(*idata);
505         elm->leaf.data_crc = crc32(idata, sizeof(*idata));
506
507         bnode->crc = crc32(&bnode->crc + 1, HAMMER_BTREE_CRCSIZE);
508
509         return(btree_off);
510 }
511