+/**
+ * @brief Copy ACLs between file descriptors as xattrs, verbatim.
+ *
+ * There is a small deficiency in libacl, namely that there is no way
+ * to get or set default ACLs through file descriptors. The @c
+ * acl_get_file and @c acl_set_file functions can do it, but they use
+ * paths, and are vulnerable to symlink attacks.
+ *
+ * Fortunately, when inheriting an ACL, we don't really need to look
+ * at what it contains. That means that we can copy the on-disk xattrs
+ * from the source directory to the destination file/directory without
+ * passing through libacl, and this can be done with file descriptors
+ * through @c fgetxattr and @c fsetxattr. That's what this function
+ * does.
+ *
+ * @param src_fd
+ * The file descriptor from which the ACL will be copied.
+ *
+ * @param src_type
+ * The type of ACL (either @c ACL_TYPE_ACCESS or @c ACL_TYPE_DEFAULT)
+ * to copy from @c src_fd.
+ *
+ * @param dst_fd
+ * The file descriptor whose ACL will be overwritten with the one
+ * from @c src_fd.
+ *
+ * @param dst_type
+ * The type of ACL (either @c ACL_TYPE_ACCESS or @c ACL_TYPE_DEFAULT)
+ * to replace on @c dst_fd.
+ *
+ * @return
+ * - @c ACL_SUCCESS - The ACL was copied successfully.
+ * - @c ACL_FAILURE - There was no ACL on @c src_fd.
+ * - @c ACL_ERROR - Unexpected library error.
+ */
+int acl_copy_xattr(int src_fd,
+ acl_type_t src_type,
+ int dst_fd,
+ acl_type_t dst_type) {
+
+ const char* src_name;
+ if (src_type == ACL_TYPE_ACCESS) {
+ src_name = XATTR_NAME_POSIX_ACL_ACCESS;
+ }
+ else if (src_type == ACL_TYPE_DEFAULT) {
+ src_name = XATTR_NAME_POSIX_ACL_DEFAULT;
+ }
+ else {
+ errno = EINVAL;
+ perror("acl_copy_xattr (src type)");
+ return ACL_ERROR;
+ }
+
+ const char* dst_name;
+ if (dst_type == ACL_TYPE_ACCESS) {
+ dst_name = XATTR_NAME_POSIX_ACL_ACCESS;
+ }
+ else if (dst_type == ACL_TYPE_DEFAULT) {
+ dst_name = XATTR_NAME_POSIX_ACL_DEFAULT;
+ }
+ else {
+ errno = EINVAL;
+ perror("acl_copy_xattr (dst type)");
+ return ACL_ERROR;
+ }
+
+ size_t src_size_guess = fgetxattr(src_fd, src_name, NULL, 0);
+ if (src_size_guess == XATTR_ERROR) {
+ if (errno == ENODATA) {
+ /* A missing ACL isn't really an error. ENOATTR and ENODATA are
+ synonyms, but using ENODATA here lets us avoid another
+ "include" directive. */
+ return ACL_FAILURE;
+ }
+ perror("acl_copy_xattr (fgetxattr size guess)");
+ return ACL_ERROR;
+ }
+ char* src_acl_p = alloca(src_size_guess);
+ /* The actual size may be smaller than our guess? I don't know. */
+ size_t src_size = fgetxattr(src_fd, src_name, src_acl_p, (int)src_size_guess);
+ if (src_size == XATTR_ERROR) {
+ if (errno == ENODATA) {
+ /* A missing ACL isn't an error. */
+ return ACL_FAILURE;
+ }
+ perror("acl_copy_xattr (fgetxattr)");
+ return ACL_ERROR;
+ }
+
+ if (fsetxattr(dst_fd, dst_name, src_acl_p, src_size, 0) == XATTR_ERROR) {
+ perror("acl_copy_xattr (fsetxattr)");
+ return ACL_ERROR;
+ }
+
+ return ACL_SUCCESS;
+}
+