summaryrefslogtreecommitdiff
path: root/lib/file-has-acl.c
diff options
context:
space:
mode:
Diffstat (limited to 'lib/file-has-acl.c')
-rw-r--r--lib/file-has-acl.c94
1 files changed, 67 insertions, 27 deletions
diff --git a/lib/file-has-acl.c b/lib/file-has-acl.c
index b31a2ea252..1edcd2cbd6 100644
--- a/lib/file-has-acl.c
+++ b/lib/file-has-acl.c
@@ -29,7 +29,7 @@
#include "acl-internal.h"
-#if USE_ACL && GETXATTR_WITH_POSIX_ACLS
+#if USE_ACL && HAVE_LINUX_XATTR_H && HAVE_LISTXATTR
# include <string.h>
# include <arpa/inet.h>
# include <sys/xattr.h>
@@ -44,6 +44,20 @@ enum {
ACE4_IDENTIFIER_GROUP = 0x00000040
};
+/* Return true if ATTR is in the set represented by the NUL-terminated
+ strings in LISTBUF, which is of size LISTSIZE. */
+
+static bool
+have_xattr (char const *attr, char const *listbuf, ssize_t listsize)
+{
+ char const *blim = listbuf + listsize;
+ for (char const *b = listbuf; b < blim; b += strlen (b) + 1)
+ for (char const *a = attr; *a == *b; a++, b++)
+ if (!*a)
+ return true;
+ return false;
+}
+
/* Return 1 if given ACL in XDR format is non-trivial, 0 if it is trivial.
-1 upon failure to determine it. Possibly change errno. Assume that
the ACL is valid, except avoid undefined behavior even if invalid.
@@ -137,37 +151,63 @@ file_has_acl (char const *name, struct stat const *sb)
if (! S_ISLNK (sb->st_mode))
{
-# if GETXATTR_WITH_POSIX_ACLS
-
- ssize_t ret;
+# if HAVE_LINUX_XATTR_H && HAVE_LISTXATTR
int initial_errno = errno;
- ret = getxattr (name, XATTR_NAME_POSIX_ACL_ACCESS, NULL, 0);
- if (ret < 0 && errno == ENODATA)
- ret = 0;
- else if (ret > 0)
- return 1;
-
- if (ret == 0 && S_ISDIR (sb->st_mode))
+ /* The max length of a trivial NFSv4 ACL is 6 words for owner,
+ 6 for group, 7 for everyone, all times 2 because there are
+ both allow and deny ACEs. There are 6 words for owner
+ because of type, flag, mask, wholen, "OWNER@"+pad and
+ similarly for group; everyone is another word to hold
+ "EVERYONE@". */
+ typedef uint32_t trivial_NFSv4_xattr_buf[2 * (6 + 6 + 7)];
+
+ /* A buffer large enough to hold any trivial NFSv4 ACL,
+ and also useful as a small array of char. */
+ union {
+ trivial_NFSv4_xattr_buf xattr;
+ char ch[sizeof (trivial_NFSv4_xattr_buf)];
+ } stackbuf;
+
+ char *listbuf = stackbuf.ch;
+ ssize_t listbufsize = sizeof stackbuf.ch;
+ char *heapbuf = NULL;
+ ssize_t listsize;
+
+ /* Use listxattr first, as this means just one syscall in the
+ typical case where the file lacks an ACL. Try stackbuf
+ first, falling back on malloc if stackbuf is too small. */
+ while ((listsize = listxattr (name, listbuf, listbufsize)) < 0
+ && errno == ERANGE)
{
- ret = getxattr (name, XATTR_NAME_POSIX_ACL_DEFAULT, NULL, 0);
- if (ret < 0 && errno == ENODATA)
- ret = 0;
- else if (ret > 0)
- return 1;
+ free (heapbuf);
+ listbufsize = listxattr (name, NULL, 0);
+ if (listbufsize < 0)
+ return -1;
+ if (SIZE_MAX < listbufsize)
+ {
+ errno = ENOMEM;
+ return -1;
+ }
+ listbuf = heapbuf = malloc (listbufsize);
+ if (!listbuf)
+ return -1;
}
- if (ret < 0)
+ int ret
+ = (listsize < 0 ? -1
+ : (have_xattr (XATTR_NAME_POSIX_ACL_ACCESS, listbuf, listsize)
+ || (S_ISDIR (sb->st_mode)
+ && have_xattr (XATTR_NAME_POSIX_ACL_DEFAULT,
+ listbuf, listsize))));
+ free (heapbuf);
+
+ /* If there is an NFSv4 ACL but no POSIX ACL, follow up with a
+ getxattr syscall to see whether the NFSv4 ACL is nontrivial. */
+ if (ret == 0 && have_xattr (XATTR_NAME_NFSV4_ACL, listbuf, listsize))
{
- /* Check for NFSv4 ACLs. The max length of a trivial
- ACL is 6 words for owner, 6 for group, 7 for everyone,
- all times 2 because there are both allow and deny ACEs.
- There are 6 words for owner because of type, flag, mask,
- wholen, "OWNER@"+pad and similarly for group; everyone is
- another word to hold "EVERYONE@". */
- uint32_t xattr[2 * (6 + 6 + 7)];
-
- ret = getxattr (name, XATTR_NAME_NFSV4_ACL, xattr, sizeof xattr);
+ ret = getxattr (name, XATTR_NAME_NFSV4_ACL,
+ stackbuf.xattr, sizeof stackbuf.xattr);
if (ret < 0)
switch (errno)
{
@@ -177,7 +217,7 @@ file_has_acl (char const *name, struct stat const *sb)
else
{
/* It looks like a trivial ACL, but investigate further. */
- ret = acl_nfs4_nontrivial (xattr, ret);
+ ret = acl_nfs4_nontrivial (stackbuf.xattr, ret);
if (ret < 0)
{
errno = EINVAL;