Logo Search packages:      
Sourcecode: unionfs version File versions

super.c

/*
 * Copyright (c) 2003-2005 Erez Zadok
 * Copyright (c) 2003-2005 Charles P. Wright
 * Copyright (c) 2003-2005 Mohammad Nayyer Zubair
 * Copyright (c) 2003-2005 Puja Gupta
 * Copyright (c) 2003-2005 Harikesavan Krishnan
 * Copyright (c) 2003-2005 Stony Brook University
 * Copyright (c) 2003-2005 The Research Foundation of State University of New York
 *
 * For specific licensing information, see the COPYING file distributed with
 * this package.
 *
 * This Copyright notice must be kept intact and distributed with all sources.
 */
/*
 *  $Id: super.c,v 1.44 2005/03/16 14:08:09 cwright Exp $
 */

#ifdef HAVE_CONFIG_H
# include <config.h>
#endif /* HAVE_CONFIG_H */
#include "fist.h"
#include "unionfs.h"

STATIC void
unionfs_read_inode(inode_t *inode)
{
    static struct address_space_operations unionfs_empty_aops;

    print_entry_location();

#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0)
    ASSERT(sizeof(struct unionfs_inode_info) < sizeof(inode->u) - sizeof(void *));
    itopd_lhs(inode) = (&(inode->u.generic_ip) + 1);
#else
    itopd_lhs(inode) = KMALLOC(sizeof(struct unionfs_inode_info), GFP_UNIONFS);
#endif
    if (!itopd(inode)) {
      FISTBUG("No kernel memory when allocating inode private data!\n");
    }
    init_itopd(inode);
    itohi_ptr(inode) = KMALLOC(sizeof(inode_t *) * sbmax(inode->i_sb), GFP_UNIONFS);
    if (!itohi_ptr(inode)) {
      FISTBUG("No kernel memory when allocating lower-pointer array!\n");
    }
    init_itohi_ptr(inode, sbmax(inode->i_sb));

#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0)
    inode->i_version = ++event;     /* increment inode version */
#else
    inode->i_version++;
#endif
    inode->i_op = &unionfs_main_iops;
    inode->i_fop = &unionfs_main_fops;
    /* I don't think ->a_ops is ever allowed to be NULL */
    inode->i_mapping->a_ops = &unionfs_empty_aops;
    fist_dprint(7, "setting inode 0x%p a_ops to empty (0x%p)\n",
            inode, inode->i_mapping->a_ops);

    print_exit_location();
}


STATIC void
unionfs_put_inode(inode_t *inode)
{
    print_entry_location();
    fist_dprint(8, "%s i_count = %d, i_nlink = %d\n", __FUNCTION__,
            atomic_read(&inode->i_count), inode->i_nlink);
    /*
     * This is really funky stuff:
     * Basically, if i_count == 1, iput will then decrement it and this inode will be destroyed.
     * It is currently holding a reference to the hidden inode.
     * Therefore, it needs to release that reference by calling iput on the hidden inode.
     * iput() _will_ do it for us (by calling our clear_inode), but _only_ if i_nlink == 0.
     * The problem is, NFS keeps i_nlink == 1 for silly_rename'd files.
     * So we must for our i_nlink to 0 here to trick iput() into calling our clear_inode.
     */
    if (atomic_read(&inode->i_count) == 1)
      inode->i_nlink = 0;
    print_exit_location();
}


/*
 * we now define delete_inode, because there are two VFS paths that may
 * destroy an inode: one of them calls clear inode before doing everything
 * else that's needed, and the other is fine.  This way we truncate the inode
 * size (and its pages) and then clear our own inode, which will do an iput
 * on our and the lower inode.
 */
STATIC void
unionfs_delete_inode(inode_t *inode)
{
    print_entry_location();

    fist_checkinode(inode, "unionfs_delete_inode IN");
    inode->i_size = 0;        /* every f/s seems to do that */
    clear_inode(inode);

    print_exit_location();
}


