#if !defined(BOOT2) && !defined(TESTING)
#define LIBSTAND 1
#endif
#ifdef BOOT2
#include "boot2.h"
#endif
#ifdef LIBSTAND
#include "stand.h"
#endif
#ifdef TESTING
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/uuid.h>
#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <stdint.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <strings.h>
#include <errno.h>
#endif
#include <machine/param.h>
#include <vfs/hammer2/hammer2_disk.h>
uint32_t iscsi_crc32(const void *buf, size_t size);
uint32_t iscsi_crc32_ext(const void *buf, size_t size, uint32_t ocrc);
static hammer2_media_data_t media;
static hammer2_blockref_t saved_base;
#define hammer2_icrc32(buf, size) iscsi_crc32(buf, size)
struct hammer2_fs {
hammer2_blockref_t sroot;
hammer2_blockset_t sroot_blockset;
#if defined(TESTING)
int fd;
#elif defined(LIBSTAND)
struct open_file *f;
#elif defined(BOOT2)
#else
#error "hammer2: unknown library API"
#endif
};
struct hammer2_inode {
struct hammer2_inode_data ino;
off_t doff;
};
#ifdef BOOT2
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);
}
#if 0
static size_t
strlen(const char *s)
{
size_t l = 0;
for (; *s != 0; s++)
l++;
return (l);
}
#endif
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
off_t
blockoff(hammer2_blockref_t *bref)
{
return(bref->data_off & ~HAMMER2_OFF_MASK_RADIX);
}
static
size_t
blocksize(hammer2_blockref_t *bref)
{
size_t bytes;
bytes = (size_t)(bref->data_off & HAMMER2_OFF_MASK_RADIX);
if (bytes)
bytes = (size_t)1 << bytes;
return bytes;
}
static
hammer2_key_t
hammer2_dirhash(const unsigned char *name, size_t len)
{
const unsigned char *aname = name;
uint32_t crcx;
uint64_t key;
size_t i;
size_t j;
key = 0;
crcx = 0;
for (i = j = 0; i < len; ++i) {
if (aname[i] == '.' ||
aname[i] == '-' ||
aname[i] == '_' ||
aname[i] == '~') {
if (i != j)
crcx += hammer2_icrc32(aname + j, i - j);
j = i + 1;
}
}
if (i != j)
crcx += hammer2_icrc32(aname + j, i - j);
crcx |= 0x80000000U;
key |= (uint64_t)crcx << 32;
crcx = hammer2_icrc32(aname, len);
crcx = crcx ^ (crcx << 16);
key |= crcx & 0xFFFF0000U;
key |= 0x8000U;
return (key);
}
static
int
h2read(struct hammer2_fs *hfs, void *buf, size_t nbytes, off_t off)
{
#if defined(LIBSTAND)
size_t rlen;
#endif
int rc;
#if defined(TESTING)
rc = pread(hfs->fd, &media, nbytes, off);
if (rc == (int)nbytes)
rc = 0;
else
rc = -1;
#elif defined(LIBSTAND)
rc = hfs->f->f_dev->dv_strategy(hfs->f->f_devdata, F_READ,
off >> DEV_BSHIFT, nbytes,
buf, &rlen);
if (rc || rlen != nbytes)
rc = -1;
#elif defined(BOOT2)
rc = 0;
while (nbytes > 16384) {
rc = dskread(buf, off >> DEV_BSHIFT, 16384 >> DEV_BSHIFT);
nbytes -= 16384;
buf = (char *)buf + 16384;
off += 16384;
}
if (nbytes)
rc = dskread(buf, off >> DEV_BSHIFT, nbytes >> DEV_BSHIFT);
if (rc)
rc = -1;
#else
#error "hammer2: unknown library API"
#endif
return rc;
}
static int
h2lookup(struct hammer2_fs *hfs, hammer2_blockref_t *base,
hammer2_key_t key_beg, hammer2_key_t key_end,
hammer2_blockref_t *bref_ret, void **pptr)
{
hammer2_blockref_t *bref;
hammer2_blockref_t best;
hammer2_key_t scan_beg;
hammer2_key_t scan_end;
int i;
int rc;
int count;
int dev_boff;
int dev_bsize;
if (base == NULL) {
saved_base.data_off = (hammer2_off_t)-1;
return(0);
}
if (base->data_off == (hammer2_off_t)-1) {
bref_ret->type = 0;
return(-1);
}
switch(base->type) {
case HAMMER2_BREF_TYPE_VOLUME:
count = HAMMER2_SET_COUNT;
break;
case HAMMER2_BREF_TYPE_INODE:
count = HAMMER2_SET_COUNT;
break;
case HAMMER2_BREF_TYPE_INDIRECT:
count = blocksize(base) / sizeof(hammer2_blockref_t);
break;
default:
count = 0;
break;
}
again:
best.key = HAMMER2_KEY_MAX;
best.type = 0;
for (i = 0; i < count; ++i) {
if (base->type != HAMMER2_BREF_TYPE_VOLUME &&
base->data_off != saved_base.data_off) {
if (blocksize(base) && h2read(hfs, &media,
blocksize(base),
blockoff(base))) {
bref_ret->type = 0;
return(-1);
}
saved_base = *base;
}
if (base->type == HAMMER2_BREF_TYPE_INODE) {
if (media.ipdata.meta.op_flags &
HAMMER2_OPFLAG_DIRECTDATA) {
*pptr = media.ipdata.u.data;
bref_ret->type = HAMMER2_BREF_TYPE_DATA;
bref_ret->key = 0;
return HAMMER2_EMBEDDED_BYTES;
}
}
switch(base->type) {
case HAMMER2_BREF_TYPE_VOLUME:
bref = &hfs->sroot_blockset.blockref[i];
break;
case HAMMER2_BREF_TYPE_INODE:
bref = &media.ipdata.u.blockset.blockref[i];
break;
case HAMMER2_BREF_TYPE_INDIRECT:
bref = &media.npdata[i];
break;
}
if (bref->type == 0)
continue;
if (bref->key > best.key)
continue;
scan_beg = bref->key;
scan_end = scan_beg + ((hammer2_key_t)1 << bref->keybits) - 1;
if (scan_end >= key_beg && scan_beg <= key_end) {
best = *bref;
}
}
switch(best.type) {
case 0:
bref_ret->type = 0;
rc = 0;
break;
case HAMMER2_BREF_TYPE_INDIRECT:
rc = h2lookup(hfs, &best, key_beg, key_end, bref_ret, pptr);
if (rc == 0) {
key_beg = best.key +
((hammer2_key_t)1 << best.keybits);
if (key_beg > best.key && key_beg <= key_end)
goto again;
}
break;
case HAMMER2_BREF_TYPE_DIRENT:
case HAMMER2_BREF_TYPE_INODE:
case HAMMER2_BREF_TYPE_DATA:
dev_bsize = blocksize(&best);
if (dev_bsize) {
if (dev_bsize < HAMMER2_LBUFSIZE)
dev_bsize = HAMMER2_LBUFSIZE;
dev_boff = blockoff(&best) -
(blockoff(&best) & ~HAMMER2_LBUFMASK64);
if (h2read(hfs, &media,
dev_bsize,
blockoff(&best) - dev_boff)) {
return(-1);
}
}
saved_base.data_off = (hammer2_off_t)-1;
*bref_ret = best;
*pptr = media.buf + dev_boff;
rc = blocksize(&best);
break;
}
return(rc);
}
static
void
h2resolve(struct hammer2_fs *hfs, const char *path,
hammer2_blockref_t *bref, hammer2_inode_data_t **inop)
{
hammer2_blockref_t bres;
hammer2_inode_data_t *ino;
hammer2_key_t key;
void *data;
ssize_t bytes;
size_t len;
ino = NULL;
*bref = hfs->sroot;
if (inop)
*inop = NULL;
while (*path) {
while (*path == '/')
++path;
if (*path == 0)
break;
for (len = 0; path[len]; ++len) {
if (path[len] == '/')
break;
}
key = hammer2_dirhash(path, len);
for (;;) {
bytes = h2lookup(hfs, bref,
key,
key | HAMMER2_DIRHASH_LOMASK,
&bres, (void **)&data);
if (bytes < 0)
break;
if (bres.type == 0)
break;
switch (bres.type) {
case HAMMER2_BREF_TYPE_DIRENT:
if (bres.embed.dirent.namlen != len)
break;
if (bres.embed.dirent.namlen <=
sizeof(bres.check.buf)) {
if (memcmp(path, bres.check.buf, len))
break;
} else {
if (memcmp(path, data, len))
break;
}
*bref = hfs->sroot;
bytes = h2lookup(hfs, bref,
bres.embed.dirent.inum,
bres.embed.dirent.inum,
&bres, (void **)&ino);
if (inop)
*inop = ino;
goto found;
break;
case HAMMER2_BREF_TYPE_INODE:
ino = data;
if (ino->meta.name_len != len)
break;
if (memcmp(path, ino->filename, len) == 0) {
if (inop)
*inop = ino;
goto found;
}
break;
}
if ((bres.key & 0xFFFF) == 0xFFFF) {
bres.type = 0;
break;
}
key = bres.key + 1;
}
found:
if (bytes < 0 || bres.type == 0) {
bref->data_off = (hammer2_off_t)-1;
ino = NULL;
break;
}
path += len;
if (*path && ino->meta.type != HAMMER2_OBJTYPE_DIRECTORY) {
bref->data_off = (hammer2_off_t)-1;
break;
}
*bref = bres;
}
}
static
ssize_t
h2readfile(struct hammer2_fs *hfs, hammer2_blockref_t *bref,
off_t off, off_t filesize, void *buf, size_t len)
{
hammer2_blockref_t bres;
ssize_t total;
ssize_t bytes;
ssize_t zfill;
char *data;
if (off >= filesize)
return (0);
if (off + len > filesize)
len = filesize - off;
total = 0;
while (len) {
bytes = h2lookup(hfs, bref, off, off + len - 1,
&bres, (void **)&data);
if (bytes < 0) {
if (total == 0)
total = -1;
break;
}
if (bytes == 0) {
bzero(buf, len);
total += len;
break;
}
if (bres.key < off) {
data += off - bres.key;
bytes -= off - bres.key;
}
if (bres.key > off) {
zfill = (ssize_t)(bres.key - off);
bzero(buf, zfill);
len -= zfill;
off += zfill;
total += zfill;
buf = (char *)buf + zfill;
}
if (bytes > len)
bytes = len;
bcopy(data, buf, bytes);
len -= bytes;
off += bytes;
total += bytes;
buf = (char *)buf + bytes;
}
return (total);
}
static
int
h2init(struct hammer2_fs *hfs)
{
#if 0
uint32_t crc0;
#endif
hammer2_tid_t best_tid = 0;
void *data;
off_t off;
int best;
int i;
int r;
best = -1;
for (i = 0; i < HAMMER2_NUM_VOLHDRS; ++i) {
off = i * HAMMER2_ZONE_BYTES64;
if (i)
no_io_error = 1;
if (h2read(hfs, &media, sizeof(media.voldata), off))
break;
if (media.voldata.magic != HAMMER2_VOLUME_ID_HBO)
break;
if (best < 0 || best_tid < media.voldata.mirror_tid) {
best = i;
best_tid = media.voldata.mirror_tid;
}
}
no_io_error = 0;
if (best < 0)
return(-1);
off = best * HAMMER2_ZONE_BYTES64;
if (h2read(hfs, &media, sizeof(media.voldata), off))
return(-1);
hfs->sroot.type = HAMMER2_BREF_TYPE_VOLUME;
hfs->sroot.data_off = off;
hfs->sroot_blockset = media.voldata.sroot_blockset;
h2lookup(hfs, NULL, 0, 0, NULL, NULL);
r = h2lookup(hfs, &hfs->sroot,
HAMMER2_SROOT_KEY, HAMMER2_SROOT_KEY,
&hfs->sroot, &data);
if (r <= 0)
return(-1);
h2lookup(hfs, NULL, 0, 0, NULL, NULL);
r = h2lookup(hfs, &hfs->sroot,
HAMMER2_BOOT_KEY,
HAMMER2_BOOT_KEY | HAMMER2_DIRHASH_LOMASK,
&hfs->sroot, &data);
if (r <= 0) {
printf("hammer2: 'BOOT' PFS not found\n");
return(-1);
}
h2lookup(hfs, NULL, 0, 0, NULL, NULL);
return (0);
}
#ifdef BOOT2
static struct hammer2_fs hfs;
static int
boot2_hammer2_init(void)
{
if (h2init(&hfs))
return(-1);
return(0);
}
static boot2_ino_t
boot2_hammer2_lookup(const char *path)
{
hammer2_blockref_t bref;
h2resolve(&hfs, path, &bref, NULL);
return ((boot2_ino_t)bref.data_off);
}
static ssize_t
boot2_hammer2_read(boot2_ino_t ino, void *buf, size_t len)
{
hammer2_blockref_t bref;
ssize_t total;
bzero(&bref, sizeof(bref));
bref.type = HAMMER2_BREF_TYPE_INODE;
bref.data_off = ino;
total = h2readfile(&hfs, &bref, fs_off, 0x7FFFFFFF, buf, len);
if (total > 0)
fs_off += total;
return total;
}
const struct boot2_fsapi boot2_hammer2_api = {
.fsinit = boot2_hammer2_init,
.fslookup = boot2_hammer2_lookup,
.fsread = boot2_hammer2_read
};
#endif
#ifdef LIBSTAND
struct hfile {
struct hammer2_fs hfs;
hammer2_blockref_t bref;
int64_t fsize;
uint32_t mode;
uint8_t type;
};
static
int
hammer2_get_dtype(uint8_t type)
{
switch(type) {
case HAMMER2_OBJTYPE_DIRECTORY:
return(DT_DIR);
case HAMMER2_OBJTYPE_REGFILE:
return(DT_REG);
case HAMMER2_OBJTYPE_FIFO:
return(DT_FIFO);
case HAMMER2_OBJTYPE_CDEV:
return(DT_CHR);
case HAMMER2_OBJTYPE_BDEV:
return(DT_BLK);
case HAMMER2_OBJTYPE_SOFTLINK:
return(DT_LNK);
case HAMMER2_OBJTYPE_SOCKET:
return(DT_SOCK);
default:
return(DT_UNKNOWN);
}
}
static
mode_t
hammer2_get_mode(uint8_t type)
{
switch(type) {
case HAMMER2_OBJTYPE_DIRECTORY:
return(S_IFDIR);
case HAMMER2_OBJTYPE_REGFILE:
return(S_IFREG);
case HAMMER2_OBJTYPE_FIFO:
return(S_IFIFO);
case HAMMER2_OBJTYPE_CDEV:
return(S_IFCHR);
case HAMMER2_OBJTYPE_BDEV:
return(S_IFBLK);
case HAMMER2_OBJTYPE_SOFTLINK:
return(S_IFLNK);
case HAMMER2_OBJTYPE_SOCKET:
return(S_IFSOCK);
default:
return(0);
}
}
static int
hammer2_open(const char *path, struct open_file *f)
{
struct hfile *hf = malloc(sizeof(*hf));
hammer2_inode_data_t *ipdata;
bzero(hf, sizeof(*hf));
f->f_offset = 0;
f->f_fsdata = hf;
hf->hfs.f = f;
if (h2init(&hf->hfs)) {
f->f_fsdata = NULL;
free(hf);
errno = ENOENT;
return(-1);
}
h2resolve(&hf->hfs, path, &hf->bref, &ipdata);
if (hf->bref.data_off == (hammer2_off_t)-1 ||
(hf->bref.type != HAMMER2_BREF_TYPE_INODE &&
hf->bref.type != HAMMER2_BREF_TYPE_VOLUME)) {
f->f_fsdata = NULL;
free(hf);
errno = ENOENT;
return(-1);
}
if (ipdata) {
hf->fsize = ipdata->meta.size;
hf->type = ipdata->meta.type;
hf->mode = ipdata->meta.mode |
hammer2_get_mode(ipdata->meta.type);
} else {
hf->fsize = 0;
hf->type = HAMMER2_OBJTYPE_DIRECTORY;
hf->mode = 0755 | S_IFDIR;
}
return(0);
}
static int
hammer2_close(struct open_file *f)
{
struct hfile *hf = f->f_fsdata;
f->f_fsdata = NULL;
if (hf)
free(hf);
return (0);
}
static int
hammer2_read(struct open_file *f, void *buf, size_t len, size_t *resid)
{
struct hfile *hf = f->f_fsdata;
ssize_t total;
int rc = 0;
total = h2readfile(&hf->hfs, &hf->bref,
f->f_offset, hf->fsize, buf, len);
if (total < 0) {
rc = EIO;
total = 0;
} else {
f->f_offset += total;
rc = 0;
}
*resid = len - total;
return rc;
}
static off_t
hammer2_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
hammer2_stat(struct open_file *f, struct stat *st)
{
struct hfile *hf = f->f_fsdata;
st->st_mode = hf->mode;
st->st_uid = 0;
st->st_gid = 0;
st->st_size = hf->fsize;
return (0);
}
static int
hammer2_readdir(struct open_file *f, struct dirent *den)
{
struct hfile *hf = f->f_fsdata;
hammer2_blockref_t bres;
hammer2_inode_data_t *ipdata;
void *data;
int bytes;
for (;;) {
bytes = h2lookup(&hf->hfs, &hf->bref,
f->f_offset | HAMMER2_DIRHASH_VISIBLE,
HAMMER2_KEY_MAX,
&bres, (void **)&data);
if (bytes < 0)
break;
switch (bres.type) {
case HAMMER2_BREF_TYPE_INODE:
ipdata = data;
den->d_namlen = ipdata->meta.name_len;
den->d_type = hammer2_get_dtype(ipdata->meta.type);
den->d_ino = ipdata->meta.inum;
bcopy(ipdata->filename, den->d_name, den->d_namlen);
den->d_name[den->d_namlen] = 0;
break;
case HAMMER2_BREF_TYPE_DIRENT:
den->d_namlen = bres.embed.dirent.namlen;
den->d_type = hammer2_get_dtype(bres.embed.dirent.type);
den->d_ino = bres.embed.dirent.inum;
if (den->d_namlen <= sizeof(bres.check.buf)) {
bcopy(bres.check.buf,
den->d_name,
den->d_namlen);
} else {
bcopy(data, den->d_name, den->d_namlen);
}
den->d_name[den->d_namlen] = 0;
break;
default:
den->d_namlen = 1;
den->d_type =
hammer2_get_dtype(HAMMER2_OBJTYPE_REGFILE);
den->d_name[0] = '?';
den->d_name[1] = 0;
break;
}
f->f_offset = bres.key + 1;
return(0);
}
return ENOENT;
}
struct fs_ops hammer2_fsops = {
"hammer2",
hammer2_open,
hammer2_close,
hammer2_read,
null_write,
hammer2_seek,
hammer2_stat,
hammer2_readdir
};
#endif