www.pudn.com > cryptfs030905.rar > inode.c


/*
 * Copyright (c) 1997-2003 Erez Zadok
 * Copyright (c) 2001-2003 Stony Brook University
 *
 * For specific licensing information, see the COPYING file distributed with
 * this package, or get one from ftp://ftp.filesystems.org/pub/fist/COPYING.
 *
 * This Copyright notice must be kept intact and distributed with all
 * fistgen sources INCLUDING sources generated by fistgen.
 */
/*
 *  $Id: inode.c,v 1.29 2003/07/29 18:17:20 ezk Exp $
 */

#ifdef HAVE_CONFIG_H
# include 
#endif /* HAVE_CONFIG_H */
#ifdef FISTGEN
# include "fist_cryptfs.h"
#endif /* FISTGEN */
#include "fist.h"
#include "cryptfs.h"


STATIC int
cryptfs_create(inode_t *dir, dentry_t *dentry, int mode)
{
    int err;
    dentry_t *hidden_dentry;
    dentry_t *hidden_dir_dentry;

    print_entry_location();
    hidden_dentry = cryptfs_hidden_dentry(dentry); /* CPW: Moved below print_entry_location */
    ASSERT(hidden_dentry != NULL);
    fist_checkinode(dir, "cryptfs_create");

    hidden_dir_dentry = lock_parent(hidden_dentry);
    err = PTR_ERR(hidden_dir_dentry);
    if (IS_ERR(hidden_dir_dentry))
	goto out;

    err = vfs_create(hidden_dir_dentry->d_inode, hidden_dentry, mode);
    /* XXX this could potentially return a negative hidden_dentry! */
    if (err)
	goto out_lock;

    err = cryptfs_interpose(hidden_dentry, dentry, dir->i_sb, 0);
    if (err)
	goto out_lock;

    fist_copy_attr_timesizes(dir, hidden_dir_dentry->d_inode);


 out_lock:
    unlock_dir(hidden_dir_dentry);
 out:
    fist_checkinode(dir, "post cryptfs_create");
    print_exit_status(err);
    return err;
}


STATIC dentry_t *
cryptfs_lookup(inode_t *dir, dentry_t *dentry)
{
    int err = 0;
    dentry_t *hidden_dir_dentry;
    dentry_t *hidden_dentry;
    const char *name;
    vnode_t *this_vnode;
    dentry_t *this_dir;
    unsigned int namelen;
    char *encoded_name;
    unsigned int encoded_namelen;

    print_entry_location();
    hidden_dir_dentry = cryptfs_hidden_dentry(dentry->d_parent); /* CPW: Moved below print_entry_location */
    name = dentry->d_name.name;
    namelen = dentry->d_name.len;
    fist_checkinode(dir, "cryptfs_lookup");

    this_vnode = dir;
    this_dir = hidden_dir_dentry;

    fist_print_dentry("cryptfs_lookup IN", dentry);
    fist_print_dentry("cryptfs_lookup: dentry->d_parent IN", dentry->d_parent);
    fist_print_dentry("cryptfs_lookup: hidden_dir_dentry IN", hidden_dir_dentry);
    fist_print_inode("cryptfs_lookup: dir IN", dir);

    if (hidden_dir_dentry->d_inode)
	fist_print_inode("cryptfs_lookup: hidden_dir_dentry->d_inode",
			 hidden_dir_dentry->d_inode);

    /* must initialize dentry operations */
    dentry->d_op = &cryptfs_dops;

    ;

    /* increase refcount of base dentry (lookup_one[_len] will decrement) */
    // THIS IS RIGHT! (don't "fix" it)
    // NO THIS IS WRONG IN 2.3.99-pre6. lookup_one[_len] will NOT decrement
    // dget(hidden_dir_dentry);

    encoded_namelen = cryptfs_encode_filename(name,
					     namelen,
					     &encoded_name,
					     SKIP_DOTS, dir, dir->i_sb);

    /*if there was an error in encoding filenames*/
    if (encoded_namelen < 0) {
	err = encoded_namelen;
	goto out_free;
    }
    /* will allocate a new hidden dentry if needed */
    hidden_dentry = lookup_one_len(encoded_name, hidden_dir_dentry, encoded_namelen - 1);
    kfree(encoded_name);

    if (IS_ERR(hidden_dentry)) {
	/*
	 * this produces an unusual dentry: one that has neither an
	 * inode, nor a private structure attached to it. All cleanup
	 * methods (d_delete, d_release, etc) must be prepared to deal
	 * with such dentries. Ion 09/29/2001
	 */
	printk("ERR from hidden_dentry!!!\n");
	err = PTR_ERR(hidden_dentry);
	goto out;
    }

    ;

    /* update parent directory's atime */
    fist_copy_attr_atime(dir, hidden_dir_dentry->d_inode);
    /* link the upper and lower dentries */
    dtopd(dentry) = (struct cryptfs_dentry_info *) kmalloc(sizeof(struct cryptfs_dentry_info), GFP_KERNEL);
    if (!dtopd(dentry)) {
	err = -ENOMEM;
	goto out_dput;
    }
    dtohd(dentry) = hidden_dentry;

    //assign the the parent directory's key to the child directory
    dtokey(dentry)= dtokey(dentry->d_parent);


    /* lookup is special: it needs to handle negative dentries */
    if (!hidden_dentry->d_inode) {
	d_add(dentry, NULL);
	fist_print_dentry("cryptfs_lookup OUT hidden_dentry", hidden_dentry);
	goto out;
    }

    fist_dprint(6, "lookup \"%s\" -> inode %d\n", name, hidden_dentry->d_inode->i_ino);
    err = cryptfs_interpose(hidden_dentry, dentry, dir->i_sb, 1);
    if (err)
	goto out_free;

    fist_checkinode(dentry->d_inode, "cryptfs_lookup OUT: dentry->d_inode:");
    fist_checkinode(dir, "cryptfs_lookup OUT: dir");

    fist_print_dentry("cryptfs_lookup OUT hidden_dentry", hidden_dentry);
    fist_print_inode("cryptfs_lookup OUT hidden_inode", hidden_dentry->d_inode);


    /* All is well */
    goto out;

 out_free:
    d_drop(dentry);		/* so that our bad dentry will get destroyed */
    kfree(dtopd(dentry));
    dtopd(dentry) = NULL;	/* be safe */

 out_dput:
    dput(hidden_dentry);

 out:
    fist_print_dentry("cryptfs_lookup OUT", dentry);
    print_exit_status(err);
    return ERR_PTR(err);
}