/* final actions when unmounting a file system */
STATIC void
unionfs_put_super(super_block_t *sb)
{
    int bindex, bstart, bend;
    print_entry_location();

    if (stopd(sb)) {
#ifdef SPLIT_VIEW_CACHES
      if ((stopd(sb)->usi_primary == sb) && !list_empty(&stopd(sb)->usi_altsupers)) {
          struct unionfs_sb_info *sbinfo;
          struct unionfs_sb_info *t;
          list_for_each_entry_safe(sbinfo, t, &stopd(sb)->usi_altsupers, usi_altsupers) {
            kill_super(sbinfo->usi_thissb);
          }
      }
#endif
        bstart = sbstart(sb);
        bend = sbend(sb);
        for (bindex = bstart; bindex <= bend; bindex++) {
          mntput(stohiddenmnt_index(sb, bindex));
        }
        KFREE(stohs_ptr(sb));
        KFREE(stohiddenmnt_ptr(sb));
        KFREE(stopd(sb)->usi_sbcount);
        KFREE(stopd(sb)->usi_branchperms);
      KFREE(stopd(sb));
      stopd_lhs(sb) = NULL;
    }
    fist_dprint(6, "unionfs: released super\n");

    print_exit_location();
}


#ifdef NOT_NEEDED
/*
 * This is called in do_umount before put_super.
 * The superblock lock is not held yet.
 * We probably do not need to define this or call write_super
 * on the hidden_sb, because sync_supers() will get to hidden_sb
 * sooner or later.  But it is also called from file_fsync()...
 */
STATIC void
unionfs_write_super(super_block_t *sb)
{
    return;
}
#endif /* NOT_NEEDED */


STATIC int
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0)
unionfs_statfs(super_block_t *sb, struct statfs *buf)
#else
unionfs_statfs(super_block_t *sb, struct kstatfs *buf)
#endif
{
      int err = 0;
      super_block_t *hidden_sb;
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0)
      struct statfs rsb;
#else
      struct kstatfs rsb;
#endif
      int bindex, bindex1, bstart, bend;

      print_entry_location();
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0)
      memset(buf, 0,  sizeof(struct statfs));
#else
      memset(buf, 0, sizeof(struct kstatfs));
#endif
      buf->f_type = UNIONFS_SUPER_MAGIC;

      bstart = sbstart(sb);
      bend = sbend(sb);
      
      for (bindex = bstart; bindex <= bend; bindex++) {
            int dup = 0;

            hidden_sb = stohs_index(sb, bindex);
            /* Ignore duplicate super blocks. */
            for (bindex1 = bstart; bindex1 < bindex; bindex1++) {
                  if (hidden_sb == stohs_index(sb, bindex1)) {
                        dup = 1;
                        break;
                  }
            }
            if (dup) {
                  continue;
            }


            err = vfs_statfs(hidden_sb, &rsb);
            fist_dprint(8, "adding values for bindex:%d bsize:%d blocks:%d bfree:%d bavail:%d\n",bindex,(int)rsb.f_bsize,(int)rsb.f_blocks,(int)rsb.f_bfree,(int)rsb.f_bavail);
            if (!buf->f_bsize) {
                  buf->f_bsize = rsb.f_bsize;
            } else {
                  if (buf->f_bsize < rsb.f_bsize) {
                        int shifter = 0;
                        while (buf->f_bsize < rsb.f_bsize) {
                              shifter++;
                              rsb.f_bsize >>= 1;
                        }
                        rsb.f_blocks <<= shifter;
                        rsb.f_bfree <<= shifter;
                        rsb.f_bavail <<= shifter;
                  } else {
                        int shifter = 0;
                        while (buf->f_bsize > rsb.f_bsize) {
                              shifter++;
                              rsb.f_bsize <<= 1;
                        }
                        rsb.f_blocks >>= shifter;
                        rsb.f_bfree >>= shifter;
                        rsb.f_bavail >>= shifter;
                  }
            }
            buf->f_blocks += rsb.f_blocks;
            buf->f_bfree += rsb.f_bfree;
            buf->f_bavail += rsb.f_bavail;
            buf->f_files += rsb.f_files;
            buf->f_ffree += rsb.f_ffree;
      }

      print_exit_status(err);
      return err;
}


/*
 * XXX: not implemented.  This is not allowed yet.
 * Should we call this on the hidden_sb?  Probably not.
 */
