/**
 * @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_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_FSAL_ESTALE)
			status = CACHE_INODE_NOT_FOUND;

		LogEvent(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_FSAL_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->ops->rename(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);

	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_FSAL_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_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;
}
/**
 * @brief Implement actual work of removing file
 *
 * Actually remove an entry from the directory.  Assume that the
 * directory contents and attributes are locked for writes.  The
 * attribute lock is released unless keep_md_lock is TRUE.
 *
 * @param[in] entry   Entry for the parent directory to be managed.
 * @param[in] name    Name of the entry that we are looking for in the cache.
 * @param[in] context FSAL credentials
 * @param[in] status  Returned status
 * @param[in] flags   Flags to control lock retention
 *
 * @return CACHE_INODE_SUCCESS if operation is a success
 *
 */
cache_inode_status_t
cache_inode_remove_impl(cache_entry_t *entry,
                        fsal_name_t *name,
                        fsal_op_context_t *context,
                        cache_inode_status_t *status,
                        uint32_t flags)
{
     cache_entry_t *to_remove_entry = NULL;
     fsal_status_t fsal_status = {0, 0};
#ifdef _USE_NFS4_ACL
     fsal_acl_t *saved_acl = NULL;
     fsal_acl_status_t acl_status = 0;
#endif /* _USE_NFS4_ACL */

     if(entry->type != DIRECTORY) {
          *status = CACHE_INODE_BAD_TYPE;
          goto out;
     }

     if (!(flags & CACHE_INODE_FLAG_CONTENT_HAVE)) {
          pthread_rwlock_rdlock(&entry->content_lock);
          flags |= CACHE_INODE_FLAG_CONTENT_HAVE;
     }

     /* Factor this somewhat.  In the case where the directory hasn't
        been populated, the entry may not exist in the cache and we'd
        be bringing it in just to dispose of it. */

     /* Looks up for the entry to remove */
     if ((to_remove_entry
          = cache_inode_lookup_impl(entry,
                                    name,
                                    context,
                                    status)) == NULL) {
          goto out;
     }

     /* Lock the attributes (so we can decrement the link count) */
     pthread_rwlock_wrlock(&to_remove_entry->attr_lock);

     LogDebug(COMPONENT_CACHE_INODE,
              "---> Cache_inode_remove : %s", name->name);


#ifdef _USE_NFS4_ACL
     saved_acl = entry->attributes.acl;
#endif /* _USE_NFS4_ACL */
     fsal_status = FSAL_unlink(&entry->handle,
                               name,
                               context,
                               &entry->attributes);

     if (FSAL_IS_ERROR(fsal_status)) {
          *status = cache_inode_error_convert(fsal_status);
          if (fsal_status.major == ERR_FSAL_STALE) {
               cache_inode_kill_entry(entry);
          }
          goto unlock;
     } else {
#ifdef _USE_NFS4_ACL
          /* Decrement refcount on saved ACL */
          nfs4_acl_release_entry(saved_acl, &acl_status);
          if (acl_status != NFS_V4_ACL_SUCCESS) {
               LogCrit(COMPONENT_CACHE_INODE,
                       "Failed to release old acl, status=%d",
                       acl_status);
          }
#endif /* _USE_NFS4_ACL */
     }
     cache_inode_fixup_md(entry);

     if ((flags & CACHE_INODE_FLAG_ATTR_HAVE) &&
         !(flags & CACHE_INODE_FLAG_ATTR_HOLD)) {
          pthread_rwlock_unlock(&entry->attr_lock);
     }

     /* Remove the entry from parent dir_entries avl */
     cache_inode_remove_cached_dirent(entry, name, status);

     LogFullDebug(COMPONENT_CACHE_INODE,
                  "cache_inode_remove_cached_dirent: status=%d", *status);

     /* Update the attributes for the removed entry */

     if ((to_remove_entry->type != DIRECTORY) &&
         (to_remove_entry->attributes.numlinks > 1)) {
          if ((*status = cache_inode_refresh_attrs(to_remove_entry,
                                                   context))
              != CACHE_INODE_SUCCESS) {
               goto unlock;
          }
     } else {
          /* Otherwise our count is zero, or it was an empty
             directory. */
          to_remove_entry->attributes.numlinks = 0;
     }

     /* Now, delete "to_remove_entry" from the cache inode and free
        its associated resources, but only if numlinks == 0 */
     if (to_remove_entry->attributes.numlinks == 0) {
          /* Destroy the entry when everyone's references to it have
             been relinquished.  Most likely now. */
          pthread_rwlock_unlock(&to_remove_entry->attr_lock);
          /* Kill off the sentinel reference (and mark the entry so
             it doesn't get recycled while a reference exists.) */
          cache_inode_lru_kill(to_remove_entry);
     } else {
     unlock:

          pthread_rwlock_unlock(&to_remove_entry->attr_lock);
     }

out:
     if ((flags & CACHE_INODE_FLAG_CONTENT_HAVE) &&
         !(flags & CACHE_INODE_FLAG_CONTENT_HOLD)) {
          pthread_rwlock_unlock(&entry->content_lock);
     }

     /* This is for the reference taken by lookup */
     if (to_remove_entry)
       {
         cache_inode_put(to_remove_entry);
       }

     return *status;
}
cache_inode_status_t
cache_inode_remove(cache_entry_t *entry,
		   const char *name,
		   struct req_op_context *req_ctx)
{
     cache_entry_t *to_remove_entry = NULL;
     fsal_status_t fsal_status = {0, 0};
     cache_inode_status_t status = CACHE_INODE_SUCCESS;
     cache_inode_status_t status_ref_entry = CACHE_INODE_SUCCESS;
     cache_inode_status_t status_ref_to_remove_entry = CACHE_INODE_SUCCESS;
     fsal_accessflags_t access_mask = 0;

