lofs_subr.c revision 79a28c7a4d69d338ccb4015ce7a43131f2dbf6a5
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 2008 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
/*
* The idea behind composition-based stacked filesystems is to add a
* vnode to the stack of vnodes for each mount. These vnodes have their
* own set of mount options and filesystem-specific functions, so they
* can modify data or operations before they are passed along. Such a
* filesystem must maintain a mapping from the underlying vnodes to its
* interposing vnodes.
*
* In lofs, this mapping is implemented by a hashtable. Each bucket
* contains a count of the number of nodes currently contained, the
* chain of vnodes, and a lock to protect the list of vnodes. The
* hashtable dynamically grows if the number of vnodes in the table as a
* whole exceeds the size of the table left-shifted by
* lo_resize_threshold. In order to minimize lock contention, there is
* no global lock protecting the hashtable, hence obtaining the
* per-bucket locks consists of a dance to make sure we've actually
* locked the correct bucket. Acquiring a bucket lock doesn't involve
* locking the hashtable itself, so we refrain from freeing old
* hashtables, and store them in a linked list of retired hashtables;
* the list is freed when the filesystem is unmounted.
*/
/*
* Due to the hashing algorithm, the size of the hash table needs to be a
* power of 2.
*/
/*
* The following macros can only be safely used when the desired bucket
* is already locked.
*/
/*
* The lock in the hashtable associated with the given vnode.
*/
/*
* The bucket in the hashtable that the given vnode hashes to.
*/
/*
* Number of elements currently in the bucket that the vnode hashes to.
*/
/*
*/
static kmem_cache_t *lnode_cache;
/*
* Since the hashtable itself isn't protected by a lock, obtaining a
* per-bucket lock proceeds as follows:
*
* (a) li->li_htlock protects li->li_hashtable, li->li_htsize, and
* li->li_retired.
*
* (b) Per-bucket locks (lh_lock) protect the contents of the bucket.
*
* (c) Locking order for resizing the hashtable is li_htlock then
* lh_lock.
*
* To grab the bucket lock we:
*
* (1) Stash away the htsize and the pointer to the hashtable to make
* sure neither change while we're using them.
*
* (2) lgrow() updates the pointer to the hashtable before it updates
* the size: the worst case scenario is that we have the wrong size (but
* the correct table), so we hash to the wrong bucket, grab the wrong
* lock, and then realize that things have changed, rewind and start
* again. If both the size and the table changed since we loaded them,
* we'll realize that too and restart.
*
* (3) The protocol for growing the hashtable involves holding *all* the
* locks in the table, hence the unlocking code (TABLE_LOCK_EXIT())
* doesn't need to do any dances, since neither the table nor the size
* can change while any bucket lock is held.
*
* (4) If the hashtable is growing (by thread t1) while another thread
* (t2) is trying to grab a bucket lock, t2 might have a stale reference
* to li->li_htsize:
*
* - t1 grabs all locks in lgrow()
* - t2 loads li->li_htsize and li->li_hashtable
* - t1 changes li->hashtable
* - t2 loads from an offset in the "stale" hashtable and tries to grab
* the relevant mutex.
*
* If t1 had free'd the stale hashtable, t2 would be in trouble. Hence,
* stale hashtables are not freed but stored in a list of "retired"
* hashtables, which is emptied when the filesystem is unmounted.
*/
static void
{
for (;;) {
break;
}
}
void
lofs_subrinit(void)
{
/*
* Initialize the cache.
*/
}
void
lofs_subrfini(void)
{
}
/*
* Initialize a (struct loinfo), and initialize the hashtable to have
* htsize buckets.
*/
void
{
if (htsize == 0)
KM_SLEEP);
}
/*
* Destroy a (struct loinfo)
*/
void
{
for (i = 0; i < htsize; i++)
/*
* Free the retired hashtables.
*/
}
}
/*
* Return a looped back vnode for the given vnode.
* If no lnode exists for this vnode create one and put it
* in a table hashed by vnode. If the lnode for
* this vnode is already in the table return it (ref count is
* incremented by lfind). The lnode will be flushed from the
* table when lo_inactive calls freelonode. The creation of
* a new lnode can be forced via the LOF_FORCE flag even if
* the vnode exists in the table. This is used in the creation
* of a terminating lnode when looping is detected. A unique
* lnode is required for the correct evaluation of the current
* working directory.
* NOTE: vp is assumed to be a held vnode.
*/
struct vnode *
{
/*
* Optimistically assume that we won't need to sleep.
*/
/* The lnode allocation may have succeeded, save it */
}
}
goto found_lnode;
}
}
lp->lo_looping = 0;
} else {
}
}
/*
*/
static struct vfs *
{
/*
* Don't grab any locks for the fast (common) case.
*/
return (li->li_mountvfs);
goto found_lfs;
}
/*
* Even though the lfsnode is strictly speaking a private
* implementation detail of lofs, it should behave as a regular
* vfs_t for the benefit of the rest of the kernel.
*/
/* Leave a reference to the mountpoint */
}
/*
* We use 1 instead of 0 as the value to associate with
* an idle lfs_vfs. This is to prevent VFS_RELE()
* trying to kmem_free() our lfs_t (which is the wrong
* size).
*/
}
}
/*
* Free lfs node since no longer in use
*/
static void
{
else
}
sizeof (vfs_impl_t));
}
return;
}
}
panic("freelfsnode");
/*NOTREACHED*/
}
/*
* Find lfs given real vfs and mount instance(li)
*/
static struct lfsnode *
{
/*
* We need to handle the case where a UFS filesystem was forced
* unmounted and then a subsequent mount got the same vfs
* structure. If the new mount lies in the lofs hierarchy, then
* this will confuse lofs, because the original vfsp (of the
* forced unmounted filesystem) is still around. We check for
* this condition here.
*
* If we find a cache vfsp hit, then we check to see if the
* cached filesystem was forced unmounted. Skip all such
* entries. This should be safe to do since no
* makelonode()->makelfsnode()->lfsfind() calls should be
* generated for such force-unmounted filesystems (because (ufs)
* lookup would've returned an error).
*/
continue;
continue;
return (lfs);
}
}
return (NULL);
}
/*
* Find real vfs given loopback vfs
*/
struct vfs *
{
if (realrootvpp != NULL)
return (li->li_realvfs);
}
if (realrootvpp != NULL)
return (lfs->lfs_realvfs);
}
}
panic("lo_realvfs");
/*NOTREACHED*/
}
/*
* Lnode lookup stuff.
* These routines maintain a table of lnodes hashed by vp so
* that the lnode for a vp can be found if it already exists.
*
* NB: A lofs shadow vnode causes exactly one VN_HOLD() on the
* underlying vnode.
*/
/*
* Retire old hashtables.
*/
static void
{
struct lo_retired_ht *lrhp;
}
/*
* Grow the hashtable.
*/
static void
{
uint_t i;
/*
* It's OK to not have enough memory to resize the hashtable.
* We'll go down this path the next time we add something to the
* table, and retry the allocation then.
*/
KM_NOSLEEP)) == NULL)
return;
return;
}
/*
* Grab all locks so TABLE_LOCK_ENTER() calls block until the
* resize is complete.
*/
for (i = 0; i < oldsize; i++)
/*
* li->li_hashtable gets set before li->li_htsize, so in the
* time between the two assignments, callers of
* TABLE_LOCK_ENTER() cannot hash to a bucket beyond oldsize,
* hence we only need to grab the locks up to oldsize.
*/
for (i = 0; i < oldsize; i++)
/*
* Rehash.
*/
for (i = 0; i < oldsize; i++) {
}
}
/*
* As soon as we store the new hashtable, future locking operations
* will use it. Therefore, we must ensure that all the state we've
* just established reaches global visibility before the new hashtable
* does.
*/
/*
* table_lock_enter() relies on the fact that li->li_hashtable
* is set to its new value before li->li_htsize.
*/
/*
* The new state is consistent now, so we can drop all the locks.
*/
for (i = 0; i < oldsize; i++) {
}
}
/*
* Put a lnode in the table
*/
static void
{
#ifdef LODEBUG
#endif
}
}
/*
* Our version of vfs_rele() that stops at 1 instead of 0, and calls
* freelfsnode() instead of kmem_free().
*/
static void
{
}
/*
* Remove a lnode from the table
*/
void
{
#ifdef LODEBUG
#endif
return;
}
#ifdef LODEBUG
#endif
vn_invalid(vp);
/*
* Check for unused lfs
*/
break;
}
/*
* Lfs is idle
*/
}
}
}
} else {
}
return;
}
}
panic("freelonode");
/*NOTREACHED*/
}
/*
* Lookup a lnode by vp
*/
static lnode_t *
{
return (lt);
}
}
return (NULL);
}
#ifdef LODEBUG
static int lofsdebug;
#endif /* LODEBUG */
/*
* Utilities used by both client and server
* Standard levels:
* 0) no debugging
* 1) hard failures
* 2) soft failures
* 3) current test software
* 4) main procedure entry points
* 5) main procedure exit points
* 6) utility procedure entry points
* 7) utility procedure exit points
* 8) obscure procedure entry points
* 9) obscure procedure exit points
* 10) random stuff
* 11) all <= 1
* 12) all <= 2
* 13) all <= 3
* ...
*/
#ifdef LODEBUG
/*VARARGS2*/
int level;
char *str;
{
}
#endif