blob: b22c4b9fb014f9582db0381e6b629031c4e7da78 [file] [log] [blame]
/* -*- mode: c; c-basic-offset: 8; indent-tabs-mode: nil; -*-
* vim:expandtab:shiftwidth=8:tabstop=8:
*
* Copyright (C) 2000 Stelias Computing, Inc.
* Copyright (C) 2000 Red Hat, Inc.
* Copyright (C) 2000 Tacitus Systems
* Copyright (C) 2000 Peter J. Braam
*
* 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.
*/
#include <stdarg.h>
#include <asm/bitops.h>
#include <asm/uaccess.h>
#include <asm/system.h>
#include <linux/smp_lock.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>
#define __NO_VERSION__
#include <linux/module.h>
#include <linux/intermezzo_fs.h>
#include <linux/intermezzo_psdev.h>
static inline void presto_relock_sem(struct inode *dir)
{
/* the lock from sys_mkdir / lookup_create */
down(&dir->i_sem);
/* the rest is done by the do_{create,mkdir, ...} */
}
static inline void presto_relock_other(struct inode *dir)
{
/* vfs_mkdir locks */
down(&dir->i_zombie);
lock_kernel();
}
static inline void presto_fulllock(struct inode *dir)
{
/* the lock from sys_mkdir / lookup_create */
down(&dir->i_sem);
/* vfs_mkdir locks */
down(&dir->i_zombie);
lock_kernel();
}
static inline void presto_unlock(struct inode *dir)
{
/* vfs_mkdir locks */
unlock_kernel();
up(&dir->i_zombie);
/* the lock from sys_mkdir / lookup_create */
up(&dir->i_sem);
}
/*
* these are initialized in super.c
*/
extern int presto_permission(struct inode *inode, int mask);
static int izo_authorized_uid = 0;
int izo_dentry_is_ilookup(struct dentry *dentry, ino_t *id,
unsigned int *generation)
{
char tmpname[64];
char *next;
ENTRY;
/* prefix is 7 characters: '...ino:' */
if ( dentry->d_name.len < 7 || dentry->d_name.len > 64 ||
memcmp(dentry->d_name.name, PRESTO_ILOOKUP_MAGIC, 7) != 0 ) {
EXIT;
return 0;
}
memcpy(tmpname, dentry->d_name.name + 7, dentry->d_name.len - 7);
*(tmpname + dentry->d_name.len - 7) = '\0';
/* name is of the form ...ino:<inode number>:<generation> */
*id = simple_strtoul(tmpname, &next, 16);
if ( *next == PRESTO_ILOOKUP_SEP ) {
*generation = simple_strtoul(next + 1, 0, 16);
CDEBUG(D_INODE, "ino string: %s, Id = %lx (%lu), "
"generation %x (%d)\n",
tmpname, *id, *id, *generation, *generation);
EXIT;
return 1;
} else {
EXIT;
return 0;
}
}
struct dentry *presto_tmpfs_ilookup(struct inode *dir,
struct dentry *dentry,
ino_t ino,
unsigned int generation)
{
return dentry;
}
inline int presto_can_ilookup(void)
{
return (current->euid == izo_authorized_uid ||
capable(CAP_DAC_READ_SEARCH));
}
struct dentry *presto_iget_ilookup(struct inode *dir,
struct dentry *dentry,
ino_t ino,
unsigned int generation)
{
struct inode *inode;
int error;
ENTRY;
if ( !presto_can_ilookup() ) {
CERROR("ilookup denied: euid %u, authorized_uid %u\n",
current->euid, izo_authorized_uid);
return ERR_PTR(-EPERM);
}
error = -ENOENT;
inode = iget(dir->i_sb, ino);
if (!inode) {
CERROR("fatal: NULL inode ino %lu\n", ino);
goto cleanup_iput;
}
if (is_bad_inode(inode) || inode->i_nlink == 0) {
CERROR("fatal: bad inode ino %lu, links %d\n", ino, inode->i_nlink);
goto cleanup_iput;
}
if (inode->i_generation != generation) {
CERROR("fatal: bad generation %u (want %u)\n",
inode->i_generation, generation);
goto cleanup_iput;
}
d_instantiate(dentry, inode);
dentry->d_flags |= DCACHE_NFSD_DISCONNECTED; /* NFS hack */
EXIT;
return NULL;
cleanup_iput:
if (inode)
iput(inode);
return ERR_PTR(error);
}
struct dentry *presto_add_ilookup_dentry(struct dentry *parent,
struct dentry *real)
{
struct inode *inode = real->d_inode;
struct dentry *de;
char buf[32];
char *ptr = buf;
struct dentry *inodir;
struct presto_dentry_data *dd;
inodir = lookup_one_len("..iopen..", parent, strlen("..iopen.."));
if (!inodir || IS_ERR(inodir) || !inodir->d_inode ) {
CERROR("%s: bad ..iopen.. lookup\n", __FUNCTION__);
return NULL;
}
inodir->d_inode->i_op = &presto_dir_iops;
snprintf(ptr, 32, "...ino:%lx:%x", inode->i_ino, inode->i_generation);
de = lookup_one_len(ptr, inodir, strlen(ptr));
if (!de || IS_ERR(de)) {
CERROR("%s: bad ...ino lookup %ld\n",
__FUNCTION__, PTR_ERR(de));
dput(inodir);
return NULL;
}
dd = presto_d2d(real);
if (!dd)
BUG();
/* already exists */
if (de->d_inode)
BUG();
#if 0
if (de->d_inode != inode ) {
CERROR("XX de->d_inode %ld, inode %ld\n",
de->d_inode->i_ino, inode->i_ino);
BUG();
}
if (dd->dd_inodentry) {
CERROR("inodentry exists %ld \n", inode->i_ino);
BUG();
}
dput(inodir);
return de;
}
#endif
if (presto_d2d(de))
BUG();
atomic_inc(&inode->i_count);
de->d_op = &presto_dentry_ops;
d_add(de, inode);
if (!de->d_op)
CERROR("DD: no ops dentry %p, dd %p\n", de, dd);
dd->dd_inodentry = de;
dd->dd_count++;
de->d_fsdata = dd;
dput(inodir);
return de;
}
struct dentry *presto_lookup(struct inode * dir, struct dentry *dentry)
{
int rc = 0;
struct dentry *de;
struct presto_cache *cache;
int minor;
ino_t ino;
unsigned int generation;
struct inode_operations *iops;
int is_ilookup = 0;
ENTRY;
cache = presto_get_cache(dir);
if (cache == NULL) {
CERROR("InterMezzo BUG: no cache in presto_lookup "
"(dir ino: %ld)!\n", dir->i_ino);
EXIT;
return NULL;
}
minor = presto_c2m(cache);
iops = filter_c2cdiops(cache->cache_filter);
if (!iops || !iops->lookup) {
CERROR("InterMezzo BUG: filesystem has no lookup\n");
EXIT;
return NULL;
}
CDEBUG(D_CACHE, "dentry %p, dir ino: %ld, name: %*s, islento: %d\n",
dentry, dir->i_ino, dentry->d_name.len, dentry->d_name.name,
ISLENTO(minor));
if (dentry->d_fsdata)
CERROR("DD -- BAD dentry %p has data\n", dentry);
dentry->d_fsdata = NULL;
#if 0
if (ext2_check_for_iopen(dir, dentry))
de = NULL;
else {
#endif
if ( izo_dentry_is_ilookup(dentry, &ino, &generation) ) {
de = cache->cache_filter->o_trops->tr_ilookup
(dir, dentry, ino, generation);
is_ilookup = 1;
} else
de = iops->lookup(dir, dentry);
#if 0
}
#endif
if ( IS_ERR(de) ) {
CERROR("dentry lookup error %ld\n", PTR_ERR(de));
return de;
}
/* some file systems have no read_inode: set methods here */
if (dentry->d_inode)
presto_set_ops(dentry->d_inode, cache->cache_filter);
/* dentry->d_op is now hooked in dcache.c:presto_set_dd */
/* In lookup we will tolerate EROFS return codes from presto_set_dd
* to placate NFS. EROFS indicates that a fileset was not found but
* we should still be able to continue through a lookup.
* Anything else is a hard error and must be returned to VFS. */
if (!is_ilookup)
rc = presto_set_dd(dentry);
if (rc && rc != -EROFS) {
CERROR("presto_set_dd failed (dir %ld, name %*s): %d\n",
dir->i_ino, dentry->d_name.len, dentry->d_name.name, rc);
return ERR_PTR(rc);
}
EXIT;
return NULL;
}
static inline int presto_check_set_fsdata (struct dentry *de)
{
if (presto_d2d(de) == NULL) {
#ifdef PRESTO_NO_NFS
CERROR("dentry without fsdata: %p: %*s\n", de,
de->d_name.len, de->d_name.name);
BUG();
#endif
return presto_set_dd (de);
}
return 0;
}
int presto_setattr(struct dentry *de, struct iattr *iattr)
{
int error;
struct presto_cache *cache;
struct presto_file_set *fset;
struct lento_vfs_context info = { 0, 0, 0 };
ENTRY;
error = presto_prep(de, &cache, &fset);
if ( error ) {
EXIT;
return error;
}
if (!iattr->ia_valid)
CDEBUG(D_INODE, "presto_setattr: iattr is not valid\n");
CDEBUG(D_INODE, "valid %#x, mode %#o, uid %u, gid %u, size %Lu, "
"atime %lu mtime %lu ctime %lu flags %d\n",
iattr->ia_valid, iattr->ia_mode, iattr->ia_uid, iattr->ia_gid,
iattr->ia_size, iattr->ia_atime, iattr->ia_mtime,
iattr->ia_ctime, iattr->ia_attr_flags);
if ( presto_get_permit(de->d_inode) < 0 ) {
EXIT;
return -EROFS;
}
if (!ISLENTO(presto_c2m(cache)))
info.flags = LENTO_FL_KML;
info.flags |= LENTO_FL_IGNORE_TIME;
error = presto_do_setattr(fset, de, iattr, &info);
presto_put_permit(de->d_inode);
return error;
}
/*
* Now the meat: the fs operations that require journaling
*
*
* XXX: some of these need modifications for hierarchical filesets
*/
int presto_prep(struct dentry *dentry, struct presto_cache **cache,
struct presto_file_set **fset)
{
int rc;
/* NFS might pass us dentries which have not gone through lookup.
* Test and set d_fsdata for such dentries
*/
rc = presto_check_set_fsdata (dentry);
if (rc) return rc;
*fset = presto_fset(dentry);
if ( *fset == NULL ) {
CERROR("No file set for dentry at %p: %*s\n", dentry,
dentry->d_name.len, dentry->d_name.name);
return -EROFS;
}
*cache = (*fset)->fset_cache;
if ( *cache == NULL ) {
CERROR("PRESTO: BAD, BAD: cannot find cache\n");
return -EBADF;
}
CDEBUG(D_PIOCTL, "---> cache flags %x, fset flags %x\n",
(*cache)->cache_flags, (*fset)->fset_flags);
if( presto_is_read_only(*fset) ) {
CERROR("PRESTO: cannot modify read-only fileset, minor %d.\n",
presto_c2m(*cache));
return -EROFS;
}
return 0;
}
static int presto_create(struct inode * dir, struct dentry * dentry, int mode)
{
int error;
struct presto_cache *cache;
struct dentry *parent = dentry->d_parent;
struct lento_vfs_context info;
struct presto_file_set *fset;
ENTRY;
error = presto_check_set_fsdata(dentry);
if ( error ) {
EXIT;
return error;
}
error = presto_prep(dentry->d_parent, &cache, &fset);
if ( error ) {
EXIT;
return error;
}
presto_unlock(dir);
/* Does blocking and non-blocking behavious need to be
checked for. Without blocking (return 1), the permit
was acquired without reintegration
*/
if ( presto_get_permit(dir) < 0 ) {
EXIT;
presto_fulllock(dir);
return -EROFS;
}
presto_relock_sem(dir);
parent = dentry->d_parent;
memset(&info, 0, sizeof(info));
if (!ISLENTO(presto_c2m(cache)))
info.flags = LENTO_FL_KML;
info.flags |= LENTO_FL_IGNORE_TIME;
error = presto_do_create(fset, parent, dentry, mode, &info);
presto_relock_other(dir);
presto_put_permit(dir);
EXIT;
return error;
}
static int presto_link(struct dentry *old_dentry, struct inode *dir,
struct dentry *new_dentry)
{
int error;
struct presto_cache *cache, *new_cache;
struct presto_file_set *fset, *new_fset;
struct dentry *parent = new_dentry->d_parent;
struct lento_vfs_context info;
ENTRY;
error = presto_prep(old_dentry, &cache, &fset);
if ( error ) {
EXIT;
return error;
}
error = presto_check_set_fsdata(new_dentry);
if ( error ) {
EXIT;
return error;
}
error = presto_prep(new_dentry->d_parent, &new_cache, &new_fset);
if ( error ) {
EXIT;
return error;
}
if (fset != new_fset) {
EXIT;
return -EXDEV;
}
presto_unlock(dir);
if ( presto_get_permit(old_dentry->d_inode) < 0 ) {
EXIT;
presto_fulllock(dir);
return -EROFS;
}
if ( presto_get_permit(dir) < 0 ) {
EXIT;
presto_fulllock(dir);
return -EROFS;
}
presto_relock_sem(dir);
parent = new_dentry->d_parent;
memset(&info, 0, sizeof(info));
if (!ISLENTO(presto_c2m(cache)))
info.flags = LENTO_FL_KML;
info.flags |= LENTO_FL_IGNORE_TIME;
error = presto_do_link(fset, old_dentry, parent,
new_dentry, &info);
#if 0
/* XXX for links this is not right */
if (cache->cache_filter->o_trops->tr_add_ilookup ) {
struct dentry *d;
d = cache->cache_filter->o_trops->tr_add_ilookup
(dir->i_sb->s_root, new_dentry, 1);
}
#endif
presto_relock_other(dir);
presto_put_permit(dir);
presto_put_permit(old_dentry->d_inode);
return error;
}
static int presto_mkdir(struct inode * dir, struct dentry * dentry, int mode)
{
int error;
struct presto_file_set *fset;
struct presto_cache *cache;
struct dentry *parent = dentry->d_parent;
struct lento_vfs_context info;
ENTRY;
error = presto_check_set_fsdata(dentry);
if ( error ) {
EXIT;
return error;
}
error = presto_prep(dentry->d_parent, &cache, &fset);
if ( error ) {
EXIT;
return error;
}
presto_unlock(dir);
if ( presto_get_permit(dir) < 0 ) {
EXIT;
presto_fulllock(dir);
return -EROFS;
}
memset(&info, 0, sizeof(info));
if (!ISLENTO(presto_c2m(cache)))
info.flags = LENTO_FL_KML;
info.flags |= LENTO_FL_IGNORE_TIME;
presto_relock_sem(dir);
parent = dentry->d_parent;
error = presto_do_mkdir(fset, parent, dentry, mode, &info);
presto_relock_other(dir);
presto_put_permit(dir);
return error;
}
static int presto_symlink(struct inode *dir, struct dentry *dentry,
const char *name)
{
int error;
struct presto_cache *cache;
struct presto_file_set *fset;
struct dentry *parent = dentry->d_parent;
struct lento_vfs_context info;
ENTRY;
error = presto_check_set_fsdata(dentry);
if ( error ) {
EXIT;
return error;
}
error = presto_prep(dentry->d_parent, &cache, &fset);
if ( error ) {
EXIT;
return error;
}
presto_unlock(dir);
if ( presto_get_permit(dir) < 0 ) {
EXIT;
presto_fulllock(dir);
return -EROFS;
}
presto_relock_sem(dir);
parent = dentry->d_parent;
memset(&info, 0, sizeof(info));
if (!ISLENTO(presto_c2m(cache)))
info.flags = LENTO_FL_KML;
info.flags |= LENTO_FL_IGNORE_TIME;
error = presto_do_symlink(fset, parent, dentry, name, &info);
presto_relock_other(dir);
presto_put_permit(dir);
return error;
}
int presto_unlink(struct inode *dir, struct dentry *dentry)
{
int error;
struct presto_cache *cache;
struct presto_file_set *fset;
struct dentry *parent = dentry->d_parent;
struct lento_vfs_context info;
ENTRY;
error = presto_check_set_fsdata(dentry);
if ( error ) {
EXIT;
return error;
}
error = presto_prep(dentry->d_parent, &cache, &fset);
if ( error ) {
EXIT;
return error;
}
presto_unlock(dir);
if ( presto_get_permit(dir) < 0 ) {
EXIT;
presto_fulllock(dir);
return -EROFS;
}
presto_relock_sem(dir);
parent = dentry->d_parent;
memset(&info, 0, sizeof(info));
if (!ISLENTO(presto_c2m(cache)))
info.flags = LENTO_FL_KML;
info.flags |= LENTO_FL_IGNORE_TIME;
error = presto_do_unlink(fset, parent, dentry, &info);
presto_relock_other(dir);
presto_put_permit(dir);
return error;
}
static int presto_rmdir(struct inode *dir, struct dentry *dentry)
{
int error;
struct presto_cache *cache;
struct presto_file_set *fset;
struct dentry *parent = dentry->d_parent;
struct lento_vfs_context info;
ENTRY;
CDEBUG(D_FILE, "prepping presto\n");
error = presto_check_set_fsdata(dentry);
if ( error ) {
EXIT;
return error;
}
error = presto_prep(dentry->d_parent, &cache, &fset);
if ( error ) {
EXIT;
return error;
}
CDEBUG(D_FILE, "unlocking\n");
/* We need to dget() before the dput in double_unlock, to ensure we
* still have dentry references. double_lock doesn't do dget for us.
*/
unlock_kernel();
if (d_unhashed(dentry))
d_rehash(dentry);
double_up(&dir->i_zombie, &dentry->d_inode->i_zombie);
double_up(&dir->i_sem, &dentry->d_inode->i_sem);
CDEBUG(D_FILE, "getting permit\n");
if ( presto_get_permit(parent->d_inode) < 0 ) {
EXIT;
double_down(&dir->i_sem, &dentry->d_inode->i_sem);
double_down(&dir->i_zombie, &dentry->d_inode->i_zombie);
lock_kernel();
return -EROFS;
}
CDEBUG(D_FILE, "locking\n");
double_down(&dir->i_sem, &dentry->d_inode->i_sem);
parent = dentry->d_parent;
memset(&info, 0, sizeof(info));
if (!ISLENTO(presto_c2m(cache)))
info.flags = LENTO_FL_KML;
info.flags |= LENTO_FL_IGNORE_TIME;
error = presto_do_rmdir(fset, parent, dentry, &info);
presto_put_permit(parent->d_inode);
lock_kernel();
EXIT;
return error;
}
static int presto_mknod(struct inode * dir, struct dentry * dentry, int mode, int rdev)
{
int error;
struct presto_cache *cache;
struct presto_file_set *fset;
struct dentry *parent = dentry->d_parent;
struct lento_vfs_context info;
ENTRY;
error = presto_check_set_fsdata(dentry);
if ( error ) {
EXIT;
return error;
}
error = presto_prep(dentry->d_parent, &cache, &fset);
if ( error ) {
EXIT;
return error;
}
presto_unlock(dir);
if ( presto_get_permit(dir) < 0 ) {
EXIT;
presto_fulllock(dir);
return -EROFS;
}
presto_relock_sem(dir);
parent = dentry->d_parent;
memset(&info, 0, sizeof(info));
if (!ISLENTO(presto_c2m(cache)))
info.flags = LENTO_FL_KML;
info.flags |= LENTO_FL_IGNORE_TIME;
error = presto_do_mknod(fset, parent, dentry, mode, rdev, &info);
presto_relock_other(dir);
presto_put_permit(dir);
EXIT;
return error;
}
inline void presto_triple_unlock(struct inode *old_dir, struct inode *new_dir,
struct dentry *old_dentry,
struct dentry *new_dentry, int triple)
{
/* rename_dir case */
if (S_ISDIR(old_dentry->d_inode->i_mode)) {
if (triple) {
triple_up(&old_dir->i_zombie,
&new_dir->i_zombie,
&new_dentry->d_inode->i_zombie);
} else {
double_up(&old_dir->i_zombie,
&new_dir->i_zombie);
}
up(&old_dir->i_sb->s_vfs_rename_sem);
} else /* this case is rename_other */
double_up(&old_dir->i_zombie, &new_dir->i_zombie);
/* done by do_rename */
unlock_kernel();
double_up(&old_dir->i_sem, &new_dir->i_sem);
}
inline void presto_triple_fulllock(struct inode *old_dir,
struct inode *new_dir,
struct dentry *old_dentry,
struct dentry *new_dentry, int triple)
{
/* done by do_rename */
double_down(&old_dir->i_sem, &new_dir->i_sem);
lock_kernel();
/* rename_dir case */
if (S_ISDIR(old_dentry->d_inode->i_mode)) {
down(&old_dir->i_sb->s_vfs_rename_sem);
if (triple) {
triple_down(&old_dir->i_zombie,
&new_dir->i_zombie,
&new_dentry->d_inode->i_zombie);
} else {
double_down(&old_dir->i_zombie,
&new_dir->i_zombie);
}
} else /* this case is rename_other */
double_down(&old_dir->i_zombie, &new_dir->i_zombie);
}
inline void presto_triple_relock_sem(struct inode *old_dir,
struct inode *new_dir,
struct dentry *old_dentry,
struct dentry *new_dentry, int triple)
{
/* done by do_rename */
double_down(&old_dir->i_sem, &new_dir->i_sem);
lock_kernel();
}
inline void presto_triple_relock_other(struct inode *old_dir,
struct inode *new_dir,
struct dentry *old_dentry,
struct dentry *new_dentry, int triple)
{
/* rename_dir case */
if (S_ISDIR(old_dentry->d_inode->i_mode)) {
down(&old_dir->i_sb->s_vfs_rename_sem);
if (triple) {
triple_down(&old_dir->i_zombie,
&new_dir->i_zombie,
&new_dentry->d_inode->i_zombie);
} else {
double_down(&old_dir->i_zombie,
&new_dir->i_zombie);
}
} else /* this case is rename_other */
double_down(&old_dir->i_zombie, &new_dir->i_zombie);
}
// XXX this can be optimized: renamtes across filesets only require
// multiple KML records, but can locally be executed normally.
int presto_rename(struct inode *old_dir, struct dentry *old_dentry,
struct inode *new_dir, struct dentry *new_dentry)
{
int error;
struct presto_cache *cache, *new_cache;
struct presto_file_set *fset, *new_fset;
struct lento_vfs_context info;
struct dentry *old_parent = old_dentry->d_parent;
struct dentry *new_parent = new_dentry->d_parent;
int triple;
ENTRY;
error = presto_prep(old_dentry, &cache, &fset);
if ( error ) {
EXIT;
return error;
}
error = presto_prep(new_parent, &new_cache, &new_fset);
if ( error ) {
EXIT;
return error;
}
if ( fset != new_fset ) {
EXIT;
return -EXDEV;
}
/* We need to do dget before the dput in double_unlock, to ensure we
* still have dentry references. double_lock doesn't do dget for us.
*/
triple = (S_ISDIR(old_dentry->d_inode->i_mode) && new_dentry->d_inode)?
1:0;
presto_triple_unlock(old_dir, new_dir, old_dentry, new_dentry, triple);
if ( presto_get_permit(old_dir) < 0 ) {
EXIT;
presto_triple_fulllock(old_dir, new_dir, old_dentry, new_dentry, triple);
return -EROFS;
}
if ( presto_get_permit(new_dir) < 0 ) {
EXIT;
presto_triple_fulllock(old_dir, new_dir, old_dentry, new_dentry, triple);
return -EROFS;
}
presto_triple_relock_sem(old_dir, new_dir, old_dentry, new_dentry, triple);
memset(&info, 0, sizeof(info));
if (!ISLENTO(presto_c2m(cache)))
info.flags = LENTO_FL_KML;
info.flags |= LENTO_FL_IGNORE_TIME;
error = do_rename(fset, old_parent, old_dentry, new_parent,
new_dentry, &info);
presto_triple_relock_other(old_dir, new_dir, old_dentry, new_dentry, triple);
presto_put_permit(new_dir);
presto_put_permit(old_dir);
return error;
}
/* basically this allows the ilookup processes access to all files for
* reading, while not making ilookup totally insecure. This could all
* go away if we could set the CAP_DAC_READ_SEARCH capability for the client.
*/
/* If posix acls are available, the underlying cache fs will export the
* appropriate permission function. Thus we do not worry here about ACLs
* or EAs. -SHP
*/
int presto_permission(struct inode *inode, int mask)
{
unsigned short mode = inode->i_mode;
struct presto_cache *cache;
int rc;
ENTRY;
if ( presto_can_ilookup() && !(mask & S_IWOTH)) {
CDEBUG(D_CACHE, "ilookup on %ld OK\n", inode->i_ino);
EXIT;
return 0;
}
cache = presto_get_cache(inode);
if ( cache ) {
/* we only override the file/dir permission operations */
struct inode_operations *fiops = filter_c2cfiops(cache->cache_filter);
struct inode_operations *diops = filter_c2cdiops(cache->cache_filter);
if ( S_ISREG(mode) && fiops && fiops->permission ) {
EXIT;
return fiops->permission(inode, mask);
}
if ( S_ISDIR(mode) && diops && diops->permission ) {
EXIT;
return diops->permission(inode, mask);
}
}
/* The cache filesystem doesn't have its own permission function,
* but we don't want to duplicate the VFS code here. In order
* to avoid looping from permission calling this function again,
* we temporarily override the permission operation while we call
* the VFS permission function.
*/
inode->i_op->permission = NULL;
rc = permission(inode, mask);
inode->i_op->permission = &presto_permission;
EXIT;
return rc;
}
int presto_ioctl(struct inode *inode, struct file *file,
unsigned int cmd, unsigned long arg)
{
char buf[1024];
struct izo_ioctl_data *data = NULL;
struct presto_dentry_data *dd;
int rc;
ENTRY;
/* Try the filesystem's ioctl first, and return if it succeeded. */
dd = presto_d2d(file->f_dentry);
if (dd && dd->dd_fset) {
int (*cache_ioctl)(struct inode *, struct file *, unsigned int, unsigned long ) = filter_c2cdfops(dd->dd_fset->fset_cache->cache_filter)->ioctl;
rc = -ENOTTY;
if (cache_ioctl)
rc = cache_ioctl(inode, file, cmd, arg);
if (rc != -ENOTTY) {
EXIT;
return rc;
}
}
if (current->euid != 0 && current->euid != izo_authorized_uid) {
EXIT;
return -EPERM;
}
memset(buf, 0, sizeof(buf));
if (izo_ioctl_getdata(buf, buf + 1024, (void *)arg)) {
CERROR("intermezzo ioctl: data error\n");
return -EINVAL;
}
data = (struct izo_ioctl_data *)buf;
switch(cmd) {
case IZO_IOC_REINTKML: {
int rc;
int cperr;
rc = kml_reint_rec(file, data);
EXIT;
cperr = copy_to_user((char *)arg, data, sizeof(*data));
if (cperr) {
CERROR("WARNING: cperr %d\n", cperr);
rc = -EFAULT;
}
return rc;
}
case IZO_IOC_GET_RCVD: {
struct izo_rcvd_rec rec;
struct presto_file_set *fset;
int rc;
fset = presto_fset(file->f_dentry);
if (fset == NULL) {
EXIT;
return -ENODEV;
}
rc = izo_rcvd_get(&rec, fset, data->ioc_uuid);
if (rc < 0) {
EXIT;
return rc;
}
EXIT;
return copy_to_user((char *)arg, &rec, sizeof(rec))? -EFAULT : 0;
}
case IZO_IOC_REPSTATUS: {
__u64 client_kmlsize;
struct izo_rcvd_rec *lr_client;
struct izo_rcvd_rec rec;
struct presto_file_set *fset;
int minor;
int rc;
fset = presto_fset(file->f_dentry);
if (fset == NULL) {
EXIT;
return -ENODEV;
}
minor = presto_f2m(fset);
client_kmlsize = data->ioc_kmlsize;
lr_client = (struct izo_rcvd_rec *) data->ioc_pbuf1;
rc = izo_repstatus(fset, client_kmlsize,
lr_client, &rec);
if (rc < 0) {
EXIT;
return rc;
}
EXIT;
return copy_to_user((char *)arg, &rec, sizeof(rec))? -EFAULT : 0;
}
case IZO_IOC_GET_CHANNEL: {
struct presto_file_set *fset;
fset = presto_fset(file->f_dentry);
if (fset == NULL) {
EXIT;
return -ENODEV;
}
data->ioc_dev = fset->fset_cache->cache_psdev->uc_minor;
CDEBUG(D_PSDEV, "CHANNEL %d\n", data->ioc_dev);
EXIT;
return copy_to_user((char *)arg, data, sizeof(*data))? -EFAULT : 0;
}
case IZO_IOC_SET_IOCTL_UID:
izo_authorized_uid = data->ioc_uid;
EXIT;
return 0;
case IZO_IOC_SET_PID:
rc = izo_psdev_setpid(data->ioc_dev);
EXIT;
return rc;
case IZO_IOC_SET_CHANNEL:
rc = izo_psdev_setchannel(file, data->ioc_dev);
EXIT;
return rc;
case IZO_IOC_GET_KML_SIZE: {
struct presto_file_set *fset;
__u64 kmlsize;
fset = presto_fset(file->f_dentry);
if (fset == NULL) {
EXIT;
return -ENODEV;
}
kmlsize = presto_kml_offset(fset) + fset->fset_kml_logical_off;
EXIT;
return copy_to_user((char *)arg, &kmlsize, sizeof(kmlsize))?-EFAULT : 0;
}
case IZO_IOC_PURGE_FILE_DATA: {
struct presto_file_set *fset;
fset = presto_fset(file->f_dentry);
if (fset == NULL) {
EXIT;
return -ENODEV;
}
rc = izo_purge_file(fset, data->ioc_inlbuf1);
EXIT;
return rc;
}
case IZO_IOC_GET_FILEID: {
rc = izo_get_fileid(file, data);
EXIT;
if (rc)
return rc;
return copy_to_user((char *)arg, data, sizeof(*data))? -EFAULT : 0;
}
case IZO_IOC_SET_FILEID: {
rc = izo_set_fileid(file, data);
EXIT;
if (rc)
return rc;
return copy_to_user((char *)arg, data, sizeof(*data))? -EFAULT : 0;
}
case IZO_IOC_ADJUST_LML: {
struct lento_vfs_context *info;
info = (struct lento_vfs_context *)data->ioc_inlbuf1;
rc = presto_adjust_lml(file, info);
EXIT;
return rc;
}
case IZO_IOC_CONNECT: {
struct presto_file_set *fset;
int minor;
fset = presto_fset(file->f_dentry);
if (fset == NULL) {
EXIT;
return -ENODEV;
}
minor = presto_f2m(fset);
rc = izo_upc_connect(minor, data->ioc_ino,
data->ioc_generation, data->ioc_uuid,
data->ioc_flags);
EXIT;
return rc;
}
case IZO_IOC_GO_FETCH_KML: {
struct presto_file_set *fset;
int minor;
fset = presto_fset(file->f_dentry);
if (fset == NULL) {
EXIT;
return -ENODEV;
}
minor = presto_f2m(fset);
rc = izo_upc_go_fetch_kml(minor, fset->fset_name,
data->ioc_uuid, data->ioc_kmlsize);
EXIT;
return rc;
}
case IZO_IOC_REVOKE_PERMIT:
if (data->ioc_flags)
rc = izo_revoke_permit(file->f_dentry, data->ioc_uuid);
else
rc = izo_revoke_permit(file->f_dentry, NULL);
EXIT;
return rc;
case IZO_IOC_CLEAR_FSET:
rc = izo_clear_fsetroot(file->f_dentry);
EXIT;
return rc;
case IZO_IOC_CLEAR_ALL_FSETS: {
struct presto_file_set *fset;
fset = presto_fset(file->f_dentry);
if (fset == NULL) {
EXIT;
return -ENODEV;
}
rc = izo_clear_all_fsetroots(fset->fset_cache);
EXIT;
return rc;
}
case IZO_IOC_SET_FSET:
/*
* Mark this dentry as being a fileset root.
*/
rc = presto_set_fsetroot_from_ioc(file->f_dentry,
data->ioc_inlbuf1,
data->ioc_flags);
EXIT;
return rc;
case IZO_IOC_MARK: {
int res = 0; /* resulting flags - returned to user */
int error;
CDEBUG(D_DOWNCALL, "mark inode: %ld, and: %x, or: %x, what %d\n",
file->f_dentry->d_inode->i_ino, data->ioc_and_flag,
data->ioc_or_flag, data->ioc_mark_what);
switch (data->ioc_mark_what) {
case MARK_DENTRY:
error = izo_mark_dentry(file->f_dentry,
data->ioc_and_flag,
data->ioc_or_flag, &res);
break;
case MARK_FSET:
error = izo_mark_fset(file->f_dentry,
data->ioc_and_flag,
data->ioc_or_flag, &res);
break;
case MARK_CACHE:
error = izo_mark_cache(file->f_dentry,
data->ioc_and_flag,
data->ioc_or_flag, &res);
break;
case MARK_GETFL: {
int fflags, cflags;
data->ioc_and_flag = 0xffffffff;
data->ioc_or_flag = 0;
error = izo_mark_dentry(file->f_dentry,
data->ioc_and_flag,
data->ioc_or_flag, &res);
if (error)
break;
error = izo_mark_fset(file->f_dentry,
data->ioc_and_flag,
data->ioc_or_flag, &fflags);
if (error)
break;
error = izo_mark_cache(file->f_dentry,
data->ioc_and_flag,
data->ioc_or_flag,
&cflags);
if (error)
break;
data->ioc_and_flag = fflags;
data->ioc_or_flag = cflags;
break;
}
default:
error = -EINVAL;
}
if (error) {
EXIT;
return error;
}
data->ioc_mark_what = res;
CDEBUG(D_DOWNCALL, "mark inode: %ld, and: %x, or: %x, what %x\n",
file->f_dentry->d_inode->i_ino, data->ioc_and_flag,
data->ioc_or_flag, data->ioc_mark_what);
EXIT;
return copy_to_user((char *)arg, data, sizeof(*data))? -EFAULT : 0;
}
#if 0
case IZO_IOC_CLIENT_MAKE_BRANCH: {
struct presto_file_set *fset;
int minor;
fset = presto_fset(file->f_dentry);
if (fset == NULL) {
EXIT;
return -ENODEV;
}
minor = presto_f2m(fset);
rc = izo_upc_client_make_branch(minor, fset->fset_name,
data->ioc_inlbuf1,
data->ioc_inlbuf2);
EXIT;
return rc;
}
#endif
case IZO_IOC_SERVER_MAKE_BRANCH: {
struct presto_file_set *fset;
int minor;
fset = presto_fset(file->f_dentry);
if (fset == NULL) {
EXIT;
return -ENODEV;
}
minor = presto_f2m(fset);
izo_upc_server_make_branch(minor, data->ioc_inlbuf1);
EXIT;
return 0;
}
case IZO_IOC_SET_KMLSIZE: {
struct presto_file_set *fset;
int minor;
struct izo_rcvd_rec rec;
fset = presto_fset(file->f_dentry);
if (fset == NULL) {
EXIT;
return -ENODEV;
}
minor = presto_f2m(fset);
rc = izo_upc_set_kmlsize(minor, fset->fset_name, data->ioc_uuid,
data->ioc_kmlsize);
if (rc != 0) {
EXIT;
return rc;
}
rc = izo_rcvd_get(&rec, fset, data->ioc_uuid);
if (rc == -EINVAL) {
/* We don't know anything about this uuid yet; no
* worries. */
memset(&rec, 0, sizeof(rec));
} else if (rc <= 0) {
CERROR("InterMezzo: error reading last_rcvd: %d\n", rc);
EXIT;
return rc;
}
rec.lr_remote_offset = data->ioc_kmlsize;
rc = izo_rcvd_write(fset, &rec);
if (rc <= 0) {
CERROR("InterMezzo: error writing last_rcvd: %d\n", rc);
EXIT;
return rc;
}
EXIT;
return rc;
}
case IZO_IOC_BRANCH_UNDO: {
struct presto_file_set *fset;
int minor;
fset = presto_fset(file->f_dentry);
if (fset == NULL) {
EXIT;
return -ENODEV;
}
minor = presto_f2m(fset);
rc = izo_upc_branch_undo(minor, fset->fset_name,
data->ioc_inlbuf1);
EXIT;
return rc;
}
case IZO_IOC_BRANCH_REDO: {
struct presto_file_set *fset;
int minor;
fset = presto_fset(file->f_dentry);
if (fset == NULL) {
EXIT;
return -ENODEV;
}
minor = presto_f2m(fset);
rc = izo_upc_branch_redo(minor, fset->fset_name,
data->ioc_inlbuf1);
EXIT;
return rc;
}
case TCGETS:
EXIT;
return -EINVAL;
default:
EXIT;
return -EINVAL;
}
EXIT;
return 0;
}
struct file_operations presto_dir_fops = {
.ioctl = presto_ioctl
};
struct inode_operations presto_dir_iops = {
.create = presto_create,
.lookup = presto_lookup,
.link = presto_link,
.unlink = presto_unlink,
.symlink = presto_symlink,
.mkdir = presto_mkdir,
.rmdir = presto_rmdir,
.mknod = presto_mknod,
.rename = presto_rename,
.permission = presto_permission,
.setattr = presto_setattr,
#ifdef CONFIG_FS_EXT_ATTR
.set_ext_attr = presto_set_ext_attr,
#endif
};