STATIC int
cryptfs_link(dentry_t *old_dentry, inode_t *dir, dentry_t *new_dentry)
{
    int err;
    dentry_t *hidden_old_dentry;
    dentry_t *hidden_new_dentry;
    dentry_t *hidden_dir_dentry;

    print_entry_location();
    hidden_old_dentry = cryptfs_hidden_dentry(old_dentry); /* CPW: Moved below print_entry_location */
    hidden_new_dentry = cryptfs_hidden_dentry(new_dentry);

    fist_checkinode(dir, "cryptfs_link-dir");
    fist_checkinode(old_dentry->d_inode, "cryptfs_link-oldinode");

    dget(hidden_old_dentry);
    dget(hidden_new_dentry);
    hidden_dir_dentry = lock_parent(hidden_new_dentry);


    err = vfs_link(hidden_old_dentry,
		   hidden_dir_dentry->d_inode,
		   hidden_new_dentry);
    if (err || !hidden_new_dentry->d_inode)
	goto out_lock;

    err = cryptfs_interpose(hidden_new_dentry, new_dentry, dir->i_sb, 0);
    if (err)
	goto out_lock;

    fist_copy_attr_timesizes(dir, hidden_new_dentry->d_inode);
    /* propagate number of hard-links */
    old_dentry->d_inode->i_nlink = itohi(old_dentry->d_inode)->i_nlink;

 out_lock:
    unlock_dir(hidden_dir_dentry);
    dput(hidden_new_dentry);
    dput(hidden_old_dentry);
    if (!new_dentry->d_inode)
	d_drop(new_dentry);

    print_exit_status(err);
    return err;
}


STATIC int
cryptfs_unlink(inode_t *dir, dentry_t *dentry)
{
    int err = 0;
    inode_t *hidden_dir;
    dentry_t *hidden_dentry;
    dentry_t *hidden_dir_dentry;

    print_entry_location();

    hidden_dir = itohi(dir); /* CPW: Moved below print_entry_location */
    hidden_dentry = cryptfs_hidden_dentry(dentry);

    ASSERT(hidden_dentry != NULL);
    fist_checkinode(dir, "cryptfs_unlink-dir");

    dget(dentry);
    hidden_dir_dentry = lock_parent(hidden_dentry);


    /* avoid destroying the hidden inode if the file is in use */
    dget(hidden_dentry);
    err = vfs_unlink(hidden_dir, hidden_dentry);
    dput(hidden_dentry);

    if (!err)			/* vfs_unlink does that */
	d_delete(hidden_dentry);

 out_lock:
    fist_copy_attr_times(dir, hidden_dir);
    /* propagate number of hard-links */
    dentry->d_inode->i_nlink = itohi(dentry->d_inode)->i_nlink;

    unlock_dir(hidden_dir_dentry);

    /*
     * call d_drop so the system "forgets" about us
     */
    if (!err) {
	//	fist_print_dentry("cryptfs_unlink: 0", dentry);
	d_drop(dentry);
	//	fist_print_dentry("cryptfs_unlink: 1", dentry);
    }

    dput(dentry);

    fist_checkinode(dir, "post cryptfs_unlink-dir");
    print_exit_status(err);
    return err;
}


