#if HAVE_NBTOOL_CONFIG_H
#include "nbtool_config.h"
#endif
#include <sys/param.h>
#include <sys/stat.h>
#include <sys/sysctl.h>
#include <sys/mman.h>
#include <sys/time.h>
#include <sys/dirent.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>
#include <fcntl.h>
#include <time.h>
#include <err.h>
#include <assert.h>
#include <util.h>
#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 *);
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[] = {
{ '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" },
{ '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);
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];
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;
}
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;
assert(image != NULL);
assert(fsopts != NULL);
if (debug & DEBUG_FS_MAKEFS)
APRINTF("image \"%s\" directory \"%s\" root %p\n",
image, dir, root);
TIMER_START(start);
hammer2_validate(dir, root, fsopts);
TIMER_RESULTS(start, "hammer2_validate");
if (h2_opt->ioctl_cmd) {
fsopts->fd = open(image, O_RDWR);
if (fsopts->fd < 0)
err(1, "failed to open `%s'", image);
} else {
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');
error = hammer2_vfs_init();
if (error)
errx(1, "failed to vfs init, error %d", error);
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);
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;
}
error = hammer2_vfs_unmount(&mp, 0);
if (error)
errx(1, "failed to unmount, error %d", error);
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();
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);
}
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;
num_level1 = h2_opt->num_volhdr * 2;
assert(num_level1 != 0);
assert(num_level1 <= 8);
used_size += HAMMER2_ZONE_SEG64 * num_level1;
used_size += HAMMER2_BOOT_NOM_BYTES + HAMMER2_AUX_NOM_BYTES;
used_size += fsopts->size;
used_size += HAMMER2_FREEMAP_LEVEL1_SIZE;
if (used_size > HAMMER2_FREEMAP_LEVEL1_SIZE * 10)
used_size += HAMMER2_FREEMAP_LEVEL1_SIZE;
image_size = HAMMER2_FREEMAP_LEVEL1_SIZE * num_level1;
printf("trying default image size %s\n", sizetostr(image_size));
if (used_size > image_size) {
delta_num_level1 = howmany(used_size - image_size,
HAMMER2_FREEMAP_LEVEL1_SIZE);
used_size += HAMMER2_ZONE_SEG64 * delta_num_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;
assert(fsopts != NULL);
if (debug & DEBUG_FS_VALIDATE) {
APRINTF("before defaults set:\n");
hammer2_dump_fsinfo(fsopts);
}
if (!h2_opt->label_specified) {
opt->DefaultLabelType = HAMMER2_LABEL_DATA;
s = hammer2_label_name(opt->DefaultLabelType);
printf("using default label \"%s\"\n", s);
}
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);
}
if (!h2_opt->num_volhdr) {
h2_opt->num_volhdr = HAMMER2_NUM_VOLHDRS;
printf("using default %d volume headers\n", h2_opt->num_volhdr);
}
if (h2_opt->ioctl_cmd) {
if (h2_opt->ioctl_cmd == HAMMER2IOC_GROWFS)
goto ignore_size_dir;
else
goto done;
}
if (fsopts->size != 0)
fsopts->size = 0;
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);
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, };
char *buf;
int i, bufsize, oflags;
off_t bufrem;
struct stat st;
assert(image != NULL);
assert(fsopts != NULL);
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;
}
}
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;
}
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) {
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:
if (debug & DEBUG_FS_CREATE_IMAGE)
APRINTF("calling mkfs(\"%s\", ...)\n", image);
hammer2_mkfs(1, av, opt);
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;
}
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) {
assert(strcmp(node->name, ".") == 0);
} else if ((node->inode->flags & FI_SIZED) == 0) {
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(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) {
hammer2_curnode = cur;
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);
}
if ((cur->inode->flags & FI_ALLOCATED) == 0) {
cur->inode->flags |= FI_ALLOCATED;
if (cur != root)
cur->parent = parent;
}
if (cur->inode->flags & FI_WRITTEN) {
assert(!S_ISDIR(cur->type));
hardlink = 1;
} else {
hardlink = 0;
}
cur->inode->flags |= FI_WRITTEN;
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 (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 (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 (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 (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 ((S_ISREG(cur->type) || S_ISFIFO(cur->type)) && hardlink) {
char buf[64];
assert(cur->child == NULL);
vp = cur->inode->param;
assert(vp);
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;
}
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;
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);
if (strlen(p) == 0)
return 0;
strlcpy(tmp, p, sizeof(tmp));
if (strncmp(tmp, p, sizeof(tmp)))
return ENOSPC;
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));
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;
}
}
if (*p == c)
memmove(p, p + 1, strlen(p + 1) + 1);
assert(*p != '/');
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)
{
if (strlen(s) >= 1 && strncmp(s, "/", 1) == 0)
return false;
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;
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;
check_algo_str = p;
name = p = o;
if (strlen(p) == 0)
return EINVAL;
error = trim_slash(p);
if (error)
return error;
if (strlen(check_algo_str) == 0)
return EINVAL;
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;
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;
comp_level_str = p;
p = o;
p = strrchr(p, ':');
if (p == NULL) {
comp_algo_str = comp_level_str;
comp_level_str = NULL;
} else {
*p++ = 0;
comp_algo_str = p;
}
name = p = o;
if (strlen(p) == 0)
return EINVAL;
error = trim_slash(p);
if (error)
return error;
if (strlen(comp_algo_str) == 0)
return EINVAL;
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);
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;
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;
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;
}
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;
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);
}
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;
}
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:
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;
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;
}