*** src/sys/fs/fuse/fuse_ipc.c.orig --- src/sys/fs/fuse/fuse_ipc.c *************** *** 636,658 **** break; case FUSE_SETXATTR: ! panic("FUSE_SETXATTR implementor has forgotten to define a" ! " response body format check"); 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"); break; case FUSE_REMOVEXATTR: ! panic("FUSE_REMOVEXATTR implementor has forgotten to define a" ! " response body format check"); break; case FUSE_FLUSH: --- 636,655 ---- break; case FUSE_SETXATTR: ! err = (blen == 0) ? 0 : EINVAL; break; case FUSE_GETXATTR: case FUSE_LISTXATTR: ! /* ! * These can have varying response lengths, and 0 length ! * isn't necessarily invalid. ! */ ! err = 0; break; case FUSE_REMOVEXATTR: ! err = (blen == 0) ? 0 : EINVAL; break; case FUSE_FLUSH: *** src/sys/fs/fuse/fuse_vnops.c.orig --- src/sys/fs/fuse/fuse_vnops.c *************** *** 73,78 **** --- 73,79 ---- #include #include #include + #include #include #include #include *************** *** 111,120 **** --- 112,124 ---- 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; *************** *** 127,132 **** --- 131,137 ---- 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; *************** *** 139,148 **** --- 144,156 ---- .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, *************** *** 156,161 **** --- 164,170 ---- .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, *************** *** 1946,1951 **** --- 1955,2410 ---- } /* + 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; + 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; + uint64_t parentnid = VTOFUD(vp)->nid; + struct thread *td = ap->a_td; + struct ucred *cred = ap->a_cred; + int err; + + err = 0; + + fuse_trace_printf_vnop(); + + if (fuse_isdeadfs(vp)) { + return ENXIO; + } + + /* String plus terminating NUL */ + len = strlen(ap->a_name) + 1; + + /* + * Default to prefixing the attribute name with "user.". + */ + 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; + + bzero(&fdi, sizeof(fdi)); + fdisp_init(&fdi, len + sizeof(*get_xattr_in)); + fdisp_make(&fdi, FUSE_GETXATTR, vnode_mount(vp), parentnid, 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.%s", prefix, ap->a_name); + attr_str[len - 1] = '\0'; + + 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((uint8_t *)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; + struct fuse_setxattr_in *set_xattr_in; + struct mount *mp = vnode_mount(vp); + char *prefix; + size_t len, prefix_len; + char *attr_str; + uint64_t parentnid = VTOFUD(vp)->nid; + struct thread *td = ap->a_td; + struct ucred *cred = ap->a_cred; + int err; + + err = 0; + + fuse_trace_printf_vnop(); + + if (fuse_isdeadfs(vp)) { + return ENXIO; + } + + /* String plus terminating NUL */ + len = strlen(ap->a_name) + 1; + + /* + * Default to prefixing the attribute name with "user.". + */ + 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; + + bzero(&fdi, sizeof(fdi)); + fdisp_init(&fdi, len + sizeof(*set_xattr_in) + uio->uio_resid); + fdisp_make(&fdi, FUSE_SETXATTR, vnode_mount(vp), parentnid, 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.%s", prefix, ap->a_name); + attr_str[len - 1] = '\0'; + + err = uiomove((uint8_t *)fdi.indata + sizeof(*set_xattr_in) + len, + uio->uio_resid, uio); + if (err != 0) { + printf("%s: got error %d from uiomove\n", __func__, 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(uint8_t *list, int list_len, int *new_list_len, + char *prefix, int prefix_len, char separator) + { + int pos, cur_count, i; + + if (list_len == 0) + return; + + /* + * The first pass through the list converts it from NUL-terminated + * strings to strings prefixed by the length. + */ + pos = list_len - 1; + cur_count = 0; + while (pos > 0) { + if (list[pos - 1] == '\0') { + list[pos] = cur_count; + cur_count = 0; + } else { + list[pos] = list[pos - 1]; + cur_count++; + } + pos--; + } + list[0] = cur_count; + + + /* + * For the second pass through the list, we check to see whether + * the strings begin with the prefix and separator, and if so, trim + * them off. + */ + *new_list_len = list_len; + + for (i = 0, cur_count = 0; i < *new_list_len; i += (cur_count + 1)) { + cur_count = list[i]; + + /* Make sure we have at least the prefix length left */ + if (cur_count < (prefix_len + 1)) + continue; + + /* + * If this particular string starts with the prefix and + * separator, then we need to consume it. + */ + if ((bcmp(&list[i+1], prefix, prefix_len) == 0) + && (list[i + prefix_len + 1] == separator)) { + list[i] = cur_count - prefix_len - 1; + *new_list_len -= (prefix_len + 1); + /* + * Note that the strings will overlap, and bcopy + * does allow this. memcpy does not necessarily + * allow it. + */ + bcopy(&list[i + prefix_len + 1 + 1], &list[i + 1], + *new_list_len); + cur_count = list[i]; + } + } + } + + /* + 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; + 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; + uint64_t parentnid = VTOFUD(vp)->nid; + struct thread *td = ap->a_td; + struct ucred *cred = ap->a_cred; + int err; + + 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_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 = '.'; + + /* Add space for a NUL and the period separator */ + len = prefix_len + 2; + + bzero(&fdi, sizeof(fdi)); + fdisp_init(&fdi, sizeof(*get_xattr_in) + len); + fdisp_make(&fdi, FUSE_LISTXATTR, vnode_mount(vp), parentnid, td, cred); + + get_xattr_in = fdi.indata; + get_xattr_out = fdi.answ; + 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, separator); + attr_str[len - 1] = '\0'; + + 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) { + 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((uint8_t *)fdi.answ, fdi.iosize, + &new_list_len, prefix, prefix_len, separator); + + err = uiomove((uint8_t *)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; + struct mount *mp = vnode_mount(vp); + char *prefix; + size_t len, prefix_len; + char *attr_str; + uint64_t parentnid = VTOFUD(vp)->nid; + struct thread *td = ap->a_td; + struct ucred *cred = ap->a_cred; + int err; + + fuse_trace_printf_vnop(); + + if (fuse_isdeadfs(vp)) { + return ENXIO; + } + + len = strlen(ap->a_name) + 1; + + /* + * Default to prefixing the attribute name with "user.". + */ + 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; + + bzero(&fdi, sizeof(fdi)); + fdisp_init(&fdi, len); + fdisp_make(&fdi, FUSE_REMOVEXATTR, vnode_mount(vp),parentnid, td, cred); + + attr_str = (char *)fdi.indata; + snprintf(attr_str, len, "%s.%s", prefix, ap->a_name); + attr_str[len - 1] = '\0'; + + 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; };