view usr/src/uts/common/fs/smbclnt/smbfs/smbfs_xattr.c @ 10023:71bf38dba3d6

6584198 SMB Client needs authentication improvements 6587713 Need to reconnect after server disconnect
author Gordon Ross <Gordon.Ross@Sun.COM>
date Thu, 02 Jul 2009 12:58:38 -0400
parents 792589b3384f
children ed3411181494
line wrap: on
line source

/*
 * 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 <sys/systm.h>
#include <sys/cred.h>
#include <sys/vnode.h>
#include <sys/vfs.h>
#include <sys/filio.h>
#include <sys/uio.h>
#include <sys/dirent.h>
#include <sys/errno.h>
#include <sys/sysmacros.h>
#include <sys/kmem.h>
#include <sys/stat.h>
#include <sys/cmn_err.h>
#include <sys/dnlc.h>
#include <sys/u8_textprep.h>

#include <netsmb/smb_osdep.h>
#include <netsmb/smb.h>
#include <netsmb/smb_conn.h>
#include <netsmb/smb_subr.h>
#include <netsmb/smb_rq.h>

#include <smbfs/smbfs.h>
#include <smbfs/smbfs_node.h>
#include <smbfs/smbfs_subr.h>

#include <fs/fs_subr.h>

/*
 * 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);
}