/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright (c) 2013 by Delphix. All rights reserved.
* Copyright 2014 Nexenta Systems, Inc. All rights reserved.
*/
#include <sys/sysmacros.h>
#include <sys/resource.h>
#include <sys/zfs_znode.h>
#include <sys/zfs_fuid.h>
#include <sys/zfs_vfsops.h>
#include <acl/acl_common.h>
static uint16_t
{
}
static uint16_t
{
}
static uint32_t
{
}
static uint64_t
{
}
static void
{
}
static void
{
}
static void
{
}
static void
{
}
/*ARGSUSED*/
static size_t
{
return (sizeof (zfs_oldace_t));
}
static size_t
zfs_ace_v0_abstract_size(void)
{
return (sizeof (zfs_oldace_t));
}
static int
zfs_ace_v0_mask_off(void)
{
}
/*ARGSUSED*/
static int
{
return (0);
}
};
static uint16_t
{
}
static uint16_t
{
}
static uint32_t
{
}
static uint64_t
{
return (-1);
}
static void
{
}
static void
{
}
static void
{
}
static void
{
return;
}
static size_t
{
return (sizeof (zfs_object_ace_t));
case ALLOW:
case DENY:
if (entry_type == ACE_OWNER ||
entry_type == OWNING_GROUP ||
return (sizeof (zfs_ace_hdr_t));
/*FALLTHROUGH*/
default:
return (sizeof (zfs_ace_t));
}
}
static size_t
{
return (sizeof (zfs_ace_hdr_t));
}
static int
zfs_ace_fuid_mask_off(void)
{
}
static int
{
return (sizeof (zfs_object_ace_t) - sizeof (zfs_ace_t));
default:
return (0);
}
}
};
/*
* The following three functions are provided for compatibility with
* older ZPL version in order to determine if the file use to have
* an external ACL and what version of ACL previously existed on the
* file. Would really be nice to not need this, sigh.
*/
{
int error;
return (0);
/*
* Need to deal with a potential
* race where zfs_sa_upgrade could cause
* z_isa_sa to change.
*
* If the lookup fails then the state of z_is_sa should have
* changed.
*/
return (acl_phys.z_acl_extern_obj);
else {
/*
* after upgrade the SA_ZPL_ZNODE_ACL should have been
* removed
*/
return (0);
}
}
/*
* Determine size of ACL in bytes
*
* This is more complicated than it should be since we have to deal
* with old external ACLs.
*/
static int
{
int size;
int error;
&size)) != 0)
return (error);
return (error);
} else {
return (error);
} else {
}
}
return (0);
}
int
{
return (ZFS_ACL_VERSION_FUID);
else {
int error;
/*
* Need to deal with a potential
* race where zfs_sa_upgrade could cause
* z_isa_sa to change.
*
* If the lookup fails then the state of z_is_sa should have
* changed.
*/
return (acl_phys.z_acl_version);
else {
/*
* After upgrade SA_ZPL_ZNODE_ACL should have
* been removed.
*/
return (ZFS_ACL_VERSION_FUID);
}
}
}
static int
{
if (version < ZPL_VERSION_FUID)
return (ZFS_ACL_VERSION_INITIAL);
else
return (ZFS_ACL_VERSION_FUID);
}
static int
{
}
{
if (vers == ZFS_ACL_VERSION_FUID)
else
return (aclp);
}
{
if (bytes) {
}
return (aclnode);
}
static void
{
if (aclnode->z_allocsize)
}
static void
{
}
aclp->z_acl_count = 0;
aclp->z_acl_bytes = 0;
}
void
{
}
static boolean_t
{
switch (type) {
case ALLOW:
case DENY:
return (entry_type == ACE_OWNER ||
entry_type == OWNING_GROUP ||
default:
return (B_TRUE);
}
return (B_FALSE);
}
static boolean_t
{
/*
* first check type of entry
*/
return (B_FALSE);
switch (type) {
return (B_FALSE);
}
/*
* next check inheritance level flags
*/
if ((iflags & (ACE_FILE_INHERIT_ACE|
ACE_DIRECTORY_INHERIT_ACE)) == 0) {
return (B_FALSE);
}
}
return (B_TRUE);
}
static void *
{
return (NULL);
}
return (NULL);
return (NULL);
else {
}
}
/*
* Make sure we don't overstep our bounds
*/
return (NULL);
}
return ((void *)acep);
}
return (NULL);
}
/*ARGSUSED*/
static uint64_t
{
}
static zfs_acl_node_t *
{
return (aclp->z_curr_node);
}
/*
* Copy ACE to internal ZFS format.
* While processing the ACL each ACE will be validated for correctness.
* ACE FUIDs will be created later.
*/
int
{
int i;
for (i = 0; i != aclcnt; i++) {
entry_type != ACE_EVERYONE) {
cr, (entry_type == 0) ?
}
/*
* Make sure ACE is valid
*/
sizeof (aceobjp->a_obj_type));
sizeof (aceobjp->a_inherit_obj_type));
break;
default:
}
}
return (0);
}
/*
* Copy ZFS ACEs to fixed size ace_t layout
*/
static void
{
switch (type) {
if (filter) {
continue;
}
sizeof (zobjacep->z_object_type));
sizeof (zobjacep->z_inherit_type));
ace_size = sizeof (ace_object_t);
break;
default:
break;
}
if ((entry_type != ACE_OWNER &&
entry_type != OWNING_GROUP &&
entry_type != ACE_EVERYONE)) {
} else {
}
}
}
static int
{
int i;
/*
* Make sure ACE is valid
*/
}
return (0);
}
/*
* convert old ACL format to new
*/
void
{
int i;
/*
* First create the ACE in a contiguous piece of memory
* for zfs_copy_ace_2_fuid().
*
* We only convert an ACL once, so this won't happen
* everytime.
*/
KM_SLEEP);
i = 0;
}
sizeof (zfs_object_ace_t));
/*
* Release all previous ACL nodes
*/
}
/*
* Convert unix access mask to v4 access mask
*/
static uint32_t
{
if (access_mask & S_IXOTH)
new_mask |= ACE_EXECUTE;
if (access_mask & S_IWOTH)
if (access_mask & S_IROTH)
return (new_mask);
}
static void
{
type != ACE_EVERYONE))
}
/*
* Determine mode of file based on ACL.
*/
{
int entry_type;
continue;
/*
* Skip over any inherit_only ACEs
*/
if (iflags & ACE_INHERIT_ONLY_ACE)
continue;
if ((access_mask & ACE_READ_DATA) &&
}
}
if ((access_mask & ACE_WRITE_DATA) &&
}
}
if ((access_mask & ACE_EXECUTE) &&
}
}
} else if (entry_type == OWNING_GROUP ||
if ((access_mask & ACE_READ_DATA) &&
}
}
if ((access_mask & ACE_WRITE_DATA) &&
}
}
if ((access_mask & ACE_EXECUTE) &&
}
}
} else if (entry_type == ACE_EVERYONE) {
if ((access_mask & ACE_READ_DATA)) {
}
}
}
}
}
}
}
if ((access_mask & ACE_WRITE_DATA)) {
}
}
}
}
}
}
}
if ((access_mask & ACE_EXECUTE)) {
}
}
}
}
}
}
}
} else {
/*
* Only care if this IDENTIFIER_GROUP or
* USER ACE denies execute access to someone,
* mode is not affected
*/
}
}
/*
* Failure to allow is effectively a deny, so execute permission
* is denied if it was never mentioned or if we explicitly
* weren't allowed it.
*/
if (!an_exec_denied &&
if (an_exec_denied)
*pflags &= ~ZFS_NO_EXECS_DENIED;
else
return (mode);
}
/*
* Read an external acl object. If the intent is to modify, always
* create a new acl and leave any cached acl in place.
*/
static int
{
int aclsize;
int acl_count;
int version;
int error;
return (0);
}
/*
* close race where znode could be upgrade while trying to
* read the znode attributes.
*
* But this could only happen if the file isn't already an SA
* znode
*/
}
goto done;
}
if (znode_acl.z_acl_extern_obj) {
} else {
}
} else {
}
if (error != 0) {
/* convert checksum errors into IO errors */
goto done;
}
if (!will_modify)
done:
if (drop_lock)
return (error);
}
/*ARGSUSED*/
void
{
if (start) {
} else {
cb->cb_acl_node);
}
}
int
{
int error;
return (error);
}
/*
* common code for setting ACLs.
*
* This function is called from zfs_mode_update, zfs_perm_init, and zfs_setacl.
* zfs_setacl passes a non-NULL inherit pointer (ihp) to indicate that it's
* already checked the acl and knows whether to inherit.
*/
int
{
int error;
int count = 0;
if (zp->z_acl_cached) {
}
/*
* Upgrade needed?
*/
if (!zfsvfs->z_use_fuids) {
} else {
otype = DMU_OT_ACL;
}
/*
* Arrgh, we have to handle old on disk format
* as well as newer (preferred) SA format.
*/
} else { /* Painful legacy way */
return (error);
/*
* If ACL was previously external and we are now
* converting to new ACL format then release old
* ACL object and create a new one.
*/
if (aoid &&
if (error)
return (error);
aoid = 0;
}
if (aoid == 0) {
otype == DMU_OT_ACL ?
otype == DMU_OT_ACL ?
DN_MAX_BONUSLEN : 0, tx);
} else {
}
if (aclnode->z_ace_count == 0)
continue;
}
} else {
/*
* Migrating back embedded?
*/
if (acl_phys.z_acl_extern_obj) {
if (error)
return (error);
acl_phys.z_acl_extern_obj = 0;
}
if (aclnode->z_ace_count == 0)
continue;
}
}
/*
* layout of znode_acl_phys_t.
*/
} else {
}
}
/*
* Replace ACL wide bits, but first clear them.
*/
}
static void
{
int ace_size;
int entry_type;
void *zacep;
new_count++;
}
new_count++;
}
new_count++;
}
/*
* ACEs used to represent the file mode may be divided
* into an equivalent pair of inherit-only and regular
* ACEs, if they are inheritable.
* Skip regular ACEs, which are replaced by the new mode.
*/
entry_type == OWNING_GROUP ||
entry_type == ACE_EVERYONE)) {
continue;
/*
* We preserve owner@, group@, or @everyone
* permissions, if they are inheritable, by
* copying them to inherit_only ACEs. This
* prevents inheritable permissions from being
* altered along with the file mode.
*/
}
/*
* If this ACL has any inheritable ACEs, mark that in
* the hints (which are later masked into the pflags)
* so create knows to do inheritance.
*/
(iflags & ACE_INHERIT_ONLY_ACE)) {
switch (type) {
break;
}
} else {
/*
* Limit permissions granted by ACEs to be no greater
* than permissions of the requested group mode.
* Applies when the "aclmode" property is set to
* "groupmask".
*/
}
new_count++;
}
new_count += 3;
}
int
{
int error = 0;
else
if (error == 0) {
}
return (error);
}
/*
* Should ACE be inherited?
*/
static int
{
return (1);
else if (iflags & ACE_FILE_INHERIT_ACE)
return (0);
}
/*
* inherit inheritable ACEs from parent
*/
static zfs_acl_t *
{
void *acep;
return (aclp);
/*
* don't inherit bogus ACEs
*/
continue;
/*
* Check if ACE is inheritable by this vnode
*/
continue;
/*
* Strip inherited execute permission from file if
* not in mode
*/
access_mask &= ~ACE_EXECUTE;
}
/*
* Strip write_acl and write_owner from permissions
* when inheriting an ACE
*/
}
/*
* Copy special opaque data if any
*/
}
aclp->z_acl_count++;
aclnode->z_ace_count++;
/*
* If ACE is not to be inherited further, or if the vnode is
* not a directory, remove all inheritance flags
*/
newflags &= ~ALL_INHERIT;
continue;
}
/*
* This directory has an inheritable ACE
*/
/*
* If only FILE_INHERIT is set then turn on
* inherit_only
*/
if ((iflags & (ACE_FILE_INHERIT_ACE |
} else {
}
}
return (aclp);
}
/*
* Create file system object initial permissions
* including inheritable ACEs.
* Also, create FUIDs for owner and group.
*/
int
{
int error;
if (vsecp)
return (error);
/*
* Determine uid and gid.
*/
} else {
secpolicy_vnode_create_gid(cr) != 0)
}
char *domain;
if (zfsvfs->z_use_fuids &&
&zfsvfs->z_fuid_idx,
}
} else {
}
}
}
/*
* If we're creating a directory, and the parent directory has the
* set-GID bit set, set in on the new directory.
* Otherwise, if the user is neither privileged nor a member of the
* file's new group, clear the file's set-GID bit.
*/
} else {
}
if (!(flag & IS_ROOT_NODE) &&
} else {
}
}
}
return (0);
}
/*
* Free ACL and fuid_infop, but not the acl_ids structure
*/
void
{
}
{
}
/*
* Retrieve a file's ACL
*/
int
{
int error;
int count = 0;
int largeace = 0;
if (mask == 0)
return (error);
if (error != 0) {
return (error);
}
/*
* Scan ACL to determine number of ACEs
*/
switch (type) {
largeace++;
continue;
default:
count++;
}
}
} else
if (mask & VSA_ACECNT) {
}
sizeof (ace_object_t) * largeace;
else {
}
aclp->z_acl_bytes);
}
}
if (mask & VSA_ACE_ACLFLAGS) {
vsecp->vsa_aclflags = 0;
}
return (0);
}
int
{
int error;
return (error);
}
} else {
return (error);
}
}
/*
* If flags are being set then add them to z_hints
*/
}
return (0);
}
/*
* Set a file's ACL
*/
int
{
int error;
if (mask == 0)
return (error);
&aclp);
if (error)
return (error);
/*
* If ACL wide flags aren't being set then preserve any
* existing flags.
*/
}
top:
if (fuid_dirtied)
/*
* If old version and ACL won't fit in bonus and we aren't
* upgrading then take out necessary DMU holds
*/
aclp->z_acl_bytes);
} else {
}
}
if (error) {
goto top;
}
return (error);
}
if (fuid_dirtied)
if (fuidp)
done:
return (error);
}
/*
* Check accesses of interest (AoI) against attributes of the dataset
* such as read-only. Returns zero if no AoI conflict with dataset
* attributes, otherwise an appropriate errno is returned.
*/
static int
{
if ((v4_mode & WRITE_MASK) &&
}
/*
* Only check for READONLY on non-directories.
*/
if ((v4_mode & WRITE_MASK_DATA) &&
}
}
}
return (0);
}
/*
* The primary usage of this function is to loop through all of the
* ACEs in the znode, determining what accesses of interest (AoI) to
* the caller are allowed or denied. The AoI are expressed as bits in
* the working_mode parameter. As each ACE is processed, bits covered
* by that ACE are removed from the working_mode. This removal
* facilitates two things. The first is that when the working mode is
* empty (= 0), we know we've looked at all the AoI. The second is
* that the ACE interpretation rules don't allow a later ACE to undo
* something granted or denied by an earlier ACE. Removing the
* discovered access or denial enforces this rule. At the end of
* processing the ACEs, all AoI that were found to be denied are
* placed into the working_mode, giving the caller a mask of denied
* accesses. Returns:
* 0 if all AoI granted
* EACCES if the denied mask is non-zero
* other error if abnormal failure (e.g., IO error)
*
* A secondary usage of the function is to determine if any of the
* AoI are granted. If an ACE grants any access in
* the working_mode, we immediately short circuit out of the function.
* This mode is chosen by setting anyaccess to B_TRUE. The
* working_mode is not a denied access mask upon exit if the function
* is used in this manner.
*/
static int
{
int error;
if (error != 0) {
return (error);
}
continue;
continue;
/* Skip ACE if it does not affect any AoI */
if (!mask_matched)
continue;
switch (entry_type) {
case ACE_OWNER:
break;
case OWNING_GROUP:
/*FALLTHROUGH*/
case ACE_IDENTIFIER_GROUP:
break;
case ACE_EVERYONE:
break;
/* USER Entry */
default:
if (entry_type == 0) {
if (newid != IDMAP_WK_CREATOR_OWNER_UID &&
break;
} else {
}
}
if (checkit) {
zfs_ace_hdr_t *, acep,
} else {
zfs_ace_hdr_t *, acep,
if (anyaccess) {
return (0);
}
}
*working_mode &= ~mask_matched;
}
/* Are we done? */
if (*working_mode == 0)
break;
}
/* Put the found 'denies' back on the working mode */
if (deny_mask) {
*working_mode |= deny_mask;
} else if (*working_mode) {
return (-1);
}
return (0);
}
/*
* Return true if any access whatsoever granted, we don't actually
* care what access is granted.
*/
{
}
return (B_TRUE);
}
static int
{
int err;
*working_mode = v4_mode;
*check_privs = B_TRUE;
/*
* Short circuit empty requests
*/
*working_mode = 0;
return (0);
}
*check_privs = B_FALSE;
return (err);
}
/*
* The caller requested that the ACL check be skipped. This
* would only happen if the caller checked VOP_ACCESS() with a
* 32 bit ACE mask and already had the appropriate permissions.
*/
if (skipaclchk) {
*working_mode = 0;
return (0);
}
}
static int
{
if (*working_mode != ACE_WRITE_DATA)
}
int
{
int error;
if (is_attr)
goto slow;
return (0);
}
goto slow;
}
return (0);
} else {
goto slow;
}
}
return (0);
} else {
goto slow;
}
}
return (0);
}
}
slow:
return (error);
}
/*
*
* The least priv subsytem is always consulted as a basic privilege
* can define any form of access.
*/
int
{
int error;
int is_attr;
/*
* If attribute then validate against base file
*/
if (is_attr) {
sizeof (parent))) != 0)
return (error);
return (error);
}
/*
* fixup mode to map to xattr perms
*/
}
}
}
/*
* Map the bits required to the standard vnode flags VREAD|VWRITE|VEXEC
* in needed_bits. Map the bits mapped by working_mode (currently
* missing) in missing_bits.
* Call secpolicy_vnode_access2() with (needed_bits & ~checkmode),
* needed_bits.
*/
needed_bits = 0;
working_mode = mode;
needed_bits |= VREAD;
needed_bits |= VWRITE;
if (working_mode & ACE_EXECUTE)
needed_bits |= VEXEC;
if (is_attr)
}
if (error && !check_privs) {
if (is_attr)
return (error);
}
}
if (error && check_privs) {
/*
* First check for implicit owner permission on
*/
error = 0;
ASSERT(working_mode != 0);
if (working_mode & ACE_EXECUTE)
if (error == 0 && (working_mode &
}
if (error == 0) {
/*
* See if any bits other than those already checked
* for are still present. If so then return EACCES
*/
if (working_mode & ~(ZFS_CHECKED_MASKS)) {
}
}
} else if (error == 0) {
}
if (is_attr)
return (error);
}
/*
* native ACL format and call zfs_zaccess()
*/
int
{
}
/*
* Access function for secpolicy_vnode_setattr
*/
int
{
}
/* See zfs_zaccess_delete() */
/*
* Determine whether delete access should be granted.
*
* The following chart outlines how we handle delete permissions which is
* how recent versions of windows (Windows 2008) handles it. The efficiency
* comes from not having to check the parent ACL where the object itself grants
* delete:
*
* -------------------------------------------------------
* | Parent Dir | Target Object Permissions |
* | permissions | |
* -------------------------------------------------------
* | | ACL Allows | ACL Denies| Delete |
* | | Delete | Delete | unspecified|
* -------------------------------------------------------
* | ACL Allows | Permit | Deny * | Permit |
* | DELETE_CHILD | | | |
* -------------------------------------------------------
* | ACL Denies | Permit | Deny | Deny |
* | DELETE_CHILD | | | |
* -------------------------------------------------------
* | ACL specifies | | | |
* | only allow | Permit | Deny * | Permit |
* | write and | | | |
* | execute | | | |
* -------------------------------------------------------
* | ACL denies | | | |
* | write and | Permit | Deny | Deny |
* | execute | | | |
* -------------------------------------------------------
* ^
* |
* Re. execute permission on the directory: if that's missing,
* the vnode lookup of the target will fail before we get here.
*
* Re [*] in the table above: NFSv4 would normally Permit delete for
* these two cells of the matrix.
* See acl.h for notes on which ACE_... flags should be checked for which
* operations. Specifically, the NFSv4 committee recommendation is in
* conflict with the Windows interpretation of DENY ACEs, where DENY ACEs
* should take precedence ahead of ALLOW ACEs.
*
* This implementation always consults the target object's ACL first.
* If a DENY ACE is present on the target object that specifies ACE_DELETE,
* delete access is denied. If an ALLOW ACE with ACE_DELETE is present on
* the target object, access is allowed. If and only if no entries with
* ACE_DELETE are present in the object's ACL, check the container's ACL
* for entries with ACE_DELETE_CHILD.
*
* A summary of the logic implemented from the table above is as follows:
*
* First check for DENY ACEs that apply.
* If either target or container has a deny, EACCES.
*
* Delete access can then be summarized as follows:
* 1: The object to be deleted grants ACE_DELETE, or
* 2: The containing directory grants ACE_DELETE_CHILD.
* In a Windows system, that would be the end of the story.
* In this system, (2) has some complications...
* 2a: "sticky" bit on a directory adds restrictions, and
* 2b: existing ACEs from previous versions of ZFS may
* not carry ACE_DELETE_CHILD where they should, so we
* also allow delete when ACE_WRITE_DATA is granted.
*
* Note: 2b is technically a work-around for a prior bug,
* which hopefully can go away some day. For those who
* no longer need the work around, and for testing, this
* work-around is made conditional via the tunable:
* zfs_write_implies_delete_child
*/
int
{
/*
* Case 1:
* If target object grants ACE_DELETE then we are done. This is
* indicated by a return value of 0. For this case we don't worry
* about the sticky bit because sticky only applies to the parent
* directory and this is the child access result.
*
* If we encounter a DENY ACE here, we're also done (EACCES).
* Note that if we hit a DENY ACE here (on the target) it should
* take precedence over a DENY ACE on the container, so that when
* we have more complete auditing support we will be able to
* report an access failure against the specific target.
* (This is part of why we're checking the target first.)
*/
/* We hit a DENY ACE. */
if (!zpcheck_privs)
return (secpolicy_vnode_remove(cr));
}
if (zp_error == 0)
return (0);
/*
* Case 2:
* If the containing directory grants ACE_DELETE_CHILD,
* or we're in backward compatibility mode and the
* containing directory has ACE_WRITE_DATA, allow.
* Case 2b is handled with wanted_dirperms.
*/
/* We hit a DENY ACE. */
if (!dzpcheck_privs)
return (secpolicy_vnode_remove(cr));
}
/*
* Cases 2a, 2b (continued)
*
* Note: dzp_working_mode now contains any permissions
* that were NOT granted. Therefore, if any of the
* wanted_dirperms WERE granted, we will have:
* dzp_working_mode != wanted_dirperms
* We're really asking if ANY of those permissions
* were granted, and if so, grant delete access.
*/
if (dzp_working_mode != wanted_dirperms)
dzp_error = 0;
/*
* dzp_error is 0 if the container granted us permissions to "modify".
* If we do not have permission via one or more ACEs, our current
* privileges may still permit us to modify the container.
*
* dzpcheck_privs is false when i.e. the FS is read-only.
* Otherwise, do privilege checks for the container.
*/
if (dzp_error != 0 && dzpcheck_privs) {
/*
* The secpolicy call needs the requested access and
* the current access mode of the container, but it
* only knows about Unix-style modes (VEXEC, VWRITE),
* so this must condense the fine-grained ACE bits into
* Unix modes.
*
* The VEXEC flag is easy, because we know that has
* always been checked before we get here (during the
* lookup of the target vnode). The container has not
* granted us permissions to "modify", so we do not set
* the VWRITE flag in the current access mode.
*/
}
if (dzp_error != 0) {
/*
* Note: We may have dzp_error = -1 here (from
* zfs_zacess_common). Don't return that.
*/
}
/*
* At this point, we know that the directory permissions allow
* us to modify, but we still need to check for the additional
* restrictions that apply when the "sticky bit" is set.
*
* Yes, zfs_sticky_remove_access() also checks this bit, but
* checking it here and skipping the call below is nice when
* you're watching all of this with dtrace.
*/
return (0);
/*
* zfs_sticky_remove_access will succeed if:
* 1. The sticky bit is absent.
* 2. We pass the sticky bit restrictions.
* 3. We have privileges that always allow file removal.
*/
}
int
{
int add_perm;
int error;
/*
* Rename permissions are combination of delete permission +
*/
/*
* first make sure we do the delete portion.
*
* If that succeeds then check for add_file/add_subdir permissions
*/
return (error);
/*
* If we have a tzp, see if we can delete it?
*/
if (tzp) {
return (error);
}
/*
* Now check for add permissions
*/
return (error);
}