xref: /illumos-gate/usr/src/uts/common/fs/smbclnt/smbfs/smbfs_xattr.c (revision 2a6e99a0f1f7d22c0396e8b2ce9b9babbd1056cf)
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 */
79 int
80 smbfs_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  */
127 int
128 smbfs_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  */
192 int
193 smbfs_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 
228 out:
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  */
241 int
242 smbfs_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 */
299 int
300 smbfs_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 
360 out:
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 */
369 int
370 smbfs_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 
386 again:
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 
485 int
486 smbfs_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