dcd0538ff4
Introduce tree rotations into the b-tree code. This will allow ocfs2 to support sparse files. Much of the added code is designed to be generic (in the ocfs2 sense) so that it can later be re-used to implement large extended attributes. This patch only adds the rotation code and does minimal updates to callers of the extent api. Signed-off-by: Mark Fasheh <mark.fasheh@oracle.com>
2320 lines
55 KiB
C
2320 lines
55 KiB
C
/* -*- mode: c; c-basic-offset: 8; -*-
|
|
* vim: noexpandtab sw=8 ts=8 sts=0:
|
|
*
|
|
* namei.c
|
|
*
|
|
* Create and rename file, directory, symlinks
|
|
*
|
|
* Copyright (C) 2002, 2004 Oracle. All rights reserved.
|
|
*
|
|
* Portions of this code from linux/fs/ext3/dir.c
|
|
*
|
|
* Copyright (C) 1992, 1993, 1994, 1995
|
|
* Remy Card (card@masi.ibp.fr)
|
|
* Laboratoire MASI - Institut Blaise pascal
|
|
* Universite Pierre et Marie Curie (Paris VI)
|
|
*
|
|
* from
|
|
*
|
|
* linux/fs/minix/dir.c
|
|
*
|
|
* Copyright (C) 1991, 1992 Linux Torvalds
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2 of the License, or (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public
|
|
* License along with this program; if not, write to the
|
|
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
|
* Boston, MA 021110-1307, USA.
|
|
*/
|
|
|
|
#include <linux/fs.h>
|
|
#include <linux/types.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/highmem.h>
|
|
|
|
#define MLOG_MASK_PREFIX ML_NAMEI
|
|
#include <cluster/masklog.h>
|
|
|
|
#include "ocfs2.h"
|
|
|
|
#include "alloc.h"
|
|
#include "dcache.h"
|
|
#include "dir.h"
|
|
#include "dlmglue.h"
|
|
#include "extent_map.h"
|
|
#include "file.h"
|
|
#include "inode.h"
|
|
#include "journal.h"
|
|
#include "namei.h"
|
|
#include "suballoc.h"
|
|
#include "super.h"
|
|
#include "symlink.h"
|
|
#include "sysfile.h"
|
|
#include "uptodate.h"
|
|
#include "vote.h"
|
|
|
|
#include "buffer_head_io.h"
|
|
|
|
#define NAMEI_RA_CHUNKS 2
|
|
#define NAMEI_RA_BLOCKS 4
|
|
#define NAMEI_RA_SIZE (NAMEI_RA_CHUNKS * NAMEI_RA_BLOCKS)
|
|
#define NAMEI_RA_INDEX(c,b) (((c) * NAMEI_RA_BLOCKS) + (b))
|
|
|
|
static int inline ocfs2_search_dirblock(struct buffer_head *bh,
|
|
struct inode *dir,
|
|
const char *name, int namelen,
|
|
unsigned long offset,
|
|
struct ocfs2_dir_entry **res_dir);
|
|
|
|
static int ocfs2_delete_entry(handle_t *handle,
|
|
struct inode *dir,
|
|
struct ocfs2_dir_entry *de_del,
|
|
struct buffer_head *bh);
|
|
|
|
static int __ocfs2_add_entry(handle_t *handle,
|
|
struct inode *dir,
|
|
const char *name, int namelen,
|
|
struct inode *inode, u64 blkno,
|
|
struct buffer_head *parent_fe_bh,
|
|
struct buffer_head *insert_bh);
|
|
|
|
static int ocfs2_mknod_locked(struct ocfs2_super *osb,
|
|
struct inode *dir,
|
|
struct dentry *dentry, int mode,
|
|
dev_t dev,
|
|
struct buffer_head **new_fe_bh,
|
|
struct buffer_head *parent_fe_bh,
|
|
handle_t *handle,
|
|
struct inode **ret_inode,
|
|
struct ocfs2_alloc_context *inode_ac);
|
|
|
|
static int ocfs2_fill_new_dir(struct ocfs2_super *osb,
|
|
handle_t *handle,
|
|
struct inode *parent,
|
|
struct inode *inode,
|
|
struct buffer_head *fe_bh,
|
|
struct ocfs2_alloc_context *data_ac);
|
|
|
|
static int ocfs2_prepare_orphan_dir(struct ocfs2_super *osb,
|
|
struct inode **ret_orphan_dir,
|
|
struct inode *inode,
|
|
char *name,
|
|
struct buffer_head **de_bh);
|
|
|
|
static int ocfs2_orphan_add(struct ocfs2_super *osb,
|
|
handle_t *handle,
|
|
struct inode *inode,
|
|
struct ocfs2_dinode *fe,
|
|
char *name,
|
|
struct buffer_head *de_bh,
|
|
struct inode *orphan_dir_inode);
|
|
|
|
static int ocfs2_create_symlink_data(struct ocfs2_super *osb,
|
|
handle_t *handle,
|
|
struct inode *inode,
|
|
const char *symname);
|
|
|
|
static inline int ocfs2_add_entry(handle_t *handle,
|
|
struct dentry *dentry,
|
|
struct inode *inode, u64 blkno,
|
|
struct buffer_head *parent_fe_bh,
|
|
struct buffer_head *insert_bh)
|
|
{
|
|
return __ocfs2_add_entry(handle, dentry->d_parent->d_inode,
|
|
dentry->d_name.name, dentry->d_name.len,
|
|
inode, blkno, parent_fe_bh, insert_bh);
|
|
}
|
|
|
|
/* An orphan dir name is an 8 byte value, printed as a hex string */
|
|
#define OCFS2_ORPHAN_NAMELEN ((int)(2 * sizeof(u64)))
|
|
|
|
static struct dentry *ocfs2_lookup(struct inode *dir, struct dentry *dentry,
|
|
struct nameidata *nd)
|
|
{
|
|
int status;
|
|
u64 blkno;
|
|
struct buffer_head *dirent_bh = NULL;
|
|
struct inode *inode = NULL;
|
|
struct dentry *ret;
|
|
struct ocfs2_dir_entry *dirent;
|
|
struct ocfs2_inode_info *oi;
|
|
|
|
mlog_entry("(0x%p, 0x%p, '%.*s')\n", dir, dentry,
|
|
dentry->d_name.len, dentry->d_name.name);
|
|
|
|
if (dentry->d_name.len > OCFS2_MAX_FILENAME_LEN) {
|
|
ret = ERR_PTR(-ENAMETOOLONG);
|
|
goto bail;
|
|
}
|
|
|
|
mlog(0, "find name %.*s in directory %llu\n", dentry->d_name.len,
|
|
dentry->d_name.name, (unsigned long long)OCFS2_I(dir)->ip_blkno);
|
|
|
|
status = ocfs2_meta_lock(dir, NULL, 0);
|
|
if (status < 0) {
|
|
if (status != -ENOENT)
|
|
mlog_errno(status);
|
|
ret = ERR_PTR(status);
|
|
goto bail;
|
|
}
|
|
|
|
status = ocfs2_find_files_on_disk(dentry->d_name.name,
|
|
dentry->d_name.len, &blkno,
|
|
dir, &dirent_bh, &dirent);
|
|
if (status < 0)
|
|
goto bail_add;
|
|
|
|
inode = ocfs2_iget(OCFS2_SB(dir->i_sb), blkno, 0);
|
|
if (IS_ERR(inode)) {
|
|
ret = ERR_PTR(-EACCES);
|
|
goto bail_unlock;
|
|
}
|
|
|
|
oi = OCFS2_I(inode);
|
|
/* Clear any orphaned state... If we were able to look up the
|
|
* inode from a directory, it certainly can't be orphaned. We
|
|
* might have the bad state from a node which intended to
|
|
* orphan this inode but crashed before it could commit the
|
|
* unlink. */
|
|
spin_lock(&oi->ip_lock);
|
|
oi->ip_flags &= ~OCFS2_INODE_MAYBE_ORPHANED;
|
|
spin_unlock(&oi->ip_lock);
|
|
|
|
bail_add:
|
|
dentry->d_op = &ocfs2_dentry_ops;
|
|
ret = d_splice_alias(inode, dentry);
|
|
|
|
if (inode) {
|
|
/*
|
|
* If d_splice_alias() finds a DCACHE_DISCONNECTED
|
|
* dentry, it will d_move() it on top of ourse. The
|
|
* return value will indicate this however, so in
|
|
* those cases, we switch them around for the locking
|
|
* code.
|
|
*
|
|
* NOTE: This dentry already has ->d_op set from
|
|
* ocfs2_get_parent() and ocfs2_get_dentry()
|
|
*/
|
|
if (ret)
|
|
dentry = ret;
|
|
|
|
status = ocfs2_dentry_attach_lock(dentry, inode,
|
|
OCFS2_I(dir)->ip_blkno);
|
|
if (status) {
|
|
mlog_errno(status);
|
|
ret = ERR_PTR(status);
|
|
goto bail_unlock;
|
|
}
|
|
}
|
|
|
|
bail_unlock:
|
|
/* Don't drop the cluster lock until *after* the d_add --
|
|
* unlink on another node will message us to remove that
|
|
* dentry under this lock so otherwise we can race this with
|
|
* the vote thread and have a stale dentry. */
|
|
ocfs2_meta_unlock(dir, 0);
|
|
|
|
bail:
|
|
if (dirent_bh)
|
|
brelse(dirent_bh);
|
|
|
|
mlog_exit_ptr(ret);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int ocfs2_fill_new_dir(struct ocfs2_super *osb,
|
|
handle_t *handle,
|
|
struct inode *parent,
|
|
struct inode *inode,
|
|
struct buffer_head *fe_bh,
|
|
struct ocfs2_alloc_context *data_ac)
|
|
{
|
|
int status;
|
|
struct buffer_head *new_bh = NULL;
|
|
struct ocfs2_dir_entry *de = NULL;
|
|
|
|
mlog_entry_void();
|
|
|
|
status = ocfs2_do_extend_dir(osb->sb, handle, inode, fe_bh,
|
|
data_ac, NULL, &new_bh);
|
|
if (status < 0) {
|
|
mlog_errno(status);
|
|
goto bail;
|
|
}
|
|
|
|
ocfs2_set_new_buffer_uptodate(inode, new_bh);
|
|
|
|
status = ocfs2_journal_access(handle, inode, new_bh,
|
|
OCFS2_JOURNAL_ACCESS_CREATE);
|
|
if (status < 0) {
|
|
mlog_errno(status);
|
|
goto bail;
|
|
}
|
|
memset(new_bh->b_data, 0, osb->sb->s_blocksize);
|
|
|
|
de = (struct ocfs2_dir_entry *) new_bh->b_data;
|
|
de->inode = cpu_to_le64(OCFS2_I(inode)->ip_blkno);
|
|
de->name_len = 1;
|
|
de->rec_len =
|
|
cpu_to_le16(OCFS2_DIR_REC_LEN(de->name_len));
|
|
strcpy(de->name, ".");
|
|
ocfs2_set_de_type(de, S_IFDIR);
|
|
de = (struct ocfs2_dir_entry *) ((char *)de + le16_to_cpu(de->rec_len));
|
|
de->inode = cpu_to_le64(OCFS2_I(parent)->ip_blkno);
|
|
de->rec_len = cpu_to_le16(inode->i_sb->s_blocksize -
|
|
OCFS2_DIR_REC_LEN(1));
|
|
de->name_len = 2;
|
|
strcpy(de->name, "..");
|
|
ocfs2_set_de_type(de, S_IFDIR);
|
|
|
|
status = ocfs2_journal_dirty(handle, new_bh);
|
|
if (status < 0) {
|
|
mlog_errno(status);
|
|
goto bail;
|
|
}
|
|
|
|
i_size_write(inode, inode->i_sb->s_blocksize);
|
|
inode->i_nlink = 2;
|
|
inode->i_blocks = ocfs2_align_bytes_to_sectors(inode->i_sb->s_blocksize);
|
|
status = ocfs2_mark_inode_dirty(handle, inode, fe_bh);
|
|
if (status < 0) {
|
|
mlog_errno(status);
|
|
goto bail;
|
|
}
|
|
|
|
status = 0;
|
|
bail:
|
|
if (new_bh)
|
|
brelse(new_bh);
|
|
|
|
mlog_exit(status);
|
|
return status;
|
|
}
|
|
|
|
static int ocfs2_mknod(struct inode *dir,
|
|
struct dentry *dentry,
|
|
int mode,
|
|
dev_t dev)
|
|
{
|
|
int status = 0;
|
|
struct buffer_head *parent_fe_bh = NULL;
|
|
handle_t *handle = NULL;
|
|
struct ocfs2_super *osb;
|
|
struct ocfs2_dinode *dirfe;
|
|
struct buffer_head *new_fe_bh = NULL;
|
|
struct buffer_head *de_bh = NULL;
|
|
struct inode *inode = NULL;
|
|
struct ocfs2_alloc_context *inode_ac = NULL;
|
|
struct ocfs2_alloc_context *data_ac = NULL;
|
|
|
|
mlog_entry("(0x%p, 0x%p, %d, %lu, '%.*s')\n", dir, dentry, mode,
|
|
(unsigned long)dev, dentry->d_name.len,
|
|
dentry->d_name.name);
|
|
|
|
/* get our super block */
|
|
osb = OCFS2_SB(dir->i_sb);
|
|
|
|
status = ocfs2_meta_lock(dir, &parent_fe_bh, 1);
|
|
if (status < 0) {
|
|
if (status != -ENOENT)
|
|
mlog_errno(status);
|
|
return status;
|
|
}
|
|
|
|
if (S_ISDIR(mode) && (dir->i_nlink >= OCFS2_LINK_MAX)) {
|
|
status = -EMLINK;
|
|
goto leave;
|
|
}
|
|
|
|
dirfe = (struct ocfs2_dinode *) parent_fe_bh->b_data;
|
|
if (!dirfe->i_links_count) {
|
|
/* can't make a file in a deleted directory. */
|
|
status = -ENOENT;
|
|
goto leave;
|
|
}
|
|
|
|
status = ocfs2_check_dir_for_entry(dir, dentry->d_name.name,
|
|
dentry->d_name.len);
|
|
if (status)
|
|
goto leave;
|
|
|
|
/* get a spot inside the dir. */
|
|
status = ocfs2_prepare_dir_for_insert(osb, dir, parent_fe_bh,
|
|
dentry->d_name.name,
|
|
dentry->d_name.len, &de_bh);
|
|
if (status < 0) {
|
|
mlog_errno(status);
|
|
goto leave;
|
|
}
|
|
|
|
/* reserve an inode spot */
|
|
status = ocfs2_reserve_new_inode(osb, &inode_ac);
|
|
if (status < 0) {
|
|
if (status != -ENOSPC)
|
|
mlog_errno(status);
|
|
goto leave;
|
|
}
|
|
|
|
/* are we making a directory? If so, reserve a cluster for his
|
|
* 1st extent. */
|
|
if (S_ISDIR(mode)) {
|
|
status = ocfs2_reserve_clusters(osb, 1, &data_ac);
|
|
if (status < 0) {
|
|
if (status != -ENOSPC)
|
|
mlog_errno(status);
|
|
goto leave;
|
|
}
|
|
}
|
|
|
|
handle = ocfs2_start_trans(osb, OCFS2_MKNOD_CREDITS);
|
|
if (IS_ERR(handle)) {
|
|
status = PTR_ERR(handle);
|
|
handle = NULL;
|
|
mlog_errno(status);
|
|
goto leave;
|
|
}
|
|
|
|
/* do the real work now. */
|
|
status = ocfs2_mknod_locked(osb, dir, dentry, mode, dev,
|
|
&new_fe_bh, parent_fe_bh, handle,
|
|
&inode, inode_ac);
|
|
if (status < 0) {
|
|
mlog_errno(status);
|
|
goto leave;
|
|
}
|
|
|
|
if (S_ISDIR(mode)) {
|
|
status = ocfs2_fill_new_dir(osb, handle, dir, inode,
|
|
new_fe_bh, data_ac);
|
|
if (status < 0) {
|
|
mlog_errno(status);
|
|
goto leave;
|
|
}
|
|
|
|
status = ocfs2_journal_access(handle, dir, parent_fe_bh,
|
|
OCFS2_JOURNAL_ACCESS_WRITE);
|
|
if (status < 0) {
|
|
mlog_errno(status);
|
|
goto leave;
|
|
}
|
|
le16_add_cpu(&dirfe->i_links_count, 1);
|
|
status = ocfs2_journal_dirty(handle, parent_fe_bh);
|
|
if (status < 0) {
|
|
mlog_errno(status);
|
|
goto leave;
|
|
}
|
|
inc_nlink(dir);
|
|
}
|
|
|
|
status = ocfs2_add_entry(handle, dentry, inode,
|
|
OCFS2_I(inode)->ip_blkno, parent_fe_bh,
|
|
de_bh);
|
|
if (status < 0) {
|
|
mlog_errno(status);
|
|
goto leave;
|
|
}
|
|
|
|
status = ocfs2_dentry_attach_lock(dentry, inode,
|
|
OCFS2_I(dir)->ip_blkno);
|
|
if (status) {
|
|
mlog_errno(status);
|
|
goto leave;
|
|
}
|
|
|
|
insert_inode_hash(inode);
|
|
dentry->d_op = &ocfs2_dentry_ops;
|
|
d_instantiate(dentry, inode);
|
|
status = 0;
|
|
leave:
|
|
if (handle)
|
|
ocfs2_commit_trans(osb, handle);
|
|
|
|
ocfs2_meta_unlock(dir, 1);
|
|
|
|
if (status == -ENOSPC)
|
|
mlog(0, "Disk is full\n");
|
|
|
|
if (new_fe_bh)
|
|
brelse(new_fe_bh);
|
|
|
|
if (de_bh)
|
|
brelse(de_bh);
|
|
|
|
if (parent_fe_bh)
|
|
brelse(parent_fe_bh);
|
|
|
|
if ((status < 0) && inode)
|
|
iput(inode);
|
|
|
|
if (inode_ac)
|
|
ocfs2_free_alloc_context(inode_ac);
|
|
|
|
if (data_ac)
|
|
ocfs2_free_alloc_context(data_ac);
|
|
|
|
mlog_exit(status);
|
|
|
|
return status;
|
|
}
|
|
|
|
static int ocfs2_mknod_locked(struct ocfs2_super *osb,
|
|
struct inode *dir,
|
|
struct dentry *dentry, int mode,
|
|
dev_t dev,
|
|
struct buffer_head **new_fe_bh,
|
|
struct buffer_head *parent_fe_bh,
|
|
handle_t *handle,
|
|
struct inode **ret_inode,
|
|
struct ocfs2_alloc_context *inode_ac)
|
|
{
|
|
int status = 0;
|
|
struct ocfs2_dinode *fe = NULL;
|
|
struct ocfs2_extent_list *fel;
|
|
u64 fe_blkno = 0;
|
|
u16 suballoc_bit;
|
|
struct inode *inode = NULL;
|
|
|
|
mlog_entry("(0x%p, 0x%p, %d, %lu, '%.*s')\n", dir, dentry, mode,
|
|
(unsigned long)dev, dentry->d_name.len,
|
|
dentry->d_name.name);
|
|
|
|
*new_fe_bh = NULL;
|
|
*ret_inode = NULL;
|
|
|
|
status = ocfs2_claim_new_inode(osb, handle, inode_ac, &suballoc_bit,
|
|
&fe_blkno);
|
|
if (status < 0) {
|
|
mlog_errno(status);
|
|
goto leave;
|
|
}
|
|
|
|
inode = new_inode(dir->i_sb);
|
|
if (IS_ERR(inode)) {
|
|
status = PTR_ERR(inode);
|
|
mlog(ML_ERROR, "new_inode failed!\n");
|
|
goto leave;
|
|
}
|
|
|
|
/* populate as many fields early on as possible - many of
|
|
* these are used by the support functions here and in
|
|
* callers. */
|
|
inode->i_ino = ino_from_blkno(osb->sb, fe_blkno);
|
|
OCFS2_I(inode)->ip_blkno = fe_blkno;
|
|
if (S_ISDIR(mode))
|
|
inode->i_nlink = 2;
|
|
else
|
|
inode->i_nlink = 1;
|
|
inode->i_mode = mode;
|
|
spin_lock(&osb->osb_lock);
|
|
inode->i_generation = osb->s_next_generation++;
|
|
spin_unlock(&osb->osb_lock);
|
|
|
|
*new_fe_bh = sb_getblk(osb->sb, fe_blkno);
|
|
if (!*new_fe_bh) {
|
|
status = -EIO;
|
|
mlog_errno(status);
|
|
goto leave;
|
|
}
|
|
ocfs2_set_new_buffer_uptodate(inode, *new_fe_bh);
|
|
|
|
status = ocfs2_journal_access(handle, inode, *new_fe_bh,
|
|
OCFS2_JOURNAL_ACCESS_CREATE);
|
|
if (status < 0) {
|
|
mlog_errno(status);
|
|
goto leave;
|
|
}
|
|
|
|
fe = (struct ocfs2_dinode *) (*new_fe_bh)->b_data;
|
|
memset(fe, 0, osb->sb->s_blocksize);
|
|
|
|
fe->i_generation = cpu_to_le32(inode->i_generation);
|
|
fe->i_fs_generation = cpu_to_le32(osb->fs_generation);
|
|
fe->i_blkno = cpu_to_le64(fe_blkno);
|
|
fe->i_suballoc_bit = cpu_to_le16(suballoc_bit);
|
|
fe->i_suballoc_slot = cpu_to_le16(osb->slot_num);
|
|
fe->i_uid = cpu_to_le32(current->fsuid);
|
|
if (dir->i_mode & S_ISGID) {
|
|
fe->i_gid = cpu_to_le32(dir->i_gid);
|
|
if (S_ISDIR(mode))
|
|
mode |= S_ISGID;
|
|
} else
|
|
fe->i_gid = cpu_to_le32(current->fsgid);
|
|
fe->i_mode = cpu_to_le16(mode);
|
|
if (S_ISCHR(mode) || S_ISBLK(mode))
|
|
fe->id1.dev1.i_rdev = cpu_to_le64(huge_encode_dev(dev));
|
|
|
|
fe->i_links_count = cpu_to_le16(inode->i_nlink);
|
|
|
|
fe->i_last_eb_blk = 0;
|
|
strcpy(fe->i_signature, OCFS2_INODE_SIGNATURE);
|
|
le32_add_cpu(&fe->i_flags, OCFS2_VALID_FL);
|
|
fe->i_atime = fe->i_ctime = fe->i_mtime =
|
|
cpu_to_le64(CURRENT_TIME.tv_sec);
|
|
fe->i_mtime_nsec = fe->i_ctime_nsec = fe->i_atime_nsec =
|
|
cpu_to_le32(CURRENT_TIME.tv_nsec);
|
|
fe->i_dtime = 0;
|
|
|
|
fel = &fe->id2.i_list;
|
|
fel->l_tree_depth = 0;
|
|
fel->l_next_free_rec = 0;
|
|
fel->l_count = cpu_to_le16(ocfs2_extent_recs_per_inode(osb->sb));
|
|
|
|
status = ocfs2_journal_dirty(handle, *new_fe_bh);
|
|
if (status < 0) {
|
|
mlog_errno(status);
|
|
goto leave;
|
|
}
|
|
|
|
if (ocfs2_populate_inode(inode, fe, 1) < 0) {
|
|
mlog(ML_ERROR, "populate inode failed! bh->b_blocknr=%llu, "
|
|
"i_blkno=%llu, i_ino=%lu\n",
|
|
(unsigned long long) (*new_fe_bh)->b_blocknr,
|
|
(unsigned long long)fe->i_blkno, inode->i_ino);
|
|
BUG();
|
|
}
|
|
|
|
ocfs2_inode_set_new(osb, inode);
|
|
if (!ocfs2_mount_local(osb)) {
|
|
status = ocfs2_create_new_inode_locks(inode);
|
|
if (status < 0)
|
|
mlog_errno(status);
|
|
}
|
|
|
|
status = 0; /* error in ocfs2_create_new_inode_locks is not
|
|
* critical */
|
|
|
|
*ret_inode = inode;
|
|
leave:
|
|
if (status < 0) {
|
|
if (*new_fe_bh) {
|
|
brelse(*new_fe_bh);
|
|
*new_fe_bh = NULL;
|
|
}
|
|
if (inode)
|
|
iput(inode);
|
|
}
|
|
|
|
mlog_exit(status);
|
|
return status;
|
|
}
|
|
|
|
static int ocfs2_mkdir(struct inode *dir,
|
|
struct dentry *dentry,
|
|
int mode)
|
|
{
|
|
int ret;
|
|
|
|
mlog_entry("(0x%p, 0x%p, %d, '%.*s')\n", dir, dentry, mode,
|
|
dentry->d_name.len, dentry->d_name.name);
|
|
ret = ocfs2_mknod(dir, dentry, mode | S_IFDIR, 0);
|
|
mlog_exit(ret);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int ocfs2_create(struct inode *dir,
|
|
struct dentry *dentry,
|
|
int mode,
|
|
struct nameidata *nd)
|
|
{
|
|
int ret;
|
|
|
|
mlog_entry("(0x%p, 0x%p, %d, '%.*s')\n", dir, dentry, mode,
|
|
dentry->d_name.len, dentry->d_name.name);
|
|
ret = ocfs2_mknod(dir, dentry, mode | S_IFREG, 0);
|
|
mlog_exit(ret);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int ocfs2_link(struct dentry *old_dentry,
|
|
struct inode *dir,
|
|
struct dentry *dentry)
|
|
{
|
|
handle_t *handle;
|
|
struct inode *inode = old_dentry->d_inode;
|
|
int err;
|
|
struct buffer_head *fe_bh = NULL;
|
|
struct buffer_head *parent_fe_bh = NULL;
|
|
struct buffer_head *de_bh = NULL;
|
|
struct ocfs2_dinode *fe = NULL;
|
|
struct ocfs2_super *osb = OCFS2_SB(dir->i_sb);
|
|
|
|
mlog_entry("(inode=%lu, old='%.*s' new='%.*s')\n", inode->i_ino,
|
|
old_dentry->d_name.len, old_dentry->d_name.name,
|
|
dentry->d_name.len, dentry->d_name.name);
|
|
|
|
if (S_ISDIR(inode->i_mode))
|
|
return -EPERM;
|
|
|
|
err = ocfs2_meta_lock(dir, &parent_fe_bh, 1);
|
|
if (err < 0) {
|
|
if (err != -ENOENT)
|
|
mlog_errno(err);
|
|
return err;
|
|
}
|
|
|
|
if (!dir->i_nlink) {
|
|
err = -ENOENT;
|
|
goto out;
|
|
}
|
|
|
|
err = ocfs2_check_dir_for_entry(dir, dentry->d_name.name,
|
|
dentry->d_name.len);
|
|
if (err)
|
|
goto out;
|
|
|
|
err = ocfs2_prepare_dir_for_insert(osb, dir, parent_fe_bh,
|
|
dentry->d_name.name,
|
|
dentry->d_name.len, &de_bh);
|
|
if (err < 0) {
|
|
mlog_errno(err);
|
|
goto out;
|
|
}
|
|
|
|
err = ocfs2_meta_lock(inode, &fe_bh, 1);
|
|
if (err < 0) {
|
|
if (err != -ENOENT)
|
|
mlog_errno(err);
|
|
goto out;
|
|
}
|
|
|
|
fe = (struct ocfs2_dinode *) fe_bh->b_data;
|
|
if (le16_to_cpu(fe->i_links_count) >= OCFS2_LINK_MAX) {
|
|
err = -EMLINK;
|
|
goto out_unlock_inode;
|
|
}
|
|
|
|
handle = ocfs2_start_trans(osb, OCFS2_LINK_CREDITS);
|
|
if (IS_ERR(handle)) {
|
|
err = PTR_ERR(handle);
|
|
handle = NULL;
|
|
mlog_errno(err);
|
|
goto out_unlock_inode;
|
|
}
|
|
|
|
err = ocfs2_journal_access(handle, inode, fe_bh,
|
|
OCFS2_JOURNAL_ACCESS_WRITE);
|
|
if (err < 0) {
|
|
mlog_errno(err);
|
|
goto out_commit;
|
|
}
|
|
|
|
inc_nlink(inode);
|
|
inode->i_ctime = CURRENT_TIME;
|
|
fe->i_links_count = cpu_to_le16(inode->i_nlink);
|
|
fe->i_ctime = cpu_to_le64(inode->i_ctime.tv_sec);
|
|
fe->i_ctime_nsec = cpu_to_le32(inode->i_ctime.tv_nsec);
|
|
|
|
err = ocfs2_journal_dirty(handle, fe_bh);
|
|
if (err < 0) {
|
|
le16_add_cpu(&fe->i_links_count, -1);
|
|
drop_nlink(inode);
|
|
mlog_errno(err);
|
|
goto out_commit;
|
|
}
|
|
|
|
err = ocfs2_add_entry(handle, dentry, inode,
|
|
OCFS2_I(inode)->ip_blkno,
|
|
parent_fe_bh, de_bh);
|
|
if (err) {
|
|
le16_add_cpu(&fe->i_links_count, -1);
|
|
drop_nlink(inode);
|
|
mlog_errno(err);
|
|
goto out_commit;
|
|
}
|
|
|
|
err = ocfs2_dentry_attach_lock(dentry, inode, OCFS2_I(dir)->ip_blkno);
|
|
if (err) {
|
|
mlog_errno(err);
|
|
goto out_commit;
|
|
}
|
|
|
|
atomic_inc(&inode->i_count);
|
|
dentry->d_op = &ocfs2_dentry_ops;
|
|
d_instantiate(dentry, inode);
|
|
|
|
out_commit:
|
|
ocfs2_commit_trans(osb, handle);
|
|
out_unlock_inode:
|
|
ocfs2_meta_unlock(inode, 1);
|
|
|
|
out:
|
|
ocfs2_meta_unlock(dir, 1);
|
|
|
|
if (de_bh)
|
|
brelse(de_bh);
|
|
if (fe_bh)
|
|
brelse(fe_bh);
|
|
if (parent_fe_bh)
|
|
brelse(parent_fe_bh);
|
|
|
|
mlog_exit(err);
|
|
|
|
return err;
|
|
}
|
|
|
|
/*
|
|
* Takes and drops an exclusive lock on the given dentry. This will
|
|
* force other nodes to drop it.
|
|
*/
|
|
static int ocfs2_remote_dentry_delete(struct dentry *dentry)
|
|
{
|
|
int ret;
|
|
|
|
ret = ocfs2_dentry_lock(dentry, 1);
|
|
if (ret)
|
|
mlog_errno(ret);
|
|
else
|
|
ocfs2_dentry_unlock(dentry, 1);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static inline int inode_is_unlinkable(struct inode *inode)
|
|
{
|
|
if (S_ISDIR(inode->i_mode)) {
|
|
if (inode->i_nlink == 2)
|
|
return 1;
|
|
return 0;
|
|
}
|
|
|
|
if (inode->i_nlink == 1)
|
|
return 1;
|
|
return 0;
|
|
}
|
|
|
|
static int ocfs2_unlink(struct inode *dir,
|
|
struct dentry *dentry)
|
|
{
|
|
int status;
|
|
int child_locked = 0;
|
|
struct inode *inode = dentry->d_inode;
|
|
struct inode *orphan_dir = NULL;
|
|
struct ocfs2_super *osb = OCFS2_SB(dir->i_sb);
|
|
u64 blkno;
|
|
struct ocfs2_dinode *fe = NULL;
|
|
struct buffer_head *fe_bh = NULL;
|
|
struct buffer_head *parent_node_bh = NULL;
|
|
handle_t *handle = NULL;
|
|
struct ocfs2_dir_entry *dirent = NULL;
|
|
struct buffer_head *dirent_bh = NULL;
|
|
char orphan_name[OCFS2_ORPHAN_NAMELEN + 1];
|
|
struct buffer_head *orphan_entry_bh = NULL;
|
|
|
|
mlog_entry("(0x%p, 0x%p, '%.*s')\n", dir, dentry,
|
|
dentry->d_name.len, dentry->d_name.name);
|
|
|
|
BUG_ON(dentry->d_parent->d_inode != dir);
|
|
|
|
mlog(0, "ino = %llu\n", (unsigned long long)OCFS2_I(inode)->ip_blkno);
|
|
|
|
if (inode == osb->root_inode) {
|
|
mlog(0, "Cannot delete the root directory\n");
|
|
return -EPERM;
|
|
}
|
|
|
|
status = ocfs2_meta_lock(dir, &parent_node_bh, 1);
|
|
if (status < 0) {
|
|
if (status != -ENOENT)
|
|
mlog_errno(status);
|
|
return status;
|
|
}
|
|
|
|
status = ocfs2_find_files_on_disk(dentry->d_name.name,
|
|
dentry->d_name.len, &blkno,
|
|
dir, &dirent_bh, &dirent);
|
|
if (status < 0) {
|
|
if (status != -ENOENT)
|
|
mlog_errno(status);
|
|
goto leave;
|
|
}
|
|
|
|
if (OCFS2_I(inode)->ip_blkno != blkno) {
|
|
status = -ENOENT;
|
|
|
|
mlog(0, "ip_blkno %llu != dirent blkno %llu ip_flags = %x\n",
|
|
(unsigned long long)OCFS2_I(inode)->ip_blkno,
|
|
(unsigned long long)blkno, OCFS2_I(inode)->ip_flags);
|
|
goto leave;
|
|
}
|
|
|
|
status = ocfs2_meta_lock(inode, &fe_bh, 1);
|
|
if (status < 0) {
|
|
if (status != -ENOENT)
|
|
mlog_errno(status);
|
|
goto leave;
|
|
}
|
|
child_locked = 1;
|
|
|
|
if (S_ISDIR(inode->i_mode)) {
|
|
if (!ocfs2_empty_dir(inode)) {
|
|
status = -ENOTEMPTY;
|
|
goto leave;
|
|
} else if (inode->i_nlink != 2) {
|
|
status = -ENOTEMPTY;
|
|
goto leave;
|
|
}
|
|
}
|
|
|
|
status = ocfs2_remote_dentry_delete(dentry);
|
|
if (status < 0) {
|
|
/* This vote should succeed under all normal
|
|
* circumstances. */
|
|
mlog_errno(status);
|
|
goto leave;
|
|
}
|
|
|
|
if (inode_is_unlinkable(inode)) {
|
|
status = ocfs2_prepare_orphan_dir(osb, &orphan_dir, inode,
|
|
orphan_name,
|
|
&orphan_entry_bh);
|
|
if (status < 0) {
|
|
mlog_errno(status);
|
|
goto leave;
|
|
}
|
|
}
|
|
|
|
handle = ocfs2_start_trans(osb, OCFS2_UNLINK_CREDITS);
|
|
if (IS_ERR(handle)) {
|
|
status = PTR_ERR(handle);
|
|
handle = NULL;
|
|
mlog_errno(status);
|
|
goto leave;
|
|
}
|
|
|
|
status = ocfs2_journal_access(handle, inode, fe_bh,
|
|
OCFS2_JOURNAL_ACCESS_WRITE);
|
|
if (status < 0) {
|
|
mlog_errno(status);
|
|
goto leave;
|
|
}
|
|
|
|
fe = (struct ocfs2_dinode *) fe_bh->b_data;
|
|
|
|
if (inode_is_unlinkable(inode)) {
|
|
status = ocfs2_orphan_add(osb, handle, inode, fe, orphan_name,
|
|
orphan_entry_bh, orphan_dir);
|
|
if (status < 0) {
|
|
mlog_errno(status);
|
|
goto leave;
|
|
}
|
|
}
|
|
|
|
/* delete the name from the parent dir */
|
|
status = ocfs2_delete_entry(handle, dir, dirent, dirent_bh);
|
|
if (status < 0) {
|
|
mlog_errno(status);
|
|
goto leave;
|
|
}
|
|
|
|
if (S_ISDIR(inode->i_mode))
|
|
drop_nlink(inode);
|
|
drop_nlink(inode);
|
|
fe->i_links_count = cpu_to_le16(inode->i_nlink);
|
|
|
|
status = ocfs2_journal_dirty(handle, fe_bh);
|
|
if (status < 0) {
|
|
mlog_errno(status);
|
|
goto leave;
|
|
}
|
|
|
|
dir->i_ctime = dir->i_mtime = CURRENT_TIME;
|
|
if (S_ISDIR(inode->i_mode))
|
|
drop_nlink(dir);
|
|
|
|
status = ocfs2_mark_inode_dirty(handle, dir, parent_node_bh);
|
|
if (status < 0) {
|
|
mlog_errno(status);
|
|
if (S_ISDIR(inode->i_mode))
|
|
inc_nlink(dir);
|
|
}
|
|
|
|
leave:
|
|
if (handle)
|
|
ocfs2_commit_trans(osb, handle);
|
|
|
|
if (child_locked)
|
|
ocfs2_meta_unlock(inode, 1);
|
|
|
|
ocfs2_meta_unlock(dir, 1);
|
|
|
|
if (orphan_dir) {
|
|
/* This was locked for us in ocfs2_prepare_orphan_dir() */
|
|
ocfs2_meta_unlock(orphan_dir, 1);
|
|
mutex_unlock(&orphan_dir->i_mutex);
|
|
iput(orphan_dir);
|
|
}
|
|
|
|
if (fe_bh)
|
|
brelse(fe_bh);
|
|
|
|
if (dirent_bh)
|
|
brelse(dirent_bh);
|
|
|
|
if (parent_node_bh)
|
|
brelse(parent_node_bh);
|
|
|
|
if (orphan_entry_bh)
|
|
brelse(orphan_entry_bh);
|
|
|
|
mlog_exit(status);
|
|
|
|
return status;
|
|
}
|
|
|
|
/*
|
|
* The only place this should be used is rename!
|
|
* if they have the same id, then the 1st one is the only one locked.
|
|
*/
|
|
static int ocfs2_double_lock(struct ocfs2_super *osb,
|
|
struct buffer_head **bh1,
|
|
struct inode *inode1,
|
|
struct buffer_head **bh2,
|
|
struct inode *inode2)
|
|
{
|
|
int status;
|
|
struct ocfs2_inode_info *oi1 = OCFS2_I(inode1);
|
|
struct ocfs2_inode_info *oi2 = OCFS2_I(inode2);
|
|
struct buffer_head **tmpbh;
|
|
struct inode *tmpinode;
|
|
|
|
mlog_entry("(inode1 = %llu, inode2 = %llu)\n",
|
|
(unsigned long long)oi1->ip_blkno,
|
|
(unsigned long long)oi2->ip_blkno);
|
|
|
|
if (*bh1)
|
|
*bh1 = NULL;
|
|
if (*bh2)
|
|
*bh2 = NULL;
|
|
|
|
/* we always want to lock the one with the lower lockid first. */
|
|
if (oi1->ip_blkno != oi2->ip_blkno) {
|
|
if (oi1->ip_blkno < oi2->ip_blkno) {
|
|
/* switch id1 and id2 around */
|
|
mlog(0, "switching them around...\n");
|
|
tmpbh = bh2;
|
|
bh2 = bh1;
|
|
bh1 = tmpbh;
|
|
|
|
tmpinode = inode2;
|
|
inode2 = inode1;
|
|
inode1 = tmpinode;
|
|
}
|
|
/* lock id2 */
|
|
status = ocfs2_meta_lock(inode2, bh2, 1);
|
|
if (status < 0) {
|
|
if (status != -ENOENT)
|
|
mlog_errno(status);
|
|
goto bail;
|
|
}
|
|
}
|
|
|
|
/* lock id1 */
|
|
status = ocfs2_meta_lock(inode1, bh1, 1);
|
|
if (status < 0) {
|
|
/*
|
|
* An error return must mean that no cluster locks
|
|
* were held on function exit.
|
|
*/
|
|
if (oi1->ip_blkno != oi2->ip_blkno)
|
|
ocfs2_meta_unlock(inode2, 1);
|
|
|
|
if (status != -ENOENT)
|
|
mlog_errno(status);
|
|
}
|
|
|
|
bail:
|
|
mlog_exit(status);
|
|
return status;
|
|
}
|
|
|
|
static void ocfs2_double_unlock(struct inode *inode1, struct inode *inode2)
|
|
{
|
|
ocfs2_meta_unlock(inode1, 1);
|
|
|
|
if (inode1 != inode2)
|
|
ocfs2_meta_unlock(inode2, 1);
|
|
}
|
|
|
|
#define PARENT_INO(buffer) \
|
|
((struct ocfs2_dir_entry *) \
|
|
((char *)buffer + \
|
|
le16_to_cpu(((struct ocfs2_dir_entry *)buffer)->rec_len)))->inode
|
|
|
|
static int ocfs2_rename(struct inode *old_dir,
|
|
struct dentry *old_dentry,
|
|
struct inode *new_dir,
|
|
struct dentry *new_dentry)
|
|
{
|
|
int status = 0, rename_lock = 0, parents_locked = 0;
|
|
int old_child_locked = 0, new_child_locked = 0;
|
|
struct inode *old_inode = old_dentry->d_inode;
|
|
struct inode *new_inode = new_dentry->d_inode;
|
|
struct inode *orphan_dir = NULL;
|
|
struct ocfs2_dinode *newfe = NULL;
|
|
char orphan_name[OCFS2_ORPHAN_NAMELEN + 1];
|
|
struct buffer_head *orphan_entry_bh = NULL;
|
|
struct buffer_head *newfe_bh = NULL;
|
|
struct buffer_head *old_inode_bh = NULL;
|
|
struct buffer_head *insert_entry_bh = NULL;
|
|
struct ocfs2_super *osb = NULL;
|
|
u64 newfe_blkno;
|
|
handle_t *handle = NULL;
|
|
struct buffer_head *old_dir_bh = NULL;
|
|
struct buffer_head *new_dir_bh = NULL;
|
|
struct ocfs2_dir_entry *old_de = NULL, *new_de = NULL; // dirent for old_dentry
|
|
// and new_dentry
|
|
struct buffer_head *new_de_bh = NULL, *old_de_bh = NULL; // bhs for above
|
|
struct buffer_head *old_inode_de_bh = NULL; // if old_dentry is a dir,
|
|
// this is the 1st dirent bh
|
|
nlink_t old_dir_nlink = old_dir->i_nlink;
|
|
|
|
/* At some point it might be nice to break this function up a
|
|
* bit. */
|
|
|
|
mlog_entry("(0x%p, 0x%p, 0x%p, 0x%p, from='%.*s' to='%.*s')\n",
|
|
old_dir, old_dentry, new_dir, new_dentry,
|
|
old_dentry->d_name.len, old_dentry->d_name.name,
|
|
new_dentry->d_name.len, new_dentry->d_name.name);
|
|
|
|
osb = OCFS2_SB(old_dir->i_sb);
|
|
|
|
if (new_inode) {
|
|
if (!igrab(new_inode))
|
|
BUG();
|
|
}
|
|
|
|
/* Assume a directory hierarchy thusly:
|
|
* a/b/c
|
|
* a/d
|
|
* a,b,c, and d are all directories.
|
|
*
|
|
* from cwd of 'a' on both nodes:
|
|
* node1: mv b/c d
|
|
* node2: mv d b/c
|
|
*
|
|
* And that's why, just like the VFS, we need a file system
|
|
* rename lock. */
|
|
if (old_dentry != new_dentry) {
|
|
status = ocfs2_rename_lock(osb);
|
|
if (status < 0) {
|
|
mlog_errno(status);
|
|
goto bail;
|
|
}
|
|
rename_lock = 1;
|
|
}
|
|
|
|
/* if old and new are the same, this'll just do one lock. */
|
|
status = ocfs2_double_lock(osb, &old_dir_bh, old_dir,
|
|
&new_dir_bh, new_dir);
|
|
if (status < 0) {
|
|
mlog_errno(status);
|
|
goto bail;
|
|
}
|
|
parents_locked = 1;
|
|
|
|
/* make sure both dirs have bhs
|
|
* get an extra ref on old_dir_bh if old==new */
|
|
if (!new_dir_bh) {
|
|
if (old_dir_bh) {
|
|
new_dir_bh = old_dir_bh;
|
|
get_bh(new_dir_bh);
|
|
} else {
|
|
mlog(ML_ERROR, "no old_dir_bh!\n");
|
|
status = -EIO;
|
|
goto bail;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Aside from allowing a meta data update, the locking here
|
|
* also ensures that the vote thread on other nodes won't have
|
|
* to concurrently downconvert the inode and the dentry locks.
|
|
*/
|
|
status = ocfs2_meta_lock(old_inode, &old_inode_bh, 1);
|
|
if (status < 0) {
|
|
if (status != -ENOENT)
|
|
mlog_errno(status);
|
|
goto bail;
|
|
}
|
|
old_child_locked = 1;
|
|
|
|
status = ocfs2_remote_dentry_delete(old_dentry);
|
|
if (status < 0) {
|
|
mlog_errno(status);
|
|
goto bail;
|
|
}
|
|
|
|
if (S_ISDIR(old_inode->i_mode)) {
|
|
status = -EIO;
|
|
old_inode_de_bh = ocfs2_bread(old_inode, 0, &status, 0);
|
|
if (!old_inode_de_bh)
|
|
goto bail;
|
|
|
|
status = -EIO;
|
|
if (le64_to_cpu(PARENT_INO(old_inode_de_bh->b_data)) !=
|
|
OCFS2_I(old_dir)->ip_blkno)
|
|
goto bail;
|
|
status = -EMLINK;
|
|
if (!new_inode && new_dir!=old_dir &&
|
|
new_dir->i_nlink >= OCFS2_LINK_MAX)
|
|
goto bail;
|
|
}
|
|
|
|
status = -ENOENT;
|
|
old_de_bh = ocfs2_find_entry(old_dentry->d_name.name,
|
|
old_dentry->d_name.len,
|
|
old_dir, &old_de);
|
|
if (!old_de_bh)
|
|
goto bail;
|
|
|
|
/*
|
|
* Check for inode number is _not_ due to possible IO errors.
|
|
* We might rmdir the source, keep it as pwd of some process
|
|
* and merrily kill the link to whatever was created under the
|
|
* same name. Goodbye sticky bit ;-<
|
|
*/
|
|
if (le64_to_cpu(old_de->inode) != OCFS2_I(old_inode)->ip_blkno)
|
|
goto bail;
|
|
|
|
/* check if the target already exists (in which case we need
|
|
* to delete it */
|
|
status = ocfs2_find_files_on_disk(new_dentry->d_name.name,
|
|
new_dentry->d_name.len,
|
|
&newfe_blkno, new_dir, &new_de_bh,
|
|
&new_de);
|
|
/* The only error we allow here is -ENOENT because the new
|
|
* file not existing is perfectly valid. */
|
|
if ((status < 0) && (status != -ENOENT)) {
|
|
/* If we cannot find the file specified we should just */
|
|
/* return the error... */
|
|
mlog_errno(status);
|
|
goto bail;
|
|
}
|
|
|
|
if (!new_de && new_inode)
|
|
mlog(ML_ERROR, "inode %lu does not exist in it's parent "
|
|
"directory!", new_inode->i_ino);
|
|
|
|
/* In case we need to overwrite an existing file, we blow it
|
|
* away first */
|
|
if (new_de) {
|
|
/* VFS didn't think there existed an inode here, but
|
|
* someone else in the cluster must have raced our
|
|
* rename to create one. Today we error cleanly, in
|
|
* the future we should consider calling iget to build
|
|
* a new struct inode for this entry. */
|
|
if (!new_inode) {
|
|
status = -EACCES;
|
|
|
|
mlog(0, "We found an inode for name %.*s but VFS "
|
|
"didn't give us one.\n", new_dentry->d_name.len,
|
|
new_dentry->d_name.name);
|
|
goto bail;
|
|
}
|
|
|
|
if (OCFS2_I(new_inode)->ip_blkno != newfe_blkno) {
|
|
status = -EACCES;
|
|
|
|
mlog(0, "Inode %llu and dir %llu disagree. flags = %x\n",
|
|
(unsigned long long)OCFS2_I(new_inode)->ip_blkno,
|
|
(unsigned long long)newfe_blkno,
|
|
OCFS2_I(new_inode)->ip_flags);
|
|
goto bail;
|
|
}
|
|
|
|
status = ocfs2_meta_lock(new_inode, &newfe_bh, 1);
|
|
if (status < 0) {
|
|
if (status != -ENOENT)
|
|
mlog_errno(status);
|
|
goto bail;
|
|
}
|
|
new_child_locked = 1;
|
|
|
|
status = ocfs2_remote_dentry_delete(new_dentry);
|
|
if (status < 0) {
|
|
mlog_errno(status);
|
|
goto bail;
|
|
}
|
|
|
|
newfe = (struct ocfs2_dinode *) newfe_bh->b_data;
|
|
|
|
mlog(0, "aha rename over existing... new_de=%p new_blkno=%llu "
|
|
"newfebh=%p bhblocknr=%llu\n", new_de,
|
|
(unsigned long long)newfe_blkno, newfe_bh, newfe_bh ?
|
|
(unsigned long long)newfe_bh->b_blocknr : 0ULL);
|
|
|
|
if (S_ISDIR(new_inode->i_mode) || (new_inode->i_nlink == 1)) {
|
|
status = ocfs2_prepare_orphan_dir(osb, &orphan_dir,
|
|
new_inode,
|
|
orphan_name,
|
|
&orphan_entry_bh);
|
|
if (status < 0) {
|
|
mlog_errno(status);
|
|
goto bail;
|
|
}
|
|
}
|
|
} else {
|
|
BUG_ON(new_dentry->d_parent->d_inode != new_dir);
|
|
|
|
status = ocfs2_check_dir_for_entry(new_dir,
|
|
new_dentry->d_name.name,
|
|
new_dentry->d_name.len);
|
|
if (status)
|
|
goto bail;
|
|
|
|
status = ocfs2_prepare_dir_for_insert(osb, new_dir, new_dir_bh,
|
|
new_dentry->d_name.name,
|
|
new_dentry->d_name.len,
|
|
&insert_entry_bh);
|
|
if (status < 0) {
|
|
mlog_errno(status);
|
|
goto bail;
|
|
}
|
|
}
|
|
|
|
handle = ocfs2_start_trans(osb, OCFS2_RENAME_CREDITS);
|
|
if (IS_ERR(handle)) {
|
|
status = PTR_ERR(handle);
|
|
handle = NULL;
|
|
mlog_errno(status);
|
|
goto bail;
|
|
}
|
|
|
|
if (new_de) {
|
|
if (S_ISDIR(new_inode->i_mode)) {
|
|
if (!ocfs2_empty_dir(new_inode) ||
|
|
new_inode->i_nlink != 2) {
|
|
status = -ENOTEMPTY;
|
|
goto bail;
|
|
}
|
|
}
|
|
status = ocfs2_journal_access(handle, new_inode, newfe_bh,
|
|
OCFS2_JOURNAL_ACCESS_WRITE);
|
|
if (status < 0) {
|
|
mlog_errno(status);
|
|
goto bail;
|
|
}
|
|
|
|
if (S_ISDIR(new_inode->i_mode) ||
|
|
(newfe->i_links_count == cpu_to_le16(1))){
|
|
status = ocfs2_orphan_add(osb, handle, new_inode,
|
|
newfe, orphan_name,
|
|
orphan_entry_bh, orphan_dir);
|
|
if (status < 0) {
|
|
mlog_errno(status);
|
|
goto bail;
|
|
}
|
|
}
|
|
|
|
/* change the dirent to point to the correct inode */
|
|
status = ocfs2_journal_access(handle, new_dir, new_de_bh,
|
|
OCFS2_JOURNAL_ACCESS_WRITE);
|
|
if (status < 0) {
|
|
mlog_errno(status);
|
|
goto bail;
|
|
}
|
|
new_de->inode = cpu_to_le64(OCFS2_I(old_inode)->ip_blkno);
|
|
new_de->file_type = old_de->file_type;
|
|
new_dir->i_version++;
|
|
status = ocfs2_journal_dirty(handle, new_de_bh);
|
|
if (status < 0) {
|
|
mlog_errno(status);
|
|
goto bail;
|
|
}
|
|
|
|
if (S_ISDIR(new_inode->i_mode))
|
|
newfe->i_links_count = 0;
|
|
else
|
|
le16_add_cpu(&newfe->i_links_count, -1);
|
|
|
|
status = ocfs2_journal_dirty(handle, newfe_bh);
|
|
if (status < 0) {
|
|
mlog_errno(status);
|
|
goto bail;
|
|
}
|
|
} else {
|
|
/* if the name was not found in new_dir, add it now */
|
|
status = ocfs2_add_entry(handle, new_dentry, old_inode,
|
|
OCFS2_I(old_inode)->ip_blkno,
|
|
new_dir_bh, insert_entry_bh);
|
|
}
|
|
|
|
old_inode->i_ctime = CURRENT_TIME;
|
|
mark_inode_dirty(old_inode);
|
|
ocfs2_mark_inode_dirty(handle, old_inode, old_inode_bh);
|
|
|
|
/* now that the name has been added to new_dir, remove the old name */
|
|
status = ocfs2_delete_entry(handle, old_dir, old_de, old_de_bh);
|
|
if (status < 0) {
|
|
mlog_errno(status);
|
|
goto bail;
|
|
}
|
|
|
|
if (new_inode) {
|
|
new_inode->i_nlink--;
|
|
new_inode->i_ctime = CURRENT_TIME;
|
|
}
|
|
old_dir->i_ctime = old_dir->i_mtime = CURRENT_TIME;
|
|
if (old_inode_de_bh) {
|
|
status = ocfs2_journal_access(handle, old_inode,
|
|
old_inode_de_bh,
|
|
OCFS2_JOURNAL_ACCESS_WRITE);
|
|
PARENT_INO(old_inode_de_bh->b_data) =
|
|
cpu_to_le64(OCFS2_I(new_dir)->ip_blkno);
|
|
status = ocfs2_journal_dirty(handle, old_inode_de_bh);
|
|
old_dir->i_nlink--;
|
|
if (new_inode) {
|
|
new_inode->i_nlink--;
|
|
} else {
|
|
inc_nlink(new_dir);
|
|
mark_inode_dirty(new_dir);
|
|
}
|
|
}
|
|
mark_inode_dirty(old_dir);
|
|
ocfs2_mark_inode_dirty(handle, old_dir, old_dir_bh);
|
|
if (new_inode) {
|
|
mark_inode_dirty(new_inode);
|
|
ocfs2_mark_inode_dirty(handle, new_inode, newfe_bh);
|
|
}
|
|
|
|
if (old_dir != new_dir) {
|
|
/* Keep the same times on both directories.*/
|
|
new_dir->i_ctime = new_dir->i_mtime = old_dir->i_ctime;
|
|
|
|
/*
|
|
* This will also pick up the i_nlink change from the
|
|
* block above.
|
|
*/
|
|
ocfs2_mark_inode_dirty(handle, new_dir, new_dir_bh);
|
|
}
|
|
|
|
if (old_dir_nlink != old_dir->i_nlink) {
|
|
if (!old_dir_bh) {
|
|
mlog(ML_ERROR, "need to change nlink for old dir "
|
|
"%llu from %d to %d but bh is NULL!\n",
|
|
(unsigned long long)OCFS2_I(old_dir)->ip_blkno,
|
|
(int)old_dir_nlink, old_dir->i_nlink);
|
|
} else {
|
|
struct ocfs2_dinode *fe;
|
|
status = ocfs2_journal_access(handle, old_dir,
|
|
old_dir_bh,
|
|
OCFS2_JOURNAL_ACCESS_WRITE);
|
|
fe = (struct ocfs2_dinode *) old_dir_bh->b_data;
|
|
fe->i_links_count = cpu_to_le16(old_dir->i_nlink);
|
|
status = ocfs2_journal_dirty(handle, old_dir_bh);
|
|
}
|
|
}
|
|
|
|
ocfs2_dentry_move(old_dentry, new_dentry, old_dir, new_dir);
|
|
status = 0;
|
|
bail:
|
|
if (rename_lock)
|
|
ocfs2_rename_unlock(osb);
|
|
|
|
if (handle)
|
|
ocfs2_commit_trans(osb, handle);
|
|
|
|
if (parents_locked)
|
|
ocfs2_double_unlock(old_dir, new_dir);
|
|
|
|
if (old_child_locked)
|
|
ocfs2_meta_unlock(old_inode, 1);
|
|
|
|
if (new_child_locked)
|
|
ocfs2_meta_unlock(new_inode, 1);
|
|
|
|
if (orphan_dir) {
|
|
/* This was locked for us in ocfs2_prepare_orphan_dir() */
|
|
ocfs2_meta_unlock(orphan_dir, 1);
|
|
mutex_unlock(&orphan_dir->i_mutex);
|
|
iput(orphan_dir);
|
|
}
|
|
|
|
if (new_inode)
|
|
sync_mapping_buffers(old_inode->i_mapping);
|
|
|
|
if (new_inode)
|
|
iput(new_inode);
|
|
if (newfe_bh)
|
|
brelse(newfe_bh);
|
|
if (old_inode_bh)
|
|
brelse(old_inode_bh);
|
|
if (old_dir_bh)
|
|
brelse(old_dir_bh);
|
|
if (new_dir_bh)
|
|
brelse(new_dir_bh);
|
|
if (new_de_bh)
|
|
brelse(new_de_bh);
|
|
if (old_de_bh)
|
|
brelse(old_de_bh);
|
|
if (old_inode_de_bh)
|
|
brelse(old_inode_de_bh);
|
|
if (orphan_entry_bh)
|
|
brelse(orphan_entry_bh);
|
|
if (insert_entry_bh)
|
|
brelse(insert_entry_bh);
|
|
|
|
mlog_exit(status);
|
|
|
|
return status;
|
|
}
|
|
|
|
/*
|
|
* we expect i_size = strlen(symname). Copy symname into the file
|
|
* data, including the null terminator.
|
|
*/
|
|
static int ocfs2_create_symlink_data(struct ocfs2_super *osb,
|
|
handle_t *handle,
|
|
struct inode *inode,
|
|
const char *symname)
|
|
{
|
|
struct buffer_head **bhs = NULL;
|
|
const char *c;
|
|
struct super_block *sb = osb->sb;
|
|
u64 p_blkno;
|
|
int p_blocks;
|
|
int virtual, blocks, status, i, bytes_left;
|
|
|
|
bytes_left = i_size_read(inode) + 1;
|
|
/* we can't trust i_blocks because we're actually going to
|
|
* write i_size + 1 bytes. */
|
|
blocks = (bytes_left + sb->s_blocksize - 1) >> sb->s_blocksize_bits;
|
|
|
|
mlog_entry("i_blocks = %llu, i_size = %llu, blocks = %d\n",
|
|
(unsigned long long)inode->i_blocks,
|
|
i_size_read(inode), blocks);
|
|
|
|
/* Sanity check -- make sure we're going to fit. */
|
|
if (bytes_left >
|
|
ocfs2_clusters_to_bytes(sb, OCFS2_I(inode)->ip_clusters)) {
|
|
status = -EIO;
|
|
mlog_errno(status);
|
|
goto bail;
|
|
}
|
|
|
|
bhs = kcalloc(blocks, sizeof(struct buffer_head *), GFP_KERNEL);
|
|
if (!bhs) {
|
|
status = -ENOMEM;
|
|
mlog_errno(status);
|
|
goto bail;
|
|
}
|
|
|
|
status = ocfs2_extent_map_get_blocks(inode, 0, 1, &p_blkno,
|
|
&p_blocks);
|
|
if (status < 0) {
|
|
mlog_errno(status);
|
|
goto bail;
|
|
}
|
|
|
|
/* links can never be larger than one cluster so we know this
|
|
* is all going to be contiguous, but do a sanity check
|
|
* anyway. */
|
|
if ((p_blocks << sb->s_blocksize_bits) < bytes_left) {
|
|
status = -EIO;
|
|
mlog_errno(status);
|
|
goto bail;
|
|
}
|
|
|
|
virtual = 0;
|
|
while(bytes_left > 0) {
|
|
c = &symname[virtual * sb->s_blocksize];
|
|
|
|
bhs[virtual] = sb_getblk(sb, p_blkno);
|
|
if (!bhs[virtual]) {
|
|
status = -ENOMEM;
|
|
mlog_errno(status);
|
|
goto bail;
|
|
}
|
|
ocfs2_set_new_buffer_uptodate(inode, bhs[virtual]);
|
|
|
|
status = ocfs2_journal_access(handle, inode, bhs[virtual],
|
|
OCFS2_JOURNAL_ACCESS_CREATE);
|
|
if (status < 0) {
|
|
mlog_errno(status);
|
|
goto bail;
|
|
}
|
|
|
|
memset(bhs[virtual]->b_data, 0, sb->s_blocksize);
|
|
|
|
memcpy(bhs[virtual]->b_data, c,
|
|
(bytes_left > sb->s_blocksize) ? sb->s_blocksize :
|
|
bytes_left);
|
|
|
|
status = ocfs2_journal_dirty(handle, bhs[virtual]);
|
|
if (status < 0) {
|
|
mlog_errno(status);
|
|
goto bail;
|
|
}
|
|
|
|
virtual++;
|
|
p_blkno++;
|
|
bytes_left -= sb->s_blocksize;
|
|
}
|
|
|
|
status = 0;
|
|
bail:
|
|
|
|
if (bhs) {
|
|
for(i = 0; i < blocks; i++)
|
|
if (bhs[i])
|
|
brelse(bhs[i]);
|
|
kfree(bhs);
|
|
}
|
|
|
|
mlog_exit(status);
|
|
return status;
|
|
}
|
|
|
|
static int ocfs2_symlink(struct inode *dir,
|
|
struct dentry *dentry,
|
|
const char *symname)
|
|
{
|
|
int status, l, credits;
|
|
u64 newsize;
|
|
struct ocfs2_super *osb = NULL;
|
|
struct inode *inode = NULL;
|
|
struct super_block *sb;
|
|
struct buffer_head *new_fe_bh = NULL;
|
|
struct buffer_head *de_bh = NULL;
|
|
struct buffer_head *parent_fe_bh = NULL;
|
|
struct ocfs2_dinode *fe = NULL;
|
|
struct ocfs2_dinode *dirfe;
|
|
handle_t *handle = NULL;
|
|
struct ocfs2_alloc_context *inode_ac = NULL;
|
|
struct ocfs2_alloc_context *data_ac = NULL;
|
|
|
|
mlog_entry("(0x%p, 0x%p, symname='%s' actual='%.*s')\n", dir,
|
|
dentry, symname, dentry->d_name.len, dentry->d_name.name);
|
|
|
|
sb = dir->i_sb;
|
|
osb = OCFS2_SB(sb);
|
|
|
|
l = strlen(symname) + 1;
|
|
|
|
credits = ocfs2_calc_symlink_credits(sb);
|
|
|
|
/* lock the parent directory */
|
|
status = ocfs2_meta_lock(dir, &parent_fe_bh, 1);
|
|
if (status < 0) {
|
|
if (status != -ENOENT)
|
|
mlog_errno(status);
|
|
return status;
|
|
}
|
|
|
|
dirfe = (struct ocfs2_dinode *) parent_fe_bh->b_data;
|
|
if (!dirfe->i_links_count) {
|
|
/* can't make a file in a deleted directory. */
|
|
status = -ENOENT;
|
|
goto bail;
|
|
}
|
|
|
|
status = ocfs2_check_dir_for_entry(dir, dentry->d_name.name,
|
|
dentry->d_name.len);
|
|
if (status)
|
|
goto bail;
|
|
|
|
status = ocfs2_prepare_dir_for_insert(osb, dir, parent_fe_bh,
|
|
dentry->d_name.name,
|
|
dentry->d_name.len, &de_bh);
|
|
if (status < 0) {
|
|
mlog_errno(status);
|
|
goto bail;
|
|
}
|
|
|
|
status = ocfs2_reserve_new_inode(osb, &inode_ac);
|
|
if (status < 0) {
|
|
if (status != -ENOSPC)
|
|
mlog_errno(status);
|
|
goto bail;
|
|
}
|
|
|
|
/* don't reserve bitmap space for fast symlinks. */
|
|
if (l > ocfs2_fast_symlink_chars(sb)) {
|
|
status = ocfs2_reserve_clusters(osb, 1, &data_ac);
|
|
if (status < 0) {
|
|
if (status != -ENOSPC)
|
|
mlog_errno(status);
|
|
goto bail;
|
|
}
|
|
}
|
|
|
|
handle = ocfs2_start_trans(osb, credits);
|
|
if (IS_ERR(handle)) {
|
|
status = PTR_ERR(handle);
|
|
handle = NULL;
|
|
mlog_errno(status);
|
|
goto bail;
|
|
}
|
|
|
|
status = ocfs2_mknod_locked(osb, dir, dentry,
|
|
S_IFLNK | S_IRWXUGO, 0,
|
|
&new_fe_bh, parent_fe_bh, handle,
|
|
&inode, inode_ac);
|
|
if (status < 0) {
|
|
mlog_errno(status);
|
|
goto bail;
|
|
}
|
|
|
|
fe = (struct ocfs2_dinode *) new_fe_bh->b_data;
|
|
inode->i_rdev = 0;
|
|
newsize = l - 1;
|
|
if (l > ocfs2_fast_symlink_chars(sb)) {
|
|
u32 offset = 0;
|
|
|
|
inode->i_op = &ocfs2_symlink_inode_operations;
|
|
status = ocfs2_do_extend_allocation(osb, inode, &offset, 1,
|
|
new_fe_bh,
|
|
handle, data_ac, NULL,
|
|
NULL);
|
|
if (status < 0) {
|
|
if (status != -ENOSPC && status != -EINTR) {
|
|
mlog(ML_ERROR,
|
|
"Failed to extend file to %llu\n",
|
|
(unsigned long long)newsize);
|
|
mlog_errno(status);
|
|
status = -ENOSPC;
|
|
}
|
|
goto bail;
|
|
}
|
|
i_size_write(inode, newsize);
|
|
inode->i_blocks = ocfs2_align_bytes_to_sectors(newsize);
|
|
} else {
|
|
inode->i_op = &ocfs2_fast_symlink_inode_operations;
|
|
memcpy((char *) fe->id2.i_symlink, symname, l);
|
|
i_size_write(inode, newsize);
|
|
inode->i_blocks = 0;
|
|
}
|
|
|
|
status = ocfs2_mark_inode_dirty(handle, inode, new_fe_bh);
|
|
if (status < 0) {
|
|
mlog_errno(status);
|
|
goto bail;
|
|
}
|
|
|
|
if (!ocfs2_inode_is_fast_symlink(inode)) {
|
|
status = ocfs2_create_symlink_data(osb, handle, inode,
|
|
symname);
|
|
if (status < 0) {
|
|
mlog_errno(status);
|
|
goto bail;
|
|
}
|
|
}
|
|
|
|
status = ocfs2_add_entry(handle, dentry, inode,
|
|
le64_to_cpu(fe->i_blkno), parent_fe_bh,
|
|
de_bh);
|
|
if (status < 0) {
|
|
mlog_errno(status);
|
|
goto bail;
|
|
}
|
|
|
|
status = ocfs2_dentry_attach_lock(dentry, inode, OCFS2_I(dir)->ip_blkno);
|
|
if (status) {
|
|
mlog_errno(status);
|
|
goto bail;
|
|
}
|
|
|
|
insert_inode_hash(inode);
|
|
dentry->d_op = &ocfs2_dentry_ops;
|
|
d_instantiate(dentry, inode);
|
|
bail:
|
|
if (handle)
|
|
ocfs2_commit_trans(osb, handle);
|
|
|
|
ocfs2_meta_unlock(dir, 1);
|
|
|
|
if (new_fe_bh)
|
|
brelse(new_fe_bh);
|
|
if (parent_fe_bh)
|
|
brelse(parent_fe_bh);
|
|
if (de_bh)
|
|
brelse(de_bh);
|
|
if (inode_ac)
|
|
ocfs2_free_alloc_context(inode_ac);
|
|
if (data_ac)
|
|
ocfs2_free_alloc_context(data_ac);
|
|
if ((status < 0) && inode)
|
|
iput(inode);
|
|
|
|
mlog_exit(status);
|
|
|
|
return status;
|
|
}
|
|
|
|
int ocfs2_check_dir_entry(struct inode * dir,
|
|
struct ocfs2_dir_entry * de,
|
|
struct buffer_head * bh,
|
|
unsigned long offset)
|
|
{
|
|
const char *error_msg = NULL;
|
|
const int rlen = le16_to_cpu(de->rec_len);
|
|
|
|
if (rlen < OCFS2_DIR_REC_LEN(1))
|
|
error_msg = "rec_len is smaller than minimal";
|
|
else if (rlen % 4 != 0)
|
|
error_msg = "rec_len % 4 != 0";
|
|
else if (rlen < OCFS2_DIR_REC_LEN(de->name_len))
|
|
error_msg = "rec_len is too small for name_len";
|
|
else if (((char *) de - bh->b_data) + rlen > dir->i_sb->s_blocksize)
|
|
error_msg = "directory entry across blocks";
|
|
|
|
if (error_msg != NULL)
|
|
mlog(ML_ERROR, "bad entry in directory #%llu: %s - "
|
|
"offset=%lu, inode=%llu, rec_len=%d, name_len=%d\n",
|
|
(unsigned long long)OCFS2_I(dir)->ip_blkno, error_msg,
|
|
offset, (unsigned long long)le64_to_cpu(de->inode), rlen,
|
|
de->name_len);
|
|
return error_msg == NULL ? 1 : 0;
|
|
}
|
|
|
|
/* we don't always have a dentry for what we want to add, so people
|
|
* like orphan dir can call this instead.
|
|
*
|
|
* If you pass me insert_bh, I'll skip the search of the other dir
|
|
* blocks and put the record in there.
|
|
*/
|
|
static int __ocfs2_add_entry(handle_t *handle,
|
|
struct inode *dir,
|
|
const char *name, int namelen,
|
|
struct inode *inode, u64 blkno,
|
|
struct buffer_head *parent_fe_bh,
|
|
struct buffer_head *insert_bh)
|
|
{
|
|
unsigned long offset;
|
|
unsigned short rec_len;
|
|
struct ocfs2_dir_entry *de, *de1;
|
|
struct super_block *sb;
|
|
int retval, status;
|
|
|
|
mlog_entry_void();
|
|
|
|
sb = dir->i_sb;
|
|
|
|
if (!namelen)
|
|
return -EINVAL;
|
|
|
|
rec_len = OCFS2_DIR_REC_LEN(namelen);
|
|
offset = 0;
|
|
de = (struct ocfs2_dir_entry *) insert_bh->b_data;
|
|
while (1) {
|
|
BUG_ON((char *)de >= sb->s_blocksize + insert_bh->b_data);
|
|
/* These checks should've already been passed by the
|
|
* prepare function, but I guess we can leave them
|
|
* here anyway. */
|
|
if (!ocfs2_check_dir_entry(dir, de, insert_bh, offset)) {
|
|
retval = -ENOENT;
|
|
goto bail;
|
|
}
|
|
if (ocfs2_match(namelen, name, de)) {
|
|
retval = -EEXIST;
|
|
goto bail;
|
|
}
|
|
if (((le64_to_cpu(de->inode) == 0) &&
|
|
(le16_to_cpu(de->rec_len) >= rec_len)) ||
|
|
(le16_to_cpu(de->rec_len) >=
|
|
(OCFS2_DIR_REC_LEN(de->name_len) + rec_len))) {
|
|
dir->i_mtime = dir->i_ctime = CURRENT_TIME;
|
|
retval = ocfs2_mark_inode_dirty(handle, dir, parent_fe_bh);
|
|
if (retval < 0) {
|
|
mlog_errno(retval);
|
|
goto bail;
|
|
}
|
|
|
|
status = ocfs2_journal_access(handle, dir, insert_bh,
|
|
OCFS2_JOURNAL_ACCESS_WRITE);
|
|
/* By now the buffer is marked for journaling */
|
|
offset += le16_to_cpu(de->rec_len);
|
|
if (le64_to_cpu(de->inode)) {
|
|
de1 = (struct ocfs2_dir_entry *)((char *) de +
|
|
OCFS2_DIR_REC_LEN(de->name_len));
|
|
de1->rec_len =
|
|
cpu_to_le16(le16_to_cpu(de->rec_len) -
|
|
OCFS2_DIR_REC_LEN(de->name_len));
|
|
de->rec_len = cpu_to_le16(OCFS2_DIR_REC_LEN(de->name_len));
|
|
de = de1;
|
|
}
|
|
de->file_type = OCFS2_FT_UNKNOWN;
|
|
if (blkno) {
|
|
de->inode = cpu_to_le64(blkno);
|
|
ocfs2_set_de_type(de, inode->i_mode);
|
|
} else
|
|
de->inode = 0;
|
|
de->name_len = namelen;
|
|
memcpy(de->name, name, namelen);
|
|
|
|
dir->i_version++;
|
|
status = ocfs2_journal_dirty(handle, insert_bh);
|
|
retval = 0;
|
|
goto bail;
|
|
}
|
|
offset += le16_to_cpu(de->rec_len);
|
|
de = (struct ocfs2_dir_entry *) ((char *) de + le16_to_cpu(de->rec_len));
|
|
}
|
|
|
|
/* when you think about it, the assert above should prevent us
|
|
* from ever getting here. */
|
|
retval = -ENOSPC;
|
|
bail:
|
|
|
|
mlog_exit(retval);
|
|
return retval;
|
|
}
|
|
|
|
|
|
/*
|
|
* ocfs2_delete_entry deletes a directory entry by merging it with the
|
|
* previous entry
|
|
*/
|
|
static int ocfs2_delete_entry(handle_t *handle,
|
|
struct inode *dir,
|
|
struct ocfs2_dir_entry *de_del,
|
|
struct buffer_head *bh)
|
|
{
|
|
struct ocfs2_dir_entry *de, *pde;
|
|
int i, status = -ENOENT;
|
|
|
|
mlog_entry("(0x%p, 0x%p, 0x%p, 0x%p)\n", handle, dir, de_del, bh);
|
|
|
|
i = 0;
|
|
pde = NULL;
|
|
de = (struct ocfs2_dir_entry *) bh->b_data;
|
|
while (i < bh->b_size) {
|
|
if (!ocfs2_check_dir_entry(dir, de, bh, i)) {
|
|
status = -EIO;
|
|
mlog_errno(status);
|
|
goto bail;
|
|
}
|
|
if (de == de_del) {
|
|
status = ocfs2_journal_access(handle, dir, bh,
|
|
OCFS2_JOURNAL_ACCESS_WRITE);
|
|
if (status < 0) {
|
|
status = -EIO;
|
|
mlog_errno(status);
|
|
goto bail;
|
|
}
|
|
if (pde)
|
|
pde->rec_len =
|
|
cpu_to_le16(le16_to_cpu(pde->rec_len) +
|
|
le16_to_cpu(de->rec_len));
|
|
else
|
|
de->inode = 0;
|
|
dir->i_version++;
|
|
status = ocfs2_journal_dirty(handle, bh);
|
|
goto bail;
|
|
}
|
|
i += le16_to_cpu(de->rec_len);
|
|
pde = de;
|
|
de = (struct ocfs2_dir_entry *)((char *)de + le16_to_cpu(de->rec_len));
|
|
}
|
|
bail:
|
|
mlog_exit(status);
|
|
return status;
|
|
}
|
|
|
|
/*
|
|
* Returns 0 if not found, -1 on failure, and 1 on success
|
|
*/
|
|
static int inline ocfs2_search_dirblock(struct buffer_head *bh,
|
|
struct inode *dir,
|
|
const char *name, int namelen,
|
|
unsigned long offset,
|
|
struct ocfs2_dir_entry **res_dir)
|
|
{
|
|
struct ocfs2_dir_entry *de;
|
|
char *dlimit, *de_buf;
|
|
int de_len;
|
|
int ret = 0;
|
|
|
|
mlog_entry_void();
|
|
|
|
de_buf = bh->b_data;
|
|
dlimit = de_buf + dir->i_sb->s_blocksize;
|
|
|
|
while (de_buf < dlimit) {
|
|
/* this code is executed quadratically often */
|
|
/* do minimal checking `by hand' */
|
|
|
|
de = (struct ocfs2_dir_entry *) de_buf;
|
|
|
|
if (de_buf + namelen <= dlimit &&
|
|
ocfs2_match(namelen, name, de)) {
|
|
/* found a match - just to be sure, do a full check */
|
|
if (!ocfs2_check_dir_entry(dir, de, bh, offset)) {
|
|
ret = -1;
|
|
goto bail;
|
|
}
|
|
*res_dir = de;
|
|
ret = 1;
|
|
goto bail;
|
|
}
|
|
|
|
/* prevent looping on a bad block */
|
|
de_len = le16_to_cpu(de->rec_len);
|
|
if (de_len <= 0) {
|
|
ret = -1;
|
|
goto bail;
|
|
}
|
|
|
|
de_buf += de_len;
|
|
offset += de_len;
|
|
}
|
|
|
|
bail:
|
|
mlog_exit(ret);
|
|
return ret;
|
|
}
|
|
|
|
struct buffer_head *ocfs2_find_entry(const char *name, int namelen,
|
|
struct inode *dir,
|
|
struct ocfs2_dir_entry **res_dir)
|
|
{
|
|
struct super_block *sb;
|
|
struct buffer_head *bh_use[NAMEI_RA_SIZE];
|
|
struct buffer_head *bh, *ret = NULL;
|
|
unsigned long start, block, b;
|
|
int ra_max = 0; /* Number of bh's in the readahead
|
|
buffer, bh_use[] */
|
|
int ra_ptr = 0; /* Current index into readahead
|
|
buffer */
|
|
int num = 0;
|
|
int nblocks, i, err;
|
|
|
|
mlog_entry_void();
|
|
|
|
*res_dir = NULL;
|
|
sb = dir->i_sb;
|
|
|
|
nblocks = i_size_read(dir) >> sb->s_blocksize_bits;
|
|
start = OCFS2_I(dir)->ip_dir_start_lookup;
|
|
if (start >= nblocks)
|
|
start = 0;
|
|
block = start;
|
|
|
|
restart:
|
|
do {
|
|
/*
|
|
* We deal with the read-ahead logic here.
|
|
*/
|
|
if (ra_ptr >= ra_max) {
|
|
/* Refill the readahead buffer */
|
|
ra_ptr = 0;
|
|
b = block;
|
|
for (ra_max = 0; ra_max < NAMEI_RA_SIZE; ra_max++) {
|
|
/*
|
|
* Terminate if we reach the end of the
|
|
* directory and must wrap, or if our
|
|
* search has finished at this block.
|
|
*/
|
|
if (b >= nblocks || (num && block == start)) {
|
|
bh_use[ra_max] = NULL;
|
|
break;
|
|
}
|
|
num++;
|
|
|
|
bh = ocfs2_bread(dir, b++, &err, 1);
|
|
bh_use[ra_max] = bh;
|
|
}
|
|
}
|
|
if ((bh = bh_use[ra_ptr++]) == NULL)
|
|
goto next;
|
|
wait_on_buffer(bh);
|
|
if (!buffer_uptodate(bh)) {
|
|
/* read error, skip block & hope for the best */
|
|
ocfs2_error(dir->i_sb, "reading directory %llu, "
|
|
"offset %lu\n",
|
|
(unsigned long long)OCFS2_I(dir)->ip_blkno,
|
|
block);
|
|
brelse(bh);
|
|
goto next;
|
|
}
|
|
i = ocfs2_search_dirblock(bh, dir, name, namelen,
|
|
block << sb->s_blocksize_bits,
|
|
res_dir);
|
|
if (i == 1) {
|
|
OCFS2_I(dir)->ip_dir_start_lookup = block;
|
|
ret = bh;
|
|
goto cleanup_and_exit;
|
|
} else {
|
|
brelse(bh);
|
|
if (i < 0)
|
|
goto cleanup_and_exit;
|
|
}
|
|
next:
|
|
if (++block >= nblocks)
|
|
block = 0;
|
|
} while (block != start);
|
|
|
|
/*
|
|
* If the directory has grown while we were searching, then
|
|
* search the last part of the directory before giving up.
|
|
*/
|
|
block = nblocks;
|
|
nblocks = i_size_read(dir) >> sb->s_blocksize_bits;
|
|
if (block < nblocks) {
|
|
start = 0;
|
|
goto restart;
|
|
}
|
|
|
|
cleanup_and_exit:
|
|
/* Clean up the read-ahead blocks */
|
|
for (; ra_ptr < ra_max; ra_ptr++)
|
|
brelse(bh_use[ra_ptr]);
|
|
|
|
mlog_exit_ptr(ret);
|
|
return ret;
|
|
}
|
|
|
|
static int ocfs2_blkno_stringify(u64 blkno, char *name)
|
|
{
|
|
int status, namelen;
|
|
|
|
mlog_entry_void();
|
|
|
|
namelen = snprintf(name, OCFS2_ORPHAN_NAMELEN + 1, "%016llx",
|
|
(long long)blkno);
|
|
if (namelen <= 0) {
|
|
if (namelen)
|
|
status = namelen;
|
|
else
|
|
status = -EINVAL;
|
|
mlog_errno(status);
|
|
goto bail;
|
|
}
|
|
if (namelen != OCFS2_ORPHAN_NAMELEN) {
|
|
status = -EINVAL;
|
|
mlog_errno(status);
|
|
goto bail;
|
|
}
|
|
|
|
mlog(0, "built filename '%s' for orphan dir (len=%d)\n", name,
|
|
namelen);
|
|
|
|
status = 0;
|
|
bail:
|
|
mlog_exit(status);
|
|
return status;
|
|
}
|
|
|
|
static int ocfs2_prepare_orphan_dir(struct ocfs2_super *osb,
|
|
struct inode **ret_orphan_dir,
|
|
struct inode *inode,
|
|
char *name,
|
|
struct buffer_head **de_bh)
|
|
{
|
|
struct inode *orphan_dir_inode;
|
|
struct buffer_head *orphan_dir_bh = NULL;
|
|
int status = 0;
|
|
|
|
status = ocfs2_blkno_stringify(OCFS2_I(inode)->ip_blkno, name);
|
|
if (status < 0) {
|
|
mlog_errno(status);
|
|
return status;
|
|
}
|
|
|
|
orphan_dir_inode = ocfs2_get_system_file_inode(osb,
|
|
ORPHAN_DIR_SYSTEM_INODE,
|
|
osb->slot_num);
|
|
if (!orphan_dir_inode) {
|
|
status = -ENOENT;
|
|
mlog_errno(status);
|
|
return status;
|
|
}
|
|
|
|
mutex_lock(&orphan_dir_inode->i_mutex);
|
|
|
|
status = ocfs2_meta_lock(orphan_dir_inode, &orphan_dir_bh, 1);
|
|
if (status < 0) {
|
|
mlog_errno(status);
|
|
goto leave;
|
|
}
|
|
|
|
status = ocfs2_prepare_dir_for_insert(osb, orphan_dir_inode,
|
|
orphan_dir_bh, name,
|
|
OCFS2_ORPHAN_NAMELEN, de_bh);
|
|
if (status < 0) {
|
|
ocfs2_meta_unlock(orphan_dir_inode, 1);
|
|
|
|
mlog_errno(status);
|
|
goto leave;
|
|
}
|
|
|
|
*ret_orphan_dir = orphan_dir_inode;
|
|
|
|
leave:
|
|
if (status) {
|
|
mutex_unlock(&orphan_dir_inode->i_mutex);
|
|
iput(orphan_dir_inode);
|
|
}
|
|
|
|
if (orphan_dir_bh)
|
|
brelse(orphan_dir_bh);
|
|
|
|
mlog_exit(status);
|
|
return status;
|
|
}
|
|
|
|
static int ocfs2_orphan_add(struct ocfs2_super *osb,
|
|
handle_t *handle,
|
|
struct inode *inode,
|
|
struct ocfs2_dinode *fe,
|
|
char *name,
|
|
struct buffer_head *de_bh,
|
|
struct inode *orphan_dir_inode)
|
|
{
|
|
struct buffer_head *orphan_dir_bh = NULL;
|
|
int status = 0;
|
|
struct ocfs2_dinode *orphan_fe;
|
|
|
|
mlog_entry("(inode->i_ino = %lu)\n", inode->i_ino);
|
|
|
|
status = ocfs2_read_block(osb,
|
|
OCFS2_I(orphan_dir_inode)->ip_blkno,
|
|
&orphan_dir_bh, OCFS2_BH_CACHED,
|
|
orphan_dir_inode);
|
|
if (status < 0) {
|
|
mlog_errno(status);
|
|
goto leave;
|
|
}
|
|
|
|
status = ocfs2_journal_access(handle, orphan_dir_inode, orphan_dir_bh,
|
|
OCFS2_JOURNAL_ACCESS_WRITE);
|
|
if (status < 0) {
|
|
mlog_errno(status);
|
|
goto leave;
|
|
}
|
|
|
|
/* we're a cluster, and nlink can change on disk from
|
|
* underneath us... */
|
|
orphan_fe = (struct ocfs2_dinode *) orphan_dir_bh->b_data;
|
|
if (S_ISDIR(inode->i_mode))
|
|
le16_add_cpu(&orphan_fe->i_links_count, 1);
|
|
orphan_dir_inode->i_nlink = le16_to_cpu(orphan_fe->i_links_count);
|
|
|
|
status = ocfs2_journal_dirty(handle, orphan_dir_bh);
|
|
if (status < 0) {
|
|
mlog_errno(status);
|
|
goto leave;
|
|
}
|
|
|
|
status = __ocfs2_add_entry(handle, orphan_dir_inode, name,
|
|
OCFS2_ORPHAN_NAMELEN, inode,
|
|
OCFS2_I(inode)->ip_blkno,
|
|
orphan_dir_bh, de_bh);
|
|
if (status < 0) {
|
|
mlog_errno(status);
|
|
goto leave;
|
|
}
|
|
|
|
le32_add_cpu(&fe->i_flags, OCFS2_ORPHANED_FL);
|
|
|
|
/* Record which orphan dir our inode now resides
|
|
* in. delete_inode will use this to determine which orphan
|
|
* dir to lock. */
|
|
fe->i_orphaned_slot = cpu_to_le16(osb->slot_num);
|
|
|
|
mlog(0, "Inode %llu orphaned in slot %d\n",
|
|
(unsigned long long)OCFS2_I(inode)->ip_blkno, osb->slot_num);
|
|
|
|
leave:
|
|
if (orphan_dir_bh)
|
|
brelse(orphan_dir_bh);
|
|
|
|
mlog_exit(status);
|
|
return status;
|
|
}
|
|
|
|
/* unlike orphan_add, we expect the orphan dir to already be locked here. */
|
|
int ocfs2_orphan_del(struct ocfs2_super *osb,
|
|
handle_t *handle,
|
|
struct inode *orphan_dir_inode,
|
|
struct inode *inode,
|
|
struct buffer_head *orphan_dir_bh)
|
|
{
|
|
char name[OCFS2_ORPHAN_NAMELEN + 1];
|
|
struct ocfs2_dinode *orphan_fe;
|
|
int status = 0;
|
|
struct buffer_head *target_de_bh = NULL;
|
|
struct ocfs2_dir_entry *target_de = NULL;
|
|
|
|
mlog_entry_void();
|
|
|
|
status = ocfs2_blkno_stringify(OCFS2_I(inode)->ip_blkno, name);
|
|
if (status < 0) {
|
|
mlog_errno(status);
|
|
goto leave;
|
|
}
|
|
|
|
mlog(0, "removing '%s' from orphan dir %llu (namelen=%d)\n",
|
|
name, (unsigned long long)OCFS2_I(orphan_dir_inode)->ip_blkno,
|
|
OCFS2_ORPHAN_NAMELEN);
|
|
|
|
/* find it's spot in the orphan directory */
|
|
target_de_bh = ocfs2_find_entry(name, OCFS2_ORPHAN_NAMELEN,
|
|
orphan_dir_inode, &target_de);
|
|
if (!target_de_bh) {
|
|
status = -ENOENT;
|
|
mlog_errno(status);
|
|
goto leave;
|
|
}
|
|
|
|
/* remove it from the orphan directory */
|
|
status = ocfs2_delete_entry(handle, orphan_dir_inode, target_de,
|
|
target_de_bh);
|
|
if (status < 0) {
|
|
mlog_errno(status);
|
|
goto leave;
|
|
}
|
|
|
|
status = ocfs2_journal_access(handle,orphan_dir_inode, orphan_dir_bh,
|
|
OCFS2_JOURNAL_ACCESS_WRITE);
|
|
if (status < 0) {
|
|
mlog_errno(status);
|
|
goto leave;
|
|
}
|
|
|
|
/* do the i_nlink dance! :) */
|
|
orphan_fe = (struct ocfs2_dinode *) orphan_dir_bh->b_data;
|
|
if (S_ISDIR(inode->i_mode))
|
|
le16_add_cpu(&orphan_fe->i_links_count, -1);
|
|
orphan_dir_inode->i_nlink = le16_to_cpu(orphan_fe->i_links_count);
|
|
|
|
status = ocfs2_journal_dirty(handle, orphan_dir_bh);
|
|
if (status < 0) {
|
|
mlog_errno(status);
|
|
goto leave;
|
|
}
|
|
|
|
leave:
|
|
if (target_de_bh)
|
|
brelse(target_de_bh);
|
|
|
|
mlog_exit(status);
|
|
return status;
|
|
}
|
|
|
|
const struct inode_operations ocfs2_dir_iops = {
|
|
.create = ocfs2_create,
|
|
.lookup = ocfs2_lookup,
|
|
.link = ocfs2_link,
|
|
.unlink = ocfs2_unlink,
|
|
.rmdir = ocfs2_unlink,
|
|
.symlink = ocfs2_symlink,
|
|
.mkdir = ocfs2_mkdir,
|
|
.mknod = ocfs2_mknod,
|
|
.rename = ocfs2_rename,
|
|
.setattr = ocfs2_setattr,
|
|
.getattr = ocfs2_getattr,
|
|
.permission = ocfs2_permission,
|
|
};
|