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