+ if (mode & (S_ISUID | S_ISGID | S_ISVTX))
+ {
+ /* We did not call chmod so far, so the special bits have not yet
+ been set. */
+ return chmod_or_fchmod (name, desc, mode);
+ }
+ return 0;
+
+# else /* Solaris, Cygwin, general case */
+
+# ifdef ACE_GETACL
+ /* Solaris also has a different variant of ACLs, used in ZFS and NFSv4
+ file systems (whereas the other ones are used in UFS file systems). */
+
+ /* The flags in the ace_t structure changed in a binary incompatible way
+ when ACL_NO_TRIVIAL etc. were introduced in <sys/acl.h> version 1.15.
+ How to distinguish the two conventions at runtime?
+ We fetch the existing ACL. In the old convention, usually three ACEs have
+ a_flags = ACE_OWNER / ACE_GROUP / ACE_OTHER, in the range 0x0100..0x0400.
+ In the new convention, these values are not used. */
+ int convention;
+
+ {
+ int count;
+ ace_t *entries;
+
+ for (;;)
+ {
+ if (desc != -1)
+ count = facl (desc, ACE_GETACLCNT, 0, NULL);
+ else
+ count = acl (name, ACE_GETACLCNT, 0, NULL);
+ if (count <= 0)
+ {
+ convention = -1;
+ break;
+ }
+ entries = (ace_t *) malloc (count * sizeof (ace_t));
+ if (entries == NULL)
+ {
+ errno = ENOMEM;
+ return -1;
+ }
+ if ((desc != -1
+ ? facl (desc, ACE_GETACL, count, entries)
+ : acl (name, ACE_GETACL, count, entries))
+ == count)
+ {
+ int i;
+
+ convention = 0;
+ for (i = 0; i < count; i++)
+ if (entries[i].a_flags & (ACE_OWNER | ACE_GROUP | ACE_OTHER))
+ {
+ convention = 1;
+ break;
+ }
+ free (entries);
+ break;
+ }
+ /* Huh? The number of ACL entries changed since the last call.
+ Repeat. */
+ free (entries);
+ }
+ }
+
+ if (convention >= 0)
+ {
+ ace_t entries[3];
+ int ret;
+
+ if (convention)
+ {
+ /* Running on Solaris 10. */
+ entries[0].a_type = ALLOW;
+ entries[0].a_flags = ACE_OWNER;
+ entries[0].a_who = 0; /* irrelevant */
+ entries[0].a_access_mask = (mode >> 6) & 7;
+ entries[1].a_type = ALLOW;
+ entries[1].a_flags = ACE_GROUP;
+ entries[1].a_who = 0; /* irrelevant */
+ entries[1].a_access_mask = (mode >> 3) & 7;
+ entries[2].a_type = ALLOW;
+ entries[2].a_flags = ACE_OTHER;
+ entries[2].a_who = 0;
+ entries[2].a_access_mask = mode & 7;
+ }
+ else
+ {
+ /* Running on Solaris 10 (newer version) or Solaris 11. */
+ entries[0].a_type = ACE_ACCESS_ALLOWED_ACE_TYPE;
+ entries[0].a_flags = NEW_ACE_OWNER;
+ entries[0].a_who = 0; /* irrelevant */
+ entries[0].a_access_mask =
+ (mode & 0400 ? NEW_ACE_READ_DATA : 0)
+ | (mode & 0200 ? NEW_ACE_WRITE_DATA : 0)
+ | (mode & 0100 ? NEW_ACE_EXECUTE : 0);
+ entries[1].a_type = ACE_ACCESS_ALLOWED_ACE_TYPE;
+ entries[1].a_flags = NEW_ACE_GROUP | NEW_ACE_IDENTIFIER_GROUP;
+ entries[1].a_who = 0; /* irrelevant */
+ entries[1].a_access_mask =
+ (mode & 0040 ? NEW_ACE_READ_DATA : 0)
+ | (mode & 0020 ? NEW_ACE_WRITE_DATA : 0)
+ | (mode & 0010 ? NEW_ACE_EXECUTE : 0);
+ entries[2].a_type = ACE_ACCESS_ALLOWED_ACE_TYPE;
+ entries[2].a_flags = ACE_EVERYONE;
+ entries[2].a_who = 0;
+ entries[2].a_access_mask =
+ (mode & 0004 ? NEW_ACE_READ_DATA : 0)
+ | (mode & 0002 ? NEW_ACE_WRITE_DATA : 0)
+ | (mode & 0001 ? NEW_ACE_EXECUTE : 0);
+ }
+ if (desc != -1)
+ ret = facl (desc, ACE_SETACL,
+ sizeof (entries) / sizeof (ace_t), entries);
+ else
+ ret = acl (name, ACE_SETACL,
+ sizeof (entries) / sizeof (ace_t), entries);
+ if (ret < 0 && errno != EINVAL && errno != ENOTSUP)
+ {
+ if (errno == ENOSYS)
+ return chmod_or_fchmod (name, desc, mode);
+ return -1;
+ }
+ }
+# endif
+
+ {
+ aclent_t entries[3];
+ int ret;
+
+ entries[0].a_type = USER_OBJ;
+ entries[0].a_id = 0; /* irrelevant */
+ entries[0].a_perm = (mode >> 6) & 7;
+ entries[1].a_type = GROUP_OBJ;
+ entries[1].a_id = 0; /* irrelevant */
+ entries[1].a_perm = (mode >> 3) & 7;
+ entries[2].a_type = OTHER_OBJ;
+ entries[2].a_id = 0;
+ entries[2].a_perm = mode & 7;
+
+ if (desc != -1)
+ ret = facl (desc, SETACL, sizeof (entries) / sizeof (aclent_t), entries);
+ else
+ ret = acl (name, SETACL, sizeof (entries) / sizeof (aclent_t), entries);
+ if (ret < 0)
+ {
+ if (errno == ENOSYS || errno == EOPNOTSUPP)
+ return chmod_or_fchmod (name, desc, mode);
+ return -1;
+ }
+ }
+
+ if (!MODE_INSIDE_ACL || (mode & (S_ISUID | S_ISGID | S_ISVTX)))
+ {
+ /* We did not call chmod so far, so the special bits have not yet
+ been set. */
+ return chmod_or_fchmod (name, desc, mode);
+ }
+ return 0;