]> git.saurik.com Git - apple/file_cmds.git/blobdiff - chmod/chmod_acl.c
file_cmds-116.9.tar.gz
[apple/file_cmds.git] / chmod / chmod_acl.c
diff --git a/chmod/chmod_acl.c b/chmod/chmod_acl.c
new file mode 100644 (file)
index 0000000..eacc60d
--- /dev/null
@@ -0,0 +1,799 @@
+/*
+ * Copyright (c) 1989, 1993, 1994
+ *     The Regents of the University of California.  All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ *    must display the following acknowledgement:
+ *     This product includes software developed by the University of
+ *     California, Berkeley and its contributors.
+ * 4. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+#include <err.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <errno.h>
+
+#include <membership.h>
+#include "chmod_acl.h"
+
+extern void usage(void);
+
+#ifdef __APPLE__
+static struct {
+       acl_perm_t      perm;
+       char            *name;
+       int             flags;
+#define ACL_PERM_DIR   (1<<0)
+#define ACL_PERM_FILE  (1<<1)
+} acl_perms[] = {
+       {ACL_READ_DATA,         "read",         ACL_PERM_FILE},
+       {ACL_LIST_DIRECTORY,    "list",         ACL_PERM_DIR},
+       {ACL_WRITE_DATA,        "write",        ACL_PERM_FILE},
+       {ACL_ADD_FILE,          "add_file",     ACL_PERM_DIR},
+       {ACL_EXECUTE,           "execute",      ACL_PERM_FILE},
+       {ACL_SEARCH,            "search",       ACL_PERM_DIR},
+       {ACL_DELETE,            "delete",       ACL_PERM_FILE | ACL_PERM_DIR},
+       {ACL_APPEND_DATA,       "append",       ACL_PERM_FILE},
+       {ACL_ADD_SUBDIRECTORY,  "add_subdirectory", ACL_PERM_DIR},
+       {ACL_DELETE_CHILD,      "delete_child", ACL_PERM_DIR},
+       {ACL_READ_ATTRIBUTES,   "readattr",     ACL_PERM_FILE | ACL_PERM_DIR},
+       {ACL_WRITE_ATTRIBUTES,  "writeattr",    ACL_PERM_FILE | ACL_PERM_DIR},
+       {ACL_READ_EXTATTRIBUTES, "readextattr", ACL_PERM_FILE | ACL_PERM_DIR},
+       {ACL_WRITE_EXTATTRIBUTES, "writeextattr", ACL_PERM_FILE | ACL_PERM_DIR},
+       {ACL_READ_SECURITY,     "readsecurity", ACL_PERM_FILE | ACL_PERM_DIR},
+       {ACL_WRITE_SECURITY,    "writesecurity", ACL_PERM_FILE | ACL_PERM_DIR},
+       {ACL_CHANGE_OWNER,      "chown",        ACL_PERM_FILE | ACL_PERM_DIR},
+       {0, NULL, 0}
+};
+
+static struct {
+       acl_flag_t      flag;
+       char            *name;
+       int             flags;
+} acl_flags[] = {
+       {ACL_ENTRY_INHERITED,           "inherited",            ACL_PERM_FILE | ACL_PERM_DIR},
+       {ACL_ENTRY_FILE_INHERIT,        "file_inherit",         ACL_PERM_DIR},
+       {ACL_ENTRY_DIRECTORY_INHERIT,   "directory_inherit",    ACL_PERM_DIR},
+       {ACL_ENTRY_LIMIT_INHERIT,       "limit_inherit",        ACL_PERM_FILE | ACL_PERM_DIR},
+       {ACL_ENTRY_ONLY_INHERIT,        "only_inherit",         ACL_PERM_DIR},
+       {0, NULL, 0}
+};
+
+/* TBD - Many of these routines could potentially be considered for
+ * inclusion in a library. If that is done, either avoid use of "err"
+ * and implement a better fall-through strategy in case of errors, 
+ * or use err_set_exit() and make various structures globals.
+ */
+
+/* Perform a name to uuid mapping - calls through to memberd */
+
+uuid_t *
+name_to_uuid(char *tok) {
+       struct passwd *tpass = NULL;
+       struct group *tgrp = NULL;
+       uuid_t *entryg = NULL;
+       char is_uid = 1;
+
+       if ((entryg = (uuid_t *) calloc(1,sizeof(uuid_t))) == NULL)
+               err(1, "Unable to allocate a uuid");
+       
+       tpass = getpwnam(tok);
+
+       if (tpass == NULL) {
+               tgrp = getgrnam(tok);
+               if (tgrp == NULL) {
+                       errno = EINVAL;
+                       err(1, "Unable to translate %s to a UID/GID", tok);
+               }
+               is_uid = 0;
+       }
+       
+       if (tpass) {
+               if (0 != mbr_uid_to_uuid(tpass->pw_uid, entryg)) {
+                       errno = EINVAL;
+                       err(1, "mbr_uid_to_uuid(): Unable to translate");
+               }
+       }
+       else {
+               if (0 != mbr_gid_to_uuid(tgrp->gr_gid, entryg)) {
+                       errno = EINVAL;
+                       err(1, "mbr_gid_to_uuid(): Unable to translate");
+               }
+       }
+       return entryg;
+}
+
+/* Convert an acl entry in string form to an acl_entry_t */
+int
+parse_entry(char *entrybuf, acl_entry_t newent) {
+       char *tok;
+       char *pebuf;
+       uuid_t *entryg;
+
+       acl_tag_t       tag;
+       acl_permset_t   perms;
+       acl_flagset_t   flags;
+       unsigned permcount = 0;
+       unsigned pindex = 0;
+
+       acl_get_permset(newent, &perms);
+       acl_get_flagset_np(newent, &flags);
+
+       pebuf = entrybuf;
+
+       tok = strsep(&pebuf, " ");
+       
+       if ((tok == NULL) || *tok == '\0') {
+               errno = EINVAL;
+               err(1, "Invalid entry format");
+       }
+
+       /* parse the name into a qualifier */
+       entryg = name_to_uuid(tok);
+
+       tok = strsep(&pebuf, " ");
+       if ((tok == NULL) || *tok == '\0') {
+               errno = EINVAL;
+               err(1, "Invalid entry format");
+       }
+
+       /* is the verb 'allow' or 'deny'? */
+       if (!strcmp(tok, "allow")) {
+               tag = ACL_EXTENDED_ALLOW;
+       } else if (!strcmp(tok, "deny")) {
+               tag = ACL_EXTENDED_DENY;
+       } else {
+               errno = EINVAL;
+               err(1, "Unknown tag type '%s'", tok);
+       }
+
+       /* parse permissions */
+       for (; (tok = strsep(&pebuf, ",")) != NULL;) {
+               if (*tok != '\0') {
+                       /* is it a permission? */
+                       for (pindex = 0; acl_perms[pindex].name != NULL; pindex++) {
+                               if (!strcmp(acl_perms[pindex].name, tok)) {
+                                       /* got one */
+                                       acl_add_perm(perms, acl_perms[pindex].perm);
+                                       permcount++;
+                                       goto found;
+                               }
+                       }
+                       /* is it a flag? */
+                       for (pindex = 0; acl_flags[pindex].name != NULL; pindex++) {
+                               if (!strcmp(acl_flags[pindex].name, tok)) {
+                                       /* got one */
+                                       acl_add_flag_np(flags, acl_flags[pindex].flag);
+                                       permcount++;
+                                       goto found;
+                               }
+                       }
+                       errno = EINVAL;
+                       err(1,"Invalid permission type %s", tok);
+               found:
+                       continue;
+               }
+       }
+       if (0 == permcount) {
+               errno = EINVAL;
+               err(1, "No permissions specified");
+       }
+       acl_set_tag_type(newent, tag);
+       acl_set_qualifier(newent, entryg);
+       acl_set_permset(newent, perms);
+       acl_set_flagset_np(newent, flags);
+       free(entryg);
+
+       return(0);
+}
+
+/* Convert one or more acl entries in string form to an acl_t */
+acl_t
+parse_acl_entries(const char *input) {
+       acl_t acl_input;
+       acl_entry_t newent;
+       char *inbuf;
+       char *oinbuf;
+
+       char **bufp, *entryv[ACL_MAX_ENTRIES];
+#if 0
+/* XXX acl_from_text(), when implemented, will presumably use the canonical 
+ * text representation format, which is what chmod should be using 
+ * We may need to add an entry number to the input
+ */
+       /* Translate the user supplied ACL entry */
+       /* acl_input = acl_from_text(input); */
+#else
+       inbuf = malloc(MAX_ACL_TEXT_SIZE);
+       
+       if (inbuf == NULL)
+               err(1, "malloc() failed");
+       strncpy(inbuf, input, MAX_ACL_TEXT_SIZE);
+       inbuf[MAX_ACL_TEXT_SIZE - 1] = '\0';
+
+       if ((acl_input = acl_init(1)) == NULL)
+               err(1, "acl_init() failed");
+
+       oinbuf = inbuf;
+
+       for (bufp = entryv; (*bufp = strsep(&oinbuf, "\n")) != NULL;)
+               if (**bufp != '\0') {
+                       if (0 != acl_create_entry(&acl_input, &newent))
+                               err(1, "acl_create_entry() failed");
+                       if (0 != parse_entry(*bufp, newent)) {
+                               errno = EINVAL;
+                               err(1, "Failed parsing entry %s", *bufp);
+                       }
+                       if (++bufp >= &entryv[ACL_MAX_ENTRIES - 1]) {
+                               errno = ERANGE;
+                               err(1, "Too many entries");
+                       }
+               }
+       
+       free(inbuf);
+       return acl_input;
+#endif /* #if 0 */
+}
+
+/* XXX No Libc support for inherited entries and generation determination yet */
+unsigned
+get_inheritance_level(acl_entry_t entry) {
+/* XXX to be implemented */
+       return 1;
+}
+
+/* Determine a "score" for an acl entry. The entry scores higher if it's
+ * tagged ACL_EXTENDED_DENY, and non-inherited entries are ranked higher
+ * than inherited entries.
+ */
+
+int
+score_acl_entry(acl_entry_t entry) {
+
+       acl_tag_t       tag;
+       acl_flagset_t   flags;
+       acl_permset_t   perms;
+       
+       int score = 0;
+
+       if (entry == NULL)
+               return (MINIMUM_TIER);
+
+       if (acl_get_tag_type(entry, &tag) != 0) {
+               err(1, "Malformed ACL entry, no tag present");
+       }
+       if (acl_get_flagset_np(entry, &flags) != 0){
+               err(1, "Unable to obtain flagset");
+       }
+       if (acl_get_permset(entry, &perms) != 0)
+               err(1, "Malformed ACL entry, no permset present");
+       
+       switch(tag) {
+       case ACL_EXTENDED_ALLOW:
+               break;
+       case ACL_EXTENDED_DENY:
+               score++;
+               break;
+       default:
+               errno = EINVAL;
+               err(1, "Unknown tag type present in ACL entry");
+               /* NOTREACHED */
+       }
+
+       if (acl_get_flag_np(flags, ACL_ENTRY_INHERITED))
+               score += get_inheritance_level(entry) * INHERITANCE_TIER;
+
+       return score;
+}
+
+int
+compare_acl_qualifiers(uuid_t *qa, uuid_t *qb) {
+       return bcmp(qa, qb, sizeof(uuid_t));
+}
+
+/* Compare two ACL permsets. 
+ *  Returns :
+ *  MATCH_SUBSET if bperms is a subset of aperms
+ *  MATCH_SUPERSET if bperms is a superset of aperms
+ *  MATCH_PARTIAL if the two permsets have a common subset
+ *  MATCH_EXACT if the two permsets are identical
+ *  MATCH_NONE if they are disjoint
+ */
+
+int
+compare_acl_permsets(acl_permset_t aperms, acl_permset_t bperms)
+{
+       int i;
+/* TBD Implement other match levels as needed */
+       for (i = 0; acl_perms[i].name != NULL; i++) {
+               if (acl_get_perm_np(aperms, acl_perms[i].perm) != 
+                   acl_get_perm_np(bperms, acl_perms[i].perm))
+                       return MATCH_NONE;
+       }
+       return MATCH_EXACT;
+}
+
+int
+compare_acl_flagsets(acl_flagset_t aflags, acl_flagset_t bflags)
+{
+       int i;
+/* TBD Implement other match levels as needed */
+       for (i = 0; acl_flags[i].name != NULL; i++) {
+               if (acl_get_flag_np(aflags, acl_flags[i].flag) != 
+                   acl_get_flag_np(bflags, acl_flags[i].flag))
+                       return MATCH_NONE;
+       }
+       return MATCH_EXACT;
+}
+
+/* Compares two ACL entries for equality */
+int
+compare_acl_entries(acl_entry_t a, acl_entry_t b)
+{
+       acl_tag_t atag, btag;
+       acl_permset_t aperms, bperms;
+       acl_flagset_t aflags, bflags;
+       int pcmp = 0, fcmp = 0;
+
+       if (0 != compare_acl_qualifiers(acl_get_qualifier(a), 
+                                       acl_get_qualifier(b)))
+               return MATCH_NONE;
+
+       if (0 != acl_get_tag_type(a, &atag))
+               err(1, "No tag type present in entry");
+       if (0!= acl_get_tag_type(b, &btag))
+               err(1, "No tag type present in entry");
+
+       if (atag != btag)
+               return MATCH_NONE;
+
+       if ((acl_get_permset(a, &aperms) != 0) ||
+           (acl_get_flagset_np(a, &aflags) != 0) ||
+           (acl_get_permset(b, &bperms) != 0) ||
+           (acl_get_flagset_np(b, &bflags) != 0))
+               err(1, "error fetching permissions");
+
+       pcmp = compare_acl_permsets(aperms, bperms);
+       fcmp = compare_acl_flagsets(aflags, bflags);
+
+       if ((pcmp == MATCH_NONE) || (fcmp == MATCH_NONE))
+               return(MATCH_PARTIAL);
+       else
+               return(MATCH_EXACT);
+}
+
+/* Verify that an ACL is in canonical order. Currently, the canonical
+ * form is:
+ * local deny
+ * local allow
+ * inherited deny (parent)
+ * inherited allow (parent)
+ * inherited deny (grandparent)
+ * inherited allow (grandparent)
+ * ...
+ */
+unsigned int
+is_canonical(acl_t acl) {
+       
+       unsigned aindex;
+       acl_entry_t entry;
+       int score = 0, next_score = 0;
+
+/* XXX - is a zero entry ACL in canonical form? */     
+       if (0 != acl_get_entry(acl, ACL_FIRST_ENTRY, &entry))
+               return 1;
+
+       score = score_acl_entry(entry);
+       
+       for (aindex = 0; acl_get_entry(acl, ACL_NEXT_ENTRY, &entry) == 0;
+            aindex++)  {
+               if (score < (next_score = score_acl_entry(entry)))
+                       return 0;
+               score = next_score;
+       }
+       return 1;
+}
+
+
+/* Iterate through an ACL, and find the canonical position for the
+ * specified entry 
+ */
+unsigned int
+find_canonical_position(acl_t acl, acl_entry_t modifier) {
+
+       acl_entry_t entry;
+       int mscore = 0;
+       unsigned mpos = 0;
+
+       /* Check if there's an entry with the same qualifier
+        * and tag type; if not, find the appropriate slot
+        * for the score.
+        */
+
+       if (0 != acl_get_entry(acl, ACL_FIRST_ENTRY, &entry))
+               return 0;
+
+       mscore = score_acl_entry(modifier);
+
+       while (mscore < score_acl_entry(entry)) {
+
+               mpos++;
+
+              if (0 != acl_get_entry(acl, ACL_NEXT_ENTRY, &entry))
+                      break;
+
+              }
+       return mpos;
+}
+
+int canonicalize_acl_entries(acl_t acl);
+
+/* For a given acl_entry_t "modifier", find the first exact or 
+ * partially matching entry from the specified acl_t acl
+ */
+
+int
+find_matching_entry (acl_t acl, acl_entry_t modifier, acl_entry_t *rentryp,
+                    unsigned match_inherited) {
+
+       acl_entry_t entry = NULL;
+
+       unsigned aindex;
+       int cmp, fcmp = MATCH_NONE;
+       
+       for (aindex = 0; 
+            acl_get_entry(acl, entry == NULL ? ACL_FIRST_ENTRY : 
+                          ACL_NEXT_ENTRY, &entry) == 0;
+            aindex++)  {
+               cmp = compare_acl_entries(entry, modifier);
+               if ((cmp == MATCH_EXACT) || (cmp == MATCH_PARTIAL)) {
+                       if (match_inherited) {
+                               acl_flagset_t eflags, mflags;
+                               
+                               if (0 != acl_get_flagset_np(modifier, &mflags))
+                                       err(1, "Unable to get flagset");
+                               
+                               if (0 != acl_get_flagset_np(entry, &eflags))
+                                       err(1, "Unable to get flagset");
+                                       
+                               if (acl_get_flag_np(mflags, ACL_ENTRY_INHERITED) ==
+                                   acl_get_flag_np(eflags, ACL_ENTRY_INHERITED)) {
+                                       *rentryp = entry;
+                                       fcmp = cmp;
+                               }
+                       }
+                       else {
+                               *rentryp = entry;
+                               fcmp = cmp;
+                       }
+               }
+               if (fcmp == MATCH_EXACT)
+                       break;
+       }
+       return fcmp;
+}
+
+/* Remove all perms specified in modifier from rentry*/
+int
+subtract_from_entry(acl_entry_t rentry, acl_entry_t  modifier)
+{
+       acl_permset_t rperms, mperms;
+       acl_flagset_t rflags, mflags;
+       int i;
+
+       if ((acl_get_permset(rentry, &rperms) != 0) ||
+           (acl_get_flagset_np(rentry, &rflags) != 0) ||
+           (acl_get_permset(modifier, &mperms) != 0) ||
+           (acl_get_flagset_np(modifier, &mflags) != 0))
+               err(1, "error computing ACL modification");
+
+       for (i = 0; acl_perms[i].name != NULL; i++) {
+               if (acl_get_perm_np(mperms, acl_perms[i].perm))
+                       acl_delete_perm(rperms, acl_perms[i].perm);
+       }
+       for (i = 0; acl_flags[i].name != NULL; i++) {
+               if (acl_get_flag_np(mflags, acl_flags[i].flag))
+                       acl_delete_flag_np(rflags, acl_flags[i].flag);
+       }
+       acl_set_permset(rentry, rperms);
+       acl_set_flagset_np(rentry, rflags);
+       return 0;
+}
+/* Add the perms specified in modifier to rentry */
+int
+merge_entry_perms(acl_entry_t rentry, acl_entry_t  modifier)
+{
+       acl_permset_t rperms, mperms;
+       acl_flagset_t rflags, mflags;
+       int i;
+
+       if ((acl_get_permset(rentry, &rperms) != 0) ||
+           (acl_get_flagset_np(rentry, &rflags) != 0) ||
+           (acl_get_permset(modifier, &mperms) != 0) ||
+           (acl_get_flagset_np(modifier, &mflags) != 0))
+               err(1, "error computing ACL modification");
+
+       for (i = 0; acl_perms[i].name != NULL; i++) {
+               if (acl_get_perm_np(mperms, acl_perms[i].perm))
+                       acl_add_perm(rperms, acl_perms[i].perm);
+       }
+       for (i = 0; acl_flags[i].name != NULL; i++) {
+               if (acl_get_flag_np(mflags, acl_flags[i].flag))
+                       acl_add_flag_np(rflags, acl_flags[i].flag);
+       }
+       acl_set_permset(rentry, rperms);
+       acl_set_flagset_np(rentry, rflags);
+       return 0;
+}
+
+int
+modify_acl(acl_t *oaclp, acl_entry_t modifier, unsigned int optflags,
+          int position, int inheritance_level, 
+          unsigned flag_new_acl) {
+
+       unsigned cpos = 0;
+       acl_entry_t newent = NULL;
+       int dmatch = 0;
+       acl_entry_t rentry = NULL;
+       unsigned retval = 0;
+       acl_t oacl = *oaclp;
+       
+/* Add the inherited flag if requested by the user*/
+       if (modifier && (optflags & ACL_INHERIT_FLAG)) {
+               acl_flagset_t mflags;
+
+               acl_get_flagset_np(modifier, &mflags);
+               acl_add_flag_np(mflags, ACL_ENTRY_INHERITED);
+               acl_set_flagset_np(modifier, mflags);
+       }
+
+       if (optflags & ACL_SET_FLAG) {
+               if (position != -1) {
+                       if (0 != acl_create_entry_np(&oacl, &newent, position))
+                               err(1, "acl_create_entry() failed");
+                       acl_copy_entry(newent, modifier);
+               }
+               else {
+/* If an entry exists, add the new permissions to it, else add an
+ * entry in the canonical position.
+ */
+
+/* First, check for a matching entry - if one exists, merge flags */
+                       dmatch = find_matching_entry(oacl, modifier, &rentry, 1);
+
+                       if (dmatch != MATCH_NONE) {
+                               if (dmatch == MATCH_EXACT)
+/* Nothing to be done */
+                                       goto ma_exit; 
+                               
+                               if (dmatch == MATCH_PARTIAL) {
+                                       merge_entry_perms(rentry, modifier);
+                                       goto ma_exit;
+                               }
+                       }
+/* Insert the entry in canonical order */
+                       cpos = find_canonical_position(oacl, modifier);
+                       if (0!= acl_create_entry_np(&oacl, &newent, cpos))
+                               err(1, "acl_create_entry() failed");
+                       acl_copy_entry(newent, modifier);
+               }
+       }
+       else
+               if (optflags & ACL_DELETE_FLAG) {
+
+                       if (flag_new_acl) {
+                               errno = EINVAL;
+                               err(1, "No ACL present");
+                       }
+                       if (position != -1 ) {
+                               if (0 != acl_get_entry(oacl, position, &rentry))
+                                       err(1, "Invalid entry number");
+                               acl_delete_entry(oacl, rentry);
+                       }
+                       else {
+                               unsigned match_found = 0, aindex;
+                               for (aindex = 0; 
+                                    acl_get_entry(oacl, rentry == NULL ? 
+                                                  ACL_FIRST_ENTRY : 
+                                                  ACL_NEXT_ENTRY, &rentry) 
+                                            == 0; aindex++)    {
+                                       unsigned cmp;
+                                       cmp = compare_acl_entries(rentry, 
+                                                                 modifier);
+                                       if ((cmp == MATCH_EXACT) || 
+                                           (cmp == MATCH_PARTIAL)) {
+                                               match_found++;
+                                               if (cmp == MATCH_EXACT)
+                                                       acl_delete_entry(oacl, rentry);
+                                               else
+/* In the event of a partial match, remove the specified perms from the 
+ * entry */
+                                                       subtract_from_entry(rentry, modifier);
+                                       }
+                               }
+                               if (0 == match_found) {
+                                       errno = EINVAL;
+                                       warn("Entry not found when attempting delete");
+                                       retval = 1;
+                               }
+                       }
+               }
+               else
+                       if (optflags & ACL_REWRITE_FLAG) {
+                               acl_entry_t rentry;
+                               
+                               if (-1 == position) {
+                                       usage();
+                               }
+                               if (0 == flag_new_acl) {
+                                       if (0 != acl_get_entry(oacl, position,
+                                                              &rentry))
+                                               err(1, "Invalid entry number");
+                                       
+                                       if (0 != acl_delete_entry(oacl, rentry))
+                                               err(1, "Unable to delete entry");
+                               }
+                               if (0!= acl_create_entry_np(&oacl, &newent, position))
+                                       err(1, "acl_create_entry() failed");
+                               acl_copy_entry(newent, modifier);
+                       }
+ma_exit:
+       *oaclp = oacl;
+       return retval;
+}
+
+int
+modify_file_acl(unsigned int optflags, const char *path, acl_t modifier, int position, int inheritance_level) {
+       
+       acl_t oacl = NULL;
+       unsigned aindex  = 0, flag_new_acl = 0;
+       acl_entry_t newent = NULL;
+       acl_entry_t entry = NULL;
+       unsigned retval = 0 ;
+
+       extern int fflag;
+
+/* XXX acl_get_file() returns a zero entry ACL if an ACL was previously
+ * associated with the file, and has had its entries removed.
+ * However, POSIX 1003.1e states that a zero entry ACL should be 
+ * returned if the caller asks for ACL_TYPE_DEFAULT, and no ACL is 
+ * associated with the path; it
+ * does not specifically state that a request for ACL_TYPE_EXTENDED
+ * should not return a zero entry ACL, however.
+ */
+
+/* Determine if we've been given a zero entry ACL, or create an ACL if 
+ * none exists. There are some issues to consider here: Should we create
+ * a zero-entry ACL for a delete or check canonicity operation?
+ */
+
+       if (path == NULL)
+               usage();
+
+       if (optflags & ACL_FROM_STDIN)
+               oacl = acl_dup(modifier);
+       else {
+               oacl = acl_get_file(path, ACL_TYPE_EXTENDED);
+               
+               if ((oacl == NULL) ||
+                   (acl_get_entry(oacl,ACL_FIRST_ENTRY, &newent) != 0)) {
+                       if ((oacl = acl_init(1)) == NULL)
+                               err(1, "acl_init() failed");
+                       flag_new_acl = 1;
+                       position = 0;
+               }
+       
+               if ((0 == flag_new_acl) && (optflags & (ACL_REMOVE_INHERIT_FLAG | 
+                                                       ACL_REMOVE_INHERITED_ENTRIES))) {
+                       acl_t facl = NULL;
+                       if ((facl = acl_init(1)) == NULL)
+                               err(1, "acl_init() failed");
+                       for (aindex = 0; 
+                            acl_get_entry(oacl, 
+                                          (entry == NULL ? ACL_FIRST_ENTRY : 
+                                           ACL_NEXT_ENTRY), &entry) == 0; 
+                            aindex++) {
+                               acl_flagset_t eflags;
+                               acl_entry_t fent = NULL;
+                               if (acl_get_flagset_np(entry, &eflags) != 0) {
+                                       err(1, "Unable to obtain flagset");
+                               }
+                               
+                               if (acl_get_flag_np(eflags, ACL_ENTRY_INHERITED)) {
+                                       if (optflags & ACL_REMOVE_INHERIT_FLAG) {
+                                               acl_delete_flag_np(eflags, ACL_ENTRY_INHERITED);
+                                               acl_set_flagset_np(entry, eflags);
+                                               acl_create_entry(&facl, &fent);
+                                               acl_copy_entry(fent, entry);
+                                       }
+                               }
+                               else {
+                                       acl_create_entry(&facl, &fent);
+                                       acl_copy_entry(fent, entry);
+                               }
+                       }
+                       if (oacl)
+                               acl_free(oacl);
+                       oacl = facl;
+               }
+               else
+               if (optflags & ACL_CHECK_CANONICITY) {
+                       if (flag_new_acl) {
+                               errno = EINVAL;
+                               warn("No ACL currently associated with file %s", path);
+                       }
+                       return(is_canonical(oacl));
+               }
+               else
+               if ((optflags & ACL_SET_FLAG) && (position == -1) && 
+                   (!is_canonical(oacl))) {
+                       errno = EINVAL;
+                       warn("The specified file %s does not have an ACL in canonical order, please specify a position with +a# ", path);
+                       retval = 1;
+               }
+               else
+               if (((optflags & ACL_DELETE_FLAG) && (position != -1))
+                   || (optflags & ACL_CHECK_CANONICITY)) {
+                       retval = modify_acl(&oacl, NULL, optflags, position, 
+                                  inheritance_level, flag_new_acl);
+               }
+               else
+                       for (aindex = 0; 
+                            acl_get_entry(modifier, 
+                                          (entry == NULL ? ACL_FIRST_ENTRY : 
+                                           ACL_NEXT_ENTRY), &entry) == 0; 
+                            aindex++) {
+
+                                           retval += modify_acl(&oacl, entry, optflags, 
+                                                                position, inheritance_level, 
+                                                                flag_new_acl);
+                                   }
+                       }
+
+/* XXX Potential race here, since someone else could've modified or
+ * read the ACL on this file (with the intention of modifying it) in
+ * the interval from acl_get_file() to acl_set_file(); we can
+ * minimize one aspect of this  window by comparing the original acl
+ * to a fresh one from acl_get_file() but we could consider a
+ * "changeset" mechanism, common locking  strategy, or kernel
+ * supplied reservation mechanism to prevent this race.
+ */
+       if (!(optflags & ACL_CHECK_CANONICITY) && 
+           (0 != acl_set_file(path, ACL_TYPE_EXTENDED, oacl))){
+               if (!fflag)
+                       warn("Failed to set ACL on file %s", path);
+               retval = 1;
+       }
+       
+       if (oacl)
+               acl_free(oacl);
+       
+       return retval;
+}
+
+#endif /*__APPLE__*/