STATIC int
cryptfs_symlink(inode_t *dir, dentry_t *dentry, const char *symname)
{
    int err;
    dentry_t *hidden_dentry;
    dentry_t *hidden_dir_dentry;
    char *encoded_symname;
    unsigned int encoded_symlen;

    print_entry_location();
    hidden_dentry = cryptfs_hidden_dentry(dentry); /* CPW: Moved below print_entry_location */
    fist_checkinode(dir, "cryptfs_symlink-dir");

    dget(hidden_dentry);
    hidden_dir_dentry = lock_parent(hidden_dentry);

    encoded_symlen = cryptfs_encode_filename(symname,
					    strlen(symname),
					    &encoded_symname,
					    DO_DOTS, dir, dir->i_sb);
    /* if length is negative, return back */
    if (encoded_symlen < 0) {
	err = encoded_symlen;
	goto out_lock;
    }

    err = vfs_symlink(hidden_dir_dentry->d_inode,
		      hidden_dentry,
		      encoded_symname);
    kfree(encoded_symname);
    if (err || !hidden_dentry->d_inode)
	goto out_lock;
    err = cryptfs_interpose(hidden_dentry, dentry, dir->i_sb, 0);
    if (err)
	goto out_lock;

    fist_copy_attr_timesizes(dir, hidden_dir_dentry->d_inode);
    fist_checkinode(dir, "post cryptfs_symlink-dir");

 out_lock:
    unlock_dir(hidden_dir_dentry);
    dput(hidden_dentry);
    if (!dentry->d_inode)
	d_drop(dentry);

    print_exit_status(err);
    return err;
}


STATIC int
cryptfs_mkdir(inode_t *dir, dentry_t *dentry, int mode)
{
    int err;
    dentry_t *hidden_dentry;
    dentry_t *hidden_dir_dentry;

    print_entry_location();
    hidden_dentry = cryptfs_hidden_dentry(dentry); /* CPW: Moved below print_entry_location */
    fist_checkinode(dir, "cryptfs_mkdir-dir");

    hidden_dir_dentry = lock_parent(hidden_dentry);

    err = vfs_mkdir(hidden_dir_dentry->d_inode,
		    hidden_dentry,
		    mode);
    if (err || !hidden_dentry->d_inode)
	goto out;

    err = cryptfs_interpose(hidden_dentry, dentry, dir->i_sb, 0);
    if (err)
	goto out;

    fist_copy_attr_timesizes(dir, hidden_dir_dentry->d_inode);
    /* update number of links on parent directory */
    dir->i_nlink = hidden_dir_dentry->d_inode->i_nlink;

    fist_checkinode(dir, "post cryptfs_mkdir-dir");

 out:
    unlock_dir(hidden_dir_dentry);
    if (!dentry->d_inode)
	d_drop(dentry);

    print_exit_status(err);
    return err;
}


STATIC int
cryptfs_rmdir(inode_t *dir, dentry_t *dentry)
{
    int err = 0;
    dentry_t *hidden_dentry;
    dentry_t *hidden_dir_dentry;

    print_entry_location();
    hidden_dentry = cryptfs_hidden_dentry(dentry); /* CPW: Moved below print_entry_location */
    fist_checkinode(dir, "cryptfs_rmdir-dir");

    dget(dentry);
    hidden_dir_dentry = lock_parent(hidden_dentry);

    /* avoid destroying the hidden inode if the file is in use */
    dget(hidden_dentry);
    err = vfs_rmdir(hidden_dir_dentry->d_inode, hidden_dentry);
    dput(hidden_dentry);

    if (!err)			/* vfs_rmdir does that */
	d_delete(hidden_dentry);

 out_lock:
    fist_copy_attr_times(dir, hidden_dir_dentry->d_inode);
    /* copy the nlink count for our dentry and our parent's dentry */
    dir->i_nlink =  hidden_dir_dentry->d_inode->i_nlink;

    unlock_dir(hidden_dir_dentry);

    /*
     * call d_drop so the system "forgets" about us
     */
    if (!err)
	d_drop(dentry);

    dput(dentry);

    print_exit_status(err);
    return err;
}


STATIC int
cryptfs_mknod(inode_t *dir, dentry_t *dentry, int mode, int dev)
{
    int err;
    dentry_t *hidden_dentry;
    dentry_t *hidden_dir_dentry;

    print_entry_location();
    hidden_dentry = cryptfs_hidden_dentry(dentry); /* CPW: Moved below print_entry_location */
    fist_checkinode(dir, "cryptfs_mknod-dir");

    hidden_dir_dentry = lock_parent(hidden_dentry);

    err = vfs_mknod(hidden_dir_dentry->d_inode,
		    hidden_dentry,
		    mode,
		    dev);
    if (err || !hidden_dentry->d_inode)
	goto out;

    err = cryptfs_interpose(hidden_dentry, dentry, dir->i_sb, 0);
    if (err)
	goto out;
    fist_copy_attr_timesizes(dir, hidden_dir_dentry->d_inode);

 out:
    unlock_dir(hidden_dir_dentry);
    if (!dentry->d_inode)
	d_drop(dentry);

    fist_checkinode(dir, "post cryptfs_mknod-dir");
    print_exit_status(err);
    return err;
}


