/**
 * @brief Set the attributes for a file.
 *
 * This function sets the attributes of a file, both in the cache and
 * in the underlying filesystem.
 *
 * @param[in]     entry   Entry whose attributes are to be set
 * @param[in,out] attr    Attributes to set/result of set
 *
 * @retval CACHE_INODE_SUCCESS if operation is a success
 */
cache_inode_status_t
cache_inode_setattr(cache_entry_t *entry,
		    struct attrlist *attr,
		    bool is_open_write)
{
	struct fsal_obj_handle *obj_handle = entry->obj_handle;
	fsal_status_t fsal_status = { 0, 0 };
	fsal_acl_t *saved_acl = NULL;
	fsal_acl_status_t acl_status = 0;
	cache_inode_status_t status = CACHE_INODE_SUCCESS;
	uint64_t before;
	const struct user_cred *creds = op_ctx->creds;

	/* True if we have taken the content lock on 'entry' */
	bool content_locked = false;

	if ((attr->mask & (ATTR_SIZE | ATTR4_SPACE_RESERVED))
	     && (entry->type != REGULAR_FILE)) {
		LogWarn(COMPONENT_CACHE_INODE,
			"Attempt to truncate non-regular file: type=%d",
			entry->type);
		status = CACHE_INODE_BAD_TYPE;
		goto out;
	}

	/* Is it allowed to change times ? */
	if (!op_ctx->fsal_export->exp_ops.fs_supports(op_ctx->fsal_export,
						    fso_cansettime)
	    &&
	    (FSAL_TEST_MASK
	     (attr->mask,
	      (ATTR_ATIME | ATTR_CREATION | ATTR_CTIME | ATTR_MTIME)))) {
		status = CACHE_INODE_INVALID_ARGUMENT;
		goto out;
	}

	/* Get wrlock on attr_lock and verify attrs */
	status = cache_inode_lock_trust_attrs(entry, true);
	if (status != CACHE_INODE_SUCCESS)
		return status;

	/* Do permission checks */
	status = cache_inode_check_setattr_perms(entry, attr, is_open_write);
	if (status != CACHE_INODE_SUCCESS)
		goto unlock;

	if (attr->mask & (ATTR_SIZE | ATTR4_SPACE_RESERVED)) {
		PTHREAD_RWLOCK_wrlock(&entry->content_lock);
		content_locked = true;
	}

	/* Test for the following condition from chown(2):
	 *
	 *     When the owner or group of an executable file are changed by an
	 *     unprivileged user the S_ISUID and S_ISGID mode bits are cleared.
	 *     POSIX does not specify whether this also should happen when
	 *     root does the chown(); the Linux behavior depends on the kernel
	 *     version.  In case of a non-group-executable file (i.e., one for
	 *     which the S_IXGRP bit is not set) the S_ISGID bit indicates
	 *     mandatory locking, and is not cleared by a chown().
	 *
	 */
	if (creds->caller_uid != 0 &&
	    (FSAL_TEST_MASK(attr->mask, ATTR_OWNER) ||
	     FSAL_TEST_MASK(attr->mask, ATTR_GROUP)) &&
	    ((entry->obj_handle->attrs->mode &
	      (S_IXOTH | S_IXUSR | S_IXGRP)) != 0) &&
	    ((entry->obj_handle->attrs->mode &
	      (S_ISUID | S_ISGID)) != 0)) {
		/* Non-priviledged user changing ownership on an executable
		 * file with S_ISUID or S_ISGID bit set, need to be cleared.
		 */
		if (!FSAL_TEST_MASK(attr->mask, ATTR_MODE)) {
			/* Mode wasn't being set, so set it now, start with
			 * the current attributes.
			 */
			attr->mode = entry->obj_handle->attrs->mode;
			FSAL_SET_MASK(attr->mask, ATTR_MODE);
		}

		/* Don't clear S_ISGID if the file isn't group executable.
		 * In that case, S_ISGID indicates mandatory locking and
		 * is not cleared by chown.
		 */
		if ((entry->obj_handle->attrs->mode & S_IXGRP) != 0)
			attr->mode &= ~S_ISGID;

		/* Clear S_ISUID. */
		attr->mode &= ~S_ISUID;
	}

	/* Test for the following condition from chmod(2):
	 *
	 *     If the calling process is not privileged (Linux: does not have
	 *     the CAP_FSETID capability), and the group of the file does not
	 *     match the effective group ID of the process or one of its
	 *     supplementary group IDs, the S_ISGID bit will be turned off,
	 *     but this will not cause an error to be returned.
	 *
	 * We test the actual mode being set before testing for group
	 * membership since that is a bit more expensive.
	 */
	if (creds->caller_uid != 0 &&
	    FSAL_TEST_MASK(attr->mask, ATTR_MODE) &&
	    (attr->mode & S_ISGID) != 0 &&
	    not_in_group_list(entry->obj_handle->attrs->group)) {
		/* Clear S_ISGID */
		attr->mode &= ~S_ISGID;
	}

	saved_acl = entry->obj_handle->attrs->acl;
	before = entry->obj_handle->attrs->change;
	fsal_status = obj_handle->obj_ops.setattrs(obj_handle, attr);
	if (FSAL_IS_ERROR(fsal_status)) {
		status = cache_inode_error_convert(fsal_status);
		if (fsal_status.major == ERR_FSAL_STALE) {
			LogEvent(COMPONENT_CACHE_INODE,
				 "FSAL returned STALE from setattrs");
			cache_inode_kill_entry(entry);
		}
		goto unlock;
	}
	fsal_status = obj_handle->obj_ops.getattrs(obj_handle);
	*attr = *entry->obj_handle->attrs;
	if (FSAL_IS_ERROR(fsal_status)) {
		status = cache_inode_error_convert(fsal_status);
		if (fsal_status.major == ERR_FSAL_STALE) {
			LogEvent(COMPONENT_CACHE_INODE,
				 "FSAL returned STALE from getattrs");
			cache_inode_kill_entry(entry);
		}
		goto unlock;
	}
	if (before == entry->obj_handle->attrs->change)
		entry->obj_handle->attrs->change++;
	/* 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);

	cache_inode_fixup_md(entry);

	/* Copy the complete set of new attributes out. */

	*attr = *entry->obj_handle->attrs;

	status = CACHE_INODE_SUCCESS;

unlock:
	if (content_locked)
		PTHREAD_RWLOCK_unlock(&entry->content_lock);
	PTHREAD_RWLOCK_unlock(&entry->attr_lock);

out:
	return status;
}
/**
 * @brief Set the attributes for a file.
 *
 * This function sets the attributes of a file, both in the cache and
 * in the underlying filesystem.
 *
 * @param[in]     entry   Entry whose attributes are to be set
 * @param[in,out] attr    Attributes to set/result of set
 *
 * @retval CACHE_INODE_SUCCESS if operation is a success
 */
