tech-kern archive

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index][Old Index]

Re: Fileassoc locking



Hey,

So... attached is a second attempt at fileassoc locking. (Entire source
file to make reading easier.) This time there are no rw locks, and I'm
using mutexes and condition variables. I've tested this version as well,
but once again, I'd appreciate a review for the changes.

One note: in fileassoc_lookup(), commented out is code that keeps a
reference to the "assoc" and the file entry. It's commented out because
it will require additional changes to fileassoc users (e.g. Veriexec and
PaX), but eventually we'll allow the users to say "just lookup" or
"lookup for further use," in which case we'll do the aforementioned
reference count bumps.

Thanks,

-e.
/* $NetBSD: kern_fileassoc.c,v 1.34 2009/12/25 20:07:18 elad Exp $ */

/*-
 * Copyright (c) 2006 Elad Efrat <elad%NetBSD.org@localhost>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: kern_fileassoc.c,v 1.34 2009/12/25 20:07:18 elad 
Exp $");

#include "opt_fileassoc.h"

#include <sys/param.h>
#include <sys/mount.h>
#include <sys/queue.h>
#include <sys/vnode.h>
#include <sys/errno.h>
#include <sys/fileassoc.h>
#include <sys/specificdata.h>
#include <sys/hash.h>
#include <sys/kmem.h>
#include <sys/once.h>

#define FILEASSOC_INITIAL_TABLESIZE     128

static specificdata_domain_t fileassoc_domain;
static specificdata_key_t fileassoc_mountspecific_key;
static ONCE_DECL(control);

/*
 * Assoc entry.
 * Includes the assoc name for identification and private clear callback.
 */
struct fileassoc {
        LIST_ENTRY(fileassoc) assoc_list;
        const char *assoc_name;                         /* Name. */
        fileassoc_cleanup_cb_t assoc_cleanup_cb;        /* Clear callback. */
        specificdata_key_t assoc_key;

        kmutex_t assoc_mtx;
        kcondvar_t assoc_cv;
        uint32_t assoc_refcnt;
};

static LIST_HEAD(, fileassoc) fileassoc_list;
static kmutex_t fileassoc_list_lock;

/* An entry in the per-mount hash table. */
struct fileassoc_file {
        fhandle_t *faf_handle;                          /* File handle */
        specificdata_reference faf_data;                /* Assoc data. */
        u_int faf_nassocs;                              /* # of assocs. */
        LIST_ENTRY(fileassoc_file) faf_list;            /* List pointer. */

        kmutex_t faf_mtx;
        kcondvar_t faf_cv;
        uint32_t faf_refcnt;
};

LIST_HEAD(fileassoc_hash_entry, fileassoc_file);

struct fileassoc_table {
        struct fileassoc_hash_entry *tbl_hash;
        u_long tbl_mask;                                /* Hash table mask. */
        size_t tbl_nslots;                              /* Number of slots. */
        size_t tbl_nused;                               /* # of used slots. */
        specificdata_reference tbl_data;

        kmutex_t tbl_mtx;
        kcondvar_t tbl_cv;
        uint32_t tbl_refcnt;
};

/*
 * Hashing function: Takes a number modulus the mask to give back an
 * index into the hash table.
 */
#define FILEASSOC_HASH(tbl, handle)     \
        (hash32_buf((handle), FHANDLE_SIZE(handle), HASH32_BUF_INIT) \
         & ((tbl)->tbl_mask))

static void
assoc_use(struct fileassoc *assoc)
{

        mutex_enter(&assoc->assoc_mtx);
        assoc->assoc_refcnt++;
        mutex_exit(&assoc->assoc_mtx);
}

static void
assoc_unuse(struct fileassoc *assoc)
{

        mutex_enter(&assoc->assoc_mtx);
        if (--assoc->assoc_refcnt == 0)
                cv_broadcast(&assoc->assoc_cv);
        mutex_exit(&assoc->assoc_mtx);
}

static void
file_use(struct fileassoc_file *faf)
{

        mutex_enter(&faf->faf_mtx);
        faf->faf_refcnt++;
        mutex_exit(&faf->faf_mtx);
}

