summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorpfg <pfg@FreeBSD.org>2017-08-20 01:08:23 +0000
committerpfg <pfg@FreeBSD.org>2017-08-20 01:08:23 +0000
commit9e20cbb8e523fa6c06b63ee797e5ec042bb972a4 (patch)
treec4f6fc59527ffb4554ed0c8dc10a049dd02318ce
parent89e8550e99695eeb7d08c7ff02a629a2be342070 (diff)
downloadFreeBSD-src-9e20cbb8e523fa6c06b63ee797e5ec042bb972a4.zip
FreeBSD-src-9e20cbb8e523fa6c06b63ee797e5ec042bb972a4.tar.gz
MFC r316341, r317779, r319071, r319077, r319557, r319558, r319827, r319829:
ext2fs: add read-write support for Extended Attributes and linux ACLs. Extended attributes and their particular implementation in linux are different from FreeBSD so in this case we have started diverging from the UFS EA implementation, which would be the natural reference. Support for linux ext2fs posix-draft ACLs. Submitted by: Fedor Uporov Reviewed by: pfg Include fixes from: cem, pluknet
-rw-r--r--sys/conf/files2
-rw-r--r--sys/fs/ext2fs/ext2_acl.c528
-rw-r--r--sys/fs/ext2fs/ext2_acl.h55
-rw-r--r--sys/fs/ext2fs/ext2_alloc.c19
-rw-r--r--sys/fs/ext2fs/ext2_extattr.c1225
-rw-r--r--sys/fs/ext2fs/ext2_extattr.h125
-rw-r--r--sys/fs/ext2fs/ext2_extern.h1
-rw-r--r--sys/fs/ext2fs/ext2_inode.c2
-rw-r--r--sys/fs/ext2fs/ext2_inode_cnv.c8
-rw-r--r--sys/fs/ext2fs/ext2_vnops.c362
-rw-r--r--sys/fs/ext2fs/ext2fs.h1
-rw-r--r--sys/fs/ext2fs/inode.h1
-rw-r--r--sys/modules/ext2fs/Makefile6
13 files changed, 2330 insertions, 5 deletions
diff --git a/sys/conf/files b/sys/conf/files
index e112065..cb42a55 100644
--- a/sys/conf/files
+++ b/sys/conf/files
@@ -3329,9 +3329,11 @@ geom/virstor/binstream.c optional geom_virstor
geom/virstor/g_virstor.c optional geom_virstor
geom/virstor/g_virstor_md.c optional geom_virstor
geom/zero/g_zero.c optional geom_zero
+fs/ext2fs/ext2_acl.c optional ext2fs
fs/ext2fs/ext2_alloc.c optional ext2fs
fs/ext2fs/ext2_balloc.c optional ext2fs
fs/ext2fs/ext2_bmap.c optional ext2fs
+fs/ext2fs/ext2_extattr.c optional ext2fs
fs/ext2fs/ext2_extents.c optional ext2fs
fs/ext2fs/ext2_inode.c optional ext2fs
fs/ext2fs/ext2_inode_cnv.c optional ext2fs
diff --git a/sys/fs/ext2fs/ext2_acl.c b/sys/fs/ext2fs/ext2_acl.c
new file mode 100644
index 0000000..fdf7121
--- /dev/null
+++ b/sys/fs/ext2fs/ext2_acl.c
@@ -0,0 +1,528 @@
+/*-
+ * Copyright (c) 2017, Fedor Uporov
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/kernel.h>
+#include <sys/malloc.h>
+#include <sys/vnode.h>
+#include <sys/bio.h>
+#include <sys/buf.h>
+#include <sys/endian.h>
+#include <sys/conf.h>
+#include <sys/mount.h>
+#include <sys/extattr.h>
+
+#include <fs/ext2fs/fs.h>
+#include <fs/ext2fs/ext2fs.h>
+#include <fs/ext2fs/inode.h>
+#include <fs/ext2fs/ext2_acl.h>
+#include <fs/ext2fs/ext2_extattr.h>
+#include <fs/ext2fs/ext2_extern.h>
+#include <fs/ext2fs/ext2_dinode.h>
+#include <fs/ext2fs/ext2_mount.h>
+
+#ifdef UFS_ACL
+
+void
+ext2_sync_acl_from_inode(struct inode *ip, struct acl *acl)
+{
+ struct acl_entry *acl_mask, *acl_group_obj;
+ int i;
+
+ /*
+ * Update ACL_USER_OBJ, ACL_OTHER, but simply identify ACL_MASK
+ * and ACL_GROUP_OBJ for use after we know whether ACL_MASK is
+ * present.
+ */
+ acl_mask = NULL;
+ acl_group_obj = NULL;
+ for (i = 0; i < acl->acl_cnt; i++) {
+ switch (acl->acl_entry[i].ae_tag) {
+ case ACL_USER_OBJ:
+ acl->acl_entry[i].ae_perm = acl_posix1e_mode_to_perm(
+ ACL_USER_OBJ, ip->i_mode);
+ acl->acl_entry[i].ae_id = ACL_UNDEFINED_ID;
+ break;
+
+ case ACL_GROUP_OBJ:
+ acl_group_obj = &acl->acl_entry[i];
+ acl->acl_entry[i].ae_id = ACL_UNDEFINED_ID;
+ break;
+
+ case ACL_OTHER:
+ acl->acl_entry[i].ae_perm = acl_posix1e_mode_to_perm(
+ ACL_OTHER, ip->i_mode);
+ acl->acl_entry[i].ae_id = ACL_UNDEFINED_ID;
+ break;
+
+ case ACL_MASK:
+ acl_mask = &acl->acl_entry[i];
+ acl->acl_entry[i].ae_id = ACL_UNDEFINED_ID;
+ break;
+
+ case ACL_USER:
+ case ACL_GROUP:
+ break;
+
+ default:
+ panic("ext2_sync_acl_from_inode(): bad ae_tag");
+ }
+ }
+
+ if (acl_group_obj == NULL)
+ panic("ext2_sync_acl_from_inode(): no ACL_GROUP_OBJ");
+
+ if (acl_mask == NULL) {
+ /*
+ * There is no ACL_MASK, so update ACL_GROUP_OBJ.
+ */
+ acl_group_obj->ae_perm = acl_posix1e_mode_to_perm(
+ ACL_GROUP_OBJ, ip->i_mode);
+ } else {
+ /*
+ * Update the ACL_MASK entry instead of ACL_GROUP_OBJ.
+ */
+ acl_mask->ae_perm = acl_posix1e_mode_to_perm(ACL_GROUP_OBJ,
+ ip->i_mode);
+ }
+}
+
+static void
+ext2_sync_inode_from_acl(struct acl *acl, struct inode *ip)
+{
+
+ ip->i_mode &= ACL_PRESERVE_MASK;
+ ip->i_mode |= acl_posix1e_acl_to_mode(acl);
+}
+
+/*
+ * Convert from filesystem to in-memory representation.
+ */
+static int
+ext4_acl_from_disk(char *value, size_t size, struct acl *acl)
+{
+ const char *end;
+ int n, count, s;
+
+ if (value == NULL)
+ return (EINVAL);
+
+ end = value + size;
+
+ if (((struct ext2_acl_header *)value)->a_version != EXT4_ACL_VERSION)
+ return (EINVAL);
+
+ if (size < sizeof(struct ext2_acl_header))
+ return (EINVAL);
+
+ s = size - sizeof(struct ext2_acl_header);
+ s -= 4 * sizeof(struct ext2_acl_entry_short);
+ if (s < 0)
+ if ((size - sizeof(struct ext2_acl_header)) %
+ sizeof(struct ext2_acl_entry_short))
+ count = -1;
+ else
+ count = (size - sizeof(struct ext2_acl_header)) /
+ sizeof(struct ext2_acl_entry_short);
+ else
+ if (s % sizeof(struct ext2_acl_entry))
+ count = -1;
+ else
+ count = s / sizeof(struct ext2_acl_entry) + 4;
+
+ if (count <= 0 || count > acl->acl_maxcnt)
+ return (EINVAL);
+
+ value = value + sizeof(struct ext2_acl_header);
+
+ for (n = 0; n < count; n++) {
+ struct ext2_acl_entry *entry = (struct ext2_acl_entry *)value;
+ if ((char *)value + sizeof(struct ext2_acl_entry_short) > end)
+ return (EINVAL);
+
+ acl->acl_entry[n].ae_tag = entry->ae_tag;
+ acl->acl_entry[n].ae_perm = entry->ae_perm;
+
+ switch (acl->acl_entry[n].ae_tag) {
+ case ACL_USER_OBJ:
+ case ACL_GROUP_OBJ:
+ case ACL_MASK:
+ case ACL_OTHER:
+ value = (char *)value + sizeof(struct ext2_acl_entry_short);
+ break;
+
+ case ACL_USER:
+ value = (char *)value + sizeof(struct ext2_acl_entry);
+ if ((char *)value > end)
+ return (EINVAL);
+
+ acl->acl_entry[n].ae_id = entry->ae_id;
+ break;
+
+ case ACL_GROUP:
+ value = (char *)value + sizeof(struct ext2_acl_entry);
+ if ((char *)value > end)
+ return (EINVAL);
+
+ acl->acl_entry[n].ae_id = entry->ae_id;
+ break;
+
+ default:
+ return (EINVAL);
+ }
+ }
+
+ if (value != end)
+ return (EINVAL);
+
+ acl->acl_cnt = count;
+
+ return (0);
+}
+
+static int
+ext2_getacl_posix1e(struct vop_getacl_args *ap)
+{
+ int attrnamespace;
+ const char *attrname;
+ char *value;
+ int len;
+ int error;
+
+ switch (ap->a_type) {
+ case ACL_TYPE_DEFAULT:
+ attrnamespace = POSIX1E_ACL_DEFAULT_EXTATTR_NAMESPACE;
+ attrname = POSIX1E_ACL_DEFAULT_EXTATTR_NAME;
+ break;
+ case ACL_TYPE_ACCESS:
+ attrnamespace = POSIX1E_ACL_ACCESS_EXTATTR_NAMESPACE;
+ attrname = POSIX1E_ACL_ACCESS_EXTATTR_NAME;
+ break;
+ default:
+ return (EINVAL);
+ }
+
+ len = sizeof(*ap->a_aclp) + sizeof(struct ext2_acl_header);
+ value = malloc(len, M_ACL, M_WAITOK);
+ if (!value)
+ return (ENOMEM);
+
+ error = vn_extattr_get(ap->a_vp, IO_NODELOCKED, attrnamespace, attrname,
+ &len, value, ap->a_td);
+ if (error == ENOATTR) {
+ switch (ap->a_type) {
+ case ACL_TYPE_ACCESS:
+ ap->a_aclp->acl_cnt = 3;
+ ap->a_aclp->acl_entry[0].ae_tag = ACL_USER_OBJ;
+ ap->a_aclp->acl_entry[0].ae_id = ACL_UNDEFINED_ID;
+ ap->a_aclp->acl_entry[0].ae_perm = ACL_PERM_NONE;
+ ap->a_aclp->acl_entry[1].ae_tag = ACL_GROUP_OBJ;
+ ap->a_aclp->acl_entry[1].ae_id = ACL_UNDEFINED_ID;
+ ap->a_aclp->acl_entry[1].ae_perm = ACL_PERM_NONE;
+ ap->a_aclp->acl_entry[2].ae_tag = ACL_OTHER;
+ ap->a_aclp->acl_entry[2].ae_id = ACL_UNDEFINED_ID;
+ ap->a_aclp->acl_entry[2].ae_perm = ACL_PERM_NONE;
+ break;
+
+ case ACL_TYPE_DEFAULT:
+ ap->a_aclp->acl_cnt = 0;
+ break;
+ }
+ } else if (error != 0)
+ goto out;
+
+ if (!error) {
+ error = ext4_acl_from_disk(value, len, ap->a_aclp);
+ if (error)
+ goto out;
+ }
+
+ if (error == ENOATTR)
+ error = 0;
+
+ if (ap->a_type == ACL_TYPE_ACCESS)
+ ext2_sync_acl_from_inode(VTOI(ap->a_vp), ap->a_aclp);
+
+out:
+ free(value, M_TEMP);
+ return (error);
+}
+
+int
+ext2_getacl(struct vop_getacl_args *ap)
+{
+
+ if (((ap->a_vp->v_mount->mnt_flag & MNT_ACLS) == 0) ||
+ ((ap->a_vp->v_mount->mnt_flag & MNT_NFS4ACLS) != 0))
+ return (EOPNOTSUPP);
+
+ if (ap->a_type == ACL_TYPE_NFS4)
+ return (ENOTSUP);
+
+ return (ext2_getacl_posix1e(ap));
+}
+
+/*
+ * Convert from in-memory to filesystem representation.
+ */
+static int
+ext4_acl_to_disk(const struct acl *acl, size_t *size, char *value)
+{
+ struct ext2_acl_header *ext_acl;
+ int disk_size;
+ char *e;
+ size_t n;
+
+ if (acl->acl_cnt <= 4)
+ disk_size = sizeof(struct ext2_acl_header) +
+ acl->acl_cnt * sizeof(struct ext2_acl_entry_short);
+ else
+ disk_size = sizeof(struct ext2_acl_header) +
+ 4 * sizeof(struct ext2_acl_entry_short) +
+ (acl->acl_cnt - 4) * sizeof(struct ext2_acl_entry);
+
+ if (disk_size > *size)
+ return (EINVAL);
+
+ *size = disk_size;
+ ext_acl = (struct ext2_acl_header *)value;
+
+ ext_acl->a_version = EXT4_ACL_VERSION;
+ e = (char *)ext_acl + sizeof(struct ext2_acl_header);
+ for (n = 0; n < acl->acl_cnt; n++) {
+ const struct acl_entry *acl_e = &acl->acl_entry[n];
+ struct ext2_acl_entry *entry = (struct ext2_acl_entry *)e;
+ entry->ae_tag = acl_e->ae_tag;
+ entry->ae_perm = acl_e->ae_perm;
+ switch (acl_e->ae_tag) {
+ case ACL_USER:
+ entry->ae_id = acl_e->ae_id;
+ e += sizeof(struct ext2_acl_entry);
+ break;
+
+ case ACL_GROUP:
+ entry->ae_id = acl_e->ae_id;
+ e += sizeof(struct ext2_acl_entry);
+ break;
+
+ case ACL_USER_OBJ:
+ case ACL_GROUP_OBJ:
+ case ACL_MASK:
+ case ACL_OTHER:
+ e += sizeof(struct ext2_acl_entry_short);
+ break;
+
+ default:
+ return (EINVAL);
+ }
+ }
+
+ return (0);
+}
+
+static int
+ext2_setacl_posix1e(struct vop_setacl_args *ap)
+{
+ struct inode *ip = VTOI(ap->a_vp);
+ char *value;
+ size_t len;
+ int error;
+
+ if ((ap->a_vp->v_mount->mnt_flag & MNT_ACLS) == 0)
+ return (EINVAL);
+
+ /*
+ * If this is a set operation rather than a delete operation,
+ * invoke VOP_ACLCHECK() on the passed ACL to determine if it is
+ * valid for the target. This will include a check on ap->a_type.
+ */
+ if (ap->a_aclp != NULL) {
+ /*
+ * Set operation.
+ */
+ error = VOP_ACLCHECK(ap->a_vp, ap->a_type, ap->a_aclp,
+ ap->a_cred, ap->a_td);
+ if (error)
+ return (error);
+ } else {
+ /*
+ * Delete operation.
+ * POSIX.1e allows only deletion of the default ACL on a
+ * directory (ACL_TYPE_DEFAULT).
+ */
+ if (ap->a_type != ACL_TYPE_DEFAULT)
+ return (EINVAL);
+ if (ap->a_vp->v_type != VDIR)
+ return (ENOTDIR);
+ }
+
+ if (ap->a_vp->v_mount->mnt_flag & MNT_RDONLY)
+ return (EROFS);
+
+ /*
+ * Authorize the ACL operation.
+ */
+ if (ip->i_flags & (IMMUTABLE | APPEND))
+ return (EPERM);
+
+ /*
+ * Must hold VADMIN (be file owner) or have appropriate privilege.
+ */
+ if ((error = VOP_ACCESS(ap->a_vp, VADMIN, ap->a_cred, ap->a_td)))
+ return (error);
+
+ switch (ap->a_type) {
+ case ACL_TYPE_ACCESS:
+ len = sizeof(*ap->a_aclp) + sizeof(struct ext2_acl_header);
+ value = malloc(len, M_ACL, M_WAITOK | M_ZERO);
+ error = ext4_acl_to_disk(ap->a_aclp, &len, value);
+ if (error == 0)
+ error = vn_extattr_set(ap->a_vp, IO_NODELOCKED,
+ POSIX1E_ACL_ACCESS_EXTATTR_NAMESPACE,
+ POSIX1E_ACL_ACCESS_EXTATTR_NAME, len,
+ value, ap->a_td);
+
+ free(value, M_ACL);
+ break;
+
+ case ACL_TYPE_DEFAULT:
+ if (ap->a_aclp == NULL) {
+ error = vn_extattr_rm(ap->a_vp, IO_NODELOCKED,
+ POSIX1E_ACL_DEFAULT_EXTATTR_NAMESPACE,
+ POSIX1E_ACL_DEFAULT_EXTATTR_NAME, ap->a_td);
+
+ /*
+ * Attempting to delete a non-present default ACL
+ * will return success for portability purposes.
+ * (TRIX)
+ *
+ * XXX: Note that since we can't distinguish
+ * "that EA is not supported" from "that EA is not
+ * defined", the success case here overlaps the
+ * the ENOATTR->EOPNOTSUPP case below.
+ */
+ if (error == ENOATTR)
+ error = 0;
+ } else {
+ len = sizeof(*ap->a_aclp) + sizeof(struct ext2_acl_header);
+ value = malloc(len, M_ACL, M_WAITOK | M_ZERO);
+ error = ext4_acl_to_disk(ap->a_aclp, &len, value);
+ if (error == 0)
+ error = vn_extattr_set(ap->a_vp, IO_NODELOCKED,
+ POSIX1E_ACL_DEFAULT_EXTATTR_NAMESPACE,
+ POSIX1E_ACL_DEFAULT_EXTATTR_NAME, len,
+ value, ap->a_td);
+
+ free(value, M_ACL);
+ }
+ break;
+
+ default:
+ error = EINVAL;
+ }
+
+ /*
+ * Map lack of attribute definition in UFS_EXTATTR into lack of
+ * support for ACLs on the filesystem.
+ */
+ if (error == ENOATTR)
+ return (EOPNOTSUPP);
+
+ if (error != 0)
+ return (error);
+
+ if (ap->a_type == ACL_TYPE_ACCESS) {
+ /*
+ * Now that the EA is successfully updated, update the
+ * inode and mark it as changed.
+ */
+ ext2_sync_inode_from_acl(ap->a_aclp, ip);
+ ip->i_flag |= IN_CHANGE;
+ error = ext2_update(ip->i_vnode, 1);
+ }
+
+ VN_KNOTE_UNLOCKED(ap->a_vp, NOTE_ATTRIB);
+
+ return (error);
+}
+
+int
+ext2_setacl(struct vop_setacl_args *ap)
+{
+ if (((ap->a_vp->v_mount->mnt_flag & MNT_ACLS) == 0) ||
+ ((ap->a_vp->v_mount->mnt_flag & MNT_NFS4ACLS) != 0))
+ return (EOPNOTSUPP);
+
+ if (ap->a_type == ACL_TYPE_NFS4)
+ return (ENOTSUP);
+
+ return (ext2_setacl_posix1e(ap));
+}
+
+/*
+ * Check the validity of an ACL for a file.
+ */
+int
+ext2_aclcheck(struct vop_aclcheck_args *ap)
+{
+
+ if (((ap->a_vp->v_mount->mnt_flag & MNT_ACLS) == 0) ||
+ ((ap->a_vp->v_mount->mnt_flag & MNT_NFS4ACLS) != 0))
+ return (EOPNOTSUPP);
+
+ if (ap->a_type == ACL_TYPE_NFS4)
+ return (ENOTSUP);
+
+ if ((ap->a_vp->v_mount->mnt_flag & MNT_ACLS) == 0)
+ return (EINVAL);
+
+ /*
+ * Verify we understand this type of ACL, and that it applies
+ * to this kind of object.
+ * Rely on the acl_posix1e_check() routine to verify the contents.
+ */
+ switch (ap->a_type) {
+ case ACL_TYPE_ACCESS:
+ break;
+
+ case ACL_TYPE_DEFAULT:
+ if (ap->a_vp->v_type != VDIR)
+ return (EINVAL);
+ break;
+
+ default:
+ return (EINVAL);
+ }
+
+ return (acl_posix1e_check(ap->a_aclp));
+}
+
+#endif /* UFS_ACL */
diff --git a/sys/fs/ext2fs/ext2_acl.h b/sys/fs/ext2fs/ext2_acl.h
new file mode 100644
index 0000000..abcb97d
--- /dev/null
+++ b/sys/fs/ext2fs/ext2_acl.h
@@ -0,0 +1,55 @@
+/*-
+ * Copyright (c) 2017, Fedor Uporov
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+#ifndef _FS_EXT2FS_EXT2_ACL_H_
+#define _FS_EXT2FS_EXT2_ACL_H_
+
+#define EXT4_ACL_VERSION 0x0001
+
+struct ext2_acl_entry {
+ int16_t ae_tag;
+ int16_t ae_perm;
+ int32_t ae_id;
+};
+
+struct ext2_acl_entry_short {
+ int16_t ae_tag;
+ int16_t ae_perm;
+};
+
+struct ext2_acl_header {
+ int32_t a_version;
+};
+
+void ext2_sync_acl_from_inode(struct inode *ip, struct acl *acl);
+
+int ext2_getacl(struct vop_getacl_args *);
+int ext2_setacl(struct vop_setacl_args *);
+int ext2_aclcheck(struct vop_aclcheck_args *);
+
+#endif /* !_FS_EXT2FS_EXT2_ACL_H_ */
diff --git a/sys/fs/ext2fs/ext2_alloc.c b/sys/fs/ext2fs/ext2_alloc.c
index a3c9385..a26fe92 100644
--- a/sys/fs/ext2fs/ext2_alloc.c
+++ b/sys/fs/ext2fs/ext2_alloc.c
@@ -132,6 +132,25 @@ nospace:
}
/*
+ * Allocate EA's block for inode.
+ */
+daddr_t
+ext2_allocfacl(struct inode *ip)
+{
+ struct m_ext2fs *fs;
+ daddr_t facl;
+
+ fs = ip->i_e2fs;
+
+ EXT2_LOCK(ip->i_ump);
+ facl = ext2_alloccg(ip, ino_to_cg(fs, ip->i_number), 0, fs->e2fs_bsize);
+ if (0 == facl)
+ EXT2_UNLOCK(ip->i_ump);
+
+ return (facl);
+}
+
+/*
* Reallocate a sequence of blocks into a contiguous sequence of blocks.
*
* The vnode and an array of buffer pointers for a range of sequential
diff --git a/sys/fs/ext2fs/ext2_extattr.c b/sys/fs/ext2fs/ext2_extattr.c
new file mode 100644
index 0000000..d19d4a0
--- /dev/null
+++ b/sys/fs/ext2fs/ext2_extattr.c
@@ -0,0 +1,1225 @@
+/*-
+ * Copyright (c) 2017, Fedor Uporov
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/types.h>
+#include <sys/kernel.h>
+#include <sys/malloc.h>
+#include <sys/vnode.h>
+#include <sys/bio.h>
+#include <sys/buf.h>
+#include <sys/endian.h>
+#include <sys/conf.h>
+#include <sys/extattr.h>
+
+#include <fs/ext2fs/fs.h>
+#include <fs/ext2fs/ext2fs.h>
+#include <fs/ext2fs/inode.h>
+#include <fs/ext2fs/ext2_dinode.h>
+#include <fs/ext2fs/ext2_mount.h>
+#include <fs/ext2fs/ext2_extattr.h>
+#include <fs/ext2fs/ext2_extern.h>
+
+static int
+ext2_extattr_attrnamespace_to_bsd(int attrnamespace)
+{
+
+ switch (attrnamespace) {
+ case EXT4_XATTR_INDEX_SYSTEM:
+ return (EXTATTR_NAMESPACE_SYSTEM);
+
+ case EXT4_XATTR_INDEX_USER:
+ return (EXTATTR_NAMESPACE_USER);
+
+ case EXT4_XATTR_INDEX_POSIX_ACL_DEFAULT:
+ return (POSIX1E_ACL_DEFAULT_EXTATTR_NAMESPACE);
+
+ case EXT4_XATTR_INDEX_POSIX_ACL_ACCESS:
+ return (POSIX1E_ACL_ACCESS_EXTATTR_NAMESPACE);
+ }
+
+ return (EXTATTR_NAMESPACE_EMPTY);
+}
+
+static const char *
+ext2_extattr_name_to_bsd(int attrnamespace, const char *name, int* name_len)
+{
+
+ if (attrnamespace == EXT4_XATTR_INDEX_SYSTEM)
+ return (name);
+ else if (attrnamespace == EXT4_XATTR_INDEX_USER)
+ return (name);
+ else if (attrnamespace == EXT4_XATTR_INDEX_POSIX_ACL_DEFAULT) {
+ *name_len = strlen(POSIX1E_ACL_DEFAULT_EXTATTR_NAME);
+ return (POSIX1E_ACL_DEFAULT_EXTATTR_NAME);
+ } else if (attrnamespace == EXT4_XATTR_INDEX_POSIX_ACL_ACCESS) {
+ *name_len = strlen(POSIX1E_ACL_ACCESS_EXTATTR_NAME);
+ return (POSIX1E_ACL_ACCESS_EXTATTR_NAME);
+ }
+
+ /*
+ * XXX: Not all linux namespaces are mapped to bsd for now,
+ * return NULL, which will be converted to ENOTSUP on upper layer.
+ */
+#ifdef EXT2FS_DEBUG
+ printf("can not convert ext2fs name to bsd: namespace=%d\n", attrnamespace);
+#endif
+
+ return (NULL);
+}
+
+static int
+ext2_extattr_attrnamespace_to_linux(int attrnamespace, const char *name)
+{
+
+ if (attrnamespace == POSIX1E_ACL_DEFAULT_EXTATTR_NAMESPACE &&
+ !strcmp(name, POSIX1E_ACL_DEFAULT_EXTATTR_NAME))
+ return (EXT4_XATTR_INDEX_POSIX_ACL_DEFAULT);
+
+ if (attrnamespace == POSIX1E_ACL_ACCESS_EXTATTR_NAMESPACE &&
+ !strcmp(name, POSIX1E_ACL_ACCESS_EXTATTR_NAME))
+ return (EXT4_XATTR_INDEX_POSIX_ACL_ACCESS);
+
+ switch (attrnamespace) {
+ case EXTATTR_NAMESPACE_SYSTEM:
+ return (EXT4_XATTR_INDEX_SYSTEM);
+
+ case EXTATTR_NAMESPACE_USER:
+ return (EXT4_XATTR_INDEX_USER);
+ }
+
+ /*
+ * In this case namespace conversion should be unique,
+ * so this point is unreachable.
+ */
+ return (-1);
+}
+
+static const char *
+ext2_extattr_name_to_linux(int attrnamespace, const char *name)
+{
+
+ if (attrnamespace == POSIX1E_ACL_DEFAULT_EXTATTR_NAMESPACE ||
+ attrnamespace == POSIX1E_ACL_ACCESS_EXTATTR_NAMESPACE)
+ return ("");
+ else
+ return (name);
+}
+
+int
+ext2_extattr_valid_attrname(int attrnamespace, const char *attrname)
+{
+ if (attrnamespace == EXTATTR_NAMESPACE_EMPTY)
+ return (EINVAL);
+
+ if (strlen(attrname) == 0)
+ return (EINVAL);
+
+ if (strlen(attrname) + 1 > EXT2_EXTATTR_NAMELEN_MAX)
+ return (ENAMETOOLONG);
+
+ return (0);
+}
+
+static int
+ext2_extattr_check(struct ext2fs_extattr_entry *entry, char *end)
+{
+ struct ext2fs_extattr_entry *next;
+
+ while (!EXT2_IS_LAST_ENTRY(entry)) {
+ next = EXT2_EXTATTR_NEXT(entry);
+ if ((char *)next >= end)
+ return (EIO);
+
+ entry = next;
+ }
+
+ return (0);
+}
+
+int
+ext2_extattr_inode_list(struct inode *ip, int attrnamespace,
+ struct uio *uio, size_t *size)
+{
+ struct m_ext2fs *fs;
+ struct buf *bp;
+ struct ext2fs_extattr_dinode_header *header;
+ struct ext2fs_extattr_entry *entry;
+ const char *attr_name;
+ int name_len;
+ int error;
+
+ fs = ip->i_e2fs;
+
+ if ((error = bread(ip->i_devvp,
+ fsbtodb(fs, ino_to_fsba(fs, ip->i_number)),
+ (int)fs->e2fs_bsize, NOCRED, &bp)) != 0) {
+ brelse(bp);
+ return (error);
+ }
+
+ struct ext2fs_dinode *dinode = (struct ext2fs_dinode *)
+ ((char *)bp->b_data +
+ EXT2_INODE_SIZE(fs) * ino_to_fsbo(fs, ip->i_number));
+
+ /* Check attributes magic value */
+ header = (struct ext2fs_extattr_dinode_header *)((char *)dinode +
+ E2FS_REV0_INODE_SIZE + dinode->e2di_extra_isize);
+
+ if (header->h_magic != EXTATTR_MAGIC) {
+ brelse(bp);
+ return (0);
+ }
+
+ error = ext2_extattr_check(EXT2_IFIRST(header),
+ (char *)dinode + EXT2_INODE_SIZE(fs));
+ if (error) {
+ brelse(bp);
+ return (error);
+ }
+
+ for (entry = EXT2_IFIRST(header); !EXT2_IS_LAST_ENTRY(entry);
+ entry = EXT2_EXTATTR_NEXT(entry)) {
+ if (ext2_extattr_attrnamespace_to_bsd(entry->e_name_index) !=
+ attrnamespace)
+ continue;
+
+ name_len = entry->e_name_len;
+ attr_name = ext2_extattr_name_to_bsd(entry->e_name_index,
+ entry->e_name, &name_len);
+ if (!attr_name) {
+ brelse(bp);
+ return (ENOTSUP);
+ }
+
+ if (uio == NULL)
+ *size += name_len + 1;
+ else {
+ char *name = malloc(name_len + 1, M_TEMP, M_WAITOK);
+ name[0] = name_len;
+ memcpy(&name[1], attr_name, name_len);
+ error = uiomove(name, name_len + 1, uio);
+ free(name, M_TEMP);
+ if (error)
+ break;
+ }
+ }
+
+ brelse(bp);
+
+ return (error);
+}
+
+int
+ext2_extattr_block_list(struct inode *ip, int attrnamespace,
+ struct uio *uio, size_t *size)
+{
+ struct m_ext2fs *fs;
+ struct buf *bp;
+ struct ext2fs_extattr_header *header;
+ struct ext2fs_extattr_entry *entry;
+ const char *attr_name;
+ int name_len;
+ int error;
+
+ fs = ip->i_e2fs;
+
+ error = bread(ip->i_devvp, fsbtodb(fs, ip->i_facl),
+ fs->e2fs_bsize, NOCRED, &bp);
+ if (error) {
+ brelse(bp);
+ return (error);
+ }
+
+ /* Check attributes magic value */
+ header = EXT2_HDR(bp);
+ if (header->h_magic != EXTATTR_MAGIC || header->h_blocks != 1) {
+ brelse(bp);
+ return (EINVAL);
+ }
+
+ error = ext2_extattr_check(EXT2_FIRST_ENTRY(bp), bp->b_data + bp->b_bufsize);
+ if (error) {
+ brelse(bp);
+ return (error);
+ }
+
+ for (entry = EXT2_FIRST_ENTRY(bp); !EXT2_IS_LAST_ENTRY(entry);
+ entry = EXT2_EXTATTR_NEXT(entry)) {
+ if (ext2_extattr_attrnamespace_to_bsd(entry->e_name_index) !=
+ attrnamespace)
+ continue;
+
+ name_len = entry->e_name_len;
+ attr_name = ext2_extattr_name_to_bsd(entry->e_name_index,
+ entry->e_name, &name_len);
+ if (!attr_name) {
+ brelse(bp);
+ return (ENOTSUP);
+ }
+
+ if (uio == NULL)
+ *size += name_len + 1;
+ else {
+ char *name = malloc(name_len + 1, M_TEMP, M_WAITOK);
+ name[0] = name_len;
+ memcpy(&name[1], attr_name, name_len);
+ error = uiomove(name, name_len + 1, uio);
+ free(name, M_TEMP);
+ if (error)
+ break;
+ }
+ }
+
+ brelse(bp);
+
+ return (error);
+}
+
+int
+ext2_extattr_inode_get(struct inode *ip, int attrnamespace,
+ const char *name, struct uio *uio, size_t *size)
+{
+ struct m_ext2fs *fs;
+ struct buf *bp;
+ struct ext2fs_extattr_dinode_header *header;
+ struct ext2fs_extattr_entry *entry;
+ const char *attr_name;
+ int name_len;
+ int error;
+
+ fs = ip->i_e2fs;
+
+ if ((error = bread(ip->i_devvp,
+ fsbtodb(fs, ino_to_fsba(fs, ip->i_number)),
+ (int)fs->e2fs_bsize, NOCRED, &bp)) != 0) {
+ brelse(bp);
+ return (error);
+ }
+
+ struct ext2fs_dinode *dinode = (struct ext2fs_dinode *)
+ ((char *)bp->b_data +
+ EXT2_INODE_SIZE(fs) * ino_to_fsbo(fs, ip->i_number));
+
+ /* Check attributes magic value */
+ header = (struct ext2fs_extattr_dinode_header *)((char *)dinode +
+ E2FS_REV0_INODE_SIZE + dinode->e2di_extra_isize);
+
+ if (header->h_magic != EXTATTR_MAGIC) {
+ brelse(bp);
+ return (ENOATTR);
+ }
+
+ error = ext2_extattr_check(EXT2_IFIRST(header),
+ (char *)dinode + EXT2_INODE_SIZE(fs));
+ if (error) {
+ brelse(bp);
+ return (error);
+ }
+
+ for (entry = EXT2_IFIRST(header); !EXT2_IS_LAST_ENTRY(entry);
+ entry = EXT2_EXTATTR_NEXT(entry)) {
+ if (ext2_extattr_attrnamespace_to_bsd(entry->e_name_index) !=
+ attrnamespace)
+ continue;
+
+ name_len = entry->e_name_len;
+ attr_name = ext2_extattr_name_to_bsd(entry->e_name_index,
+ entry->e_name, &name_len);
+ if (!attr_name) {
+ brelse(bp);
+ return (ENOTSUP);
+ }
+
+ if (strlen(name) == name_len &&
+ 0 == strncmp(attr_name, name, name_len)) {
+ if (uio == NULL)
+ *size += entry->e_value_size;
+ else {
+ error = uiomove(((char *)EXT2_IFIRST(header)) +
+ entry->e_value_offs, entry->e_value_size, uio);
+ }
+
+ brelse(bp);
+ return (error);
+ }
+ }
+
+ brelse(bp);
+
+ return (ENOATTR);
+}
+
+int
+ext2_extattr_block_get(struct inode *ip, int attrnamespace,
+ const char *name, struct uio *uio, size_t *size)
+{
+ struct m_ext2fs *fs;
+ struct buf *bp;
+ struct ext2fs_extattr_header *header;
+ struct ext2fs_extattr_entry *entry;
+ const char *attr_name;
+ int name_len;
+ int error;
+
+ fs = ip->i_e2fs;
+
+ error = bread(ip->i_devvp, fsbtodb(fs, ip->i_facl),
+ fs->e2fs_bsize, NOCRED, &bp);
+ if (error) {
+ brelse(bp);
+ return (error);
+ }
+
+ /* Check attributes magic value */
+ header = EXT2_HDR(bp);
+ if (header->h_magic != EXTATTR_MAGIC || header->h_blocks != 1) {
+ brelse(bp);
+ return (EINVAL);
+ }
+
+ error = ext2_extattr_check(EXT2_FIRST_ENTRY(bp), bp->b_data + bp->b_bufsize);
+ if (error) {
+ brelse(bp);
+ return (error);
+ }
+
+ for (entry = EXT2_FIRST_ENTRY(bp); !EXT2_IS_LAST_ENTRY(entry);
+ entry = EXT2_EXTATTR_NEXT(entry)) {
+ if (ext2_extattr_attrnamespace_to_bsd(entry->e_name_index) !=
+ attrnamespace)
+ continue;
+
+ name_len = entry->e_name_len;
+ attr_name = ext2_extattr_name_to_bsd(entry->e_name_index,
+ entry->e_name, &name_len);
+ if (!attr_name) {
+ brelse(bp);
+ return (ENOTSUP);
+ }
+
+ if (strlen(name) == name_len &&
+ 0 == strncmp(attr_name, name, name_len)) {
+ if (uio == NULL)
+ *size += entry->e_value_size;
+ else {
+ error = uiomove(bp->b_data + entry->e_value_offs,
+ entry->e_value_size, uio);
+ }
+
+ brelse(bp);
+ return (error);
+ }
+ }
+
+ brelse(bp);
+
+ return (ENOATTR);
+}
+
+static uint16_t
+ext2_extattr_delete_value(char *off,
+ struct ext2fs_extattr_entry *first_entry,
+ struct ext2fs_extattr_entry *entry, char *end)
+{
+ uint16_t min_offs;
+ struct ext2fs_extattr_entry *next;
+
+ min_offs = end - off;
+ next = first_entry;
+ while (!EXT2_IS_LAST_ENTRY(next)) {
+ if (min_offs > next->e_value_offs && next->e_value_offs > 0)
+ min_offs = next->e_value_offs;
+
+ next = EXT2_EXTATTR_NEXT(next);
+ }
+
+ if (entry->e_value_size == 0)
+ return (min_offs);
+
+ memmove(off + min_offs + EXT2_EXTATTR_SIZE(entry->e_value_size),
+ off + min_offs, entry->e_value_offs - min_offs);
+
+ /* Adjust all value offsets */
+ next = first_entry;
+ while (!EXT2_IS_LAST_ENTRY(next))
+ {
+ if (next->e_value_offs > 0 &&
+ next->e_value_offs < entry->e_value_offs)
+ next->e_value_offs +=
+ EXT2_EXTATTR_SIZE(entry->e_value_size);
+
+ next = EXT2_EXTATTR_NEXT(next);
+ }
+
+ min_offs += EXT2_EXTATTR_SIZE(entry->e_value_size);
+
+ return (min_offs);
+}
+
+static void
+ext2_extattr_delete_entry(char *off,
+ struct ext2fs_extattr_entry *first_entry,
+ struct ext2fs_extattr_entry *entry, char *end)
+{
+ char *pad;
+ struct ext2fs_extattr_entry *next;
+
+ /* Clean entry value */
+ ext2_extattr_delete_value(off, first_entry, entry, end);
+
+ /* Clean the entry */
+ next = first_entry;
+ while (!EXT2_IS_LAST_ENTRY(next))
+ next = EXT2_EXTATTR_NEXT(next);
+
+ pad = (char*)next + sizeof(uint32_t);
+
+ memmove(entry, (char *)entry + EXT2_EXTATTR_LEN(entry->e_name_len),
+ pad - ((char *)entry + EXT2_EXTATTR_LEN(entry->e_name_len)));
+}
+
+int
+ext2_extattr_inode_delete(struct inode *ip, int attrnamespace, const char *name)
+{
+ struct m_ext2fs *fs;
+ struct buf *bp;
+ struct ext2fs_extattr_dinode_header *header;
+ struct ext2fs_extattr_entry *entry;
+ const char *attr_name;
+ int name_len;
+ int error;
+
+ fs = ip->i_e2fs;
+
+ if ((error = bread(ip->i_devvp,
+ fsbtodb(fs, ino_to_fsba(fs, ip->i_number)),
+ (int)fs->e2fs_bsize, NOCRED, &bp)) != 0) {
+ brelse(bp);
+ return (error);
+ }
+
+ struct ext2fs_dinode *dinode = (struct ext2fs_dinode *)
+ ((char *)bp->b_data +
+ EXT2_INODE_SIZE(fs) * ino_to_fsbo(fs, ip->i_number));
+
+ /* Check attributes magic value */
+ header = (struct ext2fs_extattr_dinode_header *)((char *)dinode +
+ E2FS_REV0_INODE_SIZE + dinode->e2di_extra_isize);
+
+ if (header->h_magic != EXTATTR_MAGIC) {
+ brelse(bp);
+ return (ENOATTR);
+ }
+
+ error = ext2_extattr_check(EXT2_IFIRST(header),
+ (char *)dinode + EXT2_INODE_SIZE(fs));
+ if (error) {
+ brelse(bp);
+ return (error);
+ }
+
+ /* If I am last entry, just make magic zero */
+ entry = EXT2_IFIRST(header);
+ if ((EXT2_IS_LAST_ENTRY(EXT2_EXTATTR_NEXT(entry))) &&
+ (ext2_extattr_attrnamespace_to_bsd(entry->e_name_index) ==
+ attrnamespace)) {
+
+ name_len = entry->e_name_len;
+ attr_name = ext2_extattr_name_to_bsd(entry->e_name_index,
+ entry->e_name, &name_len);
+ if (!attr_name) {
+ brelse(bp);
+ return (ENOTSUP);
+ }
+
+ if (strlen(name) == name_len &&
+ 0 == strncmp(attr_name, name, name_len)) {
+ memset(header, 0, sizeof(struct ext2fs_extattr_dinode_header));
+
+ return (bwrite(bp));
+ }
+ }
+
+ for (entry = EXT2_IFIRST(header); !EXT2_IS_LAST_ENTRY(entry);
+ entry = EXT2_EXTATTR_NEXT(entry)) {
+ if (ext2_extattr_attrnamespace_to_bsd(entry->e_name_index) !=
+ attrnamespace)
+ continue;
+
+ name_len = entry->e_name_len;
+ attr_name = ext2_extattr_name_to_bsd(entry->e_name_index,
+ entry->e_name, &name_len);
+ if (!attr_name) {
+ brelse(bp);
+ return (ENOTSUP);
+ }
+
+ if (strlen(name) == name_len &&
+ 0 == strncmp(attr_name, name, name_len)) {
+ ext2_extattr_delete_entry((char *)EXT2_IFIRST(header),
+ EXT2_IFIRST(header), entry,
+ (char *)dinode + EXT2_INODE_SIZE(fs));
+
+ return (bwrite(bp));
+ }
+ }
+
+ brelse(bp);
+
+ return (ENOATTR);
+}
+
+static int
+ext2_extattr_block_clone(struct inode *ip, struct buf **bpp)
+{
+ struct m_ext2fs *fs;
+ struct buf *sbp;
+ struct buf *cbp;
+ struct ext2fs_extattr_header *header;
+ uint64_t facl;
+
+ fs = ip->i_e2fs;
+ sbp = *bpp;
+
+ header = EXT2_HDR(sbp);
+ if (header->h_magic != EXTATTR_MAGIC || header->h_refcount == 1)
+ return (EINVAL);
+
+ facl = ext2_allocfacl(ip);
+ if (!facl)
+ return (ENOSPC);
+
+ cbp = getblk(ip->i_devvp, fsbtodb(fs, facl), fs->e2fs_bsize, 0, 0, 0);
+ if (!cbp) {
+ ext2_blkfree(ip, facl, fs->e2fs_bsize);
+ return (EIO);
+ }
+
+ memcpy(cbp->b_data, sbp->b_data, fs->e2fs_bsize);
+ header->h_refcount--;
+ bwrite(sbp);
+
+ ip->i_facl = facl;
+ ext2_update(ip->i_vnode, 1);
+
+ header = EXT2_HDR(cbp);
+ header->h_refcount = 1;
+
+ *bpp = cbp;
+
+ return (0);
+}
+
+int
+ext2_extattr_block_delete(struct inode *ip, int attrnamespace, const char *name)
+{
+ struct m_ext2fs *fs;
+ struct buf *bp;
+ struct ext2fs_extattr_header *header;
+ struct ext2fs_extattr_entry *entry;
+ const char *attr_name;
+ int name_len;
+ int error;
+
+ fs = ip->i_e2fs;
+
+ error = bread(ip->i_devvp, fsbtodb(fs, ip->i_facl),
+ fs->e2fs_bsize, NOCRED, &bp);
+ if (error) {
+ brelse(bp);
+ return (error);
+ }
+
+ /* Check attributes magic value */
+ header = EXT2_HDR(bp);
+ if (header->h_magic != EXTATTR_MAGIC || header->h_blocks != 1) {
+ brelse(bp);
+ return (EINVAL);
+ }
+
+ error = ext2_extattr_check(EXT2_FIRST_ENTRY(bp), bp->b_data + bp->b_bufsize);
+ if (error) {
+ brelse(bp);
+ return (error);
+ }
+
+ if (header->h_refcount > 1) {
+ error = ext2_extattr_block_clone(ip, &bp);
+ if (error) {
+ brelse(bp);
+ return (error);
+ }
+ }
+
+ /* If I am last entry, clean me and free the block */
+ entry = EXT2_FIRST_ENTRY(bp);
+ if (EXT2_IS_LAST_ENTRY(EXT2_EXTATTR_NEXT(entry)) &&
+ (ext2_extattr_attrnamespace_to_bsd(entry->e_name_index) ==
+ attrnamespace)) {
+
+ name_len = entry->e_name_len;
+ attr_name = ext2_extattr_name_to_bsd(entry->e_name_index,
+ entry->e_name, &name_len);
+ if (!attr_name) {
+ brelse(bp);
+ return (ENOTSUP);
+ }
+
+ if (strlen(name) == name_len &&
+ 0 == strncmp(attr_name, name, name_len)) {
+ ip->i_blocks -= btodb(fs->e2fs_bsize);
+ ext2_blkfree(ip, ip->i_facl, fs->e2fs_bsize);
+ ip->i_facl = 0;
+ error = ext2_update(ip->i_vnode, 1);
+
+ brelse(bp);
+ return (error);
+ }
+ }
+
+ for (entry = EXT2_FIRST_ENTRY(bp); !EXT2_IS_LAST_ENTRY(entry);
+ entry = EXT2_EXTATTR_NEXT(entry)) {
+ if (ext2_extattr_attrnamespace_to_bsd(entry->e_name_index) !=
+ attrnamespace)
+ continue;
+
+ name_len = entry->e_name_len;
+ attr_name = ext2_extattr_name_to_bsd(entry->e_name_index,
+ entry->e_name, &name_len);
+ if (!attr_name) {
+ brelse(bp);
+ return (ENOTSUP);
+ }
+
+ if (strlen(name) == name_len &&
+ 0 == strncmp(attr_name, name, name_len)) {
+ ext2_extattr_delete_entry(bp->b_data,
+ EXT2_FIRST_ENTRY(bp), entry,
+ bp->b_data + bp->b_bufsize);
+
+ return (bwrite(bp));
+ }
+ }
+
+ brelse(bp);
+
+ return (ENOATTR);
+}
+
+static struct ext2fs_extattr_entry *
+allocate_entry(const char *name, int attrnamespace, uint16_t offs,
+ uint32_t size, uint32_t hash)
+{
+ const char *attr_name;
+ int name_len;
+ struct ext2fs_extattr_entry *entry;
+
+ attr_name = ext2_extattr_name_to_linux(attrnamespace, name);
+ name_len = strlen(attr_name);
+
+ entry = malloc(sizeof(struct ext2fs_extattr_entry) + name_len,
+ M_TEMP, M_WAITOK);
+
+ entry->e_name_len = name_len;
+ entry->e_name_index = ext2_extattr_attrnamespace_to_linux(attrnamespace, name);
+ entry->e_value_offs = offs;
+ entry->e_value_block = 0;
+ entry->e_value_size = size;
+ entry->e_hash = hash;
+ memcpy(entry->e_name, name, name_len);
+
+ return (entry);
+}
+
+static void
+free_entry(struct ext2fs_extattr_entry *entry)
+{
+
+ free(entry, M_TEMP);
+}
+
+static int
+ext2_extattr_get_size(struct ext2fs_extattr_entry *first_entry,
+ struct ext2fs_extattr_entry *exist_entry, int header_size,
+ int name_len, int new_size)
+{
+ struct ext2fs_extattr_entry *entry;
+ int size;
+
+ size = header_size;
+ size += sizeof(uint32_t);
+
+ if (NULL == exist_entry) {
+ size += EXT2_EXTATTR_LEN(name_len);
+ size += EXT2_EXTATTR_SIZE(new_size);
+ }
+
+ if (first_entry)
+ for (entry = first_entry; !EXT2_IS_LAST_ENTRY(entry);
+ entry = EXT2_EXTATTR_NEXT(entry)) {
+ if (entry != exist_entry)
+ size += EXT2_EXTATTR_LEN(entry->e_name_len) +
+ EXT2_EXTATTR_SIZE(entry->e_value_size);
+ else
+ size += EXT2_EXTATTR_LEN(entry->e_name_len) +
+ EXT2_EXTATTR_SIZE(new_size);
+ }
+
+ return (size);
+}
+
+static void
+ext2_extattr_set_exist_entry(char *off,
+ struct ext2fs_extattr_entry *first_entry,
+ struct ext2fs_extattr_entry *entry,
+ char *end, struct uio *uio)
+{
+ uint16_t min_offs;
+
+ min_offs = ext2_extattr_delete_value(off, first_entry, entry, end);
+
+ entry->e_value_size = uio->uio_resid;
+ if (entry->e_value_size)
+ entry->e_value_offs = min_offs -
+ EXT2_EXTATTR_SIZE(uio->uio_resid);
+ else
+ entry->e_value_offs = 0;
+
+ uiomove(off + entry->e_value_offs, entry->e_value_size, uio);
+}
+
+static struct ext2fs_extattr_entry *
+ext2_extattr_set_new_entry(char *off, struct ext2fs_extattr_entry *first_entry,
+ const char *name, int attrnamespace, char *end, struct uio *uio)
+{
+ int name_len;
+ char *pad;
+ uint16_t min_offs;
+ struct ext2fs_extattr_entry *entry;
+ struct ext2fs_extattr_entry *new_entry;
+
+ /* Find pad's */
+ min_offs = end - off;
+ entry = first_entry;
+ while (!EXT2_IS_LAST_ENTRY(entry)) {
+ if (min_offs > entry->e_value_offs && entry->e_value_offs > 0)
+ min_offs = entry->e_value_offs;
+
+ entry = EXT2_EXTATTR_NEXT(entry);
+ }
+
+ pad = (char*)entry + sizeof(uint32_t);
+
+ /* Find entry insert position */
+ name_len = strlen(name);
+ entry = first_entry;
+ while (!EXT2_IS_LAST_ENTRY(entry)) {
+ if (!(attrnamespace - entry->e_name_index) &&
+ !(name_len - entry->e_name_len))
+ if (memcmp(name, entry->e_name, name_len) <= 0)
+ break;
+
+ entry = EXT2_EXTATTR_NEXT(entry);
+ }
+
+ /* Create new entry and insert it */
+ new_entry = allocate_entry(name, attrnamespace, 0, uio->uio_resid, 0);
+ memmove((char *)entry + EXT2_EXTATTR_LEN(new_entry->e_name_len), entry,
+ pad - (char*)entry);
+
+ memcpy(entry, new_entry, EXT2_EXTATTR_LEN(new_entry->e_name_len));
+ free_entry(new_entry);
+
+ new_entry = entry;
+ if (new_entry->e_value_size > 0)
+ new_entry->e_value_offs = min_offs -
+ EXT2_EXTATTR_SIZE(new_entry->e_value_size);
+
+ uiomove(off + new_entry->e_value_offs, new_entry->e_value_size, uio);
+
+ return (new_entry);
+}
+
+int
+ext2_extattr_inode_set(struct inode *ip, int attrnamespace,
+ const char *name, struct uio *uio)
+{
+ struct m_ext2fs *fs;
+ struct buf *bp;
+ struct ext2fs_extattr_dinode_header *header;
+ struct ext2fs_extattr_entry *entry;
+ const char *attr_name;
+ int name_len;
+ size_t size = 0, max_size;
+ int error;
+
+ fs = ip->i_e2fs;
+
+ if ((error = bread(ip->i_devvp,
+ fsbtodb(fs, ino_to_fsba(fs, ip->i_number)),
+ (int)fs->e2fs_bsize, NOCRED, &bp)) != 0) {
+ brelse(bp);
+ return (error);
+ }
+
+ struct ext2fs_dinode *dinode = (struct ext2fs_dinode *)
+ ((char *)bp->b_data +
+ EXT2_INODE_SIZE(fs) * ino_to_fsbo(fs, ip->i_number));
+
+ /* Check attributes magic value */
+ header = (struct ext2fs_extattr_dinode_header *)((char *)dinode +
+ E2FS_REV0_INODE_SIZE + dinode->e2di_extra_isize);
+
+ if (header->h_magic != EXTATTR_MAGIC) {
+ brelse(bp);
+ return (ENOSPC);
+ }
+
+ error = ext2_extattr_check(EXT2_IFIRST(header), (char *)dinode +
+ EXT2_INODE_SIZE(fs));
+ if (error) {
+ brelse(bp);
+ return (error);
+ }
+
+ /* Find if entry exist */
+ for (entry = EXT2_IFIRST(header); !EXT2_IS_LAST_ENTRY(entry);
+ entry = EXT2_EXTATTR_NEXT(entry)) {
+ if (ext2_extattr_attrnamespace_to_bsd(entry->e_name_index) !=
+ attrnamespace)
+ continue;
+
+ name_len = entry->e_name_len;
+ attr_name = ext2_extattr_name_to_bsd(entry->e_name_index,
+ entry->e_name, &name_len);
+ if (!attr_name) {
+ brelse(bp);
+ return (ENOTSUP);
+ }
+
+ if (strlen(name) == name_len &&
+ 0 == strncmp(attr_name, name, name_len))
+ break;
+ }
+
+ max_size = EXT2_INODE_SIZE(fs) - E2FS_REV0_INODE_SIZE -
+ dinode->e2di_extra_isize;
+
+ if (!EXT2_IS_LAST_ENTRY(entry)) {
+ size = ext2_extattr_get_size(EXT2_IFIRST(header), entry,
+ sizeof(struct ext2fs_extattr_dinode_header),
+ entry->e_name_len, uio->uio_resid);
+ if (size > max_size) {
+ brelse(bp);
+ return (ENOSPC);
+ }
+
+ ext2_extattr_set_exist_entry((char *)EXT2_IFIRST(header),
+ EXT2_IFIRST(header), entry, (char *)header + max_size, uio);
+ } else {
+ /* Ensure that the same entry does not exist in the block */
+ if (ip->i_facl) {
+ error = ext2_extattr_block_get(ip, attrnamespace, name,
+ NULL, &size);
+ if (error != ENOATTR || size > 0) {
+ brelse(bp);
+ if (size > 0)
+ error = ENOSPC;
+
+ return (error);
+ }
+ }
+
+ size = ext2_extattr_get_size(EXT2_IFIRST(header), NULL,
+ sizeof(struct ext2fs_extattr_dinode_header),
+ entry->e_name_len, uio->uio_resid);
+ if (size > max_size) {
+ brelse(bp);
+ return (ENOSPC);
+ }
+
+ ext2_extattr_set_new_entry((char *)EXT2_IFIRST(header),
+ EXT2_IFIRST(header), name, attrnamespace,
+ (char *)header + max_size, uio);
+ }
+
+ return (bwrite(bp));
+}
+
+static void
+ext2_extattr_hash_entry(struct ext2fs_extattr_header *header,
+ struct ext2fs_extattr_entry *entry)
+{
+ uint32_t hash = 0;
+ char *name = entry->e_name;
+ int n;
+
+ for (n=0; n < entry->e_name_len; n++) {
+ hash = (hash << EXT2_EXTATTR_NAME_HASH_SHIFT) ^
+ (hash >> (8*sizeof(hash) - EXT2_EXTATTR_NAME_HASH_SHIFT)) ^
+ (*name++);
+ }
+
+ if (entry->e_value_block == 0 && entry->e_value_size != 0) {
+ uint32_t *value = (uint32_t *)((char *)header + entry->e_value_offs);
+ for (n = (entry->e_value_size +
+ EXT2_EXTATTR_ROUND) >> EXT2_EXTATTR_PAD_BITS; n; n--) {
+ hash = (hash << EXT2_EXTATTR_VALUE_HASH_SHIFT) ^
+ (hash >> (8*sizeof(hash) - EXT2_EXTATTR_VALUE_HASH_SHIFT)) ^
+ (*value++);
+ }
+ }
+
+ entry->e_hash = hash;
+}
+
+static void
+ext2_extattr_rehash(struct ext2fs_extattr_header *header,
+ struct ext2fs_extattr_entry *entry)
+{
+ struct ext2fs_extattr_entry *here;
+ uint32_t hash = 0;
+
+ ext2_extattr_hash_entry(header, entry);
+
+ here = EXT2_ENTRY(header+1);
+ while (!EXT2_IS_LAST_ENTRY(here)) {
+ if (!here->e_hash) {
+ /* Block is not shared if an entry's hash value == 0 */
+ hash = 0;
+ break;
+ }
+
+ hash = (hash << EXT2_EXTATTR_BLOCK_HASH_SHIFT) ^
+ (hash >> (8*sizeof(hash) - EXT2_EXTATTR_BLOCK_HASH_SHIFT)) ^
+ here->e_hash;
+
+ here = EXT2_EXTATTR_NEXT(here);
+ }
+
+ header->h_hash = hash;
+}
+
+int
+ext2_extattr_block_set(struct inode *ip, int attrnamespace,
+ const char *name, struct uio *uio)
+{
+ struct m_ext2fs *fs;
+ struct buf *bp;
+ struct ext2fs_extattr_header *header;
+ struct ext2fs_extattr_entry *entry;
+ const char *attr_name;
+ int name_len;
+ size_t size;
+ int error;
+
+ fs = ip->i_e2fs;
+
+ if (ip->i_facl) {
+ error = bread(ip->i_devvp, fsbtodb(fs, ip->i_facl),
+ fs->e2fs_bsize, NOCRED, &bp);
+ if (error) {
+ brelse(bp);
+ return (error);
+ }
+
+ /* Check attributes magic value */
+ header = EXT2_HDR(bp);
+ if (header->h_magic != EXTATTR_MAGIC || header->h_blocks != 1) {
+ brelse(bp);
+ return (EINVAL);
+ }
+
+ error = ext2_extattr_check(EXT2_FIRST_ENTRY(bp),
+ bp->b_data + bp->b_bufsize);
+ if (error) {
+ brelse(bp);
+ return (error);
+ }
+
+ if (header->h_refcount > 1) {
+ error = ext2_extattr_block_clone(ip, &bp);
+ if (error) {
+ brelse(bp);
+ return (error);
+ }
+
+ header = EXT2_HDR(bp);
+ }
+
+ /* Find if entry exist */
+ for (entry = EXT2_FIRST_ENTRY(bp); !EXT2_IS_LAST_ENTRY(entry);
+ entry = EXT2_EXTATTR_NEXT(entry)) {
+ if (ext2_extattr_attrnamespace_to_bsd(entry->e_name_index) !=
+ attrnamespace)
+ continue;
+
+ name_len = entry->e_name_len;
+ attr_name = ext2_extattr_name_to_bsd(entry->e_name_index,
+ entry->e_name, &name_len);
+ if (!attr_name) {
+ brelse(bp);
+ return (ENOTSUP);
+ }
+
+ if (strlen(name) == name_len &&
+ 0 == strncmp(attr_name, name, name_len))
+ break;
+ }
+
+ if (!EXT2_IS_LAST_ENTRY(entry)) {
+ size = ext2_extattr_get_size(EXT2_FIRST_ENTRY(bp), entry,
+ sizeof(struct ext2fs_extattr_header),
+ entry->e_name_len, uio->uio_resid);
+ if (size > bp->b_bufsize) {
+ brelse(bp);
+ return (ENOSPC);
+ }
+
+ ext2_extattr_set_exist_entry(bp->b_data, EXT2_FIRST_ENTRY(bp),
+ entry, bp->b_data + bp->b_bufsize, uio);
+ } else {
+ size = ext2_extattr_get_size(EXT2_FIRST_ENTRY(bp), NULL,
+ sizeof(struct ext2fs_extattr_header),
+ strlen(name), uio->uio_resid);
+ if (size > bp->b_bufsize) {
+ brelse(bp);
+ return (ENOSPC);
+ }
+
+ entry = ext2_extattr_set_new_entry(bp->b_data, EXT2_FIRST_ENTRY(bp),
+ name, attrnamespace, bp->b_data + bp->b_bufsize, uio);
+
+ /* Clean the same entry in the inode */
+ error = ext2_extattr_inode_delete(ip, attrnamespace, name);
+ if (error && error != ENOATTR) {
+ brelse(bp);
+ return (error);
+ }
+ }
+
+ ext2_extattr_rehash(header, entry);
+
+ return (bwrite(bp));
+ }
+
+ size = ext2_extattr_get_size(NULL, NULL,
+ sizeof(struct ext2fs_extattr_header),
+ strlen(ext2_extattr_name_to_linux(attrnamespace, name)), uio->uio_resid);
+ if (size > fs->e2fs_bsize)
+ return (ENOSPC);
+
+ /* Allocate block, fill EA header and insert entry */
+ ip->i_facl = ext2_allocfacl(ip);
+ if (0 == ip->i_facl)
+ return (ENOSPC);
+
+ ip->i_blocks += btodb(fs->e2fs_bsize);
+ ext2_update(ip->i_vnode, 1);
+
+ bp = getblk(ip->i_devvp, fsbtodb(fs, ip->i_facl), fs->e2fs_bsize, 0, 0, 0);
+ if (!bp) {
+ ext2_blkfree(ip, ip->i_facl, fs->e2fs_bsize);
+ ip->i_blocks -= btodb(fs->e2fs_bsize);
+ ip->i_facl = 0;
+ ext2_update(ip->i_vnode, 1);
+ return (EIO);
+ }
+
+ header = EXT2_HDR(bp);
+ header->h_magic = EXTATTR_MAGIC;
+ header->h_refcount = 1;
+ header->h_blocks = 1;
+ header->h_hash = 0;
+ memset(header->h_reserved, 0, sizeof(header->h_reserved));
+ memcpy(bp->b_data, header, sizeof(struct ext2fs_extattr_header));
+ memset(EXT2_FIRST_ENTRY(bp), 0, sizeof(uint32_t));
+
+ entry = ext2_extattr_set_new_entry(bp->b_data, EXT2_FIRST_ENTRY(bp),
+ name, attrnamespace, bp->b_data + bp->b_bufsize, uio);
+
+ /* Clean the same entry in the inode */
+ error = ext2_extattr_inode_delete(ip, attrnamespace, name);
+ if (error && error != ENOATTR) {
+ brelse(bp);
+ return (error);
+ }
+
+ ext2_extattr_rehash(header, entry);
+
+ return (bwrite(bp));
+}
+
+int ext2_extattr_free(struct inode *ip)
+{
+ struct m_ext2fs *fs;
+ struct buf *bp;
+ struct ext2fs_extattr_header *header;
+ int error;
+
+ fs = ip->i_e2fs;
+
+ if (!ip->i_facl)
+ return (0);
+
+ error = bread(ip->i_devvp, fsbtodb(fs, ip->i_facl),
+ fs->e2fs_bsize, NOCRED, &bp);
+ if (error) {
+ brelse(bp);
+ return (error);
+ }
+
+ /* Check attributes magic value */
+ header = EXT2_HDR(bp);
+ if (header->h_magic != EXTATTR_MAGIC || header->h_blocks != 1) {
+ brelse(bp);
+ return (EINVAL);
+ }
+
+ error = ext2_extattr_check(EXT2_FIRST_ENTRY(bp), bp->b_data + bp->b_bufsize);
+ if (error) {
+ brelse(bp);
+ return (error);
+ }
+
+ if (header->h_refcount > 1) {
+ header->h_refcount--;
+ bwrite(bp);
+ } else {
+ ext2_blkfree(ip, ip->i_facl, ip->i_e2fs->e2fs_bsize);
+ brelse(bp);
+ }
+
+ ip->i_blocks -= btodb(ip->i_e2fs->e2fs_bsize);
+ ip->i_facl = 0;
+ ext2_update(ip->i_vnode, 1);
+
+ return (0);
+}
diff --git a/sys/fs/ext2fs/ext2_extattr.h b/sys/fs/ext2fs/ext2_extattr.h
new file mode 100644
index 0000000..50ab690
--- /dev/null
+++ b/sys/fs/ext2fs/ext2_extattr.h
@@ -0,0 +1,125 @@
+/*-
+ * Copyright (c) 2017, Fedor Uporov
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+#ifndef _FS_EXT2FS_EXT2_EXTARTTR_H_
+#define _FS_EXT2FS_EXT2_EXTARTTR_H_
+
+/* Linux xattr name indexes */
+#define EXT4_XATTR_INDEX_USER 1
+#define EXT4_XATTR_INDEX_POSIX_ACL_ACCESS 2
+#define EXT4_XATTR_INDEX_POSIX_ACL_DEFAULT 3
+#define EXT4_XATTR_INDEX_TRUSTED 4
+#define EXT4_XATTR_INDEX_LUSTRE 5
+#define EXT4_XATTR_INDEX_SECURITY 6
+#define EXT4_XATTR_INDEX_SYSTEM 7
+#define EXT4_XATTR_INDEX_RICHACL 8
+#define EXT4_XATTR_INDEX_ENCRYPTION 9
+
+/* Magic value in attribute blocks */
+#define EXTATTR_MAGIC 0xEA020000
+
+/* Max EA name length */
+#define EXT2_EXTATTR_NAMELEN_MAX 255
+
+/* EA hash constants */
+#define EXT2_EXTATTR_NAME_HASH_SHIFT 5
+#define EXT2_EXTATTR_VALUE_HASH_SHIFT 16
+#define EXT2_EXTATTR_BLOCK_HASH_SHIFT 16
+
+
+struct ext2fs_extattr_header {
+ int32_t h_magic; /* magic number for identification */
+ int32_t h_refcount; /* reference count */
+ int32_t h_blocks; /* number of disk blocks used */
+ int32_t h_hash; /* hash value of all attributes */
+ uint32_t h_reserved[4]; /* zero right now */
+};
+
+struct ext2fs_extattr_dinode_header {
+ int32_t h_magic; /* magic number for identification */
+};
+
+struct ext2fs_extattr_entry {
+ uint8_t e_name_len; /* length of name */
+ uint8_t e_name_index; /* attribute name index */
+ uint16_t e_value_offs; /* offset in disk block of value */
+ uint32_t e_value_block; /* disk block attribute is stored on (n/i) */
+ uint32_t e_value_size; /* size of attribute value */
+ uint32_t e_hash; /* hash value of name and value */
+ char e_name[0]; /* attribute name */
+};
+
+#define EXT2_IFIRST(hdr) ((struct ext2fs_extattr_entry *)((hdr)+1))
+
+#define EXT2_HDR(bh) ((struct ext2fs_extattr_header *)((bh)->b_data))
+#define EXT2_ENTRY(ptr) ((struct ext2fs_extattr_entry *)(ptr))
+#define EXT2_FIRST_ENTRY(bh) EXT2_ENTRY(EXT2_HDR(bh)+1)
+#define EXT2_IS_LAST_ENTRY(entry) (*(uint32_t *)(entry) == 0)
+
+#define EXT2_EXTATTR_PAD_BITS 2
+#define EXT2_EXTATTR_PAD (1<<EXT2_EXTATTR_PAD_BITS)
+#define EXT2_EXTATTR_ROUND (EXT2_EXTATTR_PAD-1)
+#define EXT2_EXTATTR_LEN(name_len) \
+ (((name_len) + EXT2_EXTATTR_ROUND + \
+ sizeof(struct ext2fs_extattr_entry)) & ~EXT2_EXTATTR_ROUND)
+
+#define EXT2_EXTATTR_SIZE(size) \
+ (((size) + EXT2_EXTATTR_ROUND) & ~EXT2_EXTATTR_ROUND)
+
+#define EXT2_EXTATTR_NEXT(entry) \
+ ( (struct ext2fs_extattr_entry *)( \
+ (char *)(entry) + EXT2_EXTATTR_LEN((entry)->e_name_len)) )
+
+int ext2_extattr_inode_delete(struct inode *ip, int attrnamespace,
+ const char *name);
+
+int ext2_extattr_block_delete(struct inode *ip, int attrnamespace,
+ const char *name);
+
+int ext2_extattr_free(struct inode *ip);
+int ext2_extattr_inode_list(struct inode *ip, int attrnamespace,
+ struct uio *uio, size_t *size);
+
+int ext2_extattr_block_list(struct inode *ip, int attrnamespace,
+ struct uio *uio, size_t *size);
+
+int ext2_extattr_inode_get(struct inode *ip, int attrnamespace,
+ const char *name, struct uio *uio, size_t *size);
+
+int ext2_extattr_block_get(struct inode *ip, int attrnamespace,
+ const char *name, struct uio *uio, size_t *size);
+
+int ext2_extattr_inode_set(struct inode *ip, int attrnamespace,
+ const char *name, struct uio *uio);
+
+int ext2_extattr_block_set(struct inode *ip, int attrnamespace,
+ const char *name, struct uio *uio);
+
+int ext2_extattr_valid_attrname(int attrnamespace, const char *attrname);
+
+#endif /* !_FS_EXT2FS_EXT2_EXTARTTR_H_ */
diff --git a/sys/fs/ext2fs/ext2_extern.h b/sys/fs/ext2fs/ext2_extern.h
index 5e9d18f..e1199bc 100644
--- a/sys/fs/ext2fs/ext2_extern.h
+++ b/sys/fs/ext2fs/ext2_extern.h
@@ -51,6 +51,7 @@ struct vnode;
int ext2_add_entry(struct vnode *, struct ext2fs_direct_2 *);
int ext2_alloc(struct inode *, daddr_t, e4fs_daddr_t, int,
struct ucred *, e4fs_daddr_t *);
+daddr_t ext2_allocfacl(struct inode *ip);
int ext2_balloc(struct inode *,
e2fs_lbn_t, int, struct ucred *, struct buf **, int);
int ext2_blkatoff(struct vnode *, off_t, char **, struct buf **);
diff --git a/sys/fs/ext2fs/ext2_inode.c b/sys/fs/ext2fs/ext2_inode.c
index 6df911e..ff24fda 100644
--- a/sys/fs/ext2fs/ext2_inode.c
+++ b/sys/fs/ext2fs/ext2_inode.c
@@ -53,6 +53,7 @@
#include <fs/ext2fs/ext2fs.h>
#include <fs/ext2fs/fs.h>
#include <fs/ext2fs/ext2_extern.h>
+#include <fs/ext2fs/ext2_extattr.h>
static int ext2_indirtrunc(struct inode *, daddr_t, daddr_t,
daddr_t, int, e4fs_daddr_t *);
@@ -487,6 +488,7 @@ ext2_inactive(struct vop_inactive_args *ap)
if (ip->i_mode == 0)
goto out;
if (ip->i_nlink <= 0) {
+ ext2_extattr_free(ip);
error = ext2_truncate(vp, (off_t)0, 0, NOCRED, td);
ip->i_rdev = 0;
mode = ip->i_mode;
diff --git a/sys/fs/ext2fs/ext2_inode_cnv.c b/sys/fs/ext2fs/ext2_inode_cnv.c
index 3353947..47e4f7a 100644
--- a/sys/fs/ext2fs/ext2_inode_cnv.c
+++ b/sys/fs/ext2fs/ext2_inode_cnv.c
@@ -51,8 +51,8 @@ ext2_print_inode(struct inode *in)
printf("Inode: %5ju", (uintmax_t)in->i_number);
printf( /* "Inode: %5d" */
- " Type: %10s Mode: 0x%o Flags: 0x%x Version: %d\n",
- "n/a", in->i_mode, in->i_flags, in->i_gen);
+ " Type: %10s Mode: 0x%o Flags: 0x%x Version: %d acl: 0x%llx\n",
+ "n/a", in->i_mode, in->i_flags, in->i_gen, in->i_facl);
printf("User: %5u Group: %5u Size: %ju\n",
in->i_uid, in->i_gid, (uintmax_t)in->i_size);
printf("Links: %3d Blockcount: %ju\n",
@@ -114,8 +114,10 @@ ext2_ei2i(struct ext2fs_dinode *ei, struct inode *ip)
ip->i_flag |= (ei->e2di_flags & EXT3_INDEX) ? IN_E3INDEX : 0;
ip->i_flag |= (ei->e2di_flags & EXT4_EXTENTS) ? IN_E4EXTENTS : 0;
ip->i_blocks = ei->e2di_nblock;
+ ip->i_facl = ei->e2di_facl;
if (E2DI_HAS_HUGE_FILE(ip)) {
ip->i_blocks |= (uint64_t)ei->e2di_nblock_high << 32;
+ ip->i_facl |= (uint64_t)ei->e2di_facl_high << 32;
if (ei->e2di_flags & EXT4_HUGE_FILE)
ip->i_blocks = fsbtodb(ip->i_e2fs, ip->i_blocks);
}
@@ -167,6 +169,8 @@ ext2_i2ei(struct inode *ip, struct ext2fs_dinode *ei)
ei->e2di_flags |= (ip->i_flag & IN_E4EXTENTS) ? EXT4_EXTENTS : 0;
ei->e2di_nblock = ip->i_blocks & 0xffffffff;
ei->e2di_nblock_high = ip->i_blocks >> 32 & 0xffff;
+ ei->e2di_facl = ip->i_facl & 0xffffffff;
+ ei->e2di_facl_high = ip->i_facl >> 32 & 0xffff;
ei->e2di_gen = ip->i_gen;
ei->e2di_uid = ip->i_uid & 0xffff;
ei->e2di_uid_high = ip->i_uid >> 16 & 0xffff;
diff --git a/sys/fs/ext2fs/ext2_vnops.c b/sys/fs/ext2fs/ext2_vnops.c
index 8495fc8..353766c 100644
--- a/sys/fs/ext2fs/ext2_vnops.c
+++ b/sys/fs/ext2fs/ext2_vnops.c
@@ -64,6 +64,7 @@
#include <sys/event.h>
#include <sys/conf.h>
#include <sys/file.h>
+#include <sys/extattr.h>
#include <vm/vm.h>
#include <vm/vm_param.h>
@@ -79,11 +80,13 @@
#include <fs/ext2fs/fs.h>
#include <fs/ext2fs/inode.h>
+#include <fs/ext2fs/ext2_acl.h>
#include <fs/ext2fs/ext2_extern.h>
#include <fs/ext2fs/ext2fs.h>
#include <fs/ext2fs/ext2_dinode.h>
#include <fs/ext2fs/ext2_dir.h>
#include <fs/ext2fs/ext2_mount.h>
+#include <fs/ext2fs/ext2_extattr.h>
static int ext2_makeinode(int mode, struct vnode *, struct vnode **, struct componentname *);
static void ext2_itimes_locked(struct vnode *);
@@ -114,6 +117,10 @@ static vop_setattr_t ext2_setattr;
static vop_strategy_t ext2_strategy;
static vop_symlink_t ext2_symlink;
static vop_write_t ext2_write;
+static vop_deleteextattr_t ext2_deleteextattr;
+static vop_getextattr_t ext2_getextattr;
+static vop_listextattr_t ext2_listextattr;
+static vop_setextattr_t ext2_setextattr;
static vop_vptofh_t ext2_vptofh;
static vop_close_t ext2fifo_close;
static vop_kqfilter_t ext2fifo_kqfilter;
@@ -152,6 +159,15 @@ struct vop_vector ext2_vnodeops = {
.vop_strategy = ext2_strategy,
.vop_symlink = ext2_symlink,
.vop_write = ext2_write,
+ .vop_deleteextattr = ext2_deleteextattr,
+ .vop_getextattr = ext2_getextattr,
+ .vop_listextattr = ext2_listextattr,
+ .vop_setextattr = ext2_setextattr,
+#ifdef UFS_ACL
+ .vop_getacl = ext2_getacl,
+ .vop_setacl = ext2_setacl,
+ .vop_aclcheck = ext2_aclcheck,
+#endif /* UFS_ACL */
.vop_vptofh = ext2_vptofh,
};
@@ -1072,6 +1088,153 @@ out:
return (error);
}
+#ifdef UFS_ACL
+static int
+ext2_do_posix1e_acl_inheritance_dir(struct vnode *dvp, struct vnode *tvp,
+ mode_t dmode, struct ucred *cred, struct thread *td)
+{
+ int error;
+ struct inode *ip = VTOI(tvp);
+ struct acl *dacl, *acl;
+
+ acl = acl_alloc(M_WAITOK);
+ dacl = acl_alloc(M_WAITOK);
+
+ /*
+ * Retrieve default ACL from parent, if any.
+ */
+ error = VOP_GETACL(dvp, ACL_TYPE_DEFAULT, acl, cred, td);
+ switch (error) {
+ case 0:
+ /*
+ * Retrieved a default ACL, so merge mode and ACL if
+ * necessary. If the ACL is empty, fall through to
+ * the "not defined or available" case.
+ */
+ if (acl->acl_cnt != 0) {
+ dmode = acl_posix1e_newfilemode(dmode, acl);
+ ip->i_mode = dmode;
+ *dacl = *acl;
+ ext2_sync_acl_from_inode(ip, acl);
+ break;
+ }
+ /* FALLTHROUGH */
+
+ case EOPNOTSUPP:
+ /*
+ * Just use the mode as-is.
+ */
+ ip->i_mode = dmode;
+ error = 0;
+ goto out;
+
+ default:
+ goto out;
+ }
+
+ error = VOP_SETACL(tvp, ACL_TYPE_ACCESS, acl, cred, td);
+ if (error == 0)
+ error = VOP_SETACL(tvp, ACL_TYPE_DEFAULT, dacl, cred, td);
+ switch (error) {
+ case 0:
+ break;
+
+ case EOPNOTSUPP:
+ /*
+ * XXX: This should not happen, as EOPNOTSUPP above
+ * was supposed to free acl.
+ */
+#ifdef DEBUG
+ printf("ext2_mkdir: VOP_GETACL() but no VOP_SETACL()\n");
+#endif /* DEBUG */
+ break;
+
+ default:
+ goto out;
+ }
+
+out:
+ acl_free(acl);
+ acl_free(dacl);
+
+ return (error);
+}
+
+static int
+ext2_do_posix1e_acl_inheritance_file(struct vnode *dvp, struct vnode *tvp,
+ mode_t mode, struct ucred *cred, struct thread *td)
+{
+ int error;
+ struct inode *ip = VTOI(tvp);
+ struct acl *acl;
+
+ acl = acl_alloc(M_WAITOK);
+
+ /*
+ * Retrieve default ACL for parent, if any.
+ */
+ error = VOP_GETACL(dvp, ACL_TYPE_DEFAULT, acl, cred, td);
+ switch (error) {
+ case 0:
+ /*
+ * Retrieved a default ACL, so merge mode and ACL if
+ * necessary.
+ */
+ if (acl->acl_cnt != 0) {
+ /*
+ * Two possible ways for default ACL to not
+ * be present. First, the EA can be
+ * undefined, or second, the default ACL can
+ * be blank. If it's blank, fall through to
+ * the it's not defined case.
+ */
+ mode = acl_posix1e_newfilemode(mode, acl);
+ ip->i_mode = mode;
+ ext2_sync_acl_from_inode(ip, acl);
+ break;
+ }
+ /* FALLTHROUGH */
+
+ case EOPNOTSUPP:
+ /*
+ * Just use the mode as-is.
+ */
+ ip->i_mode = mode;
+ error = 0;
+ goto out;
+
+ default:
+ goto out;
+ }
+
+ error = VOP_SETACL(tvp, ACL_TYPE_ACCESS, acl, cred, td);
+ switch (error) {
+ case 0:
+ break;
+
+ case EOPNOTSUPP:
+ /*
+ * XXX: This should not happen, as EOPNOTSUPP above was
+ * supposed to free acl.
+ */
+ printf("ufs_do_posix1e_acl_inheritance_file: VOP_GETACL() "
+ "but no VOP_SETACL()\n");
+ /* panic("ufs_do_posix1e_acl_inheritance_file: VOP_GETACL() "
+ "but no VOP_SETACL()"); */
+ break;
+
+ default:
+ goto out;
+ }
+
+out:
+ acl_free(acl);
+
+ return (error);
+}
+
+#endif /* UFS_ACL */
+
/*
* Mkdir system call
*/
@@ -1181,6 +1344,16 @@ ext2_mkdir(struct vop_mkdir_args *ap)
ip->i_flag |= IN_CHANGE;
}
+#ifdef UFS_ACL
+ if (dvp->v_mount->mnt_flag & MNT_ACLS) {
+ error = ext2_do_posix1e_acl_inheritance_dir(dvp, tvp, dmode,
+ cnp->cn_cred, cnp->cn_thread);
+ if (error)
+ goto bad;
+ }
+
+#endif /* UFS_ACL */
+
/* Directory set up, now install its entry in the parent directory. */
error = ext2_direnter(ip, dvp, cnp);
if (error) {
@@ -1435,6 +1608,22 @@ ext2_pathconf(struct vop_pathconf_args *ap)
case _PC_NO_TRUNC:
*ap->a_retval = 1;
break;
+
+#ifdef UFS_ACL
+ case _PC_ACL_EXTENDED:
+ if (ap->a_vp->v_mount->mnt_flag & MNT_ACLS)
+ *ap->a_retval = 1;
+ else
+ *ap->a_retval = 0;
+ break;
+ case _PC_ACL_PATH_MAX:
+ if (ap->a_vp->v_mount->mnt_flag & MNT_ACLS)
+ *ap->a_retval = ACL_MAX_ENTRIES;
+ else
+ *ap->a_retval = 3;
+ break;
+#endif /* UFS_ACL */
+
case _PC_MIN_HOLE_SIZE:
*ap->a_retval = ap->a_vp->v_mount->mnt_stat.f_iosize;
break;
@@ -1479,6 +1668,169 @@ ext2_pathconf(struct vop_pathconf_args *ap)
}
/*
+ * Vnode operation to remove a named attribute.
+ */
+static int
+ext2_deleteextattr(struct vop_deleteextattr_args *ap)
+{
+ struct inode *ip;
+ struct m_ext2fs *fs;
+ int error;
+
+ ip = VTOI(ap->a_vp);
+ fs = ip->i_e2fs;
+
+ if (!EXT2_HAS_COMPAT_FEATURE(ip->i_e2fs, EXT2F_COMPAT_EXT_ATTR))
+ return (EOPNOTSUPP);
+
+ if (ap->a_vp->v_type == VCHR || ap->a_vp->v_type == VBLK)
+ return (EOPNOTSUPP);
+
+ error = extattr_check_cred(ap->a_vp, ap->a_attrnamespace,
+ ap->a_cred, ap->a_td, VWRITE);
+ if (error)
+ return (error);
+
+ error = ENOATTR;
+
+ if (EXT2_INODE_SIZE(fs) != E2FS_REV0_INODE_SIZE) {
+ error = ext2_extattr_inode_delete(ip, ap->a_attrnamespace, ap->a_name);
+ if (error != ENOATTR)
+ return (error);
+ }
+
+ if (ip->i_facl)
+ error = ext2_extattr_block_delete(ip, ap->a_attrnamespace, ap->a_name);
+
+ return (error);
+}
+
+/*
+ * Vnode operation to retrieve a named extended attribute.
+ */
+static int
+ext2_getextattr(struct vop_getextattr_args *ap)
+{
+ struct inode *ip;
+ struct m_ext2fs *fs;
+ int error;
+
+ ip = VTOI(ap->a_vp);
+ fs = ip->i_e2fs;
+
+ if (!EXT2_HAS_COMPAT_FEATURE(ip->i_e2fs, EXT2F_COMPAT_EXT_ATTR))
+ return (EOPNOTSUPP);
+
+ if (ap->a_vp->v_type == VCHR || ap->a_vp->v_type == VBLK)
+ return (EOPNOTSUPP);
+
+ error = extattr_check_cred(ap->a_vp, ap->a_attrnamespace,
+ ap->a_cred, ap->a_td, VREAD);
+ if (error)
+ return (error);
+
+ if (ap->a_size != NULL)
+ *ap->a_size = 0;
+
+ error = ENOATTR;
+
+ if (EXT2_INODE_SIZE(fs) != E2FS_REV0_INODE_SIZE) {
+ error = ext2_extattr_inode_get(ip, ap->a_attrnamespace,
+ ap->a_name, ap->a_uio, ap->a_size);
+ if (error != ENOATTR)
+ return (error);
+ }
+
+ if (ip->i_facl)
+ error = ext2_extattr_block_get(ip, ap->a_attrnamespace,
+ ap->a_name, ap->a_uio, ap->a_size);
+
+ return (error);
+}
+
+/*
+ * Vnode operation to retrieve extended attributes on a vnode.
+ */
+static int
+ext2_listextattr(struct vop_listextattr_args *ap)
+{
+ struct inode *ip;
+ struct m_ext2fs *fs;
+ int error;
+
+ ip = VTOI(ap->a_vp);
+ fs = ip->i_e2fs;
+
+ if (!EXT2_HAS_COMPAT_FEATURE(ip->i_e2fs, EXT2F_COMPAT_EXT_ATTR))
+ return (EOPNOTSUPP);
+
+ if (ap->a_vp->v_type == VCHR || ap->a_vp->v_type == VBLK)
+ return (EOPNOTSUPP);
+
+ error = extattr_check_cred(ap->a_vp, ap->a_attrnamespace,
+ ap->a_cred, ap->a_td, VREAD);
+ if (error)
+ return (error);
+
+ if (ap->a_size != NULL)
+ *ap->a_size = 0;
+
+ if (EXT2_INODE_SIZE(fs) != E2FS_REV0_INODE_SIZE) {
+ error = ext2_extattr_inode_list(ip, ap->a_attrnamespace,
+ ap->a_uio, ap->a_size);
+ if (error)
+ return (error);
+ }
+
+ if (ip->i_facl)
+ error = ext2_extattr_block_list(ip, ap->a_attrnamespace,
+ ap->a_uio, ap->a_size);
+
+ return (error);
+}
+
+/*
+ * Vnode operation to set a named attribute.
+ */
+static int
+ext2_setextattr(struct vop_setextattr_args *ap)
+{
+ struct inode *ip;
+ struct m_ext2fs *fs;
+ int error;
+
+ ip = VTOI(ap->a_vp);
+ fs = ip->i_e2fs;
+
+ if (!EXT2_HAS_COMPAT_FEATURE(ip->i_e2fs, EXT2F_COMPAT_EXT_ATTR))
+ return (EOPNOTSUPP);
+
+ if (ap->a_vp->v_type == VCHR || ap->a_vp->v_type == VBLK)
+ return (EOPNOTSUPP);
+
+ error = extattr_check_cred(ap->a_vp, ap->a_attrnamespace,
+ ap->a_cred, ap->a_td, VWRITE);
+ if (error)
+ return (error);
+
+ error = ext2_extattr_valid_attrname(ap->a_attrnamespace, ap->a_name);
+ if (error)
+ return (error);
+
+ if (EXT2_INODE_SIZE(fs) != E2FS_REV0_INODE_SIZE) {
+ error = ext2_extattr_inode_set(ip, ap->a_attrnamespace,
+ ap->a_name, ap->a_uio);
+ if (error != ENOSPC)
+ return (error);
+ }
+
+ error = ext2_extattr_block_set(ip, ap->a_attrnamespace,
+ ap->a_name, ap->a_uio);
+
+ return (error);
+}
+
+/*
* Vnode pointer to File handle
*/
/* ARGSUSED */
@@ -1585,6 +1937,16 @@ ext2_makeinode(int mode, struct vnode *dvp, struct vnode **vpp,
error = ext2_update(tvp, !DOINGASYNC(tvp));
if (error)
goto bad;
+
+#ifdef UFS_ACL
+ if (dvp->v_mount->mnt_flag & MNT_ACLS) {
+ error = ext2_do_posix1e_acl_inheritance_file(dvp, tvp, mode,
+ cnp->cn_cred, cnp->cn_thread);
+ if (error)
+ goto bad;
+ }
+#endif /* UFS_ACL */
+
error = ext2_direnter(ip, dvp, cnp);
if (error)
goto bad;
diff --git a/sys/fs/ext2fs/ext2fs.h b/sys/fs/ext2fs/ext2fs.h
index 400912e..b499388 100644
--- a/sys/fs/ext2fs/ext2fs.h
+++ b/sys/fs/ext2fs/ext2fs.h
@@ -204,6 +204,7 @@ struct csum {
*/
#define EXT2F_COMPAT_PREALLOC 0x0001
#define EXT2F_COMPAT_HASJOURNAL 0x0004
+#define EXT2F_COMPAT_EXT_ATTR 0x0008
#define EXT2F_COMPAT_RESIZE 0x0010
#define EXT2F_COMPAT_DIRHASHINDEX 0x0020
#define EXT2F_COMPAT_SPARSESUPER2 0x0200
diff --git a/sys/fs/ext2fs/inode.h b/sys/fs/ext2fs/inode.h
index 53300ce..1468864 100644
--- a/sys/fs/ext2fs/inode.h
+++ b/sys/fs/ext2fs/inode.h
@@ -105,6 +105,7 @@ struct inode {
int32_t i_ctimensec; /* Last inode change time. */
int32_t i_birthnsec; /* Inode creation time. */
uint32_t i_gen; /* Generation number. */
+ uint64_t i_facl; /* EA block number. */
uint32_t i_flags; /* Status flags (chflags). */
uint32_t i_db[NDADDR]; /* Direct disk blocks. */
uint32_t i_ib[NIADDR]; /* Indirect disk blocks. */
diff --git a/sys/modules/ext2fs/Makefile b/sys/modules/ext2fs/Makefile
index 70814bf..64164798 100644
--- a/sys/modules/ext2fs/Makefile
+++ b/sys/modules/ext2fs/Makefile
@@ -3,8 +3,8 @@
.PATH: ${SRCTOP}/sys/fs/ext2fs
KMOD= ext2fs
SRCS= opt_ddb.h opt_directio.h opt_quota.h opt_suiddir.h vnode_if.h \
- ext2_alloc.c ext2_balloc.c ext2_bmap.c ext2_extents.c ext2_hash.c \
- ext2_htree.c ext2_inode.c ext2_inode_cnv.c ext2_lookup.c ext2_subr.c \
- ext2_vfsops.c ext2_vnops.c
+ ext2_acl.c ext2_alloc.c ext2_balloc.c ext2_bmap.c ext2_extattr.c \
+ ext2_extents.c ext2_hash.c ext2_htree.c ext2_inode.c ext2_inode_cnv.c \
+ ext2_lookup.c ext2_subr.c ext2_vfsops.c ext2_vnops.c
.include <bsd.kmod.mk>
OpenPOWER on IntegriCloud