aboutsummaryrefslogtreecommitdiffstats
path: root/fs/aufs/xattr.c
diff options
context:
space:
mode:
Diffstat (limited to 'fs/aufs/xattr.c')
-rw-r--r--fs/aufs/xattr.c356
1 files changed, 356 insertions, 0 deletions
diff --git a/fs/aufs/xattr.c b/fs/aufs/xattr.c
new file mode 100644
index 000000000000..b5ba7e32c574
--- /dev/null
+++ b/fs/aufs/xattr.c
@@ -0,0 +1,356 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2014-2020 Junjiro R. Okajima
+ *
+ * This program, aufs is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/*
+ * handling xattr functions
+ */
+
+#include <linux/fs.h>
+#include <linux/posix_acl_xattr.h>
+#include <linux/xattr.h>
+#include "aufs.h"
+
+static int au_xattr_ignore(int err, char *name, unsigned int ignore_flags)
+{
+ if (!ignore_flags)
+ goto out;
+ switch (err) {
+ case -ENOMEM:
+ case -EDQUOT:
+ goto out;
+ }
+
+ if ((ignore_flags & AuBrAttr_ICEX) == AuBrAttr_ICEX) {
+ err = 0;
+ goto out;
+ }
+
+#define cmp(brattr, prefix) do { \
+ if (!strncmp(name, XATTR_##prefix##_PREFIX, \
+ XATTR_##prefix##_PREFIX_LEN)) { \
+ if (ignore_flags & AuBrAttr_ICEX_##brattr) \
+ err = 0; \
+ goto out; \
+ } \
+ } while (0)
+
+ cmp(SEC, SECURITY);
+ cmp(SYS, SYSTEM);
+ cmp(TR, TRUSTED);
+ cmp(USR, USER);
+#undef cmp
+
+ if (ignore_flags & AuBrAttr_ICEX_OTH)
+ err = 0;
+
+out:
+ return err;
+}
+
+static const int au_xattr_out_of_list = AuBrAttr_ICEX_OTH << 1;
+
+static int au_do_cpup_xattr(struct dentry *h_dst, struct dentry *h_src,
+ char *name, char **buf, unsigned int ignore_flags,
+ unsigned int verbose)
+{
+ int err;
+ ssize_t ssz;
+ struct inode *h_idst;
+
+ ssz = vfs_getxattr_alloc(h_src, name, buf, 0, GFP_NOFS);
+ err = ssz;
+ if (unlikely(err <= 0)) {
+ if (err == -ENODATA
+ || (err == -EOPNOTSUPP
+ && ((ignore_flags & au_xattr_out_of_list)
+ || (au_test_nfs_noacl(d_inode(h_src))
+ && (!strcmp(name, XATTR_NAME_POSIX_ACL_ACCESS)
+ || !strcmp(name,
+ XATTR_NAME_POSIX_ACL_DEFAULT))))
+ ))
+ err = 0;
+ if (err && (verbose || au_debug_test()))
+ pr_err("%s, err %d\n", name, err);
+ goto out;
+ }
+
+ /* unlock it temporary */
+ h_idst = d_inode(h_dst);
+ inode_unlock(h_idst);
+ err = vfsub_setxattr(h_dst, name, *buf, ssz, /*flags*/0);
+ inode_lock_nested(h_idst, AuLsc_I_CHILD2);
+ if (unlikely(err)) {
+ if (verbose || au_debug_test())
+ pr_err("%s, err %d\n", name, err);
+ err = au_xattr_ignore(err, name, ignore_flags);
+ }
+
+out:
+ return err;
+}
+
+int au_cpup_xattr(struct dentry *h_dst, struct dentry *h_src, int ignore_flags,
+ unsigned int verbose)
+{
+ int err, unlocked, acl_access, acl_default;
+ ssize_t ssz;
+ struct inode *h_isrc, *h_idst;
+ char *value, *p, *o, *e;
+
+ /* try stopping to update the source inode while we are referencing */
+ /* there should not be the parent-child relationship between them */
+ h_isrc = d_inode(h_src);
+ h_idst = d_inode(h_dst);
+ inode_unlock(h_idst);
+ inode_lock_shared_nested(h_isrc, AuLsc_I_CHILD);
+ inode_lock_nested(h_idst, AuLsc_I_CHILD2);
+ unlocked = 0;
+
+ /* some filesystems don't list POSIX ACL, for example tmpfs */
+ ssz = vfs_listxattr(h_src, NULL, 0);
+ err = ssz;
+ if (unlikely(err < 0)) {
+ AuTraceErr(err);
+ if (err == -ENODATA
+ || err == -EOPNOTSUPP)
+ err = 0; /* ignore */
+ goto out;
+ }
+
+ err = 0;
+ p = NULL;
+ o = NULL;
+ if (ssz) {
+ err = -ENOMEM;
+ p = kmalloc(ssz, GFP_NOFS);
+ o = p;
+ if (unlikely(!p))
+ goto out;
+ err = vfs_listxattr(h_src, p, ssz);
+ }
+ inode_unlock_shared(h_isrc);
+ unlocked = 1;
+ AuDbg("err %d, ssz %zd\n", err, ssz);
+ if (unlikely(err < 0))
+ goto out_free;
+
+ err = 0;
+ e = p + ssz;
+ value = NULL;
+ acl_access = 0;
+ acl_default = 0;
+ while (!err && p < e) {
+ acl_access |= !strncmp(p, XATTR_NAME_POSIX_ACL_ACCESS,
+ sizeof(XATTR_NAME_POSIX_ACL_ACCESS) - 1);
+ acl_default |= !strncmp(p, XATTR_NAME_POSIX_ACL_DEFAULT,
+ sizeof(XATTR_NAME_POSIX_ACL_DEFAULT)
+ - 1);
+ err = au_do_cpup_xattr(h_dst, h_src, p, &value, ignore_flags,
+ verbose);
+ p += strlen(p) + 1;
+ }
+ AuTraceErr(err);
+ ignore_flags |= au_xattr_out_of_list;
+ if (!err && !acl_access) {
+ err = au_do_cpup_xattr(h_dst, h_src,
+ XATTR_NAME_POSIX_ACL_ACCESS, &value,
+ ignore_flags, verbose);
+ AuTraceErr(err);
+ }
+ if (!err && !acl_default) {
+ err = au_do_cpup_xattr(h_dst, h_src,
+ XATTR_NAME_POSIX_ACL_DEFAULT, &value,
+ ignore_flags, verbose);
+ AuTraceErr(err);
+ }
+
+ au_kfree_try_rcu(value);
+
+out_free:
+ au_kfree_try_rcu(o);
+out:
+ if (!unlocked)
+ inode_unlock_shared(h_isrc);
+ AuTraceErr(err);
+ return err;
+}
+
+/* ---------------------------------------------------------------------- */
+
+static int au_smack_reentering(struct super_block *sb)
+{
+#if IS_ENABLED(CONFIG_SECURITY_SMACK)
+ /*
+ * as a part of lookup, smack_d_instantiate() is called, and it calls
+ * i_op->getxattr(). ouch.
+ */
+ return si_pid_test(sb);
+#else
+ return 0;
+#endif
+}
+
+enum {
+ AU_XATTR_LIST,
+ AU_XATTR_GET
+};
+
+struct au_lgxattr {
+ int type;
+ union {
+ struct {
+ char *list;
+ size_t size;
+ } list;
+ struct {
+ const char *name;
+ void *value;
+ size_t size;
+ } get;
+ } u;
+};
+
+static ssize_t au_lgxattr(struct dentry *dentry, struct au_lgxattr *arg)
+{
+ ssize_t err;
+ int reenter;
+ struct path h_path;
+ struct super_block *sb;
+
+ sb = dentry->d_sb;
+ reenter = au_smack_reentering(sb);
+ if (!reenter) {
+ err = si_read_lock(sb, AuLock_FLUSH | AuLock_NOPLM);
+ if (unlikely(err))
+ goto out;
+ }
+ err = au_h_path_getattr(dentry, /*force*/1, &h_path, reenter);
+ if (unlikely(err))
+ goto out_si;
+ if (unlikely(!h_path.dentry))
+ /* illegally overlapped or something */
+ goto out_di; /* pretending success */
+
+ /* always topmost entry only */
+ switch (arg->type) {
+ case AU_XATTR_LIST:
+ err = vfs_listxattr(h_path.dentry,
+ arg->u.list.list, arg->u.list.size);
+ break;
+ case AU_XATTR_GET:
+ AuDebugOn(d_is_negative(h_path.dentry));
+ err = vfs_getxattr(h_path.dentry,
+ arg->u.get.name, arg->u.get.value,
+ arg->u.get.size);
+ break;
+ }
+
+out_di:
+ if (!reenter)
+ di_read_unlock(dentry, AuLock_IR);
+out_si:
+ if (!reenter)
+ si_read_unlock(sb);
+out:
+ AuTraceErr(err);
+ return err;
+}
+
+ssize_t aufs_listxattr(struct dentry *dentry, char *list, size_t size)
+{
+ struct au_lgxattr arg = {
+ .type = AU_XATTR_LIST,
+ .u.list = {
+ .list = list,
+ .size = size
+ },
+ };
+
+ return au_lgxattr(dentry, &arg);
+}
+
+static ssize_t au_getxattr(struct dentry *dentry,
+ struct inode *inode __maybe_unused,
+ const char *name, void *value, size_t size)
+{
+ struct au_lgxattr arg = {
+ .type = AU_XATTR_GET,
+ .u.get = {
+ .name = name,
+ .value = value,
+ .size = size
+ },
+ };
+
+ return au_lgxattr(dentry, &arg);
+}
+
+static int au_setxattr(struct dentry *dentry, struct inode *inode,
+ const char *name, const void *value, size_t size,
+ int flags)
+{
+ struct au_sxattr arg = {
+ .type = AU_XATTR_SET,
+ .u.set = {
+ .name = name,
+ .value = value,
+ .size = size,
+ .flags = flags
+ },
+ };
+
+ return au_sxattr(dentry, inode, &arg);
+}
+
+/* ---------------------------------------------------------------------- */
+
+static int au_xattr_get(const struct xattr_handler *handler,
+ struct dentry *dentry, struct inode *inode,
+ const char *name, void *buffer, size_t size)
+{
+ return au_getxattr(dentry, inode, name, buffer, size);
+}
+
+static int au_xattr_set(const struct xattr_handler *handler,
+ struct dentry *dentry, struct inode *inode,
+ const char *name, const void *value, size_t size,
+ int flags)
+{
+ return au_setxattr(dentry, inode, name, value, size, flags);
+}
+
+static const struct xattr_handler au_xattr_handler = {
+ .name = "",
+ .prefix = "",
+ .get = au_xattr_get,
+ .set = au_xattr_set
+};
+
+static const struct xattr_handler *au_xattr_handlers[] = {
+#ifdef CONFIG_FS_POSIX_ACL
+ &posix_acl_access_xattr_handler,
+ &posix_acl_default_xattr_handler,
+#endif
+ &au_xattr_handler, /* must be last */
+ NULL
+};
+
+void au_xattr_init(struct super_block *sb)
+{
+ sb->s_xattr = au_xattr_handlers;
+}