     if(entry->type != DIRECTORY) {
         status = CACHE_INODE_NOT_A_DIRECTORY;
         goto out;
     }

     /* Check if caller is allowed to perform the operation */
     access_mask = (FSAL_MODE_MASK_SET(FSAL_W_OK) |
		    FSAL_ACE4_MASK_SET(FSAL_ACE_PERM_DELETE_CHILD));

     status = cache_inode_access(entry,
                                 access_mask,
                                 req_ctx);
     if (status != CACHE_INODE_SUCCESS) {
          goto out;
     }

     /* Factor this somewhat.  In the case where the directory hasn't
        been populated, the entry may not exist in the cache and we'd
        be bringing it in just to dispose of it. */

     /* Looks up for the entry to remove */
     PTHREAD_RWLOCK_rdlock(&entry->content_lock);
     status = cache_inode_lookup_impl(entry,
				      name,
				      req_ctx,
				      &to_remove_entry);
     PTHREAD_RWLOCK_unlock(&entry->content_lock);

     if (to_remove_entry == NULL) {
	 goto out;
     }

     status = cache_inode_check_sticky(entry, to_remove_entry, req_ctx);
     if (status != CACHE_INODE_SUCCESS) {
         goto out;
     }

     LogDebug(COMPONENT_CACHE_INODE,
              "---> Cache_inode_remove : %s", name);


     if (is_open(to_remove_entry)) {
	 /* entry is not locked and seems to be open for fd caching purpose.
	  * candidate for closing since unlink of an open file results in 'silly
	  * rename' on certain platforms */
	 status = cache_inode_close(to_remove_entry,
                                    CACHE_INODE_FLAG_REALLYCLOSE);
	 if (status != CACHE_INODE_SUCCESS) {
	     /* non-fatal error. log the warning and move on */
	     LogCrit(COMPONENT_CACHE_INODE,
                   "Error closing file before unlink: %d.",
                   status);
	 }
     }

     fsal_status = entry->obj_handle->ops->unlink(entry->obj_handle, req_ctx,
                                                  name);
     if (FSAL_IS_ERROR(fsal_status)) {
         status = cache_inode_error_convert(fsal_status);
	 if(to_remove_entry->type == DIRECTORY &&
	    status == CACHE_INODE_DIR_NOT_EMPTY) {
	     /* its dirent tree is probably stale, flush it
	      * to try and make things right again */
	     PTHREAD_RWLOCK_wrlock(&to_remove_entry->content_lock);
	     (void)cache_inode_invalidate_all_cached_dirent(to_remove_entry);
	     PTHREAD_RWLOCK_unlock(&to_remove_entry->content_lock);
	 }
         goto out;
     }

     /* Remove the entry from parent dir_entries avl */
     PTHREAD_RWLOCK_wrlock(&entry->content_lock);
     cache_inode_remove_cached_dirent(entry, name, req_ctx);
     PTHREAD_RWLOCK_unlock(&entry->content_lock);

