[BACK]Return to nfs4acl.c CVS log [TXT][DIR] Up to [Development] / xfs-cmds / nfs4acl / libnfs4acl

File: [Development] / xfs-cmds / nfs4acl / libnfs4acl / nfs4acl.c (download)

Revision 1.1, Mon Jul 28 06:10:38 2008 UTC (9 years, 3 months ago) by tes.longdrop.melbourne.sgi.com
Branch: MAIN
CVS Tags: HEAD

Bring in xfs-cmds/acl type infrastructure
Merge of master-melb:xfs-cmds:31778a by kenmcd.

#define _GNU_SOURCE
#include <sys/types.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <ctype.h>
#include <alloca.h>
#include <errno.h>
#include <pwd.h>
#include <grp.h>
#include <attr/xattr.h>
#include "string_buffer.h"
#include "nfs4acl.h"
#include "nfs4acl_xattr.h"
#include "nfs4acl-internal.h"

#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
#define ALIGN(x,a) (((x)+(a)-1)&~((a)-1))

const char *nfs4ace_owner_who	 = "OWNER@";
const char *nfs4ace_group_who	 = "GROUP@";
const char *nfs4ace_everyone_who = "EVERYONE@";

/*
 * The POSIX permissions are supersets of the following mask flags.
 */
#define ACE4_POSIX_MODE_READ ( \
	ACE4_READ_DATA | ACE4_LIST_DIRECTORY )
#define ACE4_POSIX_MODE_WRITE ( \
	ACE4_WRITE_DATA | ACE4_ADD_FILE | \
	ACE4_APPEND_DATA | ACE4_ADD_SUBDIRECTORY | \
	ACE4_DELETE_CHILD )
#define ACE4_POSIX_MODE_EXEC ( \
	ACE4_EXECUTE)

static struct {
	char		a_char;
	unsigned char	a_flag;
	const char	*a_name;
} acl_flag_bits[] = {
	{ 'a', ACL4_AUTO_INHERIT, "auto_inherit" },
	{ 'p', ACL4_PROTECTED, "protected" },
	{ 'd', ACL4_DEFAULTED, "defaulted" },
	{ 'w', ACL4_WRITE_THROUGH, "write_through" },
};

static struct {
	uint16_t	e_type;
	const char	*e_name;
} type_values[] = {
	{ ACE4_ACCESS_ALLOWED_ACE_TYPE, "allow" },
	{ ACE4_ACCESS_DENIED_ACE_TYPE,  "deny" },
};

