blob: 853c60377f166077f95624e92ba6ffc14d5ca18d [file] [log] [blame]
/*
* Namespace operations.
*
* Copyright (c) 2008-2014 Daniel Phillips
* Copyright (c) 2008-2014 OGAWA Hirofumi
*/
#include "tux3.h"
static struct dentry *tux3_lookup(struct inode *dir, struct dentry *dentry,
unsigned int flags)
{
struct sb *sb = tux_sb(dir->i_sb);
struct buffer_head *buffer;
struct inode *inode;
struct tux3_dirent *entry;
inum_t inum;
entry = tux_find_dirent(dir, &dentry->d_name, &buffer);
if (IS_ERR(entry)) {
if (PTR_ERR(entry) != -ENOENT)
return ERR_CAST(entry);
inode = NULL;
goto out;
}
inum = be64_to_cpu(entry->inum);
blockput(buffer);
inode = tux3_iget(sb, inum);
if (IS_ERR(inode) && PTR_ERR(inode) == -ENOENT)
tux3_warn(sb, "%s: inum %Lu not found", __func__, inum);
out:
return d_splice_alias(inode, dentry);
}
static int tux_add_dirent(struct inode *dir, struct dentry *dentry,
struct inode *inode)
{
int err = tux_create_dirent(dir, &dentry->d_name, inode);
if (!err)
d_instantiate(dentry, inode);
return err;
}
static int __tux3_mknod(struct inode *dir, struct dentry *dentry,
struct tux_iattr *iattr, dev_t rdev)
{
struct inode *inode;
int err, is_dir = S_ISDIR(iattr->mode);
if (!huge_valid_dev(rdev))
return -EINVAL;
if (is_dir && dir->i_nlink >= TUX_LINK_MAX)
return -EMLINK;
change_begin(tux_sb(dir->i_sb));
inode = tux_new_inode(dir, iattr, rdev);
err = PTR_ERR(inode);
if (!IS_ERR(inode)) {
err = tux_add_dirent(dir, dentry, inode);
if (!err) {
unlock_new_inode(inode);
if (is_dir)
inode_inc_link_count(dir);
}
}
change_end(tux_sb(dir->i_sb));
return err;
}
#ifdef __KERNEL__
static int tux3_mknod(struct inode *dir, struct dentry *dentry, umode_t mode,
dev_t rdev)
{
struct tux_iattr iattr = {
.uid = current_fsuid(),
.gid = current_fsgid(),
.mode = mode,
};
return __tux3_mknod(dir, dentry, &iattr, rdev);
}
static int tux3_create(struct inode *dir, struct dentry *dentry, umode_t mode,
bool excl)
{
return tux3_mknod(dir, dentry, mode, 0);
}
static int tux3_mkdir(struct inode *dir, struct dentry *dentry, umode_t mode)
{
return tux3_mknod(dir, dentry, S_IFDIR | mode, 0);
}
#endif /* !__KERNEL__ */
static int tux3_link(struct dentry *old_dentry, struct inode *dir,
struct dentry *dentry)
{
struct inode *inode = old_dentry->d_inode;
struct sb *sb = tux_sb(inode->i_sb);
int err;
if (inode->i_nlink >= TUX_LINK_MAX)
return -EMLINK;
change_begin(sb);
tux3_iattrdirty(inode);
inode->i_ctime = gettime();
inode_inc_link_count(inode);
ihold(inode);
err = tux_add_dirent(dir, dentry, inode);
if (err) {
inode_dec_link_count(inode);
iput(inode);
}
change_end(sb);
return err;
}
static int tux_del_dirent(struct inode *dir, struct dentry *dentry)
{
struct buffer_head *buffer;
struct tux3_dirent *entry;
entry = tux_find_dirent(dir, &dentry->d_name, &buffer);
if (IS_ERR(entry))
return PTR_ERR(entry);
return tux_delete_dirent(dir, buffer, entry);
}
static int __tux3_symlink(struct inode *dir, struct dentry *dentry,
struct tux_iattr *iattr, const char *symname)
{
struct sb *sb = tux_sb(dir->i_sb);
struct inode *inode;
unsigned len = strlen(symname) + 1;
int err, err2;
/* FIXME: We want more length? */
if (len > PAGE_CACHE_SIZE)
return -ENAMETOOLONG;
change_begin(sb);
inode = tux_new_inode(dir, iattr, 0);
err = PTR_ERR(inode);
if (!IS_ERR(inode)) {
err = tux_create_dirent(dir, &dentry->d_name, inode);
if (!err) {
/* FIXME: we may want to initialize symlink earlier */
err = page_symlink(inode, symname, len);
if (!err) {
d_instantiate(dentry, inode);
unlock_new_inode(inode);
goto out;
}
err2 = tux_del_dirent(dir, dentry);
if (err2)
tux3_fs_error(sb, "Failed to recover dir entry (err %d)", err2);
clear_nlink(inode);
tux3_mark_inode_dirty(inode);
unlock_new_inode(inode);
iput(inode);
}
}
out:
change_end(sb);
return err;
}
#ifdef __KERNEL__
static int tux3_symlink(struct inode *dir, struct dentry *dentry,
const char *symname)
{
struct tux_iattr iattr = {
.uid = current_fsuid(),
.gid = current_fsgid(),
.mode = S_IFLNK | S_IRWXUGO,
};
return __tux3_symlink(dir, dentry, &iattr, symname);
}
#endif /* !__KERNEL__ */
static int tux3_unlink(struct inode *dir, struct dentry *dentry)
{
struct inode *inode = dentry->d_inode;
struct sb *sb = tux_sb(inode->i_sb);
change_begin(sb);
int err = tux_del_dirent(dir, dentry);
if (!err) {
tux3_iattrdirty(inode);
inode->i_ctime = dir->i_ctime;
/* FIXME: we shouldn't write inode for i_nlink = 0? */
inode_dec_link_count(inode);
}
change_end(sb);
return err;
}
static int tux3_rmdir(struct inode *dir, struct dentry *dentry)
{
struct sb *sb = tux_sb(dir->i_sb);
struct inode *inode = dentry->d_inode;
int err = tux_dir_is_empty(inode);
if (!err) {
change_begin(sb);
err = tux_del_dirent(dir, dentry);
if (!err) {
tux3_iattrdirty(inode);
inode->i_ctime = dir->i_ctime;
/* FIXME: we need to do this for POSIX? */
/* inode->i_size = 0; */
clear_nlink(inode);
tux3_mark_inode_dirty_sync(inode);
inode_dec_link_count(dir);
}
change_end(sb);
}
return err;
}
static int tux3_rename(struct inode *old_dir, struct dentry *old_dentry,
struct inode *new_dir, struct dentry *new_dentry)
{
struct inode *old_inode = old_dentry->d_inode;
struct inode *new_inode = new_dentry->d_inode;
struct sb *sb = tux_sb(old_inode->i_sb);
struct buffer_head *old_buffer, *new_buffer, *clone;
struct tux3_dirent *old_entry, *new_entry;
void *olddata;
int err, new_subdir = 0;
unsigned delta;
old_entry = tux_find_dirent(old_dir, &old_dentry->d_name, &old_buffer);
if (IS_ERR(old_entry))
return PTR_ERR(old_entry);
/* FIXME: is this needed? */
assert(be64_to_cpu(old_entry->inum) == tux_inode(old_inode)->inum);
change_begin(sb);
delta = tux3_get_current_delta();
if (new_inode) {
int old_is_dir = S_ISDIR(old_inode->i_mode);
if (old_is_dir) {
err = tux_dir_is_empty(new_inode);
if (err)
goto error;
}
new_entry = tux_find_dirent(new_dir, &new_dentry->d_name,
&new_buffer);
if (IS_ERR(new_entry)) {
assert(PTR_ERR(new_entry) != -ENOENT);
err = PTR_ERR(new_entry);
goto error;
}
/*
* The directory is protected by i_mutex.
* blockdirty() should never return -EAGAIN.
*/
olddata = bufdata(new_buffer);
clone = blockdirty(new_buffer, delta);
if (IS_ERR(clone)) {
assert(PTR_ERR(clone) != -EAGAIN);
blockput(new_buffer);
err = PTR_ERR(clone);
goto error;
}
new_entry = ptr_redirect(new_entry, olddata, bufdata(clone));
/* this releases new_buffer */
tux_update_dirent(new_dir, clone, new_entry, old_inode);
tux3_iattrdirty(new_inode);
new_inode->i_ctime = new_dir->i_ctime;
if (old_is_dir)
drop_nlink(new_inode);
inode_dec_link_count(new_inode);
} else {
new_subdir = S_ISDIR(old_inode->i_mode) && new_dir != old_dir;
if (new_subdir) {
if (new_dir->i_nlink >= TUX_LINK_MAX) {
err = -EMLINK;
goto error;
}
}
err = tux_create_dirent(new_dir, &new_dentry->d_name,
old_inode);
if (err)
goto error;
if (new_subdir)
inode_inc_link_count(new_dir);
}
tux3_iattrdirty(old_inode);
old_inode->i_ctime = new_dir->i_ctime;
tux3_mark_inode_dirty(old_inode);
/*
* The new entry can be on same buffer with old_buffer, and
* may did buffer fork in the above path. So if old_buffer is
* forked buffer, we update the old_buffer in here.
*/
if (buffer_forked(old_buffer)) {
clone = blockget(mapping(old_dir), bufindex(old_buffer));
assert(clone);
old_entry = ptr_redirect(old_entry, bufdata(old_buffer),
bufdata(clone));
blockput(old_buffer);
old_buffer = clone;
}
err = tux_delete_dirent(old_dir, old_buffer, old_entry);
if (err) {
tux3_fs_error(sb, "couldn't delete old entry (%Lu)",
tux_inode(old_inode)->inum);
/* FIXME: now, we have hardlink even if it's dir. */
inode_inc_link_count(old_inode);
}
if (!err && new_subdir)
inode_dec_link_count(old_dir);
change_end(sb);
return err;
error:
change_end(sb);
blockput(old_buffer);
return err;
}
#ifdef __KERNEL__
const struct file_operations tux_dir_fops = {
.llseek = generic_file_llseek,
.read = generic_read_dir,
.iterate = tux_readdir,
.fsync = tux3_sync_file,
};
const struct inode_operations tux_dir_iops = {
.create = tux3_create,
.lookup = tux3_lookup,
.link = tux3_link,
.unlink = tux3_unlink,
.symlink = tux3_symlink,
.mkdir = tux3_mkdir,
.rmdir = tux3_rmdir,
.mknod = tux3_mknod,
.rename = tux3_rename,
.setattr = tux3_setattr,
.getattr = tux3_getattr
// .setxattr = generic_setxattr,
// .getxattr = generic_getxattr,
// .listxattr = ext3_listxattr,
// .removexattr = generic_removexattr,
// .permission = ext3_permission,
// .tmpfile = tux3_tmpfile,
/* FIXME: why doesn't ext4 support this for directory? */
// .fallocate = ext4_fallocate,
// .fiemap = ext4_fiemap,
};
#endif /* !__KERNEL__ */