static void
file_unuse(struct fileassoc_file *faf)
{

        mutex_enter(&faf->faf_mtx);
        if (--faf->faf_refcnt == 0)
                cv_broadcast(&faf->faf_cv);
        mutex_exit(&faf->faf_mtx);
}

static void
table_use(struct fileassoc_table *tbl)
{

        mutex_enter(&tbl->tbl_mtx);
        tbl->tbl_refcnt++;
        mutex_exit(&tbl->tbl_mtx);
}

static void
table_unuse(struct fileassoc_table *tbl)
{

        mutex_enter(&tbl->tbl_mtx);
        if (--tbl->tbl_refcnt == 0)
                cv_broadcast(&tbl->tbl_cv);
        mutex_exit(&tbl->tbl_mtx);
}

static void *
file_getdata(struct fileassoc_file *faf, const struct fileassoc *assoc)
{

        return specificdata_getspecific(fileassoc_domain, &faf->faf_data,
            assoc->assoc_key);
}

static void
file_setdata(struct fileassoc_file *faf, const struct fileassoc *assoc,
    void *data)
{

        specificdata_setspecific(fileassoc_domain, &faf->faf_data,
            assoc->assoc_key, data);
}

static void
file_cleanup(struct fileassoc_file *faf, const struct fileassoc *assoc)
{
        fileassoc_cleanup_cb_t cb;
        void *data;

        cb = assoc->assoc_cleanup_cb;
        if (cb == NULL) {
                return;
        }
        data = file_getdata(faf, assoc);
        (*cb)(data);
}

static void
file_free(struct fileassoc_file *faf)
{
        struct fileassoc *assoc;

        mutex_enter(&fileassoc_list_lock);
        LIST_FOREACH(assoc, &fileassoc_list, assoc_list) {
                assoc_use(assoc);
                file_cleanup(faf, assoc);
                assoc_unuse(assoc);
        }
        mutex_exit(&fileassoc_list_lock);

        vfs_composefh_free(faf->faf_handle);
        specificdata_fini(fileassoc_domain, &faf->faf_data);

        kmem_free(faf, sizeof(*faf));
}

/* Expects tbl to be held exclusively by the caller. */
static void
table_destroy(struct fileassoc_table *tbl)
{
        u_long i;

        /* Remove all entries from the table and lists */
        for (i = 0; i < tbl->tbl_nslots; i++) {
                struct fileassoc_file *faf;

                while ((faf = LIST_FIRST(&tbl->tbl_hash[i])) != NULL) {
                        /* Get exclusivity on this file. */
                        mutex_enter(&faf->faf_mtx);
                        while (faf->faf_refcnt > 0)
                                cv_wait(&faf->faf_cv, &faf->faf_mtx);

                        /* Remove it... */
                        LIST_REMOVE(faf, faf_list);

                        mutex_exit(&faf->faf_mtx);

                        file_free(faf);
                }
        }

        hashdone(tbl->tbl_hash, HASH_LIST, tbl->tbl_mask);
        specificdata_fini(fileassoc_domain, &tbl->tbl_data);

        mutex_exit(&tbl->tbl_mtx);

        kmem_free(tbl, sizeof(*tbl));
}

static void
table_dtor(void *v)
{
        struct fileassoc_table *tbl = v;

        /* Get exclusivity on this table. */
        mutex_enter(&tbl->tbl_mtx);
        while (tbl->tbl_refcnt > 0)
                cv_wait(&tbl->tbl_cv, &tbl->tbl_mtx);

        table_destroy(tbl);
}

/*
 * Initialize the fileassoc subsystem.
 */
static int
fileassoc_init(void)
{
        int error;

        error = mount_specific_key_create(&fileassoc_mountspecific_key,
            table_dtor);
        if (error) {
                return error;
        }
        fileassoc_domain = specificdata_domain_create();

        mutex_init(&fileassoc_list_lock, MUTEX_DEFAULT, IPL_NONE);

        return 0;
}

/*
 * Register a new assoc.
 */