STATIC int
cryptfs_rename(inode_t *old_dir, dentry_t *old_dentry,
	      inode_t *new_dir, dentry_t *new_dentry)
{
    int err;
    dentry_t *hidden_old_dentry;
    dentry_t *hidden_new_dentry;
    dentry_t *hidden_old_dir_dentry;
    dentry_t *hidden_new_dir_dentry;

    print_entry_location();

    hidden_old_dentry = cryptfs_hidden_dentry(old_dentry);/* CPW: Moved below print_entry_location */
    hidden_new_dentry = cryptfs_hidden_dentry(new_dentry);

    fist_checkinode(old_dir, "cryptfs_rename-old_dir");
    fist_checkinode(new_dir, "cryptfs_rename-new_dir");

    dget(hidden_old_dentry);
    dget(hidden_new_dentry);
    hidden_old_dir_dentry = get_parent(hidden_old_dentry);
    hidden_new_dir_dentry = get_parent(hidden_new_dentry);
    double_lock(hidden_old_dir_dentry, hidden_new_dir_dentry);


    err = vfs_rename(hidden_old_dir_dentry->d_inode, hidden_old_dentry,
		     hidden_new_dir_dentry->d_inode, hidden_new_dentry);
    if (err)
	goto out_lock;

    fist_copy_attr_all(new_dir, hidden_new_dir_dentry->d_inode);
    if (new_dir != old_dir)
	fist_copy_attr_all(old_dir, hidden_old_dir_dentry->d_inode);

 out_lock:
    // double_unlock will dput the new/old parent dentries whose refcnts
    // were incremented via get_parent above.
    double_unlock(hidden_old_dir_dentry, hidden_new_dir_dentry);
    dput(hidden_new_dentry);
    dput(hidden_old_dentry);

    fist_checkinode(new_dir, "post cryptfs_rename-new_dir");
    print_exit_status(err);
    return err;
}


STATIC int
cryptfs_readlink(dentry_t *dentry, char *buf, int bufsiz)
{
    int err;
    dentry_t *hidden_dentry;
    char *decoded_name, *hidden_buf;
    mm_segment_t old_fs;

    print_entry_location();
    hidden_dentry = cryptfs_hidden_dentry(dentry);/* CPW: Moved below print_entry_location */
    fist_print_dentry("cryptfs_readlink IN", dentry);

    if (!hidden_dentry->d_inode->i_op ||
	!hidden_dentry->d_inode->i_op->readlink) {
	err = -EINVAL;
	goto out;
    }

    hidden_buf = kmalloc(bufsiz, GFP_KERNEL);
    if (hidden_buf == NULL) {
	printk("Out of memory.\n");
	err = -ENOMEM;
	goto out;
    }

    old_fs = get_fs();
    set_fs(KERNEL_DS);
    err = hidden_dentry->d_inode->i_op->readlink(hidden_dentry,
						 hidden_buf,
						 bufsiz);
    set_fs(old_fs);
    if (err >= 0) {
	// don't do this, it's not zero-terminated!!!
	//	fist_dprint(7, "READLINK: link \"%s\", length %d\n", hidden_buf, err);
	err = cryptfs_decode_filename(hidden_buf, err,
				     &decoded_name, DO_DOTS, dentry->d_inode, dentry->d_sb);
	if (err > 0) {
	    if (copy_to_user(buf, decoded_name, err))
		err = -EFAULT;
	    kfree(decoded_name);
	}
	fist_copy_attr_atime(dentry->d_inode, hidden_dentry->d_inode);
    }

    kfree(hidden_buf);

 out:
    print_exit_status(err);
    return err;
}


STATIC int
cryptfs_follow_link(dentry_t *dentry, struct nameidata *nd)
{
    char *buf;
    int len = PAGE_SIZE, err;
    mm_segment_t old_fs;

    print_entry_location();
    //    fist_print_dentry("cryptfs_follow_link dentry IN", dentry);

    buf = kmalloc(len, GFP_KERNEL);
    if (!buf) {
	err = -ENOMEM;
	goto out;
    }

    /* read the symlink, and then we will follow it */
    old_fs = get_fs();
    set_fs(KERNEL_DS);
    err = dentry->d_inode->i_op->readlink(dentry, buf, len);
    set_fs(old_fs);
    if (err < 0)
	goto out_free;

    buf[err] = 0;	// terminate the buffer -- XXX still needed?

    // XXX: FIXME w/ cryptfs_encode_filename()
    /*
     * vfs_follow_link will increase the nd's mnt refcnt
     * we assume that some other VFS code decrements it.
     */
    err = vfs_follow_link(nd, buf);

 out_free:
    kfree(buf);
 out:
#if 0
    if (err < 0) {
	dput(nd->dentry);
	printk("EZK follow_link() mnt_cnt %d\n", atomic_read(&nd->mnt->mnt_count));
	mntput(nd->mnt);
    }
#endif

    print_exit_status(err);
    return err;
}


