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