int
fileassoc_register(const char *name, fileassoc_cleanup_cb_t cleanup_cb,
    fileassoc_t *result)
{
        int error;
        specificdata_key_t key;
        struct fileassoc *assoc;

        error = RUN_ONCE(&control, fileassoc_init);
        if (error) {
                return error;
        }
        error = specificdata_key_create(fileassoc_domain, &key, NULL);
        if (error) {
                return error;
        }
        assoc = kmem_alloc(sizeof(*assoc), KM_SLEEP);
        assoc->assoc_name = name;
        assoc->assoc_cleanup_cb = cleanup_cb;
        assoc->assoc_key = key;
        mutex_init(&assoc->assoc_mtx, MUTEX_DEFAULT, IPL_NONE);
        cv_init(&assoc->assoc_cv, "fa_assoc");
        assoc->assoc_refcnt = 0;

        mutex_enter(&fileassoc_list_lock);
        LIST_INSERT_HEAD(&fileassoc_list, assoc, assoc_list);
        mutex_exit(&fileassoc_list_lock);

        *result = assoc;

        return 0;
}

/*
 * Deregister an assoc.
 */
int
fileassoc_deregister(fileassoc_t assoc)
{

        /* Prevent others from playing with this assoc; it's now dying. */
        mutex_enter(&assoc->assoc_mtx);

        /* Wait for everyone to finish playing with it... */
        while (assoc->assoc_refcnt != 0)
                cv_wait(&assoc->assoc_cv, &assoc->assoc_mtx);

        /* Remove from the fileassocs list. */
        mutex_enter(&fileassoc_list_lock);
        LIST_REMOVE(assoc, assoc_list);
        mutex_exit(&fileassoc_list_lock);

        specificdata_key_delete(fileassoc_domain, assoc->assoc_key);

        mutex_exit(&assoc->assoc_mtx);

        kmem_free(assoc, sizeof(*assoc));

        return 0;
}

/*
 * Get the hash table for the specified device.
 *
 */
static struct fileassoc_table *
fileassoc_table_lookup(struct mount *mp)
{
        int error;

        error = RUN_ONCE(&control, fileassoc_init);
        if (error) {
                return NULL;
        }

        return mount_getspecific(mp, fileassoc_mountspecific_key);
}

/*
 * Perform a lookup on a hash table.  If hint is non-zero then use the value
 * of the hint as the identifier instead of performing a lookup for the
 * fileid.
 */
static struct fileassoc_file *
fileassoc_file_lookup(struct vnode *vp, fhandle_t *hint)
{
        struct fileassoc_table *tbl;
        struct fileassoc_hash_entry *hash_entry;
        struct fileassoc_file *faf;
        size_t indx;
        fhandle_t *th;
        int error;

        tbl = fileassoc_table_lookup(vp->v_mount);
        if (tbl == NULL) {
                return NULL;
        }

        table_use(tbl);

        if (hint == NULL) {
                error = vfs_composefh_alloc(vp, &th);
                if (error) {
                        table_unuse(tbl);
                        return (NULL);
                }
        } else {
                th = hint;
        }

        indx = FILEASSOC_HASH(tbl, th);
        hash_entry = &(tbl->tbl_hash[indx]);

        LIST_FOREACH(faf, hash_entry, faf_list) {
                file_use(faf);

                if (((FHANDLE_FILEID(faf->faf_handle)->fid_len ==
                     FHANDLE_FILEID(th)->fid_len)) &&
                    (memcmp(FHANDLE_FILEID(faf->faf_handle), FHANDLE_FILEID(th),
                           (FHANDLE_FILEID(th))->fid_len) == 0)) {
                        break;
                }

                file_unuse(faf);
        }

        if (faf != NULL)
                file_unuse(faf);

        table_unuse(tbl);

        if (hint == NULL)
                vfs_composefh_free(th);

        return faf;
}

/*
 * Return assoc data associated with a vnode.
 */