/*
 * Function to handle truncations extending the size of the file for non-SCA
 * file systems.
 */
STATIC int
cryptfs_truncate(dentry_t *dentry, loff_t new_length)
{
    inode_t *inode = dentry->d_inode;
    file_t fake_file;
    page_t *tmp_page;
    int index;
    unsigned to;
    int err = 0;

    print_entry_location();

    /*
     * if this is a truncate past the end of the file, call cryptfs_fill_zeros
     */
    if (new_length > inode->i_size) {

	memset(&fake_file, 0, sizeof(fake_file));
	fake_file.f_dentry = dentry;
	index = new_length >> PAGE_CACHE_SHIFT;

	tmp_page = cryptfs_get1page(&fake_file, index);
	if (IS_ERR(tmp_page)) {
	    err = PTR_ERR(tmp_page);
	    goto out;
	}

	to = new_length & ~PAGE_CACHE_MASK;

	/*
	 * 'to' is passed to cryptfs_fill_zeros instead of a 'from'
	 *  because unlike the normal case where we fill in zeros
	 *  till 'from', we need to fill it in with zeros till the
	 *  end of the file
	 */
	err = cryptfs_fill_zeros(&fake_file, tmp_page, to);
    }

 out:
    print_exit_status(err);
    return err;
}


STATIC int
cryptfs_permission(inode_t *inode, int mask)
{
    inode_t *hidden_inode;
    int mode;
    int err;

    print_entry_location();
    hidden_inode = itohi(inode);/* CPW: Moved below print_entry_location */
    mode = inode->i_mode;
    
    err = vfs_permission(inode, mask);
    if (err)
	goto out;
    err = permission(hidden_inode, mask);
 out:
    print_exit_status(err);
    return err;
}


STATIC int
cryptfs_inode_revalidate(dentry_t *dentry)
{
    int err = 0;
    dentry_t *hidden_dentry;
    inode_t *hidden_inode;

    print_entry_location();
    hidden_dentry = cryptfs_hidden_dentry(dentry);/* CPW: Moved below print_entry_location */
    hidden_inode = hidden_dentry->d_inode;
    //    fist_print_dentry("cryptfs_inode_revalidate IN", dentry);

    if (hidden_inode->i_op && hidden_inode->i_op->revalidate) {
	err = hidden_inode->i_op->revalidate(hidden_dentry);
	if (!err)
	    fist_copy_attr_all(dentry->d_inode, hidden_inode);
    }

    //    fist_print_dentry("cryptfs_inode_revalidate OUT", dentry);
    print_exit_status(err);
    return err;
}


STATIC int
cryptfs_setattr(dentry_t *dentry, struct iattr *ia)
{
    int err = 0;
    dentry_t *hidden_dentry;
    inode_t *inode;
    inode_t *hidden_inode;

    print_entry_location();
    hidden_dentry = cryptfs_hidden_dentry(dentry);
    inode = dentry->d_inode;
    hidden_inode = itohi(inode);
    fist_checkinode(inode, "cryptfs_setattr");

    if (ia->ia_valid & ATTR_SIZE) {
	err = cryptfs_truncate(dentry, ia->ia_size);
	if (err < 0)
	    goto out;
    }
    err = notify_change(hidden_dentry, ia);

#if defined(FIST_FILTER_DATA) || defined(FIST_FILTER_SCA)
 out:
#endif /* FIST_FILTER_DATA || FIST_FILTER_SCA */
    /*
     * The lower file system might has changed the attributes, even if
     * notify_change above resulted in an error(!)  so we copy the
     * hidden_inode's attributes (and a few more) to our inode.
     */
    fist_copy_attr_all(inode, hidden_inode);

    fist_checkinode(inode, "post cryptfs_setattr");
    print_exit_status(err);
    return err;
}


#ifdef NOT_USED_YET
STATIC int
cryptfs_getattr(dentry_t *dentry, struct iattr *ia)
{
    return -ENOSYS;
}
#endif /* NOT_USED_YET */


#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,20)
/* This is lifted from fs/xattr.c */
static void *
xattr_alloc(size_t size, size_t limit)
{
	void *ptr;

	if (size > limit)
		return ERR_PTR(-E2BIG);

	if (!size)	/* size request, no buffer is needed */
		return NULL;
	else if (size <= PAGE_SIZE)
		ptr = kmalloc((unsigned long) size, GFP_KERNEL);
	else
		ptr = vmalloc((unsigned long) size);
	if (!ptr)
		return ERR_PTR(-ENOMEM);
	return ptr;
}

static void
xattr_free(void *ptr, size_t size)
{
	if (!size)	/* size request, no buffer was needed */
		return;
	else if (size <= PAGE_SIZE)
		kfree(ptr);
	else
		vfree(ptr);
}