     status_ref_entry = cache_inode_refresh_attrs_locked(entry, req_ctx);
     if(FSAL_IS_ERROR(fsal_status)) {
         status = cache_inode_error_convert(fsal_status);
         goto out;
     }

     /* Update the attributes for the removed entry */
     status_ref_to_remove_entry = cache_inode_refresh_attrs_locked(to_remove_entry, req_ctx);
     if (status_ref_to_remove_entry == CACHE_INODE_FSAL_ESTALE) {
             status_ref_to_remove_entry = CACHE_INODE_SUCCESS;
     }

     if (((status = status_ref_entry) != CACHE_INODE_SUCCESS) ||
         ((status = status_ref_to_remove_entry) != CACHE_INODE_SUCCESS)) {
         goto out;
     }

out:
     LogFullDebug(COMPONENT_CACHE_INODE,
                  "cache_inode_remove_cached_dirent: status=%d", status);

     /* This is for the reference taken by lookup */
     if (to_remove_entry) {
         cache_inode_put(to_remove_entry);
     }

     return status;
}
/**
 *
 * cache_inode_remove_sw: removes a pentry addressed by its parent pentry and
 * its FSAL name.  Mutex management is switched.
 *
 * Removes a pentry addressed by its parent pentry and its FSAL name.  Mutex
 * management is switched.
 *
 * @param pentry  [IN]     entry for the parent directory to be managed.
 * @param name    [IN]     name of the entry that we are looking for in the cache.
 * @param pattr   [OUT]    attributes for the entry that we have found.
 * @param ht      [IN]     hash table used for the cache, unused in this call.
 * @param pclient [INOUT] ressource allocated by the client for the nfs management.
 * @param pcontext   [IN]    FSAL credentials
 * @param pstatus [OUT]   returned status.
 *
 * @return CACHE_INODE_SUCCESS if operation is a success \n
 * @return CACHE_INODE_LRU_ERROR if allocation error occured when validating the entry
 *
 */