void *
fileassoc_lookup(struct vnode *vp, fileassoc_t assoc)
{
        struct fileassoc_file *faf;
        void *data;

        faf = fileassoc_file_lookup(vp, NULL);
        if (faf == NULL)
                return (NULL);

        /* Prevent this file entry and assoc from being taken away. */
        /* file_use(faf);
        assoc_use(assoc); */

        data = file_getdata(faf, assoc);

        return data;
}

void
fileassoc_release(struct vnode *vp, fileassoc_t assoc)
{
        struct fileassoc_file *faf;

        faf = fileassoc_file_lookup(vp, NULL);
        /*if (faf != NULL)
                file_unuse(faf);

        assoc_unuse(assoc);*/
}

/*
 * Resize a fileassoc table.
 *
 * Expects tbl to be held exclusively by the caller.
 */
static struct fileassoc_table *
fileassoc_table_resize(struct fileassoc_table *tbl)
{
        struct fileassoc_table *newtbl;
        u_long i;

        /*
         * Allocate a new table. Like the condition in fileassoc_file_add(),
         * this is also temporary -- just double the number of slots.
         */
        newtbl = kmem_zalloc(sizeof(*newtbl), KM_SLEEP);
        newtbl->tbl_nslots = (tbl->tbl_nslots * 2);
        if (newtbl->tbl_nslots < tbl->tbl_nslots)
                newtbl->tbl_nslots = tbl->tbl_nslots;
        newtbl->tbl_hash = hashinit(newtbl->tbl_nslots, HASH_LIST,
            true, &newtbl->tbl_mask);
        newtbl->tbl_nused = 0;
        specificdata_init(fileassoc_domain, &newtbl->tbl_data);
        newtbl->tbl_refcnt = tbl->tbl_refcnt;
        newtbl->tbl_mtx = tbl->tbl_mtx;
        newtbl->tbl_cv = tbl->tbl_cv;
        /* mutex_init(&newtbl->tbl_mtx, MUTEX_DEFAULT, IPL_NONE);
        cv_init(&newtbl->tbl_cv, "fileassoc_tbl"); */

        for (i = 0; i < tbl->tbl_nslots; i++) {
                struct fileassoc_file *faf;

                while ((faf = LIST_FIRST(&tbl->tbl_hash[i])) != NULL) {
                        struct fileassoc_hash_entry *hash_entry;
                        size_t indx;

                        /* Wait for others to finish with each file. */
                        mutex_enter(&faf->faf_mtx);
                        while (faf->faf_refcnt > 0)
                                cv_wait(&faf->faf_cv, &faf->faf_mtx);
        
                        LIST_REMOVE(faf, faf_list);

                        indx = FILEASSOC_HASH(newtbl, faf->faf_handle);
                        hash_entry = &(newtbl->tbl_hash[indx]);

                        LIST_INSERT_HEAD(hash_entry, faf, faf_list);

                        mutex_exit(&faf->faf_mtx);

                        newtbl->tbl_nused++;
                }
        }

        if (tbl->tbl_nused != newtbl->tbl_nused)
                panic("fileassoc_table_resize: inconsistency detected! "
                    "needed %zu entries, got %zu", tbl->tbl_nused,
                    newtbl->tbl_nused);

        hashdone(tbl->tbl_hash, HASH_LIST, tbl->tbl_mask);
        specificdata_fini(fileassoc_domain, &tbl->tbl_data);
        kmem_free(tbl, sizeof(*tbl));

        return (newtbl);
}

/*
 * Create a new fileassoc table.
 *
 * The new table is returned with reference count set to 1.
 */
static struct fileassoc_table *
fileassoc_table_add(struct mount *mp)
{
        struct fileassoc_table *tbl;

        /* Check for existing table for device. */
        tbl = fileassoc_table_lookup(mp);
        if (tbl != NULL)
                return (tbl);

        /* Allocate and initialize a table. */
        tbl = kmem_zalloc(sizeof(*tbl), KM_SLEEP);
        tbl->tbl_nslots = FILEASSOC_INITIAL_TABLESIZE;
        tbl->tbl_hash = hashinit(tbl->tbl_nslots, HASH_LIST, true,
            &tbl->tbl_mask);
        tbl->tbl_nused = 0;
        specificdata_init(fileassoc_domain, &tbl->tbl_data);
        tbl->tbl_refcnt = 1;
        mutex_init(&tbl->tbl_mtx, MUTEX_DEFAULT, IPL_NONE);
        cv_init(&tbl->tbl_cv, "fa_tbl");

        mount_setspecific(mp, fileassoc_mountspecific_key, tbl);

        return (tbl);
}

