blob: 5cb1394f931348edf0cb26e1a8e1202d5580fe85 [file] [log] [blame]
/*
* vim:noexpandtab:shiftwidth=8:tabstop=8:
*
* Copyright CEA/DAM/DIF (2008)
* contributeur : Philippe DENIEL philippe.deniel@cea.fr
* Thomas LEIBOVICI thomas.leibovici@cea.fr
*
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 3 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301 USA
*
* ---------------------------------------
*/
/**
* @addtogroup cache_inode
* @{
*/
/**
* @file cache_inode_rename.c
* @brief Renames an entry.
*
* Renames an entry.
*
*/
#include "config.h"
#include "log.h"
#include "hashtable.h"
#include "fsal.h"
#include "cache_inode_lru.h"
#include <unistd.h>
#include <sys/types.h>
#include <sys/param.h>
#include <time.h>
#include <pthread.h>
#include <assert.h>
/**
*
* @brief Renames an entry in the same directory.
*
* Renames an entry in the same directory.
*
* @param[in,out] parent The directory to be managed
* @param[in] oldname The name of the entry to rename
* @param[in] newname The new name for the entry
*
* @return the same as *status
*/
cache_inode_status_t
cache_inode_rename_cached_dirent(cache_entry_t *parent,
const char *oldname,
const char *newname)
{
cache_inode_status_t status = CACHE_INODE_SUCCESS;
/* Sanity check */
if (parent->type != DIRECTORY) {
status = CACHE_INODE_NOT_A_DIRECTORY;
return status;
}
status =
cache_inode_operate_cached_dirent(parent, oldname, newname,
CACHE_INODE_DIRENT_OP_RENAME);
return status;
}
/**
* @brief Lock two directories in order
*
* This function gets the locks on both entries. If src and dest are
* the same, it takes only one lock. Locks are acquired with lowest
* cache_entry first to avoid deadlocks.
*
* @param[in] src Source directory to lock
* @param[in] dest Destination directory to lock
*/
static inline void
src_dest_lock(cache_entry_t *src, cache_entry_t *dest)
{
int rc;
/*
* A problem found in this order
* 1. cache_inode_readdir holds A's content_lock, and tries to
* grab B's attr_lock.
* 2. cache_inode_remove holds B's attr_lock, and tries to grab B's
* content_lock
* 3. cache_inode_rename holds B's content_lock, and tries to grab the
* A's content_lock (which is held by thread 1).
* This change is to avoid this deadlock.
*/
retry_lock:
if (src == dest)
PTHREAD_RWLOCK_wrlock(&src->content_lock);
else {
if (src < dest) {
PTHREAD_RWLOCK_wrlock(&src->content_lock);
rc = pthread_rwlock_trywrlock(&dest->content_lock);
if (rc) {
LogDebug(COMPONENT_CACHE_INODE,
"retry dest %p lock, src %p",
dest, src);
PTHREAD_RWLOCK_unlock(&src->content_lock);
sleep(1);
goto retry_lock;
}
} else {
PTHREAD_RWLOCK_wrlock(&dest->content_lock);
rc = pthread_rwlock_trywrlock(&src->content_lock);
if (rc) {
LogDebug(COMPONENT_CACHE_INODE,
"retry src %p lock, dest %p",
src, dest);
PTHREAD_RWLOCK_unlock(&dest->content_lock);
sleep(1);
goto retry_lock;
}
}
}
}
/**
* @brief Unlock two directories in order
*
* This function releases the locks on both entries. If src and dest
* are the same, it releases the lock and returns. Locks are released
* with lowest cache_entry first.
*
* @param[in] src Source directory to lock
* @param[in] dest Destination directory to lock
*/
static inline void
src_dest_unlock(cache_entry_t *src, cache_entry_t *dest)
{
if (src == dest)
PTHREAD_RWLOCK_unlock(&src->content_lock);
else {
if (src < dest) {
PTHREAD_RWLOCK_unlock(&dest->content_lock);
PTHREAD_RWLOCK_unlock(&src->content_lock);
} else {
PTHREAD_RWLOCK_unlock(&src->content_lock);
PTHREAD_RWLOCK_unlock(&dest->content_lock);
}
}
}
/**
* @brief Renames an entry
*
* This function calls the FSAL to rename a file, then mirrors the
* operation in the cache.
*
* @param[in] dir_src The source directory
* @param[in] oldname The current name of the file
* @param[in] dir_dest The destination directory
* @param[in] newname The name to be assigned to the object
*
* @retval CACHE_INODE_SUCCESS if operation is a success.
* @retval CACHE_INODE_NOT_FOUND if source object does not exist
* @retval CACHE_INODE_ENTRY_EXISTS on collision.
* @retval CACHE_INODE_NOT_A_DIRECTORY if dir_src or dir_dest is not a
* directory.
* @retval CACHE_INODE_BADNAME if either name is "." or ".."
*/
cache_inode_status_t
cache_inode_rename(cache_entry_t *dir_src,
const char *oldname,
cache_entry_t *dir_dest,
const char *newname)
{
fsal_status_t fsal_status = { 0, 0 };
cache_entry_t *lookup_src = NULL;
cache_entry_t *lookup_dst = NULL;
cache_inode_status_t status = CACHE_INODE_SUCCESS;
cache_inode_status_t status_ref_dir_src = CACHE_INODE_SUCCESS;
cache_inode_status_t status_ref_dir_dst = CACHE_INODE_SUCCESS;
cache_inode_status_t status_ref_src = CACHE_INODE_SUCCESS;
cache_inode_status_t status_ref_dst = CACHE_INODE_SUCCESS;
if ((dir_src->type != DIRECTORY) || (dir_dest->type != DIRECTORY)) {
status = CACHE_INODE_NOT_A_DIRECTORY;
goto out;
}
/* Check for . and .. on oldname and newname. */
if (!strcmp(oldname, ".") || !strcmp(oldname, "..")
|| !strcmp(newname, ".") || !strcmp(newname, "..")) {
status = CACHE_INODE_BADNAME;
goto out;
}
/* Check for object existence in source directory */
status =
cache_inode_lookup_impl(dir_src, oldname, &lookup_src);
if (lookup_src == NULL) {
/* If FSAL FH is stale, then this was managed in
* cache_inode_lookup */
if (status != CACHE_INODE_ESTALE)
status = CACHE_INODE_NOT_FOUND;
LogDebug(COMPONENT_CACHE_INODE,
"Rename (%p,%s)->(%p,%s) : source doesn't exist",
dir_src, oldname, dir_dest, newname);
goto out;
}
/* Do not rename a junction node or an export root. */
if (lookup_src->type == DIRECTORY) {
/* Get attr_lock for looking at junction_export */
PTHREAD_RWLOCK_rdlock(&lookup_src->attr_lock);
if (lookup_src->object.dir.junction_export != NULL ||
atomic_fetch_int32_t(&lookup_src->exp_root_refcount)
!= 0) {
/* Trying to rename an export mount point */
LogCrit(COMPONENT_CACHE_INODE,
"Attempt to rename export %s",
oldname);
/* Release attr_lock */
PTHREAD_RWLOCK_unlock(&lookup_src->attr_lock);
status = CACHE_INODE_DIR_NOT_EMPTY;
goto out;
}
/* Release attr_lock */
PTHREAD_RWLOCK_unlock(&lookup_src->attr_lock);
}
/* Check if an object with the new name exists in the destination
directory */
status =
cache_inode_lookup_impl(dir_dest, newname, &lookup_dst);
if (status == CACHE_INODE_SUCCESS) {
LogDebug(COMPONENT_CACHE_INODE,
"Rename (%p,%s)->(%p,%s) : destination already exists",
dir_src, oldname, dir_dest, newname);
}
if (status == CACHE_INODE_NOT_FOUND)
status = CACHE_INODE_SUCCESS;
if (status == CACHE_INODE_ESTALE) {
LogDebug(COMPONENT_CACHE_INODE,
"Rename (%p,%s)->(%p,%s) : stale destination", dir_src,
oldname, dir_dest, newname);
}
if (lookup_src == lookup_dst) {
/* Nothing to do according to POSIX and NFS3/4
* If from and to both refer to the same file (they might be
* hard links of each other), then RENAME should perform no
* action and return success */
LogDebug(COMPONENT_CACHE_INODE,
"Rename (%p,%s)->(%p,%s) : same file so skipping out",
dir_src, oldname, dir_dest, newname);
goto out;
}
/* Perform the rename operation in FSAL before doing anything in the
* cache. Indeed, if the FSAL_rename fails unexpectly, the cache would
* be inconsistent!
*
* We do almost no checking before making call because we want to return
* error based on the files actually present in the directories, not
* what we have in our cache.
*/
LogFullDebug(COMPONENT_CACHE_INODE, "about to call FSAL rename");
fsal_status =
dir_src->obj_handle->obj_ops.rename(lookup_src->obj_handle,
dir_src->obj_handle, oldname,
dir_dest->obj_handle, newname);
LogFullDebug(COMPONENT_CACHE_INODE, "returned from FSAL rename");
status_ref_dir_src = cache_inode_refresh_attrs_locked(dir_src);
if (dir_src != dir_dest)
status_ref_dir_dst =
cache_inode_refresh_attrs_locked(dir_dest);
status_ref_src = cache_inode_refresh_attrs_locked(lookup_src);
LogFullDebug(COMPONENT_CACHE_INODE, "done refreshing attributes");
if (FSAL_IS_ERROR(fsal_status)) {
status = cache_inode_error_convert(fsal_status);
LogFullDebug(COMPONENT_CACHE_INODE,
"FSAL rename failed with %s",
cache_inode_err_str(status));
goto out;
}
if (lookup_dst) {
/* Force a refresh of the overwritten inode */
status_ref_dst = cache_inode_refresh_attrs_locked(lookup_dst);
if (status_ref_dst == CACHE_INODE_ESTALE)
status_ref_dst = CACHE_INODE_SUCCESS;
}
status = status_ref_dir_src;
if (status == CACHE_INODE_SUCCESS)
status = status_ref_dir_dst;
if (status == CACHE_INODE_SUCCESS)
status = status_ref_src;
if (status == CACHE_INODE_SUCCESS)
status = status_ref_dst;
if (status != CACHE_INODE_SUCCESS)
goto out;
/* Must take locks on directories now,
* because if another thread checks source and destination existence
* in the same time, it will try to do the same checks...
* and it will have the same conclusion !!!
*/
src_dest_lock(dir_src, dir_dest);
if (lookup_dst) {
/* Remove the entry from parent dir_entries avl */
status_ref_dir_dst =
cache_inode_remove_cached_dirent(dir_dest, newname);
if (status_ref_dir_dst != CACHE_INODE_SUCCESS) {
LogDebug(COMPONENT_CACHE_INODE,
"remove entry failed with status %s",
cache_inode_err_str(status_ref_dir_dst));
cache_inode_invalidate_all_cached_dirent(dir_dest);
}
}
if (dir_src == dir_dest) {
/* if the rename operation is made within the same dir, then we
* use an optimization: cache_inode_rename_dirent is used
* instead of adding/removing dirent. This limits the use of
* resource in this case */
LogDebug(COMPONENT_CACHE_INODE,
"Rename (%p,%s)->(%p,%s) : source and target directory the same",
dir_src, oldname, dir_dest, newname);
cache_inode_status_t tmp_status =
cache_inode_rename_cached_dirent(dir_dest,
oldname, newname);
if (tmp_status != CACHE_INODE_SUCCESS) {
/* We're obviously out of date. Throw out the cached
directory */
cache_inode_invalidate_all_cached_dirent(dir_dest);
}
} else {
cache_inode_status_t tmp_status = CACHE_INODE_SUCCESS;
LogDebug(COMPONENT_CACHE_INODE,
"Rename (%p,%s)->(%p,%s) : moving entry", dir_src,
oldname, dir_dest, newname);
/* We may have a cache entry for the destination
* filename. If we do, we must delete it : it is stale. */
tmp_status =
cache_inode_remove_cached_dirent(dir_dest, newname);
if (tmp_status != CACHE_INODE_SUCCESS
&& tmp_status != CACHE_INODE_NOT_FOUND) {
LogDebug(COMPONENT_CACHE_INODE,
"Remove stale dirent returned %s",
cache_inode_err_str(tmp_status));
cache_inode_invalidate_all_cached_dirent(dir_dest);
}
tmp_status =
cache_inode_add_cached_dirent(dir_dest, newname, lookup_src,
NULL);
if (tmp_status != CACHE_INODE_SUCCESS) {
/* We're obviously out of date. Throw out the cached
directory */
LogCrit(COMPONENT_CACHE_INODE, "Add dirent returned %s",
cache_inode_err_str(tmp_status));
cache_inode_invalidate_all_cached_dirent(dir_dest);
}
/* Remove the old entry */
tmp_status =
cache_inode_remove_cached_dirent(dir_src, oldname);
if (tmp_status != CACHE_INODE_SUCCESS
&& tmp_status != CACHE_INODE_NOT_FOUND) {
LogDebug(COMPONENT_CACHE_INODE,
"Remove old dirent returned %s",
cache_inode_err_str(tmp_status));
cache_inode_invalidate_all_cached_dirent(dir_src);
}
}
/* unlock entries */
src_dest_unlock(dir_src, dir_dest);
out:
if (lookup_src)
cache_inode_put(lookup_src);
if (lookup_dst)
cache_inode_put(lookup_dst);
return status;
}
/** @} */