mirror of
https://github.com/genesi/linux-legacy.git
synced 2026-08-23 16:18:30 +00:00
303 lines
7.2 KiB
C
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,
|
|
};
|