#define FLAGS_BIT(c, name, str) \
	{ ACE4_ ## name, c, #name }

static struct {
	uint16_t	e_flag;
	char		e_char;
	const char	*e_name;
} ace_flag_bits[] = {
	FLAGS_BIT('f', FILE_INHERIT_ACE, "file_inherit_ace"),
	FLAGS_BIT('d', DIRECTORY_INHERIT_ACE, "directory_inherit_ace"),
	FLAGS_BIT('n', NO_PROPAGATE_INHERIT_ACE, "no_propagate_inherit_ace"),
	FLAGS_BIT('i', INHERIT_ONLY_ACE, "inherit_only_ace"),
	FLAGS_BIT('g', IDENTIFIER_GROUP, "identifier_group"),
	FLAGS_BIT('a', INHERITED_ACE, "inherited_ace"),
};

#undef FLAGS_BIT

#define MASK_BIT(c, name, str) \
	{ ACE4_ ## name, c, str, NFS4ACL_TEXT_FILE_CONTEXT | \
				 NFS4ACL_TEXT_DIRECTORY_CONTEXT }
#define FILE_MASK_BIT(c, name, str) \
	{ ACE4_ ## name, c, str, NFS4ACL_TEXT_FILE_CONTEXT }
#define DIRECTORY_MASK_BIT(c, name, str) \
	{ ACE4_ ## name, c, str, NFS4ACL_TEXT_DIRECTORY_CONTEXT }

struct {
	uint32_t	e_mask;
	char		e_char;
	const char	*e_name;
	int		e_context;
} mask_bits[] = {
	MASK_BIT('*', VALID_MASK, "*"),
	FILE_MASK_BIT('r', READ_DATA, "read_data"),
	DIRECTORY_MASK_BIT('r', LIST_DIRECTORY, "list_directory"),
	FILE_MASK_BIT('w', WRITE_DATA, "write_data"),
	DIRECTORY_MASK_BIT('w', ADD_FILE, "add_file"),
	FILE_MASK_BIT('a', APPEND_DATA, "append_data"),
	DIRECTORY_MASK_BIT('a', ADD_SUBDIRECTORY, "add_subdirectory"),
	MASK_BIT('N', READ_NAMED_ATTRS, "read_named_attrs"),
	MASK_BIT('n', WRITE_NAMED_ATTRS, "write_named_attrs"),
	MASK_BIT('x', EXECUTE, "execute"),
	MASK_BIT('d', DELETE_CHILD, "delete_child"),
	MASK_BIT('T', READ_ATTRIBUTES, "read_attributes"),
	MASK_BIT('t', WRITE_ATTRIBUTES, "write_attributes"),
	MASK_BIT('D', DELETE, "delete"),
	MASK_BIT('M', READ_ACL, "read_acl"),
	MASK_BIT('m', WRITE_ACL, "write_acl"),
	MASK_BIT('o', WRITE_OWNER, "take_ownership"),
	MASK_BIT('s', SYNCHRONIZE, "synchronize"),
};

#undef MASK_BIT
#undef FILE_MASK_BIT
#undef DIRECTORY_MASK_BIT

const char *nfs4ace_get_who(const struct nfs4ace *ace)
{
	if (!(ace->e_flags & ACE4_SPECIAL_WHO))
		return NULL;
	return ace->u.e_who;
}

int nfs4ace_is_same_identifier(const struct nfs4ace *a, const struct nfs4ace *b)
{
#define WHO_FLAGS (ACE4_SPECIAL_WHO | ACE4_IDENTIFIER_GROUP)
	if ((a->e_flags & WHO_FLAGS) != (b->e_flags & WHO_FLAGS))
		return 0;
	if (a->e_flags & ACE4_SPECIAL_WHO)
		return a->u.e_who == b->u.e_who;
	else
		return a->u.e_id == b->u.e_id;
#undef WHO_FLAGS
}

int nfs4ace_is_owner(const struct nfs4ace *ace)
{
	return (ace->e_flags & ACE4_SPECIAL_WHO) &&
		ace->u.e_who == nfs4ace_owner_who;
}

int nfs4ace_is_group(const struct nfs4ace *ace)
{
	return (ace->e_flags & ACE4_SPECIAL_WHO) &&
		ace->u.e_who == nfs4ace_group_who;
}

int nfs4ace_is_everyone(const struct nfs4ace *ace)
{
	return (ace->e_flags & ACE4_SPECIAL_WHO) &&
		ace->u.e_who == nfs4ace_everyone_who;
}

struct nfs4acl *nfs4acl_alloc(size_t count)
{
	size_t size = sizeof(struct nfs4acl) + count * sizeof(struct nfs4ace);
	struct nfs4acl *acl = malloc(size);

	if (acl) {
		memset(acl, 0, size);
		acl->a_count = count;
	}
	return acl;
}

struct nfs4acl *nfs4acl_clone(struct nfs4acl *acl)
{
	size_t size;
	struct nfs4acl *acl2;

	if (!acl)
		return NULL;
	size = sizeof(struct nfs4acl) + acl->a_count * sizeof(struct nfs4ace);
	acl2 = malloc(size);
	if (acl2)
		memcpy(acl2, acl, size);
	return acl2;
}

void nfs4acl_free(struct nfs4acl *acl)
{
	free(acl);
}

/**
 * nfs4acl_allowed_to_who  -  mask flags allowed to a specific who value
 *
 * Computes the mask values allowed to a specific who value, taking
 * EVERYONE@ entries into account.
 */
static unsigned int nfs4acl_allowed_to_who(struct nfs4acl *acl,
					   struct nfs4ace *who)
{
	struct nfs4ace *ace;
	unsigned int allowed = 0;

	nfs4acl_for_each_entry_reverse(ace, acl) {
		if (nfs4ace_is_inherit_only(ace))
			continue;
		if (nfs4ace_is_same_identifier(ace, who) ||
		    nfs4ace_is_everyone(ace)) {
			if (nfs4ace_is_allow(ace))
				allowed |= ace->e_mask;
			else if (nfs4ace_is_deny(ace))
				allowed &= ~ace->e_mask;
		}
	}
	return allowed;
}

/**
 * nfs4acl_compute_max_masks  -  compute upper bound masks
 *
 * Computes upper bound owner, group, and other masks so that none of
 * the mask flags allowed by the acl are disabled (for any choice of the
 * file owner or group membership).
 */
void nfs4acl_compute_max_masks(struct nfs4acl *acl)
{
	struct nfs4ace *ace;

	acl->a_owner_mask = 0;
	acl->a_group_mask = 0;
	acl->a_other_mask = 0;

	nfs4acl_for_each_entry_reverse(ace, acl) {
		if (nfs4ace_is_inherit_only(ace))
			continue;

		if (nfs4ace_is_owner(ace)) {
			if (nfs4ace_is_allow(ace))
				acl->a_owner_mask |= ace->e_mask;
			else if (nfs4ace_is_deny(ace))
				acl->a_owner_mask &= ~ace->e_mask;
		} else if (nfs4ace_is_everyone(ace)) {
			if (nfs4ace_is_allow(ace)) {
				struct nfs4ace who = {
					.e_flags = ACE4_SPECIAL_WHO,
					.u.e_who = nfs4ace_group_who,
				};

				acl->a_other_mask |= ace->e_mask;
				acl->a_group_mask |=
					nfs4acl_allowed_to_who(acl, &who);
				acl->a_owner_mask |= ace->e_mask;
			} else if (nfs4ace_is_deny(ace)) {
				acl->a_other_mask &= ~ace->e_mask;
				acl->a_group_mask &= ~ace->e_mask;
				acl->a_owner_mask &= ~ace->e_mask;
			}
		} else {
			if (nfs4ace_is_allow(ace)) {
				unsigned int mask =
					nfs4acl_allowed_to_who(acl, ace);

				acl->a_group_mask |= mask;
				acl->a_owner_mask |= mask;
			}
		}
	}
}

static struct nfs4acl *nfs4acl_from_xattr(const void *value, size_t size)
{
	const struct nfs4acl_xattr *xattr_acl = value;
	const struct nfs4ace_xattr *xattr_ace = (void *)(xattr_acl + 1);
	struct nfs4acl *acl = NULL;
	struct nfs4ace *ace;
	int count;

	if (size < sizeof(struct nfs4acl_xattr) ||
	    xattr_acl->a_version != ACL4_XATTR_VERSION)
		goto fail_einval;

	count = ntohs(xattr_acl->a_count);
	if (count > ACL4_XATTR_MAX_COUNT)
		goto fail_einval;

	acl = nfs4acl_alloc(count);
	if (!acl)
		return NULL;

	acl->a_flags = xattr_acl->a_flags;
	acl->a_owner_mask = ntohl(xattr_acl->a_owner_mask);
	acl->a_group_mask = ntohl(xattr_acl->a_group_mask);
	acl->a_other_mask = ntohl(xattr_acl->a_other_mask);

	nfs4acl_for_each_entry(ace, acl) {
		const char *who = (void *)(xattr_ace + 1), *end;
		ssize_t used = (void *)who - value;

		if (used > size)
			goto fail_einval;
		end = memchr(who, 0, size - used);
		if (!end)
			goto fail_einval;

		ace->e_type = ntohs(xattr_ace->e_type);
		ace->e_flags = ntohs(xattr_ace->e_flags);
		ace->e_mask = ntohl(xattr_ace->e_mask);
		ace->u.e_id = ntohl(xattr_ace->e_id);

		if (who == end) {
			if (ace->u.e_id == -1)
				goto fail_einval;  /* uid/gid needed */
		} else if (nfs4ace_set_who(ace, who))
			goto fail_einval;

		xattr_ace = (void *)who + ALIGN(end - who + 1, 4);
	}

	return acl;

fail_einval:
	nfs4acl_free(acl);
	errno = EINVAL;
	return NULL;
}

static size_t nfs4acl_xattr_size(const struct nfs4acl *acl)
{
	size_t size = sizeof(struct nfs4acl_xattr);
	const struct nfs4ace *ace;

	nfs4acl_for_each_entry(ace, acl) {
		size += sizeof(struct nfs4ace_xattr) +
			(nfs4ace_get_who(ace) ?
			 ALIGN(strlen(ace->u.e_who) + 1, 4) : 4);
	}
	return size;
}

static void nfs4acl_to_xattr(const struct nfs4acl *acl, void *buffer)
{
	struct nfs4acl_xattr *xattr_acl = buffer;
	struct nfs4ace_xattr *xattr_ace;
	const struct nfs4ace *ace;

	xattr_acl->a_version = ACL4_XATTR_VERSION;
	xattr_acl->a_flags = acl->a_flags;
	xattr_acl->a_count = htons(acl->a_count);

	xattr_acl->a_owner_mask = htonl(acl->a_owner_mask);
	xattr_acl->a_group_mask = htonl(acl->a_group_mask);
	xattr_acl->a_other_mask = htonl(acl->a_other_mask);

	xattr_ace = (void *)(xattr_acl + 1);
	nfs4acl_for_each_entry(ace, acl) {
		xattr_ace->e_type = htons(ace->e_type);
		xattr_ace->e_flags = htons(ace->e_flags & ACE4_VALID_FLAGS);
		xattr_ace->e_mask = htonl(ace->e_mask);
		if (nfs4ace_get_who(ace)) {
			int sz = ALIGN(strlen(ace->u.e_who) + 1, 4);

			xattr_ace->e_id = htonl(-1);
			memset(xattr_ace->e_who + sz - 4, 0, 4);
			strcpy(xattr_ace->e_who, ace->u.e_who);
			xattr_ace = (void *)xattr_ace->e_who + sz;
		} else {
			xattr_ace->e_id = htonl(ace->u.e_id);
			memset(xattr_ace->e_who, 0, 4);
			xattr_ace = (void *)xattr_ace->e_who + 4;
		}
	}
}

struct nfs4acl *nfs4acl_get_file(const char *path)
{
	void *value;
	ssize_t retval;
	struct nfs4acl *acl;

	retval = getxattr(path, SYSTEM_NFS4ACL, NULL, 0);
	if (retval <= 0)
		return NULL;

	value = alloca(retval);
	if (!value)
		return NULL;
	retval = getxattr(path, SYSTEM_NFS4ACL, value, retval);
	acl = nfs4acl_from_xattr(value, retval);

	return acl;
}

struct nfs4acl *nfs4acl_get_fd(int fd)
{
	void *value;
	ssize_t retval;
	struct nfs4acl *acl;

	retval = fgetxattr(fd, SYSTEM_NFS4ACL, NULL, 0);
	if (retval <= 0)
		return NULL;

	value = alloca(retval);
	if (!value)
		return NULL;
	retval = fgetxattr(fd, SYSTEM_NFS4ACL, value, retval);
	acl = nfs4acl_from_xattr(value, retval);

	return acl;
}

int nfs4acl_set_file(const char *path, const struct nfs4acl *acl)
{
	size_t size = nfs4acl_xattr_size(acl);
	void *value = alloca(size);

	nfs4acl_to_xattr(acl, value);
	return setxattr(path, SYSTEM_NFS4ACL, value, size, 0);
}

int nfs4acl_set_fd(int fd, const struct nfs4acl *acl)
{
	size_t size = nfs4acl_xattr_size(acl);
	void *value = alloca(size);

	nfs4acl_to_xattr(acl, value);
	return fsetxattr(fd, SYSTEM_NFS4ACL, value, size, 0);
}

static void write_acl_flags(struct string_buffer *buffer, unsigned char flags, int fmt)
{
	int cont = 0, i;

	if (!flags)
		return;
	buffer_sprintf(buffer, "flags:");
	for (i = 0; i < ARRAY_SIZE(acl_flag_bits); i++) {
		if (!(flags & acl_flag_bits[i].a_flag))
			continue;

		flags &= ~acl_flag_bits[i].a_flag;
		if (fmt & NFS4ACL_TEXT_LONG) {
			if (cont)
				buffer_sprintf(buffer, "/");
			buffer_sprintf(buffer, "%s", acl_flag_bits[i].a_name);
		} else
			buffer_sprintf(buffer, "%c", acl_flag_bits[i].a_char);
		cont = 1;
	}
	if (flags) {
		if (cont)
			buffer_sprintf(buffer, "/");
		buffer_sprintf(buffer, "0x%x", flags);
	}
	buffer_sprintf(buffer, "\n");
}

static void write_type(struct string_buffer *buffer, uint16_t type)
{
	int i;

	for (i = 0; i < ARRAY_SIZE(type_values); i++) {
		if (type == type_values[i].e_type) {
			buffer_sprintf(buffer, "%s", type_values[i].e_name);
			break;
		}
	}
	if (i == ARRAY_SIZE(type_values))
		buffer_sprintf(buffer, "%u", type);
}

static void write_ace_flags(struct string_buffer *buffer, uint16_t flags, int fmt)
{
	int cont = 0, i;

	flags &= ~ACE4_SPECIAL_WHO;

	for (i = 0; i < ARRAY_SIZE(ace_flag_bits); i++) {
		if (!(flags & ace_flag_bits[i].e_flag))
			continue;

		flags &= ~ace_flag_bits[i].e_flag;
		if (fmt & NFS4ACL_TEXT_LONG) {
			if (cont)
				buffer_sprintf(buffer, "/");
			buffer_sprintf(buffer, "%s", ace_flag_bits[i].e_name);
		} else
			buffer_sprintf(buffer, "%c", ace_flag_bits[i].e_char);
		cont = 1;
	}
	if (flags) {
		if (cont)
			buffer_sprintf(buffer, "/");
		buffer_sprintf(buffer, "0x%x", flags);
	}
}

static void write_mask(struct string_buffer *buffer, uint32_t mask, int fmt)
{
	int stuff_written = 0, i;
	unsigned int nondir_mask, dir_mask;

	/*
	 * In long format, we write the non-directory and/or directory mask
	 * name depending on the context which applies. The short format
	 * does not distinguish between the two, so make sure that we won't
	 * repeat the same mask letters.
	 */
	if (!(fmt & (NFS4ACL_TEXT_FILE_CONTEXT |
		     NFS4ACL_TEXT_DIRECTORY_CONTEXT)))
		fmt |= NFS4ACL_TEXT_FILE_CONTEXT |
		       NFS4ACL_TEXT_DIRECTORY_CONTEXT;
	if (!(fmt & NFS4ACL_TEXT_LONG) &&
	    (fmt & NFS4ACL_TEXT_FILE_CONTEXT))
		fmt &= ~NFS4ACL_TEXT_DIRECTORY_CONTEXT;

	nondir_mask = (fmt & NFS4ACL_TEXT_FILE_CONTEXT) ? mask : 0;
	dir_mask = (fmt & NFS4ACL_TEXT_DIRECTORY_CONTEXT) ? mask : 0;

	for (i = 0; i < ARRAY_SIZE(mask_bits); i++) {
		int found = 0;

		if ((nondir_mask & mask_bits[i].e_mask) ==
		    mask_bits[i].e_mask &&
		    (mask_bits[i].e_context & NFS4ACL_TEXT_FILE_CONTEXT)) {
			nondir_mask &= ~mask_bits[i].e_mask;
			found = 1;
		}
		if ((dir_mask & mask_bits[i].e_mask) == mask_bits[i].e_mask &&
		    (mask_bits[i].e_context & NFS4ACL_TEXT_DIRECTORY_CONTEXT)) {
			dir_mask &= ~mask_bits[i].e_mask;
			found = 1;
		}
		if (found) {
			if (fmt & NFS4ACL_TEXT_SIMPLIFY) {
				/* Hide permissions that are always allowed. */
				if (mask_bits[i].e_mask ==
				    (mask_bits[i].e_mask &
				     ACE4_POSIX_ALWAYS_ALLOWED))
					continue;
			}
			if (fmt & NFS4ACL_TEXT_LONG) {
				if (stuff_written)
					buffer_sprintf(buffer, "/");
				buffer_sprintf(buffer, "%s",
					       mask_bits[i].e_name);
			} else
				buffer_sprintf(buffer, "%c",
					       mask_bits[i].e_char);
			stuff_written = 1;
		}
	}
	if (nondir_mask | dir_mask) {
		if (stuff_written)
			buffer_sprintf(buffer, "/");
		buffer_sprintf(buffer, "0x%x", nondir_mask | dir_mask);
	}
}

static void write_identifier(struct string_buffer *buffer,
			     const struct nfs4ace *ace)
{
	/* FIXME: switch to getpwuid_r() and getgrgid_r() here. */

	if (ace->e_flags & ACE4_SPECIAL_WHO) {
		char *dup, *c;

		dup = alloca(strlen(ace->u.e_who) + 1);
		strcpy(dup, ace->u.e_who);
		for (c = dup; *c; c++)
			*c = tolower(*c);

		buffer_sprintf(buffer, "%s", dup);
	} else if (ace->e_flags & ACE4_IDENTIFIER_GROUP) {
		struct group *group = getgrgid(ace->u.e_id);

		if (group)
			buffer_sprintf(buffer, "%s", group->gr_name);
		else
			buffer_sprintf(buffer, "%d", ace->u.e_id);
	} else {
		struct passwd *passwd = getpwuid(ace->u.e_id);

		if (passwd)
			buffer_sprintf(buffer, "%s", passwd->pw_name);
		else
			buffer_sprintf(buffer, "%d", ace->u.e_id);
	}
}

char *nfs4acl_to_text(const struct nfs4acl *acl, int fmt)
{
	struct string_buffer *buffer;
	const struct nfs4ace *ace;
	int fmt2;

	buffer = alloc_string_buffer(128);
	if (!buffer)
		return NULL;

	write_acl_flags(buffer, acl->a_flags, fmt);
	if (fmt & NFS4ACL_TEXT_SHOW_MASKS) {
		unsigned int allowed = 0;
		
		fmt2 = fmt;
		nfs4acl_for_each_entry(ace, acl) {
			if (nfs4ace_is_inherit_only(ace))
				continue;

			if (nfs4ace_is_allow(ace))
				allowed |= ace->e_mask;

			if (ace->e_flags & ACE4_FILE_INHERIT_ACE)
				fmt2 |= NFS4ACL_TEXT_FILE_CONTEXT;
			if (ace->e_flags & ACE4_DIRECTORY_INHERIT_ACE)
				fmt2 |= NFS4ACL_TEXT_DIRECTORY_CONTEXT;
		}

		if (!(fmt & NFS4ACL_TEXT_SIMPLIFY))
			allowed = ~0;

		buffer_sprintf(buffer, "owner:");
		write_mask(buffer, acl->a_owner_mask & allowed, fmt2);
		buffer_sprintf(buffer, "::mask\n");
		buffer_sprintf(buffer, "group:");
		write_mask(buffer, acl->a_group_mask & allowed, fmt2);
		buffer_sprintf(buffer, "::mask\n");
		buffer_sprintf(buffer, "other:");
		write_mask(buffer, acl->a_other_mask & allowed, fmt2);
		buffer_sprintf(buffer, "::mask\n");
	}

	nfs4acl_for_each_entry(ace, acl) {
		write_identifier(buffer, ace);
		buffer_sprintf(buffer, ":");

		fmt2 = fmt;
		if (ace->e_flags & ACE4_INHERIT_ONLY_ACE)
			fmt2 &= ~(NFS4ACL_TEXT_FILE_CONTEXT |
				  NFS4ACL_TEXT_DIRECTORY_CONTEXT);
		if (ace->e_flags & ACE4_FILE_INHERIT_ACE)
			fmt2 |= NFS4ACL_TEXT_FILE_CONTEXT;
		if (ace->e_flags & ACE4_DIRECTORY_INHERIT_ACE)
			fmt2 |= NFS4ACL_TEXT_DIRECTORY_CONTEXT;

		write_mask(buffer, ace->e_mask, fmt2);
		buffer_sprintf(buffer, ":");
		write_ace_flags(buffer, ace->e_flags, fmt2);
		buffer_sprintf(buffer, ":");
		write_type(buffer, ace->e_type);
		buffer_sprintf(buffer, "\n");
	}

	if (string_buffer_okay(buffer)) {
		char *str = realloc(buffer->buffer, buffer->offset + 1);
		if (str)
			return str;
	}

	free_string_buffer(buffer);
	errno = ENOMEM;
	return NULL;
}

static int acl_flags_from_text(const char *str, struct nfs4acl *acl,
			       void (*error)(const char *, ...))
{
	char *dup, *end;

	end = alloca(strlen(str) + 1);
	strcpy(end, str);

	acl->a_flags = 0;
	while ((dup = end)) {
		char *c;
		unsigned long l;
		int i;

		while (*dup == '/')
			dup++;
		end = strchr(dup, '/');
		if (end)
			*end++ = 0;
		if (!*dup)
			break;

		l = strtoul(str, &c, 0);
		if (*c == 0) {
			acl->a_flags |= l;
			continue;
		}

		/* Recognize flag mnemonics */
		for (i = 0; i < ARRAY_SIZE(acl_flag_bits); i++) {
			if (!strcasecmp(dup, acl_flag_bits[i].a_name)) {
				acl->a_flags |= acl_flag_bits[i].a_flag;
				break;
			}
		}
		if (i != ARRAY_SIZE(acl_flag_bits))
			continue;

		/* Recognize single-character flags */
		for (c = dup; *c; c++) {
			for (i = 0; i < ARRAY_SIZE(acl_flag_bits); i++) {
				if (*c == acl_flag_bits[i].a_char) {
					acl->a_flags |= acl_flag_bits[i].a_flag;
					break;
				}
			}
			if (i != ARRAY_SIZE(acl_flag_bits))
				continue;

			error("Invalid acl flag `%s'\n", c);
			return -1;
		}
	}

	return 0;
}

static int identifier_from_text(const char *str, struct nfs4ace *ace,
				void (*error)(const char *, ...))
{
	char *c;
	unsigned long l;

	c = strchr(str, '@');
	if (c) {
		char *dup;

		if (c[1]) {
			error("Domain name not supported in `%s'\n", str);
			goto fail;
		}

		/* Ignore case in special identifiers. */
		dup = alloca(strlen(str) + 1);
		strcpy(dup, str);
		for (c = dup; *c; c++)
			*c = toupper(*c);

		if (nfs4ace_set_who(ace, dup)) {
			error("Special user `%s' not supported\n", str);
			goto fail;
		}
		return 0;
	}
	l = strtoul(str, &c, 0);
	if (*c == 0) {
		ace->u.e_id = l;
		return 0;
	}
	if (ace->e_flags & ACE4_IDENTIFIER_GROUP) {
		struct group *group = getgrnam(str);

		if (!group) {
			error("Group `%s' does not exist\n", str);
			goto fail;
		}
		ace->u.e_id = group->gr_gid;
		return 0;
	} else {
		struct passwd *passwd = getpwnam(str);

		if (!passwd) {
			error("User `%s' does not exist\n", str);
			goto fail;
		}
		ace->u.e_id = passwd->pw_uid;
		return 0;
	}
fail:
	return -1;
}

static int type_from_text(const char *str, struct nfs4ace *ace,
			  void (*error)(const char *, ...))
{
	char *c;
	int i;
	unsigned long l;

	l = strtoul(str, &c, 0);
	if (*c == 0) {
		ace->e_type = l;
		return 0;
	}

	/* Recognize type mnemonic */
	for (i = 0; i < ARRAY_SIZE(type_values); i++) {
		if (!strcasecmp(str, type_values[i].e_name)) {
			ace->e_type = type_values[i].e_type;
			return 0;
		}
	}
	error("Invalid entry type `%s'\n", str);
	return -1;
}

static int ace_flags_from_text(const char *str, struct nfs4ace *ace,
			       void (*error)(const char *, ...))
{
	char *dup, *end;

	end = alloca(strlen(str) + 1);
	strcpy(end, str);

	ace->e_flags = 0;
	while ((dup = end)) {
		char *c;
		unsigned long l;
		int i;

		while (*dup == '/')
			dup++;
		end = strchr(dup, '/');
		if (end)
			*end++ = 0;
		if (!*dup)
			break;

		l = strtoul(str, &c, 0);
		if (*c == 0) {
			ace->e_flags |= l;
			continue;
		}

		/* Recognize flag mnemonics */
		for (i = 0; i < ARRAY_SIZE(ace_flag_bits); i++) {
			if (!strcasecmp(dup, ace_flag_bits[i].e_name)) {
				ace->e_flags |= ace_flag_bits[i].e_flag;
				break;
			}
		}
		if (i != ARRAY_SIZE(ace_flag_bits))
			continue;

		/* Recognize single-character flags */
		for (c = dup; *c; c++) {
			for (i = 0; i < ARRAY_SIZE(ace_flag_bits); i++) {
				if (*c == ace_flag_bits[i].e_char) {
					ace->e_flags |= ace_flag_bits[i].e_flag;
					break;
				}
			}
			if (i != ARRAY_SIZE(ace_flag_bits))
				continue;

			error("Invalid entry flag `%s'\n", c);
			return -1;
		}
	}

	return 0;
}

static int mask_from_text(const char *str, unsigned int *mask,
			  void (*error)(const char *, ...))
{
	char *dup, *end;

	end = alloca(strlen(str) + 1);
	strcpy(end, str);

	*mask = 0;
	while ((dup = end)) {
		char *c;
		unsigned long l;
		int i;

		while (*dup == '/')
			dup++;
		end = strchr(dup, '/');
		if (end)
			*end++ = 0;
		if (!*dup)
			break;

		l = strtoul(dup, &c, 0);
		if (*c == 0) {
			*mask |= l;
			continue;
		}

		/* Recognize mask mnemonics */
		for (i = 0; i < ARRAY_SIZE(mask_bits); i++) {
			if (!strcasecmp(dup, mask_bits[i].e_name)) {
				*mask |= mask_bits[i].e_mask;
				break;
			}
		}
		if (i != ARRAY_SIZE(mask_bits))
			continue;

		/* Recognize single-character masks */
		for (c = dup; *c; c++) {
			for (i = 0; i < ARRAY_SIZE(mask_bits); i++) {
				if (*c == mask_bits[i].e_char) {
					*mask |= mask_bits[i].e_mask;
					break;
				}
			}
			if (i != ARRAY_SIZE(mask_bits))
				continue;

			error("Invalid access mask `%s'\n", dup);
			return -1;
		}
	}

	return 0;
}

struct nfs4acl *nfs4acl_from_text(const char *str, int *pflags,
				  void (*error)(const char *, ...))
{
	char *who_str = NULL, *mask_str = NULL, *flags_str = NULL,
	     *type_str = NULL;
	struct nfs4acl *acl;
	int flags = 0;

	acl = nfs4acl_alloc(0);
	if (!acl)
		return NULL;

	while (*str) {
		struct nfs4acl *acl2;
		struct nfs4ace *ace;
		unsigned int mask;
		const char *entry, *c;

		while (isspace(*str) || *str == ',')
			str++;
		if (!*str)
			break;

		entry = str;
		c = strchr(str, ':');
		if (!c)
			goto fail_syntax;
		who_str = strndup(str, c - str);
		if (!who_str)
			goto fail;
		str = c + 1;

		if (!strcasecmp(who_str, "FLAGS")) {
			for (c = str; *c; c++) {
				if (*c == ':' || *c == ',' || isspace(*c))
					break;
			}
			mask_str = strndup(str, c - str);
			if (!mask_str)
				goto fail;
			if (*c != ':') {
				if (acl_flags_from_text(mask_str, acl, error))
					goto fail_einval;
				flags |= NFS4ACL_TEXT_FLAGS;
				str = c;
				goto free_mask_str;
			} else {
				free(mask_str);
				mask_str = NULL;
			}
		}

		c = strchr(str, ':');
		if (!c)
			goto fail_syntax;
		mask_str = strndup(str, c - str);
		if (!mask_str)
			goto fail;
		str = c + 1;

		c = strchr(str, ':');
		if (!c)
			goto fail_syntax;
		flags_str = strndup(str, c - str);
		if (!flags_str)
			goto fail;
		str = c + 1;

		for (c = str; *c; c++) {
			if (*c == ',' || isspace(*c))
				break;
		}
		type_str = strndup(str, c - str);
		if (!type_str)
			goto fail;
		str = c;

		if (mask_from_text(mask_str, &mask, error))
			goto fail_einval;
		if (!strcasecmp(type_str, "MASK")) {
			if (!strcasecmp(who_str, "OWNER")) {
				acl->a_owner_mask = mask;
				flags |= NFS4ACL_TEXT_OWNER_MASK;
			} else if (!strcasecmp(who_str, "GROUP")) {
				acl->a_group_mask = mask;
				flags |= NFS4ACL_TEXT_GROUP_MASK;
			} else if (!strcasecmp(who_str, "OTHER")) {
				acl->a_other_mask = mask;
				flags |= NFS4ACL_TEXT_OTHER_MASK;
			} else {
				error("Invalid file mask `%s'\n",
				      who_str);
				goto fail_einval;
			}
		} else {
			size_t size = sizeof(struct nfs4acl) + (acl->a_count
				      + 1) * sizeof(struct nfs4ace);
			acl2 = realloc(acl, size);
			if (!acl2)
				goto fail;
			acl = acl2;
			memset(acl->a_entries + acl->a_count, 0,
			       sizeof(struct nfs4ace));
			acl->a_count++;

			ace = acl->a_entries + acl->a_count - 1;
			ace->e_mask = mask;
			if (ace_flags_from_text(flags_str, ace, error))
				goto fail_einval;
			if (identifier_from_text(who_str, ace, error))
				goto fail_einval;
			if (type_from_text(type_str, ace, error))
				goto fail_einval;
		}

		free(type_str);
		type_str = NULL;
		free(flags_str);
		flags_str = NULL;
	free_mask_str:
		free(mask_str);
		mask_str = NULL;
		free(who_str);
		who_str = NULL;
		continue;

	fail_syntax:
		for (c = entry; *c && !(isspace(*c) || *c == ','); c++)
			;
		error("Invalid entry `%.*s'\n", c - entry, entry);
		goto fail_einval;
	}

	if (pflags)
		*pflags = flags;
	return acl;

fail_einval:
	errno = EINVAL;

fail:
	free(type_str);
	free(flags_str);
	free(mask_str);
	free(who_str);
	nfs4acl_free(acl);
	return NULL;
}

int nfs4ace_set_who(struct nfs4ace *ace, const char *who)
{
	if (!strcmp(who, nfs4ace_owner_who))
		who = nfs4ace_owner_who;
	else if (!strcmp(who, nfs4ace_group_who))
		who = nfs4ace_group_who;
	else if (!strcmp(who, nfs4ace_everyone_who))
		who = nfs4ace_everyone_who;
	else
		return -1;

	ace->u.e_who = who;
	ace->e_flags |= ACE4_SPECIAL_WHO;
	/*
	 * Also clear the ACE4_IDENTIFIER_GROUP flag for ACEs with a special
	 * who value: nfs4ace_is_same_identifier() relies on that.
	 */
	ace->e_flags &= ~ACE4_IDENTIFIER_GROUP;
	return 0;
}

void nfs4ace_set_uid(struct nfs4ace *ace, uid_t uid)
{
	ace->u.e_id = uid;
	ace->e_flags &= ~(ACE4_SPECIAL_WHO | ACE4_IDENTIFIER_GROUP);
}

void nfs4ace_set_gid(struct nfs4ace *ace, gid_t gid)
{
	ace->u.e_id = gid;
	ace->e_flags |= ACE4_IDENTIFIER_GROUP;
	ace->e_flags &= ~ACE4_SPECIAL_WHO;
}

void nfs4ace_copy(struct nfs4ace *dst, const struct nfs4ace *src)
{
	memcpy(dst, src, sizeof(struct nfs4ace));
}

static unsigned int nfs4acl_mode_to_mask(mode_t mode)
{
	unsigned int mask = ACE4_POSIX_ALWAYS_ALLOWED;

	if (mode & S_IROTH)
		mask |= ACE4_POSIX_MODE_READ;
	if (mode & S_IWOTH)
		mask |= ACE4_POSIX_MODE_WRITE;
	if (mode & S_IXOTH)
		mask |= ACE4_POSIX_MODE_EXEC;

	return mask;
}

struct nfs4acl *nfs4acl_from_mode(mode_t mode)
{
	struct nfs4acl *acl;
	struct nfs4ace *ace;

	acl = nfs4acl_alloc(1);
	if (!acl)
		return NULL;
	ace = acl->a_entries;

	acl->a_owner_mask = nfs4acl_mode_to_mask(mode >> 6);
	acl->a_group_mask = nfs4acl_mode_to_mask(mode >> 3);
	acl->a_other_mask = nfs4acl_mode_to_mask(mode);

	ace->e_type = ACE4_ACCESS_ALLOWED_ACE_TYPE;
	ace->e_flags = ACE4_SPECIAL_WHO;
	ace->e_mask = ACE4_VALID_MASK;
	ace->u.e_who = nfs4ace_everyone_who;
	
	if (nfs4acl_apply_masks(&acl)) {
		free(acl);
		acl = NULL;
	}
	return acl;
}