/* BKL held by caller.
 * dentry->d_inode->i_sem down
 */
STATIC int
cryptfs_getxattr(struct dentry *dentry, const char *name, void *value, size_t size) {
	struct dentry *hidden_dentry = NULL;
	int err = -ENOTSUPP;
	char *encoded_suffix = NULL;
	int namelen = 0;
	int sufflen = 0;
	int prelen = 0;
	/* Define these anyway so we don't need as much ifdef'ed code. */
	char *encoded_name = NULL;
	char *encoded_value = NULL;

	print_entry_location();

	hidden_dentry = dtohd(dentry);

	ASSERT(hidden_dentry);
	ASSERT(hidden_dentry->d_inode);
	ASSERT(hidden_dentry->d_inode->i_op);

	fist_dprint(18, "getxattr: name=\"%s\", value %d bytes\n", name, size);

	if (hidden_dentry->d_inode->i_op->getxattr) {
		const char *suffix;

		namelen = strlen(name);

		suffix = strchr(name, '.');
		if (suffix) {
			suffix ++;
			prelen = (suffix - name) - 1;
			sufflen = namelen - (prelen + 1);
		} else {
			prelen = 0;
			sufflen = namelen;
			suffix = name;
		}

		sufflen = cryptfs_encode_filename(suffix, sufflen, &encoded_suffix, DO_DOTS, dentry->d_inode, dentry->d_inode->i_sb) - 1;
		encoded_name = xattr_alloc(prelen + sufflen + 2, XATTR_SIZE_MAX);
		if (IS_ERR(encoded_name)) {
			err = PTR_ERR(encoded_name);
			encoded_name = NULL;
			goto out;
		}
		if (prelen) {
			memcpy(encoded_name, name, prelen);
			encoded_name[prelen] = '.';
			memcpy(encoded_name + prelen + 1, encoded_suffix, sufflen);
			encoded_name[prelen + 1 + sufflen] = '\0';
		} else {
			memcpy(encoded_name + prelen, encoded_suffix, sufflen);
			encoded_name[sufflen] = '\0';
		}

		fist_dprint(18, "ENCODED XATTR NAME: %s\n", encoded_name);

		encoded_value = xattr_alloc(size, XATTR_SIZE_MAX);
		if (IS_ERR(encoded_value)) {
			err = PTR_ERR(encoded_value);
			encoded_name = NULL;
			goto out;
		}


		down(&hidden_dentry->d_inode->i_sem);
		/* lock_kernel() already done by caller. */
		err = hidden_dentry->d_inode->i_op->getxattr(hidden_dentry, encoded_name, encoded_value, size);
		/* unlock_kernel() will be done by caller. */
		up(&hidden_dentry->d_inode->i_sem);

		/* Decode the value. */
		if ((err > 0) && size) {
			int ret;

			ret = cryptfs_encode_block(encoded_value, value, err, dentry->d_inode, dentry->d_inode->i_sb, 0);
			if (ret < 0) {
				err = ret;
				goto out;
			}
		}
	}

out:
	if (encoded_name) {
		xattr_free(encoded_name, namelen + 1);
	}
	if (encoded_value) {
		xattr_free(encoded_value, size);
	}
	print_exit_status(err);
	return err;
}

/* BKL held by caller.
 * dentry->d_inode->i_sem down
 */
