Files
linux-legacy/fs/unionfs/dirfops.c
2010-12-07 07:10:30 -06:00

303 lines
7.2 KiB
C

/*
* Copyright (c) 2003-2010 Erez Zadok
* Copyright (c) 2003-2006 Charles P. Wright
* Copyright (c) 2005-2007 Josef 'Jeff' Sipek
* Copyright (c) 2005-2006 Junjiro Okajima
* Copyright (c) 2005 Arun M. Krishnakumar
* Copyright (c) 2004-2006 David P. Quigley
* Copyright (c) 2003-2004 Mohammad Nayyer Zubair
* Copyright (c) 2003 Puja Gupta
* Copyright (c) 2003 Harikesavan Krishnan
* Copyright (c) 2003-2010 Stony Brook University
* Copyright (c) 2003-2010 The Research Foundation of SUNY
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include "union.h"
/* Make sure our rdstate is playing by the rules. */
static void verify_rdstate_offset(struct unionfs_dir_state *rdstate)
{
BUG_ON(rdstate->offset >= DIREOF);
BUG_ON(rdstate->cookie >= MAXRDCOOKIE);
}
struct unionfs_getdents_callback {
struct unionfs_dir_state *rdstate;
void *dirent;
int entries_written;
int filldir_called;
int filldir_error;
filldir_t filldir;
struct super_block *sb;
};
/* based on generic filldir in fs/readir.c */
static int unionfs_filldir(void *dirent, const char *oname, int namelen,
loff_t offset, u64 ino, unsigned int d_type)
{
struct unionfs_getdents_callback *buf = dirent;
struct filldir_node *found = NULL;
int err = 0;
int is_whiteout;
char *name = (char *) oname;
buf->filldir_called++;
is_whiteout = is_whiteout_name(&name, &namelen);
found = find_filldir_node(buf->rdstate, name, namelen, is_whiteout);
if (found) {
/*
* If we had non-whiteout entry in dir cache, then mark it
* as a whiteout and but leave it in the dir cache.
*/
if (is_whiteout && !found->whiteout)
found->whiteout = is_whiteout;
goto out;
}
/* if 'name' isn't a whiteout, filldir it. */
if (!is_whiteout) {
off_t pos = rdstate2offset(buf->rdstate);
u64 unionfs_ino = ino;
err = buf->filldir(buf->dirent, name, namelen, pos,
unionfs_ino, d_type);
buf->rdstate->offset++;
verify_rdstate_offset(buf->rdstate);
}
/*
* If we did fill it, stuff it in our hash, otherwise return an
* error.
*/
if (err) {
buf->filldir_error = err;
goto out;
}
buf->entries_written++;
err = add_filldir_node(buf->rdstate, name, namelen,
buf->rdstate->bindex, is_whiteout);
if (err)
buf->filldir_error = err;
out:
return err;
}
static int unionfs_readdir(struct file *file, void *dirent, filldir_t filldir)
{
int err = 0;
struct file *lower_file = NULL;
struct dentry *dentry = file->f_path.dentry;
struct dentry *parent;
struct inode *inode = NULL;
struct unionfs_getdents_callback buf;
struct unionfs_dir_state *uds;
int bend;
loff_t offset;
unionfs_read_lock(dentry->d_sb, UNIONFS_SMUTEX_PARENT);
parent = unionfs_lock_parent(dentry, UNIONFS_DMUTEX_PARENT);
unionfs_lock_dentry(dentry, UNIONFS_DMUTEX_CHILD);
err = unionfs_file_revalidate(file, parent, false);
if (unlikely(err))
goto out;
inode = dentry->d_inode;
uds = UNIONFS_F(file)->rdstate;
if (!uds) {
if (file->f_pos == DIREOF) {
goto out;
} else if (file->f_pos > 0) {
uds = find_rdstate(inode, file->f_pos);
if (unlikely(!uds)) {
err = -ESTALE;
goto out;
}
UNIONFS_F(file)->rdstate = uds;
} else {
init_rdstate(file);
uds = UNIONFS_F(file)->rdstate;
}
}
bend = fbend(file);
while (uds->bindex <= bend) {
lower_file = unionfs_lower_file_idx(file, uds->bindex);
if (!lower_file) {
uds->bindex++;
uds->dirpos = 0;
continue;
}
/* prepare callback buffer */
buf.filldir_called = 0;
buf.filldir_error = 0;
buf.entries_written = 0;
buf.dirent = dirent;
buf.filldir = filldir;
buf.rdstate = uds;
buf.sb = inode->i_sb;
/* Read starting from where we last left off. */
offset = vfs_llseek(lower_file, uds->dirpos, SEEK_SET);
if (offset < 0) {
err = offset;
goto out;
}
err = vfs_readdir(lower_file, unionfs_filldir, &buf);
/* Save the position for when we continue. */
offset = vfs_llseek(lower_file, 0, SEEK_CUR);
if (offset < 0) {
err = offset;
goto out;
}
uds->dirpos = offset;
/* Copy the atime. */
fsstack_copy_attr_atime(inode,
lower_file->f_path.dentry->d_inode);
if (err < 0)
goto out;
if (buf.filldir_error)
break;
if (!buf.entries_written) {
uds->bindex++;
uds->dirpos = 0;
}
}
if (!buf.filldir_error && uds->bindex >= bend) {
/* Save the number of hash entries for next time. */
UNIONFS_I(inode)->hashsize = uds->hashentries;
free_rdstate(uds);
UNIONFS_F(file)->rdstate = NULL;
file->f_pos = DIREOF;
} else {
file->f_pos = rdstate2offset(uds);
}
out:
if (!err)
unionfs_check_file(file);
unionfs_unlock_dentry(dentry);
unionfs_unlock_parent(dentry, parent);
unionfs_read_unlock(dentry->d_sb);
return err;
}
/*
* This is not meant to be a generic repositioning function. If you do
* things that aren't supported, then we return EINVAL.
*
* What is allowed:
* (1) seeking to the same position that you are currently at
* This really has no effect, but returns where you are.
* (2) seeking to the beginning of the file
* This throws out all state, and lets you begin again.
*/
static loff_t unionfs_dir_llseek(struct file *file, loff_t offset, int origin)
{
struct unionfs_dir_state *rdstate;
struct dentry *dentry = file->f_path.dentry;
struct dentry *parent;
loff_t err;
unionfs_read_lock(dentry->d_sb, UNIONFS_SMUTEX_PARENT);
parent = unionfs_lock_parent(dentry, UNIONFS_DMUTEX_PARENT);
unionfs_lock_dentry(dentry, UNIONFS_DMUTEX_CHILD);
err = unionfs_file_revalidate(file, parent, false);
if (unlikely(err))
goto out;
rdstate = UNIONFS_F(file)->rdstate;
/*
* we let users seek to their current position, but not anywhere
* else.
*/
if (!offset) {
switch (origin) {
case SEEK_SET:
if (rdstate) {
free_rdstate(rdstate);
UNIONFS_F(file)->rdstate = NULL;
}
init_rdstate(file);
err = 0;
break;
case SEEK_CUR:
err = file->f_pos;
break;
case SEEK_END:
/* Unsupported, because we would break everything. */
err = -EINVAL;
break;
}
} else {
switch (origin) {
case SEEK_SET:
if (rdstate) {
if (offset == rdstate2offset(rdstate))
err = offset;
else if (file->f_pos == DIREOF)
err = DIREOF;
else
err = -EINVAL;
} else {
struct inode *inode;
inode = dentry->d_inode;
rdstate = find_rdstate(inode, offset);
if (rdstate) {
UNIONFS_F(file)->rdstate = rdstate;
err = rdstate->offset;
} else {
err = -EINVAL;
}
}
break;
case SEEK_CUR:
case SEEK_END:
/* Unsupported, because we would break everything. */
err = -EINVAL;
break;
}
}
out:
if (!err)
unionfs_check_file(file);
unionfs_unlock_dentry(dentry);
unionfs_unlock_parent(dentry, parent);
unionfs_read_unlock(dentry->d_sb);
return err;
}
/*
* Trimmed directory options, we shouldn't pass everything down since
* we don't want to operate on partial directories.
*/
struct file_operations unionfs_dir_fops = {
.llseek = unionfs_dir_llseek,
.read = generic_read_dir,
.readdir = unionfs_readdir,
.unlocked_ioctl = unionfs_ioctl,
.open = unionfs_open,
.release = unionfs_file_release,
.flush = unionfs_flush,
.fsync = unionfs_fsync,
.fasync = unionfs_fasync,
};