#include <sys/nlookup.h>
#include <sys/buf2.h>
#include "hammer.h"
static void hammer_free_volume(hammer_volume_t volume);
static int hammer_load_volume(hammer_volume_t volume);
static int hammer_load_buffer(hammer_buffer_t buffer, int isnew);
static int hammer_load_node(hammer_transaction_t trans,
hammer_node_t node, int isnew);
static void _hammer_rel_node(hammer_node_t node, int locked);
static int
hammer_vol_rb_compare(hammer_volume_t vol1, hammer_volume_t vol2)
{
if (vol1->vol_no < vol2->vol_no)
return(-1);
if (vol1->vol_no > vol2->vol_no)
return(1);
return(0);
}
static int
hammer_buf_rb_compare(hammer_buffer_t buf1, hammer_buffer_t buf2)
{
if (buf1->zoneX_offset < buf2->zoneX_offset)
return(-1);
if (buf1->zoneX_offset > buf2->zoneX_offset)
return(1);
return(0);
}
static int
hammer_nod_rb_compare(hammer_node_t node1, hammer_node_t node2)
{
if (node1->node_offset < node2->node_offset)
return(-1);
if (node1->node_offset > node2->node_offset)
return(1);
return(0);
}
RB_GENERATE2(hammer_vol_rb_tree, hammer_volume, rb_node,
hammer_vol_rb_compare, int32_t, vol_no);
RB_GENERATE2(hammer_buf_rb_tree, hammer_buffer, rb_node,
hammer_buf_rb_compare, hammer_off_t, zoneX_offset);
RB_GENERATE2(hammer_nod_rb_tree, hammer_node, rb_node,
hammer_nod_rb_compare, hammer_off_t, node_offset);
int
hammer_install_volume(hammer_mount_t hmp, const char *volname,
struct vnode *devvp, void *data)
{
struct mount *mp;
hammer_volume_t volume;
hammer_volume_ondisk_t ondisk;
hammer_volume_ondisk_t img;
struct nlookupdata nd;
struct buf *bp = NULL;
int error;
int ronly;
int setmp = 0;
int i;
mp = hmp->mp;
ronly = ((mp->mnt_flag & MNT_RDONLY) ? 1 : 0);
++hammer_count_volumes;
volume = kmalloc(sizeof(*volume), hmp->m_misc, M_WAITOK|M_ZERO);
volume->vol_name = kstrdup(volname, hmp->m_misc);
volume->io.hmp = hmp;
hammer_io_init(&volume->io, volume, HAMMER_IOTYPE_VOLUME);
volume->io.offset = 0LL;
volume->io.bytes = HAMMER_BUFSIZE;
if (devvp == NULL) {
error = nlookup_init(&nd, volume->vol_name, UIO_SYSSPACE, NLC_FOLLOW);
if (error == 0)
error = nlookup(&nd);
if (error == 0)
error = cache_vref(&nd.nl_nch, nd.nl_cred, &volume->devvp);
nlookup_done(&nd);
} else {
error = 0;
volume->devvp = devvp;
}
if (error == 0) {
if (vn_isdisk(volume->devvp, &error)) {
error = vfs_mountedon(volume->devvp);
}
}
if (error == 0 && vcount(volume->devvp) > 0)
error = EBUSY;
if (error == 0) {
vn_lock(volume->devvp, LK_EXCLUSIVE | LK_RETRY);
error = vinvalbuf(volume->devvp, V_SAVE, 0, 0);
if (error == 0) {
error = VOP_OPEN(volume->devvp,
(ronly ? FREAD : FREAD|FWRITE),
FSCRED, NULL);
}
vn_unlock(volume->devvp);
}
if (error) {
hammer_free_volume(volume);
return(error);
}
volume->devvp->v_rdev->si_mountpoint = mp;
setmp = 1;
error = bread(volume->devvp, 0LL, HAMMER_BUFSIZE, &bp);
if (error)
goto late_failure;
ondisk = (void *)bp->b_data;
if (ronly == 0 && data) {
img = (hammer_volume_ondisk_t)data;
if (ondisk->vol_signature == HAMMER_FSBUF_VOLUME) {
hkprintf("Formatting of valid HAMMER volume %s denied. "
"Erase with hammer strip or dd!\n", volname);
error = EFTYPE;
goto late_failure;
}
bcopy(img, ondisk, sizeof(*img));
}
if (ondisk->vol_signature != HAMMER_FSBUF_VOLUME) {
hkprintf("volume %s has an invalid header\n", volume->vol_name);
for (i = 0; i < (int)sizeof(ondisk->vol_signature); i++) {
kprintf("%02x", ((char*)&ondisk->vol_signature)[i] & 0xFF);
if (i != (int)sizeof(ondisk->vol_signature) - 1)
kprintf(" ");
}
kprintf("\n");
error = EFTYPE;
goto late_failure;
}
volume->vol_no = ondisk->vol_no;
volume->vol_flags = ondisk->vol_flags;
volume->maxbuf_off = HAMMER_ENCODE_RAW_BUFFER(volume->vol_no,
HAMMER_VOL_BUF_SIZE(ondisk));
if (RB_EMPTY(&hmp->rb_vols_root)) {
hmp->fsid = ondisk->vol_fsid;
} else if (kuuid_compare(&hmp->fsid, &ondisk->vol_fsid)) {
hkprintf("volume %s's fsid does not match other volumes\n",
volume->vol_name);
error = EFTYPE;
goto late_failure;
}
if (RB_INSERT(hammer_vol_rb_tree, &hmp->rb_vols_root, volume)) {
hkprintf("volume %s has a duplicate vol_no %d\n",
volume->vol_name, volume->vol_no);
error = EEXIST;
}
if (error == 0)
hammer_volume_number_add(hmp, volume);
if (error == 0 && ondisk->vol_rootvol == ondisk->vol_no) {
if (ondisk->vol_rootvol != HAMMER_ROOT_VOLNO) {
hkprintf("volume %s has invalid root vol_no %d\n",
volume->vol_name, ondisk->vol_rootvol);
error = EINVAL;
goto late_failure;
}
hmp->rootvol = volume;
hmp->nvolumes = ondisk->vol_count;
if (bp) {
brelse(bp);
bp = NULL;
}
hmp->mp->mnt_stat.f_blocks += ondisk->vol0_stat_bigblocks *
HAMMER_BUFFERS_PER_BIGBLOCK;
hmp->mp->mnt_vstat.f_blocks += ondisk->vol0_stat_bigblocks *
HAMMER_BUFFERS_PER_BIGBLOCK;
}
late_failure:
if (bp)
brelse(bp);
if (error) {
if (setmp)
volume->devvp->v_rdev->si_mountpoint = NULL;
vn_lock(volume->devvp, LK_EXCLUSIVE | LK_RETRY);
VOP_CLOSE(volume->devvp, ronly ? FREAD : FREAD|FWRITE, NULL);
vn_unlock(volume->devvp);
hammer_free_volume(volume);
}
return (error);
}
int
hammer_adjust_volume_mode(hammer_volume_t volume, void *data __unused)
{
if (volume->devvp) {
vn_lock(volume->devvp, LK_EXCLUSIVE | LK_RETRY);
if (volume->io.hmp->ronly) {
VOP_OPEN(volume->devvp, FREAD, FSCRED, NULL);
VOP_CLOSE(volume->devvp, FREAD|FWRITE, NULL);
} else {
VOP_OPEN(volume->devvp, FREAD|FWRITE, FSCRED, NULL);
VOP_CLOSE(volume->devvp, FREAD, NULL);
}
vn_unlock(volume->devvp);
}
return(0);
}
int
hammer_unload_volume(hammer_volume_t volume, void *data)
{
hammer_mount_t hmp = volume->io.hmp;
struct buf *bp = NULL;
hammer_volume_ondisk_t img;
int ronly = ((hmp->mp->mnt_flag & MNT_RDONLY) ? 1 : 0);
int error;
if (ronly == 0 && data && volume->devvp) {
img = (hammer_volume_ondisk_t)data;
error = bread(volume->devvp, 0LL, HAMMER_BUFSIZE, &bp);
if (error || bp->b_bcount < sizeof(*img)) {
hmkprintf(hmp, "Failed to read volume header: %d\n", error);
brelse(bp);
} else {
bcopy(img, bp->b_data, sizeof(*img));
error = bwrite(bp);
if (error)
hmkprintf(hmp, "Failed to clear volume header: %d\n",
error);
}
}
if (hmp->rootvol == volume)
hmp->rootvol = NULL;
hammer_io_clear_modify(&volume->io, 1);
volume->io.waitdep = 1;
if (volume->io.ioerror)
hammer_io_clear_error_noassert(&volume->io);
hammer_ref_interlock_true(&volume->io.lock);
hammer_rel_volume(volume, 1);
KKASSERT(volume->io.bp == NULL);
KKASSERT(hammer_norefs(&volume->io.lock));
volume->ondisk = NULL;
if (volume->devvp) {
if (volume->devvp->v_rdev &&
volume->devvp->v_rdev->si_mountpoint == hmp->mp) {
volume->devvp->v_rdev->si_mountpoint = NULL;
}
if (ronly) {
vn_lock(volume->devvp, LK_EXCLUSIVE | LK_RETRY);
vinvalbuf(volume->devvp, 0, 0, 0);
VOP_CLOSE(volume->devvp, FREAD, NULL);
vn_unlock(volume->devvp);
} else {
vn_lock(volume->devvp, LK_EXCLUSIVE | LK_RETRY);
vinvalbuf(volume->devvp, V_SAVE, 0, 0);
VOP_CLOSE(volume->devvp, FREAD|FWRITE, NULL);
vn_unlock(volume->devvp);
}
}
RB_REMOVE(hammer_vol_rb_tree, &hmp->rb_vols_root, volume);
hammer_volume_number_del(hmp, volume);
hammer_free_volume(volume);
return(0);
}
static
void
hammer_free_volume(hammer_volume_t volume)
{
hammer_mount_t hmp = volume->io.hmp;
if (volume->vol_name) {
kfree(volume->vol_name, hmp->m_misc);
volume->vol_name = NULL;
}
if (volume->devvp) {
vrele(volume->devvp);
volume->devvp = NULL;
}
--hammer_count_volumes;
kfree(volume, hmp->m_misc);
}
hammer_volume_t
hammer_get_volume(hammer_mount_t hmp, int32_t vol_no, int *errorp)
{
hammer_volume_t volume;
volume = RB_LOOKUP(hammer_vol_rb_tree, &hmp->rb_vols_root, vol_no);
if (volume == NULL) {
*errorp = ENOENT;
return(NULL);
}
if (hammer_ref_interlock(&volume->io.lock)) {
*errorp = hammer_load_volume(volume);
if (*errorp)
volume = NULL;
} else {
KKASSERT(volume->ondisk);
*errorp = 0;
}
return(volume);
}
int
hammer_ref_volume(hammer_volume_t volume)
{
int error;
if (hammer_ref_interlock(&volume->io.lock)) {
error = hammer_load_volume(volume);
} else {
KKASSERT(volume->ondisk);
error = 0;
}
return (error);
}
hammer_volume_t
hammer_get_root_volume(hammer_mount_t hmp, int *errorp)
{
hammer_volume_t volume;
volume = hmp->rootvol;
KKASSERT(volume != NULL);
if (hammer_ref_interlock(&volume->io.lock)) {
lwkt_gettoken(&volume->io.hmp->fs_token);
*errorp = hammer_load_volume(volume);
lwkt_reltoken(&volume->io.hmp->fs_token);
if (*errorp)
volume = NULL;
} else {
KKASSERT(volume->ondisk);
*errorp = 0;
}
return (volume);
}
static int
hammer_load_volume(hammer_volume_t volume)
{
int error;
if (volume->ondisk == NULL) {
error = hammer_io_read(volume->devvp, &volume->io,
HAMMER_BUFSIZE);
if (error == 0) {
volume->ondisk = (void *)volume->io.bp->b_data;
hammer_ref_interlock_done(&volume->io.lock);
} else {
hammer_rel_volume(volume, 1);
}
} else {
error = 0;
}
return(error);
}
void
hammer_rel_volume(hammer_volume_t volume, int locked)
{
struct buf *bp;
if (hammer_rel_interlock(&volume->io.lock, locked)) {
lwkt_gettoken(&volume->io.hmp->fs_token);
volume->ondisk = NULL;
bp = hammer_io_release(&volume->io, locked);
lwkt_reltoken(&volume->io.hmp->fs_token);
hammer_rel_interlock_done(&volume->io.lock, locked);
if (bp)
brelse(bp);
}
}
int
hammer_mountcheck_volumes(hammer_mount_t hmp)
{
hammer_volume_t vol;
int i;
HAMMER_VOLUME_NUMBER_FOREACH(hmp, i) {
vol = RB_LOOKUP(hammer_vol_rb_tree, &hmp->rb_vols_root, i);
if (vol == NULL)
return(EINVAL);
}
return(0);
}
int
hammer_get_installed_volumes(hammer_mount_t hmp)
{
int i, ret = 0;
HAMMER_VOLUME_NUMBER_FOREACH(hmp, i)
ret++;
return(ret);
}
static __inline int
hammer_direct_zone(hammer_off_t buf_offset)
{
return(hammer_is_zone_direct_xlated(buf_offset));
}
hammer_buffer_t
hammer_get_buffer(hammer_mount_t hmp, hammer_off_t buf_offset,
int bytes, int isnew, int *errorp)
{
hammer_buffer_t buffer;
hammer_volume_t volume;
hammer_off_t zone2_offset;
int vol_no;
int zone;
buf_offset &= ~HAMMER_BUFMASK64;
again:
buffer = RB_LOOKUP(hammer_buf_rb_tree, &hmp->rb_bufs_root, buf_offset);
if (buffer) {
found_aliased:
if (hammer_ref_interlock(&buffer->io.lock) == 0) {
hammer_io_advance(&buffer->io);
KKASSERT(buffer->ondisk);
*errorp = 0;
return(buffer);
}
atomic_add_int(&hammer_count_refedbufs, 1);
if (buffer->ondisk) {
hammer_io_advance(&buffer->io);
hammer_ref_interlock_done(&buffer->io.lock);
*errorp = 0;
return(buffer);
}
if (buffer->io.mod_root == &hmp->lose_root) {
lwkt_gettoken(&hmp->io_token);
if (buffer->io.mod_root == &hmp->lose_root) {
RB_REMOVE(hammer_mod_rb_tree,
buffer->io.mod_root, &buffer->io);
buffer->io.mod_root = NULL;
KKASSERT(buffer->io.modified == 0);
}
lwkt_reltoken(&hmp->io_token);
}
goto found;
} else if (hmp->ronly && hammer_direct_zone(buf_offset)) {
buffer = RB_LOOKUP(hammer_buf_rb_tree, &hmp->rb_bufs_root,
hammer_xlate_to_zone2(buf_offset));
if (buffer) {
if (hammer_debug_general & 0x0001) {
hkrateprintf(&hmp->kdiag,
"recovered aliased %016jx\n",
(intmax_t)buf_offset);
}
goto found_aliased;
}
}
zone = HAMMER_ZONE_DECODE(buf_offset);
if (hammer_is_index_record(zone)) {
zone2_offset = hammer_blockmap_lookup(hmp, buf_offset, errorp);
} else if (zone == HAMMER_ZONE_UNDO_INDEX) {
zone2_offset = hammer_undo_lookup(hmp, buf_offset, errorp);
} else {
KKASSERT(zone == HAMMER_ZONE_RAW_BUFFER_INDEX);
zone2_offset = buf_offset;
*errorp = 0;
}
if (*errorp)
return(NULL);
KKASSERT(hammer_is_zone_raw_buffer(zone2_offset));
vol_no = HAMMER_VOL_DECODE(zone2_offset);
volume = hammer_get_volume(hmp, vol_no, errorp);
if (volume == NULL)
return(NULL);
KKASSERT(zone2_offset < volume->maxbuf_off);
++hammer_count_buffers;
buffer = kmalloc(sizeof(*buffer), hmp->m_misc,
M_WAITOK|M_ZERO|M_USE_RESERVE);
buffer->zone2_offset = zone2_offset;
buffer->zoneX_offset = buf_offset;
hammer_io_init(&buffer->io, volume, hammer_zone_to_iotype(zone));
buffer->io.offset = hammer_xlate_to_phys(volume->ondisk, zone2_offset);
buffer->io.bytes = bytes;
TAILQ_INIT(&buffer->node_list);
hammer_ref_interlock_true(&buffer->io.lock);
if (RB_INSERT(hammer_buf_rb_tree, &hmp->rb_bufs_root, buffer)) {
hammer_rel_volume(volume, 0);
buffer->io.volume = NULL;
if (hammer_rel_interlock(&buffer->io.lock, 1))
hammer_rel_interlock_done(&buffer->io.lock, 1);
--hammer_count_buffers;
kfree(buffer, hmp->m_misc);
goto again;
}
atomic_add_int(&hammer_count_refedbufs, 1);
found:
if (buffer->ondisk == NULL) {
*errorp = hammer_load_buffer(buffer, isnew);
if (*errorp)
buffer = NULL;
} else {
hammer_io_advance(&buffer->io);
hammer_ref_interlock_done(&buffer->io.lock);
*errorp = 0;
}
return(buffer);
}
void
hammer_sync_buffers(hammer_mount_t hmp, hammer_off_t base_offset, int bytes)
{
hammer_buffer_t buffer;
int error;
KKASSERT(hammer_is_zone_large_data(base_offset));
while (bytes > 0) {
buffer = RB_LOOKUP(hammer_buf_rb_tree, &hmp->rb_bufs_root,
base_offset);
if (buffer && (buffer->io.modified || buffer->io.running)) {
error = hammer_ref_buffer(buffer);
if (error == 0) {
hammer_io_wait(&buffer->io);
if (buffer->io.modified) {
hammer_io_write_interlock(&buffer->io);
hammer_io_flush(&buffer->io, 0);
hammer_io_done_interlock(&buffer->io);
hammer_io_wait(&buffer->io);
}
hammer_rel_buffer(buffer, 0);
}
}
base_offset += HAMMER_BUFSIZE;
bytes -= HAMMER_BUFSIZE;
}
}
int
hammer_del_buffers(hammer_mount_t hmp, hammer_off_t base_offset,
hammer_off_t zone2_offset, int bytes,
int report_conflicts)
{
hammer_buffer_t buffer;
hammer_volume_t volume;
int vol_no;
int error;
int ret_error;
vol_no = HAMMER_VOL_DECODE(zone2_offset);
volume = hammer_get_volume(hmp, vol_no, &ret_error);
KKASSERT(ret_error == 0);
while (bytes > 0) {
buffer = RB_LOOKUP(hammer_buf_rb_tree, &hmp->rb_bufs_root,
base_offset);
if (buffer) {
error = hammer_ref_buffer(buffer);
if (hammer_debug_general & 0x20000) {
hkprintf("delbufr %016jx rerr=%d 1ref=%d\n",
(intmax_t)buffer->zoneX_offset,
error,
hammer_oneref(&buffer->io.lock));
}
if (error == 0 && !hammer_oneref(&buffer->io.lock)) {
error = EAGAIN;
hammer_rel_buffer(buffer, 0);
}
if (error == 0) {
KKASSERT(buffer->zone2_offset == zone2_offset);
hammer_io_clear_modify(&buffer->io, 1);
buffer->io.reclaim = 1;
buffer->io.waitdep = 1;
KKASSERT(buffer->io.volume == volume);
hammer_rel_buffer(buffer, 0);
}
} else {
error = hammer_io_inval(volume, zone2_offset);
}
if (error) {
ret_error = error;
if (report_conflicts ||
(hammer_debug_general & 0x8000)) {
krateprintf(&hmp->kdiag,
"hammer_del_buffers: unable to "
"invalidate %016jx buffer=%p "
"rep=%d lkrefs=%08x\n",
(intmax_t)base_offset,
buffer, report_conflicts,
(buffer ? buffer->io.lock.refs : -1));
}
}
base_offset += HAMMER_BUFSIZE;
zone2_offset += HAMMER_BUFSIZE;
bytes -= HAMMER_BUFSIZE;
}
hammer_rel_volume(volume, 0);
return (ret_error);
}
static int
hammer_load_buffer(hammer_buffer_t buffer, int isnew)
{
hammer_volume_t volume;
int error;
volume = buffer->io.volume;
if (hammer_debug_io & 0x0004) {
hdkprintf("load_buffer %016jx %016jx isnew=%d od=%p\n",
(intmax_t)buffer->zoneX_offset,
(intmax_t)buffer->zone2_offset,
isnew, buffer->ondisk);
}
if (buffer->ondisk == NULL) {
if (isnew) {
error = hammer_io_new(volume->devvp, &buffer->io);
} else if (hammer_is_zone_large_data(buffer->zoneX_offset)) {
error = hammer_io_read(volume->devvp, &buffer->io,
buffer->io.bytes);
} else {
hammer_off_t limit;
limit = HAMMER_BIGBLOCK_DOALIGN(buffer->zone2_offset);
limit -= buffer->zone2_offset;
error = hammer_io_read(volume->devvp, &buffer->io,
limit);
}
if (error == 0)
buffer->ondisk = (void *)buffer->io.bp->b_data;
} else if (isnew) {
error = hammer_io_new(volume->devvp, &buffer->io);
} else {
error = 0;
}
if (error == 0) {
hammer_io_advance(&buffer->io);
hammer_ref_interlock_done(&buffer->io.lock);
} else {
hammer_rel_buffer(buffer, 1);
}
return (error);
}
int
hammer_unload_buffer(hammer_buffer_t buffer, void *data)
{
hammer_volume_t volume = (hammer_volume_t)data;
if (volume != NULL && volume != buffer->io.volume)
return 0;
if (buffer->io.ioerror) {
hammer_io_clear_error_noassert(&buffer->io);
atomic_add_int(&hammer_count_refedbufs, -1);
}
hammer_ref_interlock_true(&buffer->io.lock);
atomic_add_int(&hammer_count_refedbufs, 1);
hammer_io_clear_modify(&buffer->io, 1);
hammer_flush_buffer_nodes(buffer);
buffer->io.waitdep = 1;
hammer_rel_buffer(buffer, 1);
return(0);
}
int
hammer_ref_buffer(hammer_buffer_t buffer)
{
hammer_mount_t hmp;
int error;
int locked;
locked = hammer_ref_interlock(&buffer->io.lock);
hmp = buffer->io.hmp;
if (buffer->io.mod_root == &hmp->lose_root) {
lwkt_gettoken(&hmp->io_token);
if (buffer->io.mod_root == &hmp->lose_root) {
RB_REMOVE(hammer_mod_rb_tree,
buffer->io.mod_root, &buffer->io);
buffer->io.mod_root = NULL;
}
lwkt_reltoken(&hmp->io_token);
}
if (locked) {
atomic_add_int(&hammer_count_refedbufs, 1);
error = hammer_load_buffer(buffer, 0);
} else {
error = 0;
}
return(error);
}
void
hammer_rel_buffer(hammer_buffer_t buffer, int locked)
{
hammer_volume_t volume;
hammer_mount_t hmp;
struct buf *bp = NULL;
int freeme = 0;
hmp = buffer->io.hmp;
if (hammer_rel_interlock(&buffer->io.lock, locked) == 0)
return;
if (locked || (buffer->io.lock.refs & HAMMER_REFS_CHECK) == 0)
atomic_add_int(&hammer_count_refedbufs, -1);
bp = hammer_io_release(&buffer->io, locked);
if (buffer->io.bp == NULL && hammer_norefs(&buffer->io.lock)) {
RB_REMOVE(hammer_buf_rb_tree,
&buffer->io.hmp->rb_bufs_root,
buffer);
volume = buffer->io.volume;
buffer->io.volume = NULL;
hammer_rel_volume(volume, 0);
hammer_io_clear_modlist(&buffer->io);
hammer_flush_buffer_nodes(buffer);
KKASSERT(TAILQ_EMPTY(&buffer->node_list));
freeme = 1;
}
hammer_rel_interlock_done(&buffer->io.lock, locked);
if (bp)
brelse(bp);
if (freeme) {
--hammer_count_buffers;
kfree(buffer, hmp->m_misc);
}
}
static __inline
void *
_hammer_bread(hammer_mount_t hmp, hammer_off_t buf_offset, int bytes,
int isnew, int *errorp, hammer_buffer_t *bufferp)
{
hammer_buffer_t buffer;
int32_t xoff = (int32_t)buf_offset & HAMMER_BUFMASK;
buf_offset &= ~HAMMER_BUFMASK64;
KKASSERT(HAMMER_ZONE(buf_offset) != 0);
buffer = *bufferp;
if (buffer == NULL || (buffer->zone2_offset != buf_offset &&
buffer->zoneX_offset != buf_offset)) {
if (buffer)
hammer_rel_buffer(buffer, 0);
buffer = hammer_get_buffer(hmp, buf_offset, bytes, isnew, errorp);
*bufferp = buffer;
} else {
*errorp = 0;
}
if (buffer == NULL)
return(NULL);
else
return((char *)buffer->ondisk + xoff);
}
void *
hammer_bread(hammer_mount_t hmp, hammer_off_t buf_offset,
int *errorp, hammer_buffer_t *bufferp)
{
return(_hammer_bread(hmp, buf_offset, HAMMER_BUFSIZE, 0, errorp, bufferp));
}
void *
hammer_bread_ext(hammer_mount_t hmp, hammer_off_t buf_offset, int bytes,
int *errorp, hammer_buffer_t *bufferp)
{
bytes = HAMMER_BUFSIZE_DOALIGN(bytes);
return(_hammer_bread(hmp, buf_offset, bytes, 0, errorp, bufferp));
}
void *
hammer_bnew(hammer_mount_t hmp, hammer_off_t buf_offset,
int *errorp, hammer_buffer_t *bufferp)
{
return(_hammer_bread(hmp, buf_offset, HAMMER_BUFSIZE, 1, errorp, bufferp));
}
void *
hammer_bnew_ext(hammer_mount_t hmp, hammer_off_t buf_offset, int bytes,
int *errorp, hammer_buffer_t *bufferp)
{
bytes = HAMMER_BUFSIZE_DOALIGN(bytes);
return(_hammer_bread(hmp, buf_offset, bytes, 1, errorp, bufferp));
}
hammer_node_t
hammer_get_node(hammer_transaction_t trans, hammer_off_t node_offset,
int isnew, int *errorp)
{
hammer_mount_t hmp = trans->hmp;
hammer_node_t node;
int doload;
KKASSERT(hammer_is_zone_btree(node_offset));
again:
node = RB_LOOKUP(hammer_nod_rb_tree, &hmp->rb_nods_root, node_offset);
if (node == NULL) {
++hammer_count_nodes;
node = kmalloc(sizeof(*node), hmp->m_misc, M_WAITOK|M_ZERO|M_USE_RESERVE);
node->node_offset = node_offset;
node->hmp = hmp;
TAILQ_INIT(&node->cursor_list);
TAILQ_INIT(&node->cache_list);
if (RB_INSERT(hammer_nod_rb_tree, &hmp->rb_nods_root, node)) {
--hammer_count_nodes;
kfree(node, hmp->m_misc);
goto again;
}
doload = hammer_ref_interlock_true(&node->lock);
} else {
doload = hammer_ref_interlock(&node->lock);
}
if (doload) {
*errorp = hammer_load_node(trans, node, isnew);
if (*errorp)
node = NULL;
} else {
KKASSERT(node->ondisk);
*errorp = 0;
hammer_io_advance(&node->buffer->io);
}
return(node);
}
void
hammer_ref_node(hammer_node_t node)
{
KKASSERT(hammer_isactive(&node->lock) && node->ondisk != NULL);
hammer_ref(&node->lock);
}
static int
hammer_load_node(hammer_transaction_t trans, hammer_node_t node, int isnew)
{
hammer_buffer_t buffer;
hammer_off_t buf_offset;
hammer_mount_t hmp = trans->hmp;
int error;
error = 0;
if (node->ondisk == NULL) {
if ((buffer = node->buffer) != NULL) {
error = hammer_ref_buffer(buffer);
if (error == 0 && node->buffer == NULL) {
TAILQ_INSERT_TAIL(&buffer->node_list, node, entry);
node->buffer = buffer;
}
} else {
buf_offset = node->node_offset & ~HAMMER_BUFMASK64;
buffer = hammer_get_buffer(node->hmp, buf_offset,
HAMMER_BUFSIZE, 0, &error);
if (buffer) {
KKASSERT(error == 0);
TAILQ_INSERT_TAIL(&buffer->node_list, node, entry);
node->buffer = buffer;
}
}
if (error)
goto failed;
node->ondisk = (void *)((char *)buffer->ondisk +
(node->node_offset & HAMMER_BUFMASK));
if (isnew == 0 &&
(node->flags & HAMMER_NODE_CRCANY) == 0) {
if (hammer_crc_test_btree(hmp->version, node->ondisk) == 0) {
hdkprintf("CRC B-TREE NODE @ %016jx/%lu FAILED\n",
(intmax_t)node->node_offset,
sizeof(*node->ondisk));
if (hammer_debug_critical)
Debugger("CRC FAILED: B-TREE NODE");
node->flags |= HAMMER_NODE_CRCBAD;
} else {
node->flags |= HAMMER_NODE_CRCGOOD;
}
}
}
if (node->flags & HAMMER_NODE_CRCBAD) {
if (trans->flags & HAMMER_TRANSF_CRCDOM)
error = EDOM;
else
error = EIO;
}
failed:
if (error) {
_hammer_rel_node(node, 1);
} else {
hammer_ref_interlock_done(&node->lock);
}
return (error);
}
hammer_node_t
hammer_ref_node_safe(hammer_transaction_t trans, hammer_node_cache_t cache,
int *errorp)
{
hammer_node_t node;
int doload;
node = cache->node;
if (node != NULL) {
doload = hammer_ref_interlock(&node->lock);
if (doload) {
*errorp = hammer_load_node(trans, node, 0);
if (*errorp)
node = NULL;
} else {
KKASSERT(node->ondisk);
if (node->flags & HAMMER_NODE_CRCBAD) {
if (trans->flags & HAMMER_TRANSF_CRCDOM)
*errorp = EDOM;
else
*errorp = EIO;
_hammer_rel_node(node, 0);
node = NULL;
} else {
*errorp = 0;
}
}
} else {
*errorp = ENOENT;
}
return(node);
}
void
_hammer_rel_node(hammer_node_t node, int locked)
{
hammer_buffer_t buffer;
if (hammer_rel_interlock(&node->lock, locked) == 0)
return;
if (node->ondisk == NULL) {
hammer_flush_node(node, locked + 1);
return;
}
if (node->flags & HAMMER_NODE_NEEDSCRC) {
hammer_rel_interlock_done(&node->lock, locked);
return;
}
buffer = node->buffer;
node->ondisk = NULL;
if ((node->flags & HAMMER_NODE_FLUSH) == 0) {
hammer_rel_interlock_done(&node->lock, locked);
} else {
hammer_flush_node(node, locked + 1);
}
hammer_rel_buffer(buffer, 0);
}
void
hammer_rel_node(hammer_node_t node)
{
_hammer_rel_node(node, 0);
}
void
hammer_delete_node(hammer_transaction_t trans, hammer_node_t node)
{
KKASSERT((node->flags & HAMMER_NODE_DELETED) == 0);
node->flags |= HAMMER_NODE_DELETED;
hammer_blockmap_free(trans, node->node_offset, sizeof(*node->ondisk));
}
void
hammer_cache_node(hammer_node_cache_t cache, hammer_node_t node)
{
if (node == NULL || (node->flags & HAMMER_NODE_DELETED))
return;
if (cache->node == node)
return;
while (cache->node)
hammer_uncache_node(cache);
if (node->flags & HAMMER_NODE_DELETED)
return;
cache->node = node;
TAILQ_INSERT_TAIL(&node->cache_list, cache, entry);
}
void
hammer_uncache_node(hammer_node_cache_t cache)
{
hammer_node_t node;
if ((node = cache->node) != NULL) {
TAILQ_REMOVE(&node->cache_list, cache, entry);
cache->node = NULL;
if (TAILQ_EMPTY(&node->cache_list))
hammer_flush_node(node, 0);
}
}
void
hammer_flush_node(hammer_node_t node, int locked)
{
hammer_node_cache_t cache;
hammer_buffer_t buffer;
hammer_mount_t hmp = node->hmp;
int dofree;
while ((cache = TAILQ_FIRST(&node->cache_list)) != NULL) {
TAILQ_REMOVE(&node->cache_list, cache, entry);
cache->node = NULL;
}
if (node->ondisk == NULL && hammer_norefs(&node->lock)) {
KKASSERT((node->flags & HAMMER_NODE_NEEDSCRC) == 0);
RB_REMOVE(hammer_nod_rb_tree, &node->hmp->rb_nods_root, node);
if ((buffer = node->buffer) != NULL) {
node->buffer = NULL;
TAILQ_REMOVE(&buffer->node_list, node, entry);
}
dofree = 1;
} else {
dofree = 0;
}
if (locked)
hammer_rel_interlock_done(&node->lock, locked - 1);
if (dofree) {
--hammer_count_nodes;
kfree(node, hmp->m_misc);
}
}
void
hammer_flush_buffer_nodes(hammer_buffer_t buffer)
{
hammer_node_t node;
while ((node = TAILQ_FIRST(&buffer->node_list)) != NULL) {
KKASSERT(node->ondisk == NULL);
KKASSERT((node->flags & HAMMER_NODE_NEEDSCRC) == 0);
if (hammer_try_interlock_norefs(&node->lock)) {
hammer_ref(&node->lock);
node->flags |= HAMMER_NODE_FLUSH;
_hammer_rel_node(node, 1);
} else {
KKASSERT(node->buffer != NULL);
buffer = node->buffer;
node->buffer = NULL;
TAILQ_REMOVE(&buffer->node_list, node, entry);
}
}
}
hammer_node_t
hammer_alloc_btree(hammer_transaction_t trans, hammer_off_t hint, int *errorp)
{
hammer_buffer_t buffer = NULL;
hammer_node_t node = NULL;
hammer_off_t node_offset;
node_offset = hammer_blockmap_alloc(trans, HAMMER_ZONE_BTREE_INDEX,
sizeof(struct hammer_node_ondisk),
hint, errorp);
if (*errorp == 0) {
node = hammer_get_node(trans, node_offset, 1, errorp);
hammer_modify_node_noundo(trans, node);
bzero(node->ondisk, sizeof(*node->ondisk));
hammer_modify_node_done(node);
}
if (buffer)
hammer_rel_buffer(buffer, 0);
return(node);
}
void *
hammer_alloc_data(hammer_transaction_t trans, int32_t data_len,
uint16_t rec_type, hammer_off_t *data_offsetp,
hammer_buffer_t *data_bufferp,
hammer_off_t hint, int *errorp)
{
void *data;
int zone;
if (data_len) {
switch(rec_type) {
case HAMMER_RECTYPE_INODE:
case HAMMER_RECTYPE_DIRENTRY:
case HAMMER_RECTYPE_EXT:
case HAMMER_RECTYPE_FIX:
case HAMMER_RECTYPE_PFS:
case HAMMER_RECTYPE_SNAPSHOT:
case HAMMER_RECTYPE_CONFIG:
zone = HAMMER_ZONE_META_INDEX;
break;
case HAMMER_RECTYPE_DATA:
case HAMMER_RECTYPE_DB:
zone = hammer_data_zone_index(data_len);
if (zone == HAMMER_ZONE_LARGE_DATA_INDEX) {
data_len = HAMMER_BUFSIZE_DOALIGN(data_len);
}
break;
default:
hpanic("rec_type %04x unknown", rec_type);
zone = HAMMER_ZONE_UNAVAIL_INDEX;
break;
}
*data_offsetp = hammer_blockmap_alloc(trans, zone, data_len,
hint, errorp);
} else {
*data_offsetp = 0;
}
data = NULL;
if (*errorp == 0 && data_bufferp && data_len)
data = hammer_bread_ext(trans->hmp, *data_offsetp, data_len,
errorp, data_bufferp);
return(data);
}
static int hammer_sync_scan2(struct mount *mp, struct vnode *vp, void *data);
struct hammer_sync_info {
int error;
};
int
hammer_queue_inodes_flusher(hammer_mount_t hmp, int waitfor)
{
struct hammer_sync_info info;
info.error = 0;
if (waitfor == MNT_WAIT) {
vsyncscan(hmp->mp, VMSC_GETVP | VMSC_ONEPASS,
hammer_sync_scan2, &info);
} else {
vsyncscan(hmp->mp, VMSC_GETVP | VMSC_ONEPASS | VMSC_NOWAIT,
hammer_sync_scan2, &info);
}
return(info.error);
}
int
hammer_sync_hmp(hammer_mount_t hmp, int waitfor)
{
struct hammer_sync_info info;
int flags;
flags = VMSC_GETVP;
if (waitfor & MNT_LAZY)
flags |= VMSC_ONEPASS;
info.error = 0;
vsyncscan(hmp->mp, flags | VMSC_NOWAIT, hammer_sync_scan2, &info);
if (info.error == 0 && (waitfor & MNT_WAIT)) {
vsyncscan(hmp->mp, flags, hammer_sync_scan2, &info);
}
if (waitfor == MNT_WAIT) {
hammer_flusher_sync(hmp);
hammer_flusher_sync(hmp);
} else {
hammer_flusher_async(hmp, NULL);
hammer_flusher_async(hmp, NULL);
}
return(info.error);
}
static int
hammer_sync_scan2(struct mount *mp, struct vnode *vp, void *data)
{
struct hammer_sync_info *info = data;
hammer_inode_t ip;
int error;
ip = VTOI(vp);
if (ip == NULL)
return(0);
if (vp->v_type == VNON || vp->v_type == VBAD) {
vclrisdirty(vp);
return(0);
}
if ((ip->flags & HAMMER_INODE_MODMASK) == 0 &&
RB_EMPTY(&vp->v_rbdirty_tree))
{
vclrisdirty(vp);
return(0);
}
error = VOP_FSYNC(vp, MNT_NOWAIT, 0);
if (error)
info->error = error;
return(0);
}