Add extended attributes support to fuse kernel module.

Author:         kem
Reviewed by:    cem, pfg (mentor)
Approved by:    pfg (mentor)
MFC after:      2 weeks

Differential Revision: https://reviews.freebsd.org/D12485
This commit is contained in:
fsu 2017-10-14 19:02:52 +00:00
parent 38a9346b2f
commit 9658e6ca36
2 changed files with 393 additions and 10 deletions

View File

@ -636,23 +636,20 @@ fuse_body_audit(struct fuse_ticket *ftick, size_t blen)
break;
case FUSE_SETXATTR:
panic("FUSE_SETXATTR implementor has forgotten to define a"
" response body format check");
err = (blen == 0) ? 0 : EINVAL;
break;
case FUSE_GETXATTR:
panic("FUSE_GETXATTR implementor has forgotten to define a"
" response body format check");
break;
case FUSE_LISTXATTR:
panic("FUSE_LISTXATTR implementor has forgotten to define a"
" response body format check");
/*
* These can have varying response lengths, and 0 length
* isn't necessarily invalid.
*/
err = 0;
break;
case FUSE_REMOVEXATTR:
panic("FUSE_REMOVEXATTR implementor has forgotten to define a"
" response body format check");
err = (blen == 0) ? 0 : EINVAL;
break;
case FUSE_FLUSH:

View File