cache_inode_status_t cache_inode_remove_sw(cache_entry_t * pentry,             /**< Parent entry */
                                           fsal_name_t * pnode_name,
                                           fsal_attrib_list_t * pattr,
                                           hash_table_t * ht,
                                           cache_inode_client_t * pclient,
                                           fsal_op_context_t * pcontext,
                                           cache_inode_status_t * pstatus, int use_mutex)
{
  fsal_status_t fsal_status;
  cache_entry_t *to_remove_entry;
  fsal_handle_t fsal_handle_parent;
  fsal_attrib_list_t remove_attr;
  fsal_attrib_list_t after_attr;
  cache_inode_status_t status;
  cache_content_status_t cache_content_status;
  int to_remove_numlinks = 0;
  fsal_accessflags_t access_mask = 0;

  /* stats */
  (pclient->stat.nb_call_total)++;
  (pclient->stat.func_stats.nb_call[CACHE_INODE_REMOVE])++;

  /* pentry is a directory */
  if(use_mutex)
    P_w(&pentry->lock);

  /* Check if caller is allowed to perform the operation */
  access_mask = FSAL_MODE_MASK_SET(FSAL_W_OK) |
                FSAL_ACE4_MASK_SET(FSAL_ACE_PERM_DELETE_CHILD);
  if((status = cache_inode_access_sw(pentry,
                                     access_mask,
                                     ht,
                                     pclient,
                                     pcontext, &status, FALSE)) != CACHE_INODE_SUCCESS)
    {
      *pstatus = status;

      /* pentry is a directory */
      if(use_mutex)
        V_w(&pentry->lock);

      return *pstatus;
    }

  /* Looks up for the entry to remove */
  if((to_remove_entry = cache_inode_lookup_sw( pentry,
                                               pnode_name,
                                               CACHE_INODE_JOKER_POLICY,
                                               &remove_attr,
                                               ht,
                                               pclient, 
                                               pcontext, 
                                               &status, 
                                               FALSE)) == NULL)
    {
      *pstatus = status;

      /* pentry is a directory */
      if(use_mutex)
        V_w(&pentry->lock);

      return *pstatus;
    }

  /* lock it */
  if(use_mutex)
    P_w(&to_remove_entry->lock);

  if(pentry->internal_md.type != DIRECTORY)
    {
      if(use_mutex)
        {
          V_w(&to_remove_entry->lock);
          V_w(&pentry->lock);
        }

      *pstatus = CACHE_INODE_BAD_TYPE;
      return *pstatus;
    }
  
  LogDebug(COMPONENT_CACHE_INODE,
           "---> Cache_inode_remove : %s", pnode_name->name);

  /* Non-empty directories should not be removed. */
  if(to_remove_entry->internal_md.type == DIRECTORY &&
     to_remove_entry->object.dir.has_been_readdir == CACHE_INODE_YES)
    {
      if(cache_inode_is_dir_empty(to_remove_entry) != CACHE_INODE_SUCCESS)
        {
          if(use_mutex)
            {
              V_w(&to_remove_entry->lock);
              V_w(&pentry->lock);
            }

          *pstatus = CACHE_INODE_DIR_NOT_EMPTY;
          return *pstatus;
        }
    }
    
  /* pentry->internal_md.type == DIRECTORY */
  fsal_handle_parent = pentry->object.dir.handle;

  if(status == CACHE_INODE_SUCCESS)
    {
      /* Remove the file from FSAL */
      after_attr.asked_attributes = pclient->attrmask;
#ifdef _USE_MFSL
      cache_inode_get_attributes(pentry, &after_attr);
#ifdef _USE_PNFS
      after_attr.numlinks = remove_attr.numlinks ; /* Hook used to pass nlinks to MFSL_unlink */
      if( to_remove_entry->internal_md.type == REGULAR_FILE )
        fsal_status = MFSL_unlink(&pentry->mobject,
                                  pnode_name,
                                  &to_remove_entry->mobject,
                                  pcontext, &pclient->mfsl_context, &after_attr,
                                  &to_remove_entry->object.file.pnfs_file );
      else
#endif /* _USE_PNFS */
      fsal_status = MFSL_unlink(&pentry->mobject,
                                pnode_name,
                                &to_remove_entry->mobject,
                                pcontext, &pclient->mfsl_context, &after_attr,
                                NULL);

#else
      fsal_status = FSAL_unlink(&fsal_handle_parent, pnode_name, pcontext, &after_attr);
#endif

      /* Set the 'after' attr */
      if(pattr != NULL)
        *pattr = after_attr;

      if(FSAL_IS_ERROR(fsal_status))
        {
          if(fsal_status.major == ERR_FSAL_STALE)
            {
              cache_inode_status_t kill_status;

              LogDebug(COMPONENT_CACHE_INODE,
                       "cache_inode_remove: Stale FSAL FH detected for pentry %p",
                       pentry);

              if(cache_inode_kill_entry(pentry, WT_LOCK, ht, pclient, &kill_status) !=
                 CACHE_INODE_SUCCESS)
                LogCrit(COMPONENT_CACHE_INODE,
                        "cache_inode_remove: Could not kill entry %p, status = %u",
                        pentry, kill_status);

              *pstatus = CACHE_INODE_FSAL_ESTALE;
            }

          *pstatus = cache_inode_error_convert(fsal_status);
          if(use_mutex)
            {
              V_w(&to_remove_entry->lock);
              V_w(&pentry->lock);
            }
          return *pstatus;
        }
    } /* CACHE_INODE_SUCCESS */
  else
    {
      if(use_mutex)
        {
          V_w(&to_remove_entry->lock);
          V_w(&pentry->lock);
        }
      (pclient->stat.func_stats.nb_err_unrecover[CACHE_INODE_REMOVE])++;
      return status;
    }

  /* Remove the entry from parent dir_entries avl */
  cache_inode_remove_cached_dirent(pentry, pnode_name, ht, pclient, &status);

  LogFullDebug(COMPONENT_CACHE_INODE,
               "cache_inode_remove_cached_dirent: status=%d", status);

  /* Update the cached attributes */
  pentry->object.dir.attributes = after_attr;

  /* Update the attributes for the removed entry */

  if(remove_attr.type != FSAL_TYPE_DIR)
    {
      if(remove_attr.numlinks > 1)
        {
          switch (to_remove_entry->internal_md.type)
            {
            case SYMBOLIC_LINK:
              assert(to_remove_entry->object.symlink);
              to_remove_entry->object.symlink->attributes.numlinks -= 1;
              cache_inode_set_time_current( &to_remove_entry->object.symlink->attributes.ctime ) ;
              to_remove_numlinks = to_remove_entry->object.symlink->attributes.numlinks;
              break;

            case REGULAR_FILE:
              to_remove_entry->object.file.attributes.numlinks -= 1;
              cache_inode_set_time_current( &to_remove_entry->object.file.attributes.ctime ) ;
              to_remove_numlinks = to_remove_entry->object.file.attributes.numlinks;
              break;

            case CHARACTER_FILE:
            case BLOCK_FILE:
            case SOCKET_FILE:
            case FIFO_FILE:
              to_remove_entry->object.special_obj.attributes.numlinks -= 1;
              cache_inode_set_time_current( &to_remove_entry->object.special_obj.attributes.ctime ) ;
              to_remove_numlinks =
                  to_remove_entry->object.special_obj.attributes.numlinks;
              break;

            default:
              /* Other objects should not be hard linked */
              if(use_mutex)
                {
                  V_w(&to_remove_entry->lock);
                  V_w(&pentry->lock);
                }
              *pstatus = CACHE_INODE_BAD_TYPE;
              return *pstatus;
              break;
            }
        }
    }
  else
    {
      /* No hardlink counter to be decremented for a directory: hardlink are not allowed for them */
    }

  /* Now, delete "to_remove_entry" from the cache inode and free its associated resources, but only if
   * numlinks == 0 */
  if(to_remove_numlinks == 0)
    {

      /* If pentry is a regular file, data cached, the related data cache entry should be removed as well */
      if(to_remove_entry->internal_md.type == REGULAR_FILE)
        {
          if(to_remove_entry->object.file.pentry_content != NULL)
            {
              /* Something is to be deleted, release the cache data entry */
              if(cache_content_release_entry
                 ((cache_content_entry_t *) to_remove_entry->object.file.pentry_content,
                  (cache_content_client_t *) pclient->pcontent_client,
                  &cache_content_status) != CACHE_CONTENT_SUCCESS)
                {
                  LogEvent(COMPONENT_CACHE_INODE,
                           "pentry %p, named %s could not be released from data cache, status=%d",
                           to_remove_entry, pnode_name->name,
                           cache_content_status);
                }
            }
        }
	
      if((*pstatus =
      	cache_inode_clean_internal(to_remove_entry, ht,
					 pclient)) != CACHE_INODE_SUCCESS)
    	{
      	  if(use_mutex)
	  {
	    V_w(&pentry->lock);
	    V_w(&to_remove_entry->lock);
	  }

      	  LogCrit(COMPONENT_CACHE_INODE,
	   	   "cache_inode_clean_internal ERROR %d", *pstatus);
      	return *pstatus;
      }

      /* Finally put the main pentry back to pool */
      if(use_mutex)
        V_w(&to_remove_entry->lock);

      /* Destroy the mutex associated with the pentry */
      cache_inode_mutex_destroy(to_remove_entry);

      ReleaseToPool(to_remove_entry, &pclient->pool_entry);

    } /* to_remove->numlinks == 0 */

  /* Validate the entries */
  *pstatus = cache_inode_valid(pentry, CACHE_INODE_OP_SET, pclient);

  /* Regular exit */
  if(use_mutex)
    {
      if(to_remove_numlinks != 0)
        V_w(&to_remove_entry->lock);    /* This was not release yet, it should be done here */

      V_w(&pentry->lock);
    }

  if(status == CACHE_INODE_SUCCESS)
      (pclient->stat.func_stats.nb_success[CACHE_INODE_REMOVE])++;
  else
      (pclient->stat.func_stats.nb_err_unrecover[CACHE_INODE_REMOVE])++;

  return status;
}                               /* cache_inode_remove */
cache_inode_status_t
cache_inode_remove(cache_entry_t *entry, const char *name)
{
	cache_entry_t *to_remove_entry = NULL;
	fsal_status_t fsal_status = { 0, 0 };
	cache_inode_status_t status = CACHE_INODE_SUCCESS;
	cache_inode_status_t status_ref_entry = CACHE_INODE_SUCCESS;

	if (entry->type != DIRECTORY) {
		status = CACHE_INODE_NOT_A_DIRECTORY;
		goto out;
	}

	/* Factor this somewhat.  In the case where the directory hasn't
	   been populated, the entry may not exist in the cache and we'd
	   be bringing it in just to dispose of it. */

	/* Looks up for the entry to remove */
	status =
	    cache_inode_lookup_impl(entry, name, &to_remove_entry);

	if (to_remove_entry == NULL) {
		LogFullDebug(COMPONENT_CACHE_INODE, "lookup %s failure %s",
			     name, cache_inode_err_str(status));
		goto out;
	}

	/* Do not remove a junction node or an export root. */
	if (to_remove_entry->type == DIRECTORY) {
		/* Get attr_lock for looking at junction_export */
		PTHREAD_RWLOCK_rdlock(&to_remove_entry->attr_lock);

		if (to_remove_entry->object.dir.junction_export != NULL ||
		    atomic_fetch_int32_t(&to_remove_entry->exp_root_refcount)
		    != 0) {
			/* Trying to remove an export mount point */
			LogCrit(COMPONENT_CACHE_INODE,
				 "Attempt to remove export %s",
				 name);

			/* Release attr_lock */
			PTHREAD_RWLOCK_unlock(&to_remove_entry->attr_lock);

			status = CACHE_INODE_DIR_NOT_EMPTY;
			goto out;
		}

		/* Release attr_lock */
		PTHREAD_RWLOCK_unlock(&to_remove_entry->attr_lock);
	}

	LogDebug(COMPONENT_CACHE_INODE, "%s", name);

	if (is_open(to_remove_entry)) {
		/* entry is not locked and seems to be open for fd caching
		 * purpose.
		 * candidate for closing since unlink of an open file results
		 * in 'silly rename' on certain platforms */
		status =
		    cache_inode_close(to_remove_entry,
				      CACHE_INODE_FLAG_REALLYCLOSE);
		if (status != CACHE_INODE_SUCCESS) {
			/* non-fatal error. log the warning and move on */
			LogCrit(COMPONENT_CACHE_INODE,
				"Error closing %s before unlink: %s.", name,
				cache_inode_err_str(status));
		}
	}

	fsal_status =
	    entry->obj_handle->obj_ops.unlink(entry->obj_handle, name);

	if (FSAL_IS_ERROR(fsal_status)) {
		if (fsal_status.major == ERR_FSAL_STALE)
			cache_inode_kill_entry(entry);

		status = cache_inode_error_convert(fsal_status);

		LogFullDebug(COMPONENT_CACHE_INODE, "unlink %s failure %s",
			     name, cache_inode_err_str(status));

		if (to_remove_entry->type == DIRECTORY
		    && status == CACHE_INODE_DIR_NOT_EMPTY) {
			/* its dirent tree is probably stale, flush it
			 * to try and make things right again */
			PTHREAD_RWLOCK_wrlock(&to_remove_entry->content_lock);
			(void)
			    cache_inode_invalidate_all_cached_dirent
			    (to_remove_entry);
			PTHREAD_RWLOCK_unlock(&to_remove_entry->content_lock);
		}
		goto out;
	}

	/* Remove the entry from parent dir_entries avl */
	PTHREAD_RWLOCK_wrlock(&entry->content_lock);
	status_ref_entry = cache_inode_remove_cached_dirent(entry, name);
	LogDebug(COMPONENT_CACHE_INODE,
		 "cache_inode_remove_cached_dirent %s status %s", name,
		 cache_inode_err_str(status_ref_entry));
	PTHREAD_RWLOCK_unlock(&entry->content_lock);

	status_ref_entry = cache_inode_refresh_attrs_locked(entry);

	if (FSAL_IS_ERROR(fsal_status)) {
		status = cache_inode_error_convert(fsal_status);
		LogFullDebug(COMPONENT_CACHE_INODE,
			     "not sure this code makes sense %s failure %s",
			     name, cache_inode_err_str(status));
		goto out;
	}

	/* Update the attributes for the removed entry */
	(void)cache_inode_refresh_attrs_locked(to_remove_entry);

	status = status_ref_entry;
	if (status != CACHE_INODE_SUCCESS) {
		LogDebug(COMPONENT_CACHE_INODE,
			 "cache_inode_refresh_attrs_locked(entry %p %s) "
			 "returned %s", entry, name,
			 cache_inode_err_str(status_ref_entry));
	}

out:
	LogFullDebug(COMPONENT_CACHE_INODE, "remove %s: status=%s", name,
		     cache_inode_err_str(status));

	/* This is for the reference taken by lookup */
	if (to_remove_entry)
		cache_inode_put(to_remove_entry);

	return status;
}