freebsd-dev/sys/fs/nandfs/nandfs_cpfile.c
Grzegorz Bernacki 7f725bcd5c Import work done under project/nand (@235533) into head.
The NAND Flash environment consists of several distinct components:
  - NAND framework (drivers harness for NAND controllers and NAND chips)
  - NAND simulator (NANDsim)
  - NAND file system (NAND FS)
  - Companion tools and utilities
  - Documentation (manual pages)

This work is still experimental. Please use with caution.

Obtained from: Semihalf
Supported by:  FreeBSD Foundation, Juniper Networks
2012-05-17 10:11:18 +00:00

777 lines
18 KiB
C

/*-
* Copyright (c) 2010-2012 Semihalf.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include <sys/cdefs.h>
__FBSDID("$FreeBSD$");
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/conf.h>
#include <sys/kernel.h>
#include <sys/lock.h>
#include <sys/malloc.h>
#include <sys/mount.h>
#include <sys/mutex.h>
#include <sys/namei.h>
#include <sys/sysctl.h>
#include <sys/vnode.h>
#include <sys/buf.h>
#include <sys/bio.h>
#include <vm/vm.h>
#include <vm/vm_param.h>
#include <vm/vm_kern.h>
#include <vm/vm_page.h>
#include "nandfs_mount.h"
#include "nandfs.h"
#include "nandfs_subr.h"
static int
nandfs_checkpoint_size(struct nandfs_device *fsdev)
{
return (fsdev->nd_fsdata.f_checkpoint_size);
}
static int
nandfs_checkpoint_blk_offset(struct nandfs_device *fsdev, uint64_t cn,
uint64_t *blk, uint64_t *offset)
{
uint64_t off;
uint16_t cp_size, cp_per_blk;
KASSERT((cn), ("checkpoing cannot be zero"));
cp_size = fsdev->nd_fsdata.f_checkpoint_size;
cp_per_blk = fsdev->nd_blocksize / cp_size;
off = roundup(sizeof(struct nandfs_cpfile_header), cp_size) / cp_size;
off += (cn - 1);
*blk = off / cp_per_blk;
*offset = (off % cp_per_blk) * cp_size;
return (0);
}
static int
nandfs_checkpoint_blk_remaining(struct nandfs_device *fsdev, uint64_t cn,
uint64_t blk, uint64_t offset)
{
uint16_t cp_size, cp_remaining;
cp_size = fsdev->nd_fsdata.f_checkpoint_size;
cp_remaining = (fsdev->nd_blocksize - offset) / cp_size;
return (cp_remaining);
}
int
nandfs_get_checkpoint(struct nandfs_device *fsdev, struct nandfs_node *cp_node,
uint64_t cn)
{
struct buf *bp;
uint64_t blk, offset;
int error;
if (cn != fsdev->nd_last_cno && cn != (fsdev->nd_last_cno + 1)) {
return (-1);
}
error = nandfs_bread(cp_node, 0, NOCRED, 0, &bp);
if (error) {
brelse(bp);
return (-1);
}
error = nandfs_dirty_buf(bp, 0);
if (error)
return (-1);
nandfs_checkpoint_blk_offset(fsdev, cn, &blk, &offset);
if (blk != 0) {
if (blk < cp_node->nn_inode.i_blocks)
error = nandfs_bread(cp_node, blk, NOCRED, 0, &bp);
else
error = nandfs_bcreate(cp_node, blk, NOCRED, 0, &bp);
if (error) {
if (bp)
brelse(bp);
return (-1);
}
nandfs_dirty_buf(bp, 1);
}
DPRINTF(CPFILE, ("%s: cn:%#jx entry block:%#jx offset:%#jx\n",
__func__, (uintmax_t)cn, (uintmax_t)blk, (uintmax_t)offset));
return (0);
}
int
nandfs_set_checkpoint(struct nandfs_device *fsdev, struct nandfs_node *cp_node,
uint64_t cn, struct nandfs_inode *ifile_inode, uint64_t nblocks)
{
struct nandfs_cpfile_header *cnh;
struct nandfs_checkpoint *cnp;
struct buf *bp;
uint64_t blk, offset;
int error;
if (cn != fsdev->nd_last_cno && cn != (fsdev->nd_last_cno + 1)) {
nandfs_error("%s: trying to set invalid chekpoint %jx - %jx\n",
__func__, cn, fsdev->nd_last_cno);
return (-1);
}
error = nandfs_bread(cp_node, 0, NOCRED, 0, &bp);
if (error) {
brelse(bp);
return error;
}
cnh = (struct nandfs_cpfile_header *) bp->b_data;
cnh->ch_ncheckpoints++;
nandfs_checkpoint_blk_offset(fsdev, cn, &blk, &offset);
if(blk != 0) {
brelse(bp);
error = nandfs_bread(cp_node, blk, NOCRED, 0, &bp);
if (error) {
brelse(bp);
return error;
}
}
cnp = (struct nandfs_checkpoint *)((uint8_t *)bp->b_data + offset);
cnp->cp_flags = 0;
cnp->cp_checkpoints_count = 1;
memset(&cnp->cp_snapshot_list, 0, sizeof(struct nandfs_snapshot_list));
cnp->cp_cno = cn;
cnp->cp_create = fsdev->nd_ts.tv_sec;
cnp->cp_nblk_inc = nblocks;
cnp->cp_blocks_count = 0;
memcpy (&cnp->cp_ifile_inode, ifile_inode, sizeof(cnp->cp_ifile_inode));
DPRINTF(CPFILE, ("%s: cn:%#jx ctime:%#jx nblk:%#jx\n",
__func__, (uintmax_t)cn, (uintmax_t)cnp->cp_create,
(uintmax_t)nblocks));
brelse(bp);
return (0);
}
static int
nandfs_cp_mounted(struct nandfs_device *nandfsdev, uint64_t cno)
{
struct nandfsmount *nmp;
int mounted = 0;
mtx_lock(&nandfsdev->nd_mutex);
/* No double-mounting of the same checkpoint */
STAILQ_FOREACH(nmp, &nandfsdev->nd_mounts, nm_next_mount) {
if (nmp->nm_mount_args.cpno == cno) {
mounted = 1;
break;
}
}
mtx_unlock(&nandfsdev->nd_mutex);
return (mounted);
}
static int
nandfs_cp_set_snapshot(struct nandfs_node *cp_node, uint64_t cno)
{
struct nandfs_device *fsdev;
struct nandfs_cpfile_header *cnh;
struct nandfs_checkpoint *cnp;
struct nandfs_snapshot_list *list;
struct buf *bp;
uint64_t blk, prev_blk, offset;
uint64_t curr, prev;
int error;
fsdev = cp_node->nn_nandfsdev;
/* Get snapshot data */
nandfs_checkpoint_blk_offset(fsdev, cno, &blk, &offset);
error = nandfs_bread(cp_node, blk, NOCRED, 0, &bp);
if (error) {
brelse(bp);
return (error);
}
cnp = (struct nandfs_checkpoint *)(bp->b_data + offset);
if (cnp->cp_flags & NANDFS_CHECKPOINT_INVALID) {
brelse(bp);
return (ENOENT);
}
if ((cnp->cp_flags & NANDFS_CHECKPOINT_SNAPSHOT)) {
brelse(bp);
return (EINVAL);
}
brelse(bp);
/* Get list from header */
error = nandfs_bread(cp_node, 0, NOCRED, 0, &bp);
if (error) {
brelse(bp);
return (error);
}
cnh = (struct nandfs_cpfile_header *) bp->b_data;
list = &cnh->ch_snapshot_list;
prev = list->ssl_prev;
brelse(bp);
prev_blk = ~(0);
curr = 0;
while (prev > cno) {
curr = prev;
nandfs_checkpoint_blk_offset(fsdev, prev, &prev_blk, &offset);
error = nandfs_bread(cp_node, prev_blk, NOCRED, 0, &bp);
if (error) {
brelse(bp);
return (error);
}
cnp = (struct nandfs_checkpoint *)(bp->b_data + offset);
list = &cnp->cp_snapshot_list;
prev = list->ssl_prev;
brelse(bp);
}
if (curr == 0) {
nandfs_bread(cp_node, 0, NOCRED, 0, &bp);
cnh = (struct nandfs_cpfile_header *) bp->b_data;
list = &cnh->ch_snapshot_list;
} else {
nandfs_checkpoint_blk_offset(fsdev, curr, &blk, &offset);
error = nandfs_bread(cp_node, blk, NOCRED, 0, &bp);
if (error) {
brelse(bp);
return (error);
}
cnp = (struct nandfs_checkpoint *)(bp->b_data + offset);
list = &cnp->cp_snapshot_list;
}
list->ssl_prev = cno;
error = nandfs_dirty_buf(bp, 0);
if (error)
return (error);
/* Update snapshot for cno */
nandfs_checkpoint_blk_offset(fsdev, cno, &blk, &offset);
error = nandfs_bread(cp_node, blk, NOCRED, 0, &bp);
if (error) {
brelse(bp);
return (error);
}
cnp = (struct nandfs_checkpoint *)(bp->b_data + offset);
list = &cnp->cp_snapshot_list;
list->ssl_prev = prev;
list->ssl_next = curr;
cnp->cp_flags |= NANDFS_CHECKPOINT_SNAPSHOT;
nandfs_dirty_buf(bp, 1);
if (prev == 0) {
nandfs_bread(cp_node, 0, NOCRED, 0, &bp);
cnh = (struct nandfs_cpfile_header *) bp->b_data;
list = &cnh->ch_snapshot_list;
} else {
/* Update snapshot list for prev */
nandfs_checkpoint_blk_offset(fsdev, prev, &blk, &offset);
error = nandfs_bread(cp_node, blk, NOCRED, 0, &bp);
if (error) {
brelse(bp);
return (error);
}
cnp = (struct nandfs_checkpoint *)(bp->b_data + offset);
list = &cnp->cp_snapshot_list;
}
list->ssl_next = cno;
nandfs_dirty_buf(bp, 1);
/* Update header */
error = nandfs_bread(cp_node, 0, NOCRED, 0, &bp);
if (error) {
brelse(bp);
return (error);
}
cnh = (struct nandfs_cpfile_header *) bp->b_data;
cnh->ch_nsnapshots++;
nandfs_dirty_buf(bp, 1);
return (0);
}
static int
nandfs_cp_clr_snapshot(struct nandfs_node *cp_node, uint64_t cno)
{
struct nandfs_device *fsdev;
struct nandfs_cpfile_header *cnh;
struct nandfs_checkpoint *cnp;
struct nandfs_snapshot_list *list;
struct buf *bp;
uint64_t blk, offset, snapshot_cnt;
uint64_t next, prev;
int error;
fsdev = cp_node->nn_nandfsdev;
/* Get snapshot data */
nandfs_checkpoint_blk_offset(fsdev, cno, &blk, &offset);
error = nandfs_bread(cp_node, blk, NOCRED, 0, &bp);
if (error) {
brelse(bp);
return (error);
}
cnp = (struct nandfs_checkpoint *)(bp->b_data + offset);
if (cnp->cp_flags & NANDFS_CHECKPOINT_INVALID) {
brelse(bp);
return (ENOENT);
}
if (!(cnp->cp_flags & NANDFS_CHECKPOINT_SNAPSHOT)) {
brelse(bp);
return (EINVAL);
}
list = &cnp->cp_snapshot_list;
next = list->ssl_next;
prev = list->ssl_prev;
brelse(bp);
/* Get previous snapshot */
if (prev != 0) {
nandfs_checkpoint_blk_offset(fsdev, prev, &blk, &offset);
error = nandfs_bread(cp_node, blk, NOCRED, 0, &bp);
if (error) {
brelse(bp);
return (error);
}
cnp = (struct nandfs_checkpoint *)(bp->b_data + offset);
list = &cnp->cp_snapshot_list;
} else {
nandfs_bread(cp_node, 0, NOCRED, 0, &bp);
cnh = (struct nandfs_cpfile_header *) bp->b_data;
list = &cnh->ch_snapshot_list;
}
list->ssl_next = next;
error = nandfs_dirty_buf(bp, 0);
if (error)
return (error);
/* Get next snapshot */
if (next != 0) {
nandfs_checkpoint_blk_offset(fsdev, next, &blk, &offset);
error = nandfs_bread(cp_node, blk, NOCRED, 0, &bp);
if (error) {
brelse(bp);
return (error);
}
cnp = (struct nandfs_checkpoint *)(bp->b_data + offset);
list = &cnp->cp_snapshot_list;
} else {
nandfs_bread(cp_node, 0, NOCRED, 0, &bp);
cnh = (struct nandfs_cpfile_header *) bp->b_data;
list = &cnh->ch_snapshot_list;
}
list->ssl_prev = prev;
nandfs_dirty_buf(bp, 1);
/* Update snapshot list for cno */
nandfs_checkpoint_blk_offset(fsdev, cno, &blk, &offset);
error = nandfs_bread(cp_node, blk, NOCRED, 0, &bp);
if (error) {
brelse(bp);
return (error);
}
cnp = (struct nandfs_checkpoint *)(bp->b_data + offset);
list = &cnp->cp_snapshot_list;
list->ssl_prev = 0;
list->ssl_next = 0;
cnp->cp_flags &= !NANDFS_CHECKPOINT_SNAPSHOT;
nandfs_dirty_buf(bp, 1);
/* Update header */
error = nandfs_bread(cp_node, 0, NOCRED, 0, &bp);
if (error) {
brelse(bp);
return (error);
}
cnh = (struct nandfs_cpfile_header *) bp->b_data;
snapshot_cnt = cnh->ch_nsnapshots;
snapshot_cnt--;
cnh->ch_nsnapshots = snapshot_cnt;
nandfs_dirty_buf(bp, 1);
return (0);
}
int
nandfs_chng_cpmode(struct nandfs_node *node, struct nandfs_cpmode *ncpm)
{
struct nandfs_device *fsdev;
uint64_t cno = ncpm->ncpm_cno;
int mode = ncpm->ncpm_mode;
int ret;
fsdev = node->nn_nandfsdev;
VOP_LOCK(NTOV(node), LK_EXCLUSIVE);
switch (mode) {
case NANDFS_CHECKPOINT:
if (nandfs_cp_mounted(fsdev, cno)) {
ret = EBUSY;
} else
ret = nandfs_cp_clr_snapshot(node, cno);
break;
case NANDFS_SNAPSHOT:
ret = nandfs_cp_set_snapshot(node, cno);
break;
default:
ret = EINVAL;
break;
}
VOP_UNLOCK(NTOV(node), 0);
return (ret);
}
static void
nandfs_cpinfo_fill(struct nandfs_checkpoint *cnp, struct nandfs_cpinfo *nci)
{
nci->nci_flags = cnp->cp_flags;
nci->nci_pad = 0;
nci->nci_cno = cnp->cp_cno;
nci->nci_create = cnp->cp_create;
nci->nci_nblk_inc = cnp->cp_nblk_inc;
nci->nci_blocks_count = cnp->cp_blocks_count;
nci->nci_next = cnp->cp_snapshot_list.ssl_next;
DPRINTF(CPFILE, ("%s: cn:%#jx ctime:%#jx\n",
__func__, (uintmax_t)cnp->cp_cno,
(uintmax_t)cnp->cp_create));
}
static int
nandfs_get_cpinfo_cp(struct nandfs_node *node, uint64_t cno,
struct nandfs_cpinfo *nci, uint32_t mnmembs, uint32_t *nmembs)
{
struct nandfs_device *fsdev;
struct buf *bp;
uint64_t blk, offset, last_cno, i;
uint16_t remaining;
int error;
#ifdef INVARIANTS
uint64_t testblk, testoffset;
#endif
if (cno == 0) {
return (ENOENT);
}
if (mnmembs < 1) {
return (EINVAL);
}
fsdev = node->nn_nandfsdev;
last_cno = fsdev->nd_last_cno;
DPRINTF(CPFILE, ("%s: cno:%#jx mnmembs: %#jx last:%#jx\n", __func__,
(uintmax_t)cno, (uintmax_t)mnmembs,
(uintmax_t)fsdev->nd_last_cno));
/*
* do {
* get block
* read checkpoints until we hit last checkpoint, end of block or
* requested number
* } while (last read checkpoint <= last checkpoint on fs &&
* read checkpoints < request number);
*/
*nmembs = i = 0;
do {
nandfs_checkpoint_blk_offset(fsdev, cno, &blk, &offset);
remaining = nandfs_checkpoint_blk_remaining(fsdev, cno,
blk, offset);
error = nandfs_bread(node, blk, NOCRED, 0, &bp);
if (error) {
brelse(bp);
return (error);
}
while (cno <= last_cno && i < mnmembs && remaining) {
#ifdef INVARIANTS
nandfs_checkpoint_blk_offset(fsdev, cno, &testblk,
&testoffset);
KASSERT(testblk == blk, ("testblk != blk"));
KASSERT(testoffset == offset, ("testoffset != offset"));
#endif
DPRINTF(CPFILE, ("%s: cno %#jx\n", __func__,
(uintmax_t)cno));
nandfs_cpinfo_fill((struct nandfs_checkpoint *)
(bp->b_data + offset), nci);
offset += nandfs_checkpoint_size(fsdev);
i++;
nci++;
cno++;
(*nmembs)++;
remaining--;
}
brelse(bp);
} while (cno <= last_cno && i < mnmembs);
return (0);
}
static int
nandfs_get_cpinfo_sp(struct nandfs_node *node, uint64_t cno,
struct nandfs_cpinfo *nci, uint32_t mnmembs, uint32_t *nmembs)
{
struct nandfs_checkpoint *cnp;
struct nandfs_cpfile_header *cnh;
struct nandfs_device *fsdev;
struct buf *bp = NULL;
uint64_t curr = 0;
uint64_t blk, offset, curr_cno;
uint32_t flag;
int i, error;
if (cno == 0 || cno == ~(0))
return (ENOENT);
fsdev = node->nn_nandfsdev;
curr_cno = cno;
if (nmembs)
*nmembs = 0;
if (curr_cno == 1) {
/* Get list from header */
error = nandfs_bread(node, 0, NOCRED, 0, &bp);
if (error) {
brelse(bp);
return (error);
}
cnh = (struct nandfs_cpfile_header *) bp->b_data;
curr_cno = cnh->ch_snapshot_list.ssl_next;
brelse(bp);
bp = NULL;
/* No snapshots */
if (curr_cno == 0)
return (0);
}
for (i = 0; i < mnmembs; i++, nci++) {
nandfs_checkpoint_blk_offset(fsdev, curr_cno, &blk, &offset);
if (i == 0 || curr != blk) {
if (bp)
brelse(bp);
error = nandfs_bread(node, blk, NOCRED, 0, &bp);
if (error) {
brelse(bp);
return (ENOENT);
}
curr = blk;
}
cnp = (struct nandfs_checkpoint *)(bp->b_data + offset);
flag = cnp->cp_flags;
if (!(flag & NANDFS_CHECKPOINT_SNAPSHOT) ||
(flag & NANDFS_CHECKPOINT_INVALID))
break;
nci->nci_flags = flag;
nci->nci_pad = 0;
nci->nci_cno = cnp->cp_cno;
nci->nci_create = cnp->cp_create;
nci->nci_nblk_inc = cnp->cp_nblk_inc;
nci->nci_blocks_count = cnp->cp_blocks_count;
nci->nci_next = cnp->cp_snapshot_list.ssl_next;
if (nmembs)
(*nmembs)++;
curr_cno = nci->nci_next;
if (!curr_cno)
break;
}
brelse(bp);
return (0);
}
int
nandfs_get_cpinfo(struct nandfs_node *node, uint64_t cno, uint16_t flags,
struct nandfs_cpinfo *nci, uint32_t nmembs, uint32_t *nnmembs)
{
int error;
VOP_LOCK(NTOV(node), LK_EXCLUSIVE);
switch (flags) {
case NANDFS_CHECKPOINT:
error = nandfs_get_cpinfo_cp(node, cno, nci, nmembs, nnmembs);
break;
case NANDFS_SNAPSHOT:
error = nandfs_get_cpinfo_sp(node, cno, nci, nmembs, nnmembs);
break;
default:
error = EINVAL;
break;
}
VOP_UNLOCK(NTOV(node), 0);
return (error);
}
int
nandfs_get_cpinfo_ioctl(struct nandfs_node *node, struct nandfs_argv *nargv)
{
struct nandfs_cpinfo *nci;
uint64_t cno = nargv->nv_index;
void *buf = (void *)((uintptr_t)nargv->nv_base);
uint16_t flags = nargv->nv_flags;
uint32_t nmembs = 0;
int error;
if (nargv->nv_nmembs > NANDFS_CPINFO_MAX)
return (EINVAL);
nci = malloc(sizeof(struct nandfs_cpinfo) * nargv->nv_nmembs,
M_NANDFSTEMP, M_WAITOK | M_ZERO);
error = nandfs_get_cpinfo(node, cno, flags, nci, nargv->nv_nmembs, &nmembs);
if (error == 0) {
nargv->nv_nmembs = nmembs;
error = copyout(nci, buf,
sizeof(struct nandfs_cpinfo) * nmembs);
}
free(nci, M_NANDFSTEMP);
return (error);
}
int
nandfs_delete_cp(struct nandfs_node *node, uint64_t start, uint64_t end)
{
struct nandfs_checkpoint *cnp;
struct nandfs_device *fsdev;
struct buf *bp;
uint64_t cno = start, blk, offset;
int error;
DPRINTF(CPFILE, ("%s: delete cno %jx-%jx\n", __func__, start, end));
VOP_LOCK(NTOV(node), LK_EXCLUSIVE);
fsdev = node->nn_nandfsdev;
for (cno = start; cno <= end; cno++) {
if (!cno)
continue;
nandfs_checkpoint_blk_offset(fsdev, cno, &blk, &offset);
error = nandfs_bread(node, blk, NOCRED, 0, &bp);
if (error) {
VOP_UNLOCK(NTOV(node), 0);
brelse(bp);
return (error);
}
cnp = (struct nandfs_checkpoint *)(bp->b_data + offset);
if (cnp->cp_flags & NANDFS_CHECKPOINT_SNAPSHOT) {
brelse(bp);
VOP_UNLOCK(NTOV(node), 0);
return (0);
}
cnp->cp_flags |= NANDFS_CHECKPOINT_INVALID;
error = nandfs_dirty_buf(bp, 0);
if (error)
return (error);
}
VOP_UNLOCK(NTOV(node), 0);
return (0);
}
int
nandfs_make_snap(struct nandfs_device *fsdev, uint64_t *cno)
{
struct nandfs_cpmode cpm;
int error;
*cno = cpm.ncpm_cno = fsdev->nd_last_cno;
cpm.ncpm_mode = NANDFS_SNAPSHOT;
error = nandfs_chng_cpmode(fsdev->nd_cp_node, &cpm);
return (error);
}
int
nandfs_delete_snap(struct nandfs_device *fsdev, uint64_t cno)
{
struct nandfs_cpmode cpm;
int error;
cpm.ncpm_cno = cno;
cpm.ncpm_mode = NANDFS_CHECKPOINT;
error = nandfs_chng_cpmode(fsdev->nd_cp_node, &cpm);
return (error);
}
int nandfs_get_cpstat(struct nandfs_node *cp_node, struct nandfs_cpstat *ncp)
{
struct nandfs_device *fsdev;
struct nandfs_cpfile_header *cnh;
struct buf *bp;
int error;
VOP_LOCK(NTOV(cp_node), LK_EXCLUSIVE);
fsdev = cp_node->nn_nandfsdev;
/* Get header */
error = nandfs_bread(cp_node, 0, NOCRED, 0, &bp);
if (error) {
brelse(bp);
VOP_UNLOCK(NTOV(cp_node), 0);
return (error);
}
cnh = (struct nandfs_cpfile_header *) bp->b_data;
ncp->ncp_cno = fsdev->nd_last_cno;
ncp->ncp_ncps = cnh->ch_ncheckpoints;
ncp->ncp_nss = cnh->ch_nsnapshots;
DPRINTF(CPFILE, ("%s: cno:%#jx ncps:%#jx nss:%#jx\n",
__func__, ncp->ncp_cno, ncp->ncp_ncps, ncp->ncp_nss));
brelse(bp);
VOP_UNLOCK(NTOV(cp_node), 0);
return (0);
}