cache_inode_status_t
cache_inode_setattr(cache_entry_t *entry,
		    struct attrlist *attr,
		    bool is_open_write)
{
	struct fsal_obj_handle *obj_handle = entry->obj_handle;
	fsal_status_t fsal_status = { 0, 0 };
	fsal_acl_t *saved_acl = NULL;
	fsal_acl_status_t acl_status = 0;
	cache_inode_status_t status = CACHE_INODE_SUCCESS;
	uint64_t before;

	/* True if we have taken the content lock on 'entry' */
	bool content_locked = false;

	if ((attr->mask & (ATTR_SIZE | ATTR4_SPACE_RESERVED))
	     && (entry->type != REGULAR_FILE)) {
		LogWarn(COMPONENT_CACHE_INODE,
			"Attempt to truncate non-regular file: type=%d",
			entry->type);
		status = CACHE_INODE_BAD_TYPE;
	}

	/* Is it allowed to change times ? */
	if (!op_ctx->fsal_export->ops->fs_supports(op_ctx->fsal_export,
						    fso_cansettime)
	    &&
	    (FSAL_TEST_MASK
	     (attr->mask,
	      (ATTR_ATIME | ATTR_CREATION | ATTR_CTIME | ATTR_MTIME)))) {
		status = CACHE_INODE_INVALID_ARGUMENT;
		goto out;
	}

	/* Get wrlock on attr_lock and verify attrs */
	status = cache_inode_lock_trust_attrs(entry, true);
	if (status != CACHE_INODE_SUCCESS)
		return status;

	/* Do permission checks */
	status = cache_inode_check_setattr_perms(entry, attr, is_open_write);
	if (status != CACHE_INODE_SUCCESS)
		goto unlock;

	if (attr->mask & (ATTR_SIZE | ATTR4_SPACE_RESERVED)) {
		PTHREAD_RWLOCK_wrlock(&entry->content_lock);
		content_locked = true;
	}

	saved_acl = obj_handle->attributes.acl;
	before = obj_handle->attributes.change;
	fsal_status = obj_handle->ops->setattrs(obj_handle, attr);
	if (FSAL_IS_ERROR(fsal_status)) {
		status = cache_inode_error_convert(fsal_status);
		if (fsal_status.major == ERR_FSAL_STALE) {
			LogEvent(COMPONENT_CACHE_INODE,
				 "FSAL returned STALE from truncate");
			cache_inode_kill_entry(entry);
		}
		goto unlock;
	}
	fsal_status = obj_handle->ops->getattrs(obj_handle);
	*attr = obj_handle->attributes;
	if (FSAL_IS_ERROR(fsal_status)) {
		status = cache_inode_error_convert(fsal_status);
		if (fsal_status.major == ERR_FSAL_STALE) {
			LogEvent(COMPONENT_CACHE_INODE,
				 "FSAL returned STALE from setattrs");
			cache_inode_kill_entry(entry);
		}
		goto unlock;
	}
	if (before == obj_handle->attributes.change)
		obj_handle->attributes.change++;
	/* 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);

	cache_inode_fixup_md(entry);

	/* Copy the complete set of new attributes out. */

	*attr = entry->obj_handle->attributes;

	status = CACHE_INODE_SUCCESS;

unlock:
	if (content_locked)
		PTHREAD_RWLOCK_unlock(&entry->content_lock);
	PTHREAD_RWLOCK_unlock(&entry->attr_lock);

out:
	return status;
}
cache_inode_status_t
cache_inode_setattr(cache_entry_t *entry,
                    fsal_attrib_list_t *attr,
                    fsal_op_context_t *context,
                    int is_open_write,
                    cache_inode_status_t *status)
{
     fsal_status_t fsal_status = {0, 0};
     int           got_content_lock = FALSE;
#ifdef _USE_NFS4_ACL
     fsal_acl_t *saved_acl = NULL;
     fsal_acl_status_t acl_status = 0;
#endif /* _USE_NFS4_ACL */

     if ((entry->type == UNASSIGNED) ||
         (entry->type == RECYCLED)) {
          LogWarn(COMPONENT_CACHE_INODE,
                  "WARNING: unknown source entry type: type=%d, "
                  "line %d in file %s", entry->type, __LINE__, __FILE__);
          *status = CACHE_INODE_BAD_TYPE;
          goto out;
     }

     if ((attr->asked_attributes & FSAL_ATTR_SIZE) &&
         (entry->type != REGULAR_FILE)) {
          LogWarn(COMPONENT_CACHE_INODE,
                   "Attempt to truncate non-regular file: type=%d",
                   entry->type);
          *status = CACHE_INODE_BAD_TYPE;
          goto out;
     }

     /* Get wrlock on attr_lock and verify attrs */
     *status = cache_inode_lock_trust_attrs(entry, context, TRUE);
     if(*status != CACHE_INODE_SUCCESS)
       return *status;

     /* Do permission checks */
     if(cache_inode_check_setattr_perms(entry,
                                        attr,
                                        context,
                                        is_open_write,
                                        status) != CACHE_INODE_SUCCESS)
       {
         goto unlock;
       }

     if (attr->asked_attributes & FSAL_ATTR_SIZE) {
          PTHREAD_RWLOCK_WRLOCK(&entry->content_lock);
          got_content_lock = TRUE;
     }

#ifdef _USE_NFS4_ACL
     saved_acl = entry->attributes.acl;
#endif /* _USE_NFS4_ACL */
     fsal_status = FSAL_setattrs(&entry->handle, context, attr,
                                 &entry->attributes);
     if (FSAL_IS_ERROR(fsal_status)) {
          *status = cache_inode_error_convert(fsal_status);
          if (fsal_status.major == ERR_FSAL_STALE) {
               LogEvent(COMPONENT_CACHE_INODE,
                       "FSAL returned STALE from setattrs");
               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);
     /* Copy the complete set of new attributes out. */

     *attr = entry->attributes;
     set_mounted_on_fileid(entry, attr, context->export_context->fe_export);

     *status = CACHE_INODE_SUCCESS;

unlock:
     if(got_content_lock) {
          PTHREAD_RWLOCK_UNLOCK(&entry->content_lock);
     }
     PTHREAD_RWLOCK_UNLOCK(&entry->attr_lock);

out:

     return *status;
} /* cache_inode_setattr */