/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * Functions supporting Solaris Extended Attributes, * used to provide access to CIFS "named streams". */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* * Solaris wants there to be a directory node to contain * all the extended attributes. The SMB protocol does not * really support a directory here, and uses very different * operations to list attributes, etc. so we "fake up" an * smbnode here to represent the attributes directory. * * We need to give this (fake) directory a unique identity, * and since we're using the full remote pathname as the * unique identity of all nodes, the easiest thing to do * here is append a colon (:) to the given pathname. * * There are several places where smbfs_fullpath and its * callers must decide what separator to use when building * a remote path name, and the rule is now as follows: * 1: When no XATTR involved, use "\\" as the separator. * 2: Traversal into the (fake) XATTR dir adds one ":" * 3: Children of the XATTR dir add nothing (sep=0) * The result should be _one_ colon before the attr name. */ /* ARGSUSED */ int smbfs_get_xattrdir(vnode_t *pvp, vnode_t **vpp, cred_t *cr, int flags) { vnode_t *xvp; smbnode_t *pnp, *xnp; pnp = VTOSMB(pvp); xvp = smbfs_make_node(pvp->v_vfsp, pnp->n_rpath, pnp->n_rplen, NULL, 0, ':', NULL); ASSERT(xvp); /* Note: xvp has a VN_HOLD, which our caller expects. */ xnp = VTOSMB(xvp); /* If it's a new node, initialize. */ if (xvp->v_type == VNON) { mutex_enter(&xvp->v_lock); xvp->v_type = VDIR; xvp->v_flag |= V_XATTRDIR; mutex_exit(&xvp->v_lock); mutex_enter(&xnp->r_statelock); xnp->n_flag |= N_XATTR; mutex_exit(&xnp->r_statelock); } /* Success! */ *vpp = xvp; return (0); } /* * Find the parent of an XATTR directory or file, * by trimming off the ":attrname" part of rpath. * Called on XATTR files to get the XATTR dir, and * called on the XATTR dir to get the real object * under which the (faked up) XATTR dir lives. */ int smbfs_xa_parent(vnode_t *vp, vnode_t **vpp) { smbnode_t *np = VTOSMB(vp); vnode_t *pvp; int rplen; if ((np->n_flag & N_XATTR) == 0) return (EINVAL); if (vp->v_flag & V_XATTRDIR) { /* * Want the parent of the XATTR directory. * That's easy: just remove trailing ":" */ rplen = np->n_rplen - 1; if (rplen < 1) { SMBVDEBUG("rplen < 1?"); return (ENOENT); } if (np->n_rpath[rplen] != ':') { SMBVDEBUG("last is not colon"); return (ENOENT); } } else { /* * Want the XATTR directory given * one of its XATTR files (children). * Find the ":" and trim after it. */ for (rplen = 1; rplen < np->n_rplen; rplen++) if (np->n_rpath[rplen] == ':') break; /* Should have found ":stream_name" */ if (rplen >= np->n_rplen) { SMBVDEBUG("colon not found"); return (ENOENT); } rplen++; /* keep the ":" */ if (rplen >= np->n_rplen) { SMBVDEBUG("no stream name"); return (ENOENT); } } pvp = smbfs_make_node(vp->v_vfsp, np->n_rpath, rplen, NULL, 0, 0, NULL); ASSERT(pvp); /* Note: pvp has a VN_HOLD from _make_node */ *vpp = pvp; return (0); } /* * This is called by smbfs_pathconf to find out * if some file has any extended attributes. * There's no short-cut way to find out, so we * just list the attributes the usual way and * check for an empty result. * * Returns 1: (exists) or 0: (none found) */ int smbfs_xa_exists(vnode_t *vp, cred_t *cr) { smbnode_t *xnp; vnode_t *xvp; struct smb_cred scred; struct smbfs_fctx ctx; int error, rc = 0; /* Get the xattr dir */ error = smbfs_get_xattrdir(vp, &xvp, cr, LOOKUP_XATTR); if (error) return (0); /* NB: have VN_HOLD on xpv */ xnp = VTOSMB(xvp); smb_credinit(&scred, cr); bzero(&ctx, sizeof (ctx)); ctx.f_flags = SMBFS_RDD_FINDFIRST; ctx.f_dnp = xnp; ctx.f_scred = &scred; ctx.f_ssp = xnp->n_mount->smi_share; error = smbfs_xa_findopen(&ctx, xnp, "*", 1); if (error) goto out; error = smbfs_xa_findnext(&ctx, 1); if (error) goto out; /* Have at least one named stream. */ SMBVDEBUG("ctx.f_name: %s\n", ctx.f_name); rc = 1; out: /* NB: Always call findclose, error or not. */ (void) smbfs_xa_findclose(&ctx); smb_credrele(&scred); VN_RELE(xvp); return (rc); } /* * This is called to get attributes (size, etc.) of either * the "faked up" XATTR directory or a named stream. */ int smbfs_xa_getfattr(struct smbnode *xnp, struct smbfattr *fap, struct smb_cred *scrp) { vnode_t *xvp; /* xattr */ vnode_t *pvp; /* parent */ smbnode_t *pnp; /* parent */ int error, nlen; const char *name, *sname; xvp = SMBTOV(xnp); /* * Simulate smbfs_smb_getfattr() for a named stream. * OK to leave a,c,m times zero (expected w/ XATTR). * The XATTR directory is easy (all fake). */ if (xvp->v_flag & V_XATTRDIR) { fap->fa_attr = SMB_FA_DIR; fap->fa_size = DEV_BSIZE; return (0); } /* * Do a lookup in the XATTR directory, * using the stream name (last part) * from the xattr node. */ error = smbfs_xa_parent(xvp, &pvp); if (error) return (error); /* Note: pvp has a VN_HOLD */ pnp = VTOSMB(pvp); /* Get stream name (ptr and length) */ ASSERT(xnp->n_rplen > pnp->n_rplen); nlen = xnp->n_rplen - pnp->n_rplen; name = xnp->n_rpath + pnp->n_rplen; sname = name; /* Note: this can allocate a new "name" */ error = smbfs_smb_lookup(pnp, &name, &nlen, fap, scrp); if (error == 0 && name != sname) smbfs_name_free(name, nlen); VN_RELE(pvp); return (error); } /* * Fetch the entire attribute list here in findopen. * Will parse the results in findnext. * * This is called on the XATTR directory, so we * have to get the (real) parent object first. */ /* ARGSUSED */ int smbfs_xa_findopen(struct smbfs_fctx *ctx, struct smbnode *dnp, const char *wildcard, int wclen) { vnode_t *pvp; /* parent */ smbnode_t *pnp; struct smb_t2rq *t2p; struct smb_vc *vcp = SSTOVC(ctx->f_ssp); struct mbchain *mbp; int error; ASSERT(dnp->n_flag & N_XATTR); ctx->f_type = ft_XA; ctx->f_namesz = SMB_MAXFNAMELEN + 1; if (SMB_UNICODE_STRINGS(SSTOVC(ctx->f_ssp))) ctx->f_namesz *= 2; ctx->f_name = kmem_alloc(ctx->f_namesz, KM_SLEEP); error = smbfs_xa_parent(SMBTOV(dnp), &pvp); if (error) return (error); ASSERT(pvp); /* Note: pvp has a VN_HOLD */ pnp = VTOSMB(pvp); if (ctx->f_t2) { smb_t2_done(ctx->f_t2); ctx->f_t2 = NULL; } error = smb_t2_alloc(SSTOCP(ctx->f_ssp), SMB_TRANS2_QUERY_PATH_INFORMATION, ctx->f_scred, &t2p); if (error) goto out; ctx->f_t2 = t2p; mbp = &t2p->t2_tparam; mb_init(mbp); mb_put_uint16le(mbp, SMB_QFILEINFO_STREAM_INFO); mb_put_uint32le(mbp, 0); error = smbfs_fullpath(mbp, vcp, pnp, NULL, NULL, 0); if (error) goto out; t2p->t2_maxpcount = 2; t2p->t2_maxdcount = INT16_MAX; error = smb_t2_request(t2p); if (error) { if (smb_t2_err(t2p) == NT_STATUS_INVALID_PARAMETER) error = ENOTSUP; } /* * No returned parameters to parse. * Returned data are in t2_rdata, * which we'll parse in _findnext. * However, save the wildcard. */ ctx->f_wildcard = wildcard; ctx->f_wclen = wclen; out: VN_RELE(pvp); return (error); } /* * Get the next name in an XATTR directory into f_name */ /* ARGSUSED */ int smbfs_xa_findnext(struct smbfs_fctx *ctx, uint16_t limit) { struct mdchain *mdp; struct smb_t2rq *t2p; uint32_t size, next; uint64_t llongint; int error, skip, used, nmlen; t2p = ctx->f_t2; mdp = &t2p->t2_rdata; if (ctx->f_flags & SMBFS_RDD_FINDSINGLE) { ASSERT(ctx->f_wildcard); SMBVDEBUG("wildcard: %s\n", ctx->f_wildcard); } again: if (ctx->f_flags & SMBFS_RDD_EOF) return (ENOENT); /* Parse FILE_STREAM_INFORMATION */ if ((error = md_get_uint32le(mdp, &next)) != 0) /* offset to */ return (ENOENT); if ((error = md_get_uint32le(mdp, &size)) != 0) /* name len */ return (ENOENT); md_get_uint64le(mdp, &llongint); /* file size */ ctx->f_attr.fa_size = llongint; md_get_uint64le(mdp, NULL); /* alloc. size */ used = 4 + 4 + 8 + 8; /* how much we consumed */ /* * Copy the string, but skip the first char (":") * Watch out for zero-length strings here. */ if (SMB_UNICODE_STRINGS(SSTOVC(ctx->f_ssp))) { if (size >= 2) { size -= 2; used += 2; md_get_uint16le(mdp, NULL); } nmlen = min(size, SMB_MAXFNAMELEN * 2); } else { if (size >= 1) { size -= 1; used += 1; md_get_uint8(mdp, NULL); } nmlen = min(size, SMB_MAXFNAMELEN); } ASSERT(nmlen < ctx->f_namesz); ctx->f_nmlen = nmlen; error = md_get_mem(mdp, ctx->f_name, nmlen, MB_MSYSTEM); if (error) return (error); used += nmlen; /* * Convert UCS-2 to UTF-8 */ smbfs_fname_tolocal(ctx); if (nmlen) SMBVDEBUG("name: %s\n", ctx->f_name); else SMBVDEBUG("null name!\n"); /* * Skip padding until next offset */ if (next > used) { skip = next - used; md_get_mem(mdp, NULL, skip, MB_MSYSTEM); } if (next == 0) ctx->f_flags |= SMBFS_RDD_EOF; /* * Chop off the trailing ":$DATA" * The 6 here is strlen(":$DATA") */ if (ctx->f_nmlen >= 6) { char *p = ctx->f_name + ctx->f_nmlen - 6; if (strncmp(p, ":$DATA", 6) == 0) { *p = '\0'; /* Chop! */ ctx->f_nmlen -= 6; } } /* * The Chop above will typically leave * an empty name in the first slot, * which we will skip here. */ if (ctx->f_nmlen == 0) goto again; /* * If this is a lookup of a specific name, * skip past any non-matching names. */ if (ctx->f_flags & SMBFS_RDD_FINDSINGLE) { if (ctx->f_wclen != ctx->f_nmlen) goto again; if (u8_strcmp(ctx->f_wildcard, ctx->f_name, ctx->f_nmlen, U8_STRCMP_CI_LOWER, U8_UNICODE_LATEST, &error) || error) goto again; } return (0); } /* * Find first/next/close for XATTR directories. * NB: also used by smbfs_smb_lookup */ int smbfs_xa_findclose(struct smbfs_fctx *ctx) { if (ctx->f_name) kmem_free(ctx->f_name, ctx->f_namesz); if (ctx->f_t2) smb_t2_done(ctx->f_t2); return (0); }