STATIC int
#if LINUX_VERSION_CODE == KERNEL_VERSION(2,4,20) /* kernel is 2.4.20 */
cryptfs_setxattr(struct dentry *dentry, const char *name, void *value, size_t size, int flags)
#  else /* kernel is not 2.4.20 */
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,21)
cryptfs_setxattr(struct dentry *dentry, const char *name, const void *value, size_t size, int flags)
#   endif /* kernel 2.4.21 or newer */
#  endif /* kernel is not 2.4.20 */
{
	struct dentry *hidden_dentry = NULL;
	int err = -ENOTSUPP;
	char *encoded_suffix = NULL;
	int namelen = 0;
	int sufflen = 0;
	int prelen = 0;
	/* Define these anyway, so we don't have as much ifdef'ed code. */
	char *encoded_value = NULL;
	char *encoded_name = NULL;

	print_entry_location();

	hidden_dentry = dtohd(dentry);

	ASSERT(hidden_dentry);
	ASSERT(hidden_dentry->d_inode);
	ASSERT(hidden_dentry->d_inode->i_op);

	fist_dprint(18, "setxattr: name=\"%s\", value %d bytes, flags=%x\n", name, size, flags);

	if (hidden_dentry->d_inode->i_op->setxattr) {
		const char *suffix;

		namelen = strlen(name);

		suffix = strchr(name, '.');
		if (suffix) {
			suffix ++;
			prelen = (suffix - name) - 1;
			sufflen = namelen - (prelen + 1);
		} else {
			prelen = 0;
			sufflen = namelen;
			suffix = name;
		}

		sufflen = cryptfs_encode_filename(suffix, sufflen, &encoded_suffix, DO_DOTS, dentry->d_inode, dentry->d_inode->i_sb) - 1;
		encoded_name = xattr_alloc(prelen + sufflen + 2, XATTR_SIZE_MAX);
		if (IS_ERR(encoded_name)) {
			err = PTR_ERR(encoded_name);
			encoded_name = NULL;
			goto out;
		}
		if (prelen) {
			memcpy(encoded_name, name, prelen);
			encoded_name[prelen] = '.';
			memcpy(encoded_name + prelen + 1, encoded_suffix, sufflen);
			encoded_name[prelen + 1 + sufflen] = '\0';
		} else {
			memcpy(encoded_name + prelen, encoded_suffix, sufflen);
			encoded_name[sufflen] = '\0';
		}

		fist_dprint(18, "ENCODED XATTR NAME: %s\n", encoded_name);

		/* Encode the value. */
		encoded_value = xattr_alloc(size, XATTR_SIZE_MAX);
		if (IS_ERR(encoded_value)) {
			err = PTR_ERR(encoded_value);
			encoded_value = NULL;
			goto out;
		}

		err = cryptfs_encode_block(value, encoded_value, size, dentry->d_inode, dentry->d_inode->i_sb, 0);
		if (err < 0) {
			goto out;
		}

		down(&hidden_dentry->d_inode->i_sem);
		/* lock_kernel() already done by caller. */
		err = hidden_dentry->d_inode->i_op->setxattr(hidden_dentry, encoded_name, encoded_value, size, flags);
		/* unlock_kernel() will be done by caller. */
		up(&hidden_dentry->d_inode->i_sem);
	}

out:
	if (encoded_name) {
		xattr_free(encoded_name, namelen + 1);
	}
	if (encoded_suffix) {
		kfree(encoded_suffix);
	}
	if (encoded_value) {
		xattr_free(encoded_value, size);
	}
	print_exit_status(err);
	return err;
}

/* BKL held by caller.
 * dentry->d_inode->i_sem down
 */
STATIC int
cryptfs_removexattr(struct dentry *dentry, const char *name) {
	struct dentry *hidden_dentry = NULL;
	int err = -ENOTSUPP;
	char *encoded_name;
	char *encoded_suffix = NULL;
	int namelen = 0;
	int sufflen = 0;
	int prelen = 0;
	print_entry_location();

	hidden_dentry = dtohd(dentry);

	ASSERT(hidden_dentry);
	ASSERT(hidden_dentry->d_inode);
	ASSERT(hidden_dentry->d_inode->i_op);

	fist_dprint(18, "removexattr: name=\"%s\"\n", name);

	if (hidden_dentry->d_inode->i_op->removexattr) {
		const char *suffix;

		namelen = strlen(name);

		suffix = strchr(name, '.');
		if (suffix) {
			suffix ++;
			prelen = (suffix - name) - 1;
			sufflen = namelen - (prelen + 1);
		} else {
			prelen = 0;
			sufflen = namelen;
			suffix = name;
		}

		sufflen = cryptfs_encode_filename(suffix, sufflen, &encoded_suffix, DO_DOTS, dentry->d_inode, dentry->d_inode->i_sb) - 1;
		encoded_name = xattr_alloc(prelen + sufflen + 2, XATTR_SIZE_MAX);
		if (IS_ERR(encoded_name)) {
			err = PTR_ERR(encoded_name);
			encoded_name = NULL;
			goto out;
		}
		if (prelen) {
			memcpy(encoded_name, name, prelen);
			encoded_name[prelen] = '.';
			memcpy(encoded_name + prelen + 1, encoded_suffix, sufflen);
			encoded_name[prelen + 1 + sufflen] = '\0';
		} else {
			memcpy(encoded_name + prelen, encoded_suffix, sufflen);
			encoded_name[sufflen] = '\0';
		}

		fist_dprint(18, "ENCODED XATTR NAME: %s\n", encoded_name);

		down(&hidden_dentry->d_inode->i_sem);
		/* lock_kernel() already done by caller. */
		err = hidden_dentry->d_inode->i_op->removexattr(hidden_dentry, encoded_name);
		/* unlock_kernel() will be done by caller. */
		up(&hidden_dentry->d_inode->i_sem);
	}

out:
	print_exit_status(err);
	return err;
}

/* BKL held by caller.
 * dentry->d_inode->i_sem down
 */
