Re: Question about ext2

From: Andrew Morton (andrewm@uow.edu.au)
Date: Fri Jul 13 2001 - 03:42:59 EST


malfet@gw.mipt.sw.ru wrote:
>
> Hi all!
> I look up in implementation in ext2_rename and see the following statment:
> if (S_ISDIR(old_inode->i_mode)) {
> if (new_inode) {
> retval = -ENOTEMPTY;
> if (!empty_dir (new_inode))
> goto end_rename;
> }
> But I don't see any checkl like S_ISDIR on new_inode neither in
> ext2_rename neither in empty_dir. Is this bug? And, anyway, can
> I rename directory into not-empty file?

It's implicit - rename can only rename files to files, and
directories to directories. So the check is made at a higher
level. Consequently when we get to ext2_rename, if we find
that the old inode is a directory, we *know* that the new
one is a directory as well.

I recently spent an hour decrypting this function. Here is
a commented version which may prove helpful. It is from a
non-mainline branch of ext3, but it's much the same.

static int ext3_rename (struct inode * old_dir, struct dentry *old_dentry,
                           struct inode * new_dir,struct dentry *new_dentry)
{
        handle_t *handle;

        /* old_inode is the thing we're renaming */
        struct inode * old_inode = old_dentry->d_inode;

        /* new_inode is what we're renaming it to (may be NULL) */
        struct inode * new_inode = new_dentry->d_inode;

        /* dir_bh is the buffer which contains old_inode's ".." entry */
        struct buffer_head * dir_bh = NULL;

        /* dir_de is the old_inode's ".." de. Points into dir_bh->b_data */
        struct ext3_dir_entry_2 * dir_de = NULL;

        /* old_bh contains the old entry's de */
        struct buffer_head * old_bh;

        /* old_de points to the old entry's de, inside old_bh->b_data */
        struct ext3_dir_entry_2 * old_de;
        int err = -ENOENT;

        old_bh = NULL;

        handle = ext3_journal_start(old_dir, 2 * EXT3_DATA_TRANS_BLOCKS + 2);
        if (IS_ERR(handle))
                return PTR_ERR(handle);

        /* Find the current directory entry's bh and de */
        old_bh = ext3_find_entry (old_dentry, &old_de);
        if (!old_bh)
                goto end_rename;

        if (S_ISDIR(old_inode->i_mode)) {
                /*
                 * If the thing we're renaming is a directory, we'll need to
                 * change its ".." to point to a different parent. Go find
                 * the ".." directory entry
                 */
                err = -EIO;
                dir_de = ext3_dotdot(handle, old_inode, &dir_bh);
                if (!dir_de)
                        goto end_rename;
        }

        if (new_inode) {
                /* We're overwriting another object */
                struct buffer_head * new_bh;
                struct ext3_dir_entry_2 * new_de;

                /* If the renamee is a dir, then the victim MUST be a dir.
                 * It must not have any entries */
                err = -ENOTEMPTY;
                if (dir_de && !empty_dir (new_inode))
                        goto out_dir;

                /* Go find the buffer and de for the victim */
                err = -ENOENT;
                new_bh = ext3_find_entry (new_dentry, &new_de);
                if (!new_bh)
                        goto out_dir;

                /* Temporarily bump the renamee's link count. Dunno why */
                ext3_inc_count(handle, old_inode);

                /* Overwrite the victim's directory info */
                ext3_set_link(handle, new_dir, new_de, new_bh, old_inode);
                new_inode->i_ctime = CURRENT_TIME;

                /* If renamee is a dir then the victim is a dir. Drop nlink
                 * to account for the "." entry */
                if (dir_de)
                        new_inode->i_nlink--;

                /* victim loses a refcount. If it is a dir, it will be
                 * removed altogether, in do_rename->dput->iput */
                ext3_dec_count(handle, new_inode);

                /* Add an orphan record into this transaction. If the victim
                 * dir is huge, iput's truncate may cross multiple transactions.
                 * We remove the orphan record inside the transaction which
                 * actually releases the inode */
                if (!new_inode->i_nlink)
                        ext3_orphan_add(handle, new_inode);
        } else {
                /* The new name is not currently used */
                if (dir_de) {
                        /* Moving a directory will add an extra ref to its
                         * parent because of the ".." entry */
                        err = -EMLINK;
                        if (new_dir->i_nlink >= EXT3_LINK_MAX)
                                goto out_dir;
                }

                /* Temporarily bump the renamee's link count. Dunno why */
                ext3_inc_count(handle, old_inode);

                /* Add the renamee to its new directory */
                err = ext3_add_entry (handle, new_dentry, old_inode);
                if (err) {
                        ext3_dec_count(handle, old_inode);
                        goto out_dir;
                }

                /* If we just moved a directory, parent gets another ref for
                 * ".." */
                if (dir_de)
                        ext3_inc_count(handle, new_dir);
        }

        /* Remove the renamee's old directory entry */
        ext3_delete_entry(handle, old_dir, old_de, old_bh);
        old_dir->i_ctime = old_dir->i_mtime = CURRENT_TIME;
        ext3_mark_inode_dirty(handle, old_dir);
        brelse (old_bh);
        old_inode->i_ctime = CURRENT_TIME;

        /* Drop the temp refcount. Also marks the renamee's inode dirty */
        ext3_dec_count(handle, old_inode);

        if (dir_bh) {
                /* We moved a directory. Make its ".." entry point to the new
                 * parent */
                ext3_set_link(handle, old_inode, dir_de, dir_bh, new_dir);

                /* The old parent no longer has the renamee's ".." pointing
                 * to it */
                ext3_dec_count(handle, old_dir);
        }

        ext3_journal_stop(handle, old_dir);
        return 0;

out_dir:
        brelse (dir_bh);
end_rename:
        brelse (old_bh);
        ext3_journal_stop(handle, old_dir);
        return err;
}
-
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/



This archive was generated by hypermail 2b29 : Sun Jul 15 2001 - 21:00:18 EST