/*
* 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