STATIC int
cryptfs_listxattr(struct dentry *dentry, char *list, size_t size) {
	struct dentry *hidden_dentry = NULL;
	int err = -ENOTSUPP;
	char *encoded_list = NULL;

	print_entry_location();

	hidden_dentry = dtohd(dentry);

	ASSERT(hidden_dentry);
	ASSERT(hidden_dentry->d_inode);
	ASSERT(hidden_dentry->d_inode->i_op);

	if (hidden_dentry->d_inode->i_op->listxattr) {
		encoded_list = xattr_alloc(size, XATTR_LIST_MAX);
		if (IS_ERR(encoded_list)) {
			err = PTR_ERR(encoded_list);
			encoded_list = NULL;
			goto out;
		}
		down(&hidden_dentry->d_inode->i_sem);
		/* lock_kernel() already done by caller. */
		err = hidden_dentry->d_inode->i_op->listxattr(hidden_dentry, encoded_list, size);
		/* unlock_kernel() will be done by caller. */
		up(&hidden_dentry->d_inode->i_sem);
		/* The problem is we don't know how big the xattrs
		 * will be without decoding them ourselves.  So,
		 * we just have to copy it attribute by attribute
		 * to figure out.
		 *
		 * XXX: If the buffer was set to zero then we
		 * actually end up lying and passing through the
		 * error.  For some transformations this works, but
		 * you may need to change it (e.g., using an SCA
		 * in the name ala cryptfs).
		 */
		if (encoded_list)
		{
			int i;
			int newsize = 0;
			char *cur = encoded_list;
			char *encoded_suffix;

			while (cur && (cur < (encoded_list + err))) {
				const char *suffix;
				int namelen, sufflen, prelen;

				ASSERT(cur);
				printk("cur: %s\n", cur);

				namelen = strlen(cur);

				suffix = strchr(cur, '.');
				if (suffix) {
					suffix ++;
					prelen = (suffix - cur) - 1;
					sufflen = namelen - (prelen + 1);
				} else {
					prelen = 0;
					sufflen = namelen;
					suffix = cur;
				}

				sufflen = cryptfs_decode_filename(suffix, sufflen, &encoded_suffix, DO_DOTS, dentry->d_inode, dentry->d_inode->i_sb);

				if (prelen) {
					if (newsize + sufflen + prelen + 2 < size) {
						/* COPY IT IN */
						memcpy(list + newsize, cur, prelen);
						list[newsize + prelen] = '.';
						memcpy(list + newsize + prelen + 1, encoded_suffix, sufflen);
						list[newsize + prelen + 1 + sufflen] = '\0';
					}
					newsize += sufflen + prelen + 2;
				} else {
					if (newsize + sufflen + 1 < size) {
						memcpy(list + newsize, encoded_suffix, sufflen);
						list[newsize + sufflen] = '\0';
					}
					newsize += sufflen + 1;
				}

				cur += (namelen + 1);
			}

			err = newsize;
		}
	}

out:
	if (encoded_list) {
		xattr_free(encoded_list, size);
	}
	print_exit_status(err);
	return err;
}
# endif /* LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,20) */


struct inode_operations cryptfs_symlink_iops =
{
    readlink:	cryptfs_readlink,
    follow_link: cryptfs_follow_link,
    permission:	cryptfs_permission,
    revalidate:	cryptfs_inode_revalidate,
    setattr:	cryptfs_setattr,
#if 0
    // XXX: off, b/c the VFS doesn't call getattr yet
    getattr:	cryptfs_getattr,
#endif

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,20)
    setxattr:	cryptfs_setxattr,
    getxattr:	cryptfs_getxattr,
    listxattr:	cryptfs_listxattr,
    removexattr: cryptfs_removexattr
# endif /* LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,20) */
};

struct inode_operations cryptfs_dir_iops =
{
    create:	cryptfs_create,
    lookup:	cryptfs_lookup,
    link:	cryptfs_link,
    unlink:	cryptfs_unlink,
    symlink:	cryptfs_symlink,
    mkdir:	cryptfs_mkdir,
    rmdir:	cryptfs_rmdir,
    mknod:	cryptfs_mknod,
    rename:	cryptfs_rename,
    /* no readlink/follow_link for non-symlinks */
    // off because we have setattr
    //    truncate:	cryptfs_truncate,
    permission:	cryptfs_permission,
    revalidate:	cryptfs_inode_revalidate,
    setattr:	cryptfs_setattr,
#if 0
    // XXX: off, b/c the VFS doesn't call getattr yet
    getattr:	cryptfs_getattr,
#endif
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,20)
    setxattr:	cryptfs_setxattr,
    getxattr:	cryptfs_getxattr,
    listxattr:	cryptfs_listxattr,
    removexattr: cryptfs_removexattr
# endif /* LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,20) */
};

struct inode_operations cryptfs_main_iops =
{
    permission:	cryptfs_permission,
    revalidate:	cryptfs_inode_revalidate,
    setattr:	cryptfs_setattr,
#if 0
    // XXX: off, b/c the VFS doesn't call getattr yet
    getattr:	cryptfs_getattr,
#endif
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,20)
    setxattr:	cryptfs_setxattr,
    getxattr:	cryptfs_getxattr,
    listxattr:	cryptfs_listxattr,
    removexattr: cryptfs_removexattr
# endif /* LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,20) */
};

/*
 * Local variables:
 * c-basic-offset: 4
 * End:
 */