Subject: bug#10639: acl code rewrite

From: Bruno Haible <[email-masked]>
Date: Mon, 20 Feb 2012 04:18:20 +0100

Hi Paul,

> The patch you sent looks like it'll fix that particular bug, but while
> looking into this I discovered so many bugs in this area, mostly
> hard-to-test race conditions and overflows, that I thought it better
> to rewrite the affected code than to try to fix each bug one at a time.
> I didn't write this up very well, and my first cut at doing this could
> stand some improvements of its own.  Here's a revised patch that tries
> to do a better job at all this.

Thanks for this rework. I apologize for the time it took me to review it:
I had a week-long private outage.

> +	 - There's a race condition in this kind of code:
> +
> +	     n = acl (f, GETACLCNT, 0, NULL);
> +	     [ allocate an array A of size N ]
> +	     if (acl (f, GETACL, n, a) == n)
> +	       return ok;
> +
> +	   The ACL can grow in size between the first and second calls to
> +	   'acl', which means that the second 'acl' returns a truncated
> +	   ACL but thinks that it has the whole thing.  To avoid this, the
> +	   following pattern should be used instead:
> +
> +	     n = acl (f, GETACLCNT, 0, NULL);
> +	     [ allocate an array A of size N + 1 ]
> +	     n1 = acl (f, GETACL, n + 1, a);
> +	     if (0 < n1 && n1 <= n)
> +	       return ok;

Have you tested that the acl() call works like this? The manual page
http://docs.oracle.com/cd/E23824_01/html/821-1463/acl-2.html
says that rather than returning a truncated result, the acl() call will
fail with error code ENOSPC.

And on HP-UX, the documentation
http://h20000.www2.hp.com/bc/docs/support/SupportManual/c02261503/c02261503.pdf
says that the argument nentries is "never greater than" NACLENTRIES.
Therefore, passing NACLENTRIES + 1, like your patch does, is invalid.

> +	 - There were several instances of this pattern:
> +
> +	     for (;;) {
> +	       n = acl (f, GETACLCNT, 0, NULL);
> +	       [ allocate an array A of size N ]
> +	       if (acl (f, GETACL, n, a) == n)
> +		 break;
> +	     }
> +
> +	   This loop might never terminate if some other process is constantly
> +	   manipulating the file's ACL.  The loop should be rewritten to
> +	   terminate.

The loop terminates with probability 1, because the probability that the
competing process is able to change the size of the file's ACL right in
time to provoke an endless loop is 0.

> +	 - The acl (... GETACLNT ...) call is merely an optimization; its value
> +	   is merely a hint as to how big to make the array.  A better
> +	   optimization is to avoid the acl (... GETACLNT ...)  call entirely,
> +	   and just guess a reasonably-big size, growing the size and trying
> +	   again if it's not large enough.  This guarantees termination, and
> +	   saves a system call.

Good point. I agree with this one.

> +	 - With this approach, for ports like HP-UX that have an upper bound
> +	   for the ACL length, there's no longer any need to loop reading the
> +	   ACL.  Just read it once and you're done reading it.

Yes.

> +	 - For ports like Solaris that do need to loop (because the ACL length
> +	   is not known a priori), it's faster to allocate a reasonably-sized
> +	   buffer on the stack and use that, and to allocate something on the
> +	   heap only if the ACL is unusually large.  This avoids malloc in the
> +	   usual case.

Yes. However, beware against premature optimizations: So far, the code
does not handles ACLs on directories, only on files. The limit of "trivial"
ACLs on files and on directories may be different.

> +	 - The code that calculated sizes of these arrays did not check for
> +	   overflow in size calculations; it should.
> +
> +	 - There are some memory leaks.  For example, in qcopy_acl, if acl
> +	   (src_name, GETACLCNT, 0, NULL) < 0 && errno == EPERM, then the
> +	   storage for ace_entries leaks.  Similarly, if acl (src_name,
> +	   ACE_GETACL, ace_count, ace_entries) returns -1, the storage for
> +	   ace_entries leaks.

Oops, indeed!

> +	 - In qset_acl, there's sometimes no need to read the entire ACL
> +	   to determine the convention; this can save system calls when
> +	   looking at very large ACLs.

You assume that when you pass less room to the acl() call than needed,
it will fill the buffer. But this is not clear from the documentation
at <http://docs.oracle.com/cd/E23823_01/html/816-5167/acl-2.html> (cf.
ENOSPC). Even if that's how it behaves in a specific version of Solaris,
it's a risky assumption.

> +	Rather than fix these bugs one at a time I thought it more efficient
> +	to rewrite the affected code.

Yup. But the code in tests/test-sameacls.c should be kept in sync if
we decide to not use GETACLCNT any more. Also, one change per commit --
otherwise it gets too complicated.

I hope you agree if I commit the parts on which we agree.

===============================================================================

2012-02-19  Bruno Haible  <[email-masked]>

	acl: Update doc references.
	* doc/acl-resources.txt: Update links to Solaris documentation.

--- doc/acl-resources.txt.orig	Sun Feb 19 16:02:46 2012
+++ doc/acl-resources.txt	Sun Feb 19 16:01:54 2012
@@ -72,10 +72,10 @@
   http://www.softpanorama.org/Solaris/ACL/index.shtml
   http://www.cs.duke.edu/csl/faqs/solaris-acls.php
 Manual pages:
-  http://docs.sun.com/app/docs/doc/816-5167/acl-2?l=en&a=view
-  http://docs.sun.com/app/docs/doc/816-5165/getfacl-1?l=en&a=view
-  http://docs.sun.com/app/docs/doc/816-5165/setfacl-1?l=en&a=view
-  http://docs.sun.com/app/docs/doc/816-5167/pathconf-2?l=en&a=view
+  http://docs.oracle.com/cd/E23823_01/html/816-5167/acl-2.html
+  http://docs.oracle.com/cd/E23823_01/html/816-5165/getfacl-1.html
+  http://docs.oracle.com/cd/E23823_01/html/816-5165/setfacl-1.html
+  http://docs.oracle.com/cd/E23823_01/html/816-5167/pathconf-2.html
 Includes:
   <sys/acl.h>
 Library:

===============================================================================

Already before any modification of the code, I'm seeing a test failure
on Solaris 11:

test-copy-acl: preserving permissions for 'tmpfile1': Operation not applicable
FAIL: test-copy-acl.sh

The reason is that the function acl_ace_nontrivial returns 1 when it should
better return 0. The original code for acl_ace_nontrivial was made to match
the code in qset_acl, but this code is apparently more specialized than
the various situations that occur on the file systems.

This fixes it (without introducing regressions on Solaris 10).

===============================================================================

2012-02-19  Bruno Haible  <[email-masked]>

	acl: Fix copy-acl test failure on Solaris 11 2011-11.
	* lib/file-has-acl.c (NEW_ACE_WRITEA_DATA): New macro.
	(acl_ace_nontrivial): Relax the restrictions on access_masks[] so
	that this function returns 0 in some more cases.

--- lib/file-has-acl.c.orig	Sun Feb 19 21:56:14 2012
+++ lib/file-has-acl.c	Sun Feb 19 21:27:12 2012
@@ -154,6 +154,9 @@
 
 # ifdef ACE_GETACL
 
+/* A shortcut for a bitmask.  */
+#  define NEW_ACE_WRITEA_DATA (NEW_ACE_WRITE_DATA | NEW_ACE_APPEND_DATA)
+
 /* Test an ACL retrieved with ACE_GETACL.
    Return 1 if the given ACL, consisting of COUNT entries, is non-trivial.
    Return 0 if it is trivial, i.e. equivalent to a simple stat() mode.  */
@@ -250,16 +253,6 @@
                            | NEW_ACE_WRITE_ATTRIBUTES
                            | NEW_ACE_WRITE_ACL
                            | NEW_ACE_WRITE_OWNER);
