/* * SPDX-License-Identifier: BSD-3-Clause * * Copyright (c) 2022 Tomohiro Kusumi * Copyright (c) 2011-2022 The DragonFly Project. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * 3. Neither the name of The DragonFly Project nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific, prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #if HAVE_NBTOOL_CONFIG_H #include "nbtool_config.h" #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "makefs.h" #include "hammer2.h" #define APRINTF(X, ...) \ printf("%s: " X, __func__, ## __VA_ARGS__) static void hammer2_parse_pfs_opts(const char *, fsinfo_t *); static void hammer2_parse_inode_opts(const char *, fsinfo_t *); static void hammer2_dump_fsinfo(fsinfo_t *); static int hammer2_create_image(const char *, fsinfo_t *); static int hammer2_populate_dir(struct m_vnode *, const char *, fsnode *, fsnode *, fsinfo_t *, int); static void hammer2_validate(const char *, fsnode *, fsinfo_t *); static void hammer2_size_dir(fsnode *, fsinfo_t *); static int hammer2_write_file(struct m_vnode *, const char *, fsnode *); static int hammer2_version_get(struct m_vnode *); static int hammer2_pfs_get(struct m_vnode *); static int hammer2_pfs_lookup(struct m_vnode *, const char *); static int hammer2_pfs_create(struct m_vnode *, const char *); static int hammer2_pfs_delete(struct m_vnode *, const char *); static int hammer2_pfs_snapshot(struct m_vnode *, const char *, const char *); static int hammer2_inode_getx(struct m_vnode *, const char *); static int hammer2_inode_setcheck(struct m_vnode *, const char *); static int hammer2_inode_setcomp(struct m_vnode *, const char *); static int hammer2_bulkfree(struct m_vnode *); static int hammer2_destroy_path(struct m_vnode *, const char *); static int hammer2_destroy_inum(struct m_vnode *, hammer2_tid_t); static int hammer2_growfs(struct m_vnode *, hammer2_off_t); struct hammer2_linkq; static int hammer2_readx_handle(struct m_vnode *, const char *, const char *, struct hammer2_linkq *); static int hammer2_readx(struct m_vnode *, const char *, const char *); static void unittest_trim_slash(void); fsnode *hammer2_curnode; void hammer2_prep_opts(fsinfo_t *fsopts) { hammer2_makefs_options_t *h2_opt = ecalloc(1, sizeof(*h2_opt)); hammer2_mkfs_options_t *opt = &h2_opt->mkfs_options; const option_t hammer2_options[] = { /* newfs_hammer2(8) compatible options */ { 'b', "BootAreaSize", NULL, OPT_STRBUF, 0, 0, "boot area size" }, { 'r', "AuxAreaSize", NULL, OPT_STRBUF, 0, 0, "aux area size" }, { 'V', "Hammer2Version", NULL, OPT_STRBUF, 0, 0, "file system version" }, { 'L', "Label", NULL, OPT_STRBUF, 0, 0, "PFS label" }, /* makefs(8) specific options */ { 'm', "MountLabel", NULL, OPT_STRBUF, 0, 0, "destination PFS label" }, { 'v', "NumVolhdr", &h2_opt->num_volhdr, OPT_INT32, 1, HAMMER2_NUM_VOLHDRS, "number of volume headers" }, { 'c', "CompressionType", NULL, OPT_STRBUF, 0, 0, "compression type" }, { 'C', "CheckType", NULL, OPT_STRBUF, 0, 0, "check type" }, { 'd', "Hammer2Debug", NULL, OPT_STRBUF, 0, 0, "debug tunable" }, { 'E', "EmergencyMode", &h2_opt->emergency_mode, OPT_BOOL, 0, 0, "emergency mode" }, { 'P', "PFS", NULL, OPT_STRBUF, 0, 0, "offline PFS" }, { 'I', "Inode", NULL, OPT_STRBUF, 0, 0, "offline inode" }, { 'B', "Bulkfree", NULL, OPT_STRBUF, 0, 0, "offline bulkfree" }, { 'D', "Destroy", NULL, OPT_STRBUF, 0, 0, "offline destroy" }, { 'G', "Growfs", NULL, OPT_STRBUF, 0, 0, "offline growfs" }, { 'R', "Read", NULL, OPT_STRBUF, 0, 0, "offline read" }, { .name = NULL }, }; hammer2_mkfs_init(opt); assert(opt->CompType == HAMMER2_COMP_DEFAULT); assert(opt->CheckType == HAMMER2_CHECK_DEFAULT); /* force debug mode for mkfs */ opt->DebugOpt = 1; fsopts->fs_specific = h2_opt; fsopts->fs_options = copy_opts(hammer2_options); fsopts->sectorsize = DEV_BSIZE; } void hammer2_cleanup_opts(fsinfo_t *fsopts) { hammer2_makefs_options_t *h2_opt = fsopts->fs_specific; hammer2_mkfs_options_t *opt = &h2_opt->mkfs_options; hammer2_mkfs_cleanup(opt); free(h2_opt); free(fsopts->fs_options); } int hammer2_parse_opts(const char *option, fsinfo_t *fsopts) { hammer2_makefs_options_t *h2_opt = fsopts->fs_specific; hammer2_mkfs_options_t *opt = &h2_opt->mkfs_options; option_t *hammer2_options = fsopts->fs_options; char buf[1024]; /* > HAMMER2_INODE_MAXNAME */ int i; assert(option != NULL); assert(fsopts != NULL); if (debug & DEBUG_FS_PARSE_OPTS) APRINTF("got `%s'\n", option); i = set_option(hammer2_options, option, buf, sizeof(buf)); if (i == -1) return 0; if (hammer2_options[i].name == NULL) abort(); switch (hammer2_options[i].letter) { case 'b': opt->BootAreaSize = getsize(buf, HAMMER2_NEWFS_ALIGN, HAMMER2_BOOT_MAX_BYTES, 2); break; case 'r': opt->AuxAreaSize = getsize(buf, HAMMER2_NEWFS_ALIGN, HAMMER2_AUX_MAX_BYTES, 2); break; case 'V': if (strlen(buf) == 0) { h2_opt->ioctl_cmd = HAMMER2IOC_VERSION_GET; } else { opt->Hammer2Version = strtol(buf, NULL, 0); if (opt->Hammer2Version < HAMMER2_VOL_VERSION_MIN || opt->Hammer2Version >= HAMMER2_VOL_VERSION_WIP) errx(1, "I don't understand how to format " "HAMMER2 version %d", opt->Hammer2Version); } break; case 'L': h2_opt->label_specified = 1; if (strcasecmp(buf, "none") == 0) break; if (opt->NLabels >= MAXLABELS) errx(1, "Limit of %d local labels", MAXLABELS - 1); if (strlen(buf) == 0) errx(1, "Volume label '%s' cannot be 0-length", buf); if (strlen(buf) >= HAMMER2_INODE_MAXNAME) errx(1, "Volume label '%s' is too long (%d chars max)", buf, HAMMER2_INODE_MAXNAME - 1); opt->Label[opt->NLabels++] = strdup(buf); break; case 'm': if (strlen(buf) == 0) errx(1, "Volume label '%s' cannot be 0-length", buf); if (strlen(buf) >= HAMMER2_INODE_MAXNAME) errx(1, "Volume label '%s' is too long (%d chars max)", buf, HAMMER2_INODE_MAXNAME - 1); strlcpy(h2_opt->mount_label, buf, sizeof(h2_opt->mount_label)); break; case 'c': if (strlen(buf) == 0) errx(1, "Compression type '%s' cannot be 0-length", buf); if (strcasecmp(buf, "none") == 0) opt->CompType = HAMMER2_COMP_NONE; else if (strcasecmp(buf, "autozero") == 0) opt->CompType = HAMMER2_COMP_AUTOZERO; else if (strcasecmp(buf, "lz4") == 0) opt->CompType = HAMMER2_COMP_LZ4; else if (strcasecmp(buf, "zlib") == 0) opt->CompType = HAMMER2_COMP_ZLIB; else errx(1, "Invalid compression type '%s'", buf); break; case 'C': if (strlen(buf) == 0) errx(1, "Check type '%s' cannot be 0-length", buf); if (strcasecmp(buf, "none") == 0) opt->CheckType = HAMMER2_CHECK_NONE; else if (strcasecmp(buf, "disabled") == 0) opt->CheckType = HAMMER2_CHECK_DISABLED; else if (strcasecmp(buf, "iscsi32") == 0) opt->CheckType = HAMMER2_CHECK_ISCSI32; else if (strcasecmp(buf, "xxhash64") == 0) opt->CheckType = HAMMER2_CHECK_XXHASH64; else if (strcasecmp(buf, "sha192") == 0) opt->CheckType = HAMMER2_CHECK_SHA192; else if (strcasecmp(buf, "freemap") == 0) opt->CheckType = HAMMER2_CHECK_FREEMAP; else errx(1, "Invalid check type '%s'", buf); break; case 'd': hammer2_debug = strtoll(buf, NULL, 0); break; case 'P': if (strlen(buf) == 0) errx(1, "PFS argument '%s' cannot be 0-length", buf); hammer2_parse_pfs_opts(buf, fsopts); break; case 'I': if (strlen(buf) == 0) errx(1, "Inode argument '%s' cannot be 0-length", buf); hammer2_parse_inode_opts(buf, fsopts); break; case 'B': h2_opt->ioctl_cmd = HAMMER2IOC_BULKFREE_SCAN; break; case 'D': h2_opt->ioctl_cmd = HAMMER2IOC_DESTROY; if (strlen(buf) == 0) errx(1, "Destroy argument '%s' cannot be 0-length", buf); if (buf[0] == '/') { strlcpy(h2_opt->destroy_path, buf, sizeof(h2_opt->destroy_path)); } else if (strncmp(buf, "0x", 2) == 0 || (buf[0] >= '0' && buf[0] <= '9')) { h2_opt->destroy_inum = strtoull(buf, NULL, 0); if (errno) err(1, "strtoull"); } else { errx(1, "Invalid destroy argument %s", buf); } break; case 'G': h2_opt->ioctl_cmd = HAMMER2IOC_GROWFS; break; case 'R': h2_opt->ioctl_cmd = HAMMER2IOC_READ; if (strlen(buf) == 0) errx(1, "Read argument '%s' cannot be 0-length", buf); strlcpy(h2_opt->read_path, buf, sizeof(h2_opt->read_path)); break; default: break; } if (hammer2_debug && h2_opt->ioctl_cmd) unittest_trim_slash(); return 1; } void hammer2_makefs(const char *image, const char *dir, fsnode *root, fsinfo_t *fsopts) { hammer2_makefs_options_t *h2_opt = fsopts->fs_specific; struct mount mp; struct hammer2_mount_info info; struct m_vnode devvp, *vroot; hammer2_inode_t *iroot; struct timeval start; int error; /* ioctl commands could have NULL dir / root */ assert(image != NULL); assert(fsopts != NULL); if (debug & DEBUG_FS_MAKEFS) APRINTF("image \"%s\" directory \"%s\" root %p\n", image, dir, root); /* validate tree and options */ TIMER_START(start); hammer2_validate(dir, root, fsopts); TIMER_RESULTS(start, "hammer2_validate"); if (h2_opt->ioctl_cmd) { /* open existing image */ fsopts->fd = open(image, O_RDWR); if (fsopts->fd < 0) err(1, "failed to open `%s'", image); } else { /* create image */ TIMER_START(start); if (hammer2_create_image(image, fsopts) == -1) errx(1, "image file `%s' not created", image); TIMER_RESULTS(start, "hammer2_create_image"); } assert(fsopts->fd > 0); if (debug & DEBUG_FS_MAKEFS) putchar('\n'); /* vfs init */ error = hammer2_vfs_init(); if (error) errx(1, "failed to vfs init, error %d", error); /* mount image */ memset(&devvp, 0, sizeof(devvp)); devvp.fs = fsopts; memset(&mp, 0, sizeof(mp)); memset(&info, 0, sizeof(info)); error = hammer2_vfs_mount(&devvp, &mp, h2_opt->mount_label, &info); if (error) errx(1, "failed to mount, error %d", error); assert(mp.mnt_data); /* get root vnode */ vroot = NULL; error = hammer2_vfs_root(&mp, &vroot); if (error) errx(1, "failed to get root vnode, error %d", error); assert(vroot); iroot = VTOI(vroot); assert(iroot); printf("root inode inum %lld, mode 0%o, refs %d\n", (long long)iroot->meta.inum, iroot->meta.mode, iroot->refs); if (h2_opt->emergency_mode) hammer2_ioctl_emerg_mode(iroot, 1); switch (h2_opt->ioctl_cmd) { case HAMMER2IOC_VERSION_GET: printf("version get `%s'\n", image); TIMER_START(start); error = hammer2_version_get(vroot); if (error) errx(1, "version get `%s' failed '%s'", image, strerror(error)); TIMER_RESULTS(start, "hammer2_version_get"); break; case HAMMER2IOC_PFS_GET: printf("PFS %s `%s'\n", h2_opt->pfs_cmd_name, image); TIMER_START(start); error = hammer2_pfs_get(vroot); if (error) errx(1, "PFS %s`%s' failed '%s'", h2_opt->pfs_cmd_name, image, strerror(error)); TIMER_RESULTS(start, "hammer2_pfs_get"); break; case HAMMER2IOC_PFS_LOOKUP: printf("PFS %s `%s'\n", h2_opt->pfs_cmd_name, image); TIMER_START(start); error = hammer2_pfs_lookup(vroot, h2_opt->pfs_name); if (error) errx(1, "PFS %s`%s' failed '%s'", h2_opt->pfs_cmd_name, image, strerror(error)); TIMER_RESULTS(start, "hammer2_pfs_lookup"); break; case HAMMER2IOC_PFS_CREATE: printf("PFS %s `%s'\n", h2_opt->pfs_cmd_name, image); TIMER_START(start); error = hammer2_pfs_create(vroot, h2_opt->pfs_name); if (error) errx(1, "PFS %s`%s' failed '%s'", h2_opt->pfs_cmd_name, image, strerror(error)); TIMER_RESULTS(start, "hammer2_pfs_create"); break; case HAMMER2IOC_PFS_DELETE: printf("PFS %s `%s'\n", h2_opt->pfs_cmd_name, image); TIMER_START(start); error = hammer2_pfs_delete(vroot, h2_opt->pfs_name); if (error) errx(1, "PFS %s`%s' failed '%s'", h2_opt->pfs_cmd_name, image, strerror(error)); TIMER_RESULTS(start, "hammer2_pfs_delete"); break; case HAMMER2IOC_PFS_SNAPSHOT: printf("PFS %s `%s'\n", h2_opt->pfs_cmd_name, image); TIMER_START(start); error = hammer2_pfs_snapshot(vroot, h2_opt->pfs_name, h2_opt->mount_label); if (error) errx(1, "PFS %s`%s' failed '%s'", h2_opt->pfs_cmd_name, image, strerror(error)); TIMER_RESULTS(start, "hammer2_pfs_snapshot"); break; case HAMMER2IOC_INODE_GET: printf("inode %s `%s'\n", h2_opt->inode_cmd_name, image); TIMER_START(start); error = hammer2_inode_getx(vroot, h2_opt->inode_path); if (error) errx(1, "inode %s `%s' failed '%s'", h2_opt->inode_cmd_name, image, strerror(error)); TIMER_RESULTS(start, "hammer2_inode_getx"); break; case HAMMER2IOC_INODE_SET: printf("inode %s `%s'\n", h2_opt->inode_cmd_name, image); TIMER_START(start); if (!strcmp(h2_opt->inode_cmd_name, "setcheck")) { error = hammer2_inode_setcheck(vroot, h2_opt->inode_path); if (error) errx(1, "inode %s `%s' failed '%s'", h2_opt->inode_cmd_name, image, strerror(error)); } else if (!strcmp(h2_opt->inode_cmd_name, "setcomp")) { error = hammer2_inode_setcomp(vroot, h2_opt->inode_path); if (error) errx(1, "inode %s `%s' failed '%s'", h2_opt->inode_cmd_name, image, strerror(error)); } else { assert(0); } TIMER_RESULTS(start, "hammer2_inode_setx"); break; case HAMMER2IOC_BULKFREE_SCAN: printf("bulkfree `%s'\n", image); TIMER_START(start); error = hammer2_bulkfree(vroot); if (error) errx(1, "bulkfree `%s' failed '%s'", image, strerror(error)); TIMER_RESULTS(start, "hammer2_bulkfree"); break; case HAMMER2IOC_DESTROY: TIMER_START(start); if (strlen(h2_opt->destroy_path)) { printf("destroy `%s' in `%s'\n", h2_opt->destroy_path, image); error = hammer2_destroy_path(vroot, h2_opt->destroy_path); if (error) errx(1, "destroy `%s' in `%s' failed '%s'", h2_opt->destroy_path, image, strerror(error)); } else { printf("destroy %lld in `%s'\n", (long long)h2_opt->destroy_inum, image); error = hammer2_destroy_inum(vroot, h2_opt->destroy_inum); if (error) errx(1, "destroy %lld in `%s' failed '%s'", (long long)h2_opt->destroy_inum, image, strerror(error)); } TIMER_RESULTS(start, "hammer2_destroy"); break; case HAMMER2IOC_GROWFS: printf("growfs `%s'\n", image); TIMER_START(start); error = hammer2_growfs(vroot, h2_opt->image_size); if (error) errx(1, "growfs `%s' failed '%s'", image, strerror(error)); TIMER_RESULTS(start, "hammer2_growfs"); break; case HAMMER2IOC_READ: printf("read `%s'\n", image); TIMER_START(start); error = hammer2_readx(vroot, dir, h2_opt->read_path); if (error) errx(1, "read `%s' failed '%s'", image, strerror(error)); TIMER_RESULTS(start, "hammer2_readx"); break; default: printf("populating `%s'\n", image); TIMER_START(start); if (hammer2_populate_dir(vroot, dir, root, root, fsopts, 0)) errx(1, "image file `%s' not populated", image); TIMER_RESULTS(start, "hammer2_populate_dir"); break; } /* unmount image */ error = hammer2_vfs_unmount(&mp, 0); if (error) errx(1, "failed to unmount, error %d", error); /* check leaked resource */ if (vnode_count) printf("XXX %lld vnode left\n", (long long)vnode_count); if (hammer2_chain_allocs) printf("XXX %ld chain left\n", hammer2_chain_allocs); bcleanup(); /* vfs uninit */ error = hammer2_vfs_uninit(); if (error) errx(1, "failed to vfs uninit, error %d", error); if (close(fsopts->fd) == -1) err(1, "closing `%s'", image); fsopts->fd = -1; printf("image `%s' complete\n", image); } /* end of public functions */ static void hammer2_parse_pfs_opts(const char *buf, fsinfo_t *fsopts) { hammer2_makefs_options_t *h2_opt = fsopts->fs_specific; char *o, *p; size_t n; o = p = strdup(buf); p = strchr(p, ':'); if (p != NULL) { *p++ = 0; n = strlen(p); } else { n = 0; } if (!strcmp(o, "get") || !strcmp(o, "list")) { h2_opt->ioctl_cmd = HAMMER2IOC_PFS_GET; } else if (!strcmp(o, "lookup")) { if (n == 0 || n > NAME_MAX) errx(1, "invalid PFS name \"%s\"", p); h2_opt->ioctl_cmd = HAMMER2IOC_PFS_LOOKUP; } else if (!strcmp(o, "create")) { if (n == 0 || n > NAME_MAX) errx(1, "invalid PFS name \"%s\"", p); h2_opt->ioctl_cmd = HAMMER2IOC_PFS_CREATE; } else if (!strcmp(o, "delete")) { if (n == 0 || n > NAME_MAX) errx(1, "invalid PFS name \"%s\"", p); h2_opt->ioctl_cmd = HAMMER2IOC_PFS_DELETE; } else if (!strcmp(o, "snapshot")) { if (n > NAME_MAX) errx(1, "invalid PFS name \"%s\"", p); h2_opt->ioctl_cmd = HAMMER2IOC_PFS_SNAPSHOT; } else { errx(1, "invalid PFS command \"%s\"", o); } strlcpy(h2_opt->pfs_cmd_name, o, sizeof(h2_opt->pfs_cmd_name)); if (n > 0) strlcpy(h2_opt->pfs_name, p, sizeof(h2_opt->pfs_name)); free(o); } static void hammer2_parse_inode_opts(const char *buf, fsinfo_t *fsopts) { hammer2_makefs_options_t *h2_opt = fsopts->fs_specific; char *o, *p; size_t n; o = p = strdup(buf); p = strchr(p, ':'); if (p != NULL) { *p++ = 0; n = strlen(p); } else { n = 0; } if (!strcmp(o, "get")) { if (n == 0 || n > PATH_MAX) errx(1, "invalid file path \"%s\"", p); h2_opt->ioctl_cmd = HAMMER2IOC_INODE_GET; } else if (!strcmp(o, "setcheck")) { if (n == 0 || n > PATH_MAX - 10) errx(1, "invalid argument \"%s\"", p); h2_opt->ioctl_cmd = HAMMER2IOC_INODE_SET; } else if (!strcmp(o, "setcomp")) { if (n == 0 || n > PATH_MAX - 10) errx(1, "invalid argument \"%s\"", p); h2_opt->ioctl_cmd = HAMMER2IOC_INODE_SET; } else { errx(1, "invalid inode command \"%s\"", o); } strlcpy(h2_opt->inode_cmd_name, o, sizeof(h2_opt->inode_cmd_name)); if (n > 0) strlcpy(h2_opt->inode_path, p, sizeof(h2_opt->inode_path)); free(o); } static hammer2_off_t hammer2_image_size(fsinfo_t *fsopts) { hammer2_makefs_options_t *h2_opt = fsopts->fs_specific; hammer2_off_t image_size, used_size = 0; int num_level1, delta_num_level1; /* use 4 volume headers by default */ num_level1 = h2_opt->num_volhdr * 2; /* default 4 x 2 */ assert(num_level1 != 0); assert(num_level1 <= 8); /* add 4MiB segment for each level1 */ used_size += HAMMER2_ZONE_SEG64 * num_level1; /* add boot/aux area, but exact size unknown at this point */ used_size += HAMMER2_BOOT_NOM_BYTES + HAMMER2_AUX_NOM_BYTES; /* add data size */ used_size += fsopts->size; /* XXX add extra level1 for meta data and indirect blocks */ used_size += HAMMER2_FREEMAP_LEVEL1_SIZE; /* XXX add extra level1 for safety */ if (used_size > HAMMER2_FREEMAP_LEVEL1_SIZE * 10) used_size += HAMMER2_FREEMAP_LEVEL1_SIZE; /* use 8GiB image size by default */ image_size = HAMMER2_FREEMAP_LEVEL1_SIZE * num_level1; printf("trying default image size %s\n", sizetostr(image_size)); /* adjust if image size isn't large enough */ if (used_size > image_size) { /* determine extra level1 needed */ delta_num_level1 = howmany(used_size - image_size, HAMMER2_FREEMAP_LEVEL1_SIZE); /* adjust used size with 4MiB segment for each extra level1 */ used_size += HAMMER2_ZONE_SEG64 * delta_num_level1; /* adjust image size with extra level1 */ image_size += HAMMER2_FREEMAP_LEVEL1_SIZE * delta_num_level1; printf("trying adjusted image size %s\n", sizetostr(image_size)); if (used_size > image_size) errx(1, "invalid used_size %lld > image_size %lld", (long long)used_size, (long long)image_size); } return image_size; } static const char * hammer2_label_name(int label_type) { switch (label_type) { case HAMMER2_LABEL_NONE: return "NONE"; case HAMMER2_LABEL_BOOT: return "BOOT"; case HAMMER2_LABEL_ROOT: return "ROOT"; case HAMMER2_LABEL_DATA: return "DATA"; default: assert(0); } return NULL; } static void hammer2_validate(const char *dir, fsnode *root, fsinfo_t *fsopts) { hammer2_makefs_options_t *h2_opt = fsopts->fs_specific; hammer2_mkfs_options_t *opt = &h2_opt->mkfs_options; hammer2_off_t image_size = 0, minsize, maxsize; const char *s; /* ioctl commands could have NULL dir / root */ assert(fsopts != NULL); if (debug & DEBUG_FS_VALIDATE) { APRINTF("before defaults set:\n"); hammer2_dump_fsinfo(fsopts); } /* makefs only supports "DATA" for default PFS label */ if (!h2_opt->label_specified) { opt->DefaultLabelType = HAMMER2_LABEL_DATA; s = hammer2_label_name(opt->DefaultLabelType); printf("using default label \"%s\"\n", s); } /* set default mount PFS label */ if (!strcmp(h2_opt->mount_label, "")) { s = hammer2_label_name(HAMMER2_LABEL_DATA); strlcpy(h2_opt->mount_label, s, sizeof(h2_opt->mount_label)); printf("using default mount label \"%s\"\n", s); } /* set default number of volume headers */ if (!h2_opt->num_volhdr) { h2_opt->num_volhdr = HAMMER2_NUM_VOLHDRS; printf("using default %d volume headers\n", h2_opt->num_volhdr); } /* done if ioctl commands */ if (h2_opt->ioctl_cmd) { if (h2_opt->ioctl_cmd == HAMMER2IOC_GROWFS) goto ignore_size_dir; else goto done; } /* calculate data size */ if (fsopts->size != 0) fsopts->size = 0; /* shouldn't reach here to begin with */ if (root == NULL) errx(1, "fsnode tree not constructed"); hammer2_size_dir(root, fsopts); printf("estimated data size %s from %lld inode\n", sizetostr(fsopts->size), (long long)fsopts->inodes); /* determine image size from data size */ image_size = hammer2_image_size(fsopts); assert((image_size & HAMMER2_FREEMAP_LEVEL1_MASK) == 0); ignore_size_dir: minsize = roundup(fsopts->minsize, HAMMER2_FREEMAP_LEVEL1_SIZE); maxsize = roundup(fsopts->maxsize, HAMMER2_FREEMAP_LEVEL1_SIZE); if (image_size < minsize) image_size = minsize; else if (maxsize > 0 && image_size > maxsize) errx(1, "`%s' size of %lld is larger than the maxsize of %lld", dir, (long long)image_size, (long long)maxsize); assert((image_size & HAMMER2_FREEMAP_LEVEL1_MASK) == 0); h2_opt->image_size = image_size; printf("using %s image size\n", sizetostr(h2_opt->image_size)); done: if (debug & DEBUG_FS_VALIDATE) { APRINTF("after defaults set:\n"); hammer2_dump_fsinfo(fsopts); } } static void hammer2_dump_fsinfo(fsinfo_t *fsopts) { hammer2_makefs_options_t *h2_opt = fsopts->fs_specific; hammer2_mkfs_options_t *opt = &h2_opt->mkfs_options; int i; char *s; assert(fsopts != NULL); APRINTF("fsinfo_t at %p\n", fsopts); printf("\tinodes %lld\n", (long long)fsopts->inodes); printf("\tsize %lld, minsize %lld, maxsize %lld\n", (long long)fsopts->size, (long long)fsopts->minsize, (long long)fsopts->maxsize); printf("\thammer2_debug 0x%x\n", hammer2_debug); printf("\tlabel_specified %d\n", h2_opt->label_specified); printf("\tmount_label \"%s\"\n", h2_opt->mount_label); printf("\tnum_volhdr %d\n", h2_opt->num_volhdr); printf("\tioctl_cmd %ld\n", h2_opt->ioctl_cmd); printf("\temergency_mode %d\n", h2_opt->emergency_mode); printf("\tpfs_cmd_name \"%s\"\n", h2_opt->pfs_cmd_name); printf("\tpfs_name \"%s\"\n", h2_opt->pfs_name); printf("\tinode_cmd_name \"%s\"\n", h2_opt->inode_cmd_name); printf("\tinode_path \"%s\"\n", h2_opt->inode_path); printf("\tdestroy_path \"%s\"\n", h2_opt->destroy_path); printf("\tdestroy_inum %lld\n", (long long)h2_opt->destroy_inum); printf("\tread_path \"%s\"\n", h2_opt->read_path); printf("\timage_size 0x%llx\n", (long long)h2_opt->image_size); printf("\tHammer2Version %d\n", opt->Hammer2Version); printf("\tBootAreaSize 0x%jx\n", opt->BootAreaSize); printf("\tAuxAreaSize 0x%jx\n", opt->AuxAreaSize); printf("\tNLabels %d\n", opt->NLabels); printf("\tCompType %d\n", opt->CompType); printf("\tCheckType %d\n", opt->CheckType); printf("\tDefaultLabelType %d\n", opt->DefaultLabelType); printf("\tDebugOpt %d\n", opt->DebugOpt); s = NULL; hammer2_uuid_to_str(&opt->Hammer2_FSType, &s); printf("\tHammer2_FSType \"%s\"\n", s); s = NULL; hammer2_uuid_to_str(&opt->Hammer2_VolFSID, &s); printf("\tHammer2_VolFSID \"%s\"\n", s); s = NULL; hammer2_uuid_to_str(&opt->Hammer2_SupCLID, &s); printf("\tHammer2_SupCLID \"%s\"\n", s); s = NULL; hammer2_uuid_to_str(&opt->Hammer2_SupFSID, &s); printf("\tHammer2_SupFSID \"%s\"\n", s); for (i = 0; i < opt->NLabels; i++) { printf("\tLabel[%d] \"%s\"\n", i, opt->Label[i]); s = NULL; hammer2_uuid_to_str(&opt->Hammer2_PfsCLID[i], &s); printf("\t Hammer2_PfsCLID[%d] \"%s\"\n", i, s); s = NULL; hammer2_uuid_to_str(&opt->Hammer2_PfsFSID[i], &s); printf("\t Hammer2_PfsFSID[%d] \"%s\"\n", i, s); } free(s); } static int hammer2_setup_blkdev(const char *image, fsinfo_t *fsopts) { hammer2_makefs_options_t *h2_opt = fsopts->fs_specific; hammer2_off_t size; if ((fsopts->fd = open(image, O_RDWR)) == -1) { warn("can't open `%s' for writing", image); return -1; } size = check_volume(fsopts->fd); if (h2_opt->image_size > size) { warnx("image size %lld exceeds %s size %lld", (long long)h2_opt->image_size, image, (long long)size); return -1; } return 0; } static int hammer2_create_image(const char *image, fsinfo_t *fsopts) { hammer2_makefs_options_t *h2_opt = fsopts->fs_specific; hammer2_mkfs_options_t *opt = &h2_opt->mkfs_options; char *av[] = { (char *)image, }; /* XXX support multi-volumes */ char *buf; int i, bufsize, oflags; off_t bufrem; struct stat st; assert(image != NULL); assert(fsopts != NULL); /* check if image is blk or chr */ if (stat(image, &st) == 0) { if (S_ISBLK(st.st_mode) || S_ISCHR(st.st_mode)) { if (hammer2_setup_blkdev(image, fsopts)) return -1; goto done; } } /* create image */ oflags = O_RDWR | O_CREAT; if (fsopts->offset == 0) oflags |= O_TRUNC; if ((fsopts->fd = open(image, oflags, 0666)) == -1) { warn("can't open `%s' for writing", image); return -1; } /* zero image */ bufsize = HAMMER2_PBUFSIZE; bufrem = h2_opt->image_size; if (fsopts->sparse) { if (ftruncate(fsopts->fd, bufrem) == -1) { warn("sparse option disabled"); fsopts->sparse = 0; } } if (fsopts->sparse) { /* File truncated at bufrem. Remaining is 0 */ bufrem = 0; buf = NULL; } else { if (debug & DEBUG_FS_CREATE_IMAGE) APRINTF("zero-ing image `%s', %lld sectors, " "using %d byte chunks\n", image, (long long)bufrem, bufsize); buf = ecalloc(1, bufsize); } if (fsopts->offset != 0) { if (lseek(fsopts->fd, fsopts->offset, SEEK_SET) == -1) { warn("can't seek"); free(buf); return -1; } } while (bufrem > 0) { i = write(fsopts->fd, buf, MIN(bufsize, bufrem)); if (i == -1) { warn("zeroing image, %lld bytes to go", (long long)bufrem); free(buf); return -1; } bufrem -= i; } if (buf) free(buf); done: /* make the file system */ if (debug & DEBUG_FS_CREATE_IMAGE) APRINTF("calling mkfs(\"%s\", ...)\n", image); hammer2_mkfs(1, av, opt); /* success if returned */ return fsopts->fd; } static off_t hammer2_phys_size(off_t size) { off_t radix_size, phys_size = 0; int i; if (size > HAMMER2_PBUFSIZE) { phys_size += rounddown(size, HAMMER2_PBUFSIZE); size = size % HAMMER2_PBUFSIZE; } for (i = HAMMER2_RADIX_MIN; i <= HAMMER2_RADIX_MAX; i++) { radix_size = 1UL << i; if (radix_size >= size) { phys_size += radix_size; break; } } return phys_size; } /* calculate data size */ static void hammer2_size_dir(fsnode *root, fsinfo_t *fsopts) { fsnode *node; assert(fsopts != NULL); if (debug & DEBUG_FS_SIZE_DIR) APRINTF("entry: bytes %lld inodes %lld\n", (long long)fsopts->size, (long long)fsopts->inodes); for (node = root; node != NULL; node = node->next) { if (node == root) { /* we're at "." */ assert(strcmp(node->name, ".") == 0); } else if ((node->inode->flags & FI_SIZED) == 0) { /* don't count duplicate names */ node->inode->flags |= FI_SIZED; if (debug & DEBUG_FS_SIZE_DIR_NODE) APRINTF("`%s' size %lld\n", node->name, (long long)node->inode->st.st_size); fsopts->inodes++; fsopts->size += sizeof(hammer2_inode_data_t); if (node->type == S_IFREG) { size_t st_size = node->inode->st.st_size; if (st_size > HAMMER2_EMBEDDED_BYTES) fsopts->size += hammer2_phys_size(st_size); } else if (node->type == S_IFLNK) { size_t nlen = strlen(node->symlink); if (nlen > HAMMER2_EMBEDDED_BYTES) fsopts->size += hammer2_phys_size(nlen); } } if (node->type == S_IFDIR) hammer2_size_dir(node->child, fsopts); } if (debug & DEBUG_FS_SIZE_DIR) APRINTF("exit: size %lld inodes %lld\n", (long long)fsopts->size, (long long)fsopts->inodes); } static void hammer2_print(const struct m_vnode *dvp, const struct m_vnode *vp, const fsnode *node, int depth, const char *msg) { if (debug & DEBUG_FS_POPULATE) { if (1) { int indent = depth * 2; char *type; if (S_ISDIR(node->type)) type = "dir"; else if (S_ISREG(node->type)) type = "reg"; else if (S_ISLNK(node->type)) type = "lnk"; else if (S_ISFIFO(node->type)) type = "fifo"; else type = "???"; printf("%*.*s", indent, indent, ""); printf("dvp=%p/%d vp=%p/%d \"%s\" %s %s\n", dvp, dvp ? VTOI(dvp)->refs : 0, vp, vp ? VTOI(vp)->refs : 0, node->name, type, msg); } else { char type; if (S_ISDIR(node->type)) type = 'd'; else if (S_ISREG(node->type)) type = 'r'; else if (S_ISLNK(node->type)) type = 'l'; else if (S_ISFIFO(node->type)) type = 'f'; else type = '?'; printf("%c", type); fflush(stdout); } } } static int hammer2_populate_dir(struct m_vnode *dvp, const char *dir, fsnode *root, fsnode *parent, fsinfo_t *fsopts, int depth) { fsnode *cur; struct m_vnode *vp; struct stat st; char f[MAXPATHLEN]; const char *path; int hardlink; int error; assert(dvp != NULL); assert(dir != NULL); assert(root != NULL); assert(parent != NULL); assert(fsopts != NULL); /* assert root directory */ assert(S_ISDIR(root->type)); assert(!strcmp(root->name, ".")); assert(!root->child); assert(!root->parent || root->parent->child == root); hammer2_print(dvp, NULL, root, depth, "enter"); if (stat(dir, &st) == -1) err(1, "no such path %s", dir); if (!S_ISDIR(st.st_mode)) errx(1, "no such dir %s", dir); for (cur = root->next; cur != NULL; cur = cur->next) { /* global variable for HAMMER2 vnops */ hammer2_curnode = cur; /* construct source path */ if (cur->contents) { path = cur->contents; } else { if (snprintf(f, sizeof(f), "%s/%s/%s", cur->root, cur->path, cur->name) >= (int)sizeof(f)) errx(1, "path %s too long", f); path = f; } if (S_ISLNK(cur->type)) { if (lstat(path, &st) == -1) err(1, "no such symlink %s", path); } else { if (stat(path, &st) == -1) err(1, "no such path %s", path); } /* update node state */ if ((cur->inode->flags & FI_ALLOCATED) == 0) { cur->inode->flags |= FI_ALLOCATED; if (cur != root) cur->parent = parent; } /* detect hardlink */ if (cur->inode->flags & FI_WRITTEN) { assert(!S_ISDIR(cur->type)); hardlink = 1; } else { hardlink = 0; } cur->inode->flags |= FI_WRITTEN; /* make sure it doesn't exist yet */ vp = NULL; error = hammer2_nresolve(dvp, &vp, cur->name, strlen(cur->name)); if (!error) errx(1, "hammer2_nresolve(\"%s\") already exists", cur->name); hammer2_print(dvp, vp, cur, depth, "nresolve"); /* if directory, mkdir and recurse */ if (S_ISDIR(cur->type)) { assert(cur->child); vp = NULL; error = hammer2_nmkdir(dvp, &vp, cur->name, strlen(cur->name), cur->inode->st.st_mode); if (error) errx(1, "hammer2_nmkdir(\"%s\") failed: %s", cur->name, strerror(error)); assert(vp); hammer2_print(dvp, vp, cur, depth, "nmkdir"); error = hammer2_populate_dir(vp, path, cur->child, cur, fsopts, depth + 1); if (error) errx(1, "failed to populate %s: %s", path, strerror(error)); cur->inode->param = vp; continue; } /* if regular file, creat and write its data */ if (S_ISREG(cur->type) && !hardlink) { assert(cur->child == NULL); vp = NULL; error = hammer2_ncreate(dvp, &vp, cur->name, strlen(cur->name), cur->inode->st.st_mode); if (error) errx(1, "hammer2_ncreate(\"%s\") failed: %s", cur->name, strerror(error)); assert(vp); hammer2_print(dvp, vp, cur, depth, "ncreate"); error = hammer2_write_file(vp, path, cur); if (error) errx(1, "hammer2_write_file(\"%s\") failed: %s", path, strerror(error)); cur->inode->param = vp; continue; } /* if symlink, create a symlink against target */ if (S_ISLNK(cur->type)) { assert(cur->child == NULL); vp = NULL; error = hammer2_nsymlink(dvp, &vp, cur->name, strlen(cur->name), cur->symlink, cur->inode->st.st_mode); if (error) errx(1, "hammer2_nsymlink(\"%s\") failed: %s", cur->name, strerror(error)); assert(vp); hammer2_print(dvp, vp, cur, depth, "nsymlink"); cur->inode->param = vp; continue; } /* if fifo, create a fifo */ if (S_ISFIFO(cur->type) && !hardlink) { assert(cur->child == NULL); vp = NULL; error = hammer2_nmknod(dvp, &vp, cur->name, strlen(cur->name), VFIFO, cur->inode->st.st_mode); if (error) errx(1, "hammer2_nmknod(\"%s\") failed: %s", cur->name, strerror(error)); assert(vp); hammer2_print(dvp, vp, cur, depth, "nmknod"); cur->inode->param = vp; continue; } /* if hardlink, creat a hardlink */ if ((S_ISREG(cur->type) || S_ISFIFO(cur->type)) && hardlink) { char buf[64]; assert(cur->child == NULL); /* source vnode must not be NULL */ vp = cur->inode->param; assert(vp); /* currently these conditions must be true */ assert(vp->v_data); assert(vp->v_type == VREG || vp->v_type == VFIFO); assert(vp->v_logical); assert(!vp->v_vflushed); assert(vp->v_malloced); assert(VTOI(vp)->refs > 0); error = hammer2_nlink(dvp, vp, cur->name, strlen(cur->name)); if (error) errx(1, "hammer2_nlink(\"%s\") failed: %s", cur->name, strerror(error)); snprintf(buf, sizeof(buf), "nlink=%lld", (long long)VTOI(vp)->meta.nlinks); hammer2_print(dvp, vp, cur, depth, buf); continue; } /* other types are unsupported */ printf("ignore %s 0%o\n", path, cur->type); } return 0; } static int hammer2_write_file(struct m_vnode *vp, const char *path, fsnode *node) { struct stat *st = &node->inode->st; size_t nsize, bufsize; off_t offset; int fd, error; char *p; nsize = st->st_size; if (nsize == 0) return 0; /* check nsize vs maximum file size */ fd = open(path, O_RDONLY); if (fd < 0) err(1, "failed to open %s", path); p = mmap(0, nsize, PROT_READ, MAP_FILE|MAP_PRIVATE, fd, 0); if (p == MAP_FAILED) err(1, "failed to mmap %s", path); close(fd); for (offset = 0; offset < nsize; ) { bufsize = MIN(nsize - offset, HAMMER2_PBUFSIZE); assert(bufsize <= HAMMER2_PBUFSIZE); error = hammer2_write(vp, p + offset, bufsize, offset); if (error) errx(1, "failed to write to %s vnode: %s", path, strerror(error)); offset += bufsize; if (bufsize == HAMMER2_PBUFSIZE) assert((offset & (HAMMER2_PBUFSIZE - 1)) == 0); } munmap(p, nsize); return 0; } static int trim_char(char *p, char c) { char *o, tmp[PATH_MAX]; bool prev_was_c; size_t n; int i; assert(p); /* nothing to do */ if (strlen(p) == 0) return 0; strlcpy(tmp, p, sizeof(tmp)); if (strncmp(tmp, p, sizeof(tmp))) return ENOSPC; /* trim consecutive */ prev_was_c = false; o = p; n = strlen(p); for (i = 0; i < n; i++) { if (tmp[i] == c) { if (!prev_was_c) *p++ = tmp[i]; prev_was_c = true; } else { *p++ = tmp[i]; prev_was_c = false; } } *p = 0; assert(strlen(p) <= strlen(tmp)); /* assert no consecutive */ prev_was_c = false; p = o; n = strlen(p); for (i = 0; i < n; i++) { if (p[i] == c) { assert(!prev_was_c); prev_was_c = true; } else { prev_was_c = false; } } /* trim leading */ if (*p == c) memmove(p, p + 1, strlen(p + 1) + 1); assert(*p != '/'); /* trim trailing */ p += strlen(p); p--; if (*p == c) *p = 0; assert(p[strlen(p) - 1] != '/'); return 0; } static int trim_slash(char *p) { return trim_char(p, '/'); } static bool is_supported_link(const char *s) { /* absolute path can't be supported */ if (strlen(s) >= 1 && strncmp(s, "/", 1) == 0) return false; /* XXX ".." is currently unsupported */ if (strlen(s) >= 3 && strncmp(s, "../", 3) == 0) return false; return true; } static int hammer2_version_get(struct m_vnode *vp) { hammer2_dev_t *hmp; hmp = VTOI(vp)->pmp->pfs_hmps[0]; if (hmp == NULL) return EINVAL; printf("version: %d\n", hmp->voldata.version); return 0; } struct pfs_entry { TAILQ_ENTRY(pfs_entry) entry; char name[NAME_MAX+1]; char s[NAME_MAX+1]; }; static int hammer2_pfs_get(struct m_vnode *vp) { hammer2_ioc_pfs_t pfs; TAILQ_HEAD(, pfs_entry) head; struct pfs_entry *p, *e; char *pfs_id_str; const char *type_str; int error; bzero(&pfs, sizeof(pfs)); TAILQ_INIT(&head); while ((pfs.name_key = pfs.name_next) != (hammer2_key_t)-1) { error = hammer2_ioctl_pfs_get(VTOI(vp), &pfs); if (error) return error; pfs_id_str = NULL; hammer2_uuid_to_str(&pfs.pfs_clid, &pfs_id_str); if (pfs.pfs_type == HAMMER2_PFSTYPE_MASTER) { if (pfs.pfs_subtype == HAMMER2_PFSSUBTYPE_NONE) type_str = "MASTER"; else type_str = hammer2_pfssubtype_to_str( pfs.pfs_subtype); } else { type_str = hammer2_pfstype_to_str(pfs.pfs_type); } e = ecalloc(1, sizeof(*e)); snprintf(e->name, sizeof(e->name), "%s", pfs.name); snprintf(e->s, sizeof(e->s), "%-11s %s", type_str, pfs_id_str); free(pfs_id_str); p = TAILQ_FIRST(&head); while (p) { if (strcmp(e->name, p->name) <= 0) { TAILQ_INSERT_BEFORE(p, e, entry); break; } p = TAILQ_NEXT(p, entry); } if (!p) TAILQ_INSERT_TAIL(&head, e, entry); } printf("Type " "ClusterId (pfs_clid) " "Label\n"); while ((p = TAILQ_FIRST(&head)) != NULL) { printf("%s %s\n", p->s, p->name); TAILQ_REMOVE(&head, p, entry); free(p); } return 0; } static int hammer2_pfs_lookup(struct m_vnode *vp, const char *pfs_name) { hammer2_ioc_pfs_t pfs; char *pfs_id_str; int error; bzero(&pfs, sizeof(pfs)); strlcpy(pfs.name, pfs_name, sizeof(pfs.name)); error = hammer2_ioctl_pfs_lookup(VTOI(vp), &pfs); if (error == 0) { printf("name: %s\n", pfs.name); printf("type: %s\n", hammer2_pfstype_to_str(pfs.pfs_type)); printf("subtype: %s\n", hammer2_pfssubtype_to_str(pfs.pfs_subtype)); pfs_id_str = NULL; hammer2_uuid_to_str(&pfs.pfs_fsid, &pfs_id_str); printf("fsid: %s\n", pfs_id_str); free(pfs_id_str); pfs_id_str = NULL; hammer2_uuid_to_str(&pfs.pfs_clid, &pfs_id_str); printf("clid: %s\n", pfs_id_str); free(pfs_id_str); } return error; } static int hammer2_pfs_create(struct m_vnode *vp, const char *pfs_name) { hammer2_ioc_pfs_t pfs; int error; bzero(&pfs, sizeof(pfs)); strlcpy(pfs.name, pfs_name, sizeof(pfs.name)); pfs.pfs_type = HAMMER2_PFSTYPE_MASTER; uuid_create(&pfs.pfs_clid, NULL); uuid_create(&pfs.pfs_fsid, NULL); error = hammer2_ioctl_pfs_create(VTOI(vp), &pfs); if (error == EEXIST) fprintf(stderr, "NOTE: Typically the same name is " "used for cluster elements on " "different mounts,\n" " but cluster elements on the " "same mount require unique names.\n" "hammer2: pfs_create(%s): already present\n", pfs_name); return error; } static int hammer2_pfs_delete(struct m_vnode *vp, const char *pfs_name) { hammer2_ioc_pfs_t pfs; bzero(&pfs, sizeof(pfs)); strlcpy(pfs.name, pfs_name, sizeof(pfs.name)); return hammer2_ioctl_pfs_delete(VTOI(vp), &pfs); } static int hammer2_pfs_snapshot(struct m_vnode *vp, const char *pfs_name, const char *mount_label) { hammer2_ioc_pfs_t pfs; struct tm *tp; time_t t; bzero(&pfs, sizeof(pfs)); strlcpy(pfs.name, pfs_name, sizeof(pfs.name)); if (strlen(pfs.name) == 0) { time(&t); tp = localtime(&t); snprintf(pfs.name, sizeof(pfs.name), "%s.%04d%02d%02d.%02d%02d%02d", mount_label, tp->tm_year + 1900, tp->tm_mon + 1, tp->tm_mday, tp->tm_hour, tp->tm_min, tp->tm_sec); } return hammer2_ioctl_pfs_snapshot(VTOI(vp), &pfs); } static int hammer2_inode_getx(struct m_vnode *dvp, const char *f) { hammer2_ioc_inode_t inode; hammer2_inode_t *ip; hammer2_inode_meta_t *meta; struct m_vnode *vp; char *o, *p, *name, *str = NULL; char tmp[PATH_MAX]; int error; uuid_t uuid; assert(strlen(f) > 0); o = p = name = strdup(f); error = trim_slash(p); if (error) return error; if (strlen(p) == 0) { vp = dvp; goto start_ioctl; } while ((p = strchr(p, '/')) != NULL) { *p++ = 0; /* NULL terminate name */ if (!strcmp(name, ".")) { name = p; continue; } vp = NULL; error = hammer2_nresolve(dvp, &vp, name, strlen(name)); if (error) return error; ip = VTOI(vp); switch (ip->meta.type) { case HAMMER2_OBJTYPE_DIRECTORY: break; case HAMMER2_OBJTYPE_SOFTLINK: bzero(tmp, sizeof(tmp)); error = hammer2_readlink(vp, tmp, sizeof(tmp)); if (error) return error; if (!is_supported_link(tmp)) return EINVAL; strlcat(tmp, "/", sizeof(tmp)); strlcat(tmp, p, sizeof(tmp)); error = trim_slash(tmp); if (error) return error; p = name = tmp; continue; default: return EINVAL; } dvp = vp; name = p; } error = hammer2_nresolve(dvp, &vp, name, strlen(name)); if (error) return error; start_ioctl: bzero(&inode, sizeof(inode)); error = hammer2_ioctl_inode_get(VTOI(vp), &inode); if (error) return error; meta = &inode.ip_data.meta; printf("--------------------\n"); printf("flags = 0x%x\n", inode.flags); printf("data_count = %ju\n", (uintmax_t)inode.data_count); printf("inode_count = %ju\n", (uintmax_t)inode.inode_count); printf("--------------------\n"); printf("version = %u\n", meta->version); printf("pfs_subtype = %u (%s)\n", meta->pfs_subtype, hammer2_pfssubtype_to_str(meta->pfs_subtype)); printf("uflags = 0x%x\n", (unsigned int)meta->uflags); printf("rmajor = %u\n", meta->rmajor); printf("rminor = %u\n", meta->rminor); printf("ctime = %s\n", hammer2_time64_to_str(meta->ctime, &str)); printf("mtime = %s\n", hammer2_time64_to_str(meta->mtime, &str)); printf("atime = %s\n", hammer2_time64_to_str(meta->atime, &str)); printf("btime = %s\n", hammer2_time64_to_str(meta->btime, &str)); uuid = meta->uid; printf("uid = %s\n", hammer2_uuid_to_str(&uuid, &str)); uuid = meta->gid; printf("gid = %s\n", hammer2_uuid_to_str(&uuid, &str)); printf("type = %u (%s)\n", meta->type, hammer2_iptype_to_str(meta->type)); printf("op_flags = 0x%x\n", meta->op_flags); printf("cap_flags = 0x%x\n", meta->cap_flags); printf("mode = 0%o\n", meta->mode); printf("inum = 0x%jx\n", (uintmax_t)meta->inum); printf("size = %ju\n", (uintmax_t)meta->size); printf("nlinks = %ju\n", (uintmax_t)meta->nlinks); printf("iparent = 0x%jx\n", (uintmax_t)meta->iparent); printf("name_key = 0x%jx\n", (uintmax_t)meta->name_key); printf("name_len = %u\n", meta->name_len); printf("ncopies = %u\n", meta->ncopies); printf("comp_algo = 0x%jx\n", (uintmax_t)meta->comp_algo); printf("check_algo = %u\n", meta->check_algo); printf("pfs_nmasters = %u\n", meta->pfs_nmasters); printf("pfs_type = %u (%s)\n", meta->pfs_type, hammer2_pfstype_to_str(meta->pfs_type)); printf("pfs_inum = 0x%jx\n", (uintmax_t)meta->pfs_inum); uuid = meta->pfs_clid; printf("pfs_clid = %s\n", hammer2_uuid_to_str(&uuid, &str)); uuid = meta->pfs_fsid; printf("pfs_fsid = %s\n", hammer2_uuid_to_str(&uuid, &str)); printf("data_quota = 0x%jx\n", (uintmax_t)meta->data_quota); printf("inode_quota = 0x%jx\n", (uintmax_t)meta->inode_quota); printf("pfs_lsnap_tid = 0x%jx\n", (uintmax_t)meta->pfs_lsnap_tid); printf("decrypt_check = 0x%jx\n", (uintmax_t)meta->decrypt_check); printf("--------------------\n"); free(o); return error; } static int hammer2_inode_setcheck(struct m_vnode *dvp, const char *f) { hammer2_ioc_inode_t inode; hammer2_inode_t *ip; struct m_vnode *vp; char *o, *p, *name, *check_algo_str; char tmp[PATH_MAX]; const char *checks[] = { "none", "disabled", "crc32", "xxhash64", "sha192", }; int check_algo_idx, error; uint8_t check_algo; assert(strlen(f) > 0); o = p = strdup(f); p = strrchr(p, ':'); if (p == NULL) return EINVAL; *p++ = 0; /* NULL terminate path */ check_algo_str = p; name = p = o; /* fail if already empty before trim */ if (strlen(p) == 0) return EINVAL; error = trim_slash(p); if (error) return error; if (strlen(check_algo_str) == 0) return EINVAL; /* convert check_algo_str to check_algo_idx */ check_algo_idx = nitems(checks); while (--check_algo_idx >= 0) if (strcasecmp(check_algo_str, checks[check_algo_idx]) == 0) break; if (check_algo_idx < 0) { if (strcasecmp(check_algo_str, "default") == 0) { check_algo_str = "xxhash64"; check_algo_idx = HAMMER2_CHECK_XXHASH64; } else if (strcasecmp(check_algo_str, "disabled") == 0) { check_algo_str = "disabled"; check_algo_idx = HAMMER2_CHECK_DISABLED; } else { printf("invalid check_algo_str: %s\n", check_algo_str); return EINVAL; } } check_algo = HAMMER2_ENC_ALGO(check_algo_idx); printf("change %s to algo %d (%s)\n", p, check_algo, check_algo_str); if (strlen(p) == 0) { vp = dvp; goto start_ioctl; } while ((p = strchr(p, '/')) != NULL) { *p++ = 0; /* NULL terminate name */ if (!strcmp(name, ".")) { name = p; continue; } vp = NULL; error = hammer2_nresolve(dvp, &vp, name, strlen(name)); if (error) return error; ip = VTOI(vp); switch (ip->meta.type) { case HAMMER2_OBJTYPE_DIRECTORY: break; case HAMMER2_OBJTYPE_SOFTLINK: bzero(tmp, sizeof(tmp)); error = hammer2_readlink(vp, tmp, sizeof(tmp)); if (error) return error; if (!is_supported_link(tmp)) return EINVAL; strlcat(tmp, "/", sizeof(tmp)); strlcat(tmp, p, sizeof(tmp)); error = trim_slash(tmp); if (error) return error; p = name = tmp; continue; default: return EINVAL; } dvp = vp; name = p; } error = hammer2_nresolve(dvp, &vp, name, strlen(name)); if (error) return error; start_ioctl: ip = VTOI(vp); bzero(&inode, sizeof(inode)); error = hammer2_ioctl_inode_get(ip, &inode); if (error) return error; inode.flags |= HAMMER2IOC_INODE_FLAG_CHECK; inode.ip_data.meta.check_algo = check_algo; error = hammer2_ioctl_inode_set(ip, &inode); if (error) return error; free(o); return error; } static int hammer2_inode_setcomp(struct m_vnode *dvp, const char *f) { hammer2_ioc_inode_t inode; hammer2_inode_t *ip; struct m_vnode *vp; char *o, *p, *name, *comp_algo_str, *comp_level_str; char tmp[PATH_MAX]; const char *comps[] = { "none", "autozero", "lz4", "zlib", }; int comp_algo_idx, comp_level_idx, error; uint8_t comp_algo, comp_level; assert(strlen(f) > 0); o = p = strdup(f); p = strrchr(p, ':'); if (p == NULL) return EINVAL; *p++ = 0; /* NULL terminate comp_algo_str */ comp_level_str = p; p = o; p = strrchr(p, ':'); if (p == NULL) { /* comp_level_str not specified */ comp_algo_str = comp_level_str; comp_level_str = NULL; } else { *p++ = 0; /* NULL terminate path */ comp_algo_str = p; } name = p = o; /* fail if already empty before trim */ if (strlen(p) == 0) return EINVAL; error = trim_slash(p); if (error) return error; if (strlen(comp_algo_str) == 0) return EINVAL; /* convert comp_algo_str to comp_algo_idx */ comp_algo_idx = nitems(comps); while (--comp_algo_idx >= 0) if (strcasecmp(comp_algo_str, comps[comp_algo_idx]) == 0) break; if (comp_algo_idx < 0) { if (strcasecmp(comp_algo_str, "default") == 0) { comp_algo_str = "lz4"; comp_algo_idx = HAMMER2_COMP_LZ4; } else if (strcasecmp(comp_algo_str, "disabled") == 0) { comp_algo_str = "autozero"; comp_algo_idx = HAMMER2_COMP_AUTOZERO; } else { printf("invalid comp_algo_str: %s\n", comp_algo_str); return EINVAL; } } comp_algo = HAMMER2_ENC_ALGO(comp_algo_idx); /* convert comp_level_str to comp_level_idx */ if (comp_level_str == NULL) { comp_level_idx = 0; } else if (isdigit((int)comp_level_str[0])) { comp_level_idx = strtol(comp_level_str, NULL, 0); } else if (strcasecmp(comp_level_str, "default") == 0) { comp_level_idx = 0; } else { printf("invalid comp_level_str: %s\n", comp_level_str); return EINVAL; } if (comp_level_idx) { switch (comp_algo) { case HAMMER2_COMP_ZLIB: if (comp_level_idx < 6 || comp_level_idx > 9) { printf("unsupported comp_level %d for %s\n", comp_level_idx, comp_algo_str); return EINVAL; } break; default: printf("unsupported comp_level %d for %s\n", comp_level_idx, comp_algo_str); return EINVAL; } } comp_level = HAMMER2_ENC_LEVEL(comp_level_idx); printf("change %s to algo %d (%s) level %d\n", p, comp_algo, comp_algo_str, comp_level_idx); if (strlen(p) == 0) { vp = dvp; goto start_ioctl; } while ((p = strchr(p, '/')) != NULL) { *p++ = 0; /* NULL terminate name */ if (!strcmp(name, ".")) { name = p; continue; } vp = NULL; error = hammer2_nresolve(dvp, &vp, name, strlen(name)); if (error) return error; ip = VTOI(vp); switch (ip->meta.type) { case HAMMER2_OBJTYPE_DIRECTORY: break; case HAMMER2_OBJTYPE_SOFTLINK: bzero(tmp, sizeof(tmp)); error = hammer2_readlink(vp, tmp, sizeof(tmp)); if (error) return error; if (!is_supported_link(tmp)) return EINVAL; strlcat(tmp, "/", sizeof(tmp)); strlcat(tmp, p, sizeof(tmp)); error = trim_slash(tmp); if (error) return error; p = name = tmp; continue; default: return EINVAL; } dvp = vp; name = p; } error = hammer2_nresolve(dvp, &vp, name, strlen(name)); if (error) return error; start_ioctl: ip = VTOI(vp); bzero(&inode, sizeof(inode)); error = hammer2_ioctl_inode_get(ip, &inode); if (error) return error; inode.flags |= HAMMER2IOC_INODE_FLAG_COMP; inode.ip_data.meta.comp_algo = comp_algo | comp_level; error = hammer2_ioctl_inode_set(ip, &inode); if (error) return error; free(o); return error; } static int hammer2_bulkfree(struct m_vnode *vp) { hammer2_ioc_bulkfree_t bfi; size_t usermem; size_t usermem_size = sizeof(usermem); bzero(&bfi, sizeof(bfi)); usermem = 0; if (sysctlbyname("hw.usermem", &usermem, &usermem_size, NULL, 0) == 0) bfi.size = usermem / 16; else bfi.size = 0; if (bfi.size < 8192 * 1024) bfi.size = 8192 * 1024; return hammer2_ioctl_bulkfree_scan(VTOI(vp), &bfi); } static int hammer2_destroy_path(struct m_vnode *dvp, const char *f) { hammer2_ioc_destroy_t destroy; hammer2_inode_t *ip; struct m_vnode *vp; char *o, *p, *name; char tmp[PATH_MAX]; int error; assert(strlen(f) > 0); o = p = name = strdup(f); error = trim_slash(p); if (error) return error; if (strlen(p) == 0) return EINVAL; while ((p = strchr(p, '/')) != NULL) { *p++ = 0; /* NULL terminate name */ if (!strcmp(name, ".")) { name = p; continue; } vp = NULL; error = hammer2_nresolve(dvp, &vp, name, strlen(name)); if (error) return error; ip = VTOI(vp); switch (ip->meta.type) { case HAMMER2_OBJTYPE_DIRECTORY: break; case HAMMER2_OBJTYPE_SOFTLINK: bzero(tmp, sizeof(tmp)); error = hammer2_readlink(vp, tmp, sizeof(tmp)); if (error) return error; if (!is_supported_link(tmp)) return EINVAL; strlcat(tmp, "/", sizeof(tmp)); strlcat(tmp, p, sizeof(tmp)); error = trim_slash(tmp); if (error) return error; p = name = tmp; continue; default: return EINVAL; } dvp = vp; name = p; } /* XXX When does (or why does not) ioctl modify this inode ? */ hammer2_inode_modify(VTOI(dvp)); bzero(&destroy, sizeof(destroy)); destroy.cmd = HAMMER2_DELETE_FILE; snprintf(destroy.path, sizeof(destroy.path), "%s", name); printf("%s\t", f); fflush(stdout); error = hammer2_ioctl_destroy(VTOI(dvp), &destroy); if (error) printf("%s\n", strerror(error)); else printf("ok\n"); free(o); return error; } static int hammer2_destroy_inum(struct m_vnode *vp, hammer2_tid_t inum) { hammer2_ioc_destroy_t destroy; int error; bzero(&destroy, sizeof(destroy)); destroy.cmd = HAMMER2_DELETE_INUM; destroy.inum = inum; printf("%jd\t", (intmax_t)destroy.inum); fflush(stdout); error = hammer2_ioctl_destroy(VTOI(vp), &destroy); if (error) printf("%s\n", strerror(error)); else printf("ok\n"); return error; } static int hammer2_growfs(struct m_vnode *vp, hammer2_off_t size) { hammer2_ioc_growfs_t growfs; int error; bzero(&growfs, sizeof(growfs)); growfs.size = size; error = hammer2_ioctl_growfs(VTOI(vp), &growfs, NULL); if (!error) { if (growfs.modified) printf("grown to %016jx\n", (intmax_t)growfs.size); else printf("no size change - %016jx\n", (intmax_t)growfs.size); } return error; } struct hammer2_link { TAILQ_ENTRY(hammer2_link) entry; hammer2_tid_t inum; uint64_t nlinks; char path[PATH_MAX]; }; TAILQ_HEAD(hammer2_linkq, hammer2_link); static void hammer2_linkq_init(struct hammer2_linkq *linkq) { TAILQ_INIT(linkq); } static void hammer2_linkq_cleanup(struct hammer2_linkq *linkq, bool is_root) { struct hammer2_link *e; int count = 0; /* * If is_root is true, linkq must be empty, or link count is broken. * Note that if an image was made by makefs, hardlinks in the source * directory became hardlinks in the image only if >1 links existed under * that directory, as makefs doesn't determine hardlink via link count. */ while ((e = TAILQ_FIRST(linkq)) != NULL) { count++; TAILQ_REMOVE(linkq, e, entry); free(e); } assert(TAILQ_EMPTY(linkq)); if (count && is_root) errx(1, "%d link entries remained", count); } static void hammer2_linkq_add(struct hammer2_linkq *linkq, hammer2_tid_t inum, uint64_t nlinks, const char *path) { struct hammer2_link *e; int count = 0; e = ecalloc(1, sizeof(*e)); e->inum = inum; e->nlinks = nlinks; strlcpy(e->path, path, sizeof(e->path)); TAILQ_INSERT_TAIL(linkq, e, entry); TAILQ_FOREACH(e, linkq, entry) if (e->inum == inum) count++; if (count > 1) errx(1, "%d link entries exist for inum %jd", count, (intmax_t)inum); } static void hammer2_linkq_del(struct hammer2_linkq *linkq, hammer2_tid_t inum) { struct hammer2_link *e, *next; TAILQ_FOREACH_MUTABLE(e, linkq, entry, next) if (e->inum == inum) { e->nlinks--; if (e->nlinks == 1) { TAILQ_REMOVE(linkq, e, entry); free(e); } } } static void hammer2_utimes(struct m_vnode *vp, const char *f) { hammer2_inode_t *ip = VTOI(vp); struct timeval tv[2]; hammer2_time_to_timeval(ip->meta.atime, &tv[0]); hammer2_time_to_timeval(ip->meta.mtime, &tv[1]); utimes(f, tv); /* ignore failure */ } static int hammer2_readx_directory(struct m_vnode *dvp, const char *dir, const char *name, struct hammer2_linkq *linkq) { struct m_vnode *vp; struct dirent *dp; struct stat st; char *buf, tmp[PATH_MAX]; off_t offset = 0; int ndirent = 0; int eofflag = 0; int i, error; snprintf(tmp, sizeof(tmp), "%s/%s", dir, name); if (stat(tmp, &st) == -1 && mkdir(tmp, 0666) == -1) err(1, "failed to mkdir %s", tmp); buf = ecalloc(1, HAMMER2_PBUFSIZE); while (!eofflag) { error = hammer2_readdir(dvp, buf, HAMMER2_PBUFSIZE, &offset, &ndirent, &eofflag); if (error) errx(1, "failed to readdir"); dp = (void *)buf; for (i = 0; i < ndirent; i++) { if (strcmp(dp->d_name, ".") && strcmp(dp->d_name, "..")) { error = hammer2_nresolve(dvp, &vp, dp->d_name, strlen(dp->d_name)); if (error) return error; error = hammer2_readx_handle(vp, tmp, dp->d_name, linkq); if (error) return error; } dp = (void *)((char *)dp + _DIRENT_RECLEN(dp->d_namlen)); } } free(buf); hammer2_utimes(dvp, tmp); return 0; } static int hammer2_readx_link(struct m_vnode *vp, const char *src, const char *lnk, struct hammer2_linkq *linkq) { hammer2_inode_t *ip = VTOI(vp); struct stat st; int error; if (!stat(lnk, &st)) { error = unlink(lnk); if (error) return error; } error = link(src, lnk); if (error) return error; hammer2_linkq_del(linkq, ip->meta.inum); return 0; } static int hammer2_readx_regfile(struct m_vnode *vp, const char *dir, const char *name, struct hammer2_linkq *linkq) { hammer2_inode_t *ip = VTOI(vp); struct hammer2_link *e; char *buf, out[PATH_MAX]; size_t resid, n; off_t offset; int fd, error; bool found = false; snprintf(out, sizeof(out), "%s/%s", dir, name); if (ip->meta.nlinks > 1) { TAILQ_FOREACH(e, linkq, entry) if (e->inum == ip->meta.inum) { found = true; error = hammer2_readx_link(vp, e->path, out, linkq); if (error == 0) return 0; /* ignore failure */ } if (!found) hammer2_linkq_add(linkq, ip->meta.inum, ip->meta.nlinks, out); } fd = open(out, O_WRONLY | O_CREAT | O_TRUNC, 0666); if (fd == -1) err(1, "failed to create %s", out); buf = ecalloc(1, HAMMER2_PBUFSIZE); resid = ip->meta.size; offset = 0; while (resid > 0) { bzero(buf, HAMMER2_PBUFSIZE); error = hammer2_read(vp, buf, HAMMER2_PBUFSIZE, offset); if (error) errx(1, "failed to read from %s", name); n = resid >= HAMMER2_PBUFSIZE ? HAMMER2_PBUFSIZE : resid; error = write(fd, buf, n); if (error == -1) err(1, "failed to write to %s", out); else if (error != n) return EINVAL; resid -= n; offset += HAMMER2_PBUFSIZE; } fsync(fd); close(fd); free(buf); hammer2_utimes(vp, out); return 0; } static int hammer2_readx_handle(struct m_vnode *vp, const char *dir, const char *name, struct hammer2_linkq *linkq) { hammer2_inode_t *ip = VTOI(vp); switch (ip->meta.type) { case HAMMER2_OBJTYPE_DIRECTORY: return hammer2_readx_directory(vp, dir, name, linkq); case HAMMER2_OBJTYPE_REGFILE: return hammer2_readx_regfile(vp, dir, name, linkq); default: /* XXX */ printf("ignore inode %jd %s \"%s\"\n", (intmax_t)ip->meta.inum, hammer2_iptype_to_str(ip->meta.type), name); return 0; } return EINVAL; } static int hammer2_readx(struct m_vnode *dvp, const char *dir, const char *f) { hammer2_inode_t *ip; struct hammer2_linkq linkq; struct m_vnode *vp, *ovp = dvp; char *o, *p, *name; char tmp[PATH_MAX]; int error; if (dir == NULL) return EINVAL; assert(strlen(f) > 0); o = p = name = strdup(f); error = trim_slash(p); if (error) return error; if (strlen(p) == 0) { vp = dvp; goto start_read; } while ((p = strchr(p, '/')) != NULL) { *p++ = 0; /* NULL terminate name */ if (!strcmp(name, ".")) { name = p; continue; } vp = NULL; error = hammer2_nresolve(dvp, &vp, name, strlen(name)); if (error) return error; ip = VTOI(vp); switch (ip->meta.type) { case HAMMER2_OBJTYPE_DIRECTORY: break; case HAMMER2_OBJTYPE_SOFTLINK: bzero(tmp, sizeof(tmp)); error = hammer2_readlink(vp, tmp, sizeof(tmp)); if (error) return error; if (!is_supported_link(tmp)) return EINVAL; strlcat(tmp, "/", sizeof(tmp)); strlcat(tmp, p, sizeof(tmp)); error = trim_slash(tmp); if (error) return error; p = name = tmp; continue; default: return EINVAL; } dvp = vp; name = p; } error = hammer2_nresolve(dvp, &vp, name, strlen(name)); if (error) return error; start_read: hammer2_linkq_init(&linkq); error = hammer2_readx_handle(vp, dir, name, &linkq); hammer2_linkq_cleanup(&linkq, vp == ovp); if (error) return error; free(o); return 0; } static void assert_trim_slash(const char *input, const char *expected) { char tmp[PATH_MAX]; int error; strlcpy(tmp, input, sizeof(tmp)); error = trim_slash(tmp); if (error) errx(1, "input \"%s\" error %d", input, error); if (strncmp(tmp, expected, sizeof(tmp))) errx(1, "input \"%s\" result \"%s\" vs expected \"%s\"", input, tmp, expected); } static void unittest_trim_slash(void) { assert_trim_slash("", ""); assert_trim_slash("/", ""); assert_trim_slash("//", ""); assert_trim_slash("///", ""); assert_trim_slash("makefs", "makefs"); assert_trim_slash("/makefs", "makefs"); assert_trim_slash("//makefs", "makefs"); assert_trim_slash("makefs/", "makefs"); assert_trim_slash("makefs//", "makefs"); assert_trim_slash("/makefs/", "makefs"); assert_trim_slash("//makefs//", "makefs"); assert_trim_slash("sys/vfs/hammer2", "sys/vfs/hammer2"); assert_trim_slash("/sys/vfs/hammer2", "sys/vfs/hammer2"); assert_trim_slash("//sys/vfs/hammer2", "sys/vfs/hammer2"); assert_trim_slash("///sys/vfs/hammer2", "sys/vfs/hammer2"); assert_trim_slash("sys/vfs/hammer2/", "sys/vfs/hammer2"); assert_trim_slash("sys/vfs/hammer2//", "sys/vfs/hammer2"); assert_trim_slash("sys/vfs/hammer2///", "sys/vfs/hammer2"); assert_trim_slash("/sys/vfs/hammer2/", "sys/vfs/hammer2"); assert_trim_slash("//sys//vfs//hammer2//", "sys/vfs/hammer2"); assert_trim_slash("///sys///vfs///hammer2///", "sys/vfs/hammer2"); APRINTF("success\n"); }