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