/*
* Copyright (c) 2008, 2009 Reinoud Zandijk
* 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 ``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 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.
*
*/
/*
* Callback from genfs to allocate len bytes at offset off; only called when
* filling up gaps in the allocation.
*/
static int
nilfs_gop_alloc(struct vnode *vp, off_t off,
off_t len, int flags, kauth_cred_t cred)
{
DPRINTF(NOTIMPL, ("nilfs_gop_alloc not implemented\n"));
DPRINTF(ALLOC, ("nilfs_gop_alloc called for %"PRIu64" bytes\n", len));
return 0;
}
/*
* callback from genfs to update our flags
*/
static void
nilfs_gop_markupdate(struct vnode *vp, int flags)
{
struct nilfs_node *nilfs_node = VTOI(vp);
u_long mask = 0;
/*
* XXX the "30" below could be dynamic, thereby eliminating one
* more instance of the "number to vfs" mapping problem, but
* "30" is the order as taken from sys/mount.h
*/
sysctl_createv(clog, 0, NULL, &node,
CTLFLAG_PERMANENT,
CTLTYPE_NODE, "nilfs",
SYSCTL_DESCR("NTT's NILFSv2"),
NULL, 0, NULL, 0,
CTL_VFS, 30, CTL_EOL);
#ifdef DEBUG
sysctl_createv(clog, 0, NULL, &node,
CTLFLAG_PERMANENT|CTLFLAG_READWRITE,
CTLTYPE_INT, "verbose",
SYSCTL_DESCR("Bitmask for filesystem debugging"),
NULL, 0, &nilfs_verbose, 0,
CTL_VFS, 30, NILFS_VERBOSE_SYSCTLOPT, CTL_EOL);
#endif
}
static int
nilfs_modcmd(modcmd_t cmd, void *arg)
{
int error;
switch (cmd) {
case MODULE_CMD_INIT:
error = vfs_attach(&nilfs_vfsops);
if (error != 0)
break;
break;
case MODULE_CMD_FINI:
error = vfs_detach(&nilfs_vfsops);
if (error != 0)
break;
break;
default:
error = ENOTTY;
break;
}
if (!sb1ok && !sb2ok) {
printf("nilfs: no valid superblocks found\n");
return EINVAL;
}
return 0;
}
/* XXX NOTHING from the system nodes should need to be written here */
static void
nilfs_unmount_base(struct nilfs_device *nilfsdev)
{
int error __diagused;
if (!nilfsdev)
return;
/* remove all our information */
error = vinvalbuf(nilfsdev->devvp, 0, FSCRED, curlwp, 0, 0);
KASSERT(error == 0);
/* release the device's system nodes */
nilfs_release_system_nodes(nilfsdev);
/* TODO writeout super_block? */
}
static int
nilfs_mount_base(struct nilfs_device *nilfsdev,
struct mount *mp, struct nilfs_args *args)
{
struct lwp *l = curlwp;
uint64_t last_pseg, last_cno, last_seq;
uint32_t log_blocksize;
int error;
/* flush out any old buffers remaining from a previous use. */
if ((error = vinvalbuf(nilfsdev->devvp, V_SAVE, l->l_cred, l, 0, 0)))
return error;
/* read in our superblock */
error = nilfs_read_superblock(nilfsdev);
if (error) {
printf("nilfs_mount: can't read in super block : %d\n", error);
return error;
}
DPRINTF(VOLUMES, ("nilfs_mount: accepted super block\n"));
/* search for the super root and roll forward when needed */
nilfs_search_super_root(nilfsdev);
nilfsdev->mount_state = nilfs_rw16(nilfsdev->super.s_state);
if (nilfsdev->mount_state != NILFS_VALID_FS) {
printf("FS is seriously damaged, needs repairing\n");
printf("aborting mount\n");
return EINVAL;
}
/*
* FS should be ok now. The superblock and the last segsum could be
* updated from the repair so extract running values again.
*/
last_pseg = nilfs_rw64(nilfsdev->super.s_last_pseg); /*blknr */
last_cno = nilfs_rw64(nilfsdev->super.s_last_cno);
last_seq = nilfs_rw64(nilfsdev->super.s_last_seq);
DPRINTF(VOLUMES, ("nilfs_mount: accepted super root\n"));
/* create system vnodes for DAT, CP and SEGSUM */
error = nilfs_create_system_nodes(nilfsdev);
if (error)
nilfs_unmount_base(nilfsdev);
return error;
}
static void
nilfs_unmount_device(struct nilfs_device *nilfsdev)
{
int error;
/* is there anything? */
if (nilfsdev == NULL)
return;
/* remove the device only if we're the last reference */
nilfsdev->refcnt--;
if (nilfsdev->refcnt >= 1)
return;
/* unmount our base */
nilfs_unmount_base(nilfsdev);
/* remove from our device list */
SLIST_REMOVE(&nilfs_devices, nilfsdev, nilfs_device, next_device);
/* close device */
DPRINTF(VOLUMES, ("closing device\n"));
/* remove our mount reference before closing device */
spec_node_setmountedfs(nilfsdev->devvp, NULL);
/* devvp is still locked by us */
vn_lock(nilfsdev->devvp, LK_EXCLUSIVE | LK_RETRY);
error = VOP_CLOSE(nilfsdev->devvp, FREAD | FWRITE, NOCRED);
if (error)
printf("Error during closure of device! error %d, "
"device might stay locked\n", error);
DPRINTF(VOLUMES, ("device close ok\n"));
/* clear our mount reference and release device node */
vput(nilfsdev->devvp);
/* free our device info */
cv_destroy(&nilfsdev->sync_cv);
free(nilfsdev, M_NILFSMNT);
}
static int
nilfs_check_mounts(struct nilfs_device *nilfsdev, struct mount *mp,
struct nilfs_args *args)
{
struct nilfs_mount *ump;
uint64_t last_cno;
/* no double-mounting of the same checkpoint */
STAILQ_FOREACH(ump, &nilfsdev->mounts, next_mount) {
if (ump->mount_args.cpno == args->cpno)
return EBUSY;
}
/* allow readonly mounts without questioning here */
if (mp->mnt_flag & MNT_RDONLY)
return 0;
/* readwrite mount you want */
STAILQ_FOREACH(ump, &nilfsdev->mounts, next_mount) {
/* only one RW mount on this device! */
if ((ump->vfs_mountp->mnt_flag & MNT_RDONLY)==0)
return EROFS;
/* RDONLY on last mountpoint is device busy */
last_cno = nilfs_rw64(ump->nilfsdev->super.s_last_cno);
if (ump->mount_args.cpno == last_cno)
return EBUSY;
}
DPRINTF(VOLUMES, ("no previous mounts on this device, mounting device\n"));
/* check if its a block device specified */
if (devvp->v_type != VBLK) {
vrele(devvp);
return ENOTBLK;
}
if (bdevsw_lookup(devvp->v_rdev) == NULL) {
vrele(devvp);
return ENXIO;
}
/*
* If mount by non-root, then verify that user has necessary
* permissions on the device.
*/
accessmode = VREAD;
if ((mp->mnt_flag & MNT_RDONLY) == 0)
accessmode |= VWRITE;
vn_lock(devvp, LK_EXCLUSIVE | LK_RETRY);
error = kauth_authorize_system(l->l_cred, KAUTH_SYSTEM_MOUNT,
KAUTH_REQ_SYSTEM_MOUNT_DEVICE, mp, devvp, KAUTH_ARG(accessmode));
VOP_UNLOCK(devvp);
if (error) {
vrele(devvp);
return error;
}
/*
* Open device read-write; TODO how about upgrading later when needed?
*/
openflags = FREAD | FWRITE;
vn_lock(devvp, LK_EXCLUSIVE | LK_RETRY);
error = VOP_OPEN(devvp, openflags, FSCRED);
VOP_UNLOCK(devvp);
if (error) {
vrele(devvp);
return error;
}
/* register nilfs_device in list */
SLIST_INSERT_HEAD(&nilfs_devices, nilfsdev, next_device);
/* get our device's size */
error = getdisksize(devvp, &psize, &secsize);
if (error) {
/* remove all our information */
nilfs_unmount_device(nilfsdev);
return EINVAL;
}
nilfsdev->devsize = psize * secsize;
/* connect to the head for most recent files XXX really pass mp and args? */
error = nilfs_mount_base(nilfsdev, mp, args);
if (error) {
/* remove all our information */
nilfs_unmount_device(nilfsdev);
return EINVAL;
}
/* needs to be a valid checkpoint */
cp = (struct nilfs_checkpoint *) ((uint8_t *) bp->b_data + off);
if (cp->cp_flags & NILFS_CHECKPOINT_INVALID) {
printf("mount_nilfs: checkpoint marked invalid\n");
brelse(bp, BC_AGE);
return EINVAL;
}
/* is this really the checkpoint we want? */
if (nilfs_rw64(cp->cp_cno) != ump->mount_args.cpno) {
printf("mount_nilfs: checkpoint file corrupt? "
"expected cpno %"PRIu64", found cpno %"PRIu64"\n",
ump->mount_args.cpno, nilfs_rw64(cp->cp_cno));
brelse(bp, BC_AGE);
return EINVAL;
}
/* check if its a snapshot ! */
last_cno = nilfs_rw64(ump->nilfsdev->super.s_last_cno);
if (ump->mount_args.cpno != last_cno) {
/* only allow snapshots if not mounting on the last cp */
if ((cp->cp_flags & NILFS_CHECKPOINT_SNAPSHOT) == 0) {
printf( "mount_nilfs: checkpoint %"PRIu64" is not a "
"snapshot\n", ump->mount_args.cpno);
brelse(bp, BC_AGE);
return EINVAL;
}
}
#define MPFREE(a, lst) \
if ((a)) free((a), lst);
static void
free_nilfs_mountinfo(struct mount *mp)
{
struct nilfs_mount *ump = VFSTONILFS(mp);
if (ump == NULL)
return;
MPFREE(ump, M_NILFSMNT);
}
#undef MPFREE
int
nilfs_mount(struct mount *mp, const char *path,
void *data, size_t *data_len)
{
struct nilfs_args *args = data;
struct nilfs_device *nilfsdev;
struct nilfs_mount *ump;
struct vnode *devvp;
int error;
DPRINTF(VFSCALL, ("nilfs_mount called\n"));
if (args == NULL)
return EINVAL;
if (*data_len < sizeof *args)
return EINVAL;
if (mp->mnt_flag & MNT_GETARGS) {
/* request for the mount arguments */
ump = VFSTONILFS(mp);
if (ump == NULL)
return EINVAL;
*args = ump->mount_args;
*data_len = sizeof *args;
return 0;
}
/* check/translate struct version */
if (args->version != 1) {
printf("mount_nilfs: unrecognized argument structure version\n");
return EINVAL;
}
/* TODO sanity checking other mount arguments */
/* handle request for updating mount parameters */
if (mp->mnt_flag & MNT_UPDATE) {
/* TODO can't update my mountpoint yet */
return EOPNOTSUPP;
}
/* lookup name to get its vnode */
error = namei_simple_user(args->fspec, NSM_FOLLOW_NOEMULROOT, &devvp);
if (error)
return error;
#ifdef DEBUG
if (nilfs_verbose & NILFS_DEBUG_VOLUMES)
vprint("NILFS mount, trying to mount \n", devvp);
#endif
error = nilfs_mount_device(devvp, mp, args, &nilfsdev);
if (error)
return error;
/*
* Create a nilfs_mount on the specified checkpoint. Note that only
* ONE RW mount point can exist and it needs to have the highest
* checkpoint nr. If mounting RW and its not on the last checkpoint we
* need to invalidate all checkpoints that follow!!! This is an
* advanced option.
*/
/* remove our mountpoint and if its the last reference, remove our device */
int
nilfs_unmount(struct mount *mp, int mntflags)
{
struct nilfs_device *nilfsdev;
struct nilfs_mount *ump;
int error, flags;
/*
* Get vnode for the file system type specific file id ino for the fs. Its
* used for reference to files by unique ID and for NFSv3.
* (optional) TODO lookup why some sources state NFSv3
*/
int
nilfs_vget(struct mount *mp, ino_t ino, int lktype,
struct vnode **vpp)
{
DPRINTF(NOTIMPL, ("nilfs_vget called\n"));
return EOPNOTSUPP;
}
/* create new inode; XXX check could be handier */
if ((ino < NILFS_USER_INO) && (ino != NILFS_ROOT_INO)) {
printf("nilfs_get_node: system ino %"PRIu64" not in mount "
"point!\n", ino);
return ENOENT;
}
/* lookup inode in the ifile */
DPRINTF(NODE, ("lookup ino %"PRIu64"\n", ino));
/*
* Create an unique file handle. Its structure is opaque and won't be used by
* other subsystems. It should uniquely identify the file in the filingsystem
* and enough information to know if a file has been removed and/or resources
* have been recycled.
*/
int
nilfs_vptofh(struct vnode *vp, struct fid *fid,
size_t *fh_size)
{
DPRINTF(NOTIMPL, ("nilfs_vptofh called\n"));
return EOPNOTSUPP;
}
/*
* Create a file system snapshot at the specified timestamp.
*/
int
nilfs_snapshot(struct mount *mp, struct vnode *vp,
struct timespec *tm)
{
DPRINTF(NOTIMPL, ("nilfs_snapshot called\n"));
return EOPNOTSUPP;
}