1/*
2 * CDDL HEADER START
3 *
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License (the "License").
6 * You may not use this file except in compliance with the License.
7 *
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 * or http://www.opensolaris.org/os/licensing.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
12 *
13 * When distributing Covered Code, include this CDDL HEADER in each
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
18 *
19 * CDDL HEADER END
20 */
21
22/*
23 * Copyright (c) 2008, 2010, Oracle and/or its affiliates. All rights reserved.
24 */
25
26/*
27 * Functions supporting Solaris Extended Attributes,
28 * used to provide access to CIFS "named streams".
29 */
30
31#include <sys/systm.h>
32#include <sys/cred.h>
33#include <sys/vnode.h>
34#include <sys/vfs.h>
35#include <sys/filio.h>
36#include <sys/uio.h>
37#include <sys/dirent.h>
38#include <sys/errno.h>
39#include <sys/sysmacros.h>
40#include <sys/kmem.h>
41#include <sys/stat.h>
42#include <sys/cmn_err.h>
43#include <sys/u8_textprep.h>
44
45#include <netsmb/smb_osdep.h>
46#include <netsmb/smb.h>
47#include <netsmb/smb_conn.h>
48#include <netsmb/smb_subr.h>
49#include <netsmb/smb_rq.h>
50
51#include <smbfs/smbfs.h>
52#include <smbfs/smbfs_node.h>
53#include <smbfs/smbfs_subr.h>
54
55#include <fs/fs_subr.h>
56
57/*
58 * Solaris wants there to be a directory node to contain
59 * all the extended attributes.  The SMB protocol does not
60 * really support a directory here, and uses very different
61 * operations to list attributes, etc. so we "fake up" an
62 * smbnode here to represent the attributes directory.
63 *
64 * We need to give this (fake) directory a unique identity,
65 * and since we're using the full remote pathname as the
66 * unique identity of all nodes, the easiest thing to do
67 * here is append a colon (:) to the given pathname.
68 *
69 * There are several places where smbfs_fullpath and its
70 * callers must decide what separator to use when building
71 * a remote path name, and the rule is now as follows:
72 * 1: When no XATTR involved, use "\\" as the separator.
73 * 2: Traversal into the (fake) XATTR dir adds one ":"
74 * 3: Children of the XATTR dir add nothing (sep=0)
75 * The result should be _one_ colon before the attr name.
76 */
77
78/* ARGSUSED */
79int
80smbfs_get_xattrdir(vnode_t *pvp, vnode_t **vpp, cred_t *cr, int flags)
81{
82	vnode_t *xvp;
83	smbnode_t *pnp, *xnp;
84
85	pnp = VTOSMB(pvp);
86
87	/*
88	 * We don't allow recursive extended attributes
89	 * (xattr under xattr dir.) so the "parent" node
90	 * (pnp) must NOT be an XATTR directory or file.
91	 */
92	if (pnp->n_flag & N_XATTR)
93		return (EINVAL);
94
95	xnp = smbfs_node_findcreate(pnp->n_mount,
96	    pnp->n_rpath, pnp->n_rplen, NULL, 0, ':',
97	    &smbfs_fattr0); /* force create */
98	ASSERT(xnp != NULL);
99	xvp = SMBTOV(xnp);
100	/* Note: xvp has a VN_HOLD, which our caller expects. */
101
102	/* If it's a new node, initialize. */
103	if (xvp->v_type == VNON) {
104
105		mutex_enter(&xvp->v_lock);
106		xvp->v_type = VDIR;
107		xvp->v_flag |= V_XATTRDIR;
108		mutex_exit(&xvp->v_lock);
109
110		mutex_enter(&xnp->r_statelock);
111		xnp->n_flag |= N_XATTR;
112		mutex_exit(&xnp->r_statelock);
113	}
114
115	/* Success! */
116	*vpp = xvp;
117	return (0);
118}
119
120/*
121 * Find the parent of an XATTR directory or file,
122 * by trimming off the ":attrname" part of rpath.
123 * Called on XATTR files to get the XATTR dir, and
124 * called on the XATTR dir to get the real object
125 * under which the (faked up) XATTR dir lives.
126 */
127int
128smbfs_xa_parent(vnode_t *vp, vnode_t **vpp)
129{
130	smbnode_t *np = VTOSMB(vp);
131	smbnode_t *pnp;
132	int rplen;
133
134	*vpp = NULL;
135
136	if ((np->n_flag & N_XATTR) == 0)
137		return (EINVAL);
138
139	if (vp->v_flag & V_XATTRDIR) {
140		/*
141		 * Want the parent of the XATTR directory.
142		 * That's easy: just remove trailing ":"
143		 */
144		rplen = np->n_rplen - 1;
145		if (rplen < 1) {
146			SMBVDEBUG("rplen < 1?");
147			return (ENOENT);
148		}
149		if (np->n_rpath[rplen] != ':') {
150			SMBVDEBUG("last is not colon");
151			return (ENOENT);
152		}
153	} else {
154		/*
155		 * Want the XATTR directory given
156		 * one of its XATTR files (children).
157		 * Find the ":" and trim after it.
158		 */
159		for (rplen = 1; rplen < np->n_rplen; rplen++)
160			if (np->n_rpath[rplen] == ':')
161				break;
162		/* Should have found ":stream_name" */
163		if (rplen >= np->n_rplen) {
164			SMBVDEBUG("colon not found");
165			return (ENOENT);
166		}
167		rplen++; /* keep the ":" */
168		if (rplen >= np->n_rplen) {
169			SMBVDEBUG("no stream name");
170			return (ENOENT);
171		}
172	}
173
174	pnp = smbfs_node_findcreate(np->n_mount,
175	    np->n_rpath, rplen, NULL, 0, 0,
176	    &smbfs_fattr0); /* force create */
177	ASSERT(pnp != NULL);
178	/* Note: have VN_HOLD from smbfs_node_findcreate */
179	*vpp = SMBTOV(pnp);
180	return (0);
181}
182
183/*
184 * This is called by smbfs_pathconf to find out
185 * if some file has any extended attributes.
186 * There's no short-cut way to find out, so we
187 * just list the attributes the usual way and
188 * check for an empty result.
189 *
190 * Returns 1: (exists) or 0: (none found)
191 */
192int
193smbfs_xa_exists(vnode_t *vp, cred_t *cr)
194{
195	smbnode_t *xnp;
196	vnode_t *xvp;
197	struct smb_cred scred;
198	struct smbfs_fctx ctx;
199	int error, rc = 0;
200
201	/* Get the xattr dir */
202	error = smbfs_get_xattrdir(vp, &xvp, cr, LOOKUP_XATTR);
203	if (error)
204		return (0);
205	/* NB: have VN_HOLD on xpv */
206	xnp = VTOSMB(xvp);
207
208	smb_credinit(&scred, cr);
209
210	bzero(&ctx, sizeof (ctx));
211	ctx.f_flags = SMBFS_RDD_FINDFIRST;
212	ctx.f_dnp = xnp;
213	ctx.f_scred = &scred;
214	ctx.f_ssp = xnp->n_mount->smi_share;
215
216	error = smbfs_xa_findopen(&ctx, xnp, "*", 1);
217	if (error)
218		goto out;
219
220	error = smbfs_xa_findnext(&ctx, 1);
221	if (error)
222		goto out;
223
224	/* Have at least one named stream. */
225	SMBVDEBUG("ctx.f_name: %s\n", ctx.f_name);
226	rc = 1;
227
228out:
229	/* NB: Always call findclose, error or not. */
230	(void) smbfs_xa_findclose(&ctx);
231	smb_credrele(&scred);
232	VN_RELE(xvp);
233	return (rc);
234}
235
236
237/*
238 * This is called to get attributes (size, etc.) of either
239 * the "faked up" XATTR directory or a named stream.
240 */
241int
242smbfs_xa_getfattr(struct smbnode *xnp, struct smbfattr *fap,
243	struct smb_cred *scrp)
244{
245	vnode_t *xvp;	/* xattr */
246	vnode_t *pvp;	/* parent */
247	smbnode_t *pnp;	/* parent */
248	int error, nlen;
249	const char *name, *sname;
250
251	xvp = SMBTOV(xnp);
252
253	/*
254	 * Simulate smbfs_smb_getfattr() for a named stream.
255	 * OK to leave a,c,m times zero (expected w/ XATTR).
256	 * The XATTR directory is easy (all fake).
257	 */
258	if (xvp->v_flag & V_XATTRDIR) {
259		fap->fa_attr = SMB_FA_DIR;
260		fap->fa_size = DEV_BSIZE;
261		return (0);
262	}
263
264	/*
265	 * Do a lookup in the XATTR directory,
266	 * using the stream name (last part)
267	 * from the xattr node.
268	 */
269	error = smbfs_xa_parent(xvp, &pvp);
270	if (error)
271		return (error);
272	/* Note: pvp has a VN_HOLD */
273	pnp = VTOSMB(pvp);
274
275	/* Get stream name (ptr and length) */
276	ASSERT(xnp->n_rplen > pnp->n_rplen);
277	nlen = xnp->n_rplen - pnp->n_rplen;
278	name = xnp->n_rpath + pnp->n_rplen;
279	sname = name;
280
281	/* Note: this can allocate a new "name" */
282	error = smbfs_smb_lookup(pnp, &name, &nlen, fap, scrp);
283	if (error == 0 && name != sname)
284		smbfs_name_free(name, nlen);
285
286	VN_RELE(pvp);
287
288	return (error);
289}
290
291/*
292 * Fetch the entire attribute list here in findopen.
293 * Will parse the results in findnext.
294 *
295 * This is called on the XATTR directory, so we
296 * have to get the (real) parent object first.
297 */
298/* ARGSUSED */
299int
300smbfs_xa_findopen(struct smbfs_fctx *ctx, struct smbnode *dnp,
301	const char *wildcard, int wclen)
302{
303	vnode_t *pvp;	/* parent */
304	smbnode_t *pnp;
305	struct smb_t2rq *t2p;
306	struct smb_vc *vcp = SSTOVC(ctx->f_ssp);
307	struct mbchain *mbp;
308	int error;
309
310	ASSERT(dnp->n_flag & N_XATTR);
311
312	ctx->f_type = ft_XA;
313	ctx->f_namesz = SMB_MAXFNAMELEN + 1;
314	if (SMB_UNICODE_STRINGS(SSTOVC(ctx->f_ssp)))
315		ctx->f_namesz *= 2;
316	ctx->f_name = kmem_alloc(ctx->f_namesz, KM_SLEEP);
317
318	error = smbfs_xa_parent(SMBTOV(dnp), &pvp);
319	if (error)
320		return (error);
321	ASSERT(pvp);
322	/* Note: pvp has a VN_HOLD */
323	pnp = VTOSMB(pvp);
324
325	if (ctx->f_t2) {
326		smb_t2_done(ctx->f_t2);
327		ctx->f_t2 = NULL;
328	}
329
330	error = smb_t2_alloc(SSTOCP(ctx->f_ssp),
331	    SMB_TRANS2_QUERY_PATH_INFORMATION,
332	    ctx->f_scred, &t2p);
333	if (error)
334		goto out;
335	ctx->f_t2 = t2p;
336
337	mbp = &t2p->t2_tparam;
338	(void) mb_init(mbp);
339	(void) mb_put_uint16le(mbp, SMB_QFILEINFO_STREAM_INFO);
340	(void) mb_put_uint32le(mbp, 0);
341	error = smbfs_fullpath(mbp, vcp, pnp, NULL, NULL, 0);
342	if (error)
343		goto out;
344	t2p->t2_maxpcount = 2;
345	t2p->t2_maxdcount = INT16_MAX;
346	error = smb_t2_request(t2p);
347	if (error) {
348		if (t2p->t2_sr_error == NT_STATUS_INVALID_PARAMETER)
349			error = ENOTSUP;
350	}
351	/*
352	 * No returned parameters to parse.
353	 * Returned data are in t2_rdata,
354	 * which we'll parse in _findnext.
355	 * However, save the wildcard.
356	 */
357	ctx->f_wildcard = wildcard;
358	ctx->f_wclen = wclen;
359
360out:
361	VN_RELE(pvp);
362	return (error);
363}
364
365/*
366 * Get the next name in an XATTR directory into f_name
367 */
368/* ARGSUSED */
369int
370smbfs_xa_findnext(struct smbfs_fctx *ctx, uint16_t limit)
371{
372	struct mdchain *mdp;
373	struct smb_t2rq *t2p;
374	uint32_t size, next;
375	uint64_t llongint;
376	int error, skip, used, nmlen;
377
378	t2p = ctx->f_t2;
379	mdp = &t2p->t2_rdata;
380
381	if (ctx->f_flags & SMBFS_RDD_FINDSINGLE) {
382		ASSERT(ctx->f_wildcard);
383		SMBVDEBUG("wildcard: %s\n", ctx->f_wildcard);
384	}
385
386again:
387	if (ctx->f_flags & SMBFS_RDD_EOF)
388		return (ENOENT);
389
390	/* Parse FILE_STREAM_INFORMATION */
391	if ((error = md_get_uint32le(mdp, &next)) != 0)	/* offset to */
392		return (ENOENT);
393	if ((error = md_get_uint32le(mdp, &size)) != 0) /* name len */
394		return (ENOENT);
395	(void) md_get_uint64le(mdp, &llongint); /* file size */
396	ctx->f_attr.fa_size = llongint;
397	(void) md_get_uint64le(mdp, NULL);	/* alloc. size */
398	used = 4 + 4 + 8 + 8;	/* how much we consumed */
399
400	/*
401	 * Copy the string, but skip the first char (":")
402	 * Watch out for zero-length strings here.
403	 */
404	if (SMB_UNICODE_STRINGS(SSTOVC(ctx->f_ssp))) {
405		if (size >= 2) {
406			size -= 2; used += 2;
407			(void) md_get_uint16le(mdp, NULL);
408		}
409		nmlen = min(size, SMB_MAXFNAMELEN * 2);
410	} else {
411		if (size >= 1) {
412			size -= 1; used += 1;
413			(void) md_get_uint8(mdp, NULL);
414		}
415		nmlen = min(size, SMB_MAXFNAMELEN);
416	}
417
418	ASSERT(nmlen < ctx->f_namesz);
419	ctx->f_nmlen = nmlen;
420	error = md_get_mem(mdp, ctx->f_name, nmlen, MB_MSYSTEM);
421	if (error)
422		return (error);
423	used += nmlen;
424
425	/*
426	 * Convert UCS-2 to UTF-8
427	 */
428	smbfs_fname_tolocal(ctx);
429	if (nmlen)
430		SMBVDEBUG("name: %s\n", ctx->f_name);
431	else
432		SMBVDEBUG("null name!\n");
433
434	/*
435	 * Skip padding until next offset
436	 */
437	if (next > used) {
438		skip = next - used;
439		(void) md_get_mem(mdp, NULL, skip, MB_MSYSTEM);
440	}
441	if (next == 0)
442		ctx->f_flags |= SMBFS_RDD_EOF;
443
444	/*
445	 * Chop off the trailing ":$DATA"
446	 * The 6 here is strlen(":$DATA")
447	 */
448	if (ctx->f_nmlen >= 6) {
449		char *p = ctx->f_name + ctx->f_nmlen - 6;
450		if (strncmp(p, ":$DATA", 6) == 0) {
451			*p = '\0'; /* Chop! */
452			ctx->f_nmlen -= 6;
453		}
454	}
455
456	/*
457	 * The Chop above will typically leave
458	 * an empty name in the first slot,
459	 * which we will skip here.
460	 */
461	if (ctx->f_nmlen == 0)
462		goto again;
463
464	/*
465	 * If this is a lookup of a specific name,
466	 * skip past any non-matching names.
467	 */
468	if (ctx->f_flags & SMBFS_RDD_FINDSINGLE) {
469		if (ctx->f_wclen != ctx->f_nmlen)
470			goto again;
471		if (u8_strcmp(ctx->f_wildcard, ctx->f_name,
472		    ctx->f_nmlen, U8_STRCMP_CI_LOWER,
473		    U8_UNICODE_LATEST, &error) || error)
474			goto again;
475	}
476
477	return (0);
478}
479
480/*
481 * Find first/next/close for XATTR directories.
482 * NB: also used by smbfs_smb_lookup
483 */
484
485int
486smbfs_xa_findclose(struct smbfs_fctx *ctx)
487{
488
489	if (ctx->f_name)
490		kmem_free(ctx->f_name, ctx->f_namesz);
491	if (ctx->f_t2)
492		smb_t2_done(ctx->f_t2);
493
494	return (0);
495}
496