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