nrelease - fix/improve livecd
[dragonfly.git] / usr.sbin / makefs / hammer2.c
1 /*
2  * SPDX-License-Identifier: BSD-3-Clause
3  *
4  * Copyright (c) 2022 Tomohiro Kusumi <tkusumi@netbsd.org>
5  * Copyright (c) 2011-2022 The DragonFly Project.  All rights reserved.
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
35 #if HAVE_NBTOOL_CONFIG_H
36 #include "nbtool_config.h"
37 #endif
38
39 #include <sys/param.h>
40 #include <sys/stat.h>
41 #include <sys/sysctl.h>
42 #include <sys/mman.h>
43 #include <sys/time.h>
44 #include <sys/dirent.h>
45
46 #include <stdio.h>
47 #include <stdlib.h>
48 #include <stdbool.h>
49 #include <string.h>
50 #include <ctype.h>
51 #include <unistd.h>
52 #include <fcntl.h>
53 #include <time.h>
54 #include <err.h>
55 #include <assert.h>
56 #include <util.h>
57
58 #include "makefs.h"
59 #include "hammer2.h"
60
61 #define APRINTF(X, ...) \
62     printf("%s: " X, __func__, ## __VA_ARGS__)
63
64 static void hammer2_parse_pfs_opts(const char *, fsinfo_t *);
65 static void hammer2_parse_inode_opts(const char *, fsinfo_t *);
66 static void hammer2_dump_fsinfo(fsinfo_t *);
67 static int hammer2_create_image(const char *, fsinfo_t *);
68 static int hammer2_populate_dir(struct m_vnode *, const char *, fsnode *,
69     fsnode *, fsinfo_t *, int);
70 static void hammer2_validate(const char *, fsnode *, fsinfo_t *);
71 static void hammer2_size_dir(fsnode *, fsinfo_t *);
72 static int hammer2_write_file(struct m_vnode *, const char *, fsnode *);
73 static int hammer2_version_get(struct m_vnode *);
74 static int hammer2_pfs_get(struct m_vnode *);
75 static int hammer2_pfs_lookup(struct m_vnode *, const char *);
76 static int hammer2_pfs_create(struct m_vnode *, const char *);
77 static int hammer2_pfs_delete(struct m_vnode *, const char *);
78 static int hammer2_pfs_snapshot(struct m_vnode *, const char *, const char *);
79 static int hammer2_inode_getx(struct m_vnode *, const char *);
80 static int hammer2_inode_setcheck(struct m_vnode *, const char *);
81 static int hammer2_inode_setcomp(struct m_vnode *, const char *);
82 static int hammer2_bulkfree(struct m_vnode *);
83 static int hammer2_destroy_path(struct m_vnode *, const char *);
84 static int hammer2_destroy_inum(struct m_vnode *, hammer2_tid_t);
85 static int hammer2_growfs(struct m_vnode *, hammer2_off_t);
86 struct hammer2_linkq;
87 static int hammer2_readx_handle(struct m_vnode *, const char *, const char *,
88     struct hammer2_linkq *);
89 static int hammer2_readx(struct m_vnode *, const char *, const char *);
90 static void unittest_trim_slash(void);
91
92 fsnode *hammer2_curnode;
93
94 void
95 hammer2_prep_opts(fsinfo_t *fsopts)
96 {
97         hammer2_makefs_options_t *h2_opt = ecalloc(1, sizeof(*h2_opt));
98         hammer2_mkfs_options_t *opt = &h2_opt->mkfs_options;
99
100         const option_t hammer2_options[] = {
101                 /* newfs_hammer2(8) compatible options */
102                 { 'b', "BootAreaSize", NULL, OPT_STRBUF, 0, 0, "boot area size" },
103                 { 'r', "AuxAreaSize", NULL, OPT_STRBUF, 0, 0, "aux area size" },
104                 { 'V', "Hammer2Version", NULL, OPT_STRBUF, 0, 0, "file system version" },
105                 { 'L', "Label", NULL, OPT_STRBUF, 0, 0, "PFS label" },
106                 /* makefs(8) specific options */
107                 { 'm', "MountLabel", NULL, OPT_STRBUF, 0, 0, "destination PFS label" },
108                 { 'v', "NumVolhdr", &h2_opt->num_volhdr, OPT_INT32,
109                     1, HAMMER2_NUM_VOLHDRS, "number of volume headers" },
110                 { 'c', "CompressionType", NULL, OPT_STRBUF, 0, 0, "compression type" },
111                 { 'C', "CheckType", NULL, OPT_STRBUF, 0, 0, "check type" },
112                 { 'd', "Hammer2Debug", NULL, OPT_STRBUF, 0, 0, "debug tunable" },
113                 { 'E', "EmergencyMode", &h2_opt->emergency_mode, OPT_BOOL, 0, 0,
114                     "emergency mode" },
115                 { 'P', "PFS", NULL, OPT_STRBUF, 0, 0, "offline PFS" },
116                 { 'I', "Inode", NULL, OPT_STRBUF, 0, 0, "offline inode" },
117                 { 'B', "Bulkfree", NULL, OPT_STRBUF, 0, 0, "offline bulkfree" },
118                 { 'D', "Destroy", NULL, OPT_STRBUF, 0, 0, "offline destroy" },
119                 { 'G', "Growfs", NULL, OPT_STRBUF, 0, 0, "offline growfs" },
120                 { 'R', "Read", NULL, OPT_STRBUF, 0, 0, "offline read" },
121                 { .name = NULL },
122         };
123
124         hammer2_mkfs_init(opt);
125
126         assert(opt->CompType == HAMMER2_COMP_DEFAULT);
127         assert(opt->CheckType == HAMMER2_CHECK_DEFAULT);
128
129         /* force debug mode for mkfs */
130         opt->DebugOpt = 1;
131
132         fsopts->fs_specific = h2_opt;
133         fsopts->fs_options = copy_opts(hammer2_options);
134         fsopts->sectorsize = DEV_BSIZE;
135 }
136
137 void
138 hammer2_cleanup_opts(fsinfo_t *fsopts)
139 {
140         hammer2_makefs_options_t *h2_opt = fsopts->fs_specific;
141         hammer2_mkfs_options_t *opt = &h2_opt->mkfs_options;
142
143         hammer2_mkfs_cleanup(opt);
144
145         free(h2_opt);
146         free(fsopts->fs_options);
147 }
148
149 int
150 hammer2_parse_opts(const char *option, fsinfo_t *fsopts)
151 {
152         hammer2_makefs_options_t *h2_opt = fsopts->fs_specific;
153         hammer2_mkfs_options_t *opt = &h2_opt->mkfs_options;
154
155         option_t *hammer2_options = fsopts->fs_options;
156         char buf[1024]; /* > HAMMER2_INODE_MAXNAME */
157         int i;
158
159         assert(option != NULL);
160         assert(fsopts != NULL);
161
162         if (debug & DEBUG_FS_PARSE_OPTS)
163                 APRINTF("got `%s'\n", option);
164
165         i = set_option(hammer2_options, option, buf, sizeof(buf));
166         if (i == -1)
167                 return 0;
168
169         if (hammer2_options[i].name == NULL)
170                 abort();
171
172         switch (hammer2_options[i].letter) {
173         case 'b':
174                 opt->BootAreaSize = getsize(buf, HAMMER2_NEWFS_ALIGN,
175                     HAMMER2_BOOT_MAX_BYTES, 2);
176                 break;
177         case 'r':
178                 opt->AuxAreaSize = getsize(buf, HAMMER2_NEWFS_ALIGN,
179                     HAMMER2_AUX_MAX_BYTES, 2);
180                 break;
181         case 'V':
182                 if (strlen(buf) == 0) {
183                         h2_opt->ioctl_cmd = HAMMER2IOC_VERSION_GET;
184                 } else {
185                         opt->Hammer2Version = strtol(buf, NULL, 0);
186                         if (opt->Hammer2Version < HAMMER2_VOL_VERSION_MIN ||
187                             opt->Hammer2Version >= HAMMER2_VOL_VERSION_WIP)
188                                 errx(1, "I don't understand how to format "
189                                     "HAMMER2 version %d",
190                                     opt->Hammer2Version);
191                 }
192                 break;
193         case 'L':
194                 h2_opt->label_specified = 1;
195                 if (strcasecmp(buf, "none") == 0)
196                         break;
197                 if (opt->NLabels >= MAXLABELS)
198                         errx(1, "Limit of %d local labels", MAXLABELS - 1);
199                 if (strlen(buf) == 0)
200                         errx(1, "Volume label '%s' cannot be 0-length", buf);
201                 if (strlen(buf) >= HAMMER2_INODE_MAXNAME)
202                         errx(1, "Volume label '%s' is too long (%d chars max)",
203                             buf, HAMMER2_INODE_MAXNAME - 1);
204                 opt->Label[opt->NLabels++] = strdup(buf);
205                 break;
206         case 'm':
207                 if (strlen(buf) == 0)
208                         errx(1, "Volume label '%s' cannot be 0-length", buf);
209                 if (strlen(buf) >= HAMMER2_INODE_MAXNAME)
210                         errx(1, "Volume label '%s' is too long (%d chars max)",
211                             buf, HAMMER2_INODE_MAXNAME - 1);
212                 strlcpy(h2_opt->mount_label, buf, sizeof(h2_opt->mount_label));
213                 break;
214         case 'c':
215                 if (strlen(buf) == 0)
216                         errx(1, "Compression type '%s' cannot be 0-length", buf);
217                 if (strcasecmp(buf, "none") == 0)
218                         opt->CompType = HAMMER2_COMP_NONE;
219                 else if (strcasecmp(buf, "autozero") == 0)
220                         opt->CompType = HAMMER2_COMP_AUTOZERO;
221                 else if (strcasecmp(buf, "lz4") == 0)
222                         opt->CompType = HAMMER2_COMP_LZ4;
223                 else if (strcasecmp(buf, "zlib") == 0)
224                         opt->CompType = HAMMER2_COMP_ZLIB;
225                 else
226                         errx(1, "Invalid compression type '%s'", buf);
227                 break;
228         case 'C':
229                 if (strlen(buf) == 0)
230                         errx(1, "Check type '%s' cannot be 0-length", buf);
231                 if (strcasecmp(buf, "none") == 0)
232                         opt->CheckType = HAMMER2_CHECK_NONE;
233                 else if (strcasecmp(buf, "disabled") == 0)
234                         opt->CheckType = HAMMER2_CHECK_DISABLED;
235                 else if (strcasecmp(buf, "iscsi32") == 0)
236                         opt->CheckType = HAMMER2_CHECK_ISCSI32;
237                 else if (strcasecmp(buf, "xxhash64") == 0)
238                         opt->CheckType = HAMMER2_CHECK_XXHASH64;
239                 else if (strcasecmp(buf, "sha192") == 0)
240                         opt->CheckType = HAMMER2_CHECK_SHA192;
241                 else if (strcasecmp(buf, "freemap") == 0)
242                         opt->CheckType = HAMMER2_CHECK_FREEMAP;
243                 else
244                         errx(1, "Invalid check type '%s'", buf);
245                 break;
246         case 'd':
247                 hammer2_debug = strtoll(buf, NULL, 0);
248                 break;
249         case 'P':
250                 if (strlen(buf) == 0)
251                         errx(1, "PFS argument '%s' cannot be 0-length", buf);
252                 hammer2_parse_pfs_opts(buf, fsopts);
253                 break;
254         case 'I':
255                 if (strlen(buf) == 0)
256                         errx(1, "Inode argument '%s' cannot be 0-length", buf);
257                 hammer2_parse_inode_opts(buf, fsopts);
258                 break;
259         case 'B':
260                 h2_opt->ioctl_cmd = HAMMER2IOC_BULKFREE_SCAN;
261                 break;
262         case 'D':
263                 h2_opt->ioctl_cmd = HAMMER2IOC_DESTROY;
264                 if (strlen(buf) == 0)
265                         errx(1, "Destroy argument '%s' cannot be 0-length", buf);
266                 if (buf[0] == '/') {
267                         strlcpy(h2_opt->destroy_path, buf,
268                             sizeof(h2_opt->destroy_path));
269                 } else if (strncmp(buf, "0x", 2) == 0 ||
270                     (buf[0] >= '0' && buf[0] <= '9')) {
271                         h2_opt->destroy_inum = strtoull(buf, NULL, 0);
272                         if (errno)
273                                 err(1, "strtoull");
274                 } else {
275                         errx(1, "Invalid destroy argument %s", buf);
276                 }
277                 break;
278         case 'G':
279                 h2_opt->ioctl_cmd = HAMMER2IOC_GROWFS;
280                 break;
281         case 'R':
282                 h2_opt->ioctl_cmd = HAMMER2IOC_READ;
283                 if (strlen(buf) == 0)
284                         errx(1, "Read argument '%s' cannot be 0-length", buf);
285                 strlcpy(h2_opt->read_path, buf, sizeof(h2_opt->read_path));
286                 break;
287         default:
288                 break;
289         }
290
291         if (hammer2_debug && h2_opt->ioctl_cmd)
292                 unittest_trim_slash();
293
294         return 1;
295 }
296
297 void
298 hammer2_makefs(const char *image, const char *dir, fsnode *root,
299     fsinfo_t *fsopts)
300 {
301         hammer2_makefs_options_t *h2_opt = fsopts->fs_specific;
302         struct mount mp;
303         struct hammer2_mount_info info;
304         struct m_vnode devvp, *vroot;
305         hammer2_inode_t *iroot;
306         struct timeval start;
307         int error;
308
309         /* ioctl commands could have NULL dir / root */
310         assert(image != NULL);
311         assert(fsopts != NULL);
312
313         if (debug & DEBUG_FS_MAKEFS)
314                 APRINTF("image \"%s\" directory \"%s\" root %p\n",
315                     image, dir, root);
316
317         /* validate tree and options */
318         TIMER_START(start);
319         hammer2_validate(dir, root, fsopts);
320         TIMER_RESULTS(start, "hammer2_validate");
321
322         if (h2_opt->ioctl_cmd) {
323                 /* open existing image */
324                 fsopts->fd = open(image, O_RDWR);
325                 if (fsopts->fd < 0)
326                         err(1, "failed to open `%s'", image);
327         } else {
328                 /* create image */
329                 TIMER_START(start);
330                 if (hammer2_create_image(image, fsopts) == -1)
331                         errx(1, "image file `%s' not created", image);
332                 TIMER_RESULTS(start, "hammer2_create_image");
333         }
334         assert(fsopts->fd > 0);
335
336         if (debug & DEBUG_FS_MAKEFS)
337                 putchar('\n');
338
339         /* vfs init */
340         error = hammer2_vfs_init();
341         if (error)
342                 errx(1, "failed to vfs init, error %d", error);
343
344         /* mount image */
345         memset(&devvp, 0, sizeof(devvp));
346         devvp.fs = fsopts;
347         memset(&mp, 0, sizeof(mp));
348         memset(&info, 0, sizeof(info));
349         error = hammer2_vfs_mount(&devvp, &mp, h2_opt->mount_label, &info);
350         if (error)
351                 errx(1, "failed to mount, error %d", error);
352         assert(mp.mnt_data);
353
354         /* get root vnode */
355         vroot = NULL;
356         error = hammer2_vfs_root(&mp, &vroot);
357         if (error)
358                 errx(1, "failed to get root vnode, error %d", error);
359         assert(vroot);
360
361         iroot = VTOI(vroot);
362         assert(iroot);
363         printf("root inode inum %lld, mode 0%o, refs %d\n",
364             (long long)iroot->meta.inum, iroot->meta.mode, iroot->refs);
365
366         if (h2_opt->emergency_mode)
367                 hammer2_ioctl_emerg_mode(iroot, 1);
368
369         switch (h2_opt->ioctl_cmd) {
370         case HAMMER2IOC_VERSION_GET:
371                 printf("version get `%s'\n", image);
372                 TIMER_START(start);
373                 error = hammer2_version_get(vroot);
374                 if (error)
375                         errx(1, "version get `%s' failed '%s'", image,
376                             strerror(error));
377                 TIMER_RESULTS(start, "hammer2_version_get");
378                 break;
379         case HAMMER2IOC_PFS_GET:
380                 printf("PFS %s `%s'\n", h2_opt->pfs_cmd_name, image);
381                 TIMER_START(start);
382                 error = hammer2_pfs_get(vroot);
383                 if (error)
384                         errx(1, "PFS %s`%s' failed '%s'", h2_opt->pfs_cmd_name,
385                             image, strerror(error));
386                 TIMER_RESULTS(start, "hammer2_pfs_get");
387                 break;
388         case HAMMER2IOC_PFS_LOOKUP:
389                 printf("PFS %s `%s'\n", h2_opt->pfs_cmd_name, image);
390                 TIMER_START(start);
391                 error = hammer2_pfs_lookup(vroot, h2_opt->pfs_name);
392                 if (error)
393                         errx(1, "PFS %s`%s' failed '%s'", h2_opt->pfs_cmd_name,
394                             image, strerror(error));
395                 TIMER_RESULTS(start, "hammer2_pfs_lookup");
396                 break;
397         case HAMMER2IOC_PFS_CREATE:
398                 printf("PFS %s `%s'\n", h2_opt->pfs_cmd_name, image);
399                 TIMER_START(start);
400                 error = hammer2_pfs_create(vroot, h2_opt->pfs_name);
401                 if (error)
402                         errx(1, "PFS %s`%s' failed '%s'", h2_opt->pfs_cmd_name,
403                             image, strerror(error));
404                 TIMER_RESULTS(start, "hammer2_pfs_create");
405                 break;
406         case HAMMER2IOC_PFS_DELETE:
407                 printf("PFS %s `%s'\n", h2_opt->pfs_cmd_name, image);
408                 TIMER_START(start);
409                 error = hammer2_pfs_delete(vroot, h2_opt->pfs_name);
410                 if (error)
411                         errx(1, "PFS %s`%s' failed '%s'", h2_opt->pfs_cmd_name,
412                             image, strerror(error));
413                 TIMER_RESULTS(start, "hammer2_pfs_delete");
414                 break;
415         case HAMMER2IOC_PFS_SNAPSHOT:
416                 printf("PFS %s `%s'\n", h2_opt->pfs_cmd_name, image);
417                 TIMER_START(start);
418                 error = hammer2_pfs_snapshot(vroot, h2_opt->pfs_name,
419                     h2_opt->mount_label);
420                 if (error)
421                         errx(1, "PFS %s`%s' failed '%s'", h2_opt->pfs_cmd_name,
422                             image, strerror(error));
423                 TIMER_RESULTS(start, "hammer2_pfs_snapshot");
424                 break;
425         case HAMMER2IOC_INODE_GET:
426                 printf("inode %s `%s'\n", h2_opt->inode_cmd_name, image);
427                 TIMER_START(start);
428                 error = hammer2_inode_getx(vroot, h2_opt->inode_path);
429                 if (error)
430                         errx(1, "inode %s `%s' failed '%s'",
431                             h2_opt->inode_cmd_name, image, strerror(error));
432                 TIMER_RESULTS(start, "hammer2_inode_getx");
433                 break;
434         case HAMMER2IOC_INODE_SET:
435                 printf("inode %s `%s'\n", h2_opt->inode_cmd_name, image);
436                 TIMER_START(start);
437                 if (!strcmp(h2_opt->inode_cmd_name, "setcheck")) {
438                         error = hammer2_inode_setcheck(vroot,
439                             h2_opt->inode_path);
440                         if (error)
441                                 errx(1, "inode %s `%s' failed '%s'",
442                                     h2_opt->inode_cmd_name, image,
443                                     strerror(error));
444                 } else if (!strcmp(h2_opt->inode_cmd_name, "setcomp")) {
445                         error = hammer2_inode_setcomp(vroot,
446                             h2_opt->inode_path);
447                         if (error)
448                                 errx(1, "inode %s `%s' failed '%s'",
449                                     h2_opt->inode_cmd_name, image,
450                                     strerror(error));
451                 } else {
452                         assert(0);
453                 }
454                 TIMER_RESULTS(start, "hammer2_inode_setx");
455                 break;
456         case HAMMER2IOC_BULKFREE_SCAN:
457                 printf("bulkfree `%s'\n", image);
458                 TIMER_START(start);
459                 error = hammer2_bulkfree(vroot);
460                 if (error)
461                         errx(1, "bulkfree `%s' failed '%s'", image,
462                             strerror(error));
463                 TIMER_RESULTS(start, "hammer2_bulkfree");
464                 break;
465         case HAMMER2IOC_DESTROY:
466                 TIMER_START(start);
467                 if (strlen(h2_opt->destroy_path)) {
468                         printf("destroy `%s' in `%s'\n",
469                             h2_opt->destroy_path, image);
470                         error = hammer2_destroy_path(vroot,
471                             h2_opt->destroy_path);
472                         if (error)
473                                 errx(1, "destroy `%s' in `%s' failed '%s'",
474                                     h2_opt->destroy_path, image,
475                                     strerror(error));
476                 } else {
477                         printf("destroy %lld in `%s'\n",
478                             (long long)h2_opt->destroy_inum, image);
479                         error = hammer2_destroy_inum(vroot,
480                             h2_opt->destroy_inum);
481                         if (error)
482                                 errx(1, "destroy %lld in `%s' failed '%s'",
483                                     (long long)h2_opt->destroy_inum, image,
484                                     strerror(error));
485                 }
486                 TIMER_RESULTS(start, "hammer2_destroy");
487                 break;
488         case HAMMER2IOC_GROWFS:
489                 printf("growfs `%s'\n", image);
490                 TIMER_START(start);
491                 error = hammer2_growfs(vroot, h2_opt->image_size);
492                 if (error)
493                         errx(1, "growfs `%s' failed '%s'", image,
494                             strerror(error));
495                 TIMER_RESULTS(start, "hammer2_growfs");
496                 break;
497         case HAMMER2IOC_READ:
498                 printf("read `%s'\n", image);
499                 TIMER_START(start);
500                 error = hammer2_readx(vroot, dir, h2_opt->read_path);
501                 if (error)
502                         errx(1, "read `%s' failed '%s'", image,
503                             strerror(error));
504                 TIMER_RESULTS(start, "hammer2_readx");
505                 break;
506         default:
507                 printf("populating `%s'\n", image);
508                 TIMER_START(start);
509                 if (hammer2_populate_dir(vroot, dir, root, root, fsopts, 0))
510                         errx(1, "image file `%s' not populated", image);
511                 TIMER_RESULTS(start, "hammer2_populate_dir");
512                 break;
513         }
514
515         /* unmount image */
516         error = hammer2_vfs_unmount(&mp, 0);
517         if (error)
518                 errx(1, "failed to unmount, error %d", error);
519
520         /* check leaked resource */
521         if (vnode_count)
522                 printf("XXX %lld vnode left\n", (long long)vnode_count);
523         if (hammer2_chain_allocs)
524                 printf("XXX %ld chain left\n", hammer2_chain_allocs);
525         bcleanup();
526
527         /* vfs uninit */
528         error = hammer2_vfs_uninit();
529         if (error)
530                 errx(1, "failed to vfs uninit, error %d", error);
531
532         if (close(fsopts->fd) == -1)
533                 err(1, "closing `%s'", image);
534         fsopts->fd = -1;
535
536         printf("image `%s' complete\n", image);
537 }
538
539 /* end of public functions */
540
541 static void
542 hammer2_parse_pfs_opts(const char *buf, fsinfo_t *fsopts)
543 {
544         hammer2_makefs_options_t *h2_opt = fsopts->fs_specific;
545         char *o, *p;
546         size_t n;
547
548         o = p = strdup(buf);
549         p = strchr(p, ':');
550         if (p != NULL) {
551                 *p++ = 0;
552                 n = strlen(p);
553         } else {
554                 n = 0;
555         }
556
557         if (!strcmp(o, "get") || !strcmp(o, "list")) {
558                 h2_opt->ioctl_cmd = HAMMER2IOC_PFS_GET;
559         } else if (!strcmp(o, "lookup")) {
560                 if (n == 0 || n > NAME_MAX)
561                         errx(1, "invalid PFS name \"%s\"", p);
562                 h2_opt->ioctl_cmd = HAMMER2IOC_PFS_LOOKUP;
563         } else if (!strcmp(o, "create")) {
564                 if (n == 0 || n > NAME_MAX)
565                         errx(1, "invalid PFS name \"%s\"", p);
566                 h2_opt->ioctl_cmd = HAMMER2IOC_PFS_CREATE;
567         } else if (!strcmp(o, "delete")) {
568                 if (n == 0 || n > NAME_MAX)
569                         errx(1, "invalid PFS name \"%s\"", p);
570                 h2_opt->ioctl_cmd = HAMMER2IOC_PFS_DELETE;
571         } else if (!strcmp(o, "snapshot")) {
572                 if (n > NAME_MAX)
573                         errx(1, "invalid PFS name \"%s\"", p);
574                 h2_opt->ioctl_cmd = HAMMER2IOC_PFS_SNAPSHOT;
575         } else {
576                 errx(1, "invalid PFS command \"%s\"", o);
577         }
578
579         strlcpy(h2_opt->pfs_cmd_name, o, sizeof(h2_opt->pfs_cmd_name));
580         if (n > 0)
581                 strlcpy(h2_opt->pfs_name, p, sizeof(h2_opt->pfs_name));
582
583         free(o);
584 }
585
586 static void
587 hammer2_parse_inode_opts(const char *buf, fsinfo_t *fsopts)
588 {
589         hammer2_makefs_options_t *h2_opt = fsopts->fs_specific;
590         char *o, *p;
591         size_t n;
592
593         o = p = strdup(buf);
594         p = strchr(p, ':');
595         if (p != NULL) {
596                 *p++ = 0;
597                 n = strlen(p);
598         } else {
599                 n = 0;
600         }
601
602         if (!strcmp(o, "get")) {
603                 if (n == 0 || n > PATH_MAX)
604                         errx(1, "invalid file path \"%s\"", p);
605                 h2_opt->ioctl_cmd = HAMMER2IOC_INODE_GET;
606         } else if (!strcmp(o, "setcheck")) {
607                 if (n == 0 || n > PATH_MAX - 10)
608                         errx(1, "invalid argument \"%s\"", p);
609                 h2_opt->ioctl_cmd = HAMMER2IOC_INODE_SET;
610         } else if (!strcmp(o, "setcomp")) {
611                 if (n == 0 || n > PATH_MAX - 10)
612                         errx(1, "invalid argument \"%s\"", p);
613                 h2_opt->ioctl_cmd = HAMMER2IOC_INODE_SET;
614         } else {
615                 errx(1, "invalid inode command \"%s\"", o);
616         }
617
618         strlcpy(h2_opt->inode_cmd_name, o, sizeof(h2_opt->inode_cmd_name));
619         if (n > 0)
620                 strlcpy(h2_opt->inode_path, p, sizeof(h2_opt->inode_path));
621
622         free(o);
623 }
624
625 static hammer2_off_t
626 hammer2_image_size(fsinfo_t *fsopts)
627 {
628         hammer2_makefs_options_t *h2_opt = fsopts->fs_specific;
629         hammer2_off_t image_size, used_size = 0;
630         int num_level1, delta_num_level1;
631
632         /* use 4 volume headers by default */
633         num_level1 = h2_opt->num_volhdr * 2; /* default 4 x 2 */
634         assert(num_level1 != 0);
635         assert(num_level1 <= 8);
636
637         /* add 4MiB segment for each level1 */
638         used_size += HAMMER2_ZONE_SEG64 * num_level1;
639
640         /* add boot/aux area, but exact size unknown at this point */
641         used_size += HAMMER2_BOOT_NOM_BYTES + HAMMER2_AUX_NOM_BYTES;
642
643         /* add data size */
644         used_size += fsopts->size;
645
646         /* XXX add extra level1 for meta data and indirect blocks */
647         used_size += HAMMER2_FREEMAP_LEVEL1_SIZE;
648
649         /* XXX add extra level1 for safety */
650         if (used_size > HAMMER2_FREEMAP_LEVEL1_SIZE * 10)
651                 used_size += HAMMER2_FREEMAP_LEVEL1_SIZE;
652
653         /* use 8GiB image size by default */
654         image_size = HAMMER2_FREEMAP_LEVEL1_SIZE * num_level1;
655         printf("trying default image size %s\n", sizetostr(image_size));
656
657         /* adjust if image size isn't large enough */
658         if (used_size > image_size) {
659                 /* determine extra level1 needed */
660                 delta_num_level1 = howmany(used_size - image_size,
661                     HAMMER2_FREEMAP_LEVEL1_SIZE);
662
663                 /* adjust used size with 4MiB segment for each extra level1 */
664                 used_size += HAMMER2_ZONE_SEG64 * delta_num_level1;
665
666                 /* adjust image size with extra level1 */
667                 image_size += HAMMER2_FREEMAP_LEVEL1_SIZE * delta_num_level1;
668                 printf("trying adjusted image size %s\n",
669                     sizetostr(image_size));
670
671                 if (used_size > image_size)
672                         errx(1, "invalid used_size %lld > image_size %lld",
673                             (long long)used_size, (long long)image_size);
674         }
675
676         return image_size;
677 }
678
679 static const char *
680 hammer2_label_name(int label_type)
681 {
682         switch (label_type) {
683         case HAMMER2_LABEL_NONE:
684                 return "NONE";
685         case HAMMER2_LABEL_BOOT:
686                 return "BOOT";
687         case HAMMER2_LABEL_ROOT:
688                 return "ROOT";
689         case HAMMER2_LABEL_DATA:
690                 return "DATA";
691         default:
692                 assert(0);
693         }
694         return NULL;
695 }
696
697 static void
698 hammer2_validate(const char *dir, fsnode *root, fsinfo_t *fsopts)
699 {
700         hammer2_makefs_options_t *h2_opt = fsopts->fs_specific;
701         hammer2_mkfs_options_t *opt = &h2_opt->mkfs_options;
702         hammer2_off_t image_size = 0, minsize, maxsize;
703         const char *s;
704
705         /* ioctl commands could have NULL dir / root */
706         assert(fsopts != NULL);
707
708         if (debug & DEBUG_FS_VALIDATE) {
709                 APRINTF("before defaults set:\n");
710                 hammer2_dump_fsinfo(fsopts);
711         }
712
713         /* makefs only supports "DATA" for default PFS label */
714         if (!h2_opt->label_specified) {
715                 opt->DefaultLabelType = HAMMER2_LABEL_DATA;
716                 s = hammer2_label_name(opt->DefaultLabelType);
717                 printf("using default label \"%s\"\n", s);
718         }
719
720         /* set default mount PFS label */
721         if (!strcmp(h2_opt->mount_label, "")) {
722                 s = hammer2_label_name(HAMMER2_LABEL_DATA);
723                 strlcpy(h2_opt->mount_label, s, sizeof(h2_opt->mount_label));
724                 printf("using default mount label \"%s\"\n", s);
725         }
726
727         /* set default number of volume headers */
728         if (!h2_opt->num_volhdr) {
729                 h2_opt->num_volhdr = HAMMER2_NUM_VOLHDRS;
730                 printf("using default %d volume headers\n", h2_opt->num_volhdr);
731         }
732
733         /* done if ioctl commands */
734         if (h2_opt->ioctl_cmd) {
735                 if (h2_opt->ioctl_cmd == HAMMER2IOC_GROWFS)
736                         goto ignore_size_dir;
737                 else
738                         goto done;
739         }
740
741         /* calculate data size */
742         if (fsopts->size != 0)
743                 fsopts->size = 0; /* shouldn't reach here to begin with */
744         if (root == NULL)
745                 errx(1, "fsnode tree not constructed");
746         hammer2_size_dir(root, fsopts);
747         printf("estimated data size %s from %lld inode\n",
748             sizetostr(fsopts->size), (long long)fsopts->inodes);
749
750         /* determine image size from data size */
751         image_size = hammer2_image_size(fsopts);
752         assert((image_size & HAMMER2_FREEMAP_LEVEL1_MASK) == 0);
753 ignore_size_dir:
754         minsize = roundup(fsopts->minsize, HAMMER2_FREEMAP_LEVEL1_SIZE);
755         maxsize = roundup(fsopts->maxsize, HAMMER2_FREEMAP_LEVEL1_SIZE);
756         if (image_size < minsize)
757                 image_size = minsize;
758         else if (maxsize > 0 && image_size > maxsize)
759                 errx(1, "`%s' size of %lld is larger than the maxsize of %lld",
760                     dir, (long long)image_size, (long long)maxsize);
761
762         assert((image_size & HAMMER2_FREEMAP_LEVEL1_MASK) == 0);
763         h2_opt->image_size = image_size;
764         printf("using %s image size\n", sizetostr(h2_opt->image_size));
765 done:
766         if (debug & DEBUG_FS_VALIDATE) {
767                 APRINTF("after defaults set:\n");
768                 hammer2_dump_fsinfo(fsopts);
769         }
770 }
771
772 static void
773 hammer2_dump_fsinfo(fsinfo_t *fsopts)
774 {
775         hammer2_makefs_options_t *h2_opt = fsopts->fs_specific;
776         hammer2_mkfs_options_t *opt = &h2_opt->mkfs_options;
777         int i;
778         char *s;
779
780         assert(fsopts != NULL);
781
782         APRINTF("fsinfo_t at %p\n", fsopts);
783
784         printf("\tinodes %lld\n", (long long)fsopts->inodes);
785         printf("\tsize %lld, minsize %lld, maxsize %lld\n",
786             (long long)fsopts->size,
787             (long long)fsopts->minsize,
788             (long long)fsopts->maxsize);
789
790         printf("\thammer2_debug 0x%x\n", hammer2_debug);
791
792         printf("\tlabel_specified %d\n", h2_opt->label_specified);
793         printf("\tmount_label \"%s\"\n", h2_opt->mount_label);
794         printf("\tnum_volhdr %d\n", h2_opt->num_volhdr);
795         printf("\tioctl_cmd %ld\n", h2_opt->ioctl_cmd);
796         printf("\temergency_mode %d\n", h2_opt->emergency_mode);
797         printf("\tpfs_cmd_name \"%s\"\n", h2_opt->pfs_cmd_name);
798         printf("\tpfs_name \"%s\"\n", h2_opt->pfs_name);
799         printf("\tinode_cmd_name \"%s\"\n", h2_opt->inode_cmd_name);
800         printf("\tinode_path \"%s\"\n", h2_opt->inode_path);
801         printf("\tdestroy_path \"%s\"\n", h2_opt->destroy_path);
802         printf("\tdestroy_inum %lld\n", (long long)h2_opt->destroy_inum);
803         printf("\tread_path \"%s\"\n", h2_opt->read_path);
804         printf("\timage_size 0x%llx\n", (long long)h2_opt->image_size);
805
806         printf("\tHammer2Version %d\n", opt->Hammer2Version);
807         printf("\tBootAreaSize 0x%jx\n", opt->BootAreaSize);
808         printf("\tAuxAreaSize 0x%jx\n", opt->AuxAreaSize);
809         printf("\tNLabels %d\n", opt->NLabels);
810         printf("\tCompType %d\n", opt->CompType);
811         printf("\tCheckType %d\n", opt->CheckType);
812         printf("\tDefaultLabelType %d\n", opt->DefaultLabelType);
813         printf("\tDebugOpt %d\n", opt->DebugOpt);
814
815         s = NULL;
816         hammer2_uuid_to_str(&opt->Hammer2_FSType, &s);
817         printf("\tHammer2_FSType \"%s\"\n", s);
818         s = NULL;
819         hammer2_uuid_to_str(&opt->Hammer2_VolFSID, &s);
820         printf("\tHammer2_VolFSID \"%s\"\n", s);
821         s = NULL;
822         hammer2_uuid_to_str(&opt->Hammer2_SupCLID, &s);
823         printf("\tHammer2_SupCLID \"%s\"\n", s);
824         s = NULL;
825         hammer2_uuid_to_str(&opt->Hammer2_SupFSID, &s);
826         printf("\tHammer2_SupFSID \"%s\"\n", s);
827
828         for (i = 0; i < opt->NLabels; i++) {
829                 printf("\tLabel[%d] \"%s\"\n", i, opt->Label[i]);
830                 s = NULL;
831                 hammer2_uuid_to_str(&opt->Hammer2_PfsCLID[i], &s);
832                 printf("\t Hammer2_PfsCLID[%d] \"%s\"\n", i, s);
833                 s = NULL;
834                 hammer2_uuid_to_str(&opt->Hammer2_PfsFSID[i], &s);
835                 printf("\t Hammer2_PfsFSID[%d] \"%s\"\n", i, s);
836         }
837
838         free(s);
839 }
840
841 static int
842 hammer2_setup_blkdev(const char *image, fsinfo_t *fsopts)
843 {
844         hammer2_makefs_options_t *h2_opt = fsopts->fs_specific;
845         hammer2_off_t size;
846
847         if ((fsopts->fd = open(image, O_RDWR)) == -1) {
848                 warn("can't open `%s' for writing", image);
849                 return -1;
850         }
851
852         size = check_volume(fsopts->fd);
853         if (h2_opt->image_size > size) {
854                 warnx("image size %lld exceeds %s size %lld",
855                     (long long)h2_opt->image_size, image, (long long)size);
856                 return -1;
857         }
858
859         return 0;
860 }
861
862 static int
863 hammer2_create_image(const char *image, fsinfo_t *fsopts)
864 {
865         hammer2_makefs_options_t *h2_opt = fsopts->fs_specific;
866         hammer2_mkfs_options_t *opt = &h2_opt->mkfs_options;
867         char *av[] = { (char *)image, }; /* XXX support multi-volumes */
868         char *buf;
869         int i, bufsize, oflags;
870         off_t bufrem;
871         struct stat st;
872
873         assert(image != NULL);
874         assert(fsopts != NULL);
875
876         /* check if image is blk or chr */
877         if (stat(image, &st) == 0) {
878                 if (S_ISBLK(st.st_mode) || S_ISCHR(st.st_mode)) {
879                         if (hammer2_setup_blkdev(image, fsopts))
880                                 return -1;
881                         goto done;
882                 }
883         }
884
885         /* create image */
886         oflags = O_RDWR | O_CREAT;
887         if (fsopts->offset == 0)
888                 oflags |= O_TRUNC;
889         if ((fsopts->fd = open(image, oflags, 0666)) == -1) {
890                 warn("can't open `%s' for writing", image);
891                 return -1;
892         }
893
894         /* zero image */
895         bufsize = HAMMER2_PBUFSIZE;
896         bufrem = h2_opt->image_size;
897         if (fsopts->sparse) {
898                 if (ftruncate(fsopts->fd, bufrem) == -1) {
899                         warn("sparse option disabled");
900                         fsopts->sparse = 0;
901                 }
902         }
903         if (fsopts->sparse) {
904                 /* File truncated at bufrem. Remaining is 0 */
905                 bufrem = 0;
906                 buf = NULL;
907         } else {
908                 if (debug & DEBUG_FS_CREATE_IMAGE)
909                         APRINTF("zero-ing image `%s', %lld sectors, "
910                             "using %d byte chunks\n",
911                             image, (long long)bufrem, bufsize);
912                 buf = ecalloc(1, bufsize);
913         }
914
915         if (fsopts->offset != 0) {
916                 if (lseek(fsopts->fd, fsopts->offset, SEEK_SET) == -1) {
917                         warn("can't seek");
918                         free(buf);
919                         return -1;
920                 }
921         }
922
923         while (bufrem > 0) {
924                 i = write(fsopts->fd, buf, MIN(bufsize, bufrem));
925                 if (i == -1) {
926                         warn("zeroing image, %lld bytes to go",
927                             (long long)bufrem);
928                         free(buf);
929                         return -1;
930                 }
931                 bufrem -= i;
932         }
933         if (buf)
934                 free(buf);
935 done:
936         /* make the file system */
937         if (debug & DEBUG_FS_CREATE_IMAGE)
938                 APRINTF("calling mkfs(\"%s\", ...)\n", image);
939         hammer2_mkfs(1, av, opt); /* success if returned */
940
941         return fsopts->fd;
942 }
943
944 static off_t
945 hammer2_phys_size(off_t size)
946 {
947         off_t radix_size, phys_size = 0;
948         int i;
949
950         if (size > HAMMER2_PBUFSIZE) {
951                 phys_size += rounddown(size, HAMMER2_PBUFSIZE);
952                 size = size % HAMMER2_PBUFSIZE;
953         }
954
955         for (i = HAMMER2_RADIX_MIN; i <= HAMMER2_RADIX_MAX; i++) {
956                 radix_size = 1UL << i;
957                 if (radix_size >= size) {
958                         phys_size += radix_size;
959                         break;
960                 }
961         }
962
963         return phys_size;
964 }
965
966 /* calculate data size */
967 static void
968 hammer2_size_dir(fsnode *root, fsinfo_t *fsopts)
969 {
970         fsnode *node;
971
972         assert(fsopts != NULL);
973
974         if (debug & DEBUG_FS_SIZE_DIR)
975                 APRINTF("entry: bytes %lld inodes %lld\n",
976                     (long long)fsopts->size, (long long)fsopts->inodes);
977
978         for (node = root; node != NULL; node = node->next) {
979                 if (node == root) { /* we're at "." */
980                         assert(strcmp(node->name, ".") == 0);
981                 } else if ((node->inode->flags & FI_SIZED) == 0) {
982                         /* don't count duplicate names */
983                         node->inode->flags |= FI_SIZED;
984                         if (debug & DEBUG_FS_SIZE_DIR_NODE)
985                                 APRINTF("`%s' size %lld\n",
986                                     node->name,
987                                     (long long)node->inode->st.st_size);
988                         fsopts->inodes++;
989                         fsopts->size += sizeof(hammer2_inode_data_t);
990                         if (node->type == S_IFREG) {
991                                 size_t st_size = node->inode->st.st_size;
992                                 if (st_size > HAMMER2_EMBEDDED_BYTES)
993                                         fsopts->size += hammer2_phys_size(st_size);
994                         } else if (node->type == S_IFLNK) {
995                                 size_t nlen = strlen(node->symlink);
996                                 if (nlen > HAMMER2_EMBEDDED_BYTES)
997                                         fsopts->size += hammer2_phys_size(nlen);
998                         }
999                 }
1000                 if (node->type == S_IFDIR)
1001                         hammer2_size_dir(node->child, fsopts);
1002         }
1003
1004         if (debug & DEBUG_FS_SIZE_DIR)
1005                 APRINTF("exit: size %lld inodes %lld\n",
1006                     (long long)fsopts->size, (long long)fsopts->inodes);
1007 }
1008
1009 static void
1010 hammer2_print(const struct m_vnode *dvp, const struct m_vnode *vp,
1011     const fsnode *node, int depth, const char *msg)
1012 {
1013         if (debug & DEBUG_FS_POPULATE) {
1014                 if (1) {
1015                         int indent = depth * 2;
1016                         char *type;
1017                         if (S_ISDIR(node->type))
1018                                 type = "dir";
1019                         else if (S_ISREG(node->type))
1020                                 type = "reg";
1021                         else if (S_ISLNK(node->type))
1022                                 type = "lnk";
1023                         else if (S_ISFIFO(node->type))
1024                                 type = "fifo";
1025                         else
1026                                 type = "???";
1027                         printf("%*.*s", indent, indent, "");
1028                         printf("dvp=%p/%d vp=%p/%d \"%s\" %s %s\n",
1029                             dvp, dvp ? VTOI(dvp)->refs : 0,
1030                             vp, vp ? VTOI(vp)->refs : 0,
1031                             node->name, type, msg);
1032                 } else {
1033                         char type;
1034                         if (S_ISDIR(node->type))
1035                                 type = 'd';
1036                         else if (S_ISREG(node->type))
1037                                 type = 'r';
1038                         else if (S_ISLNK(node->type))
1039                                 type = 'l';
1040                         else if (S_ISFIFO(node->type))
1041                                 type = 'f';
1042                         else
1043                                 type = '?';
1044                         printf("%c", type);
1045                         fflush(stdout);
1046                 }
1047         }
1048 }
1049
1050 static int
1051 hammer2_populate_dir(struct m_vnode *dvp, const char *dir, fsnode *root,
1052     fsnode *parent, fsinfo_t *fsopts, int depth)
1053 {
1054         fsnode *cur;
1055         struct m_vnode *vp;
1056         struct stat st;
1057         char f[MAXPATHLEN];
1058         const char *path;
1059         int hardlink;
1060         int error;
1061
1062         assert(dvp != NULL);
1063         assert(dir != NULL);
1064         assert(root != NULL);
1065         assert(parent != NULL);
1066         assert(fsopts != NULL);
1067
1068         /* assert root directory */
1069         assert(S_ISDIR(root->type));
1070         assert(!strcmp(root->name, "."));
1071         assert(!root->child);
1072         assert(!root->parent || root->parent->child == root);
1073
1074         hammer2_print(dvp, NULL, root, depth, "enter");
1075         if (stat(dir, &st) == -1)
1076                 err(1, "no such path %s", dir);
1077         if (!S_ISDIR(st.st_mode))
1078                 errx(1, "no such dir %s", dir);
1079
1080         for (cur = root->next; cur != NULL; cur = cur->next) {
1081                 /* global variable for HAMMER2 vnops */
1082                 hammer2_curnode = cur;
1083
1084                 /* construct source path */
1085                 if (cur->contents) {
1086                         path = cur->contents;
1087                 } else {
1088                         if (snprintf(f, sizeof(f), "%s/%s/%s",
1089                             cur->root, cur->path, cur->name) >= (int)sizeof(f))
1090                                 errx(1, "path %s too long", f);
1091                         path = f;
1092                 }
1093                 if (S_ISLNK(cur->type)) {
1094                         if (lstat(path, &st) == -1)
1095                                 err(1, "no such symlink %s", path);
1096                 } else {
1097                         if (stat(path, &st) == -1)
1098                                 err(1, "no such path %s", path);
1099                 }
1100
1101                 /* update node state */
1102                 if ((cur->inode->flags & FI_ALLOCATED) == 0) {
1103                         cur->inode->flags |= FI_ALLOCATED;
1104                         if (cur != root)
1105                                 cur->parent = parent;
1106                 }
1107
1108                 /* detect hardlink */
1109                 if (cur->inode->flags & FI_WRITTEN) {
1110                         assert(!S_ISDIR(cur->type));
1111                         hardlink = 1;
1112                 } else {
1113                         hardlink = 0;
1114                 }
1115                 cur->inode->flags |= FI_WRITTEN;
1116
1117                 /* make sure it doesn't exist yet */
1118                 vp = NULL;
1119                 error = hammer2_nresolve(dvp, &vp, cur->name,
1120                     strlen(cur->name));
1121                 if (!error)
1122                         errx(1, "hammer2_nresolve(\"%s\") already exists",
1123                             cur->name);
1124                 hammer2_print(dvp, vp, cur, depth, "nresolve");
1125
1126                 /* if directory, mkdir and recurse */
1127                 if (S_ISDIR(cur->type)) {
1128                         assert(cur->child);
1129
1130                         vp = NULL;
1131                         error = hammer2_nmkdir(dvp, &vp, cur->name,
1132                             strlen(cur->name), cur->inode->st.st_mode);
1133                         if (error)
1134                                 errx(1, "hammer2_nmkdir(\"%s\") failed: %s",
1135                                     cur->name, strerror(error));
1136                         assert(vp);
1137                         hammer2_print(dvp, vp, cur, depth, "nmkdir");
1138
1139                         error = hammer2_populate_dir(vp, path, cur->child, cur,
1140                             fsopts, depth + 1);
1141                         if (error)
1142                                 errx(1, "failed to populate %s: %s",
1143                                     path, strerror(error));
1144                         cur->inode->param = vp;
1145                         continue;
1146                 }
1147
1148                 /* if regular file, creat and write its data */
1149                 if (S_ISREG(cur->type) && !hardlink) {
1150                         assert(cur->child == NULL);
1151
1152                         vp = NULL;
1153                         error = hammer2_ncreate(dvp, &vp, cur->name,
1154                             strlen(cur->name), cur->inode->st.st_mode);
1155                         if (error)
1156                                 errx(1, "hammer2_ncreate(\"%s\") failed: %s",
1157                                     cur->name, strerror(error));
1158                         assert(vp);
1159                         hammer2_print(dvp, vp, cur, depth, "ncreate");
1160
1161                         error = hammer2_write_file(vp, path, cur);
1162                         if (error)
1163                                 errx(1, "hammer2_write_file(\"%s\") failed: %s",
1164                                     path, strerror(error));
1165                         cur->inode->param = vp;
1166                         continue;
1167                 }
1168
1169                 /* if symlink, create a symlink against target */
1170                 if (S_ISLNK(cur->type)) {
1171                         assert(cur->child == NULL);
1172
1173                         vp = NULL;
1174                         error = hammer2_nsymlink(dvp, &vp, cur->name,
1175                             strlen(cur->name), cur->symlink,
1176                             cur->inode->st.st_mode);
1177                         if (error)
1178                                 errx(1, "hammer2_nsymlink(\"%s\") failed: %s",
1179                                     cur->name, strerror(error));
1180                         assert(vp);
1181                         hammer2_print(dvp, vp, cur, depth, "nsymlink");
1182                         cur->inode->param = vp;
1183                         continue;
1184                 }
1185
1186                 /* if fifo, create a fifo */
1187                 if (S_ISFIFO(cur->type) && !hardlink) {
1188                         assert(cur->child == NULL);
1189
1190                         vp = NULL;
1191                         error = hammer2_nmknod(dvp, &vp, cur->name,
1192                             strlen(cur->name), VFIFO, cur->inode->st.st_mode);
1193                         if (error)
1194                                 errx(1, "hammer2_nmknod(\"%s\") failed: %s",
1195                                     cur->name, strerror(error));
1196                         assert(vp);
1197                         hammer2_print(dvp, vp, cur, depth, "nmknod");
1198                         cur->inode->param = vp;
1199                         continue;
1200                 }
1201
1202                 /* if hardlink, creat a hardlink */
1203                 if ((S_ISREG(cur->type) || S_ISFIFO(cur->type)) && hardlink) {
1204                         char buf[64];
1205                         assert(cur->child == NULL);
1206
1207                         /* source vnode must not be NULL */
1208                         vp = cur->inode->param;
1209                         assert(vp);
1210                         /* currently these conditions must be true */
1211                         assert(vp->v_data);
1212                         assert(vp->v_type == VREG || vp->v_type == VFIFO);
1213                         assert(vp->v_logical);
1214                         assert(!vp->v_vflushed);
1215                         assert(vp->v_malloced);
1216                         assert(VTOI(vp)->refs > 0);
1217
1218                         error = hammer2_nlink(dvp, vp, cur->name,
1219                             strlen(cur->name));
1220                         if (error)
1221                                 errx(1, "hammer2_nlink(\"%s\") failed: %s",
1222                                     cur->name, strerror(error));
1223                         snprintf(buf, sizeof(buf), "nlink=%lld",
1224                             (long long)VTOI(vp)->meta.nlinks);
1225                         hammer2_print(dvp, vp, cur, depth, buf);
1226                         continue;
1227                 }
1228
1229                 /* other types are unsupported */
1230                 printf("ignore %s 0%o\n", path, cur->type);
1231         }
1232
1233         return 0;
1234 }
1235
1236 static int
1237 hammer2_write_file(struct m_vnode *vp, const char *path, fsnode *node)
1238 {
1239         struct stat *st = &node->inode->st;
1240         size_t nsize, bufsize;
1241         off_t offset;
1242         int fd, error;
1243         char *p;
1244
1245         nsize = st->st_size;
1246         if (nsize == 0)
1247                 return 0;
1248         /* check nsize vs maximum file size */
1249
1250         fd = open(path, O_RDONLY);
1251         if (fd < 0)
1252                 err(1, "failed to open %s", path);
1253
1254         p = mmap(0, nsize, PROT_READ, MAP_FILE|MAP_PRIVATE, fd, 0);
1255         if (p == MAP_FAILED)
1256                 err(1, "failed to mmap %s", path);
1257         close(fd);
1258
1259         for (offset = 0; offset < nsize; ) {
1260                 bufsize = MIN(nsize - offset, HAMMER2_PBUFSIZE);
1261                 assert(bufsize <= HAMMER2_PBUFSIZE);
1262                 error = hammer2_write(vp, p + offset, bufsize, offset);
1263                 if (error)
1264                         errx(1, "failed to write to %s vnode: %s",
1265                             path, strerror(error));
1266                 offset += bufsize;
1267                 if (bufsize == HAMMER2_PBUFSIZE)
1268                         assert((offset & (HAMMER2_PBUFSIZE - 1)) == 0);
1269         }
1270         munmap(p, nsize);
1271
1272         return 0;
1273 }
1274
1275 static int
1276 trim_char(char *p, char c)
1277 {
1278         char *o, tmp[PATH_MAX];
1279         bool prev_was_c;
1280         size_t n;
1281         int i;
1282
1283         assert(p);
1284         /* nothing to do */
1285         if (strlen(p) == 0)
1286                 return 0;
1287
1288         strlcpy(tmp, p, sizeof(tmp));
1289         if (strncmp(tmp, p, sizeof(tmp)))
1290                 return ENOSPC;
1291
1292         /* trim consecutive */
1293         prev_was_c = false;
1294         o = p;
1295         n = strlen(p);
1296
1297         for (i = 0; i < n; i++) {
1298                 if (tmp[i] == c) {
1299                         if (!prev_was_c)
1300                                 *p++ = tmp[i];
1301                         prev_was_c = true;
1302                 } else {
1303                         *p++ = tmp[i];
1304                         prev_was_c = false;
1305                 }
1306         }
1307         *p = 0;
1308         assert(strlen(p) <= strlen(tmp));
1309
1310         /* assert no consecutive */
1311         prev_was_c = false;
1312         p = o;
1313         n = strlen(p);
1314
1315         for (i = 0; i < n; i++) {
1316                 if (p[i] == c) {
1317                         assert(!prev_was_c);
1318                         prev_was_c = true;
1319                 } else {
1320                         prev_was_c = false;
1321                 }
1322         }
1323
1324         /* trim leading */
1325         if (*p == c)
1326                 memmove(p, p + 1, strlen(p + 1) + 1);
1327         assert(*p != '/');
1328
1329         /* trim trailing */
1330         p += strlen(p);
1331         p--;
1332         if (*p == c)
1333                 *p = 0;
1334         assert(p[strlen(p) - 1] != '/');
1335
1336         return 0;
1337 }
1338
1339 static int
1340 trim_slash(char *p)
1341 {
1342         return trim_char(p, '/');
1343 }
1344
1345 static bool
1346 is_supported_link(const char *s)
1347 {
1348         /* absolute path can't be supported */
1349         if (strlen(s) >= 1 && strncmp(s, "/", 1) == 0)
1350                 return false;
1351
1352         /* XXX ".." is currently unsupported */
1353         if (strlen(s) >= 3 && strncmp(s, "../", 3) == 0)
1354                 return false;
1355
1356         return true;
1357 }
1358
1359 static int
1360 hammer2_version_get(struct m_vnode *vp)
1361 {
1362         hammer2_dev_t *hmp;
1363
1364         hmp = VTOI(vp)->pmp->pfs_hmps[0];
1365         if (hmp == NULL)
1366                 return EINVAL;
1367
1368         printf("version: %d\n", hmp->voldata.version);
1369
1370         return 0;
1371 }
1372
1373 struct pfs_entry {
1374         TAILQ_ENTRY(pfs_entry) entry;
1375         char name[NAME_MAX+1];
1376         char s[NAME_MAX+1];
1377 };
1378
1379 static int
1380 hammer2_pfs_get(struct m_vnode *vp)
1381 {
1382         hammer2_ioc_pfs_t pfs;
1383         TAILQ_HEAD(, pfs_entry) head;
1384         struct pfs_entry *p, *e;
1385         char *pfs_id_str;
1386         const char *type_str;
1387         int error;
1388
1389         bzero(&pfs, sizeof(pfs));
1390         TAILQ_INIT(&head);
1391
1392         while ((pfs.name_key = pfs.name_next) != (hammer2_key_t)-1) {
1393                 error = hammer2_ioctl_pfs_get(VTOI(vp), &pfs);
1394                 if (error)
1395                         return error;
1396
1397                 pfs_id_str = NULL;
1398                 hammer2_uuid_to_str(&pfs.pfs_clid, &pfs_id_str);
1399
1400                 if (pfs.pfs_type == HAMMER2_PFSTYPE_MASTER) {
1401                         if (pfs.pfs_subtype == HAMMER2_PFSSUBTYPE_NONE)
1402                                 type_str = "MASTER";
1403                         else
1404                                 type_str = hammer2_pfssubtype_to_str(
1405                                     pfs.pfs_subtype);
1406                 } else {
1407                         type_str = hammer2_pfstype_to_str(pfs.pfs_type);
1408                 }
1409                 e = ecalloc(1, sizeof(*e));
1410                 snprintf(e->name, sizeof(e->name), "%s", pfs.name);
1411                 snprintf(e->s, sizeof(e->s), "%-11s %s", type_str, pfs_id_str);
1412                 free(pfs_id_str);
1413
1414                 p = TAILQ_FIRST(&head);
1415                 while (p) {
1416                         if (strcmp(e->name, p->name) <= 0) {
1417                                 TAILQ_INSERT_BEFORE(p, e, entry);
1418                                 break;
1419                         }
1420                         p = TAILQ_NEXT(p, entry);
1421                 }
1422                 if (!p)
1423                         TAILQ_INSERT_TAIL(&head, e, entry);
1424         }
1425
1426         printf("Type        "
1427             "ClusterId (pfs_clid)                 "
1428             "Label\n");
1429         while ((p = TAILQ_FIRST(&head)) != NULL) {
1430                 printf("%s %s\n", p->s, p->name);
1431                 TAILQ_REMOVE(&head, p, entry);
1432                 free(p);
1433         }
1434
1435         return 0;
1436 }
1437
1438 static int
1439 hammer2_pfs_lookup(struct m_vnode *vp, const char *pfs_name)
1440 {
1441         hammer2_ioc_pfs_t pfs;
1442         char *pfs_id_str;
1443         int error;
1444
1445         bzero(&pfs, sizeof(pfs));
1446         strlcpy(pfs.name, pfs_name, sizeof(pfs.name));
1447
1448         error = hammer2_ioctl_pfs_lookup(VTOI(vp), &pfs);
1449         if (error == 0) {
1450                 printf("name: %s\n", pfs.name);
1451                 printf("type: %s\n", hammer2_pfstype_to_str(pfs.pfs_type));
1452                 printf("subtype: %s\n",
1453                     hammer2_pfssubtype_to_str(pfs.pfs_subtype));
1454
1455                 pfs_id_str = NULL;
1456                 hammer2_uuid_to_str(&pfs.pfs_fsid, &pfs_id_str);
1457                 printf("fsid: %s\n", pfs_id_str);
1458                 free(pfs_id_str);
1459
1460                 pfs_id_str = NULL;
1461                 hammer2_uuid_to_str(&pfs.pfs_clid, &pfs_id_str);
1462                 printf("clid: %s\n", pfs_id_str);
1463                 free(pfs_id_str);
1464         }
1465
1466         return error;
1467 }
1468
1469 static int
1470 hammer2_pfs_create(struct m_vnode *vp, const char *pfs_name)
1471 {
1472         hammer2_ioc_pfs_t pfs;
1473         int error;
1474
1475         bzero(&pfs, sizeof(pfs));
1476         strlcpy(pfs.name, pfs_name, sizeof(pfs.name));
1477         pfs.pfs_type = HAMMER2_PFSTYPE_MASTER;
1478         uuid_create(&pfs.pfs_clid, NULL);
1479         uuid_create(&pfs.pfs_fsid, NULL);
1480
1481         error = hammer2_ioctl_pfs_create(VTOI(vp), &pfs);
1482         if (error == EEXIST)
1483                 fprintf(stderr,
1484                     "NOTE: Typically the same name is "
1485                     "used for cluster elements on "
1486                     "different mounts,\n"
1487                     "      but cluster elements on the "
1488                     "same mount require unique names.\n"
1489                     "hammer2: pfs_create(%s): already present\n",
1490                     pfs_name);
1491
1492         return error;
1493 }
1494
1495 static int
1496 hammer2_pfs_delete(struct m_vnode *vp, const char *pfs_name)
1497 {
1498         hammer2_ioc_pfs_t pfs;
1499
1500         bzero(&pfs, sizeof(pfs));
1501         strlcpy(pfs.name, pfs_name, sizeof(pfs.name));
1502
1503         return hammer2_ioctl_pfs_delete(VTOI(vp), &pfs);
1504 }
1505
1506 static int
1507 hammer2_pfs_snapshot(struct m_vnode *vp, const char *pfs_name,
1508     const char *mount_label)
1509 {
1510         hammer2_ioc_pfs_t pfs;
1511         struct tm *tp;
1512         time_t t;
1513
1514         bzero(&pfs, sizeof(pfs));
1515         strlcpy(pfs.name, pfs_name, sizeof(pfs.name));
1516
1517         if (strlen(pfs.name) == 0) {
1518                 time(&t);
1519                 tp = localtime(&t);
1520                 snprintf(pfs.name, sizeof(pfs.name),
1521                     "%s.%04d%02d%02d.%02d%02d%02d",
1522                     mount_label,
1523                     tp->tm_year + 1900,
1524                     tp->tm_mon + 1,
1525                     tp->tm_mday,
1526                     tp->tm_hour,
1527                     tp->tm_min,
1528                     tp->tm_sec);
1529         }
1530
1531         return hammer2_ioctl_pfs_snapshot(VTOI(vp), &pfs);
1532 }
1533
1534 static int
1535 hammer2_inode_getx(struct m_vnode *dvp, const char *f)
1536 {
1537         hammer2_ioc_inode_t inode;
1538         hammer2_inode_t *ip;
1539         hammer2_inode_meta_t *meta;
1540         struct m_vnode *vp;
1541         char *o, *p, *name, *str = NULL;
1542         char tmp[PATH_MAX];
1543         int error;
1544         uuid_t uuid;
1545
1546         assert(strlen(f) > 0);
1547         o = p = name = strdup(f);
1548
1549         error = trim_slash(p);
1550         if (error)
1551                 return error;
1552         if (strlen(p) == 0) {
1553                 vp = dvp;
1554                 goto start_ioctl;
1555         }
1556
1557         while ((p = strchr(p, '/')) != NULL) {
1558                 *p++ = 0; /* NULL terminate name */
1559                 if (!strcmp(name, ".")) {
1560                         name = p;
1561                         continue;
1562                 }
1563                 vp = NULL;
1564                 error = hammer2_nresolve(dvp, &vp, name, strlen(name));
1565                 if (error)
1566                         return error;
1567
1568                 ip = VTOI(vp);
1569                 switch (ip->meta.type) {
1570                 case HAMMER2_OBJTYPE_DIRECTORY:
1571                         break;
1572                 case HAMMER2_OBJTYPE_SOFTLINK:
1573                         bzero(tmp, sizeof(tmp));
1574                         error = hammer2_readlink(vp, tmp, sizeof(tmp));
1575                         if (error)
1576                                 return error;
1577                         if (!is_supported_link(tmp))
1578                                 return EINVAL;
1579                         strlcat(tmp, "/", sizeof(tmp));
1580                         strlcat(tmp, p, sizeof(tmp));
1581                         error = trim_slash(tmp);
1582                         if (error)
1583                                 return error;
1584                         p = name = tmp;
1585                         continue;
1586                 default:
1587                         return EINVAL;
1588                 }
1589
1590                 dvp = vp;
1591                 name = p;
1592         }
1593
1594         error = hammer2_nresolve(dvp, &vp, name, strlen(name));
1595         if (error)
1596                 return error;
1597 start_ioctl:
1598         bzero(&inode, sizeof(inode));
1599         error = hammer2_ioctl_inode_get(VTOI(vp), &inode);
1600         if (error)
1601                 return error;
1602
1603         meta = &inode.ip_data.meta;
1604         printf("--------------------\n");
1605         printf("flags = 0x%x\n", inode.flags);
1606         printf("data_count = %ju\n", (uintmax_t)inode.data_count);
1607         printf("inode_count = %ju\n", (uintmax_t)inode.inode_count);
1608         printf("--------------------\n");
1609         printf("version = %u\n", meta->version);
1610         printf("pfs_subtype = %u (%s)\n", meta->pfs_subtype,
1611             hammer2_pfssubtype_to_str(meta->pfs_subtype));
1612         printf("uflags = 0x%x\n", (unsigned int)meta->uflags);
1613         printf("rmajor = %u\n", meta->rmajor);
1614         printf("rminor = %u\n", meta->rminor);
1615         printf("ctime = %s\n", hammer2_time64_to_str(meta->ctime, &str));
1616         printf("mtime = %s\n", hammer2_time64_to_str(meta->mtime, &str));
1617         printf("atime = %s\n", hammer2_time64_to_str(meta->atime, &str));
1618         printf("btime = %s\n", hammer2_time64_to_str(meta->btime, &str));
1619         uuid = meta->uid;
1620         printf("uid = %s\n", hammer2_uuid_to_str(&uuid, &str));
1621         uuid = meta->gid;
1622         printf("gid = %s\n", hammer2_uuid_to_str(&uuid, &str));
1623         printf("type = %u (%s)\n", meta->type,
1624             hammer2_iptype_to_str(meta->type));
1625         printf("op_flags = 0x%x\n", meta->op_flags);
1626         printf("cap_flags = 0x%x\n", meta->cap_flags);
1627         printf("mode = 0%o\n", meta->mode);
1628         printf("inum = 0x%jx\n", (uintmax_t)meta->inum);
1629         printf("size = %ju\n", (uintmax_t)meta->size);
1630         printf("nlinks = %ju\n", (uintmax_t)meta->nlinks);
1631         printf("iparent = 0x%jx\n", (uintmax_t)meta->iparent);
1632         printf("name_key = 0x%jx\n", (uintmax_t)meta->name_key);
1633         printf("name_len = %u\n", meta->name_len);
1634         printf("ncopies = %u\n", meta->ncopies);
1635         printf("comp_algo = 0x%jx\n", (uintmax_t)meta->comp_algo);
1636         printf("target_type = %u\n", meta->target_type);
1637         printf("check_algo = %u\n", meta->check_algo);
1638         printf("pfs_nmasters = %u\n", meta->pfs_nmasters);
1639         printf("pfs_type = %u (%s)\n", meta->pfs_type,
1640             hammer2_pfstype_to_str(meta->pfs_type));
1641         printf("pfs_inum = 0x%jx\n", (uintmax_t)meta->pfs_inum);
1642         uuid = meta->pfs_clid;
1643         printf("pfs_clid = %s\n", hammer2_uuid_to_str(&uuid, &str));
1644         uuid = meta->pfs_fsid;
1645         printf("pfs_fsid = %s\n", hammer2_uuid_to_str(&uuid, &str));
1646         printf("data_quota = 0x%jx\n", (uintmax_t)meta->data_quota);
1647         printf("inode_quota = 0x%jx\n", (uintmax_t)meta->inode_quota);
1648         printf("pfs_lsnap_tid = 0x%jx\n", (uintmax_t)meta->pfs_lsnap_tid);
1649         printf("decrypt_check = 0x%jx\n", (uintmax_t)meta->decrypt_check);
1650         printf("--------------------\n");
1651
1652         free(o);
1653
1654         return error;
1655 }
1656
1657 static int
1658 hammer2_inode_setcheck(struct m_vnode *dvp, const char *f)
1659 {
1660         hammer2_ioc_inode_t inode;
1661         hammer2_inode_t *ip;
1662         struct m_vnode *vp;
1663         char *o, *p, *name, *check_algo_str;
1664         char tmp[PATH_MAX];
1665         const char *checks[] = { "none", "disabled", "crc32", "xxhash64",
1666             "sha192", };
1667         int check_algo_idx, error;
1668         uint8_t check_algo;
1669
1670         assert(strlen(f) > 0);
1671         o = p = strdup(f);
1672
1673         p = strrchr(p, ':');
1674         if (p == NULL)
1675                 return EINVAL;
1676
1677         *p++ = 0; /* NULL terminate path */
1678         check_algo_str = p;
1679         name = p = o;
1680
1681         /* fail if already empty before trim */
1682         if (strlen(p) == 0)
1683                 return EINVAL;
1684
1685         error = trim_slash(p);
1686         if (error)
1687                 return error;
1688         if (strlen(check_algo_str) == 0)
1689                 return EINVAL;
1690
1691         /* convert check_algo_str to check_algo_idx */
1692         check_algo_idx = nitems(checks);
1693         while (--check_algo_idx >= 0)
1694                 if (strcasecmp(check_algo_str, checks[check_algo_idx]) == 0)
1695                         break;
1696         if (check_algo_idx < 0) {
1697                 if (strcasecmp(check_algo_str, "default") == 0) {
1698                         check_algo_str = "xxhash64";
1699                         check_algo_idx = HAMMER2_CHECK_XXHASH64;
1700                 } else if (strcasecmp(check_algo_str, "disabled") == 0) {
1701                         check_algo_str = "disabled";
1702                         check_algo_idx = HAMMER2_CHECK_DISABLED;
1703                 } else {
1704                         printf("invalid check_algo_str: %s\n", check_algo_str);
1705                         return EINVAL;
1706                 }
1707         }
1708         check_algo = HAMMER2_ENC_ALGO(check_algo_idx);
1709         printf("change %s to algo %d (%s)\n", p, check_algo, check_algo_str);
1710
1711         if (strlen(p) == 0) {
1712                 vp = dvp;
1713                 goto start_ioctl;
1714         }
1715
1716         while ((p = strchr(p, '/')) != NULL) {
1717                 *p++ = 0; /* NULL terminate name */
1718                 if (!strcmp(name, ".")) {
1719                         name = p;
1720                         continue;
1721                 }
1722                 vp = NULL;
1723                 error = hammer2_nresolve(dvp, &vp, name, strlen(name));
1724                 if (error)
1725                         return error;
1726
1727                 ip = VTOI(vp);
1728                 switch (ip->meta.type) {
1729                 case HAMMER2_OBJTYPE_DIRECTORY:
1730                         break;
1731                 case HAMMER2_OBJTYPE_SOFTLINK:
1732                         bzero(tmp, sizeof(tmp));
1733                         error = hammer2_readlink(vp, tmp, sizeof(tmp));
1734                         if (error)
1735                                 return error;
1736                         if (!is_supported_link(tmp))
1737                                 return EINVAL;
1738                         strlcat(tmp, "/", sizeof(tmp));
1739                         strlcat(tmp, p, sizeof(tmp));
1740                         error = trim_slash(tmp);
1741                         if (error)
1742                                 return error;
1743                         p = name = tmp;
1744                         continue;
1745                 default:
1746                         return EINVAL;
1747                 }
1748
1749                 dvp = vp;
1750                 name = p;
1751         }
1752
1753         error = hammer2_nresolve(dvp, &vp, name, strlen(name));
1754         if (error)
1755                 return error;
1756 start_ioctl:
1757         ip = VTOI(vp);
1758
1759         bzero(&inode, sizeof(inode));
1760         error = hammer2_ioctl_inode_get(ip, &inode);
1761         if (error)
1762                 return error;
1763
1764         inode.flags |= HAMMER2IOC_INODE_FLAG_CHECK;
1765         inode.ip_data.meta.check_algo = check_algo;
1766         error = hammer2_ioctl_inode_set(ip, &inode);
1767         if (error)
1768                 return error;
1769
1770         free(o);
1771
1772         return error;
1773 }
1774
1775 static int
1776 hammer2_inode_setcomp(struct m_vnode *dvp, const char *f)
1777 {
1778         hammer2_ioc_inode_t inode;
1779         hammer2_inode_t *ip;
1780         struct m_vnode *vp;
1781         char *o, *p, *name, *comp_algo_str, *comp_level_str;
1782         char tmp[PATH_MAX];
1783         const char *comps[] = { "none", "autozero", "lz4", "zlib", };
1784         int comp_algo_idx, comp_level_idx, error;
1785         uint8_t comp_algo, comp_level;
1786
1787         assert(strlen(f) > 0);
1788         o = p = strdup(f);
1789
1790         p = strrchr(p, ':');
1791         if (p == NULL)
1792                 return EINVAL;
1793
1794         *p++ = 0; /* NULL terminate comp_algo_str */
1795         comp_level_str = p;
1796         p = o;
1797
1798         p = strrchr(p, ':');
1799         if (p == NULL) {
1800                 /* comp_level_str not specified */
1801                 comp_algo_str = comp_level_str;
1802                 comp_level_str = NULL;
1803         } else {
1804                 *p++ = 0; /* NULL terminate path */
1805                 comp_algo_str = p;
1806         }
1807         name = p = o;
1808
1809         /* fail if already empty before trim */
1810         if (strlen(p) == 0)
1811                 return EINVAL;
1812
1813         error = trim_slash(p);
1814         if (error)
1815                 return error;
1816         if (strlen(comp_algo_str) == 0)
1817                 return EINVAL;
1818
1819         /* convert comp_algo_str to comp_algo_idx */
1820         comp_algo_idx = nitems(comps);
1821         while (--comp_algo_idx >= 0)
1822                 if (strcasecmp(comp_algo_str, comps[comp_algo_idx]) == 0)
1823                         break;
1824         if (comp_algo_idx < 0) {
1825                 if (strcasecmp(comp_algo_str, "default") == 0) {
1826                         comp_algo_str = "lz4";
1827                         comp_algo_idx = HAMMER2_COMP_LZ4;
1828                 } else if (strcasecmp(comp_algo_str, "disabled") == 0) {
1829                         comp_algo_str = "autozero";
1830                         comp_algo_idx = HAMMER2_COMP_AUTOZERO;
1831                 } else {
1832                         printf("invalid comp_algo_str: %s\n", comp_algo_str);
1833                         return EINVAL;
1834                 }
1835         }
1836         comp_algo = HAMMER2_ENC_ALGO(comp_algo_idx);
1837
1838         /* convert comp_level_str to comp_level_idx */
1839         if (comp_level_str == NULL) {
1840                 comp_level_idx = 0;
1841         } else if (isdigit((int)comp_level_str[0])) {
1842                 comp_level_idx = strtol(comp_level_str, NULL, 0);
1843         } else if (strcasecmp(comp_level_str, "default") == 0) {
1844                 comp_level_idx = 0;
1845         } else {
1846                 printf("invalid comp_level_str: %s\n", comp_level_str);
1847                 return EINVAL;
1848         }
1849         if (comp_level_idx) {
1850                 switch (comp_algo) {
1851                 case HAMMER2_COMP_ZLIB:
1852                         if (comp_level_idx < 6 || comp_level_idx > 9) {
1853                                 printf("unsupported comp_level %d for %s\n",
1854                                     comp_level_idx, comp_algo_str);
1855                                 return EINVAL;
1856                         }
1857                         break;
1858                 default:
1859                         printf("unsupported comp_level %d for %s\n",
1860                             comp_level_idx, comp_algo_str);
1861                         return EINVAL;
1862                 }
1863         }
1864         comp_level = HAMMER2_ENC_LEVEL(comp_level_idx);
1865         printf("change %s to algo %d (%s) level %d\n",
1866             p, comp_algo, comp_algo_str, comp_level_idx);
1867
1868         if (strlen(p) == 0) {
1869                 vp = dvp;
1870                 goto start_ioctl;
1871         }
1872
1873         while ((p = strchr(p, '/')) != NULL) {
1874                 *p++ = 0; /* NULL terminate name */
1875                 if (!strcmp(name, ".")) {
1876                         name = p;
1877                         continue;
1878                 }
1879                 vp = NULL;
1880                 error = hammer2_nresolve(dvp, &vp, name, strlen(name));
1881                 if (error)
1882                         return error;
1883
1884                 ip = VTOI(vp);
1885                 switch (ip->meta.type) {
1886                 case HAMMER2_OBJTYPE_DIRECTORY:
1887                         break;
1888                 case HAMMER2_OBJTYPE_SOFTLINK:
1889                         bzero(tmp, sizeof(tmp));
1890                         error = hammer2_readlink(vp, tmp, sizeof(tmp));
1891                         if (error)
1892                                 return error;
1893                         if (!is_supported_link(tmp))
1894                                 return EINVAL;
1895                         strlcat(tmp, "/", sizeof(tmp));
1896                         strlcat(tmp, p, sizeof(tmp));
1897                         error = trim_slash(tmp);
1898                         if (error)
1899                                 return error;
1900                         p = name = tmp;
1901                         continue;
1902                 default:
1903                         return EINVAL;
1904                 }
1905
1906                 dvp = vp;
1907                 name = p;
1908         }
1909
1910         error = hammer2_nresolve(dvp, &vp, name, strlen(name));
1911         if (error)
1912                 return error;
1913 start_ioctl:
1914         ip = VTOI(vp);
1915
1916         bzero(&inode, sizeof(inode));
1917         error = hammer2_ioctl_inode_get(ip, &inode);
1918         if (error)
1919                 return error;
1920
1921         inode.flags |= HAMMER2IOC_INODE_FLAG_COMP;
1922         inode.ip_data.meta.comp_algo = comp_algo | comp_level;
1923         error = hammer2_ioctl_inode_set(ip, &inode);
1924         if (error)
1925                 return error;
1926
1927         free(o);
1928
1929         return error;
1930 }
1931
1932 static int
1933 hammer2_bulkfree(struct m_vnode *vp)
1934 {
1935         hammer2_ioc_bulkfree_t bfi;
1936         size_t usermem;
1937         size_t usermem_size = sizeof(usermem);
1938
1939         bzero(&bfi, sizeof(bfi));
1940         usermem = 0;
1941         if (sysctlbyname("hw.usermem", &usermem, &usermem_size, NULL, 0) == 0)
1942                 bfi.size = usermem / 16;
1943         else
1944                 bfi.size = 0;
1945         if (bfi.size < 8192 * 1024)
1946                 bfi.size = 8192 * 1024;
1947
1948         return hammer2_ioctl_bulkfree_scan(VTOI(vp), &bfi);
1949 }
1950
1951 static int
1952 hammer2_destroy_path(struct m_vnode *dvp, const char *f)
1953 {
1954         hammer2_ioc_destroy_t destroy;
1955         hammer2_inode_t *ip;
1956         struct m_vnode *vp;
1957         char *o, *p, *name;
1958         char tmp[PATH_MAX];
1959         int error;
1960
1961         assert(strlen(f) > 0);
1962         o = p = name = strdup(f);
1963
1964         error = trim_slash(p);
1965         if (error)
1966                 return error;
1967         if (strlen(p) == 0)
1968                 return EINVAL;
1969
1970         while ((p = strchr(p, '/')) != NULL) {
1971                 *p++ = 0; /* NULL terminate name */
1972                 if (!strcmp(name, ".")) {
1973                         name = p;
1974                         continue;
1975                 }
1976                 vp = NULL;
1977                 error = hammer2_nresolve(dvp, &vp, name, strlen(name));
1978                 if (error)
1979                         return error;
1980
1981                 ip = VTOI(vp);
1982                 switch (ip->meta.type) {
1983                 case HAMMER2_OBJTYPE_DIRECTORY:
1984                         break;
1985                 case HAMMER2_OBJTYPE_SOFTLINK:
1986                         bzero(tmp, sizeof(tmp));
1987                         error = hammer2_readlink(vp, tmp, sizeof(tmp));
1988                         if (error)
1989                                 return error;
1990                         if (!is_supported_link(tmp))
1991                                 return EINVAL;
1992                         strlcat(tmp, "/", sizeof(tmp));
1993                         strlcat(tmp, p, sizeof(tmp));
1994                         error = trim_slash(tmp);
1995                         if (error)
1996                                 return error;
1997                         p = name = tmp;
1998                         continue;
1999                 default:
2000                         return EINVAL;
2001                 }
2002
2003                 dvp = vp;
2004                 name = p;
2005         }
2006
2007         /* XXX When does (or why does not) ioctl modify this inode ? */
2008         hammer2_inode_modify(VTOI(dvp));
2009
2010         bzero(&destroy, sizeof(destroy));
2011         destroy.cmd = HAMMER2_DELETE_FILE;
2012         snprintf(destroy.path, sizeof(destroy.path), "%s", name);
2013
2014         printf("%s\t", f);
2015         fflush(stdout);
2016
2017         error = hammer2_ioctl_destroy(VTOI(dvp), &destroy);
2018         if (error)
2019                 printf("%s\n", strerror(error));
2020         else
2021                 printf("ok\n");
2022         free(o);
2023
2024         return error;
2025 }
2026
2027 static int
2028 hammer2_destroy_inum(struct m_vnode *vp, hammer2_tid_t inum)
2029 {
2030         hammer2_ioc_destroy_t destroy;
2031         int error;
2032
2033         bzero(&destroy, sizeof(destroy));
2034         destroy.cmd = HAMMER2_DELETE_INUM;
2035         destroy.inum = inum;
2036
2037         printf("%jd\t", (intmax_t)destroy.inum);
2038         fflush(stdout);
2039
2040         error = hammer2_ioctl_destroy(VTOI(vp), &destroy);
2041         if (error)
2042                 printf("%s\n", strerror(error));
2043         else
2044                 printf("ok\n");
2045
2046         return error;
2047 }
2048
2049 static int
2050 hammer2_growfs(struct m_vnode *vp, hammer2_off_t size)
2051 {
2052         hammer2_ioc_growfs_t growfs;
2053         int error;
2054
2055         bzero(&growfs, sizeof(growfs));
2056         growfs.size = size;
2057
2058         error = hammer2_ioctl_growfs(VTOI(vp), &growfs, NULL);
2059         if (!error) {
2060                 if (growfs.modified)
2061                         printf("grown to %016jx\n", (intmax_t)growfs.size);
2062                 else
2063                         printf("no size change - %016jx\n",
2064                             (intmax_t)growfs.size);
2065         }
2066
2067         return error;
2068 }
2069
2070 struct hammer2_link {
2071         TAILQ_ENTRY(hammer2_link) entry;
2072         hammer2_tid_t inum;
2073         uint64_t nlinks;
2074         char path[PATH_MAX];
2075 };
2076
2077 TAILQ_HEAD(hammer2_linkq, hammer2_link);
2078
2079 static void
2080 hammer2_linkq_init(struct hammer2_linkq *linkq)
2081 {
2082         TAILQ_INIT(linkq);
2083 }
2084
2085 static void
2086 hammer2_linkq_cleanup(struct hammer2_linkq *linkq, bool is_root)
2087 {
2088         struct hammer2_link *e;
2089         int count = 0;
2090
2091         /*
2092          * If is_root is true, linkq must be empty, or link count is broken.
2093          * Note that if an image was made by makefs, hardlinks in the source
2094          * directory became hardlinks in the image only if >1 links existed under
2095          * that directory, as makefs doesn't determine hardlink via link count.
2096          */
2097         while ((e = TAILQ_FIRST(linkq)) != NULL) {
2098                 count++;
2099                 TAILQ_REMOVE(linkq, e, entry);
2100                 free(e);
2101         }
2102         assert(TAILQ_EMPTY(linkq));
2103
2104         if (count && is_root)
2105                 errx(1, "%d link entries remained", count);
2106 }
2107
2108 static void
2109 hammer2_linkq_add(struct hammer2_linkq *linkq, hammer2_tid_t inum,
2110     uint64_t nlinks, const char *path)
2111 {
2112         struct hammer2_link *e;
2113         int count = 0;
2114
2115         e = ecalloc(1, sizeof(*e));
2116         e->inum = inum;
2117         e->nlinks = nlinks;
2118         strlcpy(e->path, path, sizeof(e->path));
2119         TAILQ_INSERT_TAIL(linkq, e, entry);
2120
2121         TAILQ_FOREACH(e, linkq, entry)
2122                 if (e->inum == inum)
2123                         count++;
2124         if (count > 1)
2125                 errx(1, "%d link entries exist for inum %jd",
2126                     count, (intmax_t)inum);
2127 }
2128
2129 static void
2130 hammer2_linkq_del(struct hammer2_linkq *linkq, hammer2_tid_t inum)
2131 {
2132         struct hammer2_link *e, *next;
2133
2134         TAILQ_FOREACH_MUTABLE(e, linkq, entry, next)
2135                 if (e->inum == inum) {
2136                         e->nlinks--;
2137                         if (e->nlinks == 1) {
2138                                 TAILQ_REMOVE(linkq, e, entry);
2139                                 free(e);
2140                         }
2141                 }
2142 }
2143
2144 static void
2145 hammer2_utimes(struct m_vnode *vp, const char *f)
2146 {
2147         hammer2_inode_t *ip = VTOI(vp);
2148         struct timeval tv[2];
2149
2150         hammer2_time_to_timeval(ip->meta.atime, &tv[0]);
2151         hammer2_time_to_timeval(ip->meta.mtime, &tv[1]);
2152
2153         utimes(f, tv); /* ignore failure */
2154 }
2155
2156 static int
2157 hammer2_readx_directory(struct m_vnode *dvp, const char *dir, const char *name,
2158     struct hammer2_linkq *linkq)
2159 {
2160         struct m_vnode *vp;
2161         struct dirent *dp;
2162         struct stat st;
2163         char *buf, tmp[PATH_MAX];
2164         off_t offset = 0;
2165         int ndirent = 0;
2166         int eofflag = 0;
2167         int i, error;
2168
2169         snprintf(tmp, sizeof(tmp), "%s/%s", dir, name);
2170         if (stat(tmp, &st) == -1 && mkdir(tmp, 0666) == -1)
2171                 err(1, "failed to mkdir %s", tmp);
2172
2173         buf = ecalloc(1, HAMMER2_PBUFSIZE);
2174
2175         while (!eofflag) {
2176                 error = hammer2_readdir(dvp, buf, HAMMER2_PBUFSIZE, &offset,
2177                     &ndirent, &eofflag);
2178                 if (error)
2179                         errx(1, "failed to readdir");
2180                 dp = (void *)buf;
2181
2182                 for (i = 0; i < ndirent; i++) {
2183                         if (strcmp(dp->d_name, ".") &&
2184                             strcmp(dp->d_name, "..")) {
2185                                 error = hammer2_nresolve(dvp, &vp, dp->d_name,
2186                                     strlen(dp->d_name));
2187                                 if (error)
2188                                         return error;
2189                                 error = hammer2_readx_handle(vp, tmp,
2190                                     dp->d_name, linkq);
2191                                 if (error)
2192                                         return error;
2193                         }
2194                         dp = (void *)((char *)dp +
2195                             _DIRENT_RECLEN(dp->d_namlen));
2196                 }
2197         }
2198
2199         free(buf);
2200         hammer2_utimes(dvp, tmp);
2201
2202         return 0;
2203 }
2204
2205 static int
2206 hammer2_readx_link(struct m_vnode *vp, const char *src, const char *lnk,
2207     struct hammer2_linkq *linkq)
2208 {
2209         hammer2_inode_t *ip = VTOI(vp);
2210         struct stat st;
2211         int error;
2212
2213         if (!stat(lnk, &st)) {
2214                 error = unlink(lnk);
2215                 if (error)
2216                         return error;
2217         }
2218
2219         error = link(src, lnk);
2220         if (error)
2221                 return error;
2222
2223         hammer2_linkq_del(linkq, ip->meta.inum);
2224
2225         return 0;
2226 }
2227
2228 static int
2229 hammer2_readx_regfile(struct m_vnode *vp, const char *dir, const char *name,
2230     struct hammer2_linkq *linkq)
2231 {
2232         hammer2_inode_t *ip = VTOI(vp);
2233         struct hammer2_link *e;
2234         char *buf, out[PATH_MAX];
2235         size_t resid, n;
2236         off_t offset;
2237         int fd, error;
2238         bool found = false;
2239
2240         snprintf(out, sizeof(out), "%s/%s", dir, name);
2241
2242         if (ip->meta.nlinks > 1) {
2243                 TAILQ_FOREACH(e, linkq, entry)
2244                         if (e->inum == ip->meta.inum) {
2245                                 found = true;
2246                                 error = hammer2_readx_link(vp, e->path, out,
2247                                     linkq);
2248                                 if (error == 0)
2249                                         return 0;
2250                                 /* ignore failure */
2251                         }
2252                 if (!found)
2253                         hammer2_linkq_add(linkq, ip->meta.inum, ip->meta.nlinks,
2254                             out);
2255         }
2256
2257         fd = open(out, O_WRONLY | O_CREAT | O_TRUNC, 0666);
2258         if (fd == -1)
2259                 err(1, "failed to create %s", out);
2260
2261         buf = ecalloc(1, HAMMER2_PBUFSIZE);
2262         resid = ip->meta.size;
2263         offset = 0;
2264
2265         while (resid > 0) {
2266                 bzero(buf, HAMMER2_PBUFSIZE);
2267                 error = hammer2_read(vp, buf, HAMMER2_PBUFSIZE, offset);
2268                 if (error)
2269                         errx(1, "failed to read from %s", name);
2270
2271                 n = resid >= HAMMER2_PBUFSIZE ? HAMMER2_PBUFSIZE : resid;
2272                 error = write(fd, buf, n);
2273                 if (error == -1)
2274                         err(1, "failed to write to %s", out);
2275                 else if (error != n)
2276                         return EINVAL;
2277
2278                 resid -= n;
2279                 offset += HAMMER2_PBUFSIZE;
2280         }
2281         fsync(fd);
2282         close(fd);
2283
2284         free(buf);
2285         hammer2_utimes(vp, out);
2286
2287         return 0;
2288 }
2289
2290 static int
2291 hammer2_readx_handle(struct m_vnode *vp, const char *dir, const char *name,
2292     struct hammer2_linkq *linkq)
2293 {
2294         hammer2_inode_t *ip = VTOI(vp);
2295
2296         switch (ip->meta.type) {
2297         case HAMMER2_OBJTYPE_DIRECTORY:
2298                 return hammer2_readx_directory(vp, dir, name, linkq);
2299         case HAMMER2_OBJTYPE_REGFILE:
2300                 return hammer2_readx_regfile(vp, dir, name, linkq);
2301         default:
2302                 /* XXX */
2303                 printf("ignore inode %jd %s \"%s\"\n",
2304                     (intmax_t)ip->meta.inum,
2305                     hammer2_iptype_to_str(ip->meta.type),
2306                     name);
2307                 return 0;
2308         }
2309         return EINVAL;
2310 }
2311
2312 static int
2313 hammer2_readx(struct m_vnode *dvp, const char *dir, const char *f)
2314 {
2315         hammer2_inode_t *ip;
2316         struct hammer2_linkq linkq;
2317         struct m_vnode *vp, *ovp = dvp;
2318         char *o, *p, *name;
2319         char tmp[PATH_MAX];
2320         int error;
2321
2322         if (dir == NULL)
2323                 return EINVAL;
2324
2325         assert(strlen(f) > 0);
2326         o = p = name = strdup(f);
2327
2328         error = trim_slash(p);
2329         if (error)
2330                 return error;
2331         if (strlen(p) == 0) {
2332                 vp = dvp;
2333                 goto start_read;
2334         }
2335
2336         while ((p = strchr(p, '/')) != NULL) {
2337                 *p++ = 0; /* NULL terminate name */
2338                 if (!strcmp(name, ".")) {
2339                         name = p;
2340                         continue;
2341                 }
2342                 vp = NULL;
2343                 error = hammer2_nresolve(dvp, &vp, name, strlen(name));
2344                 if (error)
2345                         return error;
2346
2347                 ip = VTOI(vp);
2348                 switch (ip->meta.type) {
2349                 case HAMMER2_OBJTYPE_DIRECTORY:
2350                         break;
2351                 case HAMMER2_OBJTYPE_SOFTLINK:
2352                         bzero(tmp, sizeof(tmp));
2353                         error = hammer2_readlink(vp, tmp, sizeof(tmp));
2354                         if (error)
2355                                 return error;
2356                         if (!is_supported_link(tmp))
2357                                 return EINVAL;
2358                         strlcat(tmp, "/", sizeof(tmp));
2359                         strlcat(tmp, p, sizeof(tmp));
2360                         error = trim_slash(tmp);
2361                         if (error)
2362                                 return error;
2363                         p = name = tmp;
2364                         continue;
2365                 default:
2366                         return EINVAL;
2367                 }
2368
2369                 dvp = vp;
2370                 name = p;
2371         }
2372
2373         error = hammer2_nresolve(dvp, &vp, name, strlen(name));
2374         if (error)
2375                 return error;
2376 start_read:
2377         hammer2_linkq_init(&linkq);
2378         error = hammer2_readx_handle(vp, dir, name, &linkq);
2379         hammer2_linkq_cleanup(&linkq, vp == ovp);
2380         if (error)
2381                 return error;
2382
2383         free(o);
2384
2385         return 0;
2386 }
2387
2388 static void
2389 assert_trim_slash(const char *input, const char *expected)
2390 {
2391         char tmp[PATH_MAX];
2392         int error;
2393
2394         strlcpy(tmp, input, sizeof(tmp));
2395         error = trim_slash(tmp);
2396         if (error)
2397                 errx(1, "input \"%s\" error %d", input, error);
2398
2399         if (strncmp(tmp, expected, sizeof(tmp)))
2400                 errx(1, "input \"%s\" result \"%s\" vs expected \"%s\"",
2401                     input, tmp, expected);
2402 }
2403
2404 static void
2405 unittest_trim_slash(void)
2406 {
2407         assert_trim_slash("", "");
2408         assert_trim_slash("/", "");
2409         assert_trim_slash("//", "");
2410         assert_trim_slash("///", "");
2411
2412         assert_trim_slash("makefs", "makefs");
2413         assert_trim_slash("/makefs", "makefs");
2414         assert_trim_slash("//makefs", "makefs");
2415         assert_trim_slash("makefs/", "makefs");
2416         assert_trim_slash("makefs//", "makefs");
2417         assert_trim_slash("/makefs/", "makefs");
2418         assert_trim_slash("//makefs//", "makefs");
2419
2420         assert_trim_slash("sys/vfs/hammer2", "sys/vfs/hammer2");
2421         assert_trim_slash("/sys/vfs/hammer2", "sys/vfs/hammer2");
2422         assert_trim_slash("//sys/vfs/hammer2", "sys/vfs/hammer2");
2423         assert_trim_slash("///sys/vfs/hammer2", "sys/vfs/hammer2");
2424         assert_trim_slash("sys/vfs/hammer2/", "sys/vfs/hammer2");
2425         assert_trim_slash("sys/vfs/hammer2//", "sys/vfs/hammer2");
2426         assert_trim_slash("sys/vfs/hammer2///", "sys/vfs/hammer2");
2427         assert_trim_slash("/sys/vfs/hammer2/", "sys/vfs/hammer2");
2428         assert_trim_slash("//sys//vfs//hammer2//", "sys/vfs/hammer2");
2429         assert_trim_slash("///sys///vfs///hammer2///", "sys/vfs/hammer2");
2430
2431         APRINTF("success\n");
2432 }