-      if ((NEW_ACE_WRITE_NAMED_ATTRS
-           | NEW_ACE_WRITE_ATTRIBUTES
-           | NEW_ACE_WRITE_ACL
-           | NEW_ACE_WRITE_OWNER)
-          & ~ access_masks[4])
-        return 1;
-      access_masks[4] &= ~(NEW_ACE_WRITE_NAMED_ATTRS
-                           | NEW_ACE_WRITE_ATTRIBUTES
-                           | NEW_ACE_WRITE_ACL
-                           | NEW_ACE_WRITE_OWNER);
       if ((NEW_ACE_READ_NAMED_ATTRS
            | NEW_ACE_READ_ATTRIBUTES
            | NEW_ACE_READ_ACL
@@ -272,21 +265,60 @@
                            | NEW_ACE_SYNCHRONIZE);
 
       /* Check the allowed or denied bits.  */
-      if ((access_masks[0] | access_masks[1])
-          != (NEW_ACE_READ_DATA
-              | NEW_ACE_WRITE_DATA | NEW_ACE_APPEND_DATA
-              | NEW_ACE_EXECUTE))
-        return 1;
-      if ((access_masks[2] | access_masks[3])
-          != (NEW_ACE_READ_DATA
-              | NEW_ACE_WRITE_DATA | NEW_ACE_APPEND_DATA
-              | NEW_ACE_EXECUTE))
-        return 1;
-      if ((access_masks[4] | access_masks[5])
-          != (NEW_ACE_READ_DATA
-              | NEW_ACE_WRITE_DATA | NEW_ACE_APPEND_DATA
-              | NEW_ACE_EXECUTE))
-        return 1;
+      switch ((access_masks[0] | access_masks[1])
+              & ~(NEW_ACE_READ_NAMED_ATTRS
+                  | NEW_ACE_READ_ATTRIBUTES
+                  | NEW_ACE_READ_ACL
+                  | NEW_ACE_SYNCHRONIZE))
+        {
+        case 0:
+        case NEW_ACE_READ_DATA:
+        case                     NEW_ACE_WRITEA_DATA:
+        case NEW_ACE_READ_DATA | NEW_ACE_WRITEA_DATA:
+        case                                           NEW_ACE_EXECUTE:
+        case NEW_ACE_READ_DATA |                       NEW_ACE_EXECUTE:
+        case                     NEW_ACE_WRITEA_DATA | NEW_ACE_EXECUTE:
+        case NEW_ACE_READ_DATA | NEW_ACE_WRITEA_DATA | NEW_ACE_EXECUTE:
+          break;
+        default:
+          return 1;
+        }
+      switch ((access_masks[2] | access_masks[3])
+              & ~(NEW_ACE_READ_NAMED_ATTRS
+                  | NEW_ACE_READ_ATTRIBUTES
+                  | NEW_ACE_READ_ACL
+                  | NEW_ACE_SYNCHRONIZE))
+        {
+        case 0:
+        case NEW_ACE_READ_DATA:
+        case                     NEW_ACE_WRITEA_DATA:
+        case NEW_ACE_READ_DATA | NEW_ACE_WRITEA_DATA:
+        case                                           NEW_ACE_EXECUTE:
+        case NEW_ACE_READ_DATA |                       NEW_ACE_EXECUTE:
+        case                     NEW_ACE_WRITEA_DATA | NEW_ACE_EXECUTE:
+        case NEW_ACE_READ_DATA | NEW_ACE_WRITEA_DATA | NEW_ACE_EXECUTE:
+          break;
+        default:
+          return 1;
+        }
+      switch ((access_masks[4] | access_masks[5])
+              & ~(NEW_ACE_WRITE_NAMED_ATTRS
+                  | NEW_ACE_WRITE_ATTRIBUTES
+                  | NEW_ACE_WRITE_ACL
+                  | NEW_ACE_WRITE_OWNER))
+        {
+        case 0:
+        case NEW_ACE_READ_DATA:
+        case                     NEW_ACE_WRITEA_DATA:
+        case NEW_ACE_READ_DATA | NEW_ACE_WRITEA_DATA:
+        case                                           NEW_ACE_EXECUTE:
+        case NEW_ACE_READ_DATA |                       NEW_ACE_EXECUTE:
+        case                     NEW_ACE_WRITEA_DATA | NEW_ACE_EXECUTE:
+        case NEW_ACE_READ_DATA | NEW_ACE_WRITEA_DATA | NEW_ACE_EXECUTE:
+          break;
+        default:
+          return 1;
+        }
 
       /* Check that the NEW_ACE_WRITE_DATA and NEW_ACE_APPEND_DATA bits are
          either both allowed or both denied.  */

===============================================================================

After eliminating this test failure, let me add first the minimal workaround to
the problem reported in <http://debbugs.gnu.org/cgi/bugreport.cgi?bug=10639>.

===============================================================================

2012-02-19  Bruno Haible  <[email-masked]>

	acl: Fix endless loop on Solaris with vxfs.
	* lib/file-has-acl.c (file_has_acl) [Solaris]: Treat a failing
	acl()/facl() call for ACE_GETACL like a failing call for ACE_GETACLCNT.
	* lib/set-mode-acl.c (qset_acl) [Solaris]: Likewise.
	* lib/copy-acl.c (qcopy_acl)[Solaris]: Likewise.
	* tests/test-sameacls.c (main)[Solaris]: Likewise.
	Reported by Bill Jones in
	<http://debbugs.gnu.org/cgi/bugreport.cgi?bug=10639>, via Paul Eggert.

--- lib/copy-acl.c.orig	Sun Feb 19 22:04:57 2012
+++ lib/copy-acl.c	Sun Feb 19 22:00:03 2012
@@ -235,10 +235,22 @@
           return -2;
         }
 
-      if ((source_desc != -1
-           ? facl (source_desc, ACE_GETACL, ace_count, ace_entries)
-           : acl (src_name, ACE_GETACL, ace_count, ace_entries))
-          == ace_count)
+      ret = (source_desc != -1
+             ? facl (source_desc, ACE_GETACL, ace_count, ace_entries)
+             : acl (src_name, ACE_GETACL, ace_count, ace_entries));
+      if (ret < 0)
+        {
+          free (ace_entries);
+          if (errno == ENOSYS || errno == EINVAL)
+            {
+              ace_count = 0;
+              ace_entries = NULL;
+              break;
+            }
+          else
+            return -2;
+        }
+      if (ret == ace_count)
         break;
       /* Huh? The number of ACL entries changed since the last call.
          Repeat.  */
--- lib/file-has-acl.c.orig	Sun Feb 19 22:04:57 2012
+++ lib/file-has-acl.c	Sun Feb 19 22:00:03 2012
@@ -626,6 +626,8 @@
 
         for (;;)
           {
+            int ret;
+
             count = acl (name, ACE_GETACLCNT, 0, NULL);
 
             if (count < 0)
@@ -656,7 +658,16 @@
                 errno = ENOMEM;
                 return -1;
               }
