xref: /titanic_44/usr/src/uts/common/fs/smbclnt/smbfs/smbfs_subr2.c (revision 1a5e258f5471356ca102c7176637cdce45bac147)
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  * Copyright 2010 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  *
25  *	Copyright (c) 1983,1984,1985,1986,1987,1988,1989  AT&T.
26  *	All rights reserved.
27  */
28 
29 /*
30  * Node hash implementation initially borrowed from NFS (nfs_subr.c)
31  * but then heavily modified. It's no longer an array of hash lists,
32  * but an AVL tree per mount point.  More on this below.
33  */
34 
35 #include <sys/param.h>
36 #include <sys/systm.h>
37 #include <sys/time.h>
38 #include <sys/vnode.h>
39 #include <sys/bitmap.h>
40 #include <sys/dnlc.h>
41 #include <sys/kmem.h>
42 #include <sys/sunddi.h>
43 #include <sys/sysmacros.h>
44 
45 #include <netsmb/smb_osdep.h>
46 
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 /*
57  * The AVL trees (now per-mount) allow finding an smbfs node by its
58  * full remote path name.  It also allows easy traversal of all nodes
59  * below (path wise) any given node.  A reader/writer lock for each
60  * (per mount) AVL tree is used to control access and to synchronize
61  * lookups, additions, and deletions from that AVL tree.
62  *
63  * Previously, this code use a global array of hash chains, each with
64  * its own rwlock.  A few struct members, functions, and comments may
65  * still refer to a "hash", and those should all now be considered to
66  * refer to the per-mount AVL tree that replaced the old hash chains.
67  * (i.e. member smi_hash_lk, function sn_hashfind, etc.)
68  *
69  * The smbnode freelist is organized as a doubly linked list with
70  * a head pointer.  Additions and deletions are synchronized via
71  * a single mutex.
72  *
73  * In order to add an smbnode to the free list, it must be linked into
74  * the mount's AVL tree and the exclusive lock for the AVL must be held.
75  * If an smbnode is not linked into the AVL tree, then it is destroyed
76  * because it represents no valuable information that can be reused
77  * about the file.  The exclusive lock for the AVL tree must be held
78  * in order to prevent a lookup in the AVL tree from finding the
79  * smbnode and using it and assuming that the smbnode is not on the
80  * freelist.  The lookup in the AVL tree will have the AVL tree lock
81  * held, either exclusive or shared.
82  *
83  * The vnode reference count for each smbnode is not allowed to drop
84  * below 1.  This prevents external entities, such as the VM
85  * subsystem, from acquiring references to vnodes already on the
86  * freelist and then trying to place them back on the freelist
87  * when their reference is released.  This means that the when an
88  * smbnode is looked up in the AVL tree, then either the smbnode
89  * is removed from the freelist and that reference is tranfered to
90  * the new reference or the vnode reference count must be incremented
91  * accordingly.  The mutex for the freelist must be held in order to
92  * accurately test to see if the smbnode is on the freelist or not.
93  * The AVL tree lock might be held shared and it is possible that
94  * two different threads may race to remove the smbnode from the
95  * freelist.  This race can be resolved by holding the mutex for the
96  * freelist.  Please note that the mutex for the freelist does not
97  * need to held if the smbnode is not on the freelist.  It can not be
98  * placed on the freelist due to the requirement that the thread
99  * putting the smbnode on the freelist must hold the exclusive lock
100  * for the AVL tree and the thread doing the lookup in the AVL tree
101  * is holding either a shared or exclusive lock for the AVL tree.
102  *
103  * The lock ordering is:
104  *
105  *	AVL tree lock -> vnode lock
106  *	AVL tree lock -> freelist lock
107  */
108 
109 static kmutex_t smbfreelist_lock;
110 static smbnode_t *smbfreelist = NULL;
111 static ulong_t	smbnodenew = 0;
112 long	nsmbnode = 0;
113 
114 static struct kmem_cache *smbnode_cache;
115 
116 static const vsecattr_t smbfs_vsa0 = { 0 };
117 
118 /*
119  * Mutex to protect the following variables:
120  *	smbfs_major
121  *	smbfs_minor
122  */
123 kmutex_t smbfs_minor_lock;
124 int smbfs_major;
125 int smbfs_minor;
126 
127 /* See smbfs_node_findcreate() */
128 struct smbfattr smbfs_fattr0;
129 
130 /*
131  * Local functions.
132  * SN for Smb Node
133  */
134 static void sn_rmfree(smbnode_t *);
135 static void sn_inactive(smbnode_t *);
136 static void sn_addhash_locked(smbnode_t *, avl_index_t);
137 static void sn_rmhash_locked(smbnode_t *);
138 static void sn_destroy_node(smbnode_t *);
139 void smbfs_kmem_reclaim(void *cdrarg);
140 
141 static smbnode_t *
142 sn_hashfind(smbmntinfo_t *, const char *, int, avl_index_t *);
143 
144 static smbnode_t *
145 make_smbnode(smbmntinfo_t *, const char *, int, int *);
146 
147 /*
148  * Free the resources associated with an smbnode.
149  * Note: This is different from smbfs_inactive
150  *
151  * NFS: nfs_subr.c:rinactive
152  */
153 static void
sn_inactive(smbnode_t * np)154 sn_inactive(smbnode_t *np)
155 {
156 	vsecattr_t	ovsa;
157 	cred_t		*oldcr;
158 	char 		*orpath;
159 	int		orplen;
160 
161 	/*
162 	 * Flush and invalidate all pages (todo)
163 	 * Free any held credentials and caches...
164 	 * etc.  (See NFS code)
165 	 */
166 	mutex_enter(&np->r_statelock);
167 
168 	ovsa = np->r_secattr;
169 	np->r_secattr = smbfs_vsa0;
170 	np->r_sectime = 0;
171 
172 	oldcr = np->r_cred;
173 	np->r_cred = NULL;
174 
175 	orpath = np->n_rpath;
176 	orplen = np->n_rplen;
177 	np->n_rpath = NULL;
178 	np->n_rplen = 0;
179 
180 	mutex_exit(&np->r_statelock);
181 
182 	if (ovsa.vsa_aclentp != NULL)
183 		kmem_free(ovsa.vsa_aclentp, ovsa.vsa_aclentsz);
184 
185 	if (oldcr != NULL)
186 		crfree(oldcr);
187 
188 	if (orpath != NULL)
189 		kmem_free(orpath, orplen + 1);
190 }
191 
192 /*
193  * Find and optionally create an smbnode for the passed
194  * mountinfo, directory, separator, and name.  If the
195  * desired smbnode already exists, return a reference.
196  * If the file attributes pointer is non-null, the node
197  * is created if necessary and linked into the AVL tree.
198  *
199  * Callers that need a node created but don't have the
200  * real attributes pass smbfs_fattr0 to force creation.
201  *
202  * Note: make_smbnode() may upgrade the "hash" lock to exclusive.
203  *
204  * NFS: nfs_subr.c:makenfsnode
205  */
206 smbnode_t *
smbfs_node_findcreate(smbmntinfo_t * mi,const char * dirnm,int dirlen,const char * name,int nmlen,char sep,struct smbfattr * fap)207 smbfs_node_findcreate(
208 	smbmntinfo_t *mi,
209 	const char *dirnm,
210 	int dirlen,
211 	const char *name,
212 	int nmlen,
213 	char sep,
214 	struct smbfattr *fap)
215 {
216 	char tmpbuf[256];
217 	size_t rpalloc;
218 	char *p, *rpath;
219 	int rplen;
220 	smbnode_t *np;
221 	vnode_t *vp;
222 	int newnode;
223 
224 	/*
225 	 * Build the search string, either in tmpbuf or
226 	 * in allocated memory if larger than tmpbuf.
227 	 */
228 	rplen = dirlen;
229 	if (sep != '\0')
230 		rplen++;
231 	rplen += nmlen;
232 	if (rplen < sizeof (tmpbuf)) {
233 		/* use tmpbuf */
234 		rpalloc = 0;
235 		rpath = tmpbuf;
236 	} else {
237 		rpalloc = rplen + 1;
238 		rpath = kmem_alloc(rpalloc, KM_SLEEP);
239 	}
240 	p = rpath;
241 	bcopy(dirnm, p, dirlen);
242 	p += dirlen;
243 	if (sep != '\0')
244 		*p++ = sep;
245 	if (name != NULL) {
246 		bcopy(name, p, nmlen);
247 		p += nmlen;
248 	}
249 	ASSERT(p == rpath + rplen);
250 
251 	/*
252 	 * Find or create a node with this path.
253 	 */
254 	rw_enter(&mi->smi_hash_lk, RW_READER);
255 	if (fap == NULL)
256 		np = sn_hashfind(mi, rpath, rplen, NULL);
257 	else
258 		np = make_smbnode(mi, rpath, rplen, &newnode);
259 	rw_exit(&mi->smi_hash_lk);
260 
261 	if (rpalloc)
262 		kmem_free(rpath, rpalloc);
263 
264 	if (fap == NULL) {
265 		/*
266 		 * Caller is "just looking" (no create)
267 		 * so np may or may not be NULL here.
268 		 * Either way, we're done.
269 		 */
270 		return (np);
271 	}
272 
273 	/*
274 	 * We should have a node, possibly created.
275 	 * Do we have (real) attributes to apply?
276 	 */
277 	ASSERT(np != NULL);
278 	if (fap == &smbfs_fattr0)
279 		return (np);
280 
281 	/*
282 	 * Apply the given attributes to this node,
283 	 * dealing with any cache impact, etc.
284 	 */
285 	vp = SMBTOV(np);
286 	if (!newnode) {
287 		/*
288 		 * Found an existing node.
289 		 * Maybe purge caches...
290 		 */
291 		smbfs_cache_check(vp, fap);
292 	}
293 	smbfs_attrcache_fa(vp, fap);
294 
295 	/*
296 	 * Note NFS sets vp->v_type here, assuming it
297 	 * can never change for the life of a node.
298 	 * We allow v_type to change, and set it in
299 	 * smbfs_attrcache().  Also: mode, uid, gid
300 	 */
301 	return (np);
302 }
303 
304 /*
305  * NFS: nfs_subr.c:rtablehash
306  * We use smbfs_hash().
307  */
308 
309 /*
310  * Find or create an smbnode.
311  * NFS: nfs_subr.c:make_rnode
312  */
313 static smbnode_t *
make_smbnode(smbmntinfo_t * mi,const char * rpath,int rplen,int * newnode)314 make_smbnode(
315 	smbmntinfo_t *mi,
316 	const char *rpath,
317 	int rplen,
318 	int *newnode)
319 {
320 	smbnode_t *np;
321 	smbnode_t *tnp;
322 	vnode_t *vp;
323 	vfs_t *vfsp;
324 	avl_index_t where;
325 	char *new_rpath = NULL;
326 
327 	ASSERT(RW_READ_HELD(&mi->smi_hash_lk));
328 	vfsp = mi->smi_vfsp;
329 
330 start:
331 	np = sn_hashfind(mi, rpath, rplen, NULL);
332 	if (np != NULL) {
333 		*newnode = 0;
334 		return (np);
335 	}
336 
337 	/* Note: will retake this lock below. */
338 	rw_exit(&mi->smi_hash_lk);
339 
340 	/*
341 	 * see if we can find something on the freelist
342 	 */
343 	mutex_enter(&smbfreelist_lock);
344 	if (smbfreelist != NULL && smbnodenew >= nsmbnode) {
345 		np = smbfreelist;
346 		sn_rmfree(np);
347 		mutex_exit(&smbfreelist_lock);
348 
349 		vp = SMBTOV(np);
350 
351 		if (np->r_flags & RHASHED) {
352 			smbmntinfo_t *tmp_mi = np->n_mount;
353 			ASSERT(tmp_mi != NULL);
354 			rw_enter(&tmp_mi->smi_hash_lk, RW_WRITER);
355 			mutex_enter(&vp->v_lock);
356 			if (vp->v_count > 1) {
357 				vp->v_count--;
358 				mutex_exit(&vp->v_lock);
359 				rw_exit(&tmp_mi->smi_hash_lk);
360 				/* start over */
361 				rw_enter(&mi->smi_hash_lk, RW_READER);
362 				goto start;
363 			}
364 			mutex_exit(&vp->v_lock);
365 			sn_rmhash_locked(np);
366 			rw_exit(&tmp_mi->smi_hash_lk);
367 		}
368 
369 		sn_inactive(np);
370 
371 		mutex_enter(&vp->v_lock);
372 		if (vp->v_count > 1) {
373 			vp->v_count--;
374 			mutex_exit(&vp->v_lock);
375 			rw_enter(&mi->smi_hash_lk, RW_READER);
376 			goto start;
377 		}
378 		mutex_exit(&vp->v_lock);
379 		vn_invalid(vp);
380 		/*
381 		 * destroy old locks before bzero'ing and
382 		 * recreating the locks below.
383 		 */
384 		smbfs_rw_destroy(&np->r_rwlock);
385 		smbfs_rw_destroy(&np->r_lkserlock);
386 		mutex_destroy(&np->r_statelock);
387 		cv_destroy(&np->r_cv);
388 		/*
389 		 * Make sure that if smbnode is recycled then
390 		 * VFS count is decremented properly before
391 		 * reuse.
392 		 */
393 		VFS_RELE(vp->v_vfsp);
394 		vn_reinit(vp);
395 	} else {
396 		/*
397 		 * allocate and initialize a new smbnode
398 		 */
399 		vnode_t *new_vp;
400 
401 		mutex_exit(&smbfreelist_lock);
402 
403 		np = kmem_cache_alloc(smbnode_cache, KM_SLEEP);
404 		new_vp = vn_alloc(KM_SLEEP);
405 
406 		atomic_inc_ulong((ulong_t *)&smbnodenew);
407 		vp = new_vp;
408 	}
409 
410 	/*
411 	 * Allocate and copy the rpath we'll need below.
412 	 */
413 	new_rpath = kmem_alloc(rplen + 1, KM_SLEEP);
414 	bcopy(rpath, new_rpath, rplen);
415 	new_rpath[rplen] = '\0';
416 
417 	/* Initialize smbnode_t */
418 	bzero(np, sizeof (*np));
419 
420 	smbfs_rw_init(&np->r_rwlock, NULL, RW_DEFAULT, NULL);
421 	smbfs_rw_init(&np->r_lkserlock, NULL, RW_DEFAULT, NULL);
422 	mutex_init(&np->r_statelock, NULL, MUTEX_DEFAULT, NULL);
423 	cv_init(&np->r_cv, NULL, CV_DEFAULT, NULL);
424 	/* cv_init(&np->r_commit.c_cv, NULL, CV_DEFAULT, NULL); */
425 
426 	np->r_vnode = vp;
427 	np->n_mount = mi;
428 
429 	np->n_fid = SMB_FID_UNUSED;
430 	np->n_uid = mi->smi_uid;
431 	np->n_gid = mi->smi_gid;
432 	/* Leave attributes "stale." */
433 
434 #if 0 /* XXX dircache */
435 	/*
436 	 * We don't know if it's a directory yet.
437 	 * Let the caller do this?  XXX
438 	 */
439 	avl_create(&np->r_dir, compar, sizeof (rddir_cache),
440 	    offsetof(rddir_cache, tree));
441 #endif
442 
443 	/* Now fill in the vnode. */
444 	vn_setops(vp, smbfs_vnodeops);
445 	vp->v_data = (caddr_t)np;
446 	VFS_HOLD(vfsp);
447 	vp->v_vfsp = vfsp;
448 	vp->v_type = VNON;
449 
450 	/*
451 	 * We entered with mi->smi_hash_lk held (reader).
452 	 * Retake it now, (as the writer).
453 	 * Will return with it held.
454 	 */
455 	rw_enter(&mi->smi_hash_lk, RW_WRITER);
456 
457 	/*
458 	 * There is a race condition where someone else
459 	 * may alloc the smbnode while no locks are held,
460 	 * so check again and recover if found.
461 	 */
462 	tnp = sn_hashfind(mi, rpath, rplen, &where);
463 	if (tnp != NULL) {
464 		/*
465 		 * Lost the race.  Put the node we were building
466 		 * on the free list and return the one we found.
467 		 */
468 		rw_exit(&mi->smi_hash_lk);
469 		kmem_free(new_rpath, rplen + 1);
470 		smbfs_addfree(np);
471 		rw_enter(&mi->smi_hash_lk, RW_READER);
472 		*newnode = 0;
473 		return (tnp);
474 	}
475 
476 	/*
477 	 * Hash search identifies nodes by the remote path
478 	 * (n_rpath) so fill that in now, before linking
479 	 * this node into the node cache (AVL tree).
480 	 */
481 	np->n_rpath = new_rpath;
482 	np->n_rplen = rplen;
483 	np->n_ino = smbfs_gethash(new_rpath, rplen);
484 
485 	sn_addhash_locked(np, where);
486 	*newnode = 1;
487 	return (np);
488 }
489 
490 /*
491  * smbfs_addfree
492  * Put an smbnode on the free list, or destroy it immediately
493  * if it offers no value were it to be reclaimed later.  Also
494  * destroy immediately when we have too many smbnodes, etc.
495  *
496  * Normally called by smbfs_inactive, but also
497  * called in here during cleanup operations.
498  *
499  * NFS: nfs_subr.c:rp_addfree
500  */
501 void
smbfs_addfree(smbnode_t * np)502 smbfs_addfree(smbnode_t *np)
503 {
504 	vnode_t *vp;
505 	struct vfs *vfsp;
506 	smbmntinfo_t *mi;
507 
508 	ASSERT(np->r_freef == NULL && np->r_freeb == NULL);
509 
510 	vp = SMBTOV(np);
511 	ASSERT(vp->v_count >= 1);
512 
513 	vfsp = vp->v_vfsp;
514 	mi = VFTOSMI(vfsp);
515 
516 	/*
517 	 * If there are no more references to this smbnode and:
518 	 * we have too many smbnodes allocated, or if the node
519 	 * is no longer accessible via the AVL tree (!RHASHED),
520 	 * or an i/o error occurred while writing to the file,
521 	 * or it's part of an unmounted FS, then try to destroy
522 	 * it instead of putting it on the smbnode freelist.
523 	 */
524 	if (np->r_count == 0 && (
525 	    (np->r_flags & RHASHED) == 0 ||
526 	    (np->r_error != 0) ||
527 	    (vfsp->vfs_flag & VFS_UNMOUNTED) ||
528 	    (smbnodenew > nsmbnode))) {
529 
530 		/* Try to destroy this node. */
531 
532 		if (np->r_flags & RHASHED) {
533 			rw_enter(&mi->smi_hash_lk, RW_WRITER);
534 			mutex_enter(&vp->v_lock);
535 			if (vp->v_count > 1) {
536 				vp->v_count--;
537 				mutex_exit(&vp->v_lock);
538 				rw_exit(&mi->smi_hash_lk);
539 				return;
540 				/*
541 				 * Will get another call later,
542 				 * via smbfs_inactive.
543 				 */
544 			}
545 			mutex_exit(&vp->v_lock);
546 			sn_rmhash_locked(np);
547 			rw_exit(&mi->smi_hash_lk);
548 		}
549 
550 		sn_inactive(np);
551 
552 		/*
553 		 * Recheck the vnode reference count.  We need to
554 		 * make sure that another reference has not been
555 		 * acquired while we were not holding v_lock.  The
556 		 * smbnode is not in the smbnode "hash" AVL tree, so
557 		 * the only way for a reference to have been acquired
558 		 * is for a VOP_PUTPAGE because the smbnode was marked
559 		 * with RDIRTY or for a modified page.  This vnode
560 		 * reference may have been acquired before our call
561 		 * to sn_inactive.  The i/o may have been completed,
562 		 * thus allowing sn_inactive to complete, but the
563 		 * reference to the vnode may not have been released
564 		 * yet.  In any case, the smbnode can not be destroyed
565 		 * until the other references to this vnode have been
566 		 * released.  The other references will take care of
567 		 * either destroying the smbnode or placing it on the
568 		 * smbnode freelist.  If there are no other references,
569 		 * then the smbnode may be safely destroyed.
570 		 */
571 		mutex_enter(&vp->v_lock);
572 		if (vp->v_count > 1) {
573 			vp->v_count--;
574 			mutex_exit(&vp->v_lock);
575 			return;
576 		}
577 		mutex_exit(&vp->v_lock);
578 
579 		sn_destroy_node(np);
580 		return;
581 	}
582 
583 	/*
584 	 * Lock the AVL tree and then recheck the reference count
585 	 * to ensure that no other threads have acquired a reference
586 	 * to indicate that the smbnode should not be placed on the
587 	 * freelist.  If another reference has been acquired, then
588 	 * just release this one and let the other thread complete
589 	 * the processing of adding this smbnode to the freelist.
590 	 */
591 	rw_enter(&mi->smi_hash_lk, RW_WRITER);
592 
593 	mutex_enter(&vp->v_lock);
594 	if (vp->v_count > 1) {
595 		vp->v_count--;
596 		mutex_exit(&vp->v_lock);
597 		rw_exit(&mi->smi_hash_lk);
598 		return;
599 	}
600 	mutex_exit(&vp->v_lock);
601 
602 	/*
603 	 * Put this node on the free list.
604 	 */
605 	mutex_enter(&smbfreelist_lock);
606 	if (smbfreelist == NULL) {
607 		np->r_freef = np;
608 		np->r_freeb = np;
609 		smbfreelist = np;
610 	} else {
611 		np->r_freef = smbfreelist;
612 		np->r_freeb = smbfreelist->r_freeb;
613 		smbfreelist->r_freeb->r_freef = np;
614 		smbfreelist->r_freeb = np;
615 	}
616 	mutex_exit(&smbfreelist_lock);
617 
618 	rw_exit(&mi->smi_hash_lk);
619 }
620 
621 /*
622  * Remove an smbnode from the free list.
623  *
624  * The caller must be holding smbfreelist_lock and the smbnode
625  * must be on the freelist.
626  *
627  * NFS: nfs_subr.c:rp_rmfree
628  */
629 static void
sn_rmfree(smbnode_t * np)630 sn_rmfree(smbnode_t *np)
631 {
632 
633 	ASSERT(MUTEX_HELD(&smbfreelist_lock));
634 	ASSERT(np->r_freef != NULL && np->r_freeb != NULL);
635 
636 	if (np == smbfreelist) {
637 		smbfreelist = np->r_freef;
638 		if (np == smbfreelist)
639 			smbfreelist = NULL;
640 	}
641 
642 	np->r_freeb->r_freef = np->r_freef;
643 	np->r_freef->r_freeb = np->r_freeb;
644 
645 	np->r_freef = np->r_freeb = NULL;
646 }
647 
648 /*
649  * Put an smbnode in the "hash" AVL tree.
650  *
651  * The caller must be hold the rwlock as writer.
652  *
653  * NFS: nfs_subr.c:rp_addhash
654  */
655 static void
sn_addhash_locked(smbnode_t * np,avl_index_t where)656 sn_addhash_locked(smbnode_t *np, avl_index_t where)
657 {
658 	smbmntinfo_t *mi = np->n_mount;
659 
660 	ASSERT(RW_WRITE_HELD(&mi->smi_hash_lk));
661 	ASSERT(!(np->r_flags & RHASHED));
662 
663 	avl_insert(&mi->smi_hash_avl, np, where);
664 
665 	mutex_enter(&np->r_statelock);
666 	np->r_flags |= RHASHED;
667 	mutex_exit(&np->r_statelock);
668 }
669 
670 /*
671  * Remove an smbnode from the "hash" AVL tree.
672  *
673  * The caller must hold the rwlock as writer.
674  *
675  * NFS: nfs_subr.c:rp_rmhash_locked
676  */
677 static void
sn_rmhash_locked(smbnode_t * np)678 sn_rmhash_locked(smbnode_t *np)
679 {
680 	smbmntinfo_t *mi = np->n_mount;
681 
682 	ASSERT(RW_WRITE_HELD(&mi->smi_hash_lk));
683 	ASSERT(np->r_flags & RHASHED);
684 
685 	avl_remove(&mi->smi_hash_avl, np);
686 
687 	mutex_enter(&np->r_statelock);
688 	np->r_flags &= ~RHASHED;
689 	mutex_exit(&np->r_statelock);
690 }
691 
692 /*
693  * Remove an smbnode from the "hash" AVL tree.
694  *
695  * The caller must not be holding the rwlock.
696  */
697 void
smbfs_rmhash(smbnode_t * np)698 smbfs_rmhash(smbnode_t *np)
699 {
700 	smbmntinfo_t *mi = np->n_mount;
701 
702 	rw_enter(&mi->smi_hash_lk, RW_WRITER);
703 	sn_rmhash_locked(np);
704 	rw_exit(&mi->smi_hash_lk);
705 }
706 
707 /*
708  * Lookup an smbnode by remote pathname
709  *
710  * The caller must be holding the AVL rwlock, either shared or exclusive.
711  *
712  * NFS: nfs_subr.c:rfind
713  */
714 static smbnode_t *
sn_hashfind(smbmntinfo_t * mi,const char * rpath,int rplen,avl_index_t * pwhere)715 sn_hashfind(
716 	smbmntinfo_t *mi,
717 	const char *rpath,
718 	int rplen,
719 	avl_index_t *pwhere) /* optional */
720 {
721 	smbfs_node_hdr_t nhdr;
722 	smbnode_t *np;
723 	vnode_t *vp;
724 
725 	ASSERT(RW_LOCK_HELD(&mi->smi_hash_lk));
726 
727 	bzero(&nhdr, sizeof (nhdr));
728 	nhdr.hdr_n_rpath = (char *)rpath;
729 	nhdr.hdr_n_rplen = rplen;
730 
731 	/* See smbfs_node_cmp below. */
732 	np = avl_find(&mi->smi_hash_avl, &nhdr, pwhere);
733 
734 	if (np == NULL)
735 		return (NULL);
736 
737 	/*
738 	 * Found it in the "hash" AVL tree.
739 	 * Remove from free list, if necessary.
740 	 */
741 	vp = SMBTOV(np);
742 	if (np->r_freef != NULL) {
743 		mutex_enter(&smbfreelist_lock);
744 		/*
745 		 * If the smbnode is on the freelist,
746 		 * then remove it and use that reference
747 		 * as the new reference.  Otherwise,
748 		 * need to increment the reference count.
749 		 */
750 		if (np->r_freef != NULL) {
751 			sn_rmfree(np);
752 			mutex_exit(&smbfreelist_lock);
753 		} else {
754 			mutex_exit(&smbfreelist_lock);
755 			VN_HOLD(vp);
756 		}
757 	} else
758 		VN_HOLD(vp);
759 
760 	return (np);
761 }
762 
763 static int
smbfs_node_cmp(const void * va,const void * vb)764 smbfs_node_cmp(const void *va, const void *vb)
765 {
766 	const smbfs_node_hdr_t *a = va;
767 	const smbfs_node_hdr_t *b = vb;
768 	int clen, diff;
769 
770 	/*
771 	 * Same semantics as strcmp, but does not
772 	 * assume the strings are null terminated.
773 	 */
774 	clen = (a->hdr_n_rplen < b->hdr_n_rplen) ?
775 	    a->hdr_n_rplen : b->hdr_n_rplen;
776 	diff = strncmp(a->hdr_n_rpath, b->hdr_n_rpath, clen);
777 	if (diff < 0)
778 		return (-1);
779 	if (diff > 0)
780 		return (1);
781 	/* they match through clen */
782 	if (b->hdr_n_rplen > clen)
783 		return (-1);
784 	if (a->hdr_n_rplen > clen)
785 		return (1);
786 	return (0);
787 }
788 
789 /*
790  * Setup the "hash" AVL tree used for our node cache.
791  * See: smbfs_mount, smbfs_destroy_table.
792  */
793 void
smbfs_init_hash_avl(avl_tree_t * avl)794 smbfs_init_hash_avl(avl_tree_t *avl)
795 {
796 	avl_create(avl, smbfs_node_cmp, sizeof (smbnode_t),
797 	    offsetof(smbnode_t, r_avl_node));
798 }
799 
800 /*
801  * Invalidate the cached attributes for all nodes "under" the
802  * passed-in node.  Note: the passed-in node is NOT affected by
803  * this call.  This is used both for files under some directory
804  * after the directory is deleted or renamed, and for extended
805  * attribute files (named streams) under a plain file after that
806  * file is renamed or deleted.
807  *
808  * Do this by walking the AVL tree starting at the passed in node,
809  * and continuing while the visited nodes have a path prefix matching
810  * the entire path of the passed-in node, and a separator just after
811  * that matching path prefix.  Watch out for cases where the AVL tree
812  * order may not exactly match the order of an FS walk, i.e.
813  * consider this sequence:
814  *	"foo"		(directory)
815  *	"foo bar"	(name containing a space)
816  *	"foo/bar"
817  * The walk needs to skip "foo bar" and keep going until it finds
818  * something that doesn't match the "foo" name prefix.
819  */
820 void
smbfs_attrcache_prune(smbnode_t * top_np)821 smbfs_attrcache_prune(smbnode_t *top_np)
822 {
823 	smbmntinfo_t *mi;
824 	smbnode_t *np;
825 	char *rpath;
826 	int rplen;
827 
828 	mi = top_np->n_mount;
829 	rw_enter(&mi->smi_hash_lk, RW_READER);
830 
831 	np = top_np;
832 	rpath = top_np->n_rpath;
833 	rplen = top_np->n_rplen;
834 	for (;;) {
835 		np = avl_walk(&mi->smi_hash_avl, np, AVL_AFTER);
836 		if (np == NULL)
837 			break;
838 		if (np->n_rplen < rplen)
839 			break;
840 		if (0 != strncmp(np->n_rpath, rpath, rplen))
841 			break;
842 		if (np->n_rplen > rplen && (
843 		    np->n_rpath[rplen] == ':' ||
844 		    np->n_rpath[rplen] == '\\'))
845 			smbfs_attrcache_remove(np);
846 	}
847 
848 	rw_exit(&mi->smi_hash_lk);
849 }
850 
851 #ifdef SMB_VNODE_DEBUG
852 int smbfs_check_table_debug = 1;
853 #else /* SMB_VNODE_DEBUG */
854 int smbfs_check_table_debug = 0;
855 #endif /* SMB_VNODE_DEBUG */
856 
857 
858 /*
859  * Return 1 if there is a active vnode belonging to this vfs in the
860  * smbnode cache.
861  *
862  * Several of these checks are done without holding the usual
863  * locks.  This is safe because destroy_smbtable(), smbfs_addfree(),
864  * etc. will redo the necessary checks before actually destroying
865  * any smbnodes.
866  *
867  * NFS: nfs_subr.c:check_rtable
868  *
869  * Debugging changes here relative to NFS.
870  * Relatively harmless, so left 'em in.
871  */
872 int
smbfs_check_table(struct vfs * vfsp,smbnode_t * rtnp)873 smbfs_check_table(struct vfs *vfsp, smbnode_t *rtnp)
874 {
875 	smbmntinfo_t *mi;
876 	smbnode_t *np;
877 	vnode_t *vp;
878 	int busycnt = 0;
879 
880 	mi = VFTOSMI(vfsp);
881 	rw_enter(&mi->smi_hash_lk, RW_READER);
882 	for (np = avl_first(&mi->smi_hash_avl); np != NULL;
883 	    np = avl_walk(&mi->smi_hash_avl, np, AVL_AFTER)) {
884 
885 		if (np == rtnp)
886 			continue; /* skip the root */
887 		vp = SMBTOV(np);
888 
889 		/* Now the 'busy' checks: */
890 		/* Not on the free list? */
891 		if (np->r_freef == NULL) {
892 			SMBVDEBUG("!r_freef: node=0x%p, rpath=%s\n",
893 			    (void *)np, np->n_rpath);
894 			busycnt++;
895 		}
896 
897 		/* Has dirty pages? */
898 		if (vn_has_cached_data(vp) &&
899 		    (np->r_flags & RDIRTY)) {
900 			SMBVDEBUG("is dirty: node=0x%p, rpath=%s\n",
901 			    (void *)np, np->n_rpath);
902 			busycnt++;
903 		}
904 
905 		/* Other refs? (not reflected in v_count) */
906 		if (np->r_count > 0) {
907 			SMBVDEBUG("+r_count: node=0x%p, rpath=%s\n",
908 			    (void *)np, np->n_rpath);
909 			busycnt++;
910 		}
911 
912 		if (busycnt && !smbfs_check_table_debug)
913 			break;
914 
915 	}
916 	rw_exit(&mi->smi_hash_lk);
917 
918 	return (busycnt);
919 }
920 
921 /*
922  * Destroy inactive vnodes from the AVL tree which belong to this
923  * vfs.  It is essential that we destroy all inactive vnodes during a
924  * forced unmount as well as during a normal unmount.
925  *
926  * NFS: nfs_subr.c:destroy_rtable
927  *
928  * In here, we're normally destrying all or most of the AVL tree,
929  * so the natural choice is to use avl_destroy_nodes.  However,
930  * there may be a few busy nodes that should remain in the AVL
931  * tree when we're done.  The solution: use a temporary tree to
932  * hold the busy nodes until we're done destroying the old tree,
933  * then copy the temporary tree over the (now emtpy) real tree.
934  */
935 void
smbfs_destroy_table(struct vfs * vfsp)936 smbfs_destroy_table(struct vfs *vfsp)
937 {
938 	avl_tree_t tmp_avl;
939 	smbmntinfo_t *mi;
940 	smbnode_t *np;
941 	smbnode_t *rlist;
942 	void *v;
943 
944 	mi = VFTOSMI(vfsp);
945 	rlist = NULL;
946 	smbfs_init_hash_avl(&tmp_avl);
947 
948 	rw_enter(&mi->smi_hash_lk, RW_WRITER);
949 	v = NULL;
950 	while ((np = avl_destroy_nodes(&mi->smi_hash_avl, &v)) != NULL) {
951 
952 		mutex_enter(&smbfreelist_lock);
953 		if (np->r_freef == NULL) {
954 			/*
955 			 * Busy node (not on the free list).
956 			 * Will keep in the final AVL tree.
957 			 */
958 			mutex_exit(&smbfreelist_lock);
959 			avl_add(&tmp_avl, np);
960 		} else {
961 			/*
962 			 * It's on the free list.  Remove and
963 			 * arrange for it to be destroyed.
964 			 */
965 			sn_rmfree(np);
966 			mutex_exit(&smbfreelist_lock);
967 
968 			/*
969 			 * Last part of sn_rmhash_locked().
970 			 * NB: avl_destroy_nodes has already
971 			 * removed this from the "hash" AVL.
972 			 */
973 			mutex_enter(&np->r_statelock);
974 			np->r_flags &= ~RHASHED;
975 			mutex_exit(&np->r_statelock);
976 
977 			/*
978 			 * Add to the list of nodes to destroy.
979 			 * Borrowing avl_child[0] for this list.
980 			 */
981 			np->r_avl_node.avl_child[0] =
982 			    (struct avl_node *)rlist;
983 			rlist = np;
984 		}
985 	}
986 	avl_destroy(&mi->smi_hash_avl);
987 
988 	/*
989 	 * Replace the (now destroyed) "hash" AVL with the
990 	 * temporary AVL, which restores the busy nodes.
991 	 */
992 	mi->smi_hash_avl = tmp_avl;
993 	rw_exit(&mi->smi_hash_lk);
994 
995 	/*
996 	 * Now destroy the nodes on our temporary list (rlist).
997 	 * This call to smbfs_addfree will end up destroying the
998 	 * smbnode, but in a safe way with the appropriate set
999 	 * of checks done.
1000 	 */
1001 	while ((np = rlist) != NULL) {
1002 		rlist = (smbnode_t *)np->r_avl_node.avl_child[0];
1003 		smbfs_addfree(np);
1004 	}
1005 }
1006 
1007 /*
1008  * This routine destroys all the resources associated with the smbnode
1009  * and then the smbnode itself.  Note: sn_inactive has been called.
1010  *
1011  * NFS: nfs_subr.c:destroy_rnode
1012  */
1013 static void
sn_destroy_node(smbnode_t * np)1014 sn_destroy_node(smbnode_t *np)
1015 {
1016 	vnode_t *vp;
1017 	vfs_t *vfsp;
1018 
1019 	vp = SMBTOV(np);
1020 	vfsp = vp->v_vfsp;
1021 
1022 	ASSERT(vp->v_count == 1);
1023 	ASSERT(np->r_count == 0);
1024 	ASSERT(np->r_mapcnt == 0);
1025 	ASSERT(np->r_secattr.vsa_aclentp == NULL);
1026 	ASSERT(np->r_cred == NULL);
1027 	ASSERT(np->n_rpath == NULL);
1028 	ASSERT(!(np->r_flags & RHASHED));
1029 	ASSERT(np->r_freef == NULL && np->r_freeb == NULL);
1030 	atomic_dec_ulong((ulong_t *)&smbnodenew);
1031 	vn_invalid(vp);
1032 	vn_free(vp);
1033 	kmem_cache_free(smbnode_cache, np);
1034 	VFS_RELE(vfsp);
1035 }
1036 
1037 /*
1038  * Flush all vnodes in this (or every) vfs.
1039  * Used by nfs_sync and by nfs_unmount.
1040  */
1041 /*ARGSUSED*/
1042 void
smbfs_rflush(struct vfs * vfsp,cred_t * cr)1043 smbfs_rflush(struct vfs *vfsp, cred_t *cr)
1044 {
1045 	/* Todo: mmap support. */
1046 }
1047 
1048 /* access cache */
1049 /* client handles */
1050 
1051 /*
1052  * initialize resources that are used by smbfs_subr.c
1053  * this is called from the _init() routine (by the way of smbfs_clntinit())
1054  *
1055  * NFS: nfs_subr.c:nfs_subrinit
1056  */
1057 int
smbfs_subrinit(void)1058 smbfs_subrinit(void)
1059 {
1060 	ulong_t nsmbnode_max;
1061 
1062 	/*
1063 	 * Allocate and initialize the smbnode cache
1064 	 */
1065 	if (nsmbnode <= 0)
1066 		nsmbnode = ncsize; /* dnlc.h */
1067 	nsmbnode_max = (ulong_t)((kmem_maxavail() >> 2) /
1068 	    sizeof (struct smbnode));
1069 	if (nsmbnode > nsmbnode_max || (nsmbnode == 0 && ncsize == 0)) {
1070 		zcmn_err(GLOBAL_ZONEID, CE_NOTE,
1071 		    "setting nsmbnode to max value of %ld", nsmbnode_max);
1072 		nsmbnode = nsmbnode_max;
1073 	}
1074 
1075 	smbnode_cache = kmem_cache_create("smbnode_cache", sizeof (smbnode_t),
1076 	    0, NULL, NULL, smbfs_kmem_reclaim, NULL, NULL, 0);
1077 
1078 	/*
1079 	 * Initialize the various mutexes and reader/writer locks
1080 	 */
1081 	mutex_init(&smbfreelist_lock, NULL, MUTEX_DEFAULT, NULL);
1082 	mutex_init(&smbfs_minor_lock, NULL, MUTEX_DEFAULT, NULL);
1083 
1084 	/*
1085 	 * Assign unique major number for all smbfs mounts
1086 	 */
1087 	if ((smbfs_major = getudev()) == -1) {
1088 		zcmn_err(GLOBAL_ZONEID, CE_WARN,
1089 		    "smbfs: init: can't get unique device number");
1090 		smbfs_major = 0;
1091 	}
1092 	smbfs_minor = 0;
1093 
1094 	return (0);
1095 }
1096 
1097 /*
1098  * free smbfs hash table, etc.
1099  * NFS: nfs_subr.c:nfs_subrfini
1100  */
1101 void
smbfs_subrfini(void)1102 smbfs_subrfini(void)
1103 {
1104 
1105 	/*
1106 	 * Destroy the smbnode cache
1107 	 */
1108 	kmem_cache_destroy(smbnode_cache);
1109 
1110 	/*
1111 	 * Destroy the various mutexes and reader/writer locks
1112 	 */
1113 	mutex_destroy(&smbfreelist_lock);
1114 	mutex_destroy(&smbfs_minor_lock);
1115 }
1116 
1117 /* rddir_cache ? */
1118 
1119 /*
1120  * Support functions for smbfs_kmem_reclaim
1121  */
1122 
1123 static void
smbfs_node_reclaim(void)1124 smbfs_node_reclaim(void)
1125 {
1126 	smbmntinfo_t *mi;
1127 	smbnode_t *np;
1128 	vnode_t *vp;
1129 
1130 	mutex_enter(&smbfreelist_lock);
1131 	while ((np = smbfreelist) != NULL) {
1132 		sn_rmfree(np);
1133 		mutex_exit(&smbfreelist_lock);
1134 		if (np->r_flags & RHASHED) {
1135 			vp = SMBTOV(np);
1136 			mi = np->n_mount;
1137 			rw_enter(&mi->smi_hash_lk, RW_WRITER);
1138 			mutex_enter(&vp->v_lock);
1139 			if (vp->v_count > 1) {
1140 				vp->v_count--;
1141 				mutex_exit(&vp->v_lock);
1142 				rw_exit(&mi->smi_hash_lk);
1143 				mutex_enter(&smbfreelist_lock);
1144 				continue;
1145 			}
1146 			mutex_exit(&vp->v_lock);
1147 			sn_rmhash_locked(np);
1148 			rw_exit(&mi->smi_hash_lk);
1149 		}
1150 		/*
1151 		 * This call to smbfs_addfree will end up destroying the
1152 		 * smbnode, but in a safe way with the appropriate set
1153 		 * of checks done.
1154 		 */
1155 		smbfs_addfree(np);
1156 		mutex_enter(&smbfreelist_lock);
1157 	}
1158 	mutex_exit(&smbfreelist_lock);
1159 }
1160 
1161 /*
1162  * Called by kmem_cache_alloc ask us if we could
1163  * "Please give back some memory!"
1164  *
1165  * Todo: dump nodes from the free list?
1166  */
1167 /*ARGSUSED*/
1168 void
smbfs_kmem_reclaim(void * cdrarg)1169 smbfs_kmem_reclaim(void *cdrarg)
1170 {
1171 	smbfs_node_reclaim();
1172 }
1173 
1174 /* nfs failover stuff */
1175 /* nfs_rw_xxx - see smbfs_rwlock.c */
1176