Index: sys/fs/fuse/fuse_ipc.c =================================================================== --- sys/fs/fuse/fuse_ipc.c +++ sys/fs/fuse/fuse_ipc.c @@ -636,23 +636,20 @@ 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: Index: sys/fs/fuse/fuse_vnops.c =================================================================== --- sys/fs/fuse/fuse_vnops.c +++ sys/fs/fuse/fuse_vnops.c @@ -73,6 +73,7 @@ #include #include #include +#include #include #include #include @@ -112,10 +113,13 @@ 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_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 @@ .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 @@ .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, @@ -180,6 +189,11 @@ SYSCTL_INT(_vfs_fuse, OID_AUTO, lookup_cache_enable, CTLFLAG_RW, &fuse_lookup_cache_enable, 0, ""); +int fuse_extattr_namespace_prefix_enable = 1; + +SYSCTL_INT(_vfs_fuse, OID_AUTO, extattr_namespace_prefix_enable, CTLFLAG_RW, + &fuse_extattr_namespace_prefix_enable, 0, "Add namespace prefix to extattr name"); + /* * XXX: This feature is highly experimental and can bring to instabilities, * needs revisiting before to be enabled by default. @@ -1959,6 +1973,431 @@ } /* + 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, prefix_len; + char *attr_str; + struct thread *td = ap->a_td; + struct ucred *cred = ap->a_cred; + int use_namespace_prefix, err = 0; + + fuse_trace_printf_vnop(); + + if (fuse_isdeadfs(vp)) + return ENXIO; + + /* String plus terminating NUL */ + len = strlen(ap->a_name) + 1; + use_namespace_prefix = fuse_extattr_namespace_prefix_enable; + + if (use_namespace_prefix) { + /* Default to looking for user attributes. */ + if (ap->a_attrnamespace == EXTATTR_NAMESPACE_SYSTEM) { + prefix_len = strlen(EXTATTR_NAMESPACE_SYSTEM_STRING) + 1; + prefix = EXTATTR_NAMESPACE_SYSTEM_STRING; + } else { + prefix_len = strlen(EXTATTR_NAMESPACE_USER_STRING) + 1; + prefix = EXTATTR_NAMESPACE_USER_STRING; + } + + len += prefix_len; + } + + 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); + if (use_namespace_prefix) + snprintf(attr_str, len, "%s.%s", prefix, ap->a_name); + else + snprintf(attr_str, len, "%s", 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, prefix_len; + char *attr_str; + struct thread *td = ap->a_td; + struct ucred *cred = ap->a_cred; + int use_namespace_prefix, err = 0; + + fuse_trace_printf_vnop(); + + if (fuse_isdeadfs(vp)) + return ENXIO; + + /* String plus terminating NUL */ + len = strlen(ap->a_name) + 1; + use_namespace_prefix = fuse_extattr_namespace_prefix_enable; + + if (use_namespace_prefix) { + /* Default to looking for user attributes. */ + if (ap->a_attrnamespace == EXTATTR_NAMESPACE_SYSTEM) { + prefix_len = strlen(EXTATTR_NAMESPACE_SYSTEM_STRING) + 1; + prefix = EXTATTR_NAMESPACE_SYSTEM_STRING; + } else { + prefix_len = strlen(EXTATTR_NAMESPACE_USER_STRING) + 1; + prefix = EXTATTR_NAMESPACE_USER_STRING; + } + + len += prefix_len; + } + + 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); + if (use_namespace_prefix) + snprintf(attr_str, len, "%s.%s", prefix, ap->a_name); + else + snprintf(attr_str, len, "%s", ap->a_name); + + err = uiomove(fdi.indata + sizeof(*set_xattr_in) + len, + uio->uio_resid, uio); + if (err != 0) { + 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: + * + * attr_name1attr_name2 + * + * Where "" is a single byte number of characters in the attribute name. + */ +static void +fuse_xattrlist_convert(int fix_prefix, uint8_t *list, int list_len, int *new_list_len, + char *prefix, int prefix_len, char separator) +{ + int len, pos, pos_next, count; + + if (fix_prefix) { + pos = 0; + *new_list_len = 0; + + while (pos < list_len && list[pos] != '\0') { + pos_next = strlen(&list[pos]) + 1; + if (!bcmp(&list[pos], prefix, prefix_len) && + list[pos + prefix_len] == separator) { + len = pos_next - (prefix_len + 1) - 1; + list[*new_list_len] = len; + memmove(&list[*new_list_len + 1], + &list[pos + prefix_len + 1], len); + *new_list_len += len + 1; + } + + pos += pos_next; + } + } else { + pos = list_len - 1; + count = 0; + + while (pos > 0) { + if (list[pos - 1] == '\0') { + list[pos] = count; + count = 0; + } else { + list[pos] = list[pos - 1]; + count++; + } + pos--; + } + + list[0] = count; + *new_list_len = list_len; + } +} + +/* + 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; + size_t prefix_len; + char *attr_str; + char separator; + struct thread *td = ap->a_td; + struct ucred *cred = ap->a_cred; + int use_namespace_prefix, err = 0; + + fuse_trace_printf_vnop(); + + if (fuse_isdeadfs(vp)) + return ENXIO; + + use_namespace_prefix = fuse_extattr_namespace_prefix_enable; + + /* + * Add space for a NUL and the period separator if enabled. + */ + if (use_namespace_prefix) { + /* Default to looking for user attributes. */ + if (ap->a_attrnamespace == EXTATTR_NAMESPACE_SYSTEM) { + prefix_len = strlen(EXTATTR_NAMESPACE_SYSTEM_STRING); + prefix = EXTATTR_NAMESPACE_SYSTEM_STRING; + } else { + prefix_len = strlen(EXTATTR_NAMESPACE_USER_STRING); + prefix = EXTATTR_NAMESPACE_USER_STRING; + } + separator = '.'; + len = prefix_len + 2; + } else + len = 0; + + 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); + + if (use_namespace_prefix) { + attr_str = (char *)fdi.indata + sizeof(*get_xattr_in); + snprintf(attr_str, len, "%s%c", prefix, separator); + } + + err = fdisp_wait_answ(&fdi); + if (err) { + 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) { + int new_list_len; + + new_list_len = fdi.iosize; + + /* + * 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. + */ + fuse_xattrlist_convert(use_namespace_prefix, + (uint8_t *)fdi.answ, fdi.iosize, &new_list_len, + prefix, prefix_len, separator); + + err = uiomove(fdi.answ, new_list_len, uio); + } else { + printf("%s: returned iosize %zu for %s attribute list is " + "too small\n", __func__, fdi.iosize, prefix); + err = EINVAL; + } + +out: + 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, prefix_len; + char *attr_str; + struct thread *td = ap->a_td; + struct ucred *cred = ap->a_cred; + int use_namespace_prefix, err; + + fuse_trace_printf_vnop(); + + if (fuse_isdeadfs(vp)) + return ENXIO; + + len = strlen(ap->a_name) + 1; + use_namespace_prefix = fuse_extattr_namespace_prefix_enable; + + if (use_namespace_prefix) { + /* Default to looking for user attributes. */ + if (ap->a_attrnamespace == EXTATTR_NAMESPACE_SYSTEM) { + prefix_len = strlen(EXTATTR_NAMESPACE_SYSTEM_STRING) + 1; + prefix = EXTATTR_NAMESPACE_SYSTEM_STRING; + } else { + prefix_len = strlen(EXTATTR_NAMESPACE_USER_STRING) + 1; + prefix = EXTATTR_NAMESPACE_USER_STRING; + } + + len += prefix_len; + } + + fdisp_init(&fdi, len); + fdisp_make_vp(&fdi, FUSE_REMOVEXATTR, vp, td, cred); + + attr_str = (char *)fdi.indata; + if (use_namespace_prefix) + snprintf(attr_str, len, "%s.%s", prefix, ap->a_name); + else + snprintf(attr_str, len, "%s", ap->a_name); + + err = fdisp_wait_answ(&fdi); + if (err) { + 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; };