diff --git a/admin/merge-gnulib b/admin/merge-gnulib index 1c8b442700..c12e83dd2f 100755 --- a/admin/merge-gnulib +++ b/admin/merge-gnulib @@ -33,7 +33,7 @@ GNULIB_MODULES= crypto/md5-buffer crypto/sha1-buffer crypto/sha256-buffer crypto/sha512-buffer d-type diffseq double-slash-root dtoastr dtotimespec dup2 environ execinfo explicit_bzero faccessat - fchmodat fcntl fcntl-h fdopendir + fchmodat fcntl fcntl-h fdopendir file-has-acl filemode filename filevercmp flexmember fpieee free-posix fstatat fsusage fsync futimens getloadavg getopt-gnu getrandom gettime gettimeofday gitlog-to-changelog diff --git a/lib-src/emacsclient.c b/lib-src/emacsclient.c index 12ced4aadb..3972cbba45 100644 --- a/lib-src/emacsclient.c +++ b/lib-src/emacsclient.c @@ -80,6 +80,7 @@ Copyright (C) 1986-1987, 1994, 1999-2021 Free Software Foundation, Inc. #include #include +#include #include #include #include @@ -91,6 +92,10 @@ Copyright (C) 1986-1987, 1994, 1999-2021 Free Software Foundation, Inc. # pragma GCC diagnostic ignored "-Wformat-truncation=2" #endif +#ifndef O_PATH +# define O_PATH O_SEARCH +#endif + /* Name used to invoke this program. */ static char const *progname; @@ -1128,24 +1133,74 @@ process_grouping (void) #ifdef SOCKETS_IN_FILE_SYSTEM -/* Return the file status of NAME, ordinarily a socket. - It should be owned by UID. Return one of the following: - >0 - 'stat' failed with this errno value - -1 - isn't owned by us - 0 - success: none of the above */ +/* A local socket address. The union avoids the need to cast. */ +union local_sockaddr +{ + struct sockaddr_un un; + struct sockaddr sa; +}; + +/* Relative to the directory DIRFD, connect the socket file named ADDR + to the socket S. Return 0 if successful, -1 if DIRFD is not + AT_FDCWD and DIRFD's permissions would allow a symlink attack, an + errno otherwise. */ static int -socket_status (const char *name, uid_t uid) +connect_socket (int dirfd, char const *addr, int s, uid_t uid) { - struct stat statbfr; + int sock_status = 0; - if (stat (name, &statbfr) != 0) - return errno; + union local_sockaddr server; + if (sizeof server.un.sun_path <= strlen (addr)) + return ENAMETOOLONG; + server.un.sun_family = AF_UNIX; + strcpy (server.un.sun_path, addr); - if (statbfr.st_uid != uid) - return -1; + /* If -1, WDFD is not set yet. If nonnegative, WDFD is a file + descriptor for the initial working directory. Otherwise -1 - WDFD is + the error number for the initial working directory. */ + static int wdfd = -1; - return 0; + if (dirfd != AT_FDCWD) + { + /* Fail if DIRFD's permissions are bogus. */ + struct stat st; + if (fstat (dirfd, &st) != 0) + return errno; + if (st.st_uid != uid || (st.st_mode & (S_IWGRP | S_IWOTH))) + return -1; + + if (wdfd == -1) + { + /* Save the initial working directory. */ + wdfd = open (".", O_PATH | O_CLOEXEC); + if (wdfd < 0) + wdfd = -1 - errno; + } + if (wdfd < 0) + return -1 - wdfd; + if (fchdir (dirfd) != 0) + return errno; + + /* Fail if DIRFD has an ACL, which means its permissions are + almost surely bogus. */ + int has_acl = file_has_acl (".", &st); + if (has_acl) + sock_status = has_acl < 0 ? errno : -1; + } + + if (!sock_status) + sock_status = connect (s, &server.sa, sizeof server.un) == 0 ? 0 : errno; + + /* Fail immediately if we cannot change back to the initial working + directory, as that can mess up the rest of execution. */ + if (dirfd != AT_FDCWD && fchdir (wdfd) != 0) + { + message (true, "%s: .: %s\n", progname, strerror (errno)); + exit (EXIT_FAILURE); + } + + return sock_status; } @@ -1322,32 +1377,49 @@ act_on_signals (HSOCKET emacs_socket) } } -/* Create in SOCKNAME (of size SOCKNAMESIZE) a name for a local socket. - The first TMPDIRLEN bytes of SOCKNAME are already initialized to be - the name of a temporary directory. Use UID and SERVER_NAME to - concoct the name. Return the total length of the name if successful, - -1 if it does not fit (and store a truncated name in that case). - Fail if TMPDIRLEN is out of range. */ +enum { socknamesize = sizeof ((struct sockaddr_un *) NULL)->sun_path }; + +/* Given a local socket S, create in *SOCKNAME a name for a local socket + and connect to that socket. The first TMPDIRLEN bytes of *SOCKNAME are + already initialized to be the name of a temporary directory. + Use UID and SERVER_NAME to concoct the name. Return 0 if + successful, -1 if the socket's parent directory is not safe, and an + errno if there is some other problem. */ static int -local_sockname (char *sockname, int socknamesize, int tmpdirlen, - uintmax_t uid, char const *server_name) +local_sockname (int s, char sockname[socknamesize], int tmpdirlen, + uid_t uid, char const *server_name) { /* If ! (0 <= TMPDIRLEN && TMPDIRLEN < SOCKNAMESIZE) the truncated temporary directory name is already in SOCKNAME, so nothing more need be stored. */ - if (0 <= tmpdirlen) - { - int remaining = socknamesize - tmpdirlen; - if (0 < remaining) - { - int suffixlen = snprintf (&sockname[tmpdirlen], remaining, - "/emacs%"PRIuMAX"/%s", uid, server_name); - if (0 <= suffixlen && suffixlen < remaining) - return tmpdirlen + suffixlen; - } - } - return -1; + if (! (0 <= tmpdirlen && tmpdirlen < socknamesize)) + return ENAMETOOLONG; + + /* Put the full address name into the buffer, since the caller might + need it for diagnostics. But don't overrun the buffer. */ + uintmax_t uidmax = uid; + int emacsdirlen; + int suffixlen = snprintf (sockname + tmpdirlen, socknamesize - tmpdirlen, + "/emacs%"PRIuMAX"%n/%s", uidmax, &emacsdirlen, + server_name); + if (! (0 <= suffixlen && suffixlen < socknamesize - tmpdirlen)) + return ENAMETOOLONG; + + /* Make sure the address's parent directory is not a symlink and is + this user's directory and does not let others write to it; this + fends off some symlink attacks. To avoid races, keep the parent + directory open while checking. */ + char *emacsdirend = sockname + tmpdirlen + emacsdirlen; + *emacsdirend = '\0'; + int dir = openat (AT_FDCWD, sockname, + O_PATH | O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC); + *emacsdirend = '/'; + if (dir < 0) + return errno; + int sock_status = connect_socket (dir, server_name, s, uid); + close (dir); + return sock_status; } /* Create a local socket for SERVER_NAME and connect it to Emacs. If @@ -1358,28 +1430,43 @@ local_sockname (char *sockname, int socknamesize, int tmpdirlen, static HSOCKET set_local_socket (char const *server_name) { - union { - struct sockaddr_un un; - struct sockaddr sa; - } server = {{ .sun_family = AF_UNIX }}; + union local_sockaddr server; + int sock_status; char *sockname = server.un.sun_path; - enum { socknamesize = sizeof server.un.sun_path }; int tmpdirlen = -1; int socknamelen = -1; uid_t uid = geteuid (); bool tmpdir_used = false; + int s = cloexec_socket (AF_UNIX, SOCK_STREAM, 0); + if (s < 0) + { + message (true, "%s: can't create socket: %s\n", + progname, strerror (errno)); + fail (); + } if (strchr (server_name, '/') || (ISSLASH ('\\') && strchr (server_name, '\\'))) - socknamelen = snprintf (sockname, socknamesize, "%s", server_name); + { + socknamelen = snprintf (sockname, socknamesize, "%s", server_name); + sock_status = (0 <= socknamelen && socknamelen < socknamesize + ? connect_socket (AT_FDCWD, sockname, s, 0) + : ENAMETOOLONG); + } else { /* socket_name is a file name component. */ + sock_status = ENOENT; char const *xdg_runtime_dir = egetenv ("XDG_RUNTIME_DIR"); if (xdg_runtime_dir) - socknamelen = snprintf (sockname, socknamesize, "%s/emacs/%s", - xdg_runtime_dir, server_name); - else + { + socknamelen = snprintf (sockname, socknamesize, "%s/emacs/%s", + xdg_runtime_dir, server_name); + sock_status = (0 <= socknamelen && socknamelen < socknamesize + ? connect_socket (AT_FDCWD, sockname, s, 0) + : ENAMETOOLONG); + } + if (sock_status == ENOENT) { char const *tmpdir = egetenv ("TMPDIR"); if (tmpdir) @@ -1398,23 +1485,24 @@ set_local_socket (char const *server_name) if (tmpdirlen < 0) tmpdirlen = snprintf (sockname, socknamesize, "/tmp"); } - socknamelen = local_sockname (sockname, socknamesize, tmpdirlen, + sock_status = local_sockname (s, sockname, tmpdirlen, uid, server_name); tmpdir_used = true; } } - if (! (0 <= socknamelen && socknamelen < socknamesize)) + if (sock_status == 0) + return s; + + if (sock_status == ENAMETOOLONG) { message (true, "%s: socket-name %s... too long\n", progname, sockname); fail (); } - /* See if the socket exists, and if it's owned by us. */ - int sock_status = socket_status (sockname, uid); - if (sock_status) + if (tmpdir_used) { - /* Failing that, see if LOGNAME or USER exist and differ from + /* See whether LOGNAME or USER exist and differ from our euid. If so, look for a socket based on the UID associated with the name. This is reminiscent of the logic that init_editfns uses to set the global Vuser_full_name. */ @@ -1431,48 +1519,26 @@ set_local_socket (char const *server_name) if (pw && pw->pw_uid != uid) { /* We're running under su, apparently. */ - socknamelen = local_sockname (sockname, socknamesize, tmpdirlen, + sock_status = local_sockname (s, sockname, tmpdirlen, pw->pw_uid, server_name); - if (socknamelen < 0) + if (sock_status == 0) + return s; + if (sock_status == ENAMETOOLONG) { message (true, "%s: socket-name %s... too long\n", progname, sockname); exit (EXIT_FAILURE); } - - sock_status = socket_status (sockname, uid); } } } - if (sock_status == 0) - { - HSOCKET s = cloexec_socket (AF_UNIX, SOCK_STREAM, 0); - if (s < 0) - { - message (true, "%s: socket: %s\n", progname, strerror (errno)); - return INVALID_SOCKET; - } - if (connect (s, &server.sa, sizeof server.un) != 0) - { - message (true, "%s: connect: %s\n", progname, strerror (errno)); - CLOSE_SOCKET (s); - return INVALID_SOCKET; - } - - struct stat connect_stat; - if (fstat (s, &connect_stat) != 0) - sock_status = errno; - else if (connect_stat.st_uid == uid) - return s; - else - sock_status = -1; - - CLOSE_SOCKET (s); - } + close (s); - if (sock_status < 0) - message (true, "%s: Invalid socket owner\n", progname); + if (sock_status == -1) + message (true, + "%s: Invalid permissions on parent directory of socket: %s\n", + progname, sockname); else if (sock_status == ENOENT) { if (tmpdir_used) @@ -1502,7 +1568,7 @@ set_local_socket (char const *server_name) } } else - message (true, "%s: can't stat %s: %s\n", + message (true, "%s: can't connect to %s: %s\n", progname, sockname, strerror (sock_status)); return INVALID_SOCKET; diff --git a/lib/file-has-acl.c b/lib/file-has-acl.c new file mode 100644 index 0000000000..c667ae9d24 --- /dev/null +++ b/lib/file-has-acl.c @@ -0,0 +1,510 @@ +/* Test whether a file has a nontrivial ACL. -*- coding: utf-8 -*- + + Copyright (C) 2002-2003, 2005-2020 Free Software Foundation, Inc. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + + Written by Paul Eggert, Andreas Grünbacher, and Bruno Haible. */ + +/* Without this pragma, gcc 4.7.0 20120126 may suggest that the + file_has_acl function might be candidate for attribute 'const' */ +#if (__GNUC__ == 4 && 6 <= __GNUC_MINOR__) || 4 < __GNUC__ +# pragma GCC diagnostic ignored "-Wsuggest-attribute=const" +#endif + +#include + +#include "acl.h" + +#include "acl-internal.h" + +#if GETXATTR_WITH_POSIX_ACLS +# include +# include +#endif + +/* Return 1 if NAME has a nontrivial access control list, + 0 if ACLs are not supported, or if NAME has no or only a base ACL, + and -1 (setting errno) on error. Note callers can determine + if ACLs are not supported as errno is set in that case also. + SB must be set to the stat buffer of NAME, + obtained through stat() or lstat(). */ + +int +file_has_acl (char const *name, struct stat const *sb) +{ +#if USE_ACL + if (! S_ISLNK (sb->st_mode)) + { + +# if GETXATTR_WITH_POSIX_ACLS + + ssize_t ret; + + 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)) + { + ret = getxattr (name, XATTR_NAME_POSIX_ACL_DEFAULT, NULL, 0); + if (ret < 0 && errno == ENODATA) + ret = 0; + else if (ret > 0) + return 1; + } + + if (ret < 0) + return - acl_errno_valid (errno); + return ret; + +# elif HAVE_ACL_GET_FILE + + /* POSIX 1003.1e (draft 17 -- abandoned) specific version. */ + /* Linux, FreeBSD, Mac OS X, IRIX, Tru64, Cygwin >= 2.5 */ + int ret; + + if (HAVE_ACL_EXTENDED_FILE) /* Linux */ + { + /* On Linux, acl_extended_file is an optimized function: It only + makes two calls to getxattr(), one for ACL_TYPE_ACCESS, one for + ACL_TYPE_DEFAULT. */ + ret = acl_extended_file (name); + } + else /* FreeBSD, Mac OS X, IRIX, Tru64, Cygwin >= 2.5 */ + { +# if HAVE_ACL_TYPE_EXTENDED /* Mac OS X */ + /* On Mac OS X, acl_get_file (name, ACL_TYPE_ACCESS) + and acl_get_file (name, ACL_TYPE_DEFAULT) + always return NULL / EINVAL. There is no point in making + these two useless calls. The real ACL is retrieved through + acl_get_file (name, ACL_TYPE_EXTENDED). */ + acl_t acl = acl_get_file (name, ACL_TYPE_EXTENDED); + if (acl) + { + ret = acl_extended_nontrivial (acl); + acl_free (acl); + } + else + ret = -1; +# else /* FreeBSD, IRIX, Tru64, Cygwin >= 2.5 */ + acl_t acl = acl_get_file (name, ACL_TYPE_ACCESS); + if (acl) + { + int saved_errno; + + ret = acl_access_nontrivial (acl); + saved_errno = errno; + acl_free (acl); + errno = saved_errno; +# if HAVE_ACL_FREE_TEXT /* Tru64 */ + /* On OSF/1, acl_get_file (name, ACL_TYPE_DEFAULT) always + returns NULL with errno not set. There is no point in + making this call. */ +# else /* FreeBSD, IRIX, Cygwin >= 2.5 */ + /* On Linux, FreeBSD, IRIX, acl_get_file (name, ACL_TYPE_ACCESS) + and acl_get_file (name, ACL_TYPE_DEFAULT) on a directory + either both succeed or both fail; it depends on the + file system. Therefore there is no point in making the second + call if the first one already failed. */ + if (ret == 0 && S_ISDIR (sb->st_mode)) + { + acl = acl_get_file (name, ACL_TYPE_DEFAULT); + if (acl) + { +# ifdef __CYGWIN__ /* Cygwin >= 2.5 */ + ret = acl_access_nontrivial (acl); + saved_errno = errno; + acl_free (acl); + errno = saved_errno; +# else + ret = (0 < acl_entries (acl)); + acl_free (acl); +# endif + } + else + ret = -1; + } +# endif + } + else + ret = -1; +# endif + } + if (ret < 0) + return - acl_errno_valid (errno); + return ret; + +# elif HAVE_FACL && defined GETACL /* Solaris, Cygwin < 2.5, not HP-UX */ + +# if defined ACL_NO_TRIVIAL + + /* Solaris 10 (newer version), which has additional API declared in + (acl_t) and implemented in libsec (acl_set, acl_trivial, + acl_fromtext, ...). */ + return acl_trivial (name); + +# else /* Solaris, Cygwin, general case */ + + /* 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. + In the newer Solaris 10 and Solaris 11 convention: + If there are more than 6 entries, there cannot be only the + ACE_OWNER, ACE_GROUP, ACE_EVERYONE entries, each once with + 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 + + return 0; +# endif + +# 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; + + if (stat (name, &statbuf) < 0) + return -1; + + return acl_nontrivial (count, entries); + } + } + } + +# 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 + +# elif HAVE_ACLX_GET && defined ACL_AIX_WIP /* AIX */ + + acl_type_t type; + char aclbuf[1024]; + void *acl = aclbuf; + size_t aclsize = sizeof (aclbuf); + mode_t mode; + + for (;;) + { + /* The docs say that type being 0 is equivalent to ACL_ANY, but it + is not true, in AIX 5.3. */ + type.u64 = ACL_ANY; + if (aclx_get (name, 0, &type, aclbuf, &aclsize, &mode) >= 0) + break; + if (errno == ENOSYS) + return 0; + if (errno != ENOSPC) + { + if (acl != aclbuf) + { + int saved_errno = errno; + free (acl); + errno = saved_errno; + } + return -1; + } + aclsize = 2 * aclsize; + if (acl != aclbuf) + free (acl); + acl = malloc (aclsize); + if (acl == NULL) + { + errno = ENOMEM; + return -1; + } + } + + if (type.u64 == ACL_AIXC) + { + int result = acl_nontrivial ((struct acl *) acl); + if (acl != aclbuf) + free (acl); + return result; + } + else if (type.u64 == ACL_NFS4) + { + int result = acl_nfs4_nontrivial ((nfs4_acl_int_t *) acl); + if (acl != aclbuf) + free (acl); + return result; + } + else + { + /* A newer type of ACL has been introduced in the system. + We should better support it. */ + if (acl != aclbuf) + free (acl); + errno = EINVAL; + return -1; + } + +# elif HAVE_STATACL /* older AIX */ + + union { struct acl a; char room[4096]; } u; + + if (statacl ((char *) name, STX_NORMAL, &u.a, sizeof (u)) < 0) + return -1; + + return acl_nontrivial (&u.a); + +# 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 + } +#endif + + return 0; +} diff --git a/lib/gnulib.mk.in b/lib/gnulib.mk.in index 07736f9b8b..0b9aaf6d9e 100644 --- a/lib/gnulib.mk.in +++ b/lib/gnulib.mk.in @@ -98,6 +98,7 @@ # fcntl \ # fcntl-h \ # fdopendir \ +# file-has-acl \ # filemode \ # filename \ # filevercmp \ @@ -1788,6 +1789,16 @@ EXTRA_libgnu_a_SOURCES += fdopendir.c endif ## end gnulib module fdopendir +## begin gnulib module file-has-acl +ifeq (,$(OMIT_GNULIB_MODULE_file-has-acl)) + +libgnu_a_SOURCES += file-has-acl.c + +EXTRA_DIST += acl-internal.h + +endif +## end gnulib module file-has-acl + ## begin gnulib module filemode ifeq (,$(OMIT_GNULIB_MODULE_filemode)) diff --git a/m4/gnulib-comp.m4 b/m4/gnulib-comp.m4 index cd6f7b4bbd..05e7faa993 100644 --- a/m4/gnulib-comp.m4 +++ b/m4/gnulib-comp.m4 @@ -89,6 +89,7 @@ AC_DEFUN # Code from module fcntl: # Code from module fcntl-h: # Code from module fdopendir: + # Code from module file-has-acl: # Code from module filemode: # Code from module filename: # Code from module filevercmp: @@ -287,6 +288,7 @@ AC_DEFUN fi gl_DIRENT_MODULE_INDICATOR([fdopendir]) gl_MODULE_INDICATOR([fdopendir]) + gl_FILE_HAS_ACL gl_FILEMODE AC_C_FLEXIBLE_ARRAY_MEMBER gl_FUNC_FPENDING @@ -1045,6 +1047,7 @@ AC_DEFUN lib/fcntl.c lib/fcntl.in.h lib/fdopendir.c + lib/file-has-acl.c lib/filemode.c lib/filemode.h lib/filename.h