@ -73,6 +73,7 @@ __FBSDID("$FreeBSD$");
#include <sys/mount.h>
#include <sys/vnode.h>
#include <sys/namei.h>
#include <sys/extattr.h>
#include <sys/stat.h>
#include <sys/unistd.h>
#include <sys/filedesc.h>
@ -112,10 +113,13 @@ __FBSDID("$FreeBSD$");
static vop_access_t fuse_vnop_access;
static vop_close_t fuse_vnop_close;
static vop_create_t fuse_vnop_create;
static vop_deleteextattr_t fuse_vnop_deleteextattr;
static vop_fsync_t fuse_vnop_fsync;
static vop_getattr_t fuse_vnop_getattr;
static vop_getextattr_t fuse_vnop_getextattr;
static vop_inactive_t fuse_vnop_inactive;
static vop_link_t fuse_vnop_link;
static vop_listextattr_t fuse_vnop_listextattr;
static vop_lookup_t fuse_vnop_lookup;
static vop_mkdir_t fuse_vnop_mkdir;
static vop_mknod_t fuse_vnop_mknod;
@ -128,6 +132,7 @@ static vop_remove_t fuse_vnop_remove;
static vop_rename_t fuse_vnop_rename;
static vop_rmdir_t fuse_vnop_rmdir;
static vop_setattr_t fuse_vnop_setattr;
static vop_setextattr_t fuse_vnop_setextattr;
static vop_strategy_t fuse_vnop_strategy;
static vop_symlink_t fuse_vnop_symlink;
static vop_write_t fuse_vnop_write;
@ -140,10 +145,13 @@ struct vop_vector fuse_vnops = {
.vop_access = fuse_vnop_access,
.vop_close = fuse_vnop_close,
.vop_create = fuse_vnop_create,
.vop_deleteextattr = fuse_vnop_deleteextattr,
.vop_fsync = fuse_vnop_fsync,
.vop_getattr = fuse_vnop_getattr,
.vop_getextattr = fuse_vnop_getextattr,
.vop_inactive = fuse_vnop_inactive,
.vop_link = fuse_vnop_link,
.vop_listextattr = fuse_vnop_listextattr,
.vop_lookup = fuse_vnop_lookup,
.vop_mkdir = fuse_vnop_mkdir,
.vop_mknod = fuse_vnop_mknod,
@ -157,6 +165,7 @@ struct vop_vector fuse_vnops = {
.vop_rename = fuse_vnop_rename,
.vop_rmdir = fuse_vnop_rmdir,
.vop_setattr = fuse_vnop_setattr,
.vop_setextattr = fuse_vnop_setextattr,
.vop_strategy = fuse_vnop_strategy,
.vop_symlink = fuse_vnop_symlink,
.vop_write = fuse_vnop_write,
@ -1958,6 +1967,383 @@ fuse_vnop_putpages(struct vop_putpages_args *ap)
return rtvals[0];
}
static const char extattr_namespace_separator = '.';
/*
struct vop_getextattr_args {
struct vop_generic_args a_gen;
struct vnode *a_vp;
int a_attrnamespace;
const char *a_name;
struct uio *a_uio;
size_t *a_size;
struct ucred *a_cred;
struct thread *a_td;
};
*/
static int
fuse_vnop_getextattr(struct vop_getextattr_args *ap)
{
struct vnode *vp = ap->a_vp;
struct uio *uio = ap->a_uio;
struct fuse_dispatcher fdi = {0};
struct fuse_getxattr_in *get_xattr_in;
struct fuse_getxattr_out *get_xattr_out;
struct mount *mp = vnode_mount(vp);
char *prefix;
size_t len;
char *attr_str;
struct thread *td = ap->a_td;
struct ucred *cred = ap->a_cred;
int err = 0;
fuse_trace_printf_vnop();
if (fuse_isdeadfs(vp))
return ENXIO;
/* Default to looking for user attributes. */
if (ap->a_attrnamespace == EXTATTR_NAMESPACE_SYSTEM)
prefix = EXTATTR_NAMESPACE_SYSTEM_STRING;
else
prefix = EXTATTR_NAMESPACE_USER_STRING;
len = strlen(prefix) + sizeof(extattr_namespace_separator) +
strlen(ap->a_name) + 1;
fdisp_init(&fdi, len + sizeof(*get_xattr_in));
fdisp_make_vp(&fdi, FUSE_GETXATTR, vp, td, cred);
get_xattr_in = fdi.indata;
/*
* Check to see whether we're querying the available size or
* issuing the actual request. If we pass in 0, we get back struct
* fuse_getxattr_out. If we pass in a non-zero size, we get back
* that much data, without the struct fuse_getxattr_out header.
*/
if (ap->a_size != NULL)
get_xattr_in->size = 0;
else
get_xattr_in->size = uio->uio_resid;
attr_str = (char *)fdi.indata + sizeof(*get_xattr_in);
snprintf(attr_str, len, "%s%c%s", prefix, extattr_namespace_separator,
ap->a_name);
err = fdisp_wait_answ(&fdi);
if (err != 0) {
if (err == ENOSYS)
fsess_set_notimpl(mp, FUSE_GETXATTR);
debug_printf("getxattr: got err=%d from daemon\n", err);
goto out;
}
/*
* If we get to this point (i.e. no error), we should have a valid
* answer of some sort. i.e. non-zero iosize and a valid pointer.
*/
if ((fdi.answ == NULL) || (fdi.iosize == 0)) {
debug_printf("getxattr: err = 0, but answ = %p, iosize = %zu\n",
fdi.answ, fdi.iosize);
err = EINVAL;
goto out;
}
get_xattr_out = fdi.answ;
if (ap->a_size != NULL) {
*ap->a_size = get_xattr_out->size;
} else if (fdi.iosize > 0) {
err = uiomove(fdi.answ, fdi.iosize, uio);
} else {
err = EINVAL;
}
out:
fdisp_destroy(&fdi);
return (err);
}
/*
struct vop_setextattr_args {
struct vop_generic_args a_gen;
struct vnode *a_vp;
int a_attrnamespace;
const char *a_name;
struct uio *a_uio;
struct ucred *a_cred;
struct thread *a_td;
};
*/
static int
fuse_vnop_setextattr(struct vop_setextattr_args *ap)
{
struct vnode *vp = ap->a_vp;
struct uio *uio = ap->a_uio;
struct fuse_dispatcher fdi = {0};
struct fuse_setxattr_in *set_xattr_in;
struct mount *mp = vnode_mount(vp);
char *prefix;
size_t len;
char *attr_str;
struct thread *td = ap->a_td;
struct ucred *cred = ap->a_cred;
int err = 0;
fuse_trace_printf_vnop();
if (fuse_isdeadfs(vp))
return ENXIO;
/* Default to looking for user attributes. */
if (ap->a_attrnamespace == EXTATTR_NAMESPACE_SYSTEM)
prefix = EXTATTR_NAMESPACE_SYSTEM_STRING;
else
prefix = EXTATTR_NAMESPACE_USER_STRING;
len = strlen(prefix) + sizeof(extattr_namespace_separator) +
strlen(ap->a_name) + 1;
fdisp_init(&fdi, len + sizeof(*set_xattr_in) + uio->uio_resid);
fdisp_make_vp(&fdi, FUSE_SETXATTR, vp, td, cred);
set_xattr_in = fdi.indata;
set_xattr_in->size = uio->uio_resid;
attr_str = (char *)fdi.indata + sizeof(*set_xattr_in);
snprintf(attr_str, len, "%s%c%s", prefix, extattr_namespace_separator,
ap->a_name);
err = uiomove((char *)fdi.indata + sizeof(*set_xattr_in) + len,
uio->uio_resid, uio);
if (err != 0) {
debug_printf("setxattr: got error %d from uiomove\n", err);
goto out;
}
err = fdisp_wait_answ(&fdi);
if (err != 0) {
if (err == ENOSYS)
fsess_set_notimpl(mp, FUSE_SETXATTR);
debug_printf("setxattr: got err=%d from daemon\n", err);
goto out;
}
out:
fdisp_destroy(&fdi);
return (err);
}
/*
* The Linux / FUSE extended attribute list is simply a collection of
* NUL-terminated strings. The FreeBSD extended attribute list is a single
* byte length followed by a non-NUL terminated string. So, this allows
* conversion of the Linux / FUSE format to the FreeBSD format in place.
* Linux attribute names are reported with the namespace as a prefix (e.g.
* "user.attribute_name"), but in FreeBSD they are reported without the
* namespace prefix (e.g. "attribute_name"). So, we're going from:
*
* user.attr_name1\0user.attr_name2\0
*
* to:
*
* <num>attr_name1<num>attr_name2
*
* Where "<num>" is a single byte number of characters in the attribute name.
*
* Args:
* prefix - exattr namespace prefix string
* list, list_len - input list with namespace prefixes
* bsd_list, bsd_list_len - output list compatible with bsd vfs
*/
static int
fuse_xattrlist_convert(char *prefix, const char *list, int list_len,
char *bsd_list, int *bsd_list_len)
{
int len, pos, dist_to_next, prefix_len;
pos = 0;
*bsd_list_len = 0;
prefix_len = strlen(prefix);
while (pos < list_len && list[pos] != '\0') {
dist_to_next = strlen(&list[pos]) + 1;
if (bcmp(&list[pos], prefix, prefix_len) == 0 &&
list[pos + prefix_len] == extattr_namespace_separator) {
len = dist_to_next -
(prefix_len + sizeof(extattr_namespace_separator)) - 1;
if (len >= EXTATTR_MAXNAMELEN)
return (ENAMETOOLONG);
bsd_list[*bsd_list_len] = len;
memcpy(&bsd_list[*bsd_list_len + 1],
&list[pos + prefix_len +
sizeof(extattr_namespace_separator)], len);
*bsd_list_len += len + 1;
}
pos += dist_to_next;
}
return (0);
}
/*
struct vop_listextattr_args {
struct vop_generic_args a_gen;
struct vnode *a_vp;
int a_attrnamespace;
struct uio *a_uio;
size_t *a_size;
struct ucred *a_cred;
struct thread *a_td;
};
*/
static int
fuse_vnop_listextattr(struct vop_listextattr_args *ap)
{
struct vnode *vp = ap->a_vp;
struct uio *uio = ap->a_uio;
struct fuse_dispatcher fdi = {0};
struct fuse_getxattr_in *get_xattr_in;
struct fuse_getxattr_out *get_xattr_out;
struct mount *mp = vnode_mount(vp);
size_t len;
char *prefix;
char *attr_str;
char *bsd_list = NULL;
int bsd_list_len;
struct thread *td = ap->a_td;
struct ucred *cred = ap->a_cred;
int err = 0;
fuse_trace_printf_vnop();
if (fuse_isdeadfs(vp))
return ENXIO;
/*
* Add space for a NUL and the period separator if enabled.
* Default to looking for user attributes.
*/
if (ap->a_attrnamespace == EXTATTR_NAMESPACE_SYSTEM)
prefix = EXTATTR_NAMESPACE_SYSTEM_STRING;
else
prefix = EXTATTR_NAMESPACE_USER_STRING;
len = strlen(prefix) + sizeof(extattr_namespace_separator) + 1;
fdisp_init(&fdi, sizeof(*get_xattr_in) + len);
fdisp_make_vp(&fdi, FUSE_LISTXATTR, vp, td, cred);
get_xattr_in = fdi.indata;
if (ap->a_size != NULL)
get_xattr_in->size = 0;
else
get_xattr_in->size = uio->uio_resid + sizeof(*get_xattr_out);
attr_str = (char *)fdi.indata + sizeof(*get_xattr_in);
snprintf(attr_str, len, "%s%c", prefix, extattr_namespace_separator);
err = fdisp_wait_answ(&fdi);
if (err != 0) {
if (err == ENOSYS)
fsess_set_notimpl(mp, FUSE_LISTXATTR);
debug_printf("listextattr: got err=%d from daemon\n", err);
goto out;
}
if ((fdi.answ == NULL) || (fdi.iosize == 0)) {
err = EINVAL;
goto out;
}
get_xattr_out = fdi.answ;
if (ap->a_size != NULL) {
*ap->a_size = get_xattr_out->size;
} else if (fdi.iosize > 0) {
/*
* The Linux / FUSE attribute list format isn't the same
* as FreeBSD's format. So we need to transform it into
* FreeBSD's format before giving it to the user.
*/
bsd_list = malloc(fdi.iosize, M_TEMP, M_WAITOK);
err = fuse_xattrlist_convert(prefix, fdi.answ, fdi.iosize,
bsd_list, &bsd_list_len);
if (err != 0)
goto out;
err = uiomove(bsd_list, bsd_list_len, uio);
} else {
debug_printf("listextattr: returned iosize %zu for %s attribute list is "
"too small\n", fdi.iosize, prefix);
err = EINVAL;
}
out:
free(bsd_list, M_TEMP);
fdisp_destroy(&fdi);
return (err);
}
/*
struct vop_deleteextattr_args {
struct vop_generic_args a_gen;
struct vnode *a_vp;
int a_attrnamespace;
const char *a_name;
struct ucred *a_cred;
struct thread *a_td;
};
*/
static int
fuse_vnop_deleteextattr(struct vop_deleteextattr_args *ap)
{
struct vnode *vp = ap->a_vp;
struct fuse_dispatcher fdi = {0};
struct mount *mp = vnode_mount(vp);
char *prefix;
size_t len;
char *attr_str;
struct thread *td = ap->a_td;
struct ucred *cred = ap->a_cred;
int err;
fuse_trace_printf_vnop();
if (fuse_isdeadfs(vp))
return ENXIO;
/* Default to looking for user attributes. */
if (ap->a_attrnamespace == EXTATTR_NAMESPACE_SYSTEM)
prefix = EXTATTR_NAMESPACE_SYSTEM_STRING;
else
prefix = EXTATTR_NAMESPACE_USER_STRING;
len = strlen(prefix) + sizeof(extattr_namespace_separator) +
strlen(ap->a_name) + 1;
fdisp_init(&fdi, len);
fdisp_make_vp(&fdi, FUSE_REMOVEXATTR, vp, td, cred);
attr_str = fdi.indata;
snprintf(attr_str, len, "%s%c%s", prefix, extattr_namespace_separator,
ap->a_name);
err = fdisp_wait_answ(&fdi);
if (err != 0) {
if (err == ENOSYS)
fsess_set_notimpl(mp, FUSE_REMOVEXATTR);
debug_printf("removexattr: got err=%d from daemon\n", err);
}
fdisp_destroy(&fdi);
return (err);
}
/*
struct vnop_print_args {
struct vnode *a_vp;