-            if (acl (name, ACE_GETACL, count, entries) == count)
+            ret = acl (name, ACE_GETACL, count, entries);
+            if (ret < 0)
+              {
+                free (entries);
+                if (errno == ENOSYS || errno == EINVAL)
+                  break;
+                else
+                  return -1;
+              }
+            if (ret == count)
               {
                 if (acl_ace_nontrivial (count, entries))
                   {
--- lib/set-mode-acl.c.orig	Sun Feb 19 22:04:57 2012
+++ lib/set-mode-acl.c	Sun Feb 19 22:00:03 2012
@@ -219,6 +219,8 @@
 
     for (;;)
       {
+        int ret;
+
         if (desc != -1)
           count = facl (desc, ACE_GETACLCNT, 0, NULL);
         else
@@ -234,10 +236,16 @@
             errno = ENOMEM;
             return -1;
           }
-        if ((desc != -1
-             ? facl (desc, ACE_GETACL, count, entries)
-             : acl (name, ACE_GETACL, count, entries))
-            == count)
+        ret = (desc != -1
+               ? facl (desc, ACE_GETACL, count, entries)
+               : acl (name, ACE_GETACL, count, entries));
+        if (ret < 0)
+          {
+            free (entries);
+            convention = -1;
+            break;
+          }
+        if (ret == count)
           {
             int i;
 
--- tests/test-sameacls.c.orig	Sun Feb 19 22:04:57 2012
+++ tests/test-sameacls.c	Sun Feb 19 22:00:03 2012
@@ -310,58 +310,66 @@
       fflush (stderr);
       abort ();
     }
-  if (count1 != count2)
-    {
-      fprintf (stderr, "files %s and %s have different number of ACE-ACLs: %d and %d\n",
-               file1, file2, count1, count2);
-      return 1;
-    }
-  else if (count1 > 0)
-    {
-      ace_t *entries1 = XNMALLOC (count1, ace_t);
-      ace_t *entries2 = XNMALLOC (count2, ace_t);
-      int i;
+  {
+    ace_t *entries1 = XNMALLOC (count1, ace_t);
+    ace_t *entries2 = XNMALLOC (count2, ace_t);
+    int ret;
+    int i;
 
-      if (acl (file1, ACE_GETACL, count1, entries1) < count1)
-        {
-          fprintf (stderr, "error retrieving the ACE-ACLs of file %s\n", file1);
-          fflush (stderr);
-          abort ();
-        }
-      if (acl (file2, ACE_GETACL, count2, entries2) < count1)
-        {
-          fprintf (stderr, "error retrieving the ACE-ACLs of file %s\n", file2);
-          fflush (stderr);
-          abort ();
-        }
-      for (i = 0; i < count1; i++)
-        {
-          if (entries1[i].a_type != entries2[i].a_type)
-            {
-              fprintf (stderr, "files %s and %s: different ACE-ACL entry #%d: different types %d and %d\n",
-                       file1, file2, i, entries1[i].a_type, entries2[i].a_type);
-              return 1;
-            }
-          if (entries1[i].a_who != entries2[i].a_who)
-            {
-              fprintf (stderr, "files %s and %s: different ACE-ACL entry #%d: different ids %d and %d\n",
-                       file1, file2, i, (int)entries1[i].a_who, (int)entries2[i].a_who);
-              return 1;
-            }
-          if (entries1[i].a_access_mask != entries2[i].a_access_mask)
-            {
-              fprintf (stderr, "files %s and %s: different ACE-ACL entry #%d: different access masks %03o and %03o\n",
-                       file1, file2, i, (unsigned int) entries1[i].a_access_mask, (unsigned int) entries2[i].a_access_mask);
-              return 1;
-            }
-          if (entries1[i].a_flags != entries2[i].a_flags)
-            {
-              fprintf (stderr, "files %s and %s: different ACE-ACL entry #%d: different flags 0x%x and 0x%x\n",
-                       file1, file2, i, (unsigned int) entries1[i].a_flags, (unsigned int) entries2[i].a_flags);
-              return 1;
-            }
-        }
-    }
+    ret = acl (file1, ACE_GETACL, count1, entries1);
+    if (ret < 0 && errno == EINVAL)
+      count1 = 0;
+    else if (ret < count1)
+      {
+        fprintf (stderr, "error retrieving the ACE-ACLs of file %s\n", file1);
+        fflush (stderr);
+        abort ();
+      }
+    ret = acl (file2, ACE_GETACL, count2, entries2);
+    if (ret < 0 && errno == EINVAL)
+      count2 = 0;
+    else if (ret < count2)
+      {
+        fprintf (stderr, "error retrieving the ACE-ACLs of file %s\n", file2);
+        fflush (stderr);
+        abort ();
+      }
+
+    if (count1 != count2)
+      {
+        fprintf (stderr, "files %s and %s have different number of ACE-ACLs: %d and %d\n",
+                 file1, file2, count1, count2);
+        return 1;
+      }
+
+    for (i = 0; i < count1; i++)
+      {
+        if (entries1[i].a_type != entries2[i].a_type)
+          {
+            fprintf (stderr, "files %s and %s: different ACE-ACL entry #%d: different types %d and %d\n",
+                     file1, file2, i, entries1[i].a_type, entries2[i].a_type);
+            return 1;
+          }
+        if (entries1[i].a_who != entries2[i].a_who)
+          {
+            fprintf (stderr, "files %s and %s: different ACE-ACL entry #%d: different ids %d and %d\n",
+                     file1, file2, i, (int)entries1[i].a_who, (int)entries2[i].a_who);
+            return 1;
+          }
+        if (entries1[i].a_access_mask != entries2[i].a_access_mask)
+          {
+            fprintf (stderr, "files %s and %s: different ACE-ACL entry #%d: different access masks %03o and %03o\n",
+                     file1, file2, i, (unsigned int) entries1[i].a_access_mask, (unsigned int) entries2[i].a_access_mask);
+            return 1;
+          }
+        if (entries1[i].a_flags != entries2[i].a_flags)
+          {
+            fprintf (stderr, "files %s and %s: different ACE-ACL entry #%d: different flags 0x%x and 0x%x\n",
+                     file1, file2, i, (unsigned int) entries1[i].a_flags, (unsigned int) entries2[i].a_flags);
+            return 1;
+          }
+      }
+  }
 # endif
 #elif HAVE_GETACL /* HP-UX */
   int count1;

===============================================================================

Then comes the main part of your rewrite. First the part for Solaris:

===============================================================================

2012-02-19  Paul Eggert  <[email-masked]>
	    Bruno Haible  <[email-masked]>

	acl: Don't use GETACLCNT and similar ops, since they are unreliable.

	 - There were several instances of this pattern:

	     for (;;) {
	       n = acl (f, GETACLCNT, 0, NULL);
	       [ allocate an array A of size N ]
	       if (acl (f, GETACL, n, a) == n)
		 break;
	     }

	   This loop might never terminate if some other process is constantly
	   manipulating the file's ACL.  The loop should be rewritten to
	   terminate.

	 - The acl (... GETACLNT ...) call is merely an optimization; its value
	   is merely a hint as to how big to make the array.  A better
	   optimization is to avoid the acl (... GETACLNT ...)  call entirely,
	   and just guess a reasonably-big size, growing the size and trying
	   again if it's not large enough.  This guarantees termination, and
	   saves a system call.

	* lib/acl-internal.h: Include <limits.h>.
	(MIN, SIZE_MAX): New macros.
	* lib/file-has-acl.c (file_has_acl) [Solaris]: Read the entries into
	a stack-allocated buffer, and use malloc if it does not fit. Don't
	use GETACLCNT.
	* lib/set-mode-acl.c (qset_acl) [Solaris]: Likewise.
	* lib/copy-acl.c (qcopy_acl) [Solaris]: Likewise.

*** lib/acl-internal.h.orig	Mon Feb 20 01:10:13 2012
--- lib/acl-internal.h	Sun Feb 19 22:59:06 2012
***************
*** 55,60 ****
--- 55,69 ----
  # define ENOTSUP (-1)
  #endif
  
+ #include <limits.h>
+ #ifndef MIN
+ # define MIN(a,b) ((a) < (b) ? (a) : (b))
+ #endif
+ 
+ #ifndef SIZE_MAX
+ # define SIZE_MAX ((size_t) -1)
+ #endif
+ 
  #ifndef HAVE_FCHMOD
  # define HAVE_FCHMOD false
  # define fchmod(fd, mode) (-1)
*** lib/copy-acl.c.orig	Mon Feb 20 01:10:13 2012
--- lib/copy-acl.c	Mon Feb 20 01:04:36 2012
***************
*** 181,191 ****
       of Unixware.  The acl() call returns the access and default ACL both
       at once.  */
  # ifdef ACE_GETACL
    int ace_count;
-   ace_t *ace_entries;
  # endif
    int count;
-   aclent_t *entries;
    int did_chmod;
    int saved_errno;
    int ret;
--- 181,207 ----
       of Unixware.  The acl() call returns the access and default ACL both
       at once.  */
  # ifdef ACE_GETACL
+   enum
+     {
+       ace_alloc_init = 4000 / sizeof (ace_t), /* >= 3 */
+       ace_alloc_max = MIN (INT_MAX, SIZE_MAX / sizeof (ace_t))
+     };
+   ace_t ace_buf[ace_alloc_init];
+   size_t ace_alloc = ace_alloc_init;
+   ace_t *ace_entries = ace_buf;
+   ace_t *ace_malloced = NULL;
    int ace_count;
  # endif
+   enum
+     {
+       alloc_init = 4000 / sizeof (aclent_t), /* >= 3 */
+       alloc_max = MIN (INT_MAX, SIZE_MAX / sizeof (aclent_t))
+     };
+   aclent_t buf[alloc_init];
+   size_t alloc = alloc_init;
+   aclent_t *entries = buf;
+   aclent_t *malloced = NULL;
    int count;
    int did_chmod;
    int saved_errno;
    int ret;
***************
*** 204,308 ****
       that the kernel will translate the ACL from one form to the other.
       (See in <http://docs.sun.com/app/docs/doc/819-2241/6n4huc7ia?l=en&a=view>
       the description of ENOTSUP.)  */
    for (;;)
      {
        ace_count = (source_desc != -1
!                    ? facl (source_desc, ACE_GETACLCNT, 0, NULL)
!                    : acl (src_name, ACE_GETACLCNT, 0, NULL));
! 
!       if (ace_count < 0)
!         {
!           if (errno == ENOSYS || errno == EINVAL)
              {
!               ace_count = 0;
!               ace_entries = NULL;
!               break;
              }
!           else
!             return -2;
          }
! 
!       if (ace_count == 0)
          {
            ace_entries = NULL;
-           break;
          }
! 
!       ace_entries = (ace_t *) malloc (ace_count * sizeof (ace_t));
!       if (ace_entries == NULL)
          {
!           errno = ENOMEM;
            return -2;
          }
- 
-       ret = (source_desc != -1
-              ? facl (source_desc, ACE_GETACL, ace_count, ace_entries)
-              : acl (src_name, ACE_GETACL, ace_count, ace_entries));
-       if (ret < 0)
-         {
-           free (ace_entries);
-           if (errno == ENOSYS || errno == EINVAL)
-             {
-               ace_count = 0;
-               ace_entries = NULL;
-               break;
-             }
-           else
-             return -2;
-         }
-       if (ret == ace_count)
-         break;
-       /* Huh? The number of ACL entries changed since the last call.
-          Repeat.  */
      }
  # endif
  
    for (;;)
      {
        count = (source_desc != -1
!                ? facl (source_desc, GETACLCNT, 0, NULL)
!                : acl (src_name, GETACLCNT, 0, NULL));
! 
!       if (count < 0)
!         {
!           if (errno == ENOSYS || errno == ENOTSUP || errno == EOPNOTSUPP)
              {
!               count = 0;
!               entries = NULL;
!               break;
              }
!           else
!             return -2;
          }
! 
!       if (count == 0)
          {
            entries = NULL;
-           break;
          }
! 
!       entries = (aclent_t *) malloc (count * sizeof (aclent_t));
!       if (entries == NULL)
          {
!           errno = ENOMEM;
            return -2;
          }
- 
-       if ((source_desc != -1
-            ? facl (source_desc, GETACL, count, entries)
-            : acl (src_name, GETACL, count, entries))
-           == count)
-         break;
-       /* Huh? The number of ACL entries changed since the last call.
-          Repeat.  */
      }
  
    /* Is there an ACL of either kind?  */
  # ifdef ACE_GETACL
    if (ace_count == 0)
  # endif
      if (count == 0)
!       return qset_acl (dst_name, dest_desc, mode);
  
    did_chmod = 0; /* set to 1 once the mode bits in 0777 have been set */
    saved_errno = 0; /* the first non-ignorable error code */
--- 220,339 ----
       that the kernel will translate the ACL from one form to the other.
       (See in <http://docs.sun.com/app/docs/doc/819-2241/6n4huc7ia?l=en&a=view>
       the description of ENOTSUP.)  */
+ 
+   /* Initially, try to read the entries into a stack-allocated buffer.
+      Use malloc if it does not fit.  */
    for (;;)
      {
        ace_count = (source_desc != -1
!                    ? facl (source_desc, ACE_GETACL, ace_alloc, ace_entries)
!                    : acl (src_name, ACE_GETACL, ace_alloc, ace_entries));
!       if (ace_count < 0 && errno == ENOSPC)
!         {
!           /* Increase the size of the buffer.  */
!           free (ace_malloced);
!           if (ace_alloc > ace_alloc_max / 2)
              {
!               errno = ENOMEM;
!               return -2;
              }
!           ace_alloc = 2 * ace_alloc; /* <= ace_alloc_max */
!           ace_entries = ace_malloced =
!             (ace_t *) malloc (ace_alloc * sizeof (ace_t));
!           if (ace_entries == NULL)
!             {
!               errno = ENOMEM;
!               return -2;
!             }
!           continue;
          }
!       break;
!     }
!   if (ace_count < 0)
!     {
!       if (errno == ENOSYS || errno == EINVAL)
          {
+           ace_count = 0;
            ace_entries = NULL;
          }
!       else
          {
!           int saved_errno = errno;
!           free (ace_malloced);
!           errno = saved_errno;
            return -2;
          }
      }
+   else if (ace_count == 0)
+     ace_entries = NULL;
  # endif
  
+   /* Initially, try to read the entries into a stack-allocated buffer.
+      Use malloc if it does not fit.  */
    for (;;)
      {
        count = (source_desc != -1
!                ? facl (source_desc, GETACL, alloc, entries)
!                : acl (src_name, GETACL, alloc, entries));
!       if (count < 0 && errno == ENOSPC)
!         {
!           /* Increase the size of the buffer.  */
!           free (malloced);
!           if (alloc > alloc_max / 2)
              {
! # ifdef ACE_GETACL
!               free (ace_malloced);
! # endif
!               errno = ENOMEM;
!               return -2;
              }
!           alloc = 2 * alloc; /* <= alloc_max */
!           entries = malloced = (aclent_t *) malloc (alloc * sizeof (aclent_t));
!           if (entries == NULL)
!             {
! # ifdef ACE_GETACL
!               free (ace_malloced);
! # endif
!               errno = ENOMEM;
!               return -2;
!             }
!           continue;
          }
!       break;
!     }
!   if (count < 0)
!     {
!       if (errno == ENOSYS || errno == ENOTSUP || errno == EOPNOTSUPP)
          {
+           count = 0;
            entries = NULL;
          }
!       else
          {
!           int saved_errno = errno;
! # ifdef ACE_GETACL
!           free (ace_malloced);
! # endif
!           free (malloced);
!           errno = saved_errno;
            return -2;
          }
      }
+   else if (count == 0)
+     entries = NULL;
  
    /* Is there an ACL of either kind?  */
  # ifdef ACE_GETACL
    if (ace_count == 0)
  # endif
      if (count == 0)
!       {
! # ifdef ACE_GETACL
!         free (ace_malloced);
! # endif
!         free (malloced);
!         return qset_acl (dst_name, dest_desc, mode);
!       }
  
    did_chmod = 0; /* set to 1 once the mode bits in 0777 have been set */
    saved_errno = 0; /* the first non-ignorable error code */
***************
*** 336,342 ****
        else
          did_chmod = 1;
      }
!   free (entries);
  
  # ifdef ACE_GETACL
    if (ace_count > 0)
--- 367,373 ----
        else
          did_chmod = 1;
      }