/*
 * Delete a table.
 */
int
fileassoc_table_delete(struct mount *mp)
{
        struct fileassoc_table *tbl;

        tbl = fileassoc_table_lookup(mp);
        if (tbl == NULL)
                return (EEXIST);

        /* Prevent others from touching the table. */
        mutex_enter(&tbl->tbl_mtx);
        while (tbl->tbl_refcnt > 0)
                cv_wait(&tbl->tbl_cv, &tbl->tbl_mtx);

        /* Detach it from the mount-point. */
        mount_setspecific(mp, fileassoc_mountspecific_key, NULL);

        /* Free it. */
        table_destroy(tbl);

        return (0);
}

/*
 * Run a callback for each assoc in a table.
 */
int
fileassoc_table_run(struct mount *mp, fileassoc_t assoc, fileassoc_cb_t cb,
    void *cookie)
{
        struct fileassoc_table *tbl;
        u_long i;

        tbl = fileassoc_table_lookup(mp);
        if (tbl == NULL)
                return (EEXIST);

        table_use(tbl);

        assoc_use(assoc);

        for (i = 0; i < tbl->tbl_nslots; i++) {
                struct fileassoc_file *faf;

                LIST_FOREACH(faf, &tbl->tbl_hash[i], faf_list) {
                        void *data;

                        file_use(faf);

                        data = file_getdata(faf, assoc);
                        if (data != NULL)
                                cb(data, cookie);

                        file_unuse(faf);
                }
        }

        assoc_unuse(assoc);

        table_unuse(tbl);

        return (0);
}

/*
 * Clear a table for a given assoc.
 */
int
fileassoc_table_clear(struct mount *mp, fileassoc_t assoc)
{
        struct fileassoc_table *tbl;
        u_long i;

        tbl = fileassoc_table_lookup(mp);
        if (tbl == NULL)
                return (EEXIST);

        table_use(tbl);

        assoc_use(assoc);

        for (i = 0; i < tbl->tbl_nslots; i++) {
                struct fileassoc_file *faf;

                LIST_FOREACH(faf, &tbl->tbl_hash[i], faf_list) {
                        mutex_enter(&faf->faf_mtx);
                        while (faf->faf_refcnt > 0)
                                cv_wait(&faf->faf_cv, &faf->faf_mtx);

                        file_cleanup(faf, assoc);
                        file_setdata(faf, assoc, NULL);

                        mutex_exit(&faf->faf_mtx);
                }
        }

        assoc_unuse(assoc);

        table_unuse(tbl);

        return (0);
}

/*
 * Add a file entry to a table.
 *
 * XXX: Set reference count to 1 here too?
 */
