#if !defined(BOOT2) && !defined(TESTING)
#define LIBSTAND 1
#endif
#ifndef DEBUG
#define DEBUG 0
#endif
#ifdef BOOT2
#include "boot2.h"
#else
#include <sys/param.h>
#include <stddef.h>
#include <stdint.h>
#endif
#ifdef TESTING
#include <sys/fcntl.h>
#include <sys/stat.h>
#include <unistd.h>
#include <err.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <dirent.h>
#endif
#ifdef LIBSTAND
#include "stand.h"
#endif
#include <vfs/hammer/hammer_disk.h>
#ifndef BOOT2
struct blockentry {
hammer_off_t off;
int use;
char *data;
};
#ifdef TESTING
#define NUMCACHE 16
#else
#define NUMCACHE 6
#endif
struct hfs {
#ifdef TESTING
int fd;
#else
struct open_file *f;
#endif
hammer_off_t root;
int64_t buf_beg;
int64_t last_dir_ino;
u_int8_t last_dir_cap_flags;
int lru;
struct blockentry cache[NUMCACHE];
};
static void *
hread(struct hfs *hfs, hammer_off_t off)
{
hammer_off_t boff = off & ~HAMMER_BUFMASK64;
boff &= HAMMER_OFF_LONG_MASK;
if (HAMMER_ZONE_DECODE(off) != HAMMER_ZONE_RAW_VOLUME_INDEX)
boff += hfs->buf_beg;
struct blockentry *be = NULL;
for (int i = 0; i < NUMCACHE; i++) {
if (be == NULL || be->use > hfs->cache[i].use)
be = &hfs->cache[i];
if (hfs->cache[i].off == boff) {
be = &hfs->cache[i];
break;
}
}
if (be->off != boff) {
be->off = boff;
#ifdef TESTING
ssize_t res = pread(hfs->fd, be->data, HAMMER_BUFSIZE,
boff & HAMMER_OFF_SHORT_MASK);
if (res != HAMMER_BUFSIZE)
err(1, "short read on off %llx", boff);
#else
size_t rlen;
int rv = hfs->f->f_dev->dv_strategy(hfs->f->f_devdata, F_READ,
boff >> DEV_BSHIFT, HAMMER_BUFSIZE,
be->data, &rlen);
if (rv || rlen != HAMMER_BUFSIZE)
return (NULL);
#endif
}
be->use = ++hfs->lru;
return &be->data[off & HAMMER_BUFMASK];
}
#else
struct hammer_dmadat {
struct boot2_dmadat boot2;
char buf[HAMMER_BUFSIZE];
};
#define fsdmadat ((struct hammer_dmadat *)boot2_dmadat)
struct hfs {
hammer_off_t root;
int64_t last_dir_ino;
u_int8_t last_dir_cap_flags;
int64_t buf_beg;
};
static void *
hread(struct hfs *hfs, hammer_off_t off)
{
char *buf = fsdmadat->buf;
hammer_off_t boff = off & ~HAMMER_BUFMASK64;
boff &= HAMMER_OFF_LONG_MASK;
if (HAMMER_ZONE_DECODE(off) != HAMMER_ZONE_RAW_VOLUME_INDEX)
boff += hfs->buf_beg;
boff &= HAMMER_OFF_SHORT_MASK;
boff >>= DEV_BSHIFT;
if (dskread(buf, boff, HAMMER_BUFSIZE >> DEV_BSHIFT))
return (NULL);
return (&buf[off & HAMMER_BUFMASK]);
}
static void
bzero(void *buf, size_t size)
{
for (size_t i = 0; i < size; i++)
((char *)buf)[i] = 0;
}
static void
bcopy(void *src, void *dst, size_t size)
{
memcpy(dst, src, size);
}
static size_t
strlen(const char *s)
{
size_t l = 0;
for (; *s != 0; s++)
l++;
return (l);
}
static int
memcmp(const void *a, const void *b, size_t len)
{
for (size_t p = 0; p < len; p++) {
int r = ((const char *)a)[p] - ((const char *)b)[p];
if (r != 0)
return (r);
}
return (0);
}
#endif
static int
hammer_btree_cmp(hammer_base_elm_t key1, hammer_base_elm_t key2)
{
if (key1->localization < key2->localization)
return(-5);
if (key1->localization > key2->localization)
return(5);
if (key1->obj_id < key2->obj_id)
return(-4);
if (key1->obj_id > key2->obj_id)
return(4);
if (key1->rec_type < key2->rec_type)
return(-3);
if (key1->rec_type > key2->rec_type)
return(3);
if (key1->key < key2->key)
return(-2);
if (key1->key > key2->key)
return(2);
if (key1->create_tid == 0) {
if (key2->create_tid == 0)
return(0);
return(1);
}
if (key2->create_tid == 0)
return(-1);
if (key1->create_tid < key2->create_tid)
return(-1);
if (key1->create_tid > key2->create_tid)
return(1);
return(0);
}
static int
hammer_btree_search_node(hammer_base_elm_t elm, hammer_node_ondisk_t node)
{
int b;
int s;
int i;
int r;
b = 0;
s = node->count;
while (s - b > 4) {
i = b + (s - b) / 2;
r = hammer_btree_cmp(elm, &node->elms[i].leaf.base);
if (r <= 1) {
s = i;
} else {
b = i;
}
}
return(b);
}
#if 0
static int64_t
hammer_directory_namekey(const void *name, int len)
{
int64_t key;
key = (int64_t)(crc32(name, len) & 0x7FFFFFFF) << 32;
if (key == 0)
key |= 0x100000000LL;
return(key);
}
#else
static int64_t
hammer_directory_namekey(const void *name __unused, int len __unused)
{
return (0);
}
#endif
#ifndef BOOT2
static u_int32_t
hammer_to_unix_xid(hammer_uuid_t *uuid)
{
return(*(u_int32_t *)&uuid->node[2]);
}
static int
hammer_get_dtype(u_int8_t obj_type)
{
switch(obj_type) {
case HAMMER_OBJTYPE_DIRECTORY:
return(DT_DIR);
case HAMMER_OBJTYPE_REGFILE:
return(DT_REG);
case HAMMER_OBJTYPE_DBFILE:
return(DT_DBF);
case HAMMER_OBJTYPE_FIFO:
return(DT_FIFO);
case HAMMER_OBJTYPE_SOCKET:
return(DT_SOCK);
case HAMMER_OBJTYPE_CDEV:
return(DT_CHR);
case HAMMER_OBJTYPE_BDEV:
return(DT_BLK);
case HAMMER_OBJTYPE_SOFTLINK:
return(DT_LNK);
default:
return(DT_UNKNOWN);
}
}
static int
hammer_get_mode(u_int8_t obj_type)
{
switch(obj_type) {
case HAMMER_OBJTYPE_DIRECTORY:
return(S_IFDIR);
case HAMMER_OBJTYPE_REGFILE:
return(S_IFREG);
case HAMMER_OBJTYPE_DBFILE:
return(S_IFDB);
case HAMMER_OBJTYPE_FIFO:
return(S_IFIFO);
case HAMMER_OBJTYPE_SOCKET:
return(S_IFSOCK);
case HAMMER_OBJTYPE_CDEV:
return(S_IFCHR);
case HAMMER_OBJTYPE_BDEV:
return(S_IFBLK);
case HAMMER_OBJTYPE_SOFTLINK:
return(S_IFLNK);
default:
return(0);
}
}
#if DEBUG > 1
static void
hprintb(hammer_base_elm_t e)
{
printf("%d/", e->localization);
if (e->obj_id >> 32 != 0)
printf("%lx%08lx",
(long)(e->obj_id >> 32),
(long)(e->obj_id & 0xffffffff));
else
printf("%lx", (long)e->obj_id);
printf("/%d/", e->rec_type);
if (e->key >> 32 != 0)
printf("%lx%08lx",
(long)(e->key >> 32),
(long)(e->key & 0xffffffff));
else
printf("%lx", (long)e->key);
#ifdef TESTING
printf("/%llx/%llx", e->create_tid, e->delete_tid);
#endif
}
#endif
#endif
static hammer_btree_leaf_elm_t
hfind(struct hfs *hfs, hammer_base_elm_t key, hammer_base_elm_t end)
{
#if DEBUG > 1
printf("searching for ");
hprintb(key);
printf(" end ");
hprintb(end);
printf("\n");
#endif
int n;
int r;
struct hammer_base_elm search = *key;
struct hammer_base_elm backtrack;
hammer_off_t nodeoff = hfs->root;
hammer_node_ondisk_t node;
hammer_btree_elm_t e = NULL;
int internal;
loop:
node = hread(hfs, nodeoff);
if (node == NULL)
return (NULL);
internal = node->type == HAMMER_BTREE_TYPE_INTERNAL;
#if DEBUG > 3
for (int i = 0; i < node->count; i++) {
printf("E: ");
hprintb(&node->elms[i].base);
printf("\n");
}
if (internal) {
printf("B: ");
hprintb(&node->elms[node->count].base);
printf("\n");
}
#endif
n = hammer_btree_search_node(&search, node);
for (; n < node->count + internal; n++) {
e = &node->elms[n];
r = hammer_btree_cmp(&search, &e->base);
if (r < 0)
break;
}
if (n > 0)
e = &node->elms[--n];
#if DEBUG > 2
printf(" found: ");
hprintb(&e->base);
printf("\n");
#endif
if (internal) {
if (n == node->count)
goto backtrack;
nodeoff = e->internal.subtree_offset;
backtrack = (e+1)->base;
goto loop;
}
r = hammer_btree_cmp(key, &e->base);
if (r > 1) {
e++;
n++;
}
while (n < node->count && e->base.delete_tid != 0) {
e++;
n++;
}
if (n == node->count) {
backtrack:
search = backtrack;
nodeoff = hfs->root;
#if DEBUG > 2
printf("hit right boundary (%d), resetting search to ",
node->count);
hprintb(&search);
printf("\n");
#endif
goto loop;
}
#if DEBUG > 1
printf(" result: ");
hprintb(&e->base);
printf("\n");
#endif
if (end != NULL)
if (hammer_btree_cmp(end, &e->base) < -1)
goto fail;
return (&e->leaf);
fail:
#if DEBUG > 1
printf(" fail.\n");
#endif
return (NULL);
}
static u_int32_t
hdirlocalization(struct hfs *hfs, ino_t ino)
{
struct hammer_base_elm key;
if (ino != hfs->last_dir_ino) {
bzero(&key, sizeof(key));
key.obj_id = ino;
key.localization = HAMMER_LOCALIZE_INODE;
key.rec_type = HAMMER_RECTYPE_INODE;
hammer_btree_leaf_elm_t e;
hammer_data_ondisk_t ed;
e = hfind(hfs, &key, &key);
if (e) {
ed = hread(hfs, e->data_offset);
if (ed) {
hfs->last_dir_ino = ino;
hfs->last_dir_cap_flags = ed->inode.cap_flags;
} else {
printf("hdirlocal: no inode data for %llx\n",
(long long)ino);
}
} else {
printf("hdirlocal: no inode entry for %llx\n",
(long long)ino);
}
}
if (hfs->last_dir_cap_flags & HAMMER_INODE_CAP_DIR_LOCAL_INO)
return(HAMMER_LOCALIZE_INODE);
else
return(HAMMER_LOCALIZE_MISC);
}
#ifndef BOOT2
static int
hreaddir(struct hfs *hfs, ino_t ino, int64_t *off, struct dirent *de)
{
struct hammer_base_elm key, end;
#if DEBUG > 2
printf("%s(%llx, %lld)\n", __func__, (long long)ino, *off);
#endif
bzero(&key, sizeof(key));
key.obj_id = ino;
key.localization = hdirlocalization(hfs, ino);
key.rec_type = HAMMER_RECTYPE_DIRENTRY;
key.key = *off;
end = key;
end.key = HAMMER_MAX_KEY;
hammer_btree_leaf_elm_t e;
e = hfind(hfs, &key, &end);
if (e == NULL) {
errno = ENOENT;
return (-1);
}
*off = e->base.key + 1;
de->d_namlen = e->data_len - HAMMER_ENTRY_NAME_OFF;
de->d_type = hammer_get_dtype(e->base.obj_type);
hammer_data_ondisk_t ed = hread(hfs, e->data_offset);
if (ed == NULL)
return (-1);
de->d_ino = ed->entry.obj_id;
bcopy(ed->entry.name, de->d_name, de->d_namlen);
de->d_name[de->d_namlen] = 0;
return (0);
}
#endif
static ino_t
hresolve(struct hfs *hfs, ino_t dirino, const char *name)
{
struct hammer_base_elm key, end;
size_t namel = strlen(name);
#if DEBUG > 2
printf("%s(%llx, %s)\n", __func__, (long long)dirino, name);
#endif
bzero(&key, sizeof(key));
key.obj_id = dirino;
key.localization = hdirlocalization(hfs, dirino);
key.key = hammer_directory_namekey(name, namel);
key.rec_type = HAMMER_RECTYPE_DIRENTRY;
end = key;
end.key = HAMMER_MAX_KEY;
hammer_btree_leaf_elm_t e;
while ((e = hfind(hfs, &key, &end)) != NULL) {
key.key = e->base.key + 1;
size_t elen = e->data_len - HAMMER_ENTRY_NAME_OFF;
hammer_data_ondisk_t ed = hread(hfs, e->data_offset);
if (ed == NULL)
return (-1);
#ifdef BOOT2
if (ls) {
for (int i = 0; i < elen; i++)
putchar(ed->entry.name[i]);
putchar(' ');
ls = 2;
continue;
}
#endif
if (elen == namel && memcmp(ed->entry.name, name, MIN(elen, namel)) == 0)
return (ed->entry.obj_id);
}
#ifdef BOOT2
if (ls == 2)
printf("\n");
#endif
return -1;
}
static ino_t
hlookup(struct hfs *hfs, const char *path)
{
#if DEBUG > 2
printf("%s(%s)\n", __func__, path);
#endif
#ifdef BOOT2
ls = 0;
#endif
ino_t ino = 1;
do {
char name[MAXPATHLEN + 1];
while (*path == '/')
path++;
if (*path == 0)
break;
for (char *n = name; *path != 0 && *path != '/'; path++, n++) {
n[0] = *path;
n[1] = 0;
}
#ifdef BOOT2
if (name[0] == '?' && name[1] == 0)
ls = 1;
#endif
ino = hresolve(hfs, ino, name);
} while (ino != (ino_t)-1 && *path != 0);
return (ino);
}
#ifndef BOOT2
static int
hstat(struct hfs *hfs, ino_t ino, struct stat* st)
{
struct hammer_base_elm key;
#if DEBUG > 2
printf("%s(%llx)\n", __func__, (long long)ino);
#endif
bzero(&key, sizeof(key));
key.obj_id = ino;
key.localization = HAMMER_LOCALIZE_INODE;
key.rec_type = HAMMER_RECTYPE_INODE;
hammer_btree_leaf_elm_t e = hfind(hfs, &key, &key);
if (e == NULL) {
#ifndef BOOT2
errno = ENOENT;
#endif
return -1;
}
hammer_data_ondisk_t ed = hread(hfs, e->data_offset);
if (ed == NULL)
return (-1);
st->st_mode = ed->inode.mode | hammer_get_mode(ed->inode.obj_type);
st->st_uid = hammer_to_unix_xid(&ed->inode.uid);
st->st_gid = hammer_to_unix_xid(&ed->inode.gid);
st->st_size = ed->inode.size;
return (0);
}
#endif
static ssize_t
hreadf(struct hfs *hfs, ino_t ino, int64_t off, int64_t len, char *buf)
{
int64_t startoff = off;
struct hammer_base_elm key, end;
bzero(&key, sizeof(key));
key.obj_id = ino;
key.localization = HAMMER_LOCALIZE_MISC;
key.rec_type = HAMMER_RECTYPE_DATA;
end = key;
end.key = HAMMER_MAX_KEY;
while (len > 0) {
key.key = off + 1;
hammer_btree_leaf_elm_t e = hfind(hfs, &key, &end);
int64_t dlen;
if (e == NULL || off > e->base.key) {
bzero(buf, len);
off += len;
len = 0;
break;
}
int64_t doff = e->base.key - e->data_len;
if (off < doff) {
dlen = doff - off;
dlen = MIN(dlen, len);
bzero(buf, dlen);
} else {
int64_t boff = off - doff;
hammer_off_t roff = e->data_offset;
dlen = e->data_len;
dlen -= boff;
dlen = MIN(dlen, len);
while (boff >= HAMMER_BUFSIZE) {
boff -= HAMMER_BUFSIZE;
roff += HAMMER_BUFSIZE;
}
if ((roff & ~HAMMER_BUFMASK64) != ((roff + boff + dlen - 1) & ~HAMMER_BUFMASK64))
dlen = HAMMER_BUFSIZE - ((boff + roff) & HAMMER_BUFMASK);
char *data = hread(hfs, roff);
if (data == NULL)
return (-1);
bcopy(data + boff, buf, dlen);
}
buf += dlen;
off += dlen;
len -= dlen;
}
return (off - startoff);
}
#ifdef BOOT2
struct hfs hfs;
static int
boot2_hammer_init(void)
{
hammer_volume_ondisk_t volhead;
volhead = hread(&hfs, HAMMER_ZONE_ENCODE(1, 0));
if (volhead == NULL)
return (-1);
if (volhead->vol_signature != HAMMER_FSBUF_VOLUME)
return (-1);
hfs.root = volhead->vol0_btree_root;
hfs.buf_beg = volhead->vol_buf_beg;
return (0);
}
static boot2_ino_t
boot2_hammer_lookup(const char *path)
{
ino_t ino = hlookup(&hfs, path);
if (ino == -1)
ino = 0;
fs_off = 0;
return (ino);
}
static ssize_t
boot2_hammer_read(boot2_ino_t ino, void *buf, size_t len)
{
ssize_t rlen = hreadf(&hfs, ino, fs_off, len, buf);
if (rlen != -1)
fs_off += rlen;
return (rlen);
}
const struct boot2_fsapi boot2_hammer_api = {
.fsinit = boot2_hammer_init,
.fslookup = boot2_hammer_lookup,
.fsread = boot2_hammer_read
};
#endif
#ifndef BOOT2
static int
hinit(struct hfs *hfs)
{
#if DEBUG
printf("hinit\n");
#endif
for (int i = 0; i < NUMCACHE; i++) {
hfs->cache[i].data = malloc(HAMMER_BUFSIZE);
hfs->cache[i].off = -1;
hfs->cache[i].use = 0;
#if DEBUG
if (hfs->cache[i].data == NULL)
printf("malloc failed\n");
#endif
}
hfs->lru = 0;
hfs->last_dir_ino = -1;
hammer_volume_ondisk_t volhead = hread(hfs, HAMMER_ZONE_ENCODE(1, 0));
#ifdef TESTING
if (volhead) {
printf("signature: %svalid\n",
volhead->vol_signature != HAMMER_FSBUF_VOLUME ?
"in" :
"");
printf("name: %s\n", volhead->vol_label);
}
#endif
if (volhead == NULL || volhead->vol_signature != HAMMER_FSBUF_VOLUME) {
for (int i = 0; i < NUMCACHE; i++) {
free(hfs->cache[i].data);
hfs->cache[i].data = NULL;
}
errno = ENODEV;
return (-1);
}
hfs->root = volhead->vol0_btree_root;
hfs->buf_beg = volhead->vol_buf_beg;
return (0);
}
static void
hclose(struct hfs *hfs)
{
#if DEBUG
printf("hclose\n");
#endif
for (int i = 0; i < NUMCACHE; i++) {
if (hfs->cache[i].data) {
free(hfs->cache[i].data);
hfs->cache[i].data = NULL;
}
}
}
#endif
#ifdef LIBSTAND
struct hfile {
struct hfs hfs;
ino_t ino;
int64_t fsize;
};
static int
hammer_open(const char *path, struct open_file *f)
{
struct hfile *hf = malloc(sizeof(*hf));
bzero(hf, sizeof(*hf));
f->f_fsdata = hf;
hf->hfs.f = f;
f->f_offset = 0;
int rv = hinit(&hf->hfs);
if (rv) {
f->f_fsdata = NULL;
free(hf);
return (rv);
}
#if DEBUG
printf("hammer_open %s %p\n", path, f);
#endif
hf->ino = hlookup(&hf->hfs, path);
if (hf->ino == -1)
goto fail;
struct stat st;
if (hstat(&hf->hfs, hf->ino, &st) == -1)
goto fail;
hf->fsize = st.st_size;
#if DEBUG
printf(" %ld\n", (long)hf->fsize);
#endif
return (0);
fail:
#if DEBUG
printf("hammer_open fail\n");
#endif
f->f_fsdata = NULL;
hclose(&hf->hfs);
free(hf);
return (ENOENT);
}
static int
hammer_close(struct open_file *f)
{
struct hfile *hf = f->f_fsdata;
f->f_fsdata = NULL;
if (hf) {
hclose(&hf->hfs);
free(hf);
}
return (0);
}
static int
hammer_read(struct open_file *f, void *buf, size_t len, size_t *resid)
{
struct hfile *hf = f->f_fsdata;
#if DEBUG
printf("hammer_read %p %ld %ld\n", f, f->f_offset, len);
#endif
if (f->f_offset >= hf->fsize)
return (EINVAL);
size_t maxlen = len;
if (f->f_offset + len > hf->fsize)
maxlen = hf->fsize - f->f_offset;
ssize_t rlen = hreadf(&hf->hfs, hf->ino, f->f_offset, maxlen, buf);
if (rlen == -1)
return (EINVAL);
f->f_offset += rlen;
*resid = len - rlen;
return (0);
}
static off_t
hammer_seek(struct open_file *f, off_t offset, int whence)
{
struct hfile *hf = f->f_fsdata;
switch (whence) {
case SEEK_SET:
f->f_offset = offset;
break;
case SEEK_CUR:
f->f_offset += offset;
break;
case SEEK_END:
f->f_offset = hf->fsize - offset;
break;
default:
return (-1);
}
return (f->f_offset);
}
static int
hammer_stat(struct open_file *f, struct stat *st)
{
struct hfile *hf = f->f_fsdata;
return (hstat(&hf->hfs, hf->ino, st));
}
static int
hammer_readdir(struct open_file *f, struct dirent *d)
{
struct hfile *hf = f->f_fsdata;
int64_t off = f->f_offset;
int rv = hreaddir(&hf->hfs, hf->ino, &off, d);
f->f_offset = off;
return (rv);
}
struct fs_ops hammer1_fsops = {
"hammer",
hammer_open,
hammer_close,
hammer_read,
null_write,
hammer_seek,
hammer_stat,
hammer_readdir
};
#endif
#ifdef TESTING
int
main(int argc, char **argv)
{
if (argc < 2) {
fprintf(stderr, "usage: hammerread <dev>\n");
return (1);
}
struct hfs hfs;
hfs.fd = open(argv[1], O_RDONLY);
if (hfs.fd == -1)
err(1, "unable to open %s", argv[1]);
if (hinit(&hfs) == -1)
err(1, "invalid hammerfs");
for (int i = 2; i < argc; i++) {
ino_t ino = hlookup(&hfs, argv[i]);
if (ino == (ino_t)-1) {
warn("hlookup %s", argv[i]);
continue;
}
struct stat st;
if (hstat(&hfs, ino, &st)) {
warn("hstat %s", argv[i]);
continue;
}
printf("%s %d/%d %o %lld\n",
argv[i],
st.st_uid, st.st_gid,
st.st_mode, st.st_size);
if (S_ISDIR(st.st_mode)) {
int64_t off = 0;
struct dirent de;
while (hreaddir(&hfs, ino, &off, &de) == 0) {
printf("%s %d %llx\n",
de.d_name, de.d_type, de.d_ino);
}
} else if (S_ISREG(st.st_mode)) {
char *buf = malloc(100000);
int64_t off = 0;
while (off < st.st_size) {
int64_t len = MIN(100000, st.st_size - off);
int64_t rl = hreadf(&hfs, ino, off, len, buf);
fwrite(buf, rl, 1, stdout);
off += rl;
}
free(buf);
}
}
return 0;
}
#endif