!   free (malloced);
  
  # ifdef ACE_GETACL
    if (ace_count > 0)
***************
*** 352,358 ****
              saved_errno = 0;
          }
      }
!   free (ace_entries);
  # endif
  
    if (MODE_INSIDE_ACL
--- 383,389 ----
              saved_errno = 0;
          }
      }
!   free (ace_malloced);
  # endif
  
    if (MODE_INSIDE_ACL
*** lib/file-has-acl.c.orig	Mon Feb 20 01:10:13 2012
--- lib/file-has-acl.c	Mon Feb 20 00:14:37 2012
***************
*** 556,562 ****
          return ACL_NOT_WELL_SUPPORTED (errno) ? 0 : -1;
        return ret;
  
! # elif HAVE_FACL && defined GETACLCNT /* Solaris, Cygwin, not HP-UX */
  
  #  if defined ACL_NO_TRIVIAL
  
--- 556,562 ----
          return ACL_NOT_WELL_SUPPORTED (errno) ? 0 : -1;
        return ret;
  
! # elif HAVE_FACL && defined GETACL /* Solaris, Cygwin, not HP-UX */
  
  #  if defined ACL_NO_TRIVIAL
  
***************
*** 570,646 ****
        /* Solaris 2.5 through Solaris 10, Cygwin, and contemporaneous versions
           of Unixware.  The acl() call returns the access and default ACL both
           at once.  */
-       int count;
        {
!         aclent_t *entries;
  
          for (;;)
            {
!             count = acl (name, GETACLCNT, 0, NULL);
! 
!             if (count < 0)
                {
!                 if (errno == ENOSYS || errno == ENOTSUP)
!                   break;
!                 else
!                   return -1;
                }
! 
!             if (count == 0)
!               break;
! 
              /* Don't use MIN_ACL_ENTRIES:  It's set to 4 on Cygwin, but Cygwin
                 returns only 3 entries for files with no ACL.  But this is safe:
                 If there are more than 4 entries, there cannot be only the
                 "user::", "group::", "other:", and "mask:" entries.  */
              if (count > 4)
-               return 1;
- 
-             entries = (aclent_t *) malloc (count * sizeof (aclent_t));
-             if (entries == NULL)
                {
!                 errno = ENOMEM;
!                 return -1;
                }
!             if (acl (name, GETACL, count, entries) == count)
                {
!                 if (acl_nontrivial (count, entries))
!                   {
!                     free (entries);
!                     return 1;
!                   }
!                 free (entries);
!                 break;
                }
-             /* Huh? The number of ACL entries changed since the last call.
-                Repeat.  */
-             free (entries);
            }
        }
  
  #   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).  */
        {
!         ace_t *entries;
  
          for (;;)
            {
!             int ret;
! 
!             count = acl (name, ACE_GETACLCNT, 0, NULL);
! 
!             if (count < 0)
                {
!                 if (errno == ENOSYS || errno == EINVAL)
!                   break;
!                 else
!                   return -1;
                }
! 
!             if (count == 0)
!               break;
! 
              /* In the old (original Solaris 10) convention:
                 If there are more than 3 entries, there cannot be only the
                 ACE_OWNER, ACE_GROUP, ACE_OTHER entries.
--- 570,704 ----
        /* Solaris 2.5 through Solaris 10, Cygwin, and contemporaneous versions
           of Unixware.  The acl() call returns the access and default ACL both
           at once.  */
        {
!         /* Initially, try to read the entries into a stack-allocated buffer.
!            Use malloc if it does not fit.  */
!         enum
!           {
!             alloc_init = 4000 / sizeof (aclent_t), /* >= 3 */
!             alloc_max = MIN (INT_MAX, SIZE_MAX / sizeof (aclent_t))
!           };
!         aclent_t buf[alloc_init];
!         size_t alloc = alloc_init;
!         aclent_t *entries = buf;
!         aclent_t *malloced = NULL;
!         int count;
  
          for (;;)
            {
!             count = acl (name, GETACL, alloc, entries);
!             if (count < 0 && errno == ENOSPC)
                {
!                 /* Increase the size of the buffer.  */
!                 free (malloced);
!                 if (alloc > alloc_max / 2)
!                   {
!                     errno = ENOMEM;
!                     return -1;
!                   }
!                 alloc = 2 * alloc; /* <= alloc_max */
!                 entries = malloced =
!                   (aclent_t *) malloc (alloc * sizeof (aclent_t));
!                 if (entries == NULL)
!                   {
!                     errno = ENOMEM;
!                     return -1;
!                   }
!                 continue;
                }
!             break;
!           }
!         if (count < 0)
!           {
!             if (errno == ENOSYS || errno == ENOTSUP)
!               ;
!             else
!               {
!                 int saved_errno = errno;
!                 free (malloced);
!                 errno = saved_errno;
!                 return -1;
!               }
!           }
!         else if (count == 0)
!           ;
!         else
!           {
              /* Don't use MIN_ACL_ENTRIES:  It's set to 4 on Cygwin, but Cygwin
                 returns only 3 entries for files with no ACL.  But this is safe:
                 If there are more than 4 entries, there cannot be only the
                 "user::", "group::", "other:", and "mask:" entries.  */
              if (count > 4)
                {
!                 free (malloced);
!                 return 1;
                }
! 
!             if (acl_nontrivial (count, entries))
                {
!                 free (malloced);
!                 return 1;
                }
            }
+         free (malloced);
        }
  
  #   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).  */
        {
!         /* Initially, try to read the entries into a stack-allocated buffer.
!            Use malloc if it does not fit.  */
!         enum
!           {
!             alloc_init = 4000 / sizeof (ace_t), /* >= 3 */
!             alloc_max = MIN (INT_MAX, SIZE_MAX / sizeof (ace_t))
!           };
!         ace_t buf[alloc_init];
!         size_t alloc = alloc_init;
!         ace_t *entries = buf;
!         ace_t *malloced = NULL;
!         int count;
  
          for (;;)
            {
!             count = acl (name, ACE_GETACL, alloc, entries);
!             if (count < 0 && errno == ENOSPC)
                {
!                 /* Increase the size of the buffer.  */
!                 free (malloced);
!                 if (alloc > alloc_max / 2)
!                   {
!                     errno = ENOMEM;
!                     return -1;
!                   }
!                 alloc = 2 * alloc; /* <= alloc_max */
!                 entries = malloced = (ace_t *) malloc (alloc * sizeof (ace_t));
!                 if (entries == NULL)
!                   {
!                     errno = ENOMEM;
!                     return -1;
!                   }
!                 continue;
                }
!             break;
!           }
!         if (count < 0)
!           {
!             if (errno == ENOSYS || errno == EINVAL)
!               ;
!             else
!               {
!                 int saved_errno = errno;
!                 free (malloced);
!                 errno = saved_errno;
!                 return -1;
!               }
!           }
!         else if (count == 0)
!           ;
!         else
!           {
              /* In the old (original Solaris 10) convention:
                 If there are more than 3 entries, there cannot be only the
                 ACE_OWNER, ACE_GROUP, ACE_OTHER entries.
***************
*** 650,686 ****
                 NEW_ACE_ACCESS_ALLOWED_ACE_TYPE and once with
                 NEW_ACE_ACCESS_DENIED_ACE_TYPE.  */
              if (count > 6)
-               return 1;
- 
-             entries = (ace_t *) malloc (count * sizeof (ace_t));
-             if (entries == NULL)
-               {
-                 errno = ENOMEM;
-                 return -1;
-               }
-             ret = acl (name, ACE_GETACL, count, entries);
-             if (ret < 0)
                {
!                 free (entries);
!                 if (errno == ENOSYS || errno == EINVAL)
!                   break;
!                 else
!                   return -1;
                }
!             if (ret == count)
                {
!                 if (acl_ace_nontrivial (count, entries))
!                   {
!                     free (entries);
!                     return 1;
!                   }
!                 free (entries);
!                 break;
                }
-             /* Huh? The number of ACL entries changed since the last call.
-                Repeat.  */
-             free (entries);
            }
        }
  #   endif
  
--- 708,725 ----
                 NEW_ACE_ACCESS_ALLOWED_ACE_TYPE and once with
                 NEW_ACE_ACCESS_DENIED_ACE_TYPE.  */
              if (count > 6)
                {
!                 free (malloced);
!                 return 1;
                }
! 
!             if (acl_ace_nontrivial (count, entries))
                {
!                 free (malloced);
!                 return 1;
                }
            }
+         free (malloced);
        }
  #   endif
  
*** lib/set-mode-acl.c.orig	Mon Feb 20 01:10:13 2012
--- lib/set-mode-acl.c	Mon Feb 20 00:14:51 2012
***************
*** 197,203 ****
    return chmod_or_fchmod (name, desc, mode);
  #  endif
  
! # elif HAVE_FACL && defined GETACLCNT /* Solaris, Cygwin, not HP-UX */
  
    int done_setacl = 0;
  
--- 197,203 ----
    return chmod_or_fchmod (name, desc, mode);
  #  endif
  
! # elif HAVE_FACL && defined GETACL /* Solaris, Cygwin, not HP-UX */
  
    int done_setacl = 0;
  
***************
*** 214,268 ****
    int convention;
  
    {
      int count;
-     ace_t *entries;
  
      for (;;)
        {
!         int ret;
! 
!         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;
!           }
!         ret = (desc != -1
!                ? facl (desc, ACE_GETACL, count, entries)
!                : acl (name, ACE_GETACL, count, entries));
!         if (ret < 0)
            {
!             free (entries);
!             convention = -1;
!             break;
            }
!         if (ret == count)
!           {
!             int i;
  
!             convention = 0;
!             for (i = 0; i < count; i++)
!               if (entries[i].a_flags & (OLD_ACE_OWNER | OLD_ACE_GROUP | OLD_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)
--- 214,273 ----
    int convention;
  
    {
+     /* Initially, try to read the entries into a stack-allocated buffer.
+        Use malloc if it does not fit.  */
+     enum
+       {
+         alloc_init = 4000 / sizeof (ace_t), /* >= 3 */
+         alloc_max = MIN (INT_MAX, SIZE_MAX / sizeof (ace_t))
+       };
+     ace_t buf[alloc_init];
+     size_t alloc = alloc_init;
+     ace_t *entries = buf;
+     ace_t *malloced = NULL;
      int count;
  
      for (;;)
        {
!         count = (desc != -1
!                  ? facl (desc, ACE_GETACL, alloc, entries)
!                  : acl (name, ACE_GETACL, alloc, entries));
!         if (count < 0 && errno == ENOSPC)
            {
!             /* Increase the size of the buffer.  */
!             free (malloced);
!             if (alloc > alloc_max / 2)
!               {
!                 errno = ENOMEM;
!                 return -1;
!               }
!             alloc = 2 * alloc; /* <= alloc_max */
!             entries = malloced = (ace_t *) malloc (alloc * sizeof (ace_t));
!             if (entries == NULL)
!               {
!                 errno = ENOMEM;
!                 return -1;
!               }
!             continue;
            }
!         break;
!       }
  
!     if (count <= 0)
!       convention = -1;
!     else
!       {
!         int i;
! 
!         convention = 0;
!         for (i = 0; i < count; i++)
!           if (entries[i].a_flags & (OLD_ACE_OWNER | OLD_ACE_GROUP | OLD_ACE_OTHER))
!             {
!               convention = 1;
!               break;
!             }
        }
+     free (malloced);
    }
  
    if (convention >= 0)

===============================================================================

You would think that commit is fine, right? But it introduces a test failure
on Solaris 10 over NFS:

files tmpfile0 and tmpfile2: different ACE-ACL entry #0: different access masks 1400207 and 5400607
FAIL: test-copy-acl-2.sh

What's happening, is that the test-copy-acl copies wrong ACL entries.
In the debugger, I can see the following values.
Old code (with ACE_GETACLCNT):

(gdb) print ace_entries[0]
$3 = {a_who = 1088, a_access_mask = 1442183, a_flags = 4096, a_type = 0}
(gdb) print ace_entries[1]
$4 = {a_who = 1088, a_access_mask = 32, a_flags = 4096, a_type = 1}
(gdb) print ace_entries[2]
$5 = {a_who = 1, a_access_mask = 262400, a_flags = 0, a_type = 1}
(gdb) print ace_entries[3]
$6 = {a_who = 1, a_access_mask = 1179808, a_flags = 0, a_type = 0}
(gdb) print ace_entries[4]
$7 = {a_who = 1, a_access_mask = 262407, a_flags = 0, a_type = 1}
(gdb) print ace_entries[5]
$8 = {a_who = 1088, a_access_mask = 262400, a_flags = 8256, a_type = 1}
(gdb) print ace_entries[6]
$9 = {a_who = 1088, a_access_mask = 1179776, a_flags = 8256, a_type = 0}
(gdb) print ace_entries[7]
$10 = {a_who = 1088, a_access_mask = 262439, a_flags = 8256, a_type = 1}
(gdb) print ace_entries[8]
$11 = {a_who = 0, a_access_mask = 1179776, a_flags = 16384, a_type = 0}
(gdb) print ace_entries[9]
$12 = {a_who = 0, a_access_mask = 262439, a_flags = 16384, a_type = 1}

New code (without use of ACE_GETACLCNT):

(gdb) print ace_entries[0]
$8 = {a_who = 1088, a_access_mask = 393351, a_flags = 4096, a_type = 0}
(gdb) print ace_entries[1]
$9 = {a_who = 1088, a_access_mask = 32, a_flags = 4096, a_type = 1}
(gdb) print ace_entries[2]
$10 = {a_who = 1, a_access_mask = 262144, a_flags = 0, a_type = 1}
(gdb) print ace_entries[3]
$11 = {a_who = 1, a_access_mask = 131232, a_flags = 0, a_type = 0}
(gdb) print ace_entries[4]
$12 = {a_who = 1, a_access_mask = 262151, a_flags = 0, a_type = 1}
(gdb) print ace_entries[5]
$13 = {a_who = 1088, a_access_mask = 262144, a_flags = 8256, a_type = 1}
(gdb) print ace_entries[6]
$14 = {a_who = 1088, a_access_mask = 131200, a_flags = 8256, a_type = 0}
(gdb) print ace_entries[7]
$15 = {a_who = 1088, a_access_mask = 262183, a_flags = 8256, a_type = 1}
(gdb) print ace_entries[8]
$16 = {a_who = 0, a_access_mask = 131200, a_flags = 16384, a_type = 0}
(gdb) print ace_entries[9]
$17 = {a_who = 0, a_access_mask = 262183, a_flags = 16384, a_type = 1}

As you can see, most of the a_access_mask values are broken. I haven't
yet found out the real cause, but I know that the modification of copy-acl.c
triggers it. Therefore I'm not committing this part of the patch today.
I attach it, in case you want to experiment further with it.

Then the part for HP-UX and NSK:

===============================================================================

2012-02-19  Paul Eggert  <[email-masked]>
	    Bruno Haible  <[email-masked]>

	acl: Don't use ACL_CNT and similar ops, since they are unreliable.
	* lib/file-has-acl.c (file_has_acl) [HP-UX, NonStop Kernel]: Read the
	entries into a stack-allocated buffer directly.
	* lib/copy-acl.c (qcopy_acl) [HP-UX, NonStop Kernel]: Likewise.

*** lib/copy-acl.c.orig	Mon Feb 20 01:51:49 2012
--- lib/copy-acl.c	Mon Feb 20 01:29:02 2012
***************
*** 408,484 ****
  
  #elif USE_ACL && HAVE_GETACL /* HP-UX */
  
-   int count;
    struct acl_entry entries[NACLENTRIES];
  # if HAVE_ACLV_H
-   int aclv_count;
    struct acl aclv_entries[NACLVENTRIES];
  # endif
    int did_chmod;
    int saved_errno;
    int ret;
  
!   for (;;)
!     {
!       count = (source_desc != -1
!                ? fgetacl (source_desc, 0, NULL)
!                : getacl (src_name, 0, NULL));
! 
!       if (count < 0)
!         {
!           if (errno == ENOSYS || errno == EOPNOTSUPP || errno == ENOTSUP)
!             {
!               count = 0;
!               break;
!             }
!           else
!             return -2;
!         }
! 
!       if (count == 0)
!         break;
  
        if (count > NACLENTRIES)
          /* If NACLENTRIES cannot be trusted, use dynamic memory allocation.  */
          abort ();
- 
-       if ((source_desc != -1
-            ? fgetacl (source_desc, count, entries)
-            : getacl (src_name, count, entries))
-           == count)
-         break;
-       /* Huh? The number of ACL entries changed since the last call.
-          Repeat.  */
      }
  
  # if HAVE_ACLV_H
!   for (;;)
!     {
!       aclv_count = acl ((char *) src_name, ACL_CNT, NACLVENTRIES, aclv_entries);
! 
!       if (aclv_count < 0)
!         {
!           if (errno == ENOSYS || errno == EOPNOTSUPP || errno == EINVAL)
!             {
!               count = 0;
!               break;
!             }
!           else
!             return -2;
!         }
! 
!       if (aclv_count == 0)
!         break;
  
        if (aclv_count > NACLVENTRIES)
          /* If NACLVENTRIES cannot be trusted, use dynamic memory allocation.  */
          abort ();
- 
-       if (acl ((char *) src_name, ACL_GET, aclv_count, aclv_entries)
-           == aclv_count)
-         break;
-       /* Huh? The number of ACL entries changed since the last call.
-          Repeat.  */
      }
  # endif
  
--- 408,456 ----
  
  #elif USE_ACL && HAVE_GETACL /* HP-UX */
  
    struct acl_entry entries[NACLENTRIES];
+   int count;
  # if HAVE_ACLV_H
    struct acl aclv_entries[NACLVENTRIES];
+   int aclv_count;
  # endif
    int did_chmod;
    int saved_errno;
    int ret;
  
!   count = (source_desc != -1
!            ? fgetacl (source_desc, NACLENTRIES, entries)
!            : getacl (src_name, NACLENTRIES, entries));
  
+   if (count < 0)
+     {
+       if (errno == ENOSYS || errno == EOPNOTSUPP || errno == ENOTSUP)
+         count = 0;
+       else
+         return -2;
+     }
+   else if (count > 0)
+     {
        if (count > NACLENTRIES)
          /* If NACLENTRIES cannot be trusted, use dynamic memory allocation.  */
          abort ();
      }
  
  # if HAVE_ACLV_H
!   aclv_count = acl ((char *) src_name, ACL_GET, NACLVENTRIES, aclv_entries);
  
+   if (aclv_count < 0)
+     {
+       if (errno == ENOSYS || errno == EOPNOTSUPP || errno == EINVAL)
+         count = 0;
+       else
+         return -2;
+     }
+   else if (aclv_count > 0)
+     {
        if (aclv_count > NACLVENTRIES)
          /* If NACLVENTRIES cannot be trusted, use dynamic memory allocation.  */
          abort ();
      }
  # endif
  
***************
*** 589,624 ****
  
  #elif USE_ACL && HAVE_ACLSORT /* NonStop Kernel */
  
-   int count;
    struct acl entries[NACLENTRIES];
    int ret;
  
!   for (;;)
!     {
!       count = acl ((char *) src_name, ACL_CNT, NACLENTRIES, NULL);
! 
!       if (count < 0)
!         {
!           if (0)
!             {
!               count = 0;
!               break;
!             }
!           else
!             return -2;
!         }
! 
!       if (count == 0)
!         break;
  
        if (count > NACLENTRIES)
          /* If NACLENTRIES cannot be trusted, use dynamic memory allocation.  */
          abort ();
- 
-       if (acl ((char *) src_name, ACL_GET, count, entries) == count)
-         break;
-       /* Huh? The number of ACL entries changed since the last call.
-          Repeat.  */
      }
  
    if (count == 0)
--- 561,584 ----
  
  #elif USE_ACL && HAVE_ACLSORT /* NonStop Kernel */
  
    struct acl entries[NACLENTRIES];
+   int count;
    int ret;
  
!   count = acl ((char *) src_name, ACL_GET, NACLENTRIES, entries);
  
+   if (count < 0)
+     {
+       if (0)
+         count = 0;
+       else
+         return -2;
+     }
+   else if (count > 0)
+     {
        if (count > NACLENTRIES)
          /* If NACLENTRIES cannot be trusted, use dynamic memory allocation.  */
          abort ();
      }
  
    if (count == 0)
*** lib/file-has-acl.c.orig	Mon Feb 20 01:51:49 2012
--- lib/file-has-acl.c	Mon Feb 20 01:49:41 2012
***************
*** 728,765 ****
  
  # elif HAVE_GETACL /* HP-UX */
  
!       for (;;)
!         {
!           int count;
!           struct acl_entry entries[NACLENTRIES];
! 
!           count = getacl (name, 0, NULL);
! 
!           if (count < 0)
!             {
!               /* ENOSYS is seen on newer HP-UX versions.
!                  EOPNOTSUPP is typically seen on NFS mounts.
!                  ENOTSUP was seen on Quantum StorNext file systems (cvfs).  */
!               if (errno == ENOSYS || errno == EOPNOTSUPP || errno == ENOTSUP)
!                 break;
!               else
!                 return -1;
!             }
! 
!           if (count == 0)
!             return 0;
! 
!           if (count > NACLENTRIES)
!             /* If NACLENTRIES cannot be trusted, use dynamic memory
!                allocation.  */
!             abort ();
! 
!           /* If there are more than 3 entries, there cannot be only the
!              (uid,%), (%,gid), (%,%) entries.  */
!           if (count > 3)
!             return 1;
  
-           if (getacl (name, count, entries) == count)
              {
                struct stat statbuf;
  
--- 728,763 ----
  
  # elif HAVE_GETACL /* HP-UX */
  
!       {
!         struct acl_entry entries[NACLENTRIES];
!         int count;
! 
!         count = getacl (name, NACLENTRIES, entries);
! 
!         if (count < 0)
!           {
!             /* ENOSYS is seen on newer HP-UX versions.
!                EOPNOTSUPP is typically seen on NFS mounts.
!                ENOTSUP was seen on Quantum StorNext file systems (cvfs).  */
!             if (errno == ENOSYS || errno == EOPNOTSUPP || errno == ENOTSUP)
!               ;
!             else
!               return -1;
!           }
!         else if (count == 0)
!           return 0;
!         else /* count > 0 */
!           {
!             if (count > NACLENTRIES)
!               /* If NACLENTRIES cannot be trusted, use dynamic memory
!                  allocation.  */
!               abort ();
! 
!             /* If there are more than 3 entries, there cannot be only the
!                (uid,%), (%,gid), (%,%) entries.  */
!             if (count > 3)
!               return 1;
  
              {
                struct stat statbuf;
  
***************
*** 768,814 ****
  
                return acl_nontrivial (count, entries, &statbuf);
              }
!           /* Huh? The number of ACL entries changed since the last call.
!              Repeat.  */
!         }
  
  #  if HAVE_ACLV_H /* HP-UX >= 11.11 */
  
!       for (;;)
!         {
!           int count;
!           struct acl entries[NACLVENTRIES];
! 
!           count = acl ((char *) name, ACL_CNT, NACLVENTRIES, entries);
! 
!           if (count < 0)
!             {
!               /* EOPNOTSUPP is seen on NFS in HP-UX 11.11, 11.23.
!                  EINVAL is seen on NFS in HP-UX 11.31.  */
!               if (errno == ENOSYS || errno == EOPNOTSUPP || errno == EINVAL)
!                 break;
!               else
!                 return -1;
!             }
! 
!           if (count == 0)
!             return 0;
! 
!           if (count > NACLVENTRIES)
!             /* If NACLVENTRIES cannot be trusted, use dynamic memory
!                allocation.  */
!             abort ();
! 
!           /* If there are more than 4 entries, there cannot be only the
!              four base ACL entries.  */
!           if (count > 4)
!             return 1;
  
-           if (acl ((char *) name, ACL_GET, count, entries) == count)
              return aclv_nontrivial (count, entries);
!           /* Huh? The number of ACL entries changed since the last call.
!              Repeat.  */
!         }
  
  #  endif
  
--- 766,808 ----
  
                return acl_nontrivial (count, entries, &statbuf);
              }
!           }
!       }
  
  #  if HAVE_ACLV_H /* HP-UX >= 11.11 */
  
!       {
!         struct acl entries[NACLVENTRIES];
!         int count;
! 
!         count = acl ((char *) name, ACL_GET, NACLVENTRIES, entries);
! 
!         if (count < 0)
!           {
!             /* EOPNOTSUPP is seen on NFS in HP-UX 11.11, 11.23.
!                EINVAL is seen on NFS in HP-UX 11.31.  */
!             if (errno == ENOSYS || errno == EOPNOTSUPP || errno == EINVAL)
!               ;
!             else
!               return -1;
!           }
!         else if (count == 0)
!           return 0;
!         else /* count > 0 */
!           {
!             if (count > NACLVENTRIES)
!               /* If NACLVENTRIES cannot be trusted, use dynamic memory
!                  allocation.  */
!               abort ();
! 
!             /* If there are more than 4 entries, there cannot be only the
!                four base ACL entries.  */
!             if (count > 4)
!               return 1;
  
              return aclv_nontrivial (count, entries);
!           }
!       }
  
  #  endif
  
***************
*** 885,923 ****
  
  # elif HAVE_ACLSORT /* NonStop Kernel */
  
!       int count;
!       struct acl entries[NACLENTRIES];
! 
!       for (;;)
!         {
!           count = acl ((char *) name, ACL_CNT, NACLENTRIES, NULL);
! 
!           if (count < 0)
!             {
!               if (errno == ENOSYS || errno == ENOTSUP)
!                 break;
!               else
!                 return -1;
!             }
! 
!           if (count == 0)
!             return 0;
! 
!           if (count > NACLENTRIES)
!             /* If NACLENTRIES cannot be trusted, use dynamic memory
!                allocation.  */
!             abort ();
! 
!           /* If there are more than 4 entries, there cannot be only the
!              four base ACL entries.  */
!           if (count > 4)
!             return 1;
  
-           if (acl ((char *) name, ACL_GET, count, entries) == count)
              return acl_nontrivial (count, entries);
!           /* Huh? The number of ACL entries changed since the last call.
!              Repeat.  */
!         }
  
  # endif
      }
--- 879,914 ----
  
  # elif HAVE_ACLSORT /* NonStop Kernel */
  
!       {
!         struct acl entries[NACLENTRIES];
!         int count;
! 
!         count = acl ((char *) name, ACL_GET, NACLENTRIES, entries);
! 
!         if (count < 0)
!           {
!             if (errno == ENOSYS || errno == ENOTSUP)
!               ;
!             else
!               return -1;
!           }
!         else if (count == 0)
!           return 0;
!         else /* count > 0 */
!           {
!             if (count > NACLENTRIES)
!               /* If NACLENTRIES cannot be trusted, use dynamic memory
!                  allocation.  */
!               abort ();
! 
!             /* If there are more than 4 entries, there cannot be only the
!                four base ACL entries.  */
!             if (count > 4)
!               return 1;
  
              return acl_nontrivial (count, entries);
!           }
!       }
  
  # endif
      }

===============================================================================

Bruno

diff --git a/lib/copy-acl.c b/lib/copy-acl.c
*** a/lib/copy-acl.c
--- b/lib/copy-acl.c
***************
*** 181,191 ****
       of Unixware.  The acl() call returns the access and default ACL both
       at once.  */
  # ifdef ACE_GETACL
    int ace_count;
-   ace_t *ace_entries;
  # endif
    int count;
-   aclent_t *entries;
    int did_chmod;
    int saved_errno;
    int ret;
--- 181,207 ----
       of Unixware.  The acl() call returns the access and default ACL both
       at once.  */
  # ifdef ACE_GETACL
+   enum
+     {
+       ace_alloc_init = 4000 / sizeof (ace_t), /* >= 3 */
+       ace_alloc_max = MIN (INT_MAX, SIZE_MAX / sizeof (ace_t))
+     };
+   ace_t ace_buf[ace_alloc_init];
+   size_t ace_alloc = ace_alloc_init;
+   ace_t *ace_entries = ace_buf;
+   ace_t *ace_malloced = NULL;
    int ace_count;
  # endif
+   enum
+     {
+       alloc_init = 4000 / sizeof (aclent_t), /* >= 3 */
+       alloc_max = MIN (INT_MAX, SIZE_MAX / sizeof (aclent_t))
+     };
+   aclent_t buf[alloc_init];
+   size_t alloc = alloc_init;
+   aclent_t *entries = buf;
+   aclent_t *malloced = NULL;
    int count;
    int did_chmod;
    int saved_errno;
    int ret;
***************
*** 204,308 ****
       that the kernel will translate the ACL from one form to the other.
       (See in <http://docs.sun.com/app/docs/doc/819-2241/6n4huc7ia?l=en&a=view>
       the description of ENOTSUP.)  */
    for (;;)
      {
        ace_count = (source_desc != -1
!                    ? facl (source_desc, ACE_GETACLCNT, 0, NULL)
!                    : acl (src_name, ACE_GETACLCNT, 0, NULL));
! 
!       if (ace_count < 0)
          {
!           if (errno == ENOSYS || errno == EINVAL)
              {
!               ace_count = 0;
!               ace_entries = NULL;
!               break;
              }
!           else
!             return -2;
          }
! 
!       if (ace_count == 0)
          {
            ace_entries = NULL;
-           break;
          }
! 
!       ace_entries = (ace_t *) malloc (ace_count * sizeof (ace_t));
!       if (ace_entries == NULL)
          {
!           errno = ENOMEM;
            return -2;
          }
- 
-       ret = (source_desc != -1
-              ? facl (source_desc, ACE_GETACL, ace_count, ace_entries)
-              : acl (src_name, ACE_GETACL, ace_count, ace_entries));
-       if (ret < 0)
-         {
-           free (ace_entries);
-           if (errno == ENOSYS || errno == EINVAL)
-             {
-               ace_count = 0;
-               ace_entries = NULL;
-               break;
-             }
-           else
-             return -2;
-         }
-       if (ret == ace_count)
-         break;
-       /* Huh? The number of ACL entries changed since the last call.
-          Repeat.  */
      }
  # endif
  
    for (;;)
      {
        count = (source_desc != -1
!                ? facl (source_desc, GETACLCNT, 0, NULL)
!                : acl (src_name, GETACLCNT, 0, NULL));
! 
!       if (count < 0)
          {
!           if (errno == ENOSYS || errno == ENOTSUP || errno == EOPNOTSUPP)
              {
!               count = 0;
!               entries = NULL;
!               break;
              }
!           else
!             return -2;
          }
! 
!       if (count == 0)
          {
            entries = NULL;
-           break;
          }
! 
!       entries = (aclent_t *) malloc (count * sizeof (aclent_t));
!       if (entries == NULL)
          {
!           errno = ENOMEM;
            return -2;
          }
- 
-       if ((source_desc != -1
-            ? facl (source_desc, GETACL, count, entries)
-            : acl (src_name, GETACL, count, entries))
-           == count)
-         break;
-       /* Huh? The number of ACL entries changed since the last call.
-          Repeat.  */
      }
  
    /* Is there an ACL of either kind?  */
  # ifdef ACE_GETACL
    if (ace_count == 0)
  # endif
      if (count == 0)
!       return qset_acl (dst_name, dest_desc, mode);
  
    did_chmod = 0; /* set to 1 once the mode bits in 0777 have been set */
    saved_errno = 0; /* the first non-ignorable error code */
--- 220,339 ----
       that the kernel will translate the ACL from one form to the other.
       (See in <http://docs.sun.com/app/docs/doc/819-2241/6n4huc7ia?l=en&a=view>
       the description of ENOTSUP.)  */
+ 
+   /* Initially, try to read the entries into a stack-allocated buffer.
+      Use malloc if it does not fit.  */
    for (;;)
      {
        ace_count = (source_desc != -1
!                    ? facl (source_desc, ACE_GETACL, ace_alloc, ace_entries)
!                    : acl (src_name, ACE_GETACL, ace_alloc, ace_entries));
!       if (ace_count < 0 && errno == ENOSPC)
          {
!           /* Increase the size of the buffer.  */
!           free (ace_malloced);
!           if (ace_alloc > ace_alloc_max / 2)
!             {
!               errno = ENOMEM;
!               return -2;
!             }
!           ace_alloc = 2 * ace_alloc; /* <= ace_alloc_max */
!           ace_entries = ace_malloced =
!             (ace_t *) malloc (ace_alloc * sizeof (ace_t));
!           if (ace_entries == NULL)
              {
!               errno = ENOMEM;
!               return -2;
              }
!           continue;
          }
!       break;
!     }
!   if (ace_count < 0)
!     {
!       if (errno == ENOSYS || errno == EINVAL)
          {
+           ace_count = 0;
            ace_entries = NULL;
          }
!       else
          {
!           int saved_errno = errno;
!           free (ace_malloced);
!           errno = saved_errno;
            return -2;
          }
      }
+   else if (ace_count == 0)
+     ace_entries = NULL;
  # endif
  
+   /* Initially, try to read the entries into a stack-allocated buffer.
+      Use malloc if it does not fit.  */
    for (;;)
      {
        count = (source_desc != -1
!                ? facl (source_desc, GETACL, alloc, entries)
!                : acl (src_name, GETACL, alloc, entries));
!       if (count < 0 && errno == ENOSPC)
          {
!           /* Increase the size of the buffer.  */
!           free (malloced);
!           if (alloc > alloc_max / 2)
!             {
! # ifdef ACE_GETACL
!               free (ace_malloced);
! # endif
!               errno = ENOMEM;
!               return -2;
!             }
!           alloc = 2 * alloc; /* <= alloc_max */
!           entries = malloced = (aclent_t *) malloc (alloc * sizeof (aclent_t));
!           if (entries == NULL)
              {
! # ifdef ACE_GETACL
!               free (ace_malloced);
! # endif
!               errno = ENOMEM;
!               return -2;
              }
!           continue;
          }
!       break;
!     }
!   if (count < 0)
!     {
!       if (errno == ENOSYS || errno == ENOTSUP || errno == EOPNOTSUPP)
          {
+           count = 0;
            entries = NULL;
          }
!       else
          {
!           int saved_errno = errno;
! # ifdef ACE_GETACL
!           free (ace_malloced);
! # endif
!           free (malloced);
!           errno = saved_errno;
            return -2;
          }
      }
+   else if (count == 0)
+     entries = NULL;
  
    /* Is there an ACL of either kind?  */
  # ifdef ACE_GETACL
    if (ace_count == 0)
  # endif
      if (count == 0)
!       {
! # ifdef ACE_GETACL
!         free (ace_malloced);
! # endif
!         free (malloced);
!         return qset_acl (dst_name, dest_desc, mode);
!       }
  
    did_chmod = 0; /* set to 1 once the mode bits in 0777 have been set */
    saved_errno = 0; /* the first non-ignorable error code */
***************
*** 336,342 ****
        else
          did_chmod = 1;
      }
!   free (entries);
  
  # ifdef ACE_GETACL
    if (ace_count > 0)
--- 367,373 ----
        else
          did_chmod = 1;
      }
!   free (malloced);
  
  # ifdef ACE_GETACL
    if (ace_count > 0)
***************
*** 352,358 ****
              saved_errno = 0;
          }
      }
!   free (ace_entries);
  # endif
  
    if (MODE_INSIDE_ACL
--- 383,389 ----
              saved_errno = 0;
          }
      }
!   free (ace_malloced);
  # endif
  
    if (MODE_INSIDE_ACL