STATIC int
unionfs_remount_fs(super_block_t *sb, int *flags, char *data)
{
    return -ENOSYS;
}


/*
 * Called by iput() when the inode reference count reached zero
 * and the inode is not hashed anywhere.  Used to clear anything
 * that needs to be, before the inode is completely destroyed and put
 * on the inode free list.
 */
STATIC void
unionfs_clear_inode(inode_t *inode)
{
    int bindex, bstart,bend;
    inode_t *hidden_inode;
    struct list_head *pos, *n;
    struct unionfs_dir_state *rdstate;

    print_entry_location();

    fist_checkinode(inode, "unionfs_clear_inode IN");

    list_for_each_safe(pos, n, &itopd(inode)->uii_readdircache) {
      rdstate = list_entry(pos, struct unionfs_dir_state, uds_cache);
      list_del(&rdstate->uds_cache);
      free_rdstate(rdstate);
    }

    /* Decrement a reference to a hidden_inode, which was incremented
     * by our read_inode when it was created initially.  */
    bstart = ibstart(inode);
    bend = ibend(inode);
    if (bstart >= 0) {
        for (bindex = bstart; bindex <= bend; bindex++) {

            hidden_inode = itohi_index(inode, bindex);

            if (hidden_inode == NULL) continue;

            iput(hidden_inode);
        }
    }
    // XXX: why this assertion fails?
    // because it doesn't like us
    // ASSERT((inode->i_state & I_DIRTY) == 0);
    KFREE(itohi_ptr(inode));
    itohi_ptr(inode) = NULL;
#if LINUX_VERSION_CODE > KERNEL_VERSION(2,6,0)
    KFREE(itopd(inode));
#endif
    itopd_lhs(inode) = NULL;

    print_exit_location();
}

