7f725bcd5c
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
777 lines
18 KiB
C
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);
|
|
}
|