blob: ebfa7e02d7ac4a2d94235b335fef8c101e211a0a [file] [log] [blame]
/* -*- mode: c; c-basic-offset: 8; indent-tabs-mode: nil; -*-
* vim:expandtab:shiftwidth=8:tabstop=8:
*
* Copyright (C) 2001 Cluster File Systems, Inc. <braam@clusterfs.com>
*
* This file is part of InterMezzo, http://www.inter-mezzo.org.
*
* InterMezzo is free software; you can redistribute it and/or
* modify it under the terms of version 2 of the GNU General Public
* License as published by the Free Software Foundation.
*
* InterMezzo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with InterMezzo; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*
* Managing filesets
*
*/
#define __NO_VERSION__
#include <stdarg.h>
#include <asm/bitops.h>
#include <asm/uaccess.h>
#include <asm/system.h>
#include <linux/errno.h>
#include <linux/fs.h>
#include <linux/ext2_fs.h>
#include <linux/slab.h>
#include <linux/vmalloc.h>
#include <linux/sched.h>
#include <linux/stat.h>
#include <linux/string.h>
#include <linux/locks.h>
#include <linux/blkdev.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/intermezzo_fs.h>
#include <linux/intermezzo_psdev.h>
static inline struct presto_file_set *presto_dentry2fset(struct dentry *dentry)
{
if (presto_d2d(dentry) == NULL) {
EXIT;
return NULL;
}
return presto_d2d(dentry)->dd_fset;
}
/* find the fileset dentry for this dentry */
struct presto_file_set *presto_fset(struct dentry *de)
{
struct dentry *fsde;
ENTRY;
if ( !de->d_inode ) {
/* FIXME: is this ok to be NULL? */
CDEBUG(D_INODE,"presto_fset: warning %*s has NULL inode.\n",
de->d_name.len, de->d_name.name);
}
for (fsde = de;; fsde = fsde->d_parent) {
if ( presto_dentry2fset(fsde) ) {
EXIT;
return presto_dentry2fset(fsde);
}
if (fsde->d_parent == fsde)
break;
}
EXIT;
return NULL;
}
int presto_get_lastrecno(char *path, off_t *recno)
{
struct nameidata nd;
struct presto_file_set *fset;
struct dentry *dentry;
int error;
ENTRY;
error = presto_walk(path, &nd);
if (error) {
EXIT;
return error;
}
dentry = nd.dentry;
error = -ENXIO;
if ( !presto_ispresto(dentry->d_inode) ) {
EXIT;
goto kml_out;
}
error = -EINVAL;
if ( ! presto_dentry2fset(dentry)) {
EXIT;
goto kml_out;
}
fset = presto_dentry2fset(dentry);
if (!fset) {
EXIT;
goto kml_out;
}
error = 0;
*recno = fset->fset_kml.fd_recno;
kml_out:
path_release(&nd);
return error;
}
static char * _izo_make_path(char *fsetname, char *name)
{
char *path = NULL;
int len;
len = strlen("/.intermezzo/") + strlen(fsetname)
+ 1 + strlen(name) + 1;
PRESTO_ALLOC(path, len);
if (path == NULL)
return NULL;
sprintf(path, "/.intermezzo/%s/%s", fsetname, name);
return path;
}
char * izo_make_path(struct presto_file_set *fset, char *name)
{
return _izo_make_path(fset->fset_name, name);
}
static struct file *_izo_fset_open(char *fsetname, char *name, int flags, int mode)
{
char *path;
struct file *f;
int error;
ENTRY;
path = _izo_make_path(fsetname, name);
if (path == NULL) {
EXIT;
return ERR_PTR(-ENOMEM);
}
CDEBUG(D_INODE, "opening file %s\n", path);
f = filp_open(path, flags, mode);
error = PTR_ERR(f);
if (IS_ERR(f)) {
CDEBUG(D_INODE, "Error %d\n", error);
}
PRESTO_FREE(path, strlen(path)+1);
EXIT;
return f;
}
struct file *izo_fset_open(struct presto_file_set *fset, char *name, int flags, int mode)
{
return _izo_fset_open(fset->fset_name, name, flags, mode);
}
/*
* note: this routine "pins" a dentry for a fileset root
*/
int presto_set_fsetroot(struct dentry *ioctl_dentry, char *fsetname,
unsigned int flags)
{
struct presto_file_set *fset = NULL;
struct presto_cache *cache;
int error;
struct file *fset_root;
struct dentry *dentry;
ENTRY;
fset_root = _izo_fset_open(fsetname, "ROOT", O_RDONLY, 000);
if (IS_ERR(fset_root)) {
CERROR("Can't open %s/ROOT\n", fsetname);
EXIT;
error = PTR_ERR(fset_root);
goto out;
}
dentry = dget(fset_root->f_dentry);
filp_close(fset_root, NULL);
dentry->d_inode->i_op = ioctl_dentry->d_inode->i_op;
dentry->d_inode->i_fop = ioctl_dentry->d_inode->i_fop;
dentry->d_op = ioctl_dentry->d_op;
fset = presto_dentry2fset(dentry);
if (fset && (fset->fset_dentry == dentry) ) {
CERROR("Fsetroot already set (inode %ld)\n",
dentry->d_inode->i_ino);
/* XXX: ignore because clear_fsetroot is broken */
#if 0
dput(dentry);
EXIT;
error = -EEXIST;
goto out;
#endif
}
cache = presto_get_cache(dentry->d_inode);
if (!cache) {
CERROR("No cache found for inode %ld\n",
dentry->d_inode->i_ino);
EXIT;
error = -ENODEV;
goto out_free;
}
PRESTO_ALLOC(fset, sizeof(*fset));
if ( !fset ) {
CERROR("No memory allocating fset for %s\n", fsetname);
EXIT;
error = -ENOMEM;
goto out_free;
}
CDEBUG(D_INODE, "fset at %p\n", fset);
CDEBUG(D_INODE, "InterMezzo: fsetroot: inode %ld, fileset name %s\n",
dentry->d_inode->i_ino, fsetname);
fset->fset_mnt = mntget(current->fs->pwdmnt);
fset->fset_cache = cache;
fset->fset_dentry = dentry;
fset->fset_name = strdup(fsetname);
fset->fset_chunkbits = CHUNK_BITS;
fset->fset_flags = flags;
fset->fset_file_maxio = FSET_DEFAULT_MAX_FILEIO;
fset->fset_permit_lock = SPIN_LOCK_UNLOCKED;
PRESTO_ALLOC(fset->fset_reint_buf, 64 * 1024);
if (fset->fset_reint_buf == NULL) {
EXIT;
error = -ENOMEM;
goto out_free;
}
init_waitqueue_head(&fset->fset_permit_queue);
if (presto_d2d(dentry) == NULL) {
dentry->d_fsdata = izo_alloc_ddata();
}
if (presto_d2d(dentry) == NULL) {
CERROR("InterMezzo: %s: no memory\n", __FUNCTION__);
EXIT;
error = -ENOMEM;
goto out_free;
}
presto_d2d(dentry)->dd_fset = fset;
list_add(&fset->fset_list, &cache->cache_fset_list);
error = izo_init_kml_file(fset, &fset->fset_kml);
if ( error ) {
EXIT;
CDEBUG(D_JOURNAL, "Error init_kml %d\n", error);
goto out_list_del;
}
error = izo_init_lml_file(fset, &fset->fset_lml);
if ( error ) {
int rc;
EXIT;
rc = izo_log_close(&fset->fset_kml);
CDEBUG(D_JOURNAL, "Error init_lml %d, cleanup %d\n", error, rc);
goto out_list_del;
}
/* init_last_rcvd_file could trigger a presto_file_write(), which
* requires that the lml structure be initialized. -phil */
error = izo_init_last_rcvd_file(fset, &fset->fset_rcvd);
if ( error ) {
int rc;
EXIT;
rc = izo_log_close(&fset->fset_kml);
rc = izo_log_close(&fset->fset_lml);
CDEBUG(D_JOURNAL, "Error init_lastrcvd %d, cleanup %d\n", error, rc);
goto out_list_del;
}
CDEBUG(D_PIOCTL, "-------> fset at %p, dentry at %p, mtpt %p,"
"fset %s, cache %p, presto_d2d(dentry)->dd_fset %p\n",
fset, dentry, fset->fset_dentry, fset->fset_name, cache,
presto_d2d(dentry)->dd_fset);
EXIT;
return 0;
out_list_del:
list_del(&fset->fset_list);
presto_d2d(dentry)->dd_fset = NULL;
out_free:
if (fset) {
mntput(fset->fset_mnt);
if (fset->fset_reint_buf != NULL)
PRESTO_FREE(fset->fset_reint_buf, 64 * 1024);
PRESTO_FREE(fset, sizeof(*fset));
}
dput(dentry);
out:
return error;
}
static int izo_cleanup_fset(struct presto_file_set *fset)
{
int error;
struct presto_cache *cache;
ENTRY;
CERROR("Cleaning up fset %s\n", fset->fset_name);
error = izo_log_close(&fset->fset_kml);
if (error)
CERROR("InterMezzo: Closing kml for fset %s: %d\n",
fset->fset_name, error);
error = izo_log_close(&fset->fset_lml);
if (error)
CERROR("InterMezzo: Closing lml for fset %s: %d\n",
fset->fset_name, error);
error = izo_log_close(&fset->fset_rcvd);
if (error)
CERROR("InterMezzo: Closing last_rcvd for fset %s: %d\n",
fset->fset_name, error);
cache = fset->fset_cache;
list_del(&fset->fset_list);
presto_d2d(fset->fset_dentry)->dd_fset = NULL;
dput(fset->fset_dentry);
mntput(fset->fset_mnt);
izo_rep_cache_clean(fset);
PRESTO_FREE(fset->fset_name, strlen(fset->fset_name) + 1);
PRESTO_FREE(fset->fset_reint_buf, 64 * 1024);
PRESTO_FREE(fset, sizeof(*fset));
EXIT;
return error;
}
int izo_clear_fsetroot(struct dentry *dentry)
{
struct presto_file_set *fset;
ENTRY;
fset = presto_dentry2fset(dentry);
if (!fset) {
EXIT;
return -EINVAL;
}
izo_cleanup_fset(fset);
EXIT;
return 0;
}
int izo_clear_all_fsetroots(struct presto_cache *cache)
{
struct presto_file_set *fset;
struct list_head *tmp,*tmpnext;
int error;
error = 0;
tmp = &cache->cache_fset_list;
tmpnext = tmp->next;
while ( tmpnext != &cache->cache_fset_list) {
tmp = tmpnext;
tmpnext = tmp->next;
fset = list_entry(tmp, struct presto_file_set, fset_list);
error = izo_cleanup_fset(fset);
if (error)
break;
}
return error;
}
static struct vfsmount *izo_alloc_vfsmnt(void)
{
struct vfsmount *mnt;
PRESTO_ALLOC(mnt, sizeof(*mnt));
if (mnt) {
memset(mnt, 0, sizeof(struct vfsmount));
atomic_set(&mnt->mnt_count,1);
INIT_LIST_HEAD(&mnt->mnt_hash);
INIT_LIST_HEAD(&mnt->mnt_child);
INIT_LIST_HEAD(&mnt->mnt_mounts);
INIT_LIST_HEAD(&mnt->mnt_list);
}
return mnt;
}
static void izo_setup_ctxt(struct dentry *root, struct vfsmount *mnt,
struct run_ctxt *save)
{
struct run_ctxt new;
mnt->mnt_root = root;
mnt->mnt_sb = root->d_inode->i_sb;
unlock_super(mnt->mnt_sb);
new.rootmnt = mnt;
new.root = root;
new.pwdmnt = mnt;
new.pwd = root;
new.fsuid = 0;
new.fsgid = 0;
new.fs = get_fs();
/* XXX where can we get the groups from? */
new.ngroups = 0;
push_ctxt(save, &new);
}
static void izo_cleanup_ctxt(struct vfsmount *mnt, struct run_ctxt *save)
{
lock_super(mnt->mnt_sb);
pop_ctxt(save);
}
static int izo_simple_mkdir(struct dentry *dir, char *name, int mode)
{
struct dentry *dchild;
int err;
ENTRY;
dchild = lookup_one_len(name, dir, strlen(name));
if (IS_ERR(dchild)) {
EXIT;
return PTR_ERR(dchild);
}
if (dchild->d_inode) {
dput(dchild);
EXIT;
return -EEXIST;
}
err = vfs_mkdir(dir->d_inode, dchild, mode);
dput(dchild);
EXIT;
return err;
}
static int izo_simple_symlink(struct dentry *dir, char *name, char *tgt)
{
struct dentry *dchild;
int err;
ENTRY;
dchild = lookup_one_len(name, dir, strlen(name));
if (IS_ERR(dchild)) {
EXIT;
return PTR_ERR(dchild);
}
if (dchild->d_inode) {
dput(dchild);
EXIT;
return -EEXIST;
}
err = vfs_symlink(dir->d_inode, dchild, tgt);
dput(dchild);
EXIT;
return err;
}
/*
* run set_fsetroot in chroot environment
*/
int presto_set_fsetroot_from_ioc(struct dentry *root, char *fsetname,
unsigned int flags)
{
int rc;
struct presto_cache *cache;
struct vfsmount *mnt;
struct run_ctxt save;
if (root != root->d_inode->i_sb->s_root) {
CERROR ("IOC_SET_FSET must be called on mount point\n");
return -ENODEV;
}
cache = presto_get_cache(root->d_inode);
mnt = cache->cache_vfsmount;
if (!mnt) {
EXIT;
return -ENOMEM;
}
izo_setup_ctxt(root, mnt, &save);
rc = presto_set_fsetroot(root, fsetname, flags);
izo_cleanup_ctxt(mnt, &save);
return rc;
}
/* XXX: this function should detect if fsetname is already in use for
the cache under root
*/
int izo_prepare_fileset(struct dentry *root, char *fsetname)
{
int err;
struct dentry *dotizo = NULL, *fsetdir = NULL, *dotiopen = NULL;
struct presto_cache *cache;
struct vfsmount *mnt;
struct run_ctxt save;
cache = presto_get_cache(root->d_inode);
mnt = cache->cache_vfsmount = izo_alloc_vfsmnt();
if (!mnt) {
EXIT;
return -ENOMEM;
}
if (!fsetname)
fsetname = "rootfset";
izo_setup_ctxt(root, mnt, &save);
err = izo_simple_mkdir(root, ".intermezzo", 0755);
CDEBUG(D_CACHE, "mkdir on .intermezzo err %d\n", err);
err = izo_simple_mkdir(root, "..iopen..", 0755);
CDEBUG(D_CACHE, "mkdir on ..iopen.. err %d\n", err);
dotiopen = lookup_one_len("..iopen..", root, strlen("..iopen.."));
if (IS_ERR(dotiopen)) {
EXIT;
goto out;
}
dotiopen->d_inode->i_op = &presto_dir_iops;
dput(dotiopen);
dotizo = lookup_one_len(".intermezzo", root, strlen(".intermezzo"));
if (IS_ERR(dotizo)) {
EXIT;
goto out;
}
err = izo_simple_mkdir(dotizo, fsetname, 0755);
CDEBUG(D_CACHE, "mkdir err %d\n", err);
/* XXX find the dentry of the root of the fileset (root for now) */
fsetdir = lookup_one_len(fsetname, dotizo, strlen(fsetname));
if (IS_ERR(fsetdir)) {
EXIT;
goto out;
}
err = izo_simple_symlink(fsetdir, "ROOT", "../..");
/* XXX read flags from flags file */
err = presto_set_fsetroot(root, fsetname, 0);
CDEBUG(D_CACHE, "set_fsetroot err %d\n", err);
out:
if (dotizo && !IS_ERR(dotizo))
dput(dotizo);
if (fsetdir && !IS_ERR(fsetdir))
dput(fsetdir);
izo_cleanup_ctxt(mnt, &save);
return err;
}
int izo_set_fileid(struct file *dir, struct izo_ioctl_data *data)
{
int rc = 0;
struct presto_cache *cache;
struct vfsmount *mnt;
struct run_ctxt save;
struct nameidata nd;
struct dentry *dentry;
struct presto_dentry_data *dd;
struct dentry *root;
char *buf = NULL;
ENTRY;
root = dir->f_dentry;
/* actually, needs to be called on ROOT of fset, not mount point
if (root != root->d_inode->i_sb->s_root) {
CERROR ("IOC_SET_FSET must be called on mount point\n");
return -ENODEV;
}
*/
cache = presto_get_cache(root->d_inode);
mnt = cache->cache_vfsmount;
if (!mnt) {
EXIT;
return -ENOMEM;
}
izo_setup_ctxt(root, mnt, &save);
PRESTO_ALLOC(buf, data->ioc_plen1);
if (!buf) {
rc = -ENOMEM;
EXIT;
goto out;
}
if (copy_from_user(buf, data->ioc_pbuf1, data->ioc_plen1)) {
rc = -EFAULT;
EXIT;
goto out;
}
rc = presto_walk(buf, &nd);
if (rc) {
CERROR("Unable to open: %s\n", buf);
EXIT;
goto out;
}
dentry = nd.dentry;
if (!dentry) {
CERROR("no dentry!\n");
rc = -EINVAL;
EXIT;
goto out_close;
}
dd = presto_d2d(dentry);
if (!dd) {
CERROR("no dentry_data!\n");
rc = -EINVAL;
EXIT;
goto out_close;
}
CDEBUG(D_FILE,"de:%p dd:%p\n", dentry, dd);
if (dd->remote_ino != 0) {
CERROR("remote_ino already set? %Lx:%Lx\n", dd->remote_ino,
dd->remote_generation);
rc = 0;
EXIT;
goto out_close;
}
CDEBUG(D_FILE,"setting %p %p, %s to %Lx:%Lx\n", dentry, dd,
buf, data->ioc_ino,
data->ioc_generation);
dd->remote_ino = data->ioc_ino;
dd->remote_generation = data->ioc_generation;
EXIT;
out_close:
path_release(&nd);
out:
if (buf)
PRESTO_FREE(buf, data->ioc_plen1);
izo_cleanup_ctxt(mnt, &save);
return rc;
}