/* Called when we have a dirty inode, right here we only throw out
 * parts of our readdir list that are too old.
 */
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,9)
static int unionfs_write_inode(struct inode *inode, int sync) {
#else
static void unionfs_write_inode(struct inode *inode, int sync) {
#endif
    struct list_head *pos, *n;
    struct unionfs_dir_state *rdstate;

    print_entry_location();

    PASSERT(inode);
    PASSERT(itopd(inode));
    spin_lock(&itopd(inode)->uii_rdlock);
    list_for_each_safe(pos, n, &itopd(inode)->uii_readdircache) {
      rdstate = list_entry(pos, struct unionfs_dir_state, uds_cache);
      /* We keep this list in LRU order. */
      if ((rdstate->uds_access + RDCACHE_JIFFIES) > jiffies)
          break;
      itopd(inode)->uii_rdcount--;
      list_del(&rdstate->uds_cache);
      free_rdstate(rdstate);
    }
    spin_unlock(&itopd(inode)->uii_rdlock);

    print_exit_location();
#if LINUX_VERSION_CODE > KERNEL_VERSION(2,6,0)
    return 0;
#endif
}

/*
 * Called in do_umount() if the MNT_FORCE flag was used and this
 * function is defined.  See comment in linux/fs/super.c:do_umount().
 * Used only in nfs, to kill any pending RPC tasks, so that subsequent
 * code can actually succeed and won't leave tasks that need handling.
 *
 * PS. I wonder if this is somehow useful to undo damage that was
 * left in the kernel after a user level file server (such as amd)
 * dies.
 */
STATIC void
unionfs_umount_begin(super_block_t *sb)
{
    super_block_t *hidden_sb;
    int bindex, bstart, bend;

    print_entry_location();

    bstart = sbstart(sb);
    bend = sbend(sb);
    for (bindex = bstart; bindex <= bend; bindex++) {

        hidden_sb = stohs_index(sb, bindex);

        if (hidden_sb && hidden_sb->s_op && hidden_sb->s_op->umount_begin) {
          hidden_sb->s_op->umount_begin(hidden_sb);
        }
    }
    print_exit_location();
}


STATIC int
unionfs_show_options(struct seq_file *m, struct vfsmount *mnt)
{
      struct super_block *sb = mnt->mnt_sb;
      int ret = 0;
      unsigned long tmp = 0;
      char *hidden_path;
        int bindex, bstart, bend;
        int perms;

      tmp = __get_free_page(GFP_UNIONFS);
      if (!tmp) {
            ret = -ENOMEM;
            goto out;
      }

        bindex = bstart = sbstart(sb);
        bend = sbend(sb);

        seq_printf(m, ",dirs=");
        for (bindex = bstart; bindex <= bend; bindex++) {
                hidden_path = d_path(dtohd_index(sb->s_root, bindex), stohiddenmnt_index(sb, bindex), (char *)tmp, PAGE_SIZE);
                perms = stopd(sb)->usi_branchperms[bindex];
                seq_printf(m, "%s=%s", hidden_path, perms & MAY_WRITE ? "rw" : "ro");
                if (bindex != bend) {
                        seq_printf(m, ":");
                }
        }

        seq_printf(m, ",debug=%d", fist_get_debug_value());

        if (IS_SET(sb, GLOBAL_ERR_PASSUP)) {
                seq_printf(m, ",err=passup");
        } else {
                seq_printf(m, ",err=tryleft");
        }

        if (IS_SET(sb, DELETE_FIRST)) {
                seq_printf(m, ",delete=first");
        } else if (IS_SET(sb, DELETE_WHITEOUT)) {
                seq_printf(m, ",delete=whiteout");
        } else {
                seq_printf(m, ",delete=all");
        }

        if (IS_SET(sb, COPYUP_CURRENT_USER)) {
                seq_printf(m, ",copyup=currentuser");
        } else if (IS_SET(sb, COPYUP_FS_MOUNTER)) {
                seq_printf(m, ",copyup=mounter");
        } else {
                seq_printf(m, ",copyup=preserve");
        }

        if (IS_SET(sb, SETATTR_ALL)) {
                seq_printf(m, ",setattr=all");
        } else {
                seq_printf(m, ",setattr=left");
        }
out:
      if (tmp) {
            free_page(tmp);
      }
      return ret;
}

#ifdef SPLIT_VIEW_CACHES
static struct dentry *unionfs_select_super(super_block_t *sb, struct vfsmount *mnt) {
    struct dentry *root = NULL;

    print_entry_location();

    if (current->fsuid == 0 || list_empty(&stopd(sb)->usi_altsupers)) {
      root = sb->s_root;
    } else {
      struct unionfs_sb_info *sbinfo;
      lock_super(sb);
      list_for_each_entry(sbinfo, &stopd(sb)->usi_altsupers, usi_altsupers) {
          if (sbinfo->usi_thissb != sb) {
            root = sbinfo->usi_thissb->s_root;
            break;
          }
      }
      if (!root) {
            FISTBUG("How can an non-empty list have only the same super!\n");
            root = sb->s_root;
      }
      unlock_super(sb);
    }

out:
    print_exit_pointer(root);
    return root;
}
#endif

#if LINUX_VERSION_CODE > KERNEL_VERSION(2,6,0)
/**
 * The entry given by dentry here is always a directory.
 * We can't just do dentry->d_parent, because it may
 * not be there, since this dentry could have been
 * created by calling d_alloc_anon.
 */
static struct dentry *unionfs_get_parent(struct dentry *dentry) {

      struct dentry *ret;
      inode_t *childinode;
      childinode = dentry->d_inode;
      ret = d_find_alias(childinode);
        return ret;
}

struct export_operations unionfs_export_ops = {
      .get_parent =     unionfs_get_parent
};
#endif

struct super_operations unionfs_sops =
{
    read_inode:         unionfs_read_inode,
    put_inode:          unionfs_put_inode,
    delete_inode: unionfs_delete_inode,
    put_super:          unionfs_put_super,
    statfs:       unionfs_statfs,
    remount_fs:         unionfs_remount_fs,
    clear_inode:  unionfs_clear_inode,
    umount_begin: unionfs_umount_begin,
    show_options:       unionfs_show_options,
    write_inode:  unionfs_write_inode,
#ifdef SPLIT_VIEW_CACHES
    select_super: unionfs_select_super,
#endif
};

/*
 * Local variables:
 * c-basic-offset: 4
 * End:
 * vim:shiftwidth=4
 */

Generated by  Doxygen 1.6.0   Back to index