/* * Copyright (C) 2014-2015 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 . */ /* * handling xattr functions */ #include #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); mutex_unlock(&h_idst->i_mutex); err = vfsub_setxattr(h_dst, name, *buf, ssz, /*flags*/0); mutex_lock_nested(&h_idst->i_mutex, 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); mutex_unlock(&h_idst->i_mutex); mutex_lock_nested(&h_isrc->i_mutex, AuLsc_I_CHILD); mutex_lock_nested(&h_idst->i_mutex, 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); } mutex_unlock(&h_isrc->i_mutex); 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); } kfree(value); out_free: kfree(o); out: if (!unlocked) mutex_unlock(&h_isrc->i_mutex); AuTraceErr(err); return err; } /* ---------------------------------------------------------------------- */ 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; struct path h_path; struct super_block *sb; sb = dentry->d_sb; err = si_read_lock(sb, AuLock_FLUSH | AuLock_NOPLM); if (unlikely(err)) goto out; err = au_h_path_getattr(dentry, /*force*/1, &h_path); 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: err = vfs_getxattr(h_path.dentry, arg->u.get.name, arg->u.get.value, arg->u.get.size); break; } out_di: di_read_unlock(dentry, AuLock_IR); out_si: 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); } ssize_t aufs_getxattr(struct dentry *dentry, 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); } int aufs_setxattr(struct dentry *dentry, const char *name, const void *value, size_t size, int flags) { struct au_srxattr arg = { .type = AU_XATTR_SET, .u.set = { .name = name, .value = value, .size = size, .flags = flags }, }; return au_srxattr(dentry, &arg); } int aufs_removexattr(struct dentry *dentry, const char *name) { struct au_srxattr arg = { .type = AU_XATTR_REMOVE, .u.remove = { .name = name }, }; return au_srxattr(dentry, &arg); } /* ---------------------------------------------------------------------- */ #if 0 static size_t au_xattr_list(struct dentry *dentry, char *list, size_t list_size, const char *name, size_t name_len, int type) { return aufs_listxattr(dentry, list, list_size); } static int au_xattr_get(struct dentry *dentry, const char *name, void *buffer, size_t size, int type) { return aufs_getxattr(dentry, name, buffer, size); } static int au_xattr_set(struct dentry *dentry, const char *name, const void *value, size_t size, int flags, int type) { return aufs_setxattr(dentry, name, value, size, flags); } static const struct xattr_handler au_xattr_handler = { /* no prefix, no flags */ .list = au_xattr_list, .get = au_xattr_get, .set = au_xattr_set /* why no remove? */ }; static const struct xattr_handler *au_xattr_handlers[] = { &au_xattr_handler }; void au_xattr_init(struct super_block *sb) { /* sb->s_xattr = au_xattr_handlers; */ } #endif