static struct fileassoc_file *
fileassoc_file_add(struct vnode *vp, fhandle_t *hint)
{
        struct fileassoc_table *tbl;
        struct fileassoc_hash_entry *hash_entry;
        struct fileassoc_file *faf;
        size_t indx;
        fhandle_t *th;
        int error;

        if (hint == NULL) {
                error = vfs_composefh_alloc(vp, &th);
                if (error)
                        return (NULL);
        } else
                th = hint;

        faf = fileassoc_file_lookup(vp, th);
        if (faf != NULL) {
                if (hint == NULL)
                        vfs_composefh_free(th);

                return (faf);
        }

        tbl = fileassoc_table_lookup(vp->v_mount);
        if (tbl == NULL) {
                /* This will "keep" a reference for us. */
                tbl = fileassoc_table_add(vp->v_mount);
        } else {
                /* Keep a reference to the table. */
                table_use(tbl);
        }

        faf = kmem_zalloc(sizeof(*faf), KM_SLEEP);
        faf->faf_handle = th;
        specificdata_init(fileassoc_domain, &faf->faf_data);
        faf->faf_refcnt = 0;
        mutex_init(&faf->faf_mtx, MUTEX_DEFAULT, IPL_NONE);
        cv_init(&faf->faf_cv, "fa_faf");

        /* We need the table exclusively. */
        mutex_enter(&tbl->tbl_mtx);
        while (tbl->tbl_refcnt > 1)
                cv_wait(&tbl->tbl_cv, &tbl->tbl_mtx);

        /*
         * This decides when we need to resize the table. For now,
         * resize it whenever we "filled" up the number of slots it
         * has. That's not really true unless of course we had zero
         * collisions. Think positive! :)
         */
        if (((tbl->tbl_nused) + 1) == tbl->tbl_nslots) { 
                struct fileassoc_table *newtbl;

                newtbl = fileassoc_table_resize(tbl);
                mount_setspecific(vp->v_mount, fileassoc_mountspecific_key,
                    newtbl);

                tbl = newtbl;
        }

        indx = FILEASSOC_HASH(tbl, th);
        hash_entry = &(tbl->tbl_hash[indx]);
        LIST_INSERT_HEAD(hash_entry, faf, faf_list);
        ++(tbl->tbl_nused);

        mutex_exit(&tbl->tbl_mtx);

        table_unuse(tbl);

        return (faf);
}

/*
 * Delete a file entry from a table.
 */
int
fileassoc_file_delete(struct vnode *vp)
{
        struct fileassoc_table *tbl;
        struct fileassoc_file *faf;

        KERNEL_LOCK(1, NULL);

        faf = fileassoc_file_lookup(vp, NULL);
        if (faf == NULL) {
                KERNEL_UNLOCK_ONE(NULL);
                return (ENOENT);
        }

        /* Wait for everyone to finish... */
        mutex_enter(&faf->faf_mtx);
        while (faf->faf_refcnt > 0)
                cv_wait(&faf->faf_cv, &faf->faf_mtx);

        /* Remove it... */
        LIST_REMOVE(faf, faf_list);

        mutex_exit(&faf->faf_mtx);

        file_free(faf);

        tbl = fileassoc_table_lookup(vp->v_mount);

        mutex_enter(&tbl->tbl_mtx);
        --(tbl->tbl_nused); /* XXX gc? */
        mutex_exit(&tbl->tbl_mtx);

        KERNEL_UNLOCK_ONE(NULL);

        return (0);
}

/*
 * Add an assoc to a vnode.
 */
int
fileassoc_add(struct vnode *vp, fileassoc_t assoc, void *data)
{
        struct fileassoc_file *faf;
        void *olddata;

        faf = fileassoc_file_lookup(vp, NULL);
        if (faf == NULL) {
                faf = fileassoc_file_add(vp, NULL);
                if (faf == NULL)
                        return (ENOTDIR);
        }

        mutex_enter(&faf->faf_mtx);

        assoc_use(assoc);

        olddata = file_getdata(faf, assoc);
        if (olddata != NULL) {
                assoc_unuse(assoc);
                mutex_exit(&faf->faf_mtx);
                return (EEXIST);
        }

        file_setdata(faf, assoc, data);

        faf->faf_nassocs++;

        assoc_unuse(assoc);

        mutex_exit(&faf->faf_mtx);

        return (0);
}

/*
 * Clear an assoc from a vnode.
 */
int
fileassoc_clear(struct vnode *vp, fileassoc_t assoc)
{
        struct fileassoc_file *faf;

        faf = fileassoc_file_lookup(vp, NULL);
        if (faf == NULL)
                return (ENOENT);

        file_use(faf);

        assoc_use(assoc);

        file_cleanup(faf, assoc);
        file_setdata(faf, assoc, NULL);

        assoc_unuse(assoc);

        mutex_enter(&faf->faf_mtx);
        --(faf->faf_nassocs); /* XXX gc? */
        mutex_exit(&faf->faf_mtx);

        file_unuse(faf);

        return (0);
}


Home | Main Index | Thread Index | Old Index