Mercurial > illumos > illumos-gate
view usr/src/uts/common/brand/lx/autofs/lx_autofs.c @ 3898:c788126f2a20
PSARC/2007/124 Strong Type-Checking for VFS Operation Registration Mechanism
6505923 Need better type checking for vnodeops
6531594 lxpr_readlink() is missing the "cred_t *cr" arg
6532559 vfs_strayops does not use the vnode/vfs operation registration mechanism
author | rsb |
---|---|
date | Mon, 26 Mar 2007 17:41:06 -0700 |
parents | f74a135872bc |
children | 3047ad28a67b |
line wrap: on
line source
/* * 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 * or http://www.opensolaris.org/os/licensing. * 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 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #pragma ident "%Z%%M% %I% %E% SMI" #include <fs/fs_subr.h> #include <sys/atomic.h> #include <sys/cmn_err.h> #include <sys/dirent.h> #include <sys/fs/fifonode.h> #include <sys/modctl.h> #include <sys/mount.h> #include <sys/policy.h> #include <sys/sunddi.h> #include <sys/sysmacros.h> #include <sys/vfs.h> #include <sys/vfs_opreg.h> #include <sys/lx_autofs_impl.h> /* * External functions */ extern uintptr_t space_fetch(char *key); extern int space_store(char *key, uintptr_t ptr); /* * Globals */ static vfsops_t *lx_autofs_vfsops; static vnodeops_t *lx_autofs_vn_ops = NULL; static int lx_autofs_fstype; static major_t lx_autofs_major; static minor_t lx_autofs_minor = 0; /* * Support functions */ static void i_strfree(char *str) { kmem_free(str, strlen(str) + 1); } static char * i_strdup(char *str) { int n = strlen(str); char *ptr = kmem_alloc(n + 1, KM_SLEEP); bcopy(str, ptr, n + 1); return (ptr); } static int i_str_to_int(char *str, int *val) { long res; if (str == NULL) return (-1); if ((ddi_strtol(str, NULL, 10, &res) != 0) || (res < INT_MIN) || (res > INT_MAX)) return (-1); *val = res; return (0); } static void i_stack_init(list_t *lp) { list_create(lp, sizeof (stack_elem_t), offsetof(stack_elem_t, se_list)); } static void i_stack_fini(list_t *lp) { ASSERT(list_head(lp) == NULL); list_destroy(lp); } static void i_stack_push(list_t *lp, caddr_t ptr1, caddr_t ptr2, caddr_t ptr3) { stack_elem_t *se; se = kmem_alloc(sizeof (*se), KM_SLEEP); se->se_ptr1 = ptr1; se->se_ptr2 = ptr2; se->se_ptr3 = ptr3; list_insert_head(lp, se); } static int i_stack_pop(list_t *lp, caddr_t *ptr1, caddr_t *ptr2, caddr_t *ptr3) { stack_elem_t *se; if ((se = list_head(lp)) == NULL) return (-1); list_remove(lp, se); if (ptr1 != NULL) *ptr1 = se->se_ptr1; if (ptr2 != NULL) *ptr2 = se->se_ptr2; if (ptr3 != NULL) *ptr3 = se->se_ptr3; kmem_free(se, sizeof (*se)); return (0); } static vnode_t * fifo_peer_vp(vnode_t *vp) { fifonode_t *fnp = VTOF(vp); fifonode_t *fn_dest = fnp->fn_dest; return (FTOV(fn_dest)); } static vnode_t * i_vn_alloc(vfs_t *vfsp, vnode_t *uvp) { lx_autofs_vfs_t *data = vfsp->vfs_data; vnode_t *vp, *vp_old; /* Allocate a new vnode structure in case we need it. */ vp = vn_alloc(KM_SLEEP); vn_setops(vp, lx_autofs_vn_ops); VN_SET_VFS_TYPE_DEV(vp, vfsp, uvp->v_type, uvp->v_rdev); vp->v_data = uvp; ASSERT(vp->v_count == 1); /* * Take a hold on the vfs structure. This is how unmount will * determine if there are any active vnodes in the file system. */ VFS_HOLD(vfsp); /* * Check if we already have a vnode allocated for this underlying * vnode_t. */ mutex_enter(&data->lav_lock); if (mod_hash_find(data->lav_vn_hash, (mod_hash_key_t)uvp, (mod_hash_val_t *)&vp_old) != 0) { /* * Didn't find an existing node. * Add this node to the hash and return. */ VERIFY(mod_hash_insert(data->lav_vn_hash, (mod_hash_key_t)uvp, (mod_hash_val_t)vp) == 0); mutex_exit(&data->lav_lock); return (vp); } /* Get a hold on the existing vnode and free up the one we allocated. */ VN_HOLD(vp_old); mutex_exit(&data->lav_lock); /* Free up the new vnode we allocated. */ VN_RELE(uvp); VFS_RELE(vfsp); vn_invalid(vp); vn_free(vp); return (vp_old); } static void i_vn_free(vnode_t *vp) { vfs_t *vfsp = vp->v_vfsp; lx_autofs_vfs_t *data = vfsp->vfs_data; vnode_t *uvp = vp->v_data; vnode_t *vp_tmp; ASSERT(MUTEX_HELD((&data->lav_lock))); ASSERT(MUTEX_HELD((&vp->v_lock))); ASSERT(vp->v_count == 0); /* We're about to free this vnode so take it out of the hash. */ (void) mod_hash_remove(data->lav_vn_hash, (mod_hash_key_t)uvp, (mod_hash_val_t)&vp_tmp); /* * No one else can lookup this vnode any more so there's no need * to hold locks. */ mutex_exit(&data->lav_lock); mutex_exit(&vp->v_lock); /* Release the underlying vnode. */ VN_RELE(uvp); VFS_RELE(vfsp); vn_invalid(vp); vn_free(vp); } static lx_autofs_lookup_req_t * i_lalr_alloc(lx_autofs_vfs_t *data, int *dup_request, char *nm) { lx_autofs_lookup_req_t *lalr, *lalr_dup; /* Pre-allocate a new automounter request before grabbing locks. */ lalr = kmem_zalloc(sizeof (*lalr), KM_SLEEP); mutex_init(&lalr->lalr_lock, NULL, MUTEX_DEFAULT, NULL); cv_init(&lalr->lalr_cv, NULL, CV_DEFAULT, NULL); lalr->lalr_ref = 1; lalr->lalr_pkt.lap_protover = LX_AUTOFS_PROTO_VERSION; /* Assign a unique id for this request. */ lalr->lalr_pkt.lap_id = id_alloc(data->lav_ids); /* * The token expected by the linux automount is the name of * the directory entry to look up. (And not the entire * path that is being accessed.) */ lalr->lalr_pkt.lap_name_len = strlen(nm); if (lalr->lalr_pkt.lap_name_len > (sizeof (lalr->lalr_pkt.lap_name) - 1)) { zcmn_err(getzoneid(), CE_NOTE, "invalid autofs lookup: \"%s\"", nm); id_free(data->lav_ids, lalr->lalr_pkt.lap_id); kmem_free(lalr, sizeof (*lalr)); return (NULL); } (void) strlcpy(lalr->lalr_pkt.lap_name, nm, sizeof (lalr->lalr_pkt.lap_name)); /* Check for an outstanding request for this path. */ mutex_enter(&data->lav_lock); if (mod_hash_find(data->lav_path_hash, (mod_hash_key_t)nm, (mod_hash_val_t *)&lalr_dup) == 0) { /* * There's already an outstanding request for this * path so we don't need a new one. */ id_free(data->lav_ids, lalr->lalr_pkt.lap_id); kmem_free(lalr, sizeof (*lalr)); lalr = lalr_dup; /* Bump the ref count on the old request. */ atomic_add_int(&lalr->lalr_ref, 1); *dup_request = 1; } else { /* Add it to the hashes. */ VERIFY(mod_hash_insert(data->lav_id_hash, (mod_hash_key_t)(uintptr_t)lalr->lalr_pkt.lap_id, (mod_hash_val_t)lalr) == 0); VERIFY(mod_hash_insert(data->lav_path_hash, (mod_hash_key_t)i_strdup(nm), (mod_hash_val_t)lalr) == 0); *dup_request = 0; } mutex_exit(&data->lav_lock); return (lalr); } static lx_autofs_lookup_req_t * i_lalr_find(lx_autofs_vfs_t *data, int id) { lx_autofs_lookup_req_t *lalr; /* Check for an outstanding request for this id. */ mutex_enter(&data->lav_lock); if (mod_hash_find(data->lav_id_hash, (mod_hash_key_t)(uintptr_t)id, (mod_hash_val_t *)&lalr) != 0) { mutex_exit(&data->lav_lock); return (NULL); } atomic_add_int(&lalr->lalr_ref, 1); mutex_exit(&data->lav_lock); return (lalr); } static void i_lalr_complete(lx_autofs_vfs_t *data, lx_autofs_lookup_req_t *lalr) { lx_autofs_lookup_req_t *lalr_tmp; /* Remove this request from the hashes so no one can look it up. */ mutex_enter(&data->lav_lock); (void) mod_hash_remove(data->lav_id_hash, (mod_hash_key_t)(uintptr_t)lalr->lalr_pkt.lap_id, (mod_hash_val_t)&lalr_tmp); (void) mod_hash_remove(data->lav_path_hash, (mod_hash_key_t)lalr->lalr_pkt.lap_name, (mod_hash_val_t)&lalr_tmp); mutex_exit(&data->lav_lock); /* Mark this requst as complete and wakeup anyone waiting on it. */ mutex_enter(&lalr->lalr_lock); lalr->lalr_complete = 1; cv_broadcast(&lalr->lalr_cv); mutex_exit(&lalr->lalr_lock); } static void i_lalr_release(lx_autofs_vfs_t *data, lx_autofs_lookup_req_t *lalr) { ASSERT(!MUTEX_HELD(&lalr->lalr_lock)); if (atomic_add_int_nv(&lalr->lalr_ref, -1) > 0) return; ASSERT(lalr->lalr_ref == 0); id_free(data->lav_ids, lalr->lalr_pkt.lap_id); kmem_free(lalr, sizeof (*lalr)); } static void i_lalr_abort(lx_autofs_vfs_t *data, lx_autofs_lookup_req_t *lalr) { lx_autofs_lookup_req_t *lalr_tmp; /* * This is a little tricky. We're aborting the wait for this * request. So if anyone else is waiting for this request we * can't free it, but if no one else is waiting for the request * we should free it. */ mutex_enter(&data->lav_lock); if (atomic_add_int_nv(&lalr->lalr_ref, -1) > 0) { mutex_exit(&data->lav_lock); return; } ASSERT(lalr->lalr_ref == 0); /* Remove this request from the hashes so no one can look it up. */ (void) mod_hash_remove(data->lav_id_hash, (mod_hash_key_t)(uintptr_t)lalr->lalr_pkt.lap_id, (mod_hash_val_t)&lalr_tmp); (void) mod_hash_remove(data->lav_path_hash, (mod_hash_key_t)lalr->lalr_pkt.lap_name, (mod_hash_val_t)&lalr_tmp); mutex_exit(&data->lav_lock); /* It's ok to free this now because the ref count was zero. */ id_free(data->lav_ids, lalr->lalr_pkt.lap_id); kmem_free(lalr, sizeof (*lalr)); } static int i_fifo_lookup(pid_t pgrp, int fd, file_t **fpp_wr, file_t **fpp_rd) { proc_t *prp; uf_info_t *fip; uf_entry_t *ufp_wr, *ufp_rd; file_t *fp_wr, *fp_rd; vnode_t *vp_wr, *vp_rd; int i; /* * sprlock() is zone aware, so assuming this mount call was * initiated by a process in a zone, if it tries to specify * a pgrp outside of it's zone this call will fail. * * Also, we want to grab hold of the main automounter process * and its going to be the group leader for pgrp, so its * pid will be equal to pgrp. */ prp = sprlock(pgrp); if (prp == NULL) return (-1); mutex_exit(&prp->p_lock); /* Now we want to access the processes open file descriptors. */ fip = P_FINFO(prp); mutex_enter(&fip->fi_lock); /* Sanity check fifo write fd. */ if (fd >= fip->fi_nfiles) { mutex_exit(&fip->fi_lock); mutex_enter(&prp->p_lock); sprunlock(prp); return (-1); } /* Get a pointer to the write fifo. */ UF_ENTER(ufp_wr, fip, fd); if (((fp_wr = ufp_wr->uf_file) == NULL) || ((vp_wr = fp_wr->f_vnode) == NULL) || (vp_wr->v_type != VFIFO)) { /* Invalid fifo fd. */ UF_EXIT(ufp_wr); mutex_exit(&fip->fi_lock); mutex_enter(&prp->p_lock); sprunlock(prp); return (-1); } /* * Now we need to find the read end of the fifo (for reasons * explained below.) We assume that the read end of the fifo * is in the same process as the write end. */ vp_rd = fifo_peer_vp(fp_wr->f_vnode); for (i = 0; i < fip->fi_nfiles; i++) { UF_ENTER(ufp_rd, fip, i); if (((fp_rd = ufp_rd->uf_file) != NULL) && (fp_rd->f_vnode == vp_rd)) break; UF_EXIT(ufp_rd); } if (i == fip->fi_nfiles) { /* Didn't find it. */ UF_EXIT(ufp_wr); mutex_exit(&fip->fi_lock); mutex_enter(&prp->p_lock); sprunlock(prp); return (-1); } /* * We need to drop fi_lock before we can try to aquire f_tlock * the good news is that the file pointers are protected because * we're still holding uf_lock. */ mutex_exit(&fip->fi_lock); /* * Here we bump the open counts on the fifos. The reason * that we do this is because when we go to write to the * fifo we want to ensure that they are actually open (and * not in the process of being closed) without having to * stop the automounter. (If the write end of the fifo * were closed and we tried to write to it we would panic. * If the read end of the fifo was closed and we tried to * write to the other end, the process that invoked the * lookup operation would get an unexpected SIGPIPE.) */ mutex_enter(&fp_wr->f_tlock); fp_wr->f_count++; ASSERT(fp_wr->f_count >= 2); mutex_exit(&fp_wr->f_tlock); mutex_enter(&fp_rd->f_tlock); fp_rd->f_count++; ASSERT(fp_rd->f_count >= 2); mutex_exit(&fp_rd->f_tlock); /* Release all our locks. */ UF_EXIT(ufp_wr); UF_EXIT(ufp_rd); mutex_enter(&prp->p_lock); sprunlock(prp); /* Return the file pointers. */ *fpp_rd = fp_rd; *fpp_wr = fp_wr; return (0); } static uint_t /*ARGSUSED*/ i_fifo_close_cb(mod_hash_key_t key, mod_hash_val_t *val, void *arg) { int *id = (int *)arg; /* Return the key and terminate the walk. */ *id = (uintptr_t)key; return (MH_WALK_TERMINATE); } static void i_fifo_close(lx_autofs_vfs_t *data) { /* * Close the fifo to prevent any future requests from * getting sent to the automounter. */ mutex_enter(&data->lav_lock); if (data->lav_fifo_wr != NULL) { (void) closef(data->lav_fifo_wr); data->lav_fifo_wr = NULL; } if (data->lav_fifo_rd != NULL) { (void) closef(data->lav_fifo_rd); data->lav_fifo_rd = NULL; } mutex_exit(&data->lav_lock); /* * Wakeup any threads currently waiting for the automounter * note that it's possible for multiple threads to have entered * this function and to be doing the work below simultaneously. */ for (;;) { lx_autofs_lookup_req_t *lalr; int id; /* Lookup the first entry in the hash. */ id = -1; mod_hash_walk(data->lav_id_hash, i_fifo_close_cb, &id); if (id == -1) { /* No more id's in the hash. */ break; } if ((lalr = i_lalr_find(data, id)) == NULL) { /* Someone else beat us to it. */ continue; } /* Mark the request as compleate and release it. */ i_lalr_complete(data, lalr); i_lalr_release(data, lalr); } } static int i_fifo_verify_rd(lx_autofs_vfs_t *data) { proc_t *prp; uf_info_t *fip; uf_entry_t *ufp_rd; file_t *fp_rd; vnode_t *vp_rd; int i; ASSERT(MUTEX_HELD((&data->lav_lock))); /* Check if we've already been shut down. */ if (data->lav_fifo_wr == NULL) { ASSERT(data->lav_fifo_rd == NULL); return (-1); } vp_rd = fifo_peer_vp(data->lav_fifo_wr->f_vnode); /* * sprlock() is zone aware, so assuming this mount call was * initiated by a process in a zone, if it tries to specify * a pgrp outside of it's zone this call will fail. * * Also, we want to grab hold of the main automounter process * and its going to be the group leader for pgrp, so its * pid will be equal to pgrp. */ prp = sprlock(data->lav_pgrp); if (prp == NULL) return (-1); mutex_exit(&prp->p_lock); /* Now we want to access the processes open file descriptors. */ fip = P_FINFO(prp); mutex_enter(&fip->fi_lock); /* * Now we need to find the read end of the fifo (for reasons * explained below.) We assume that the read end of the fifo * is in the same process as the write end. */ for (i = 0; i < fip->fi_nfiles; i++) { UF_ENTER(ufp_rd, fip, i); if (((fp_rd = ufp_rd->uf_file) != NULL) && (fp_rd->f_vnode == vp_rd)) break; UF_EXIT(ufp_rd); } if (i == fip->fi_nfiles) { /* Didn't find it. */ mutex_exit(&fip->fi_lock); mutex_enter(&prp->p_lock); sprunlock(prp); return (-1); } /* * Seems the automounter still has the read end of the fifo * open, we're done here. Release all our locks and exit. */ mutex_exit(&fip->fi_lock); UF_EXIT(ufp_rd); mutex_enter(&prp->p_lock); sprunlock(prp); return (0); } static int i_fifo_write(lx_autofs_vfs_t *data, lx_autofs_pkt_t *lap) { struct uio uio; struct iovec iov; file_t *fp_wr, *fp_rd; int error; /* * The catch here is we need to make sure _we_ don't close * the the fifo while writing to it. (Another thread could come * along and realize the automounter process is gone and close * the fifo. To do this we bump the open count before we * write to the fifo. */ mutex_enter(&data->lav_lock); if (data->lav_fifo_wr == NULL) { ASSERT(data->lav_fifo_rd == NULL); mutex_exit(&data->lav_lock); return (ENOENT); } fp_wr = data->lav_fifo_wr; fp_rd = data->lav_fifo_rd; /* Bump the open count on the write fifo. */ mutex_enter(&fp_wr->f_tlock); fp_wr->f_count++; mutex_exit(&fp_wr->f_tlock); /* Bump the open count on the read fifo. */ mutex_enter(&fp_rd->f_tlock); fp_rd->f_count++; mutex_exit(&fp_rd->f_tlock); mutex_exit(&data->lav_lock); iov.iov_base = (caddr_t)lap; iov.iov_len = sizeof (*lap); uio.uio_iov = &iov; uio.uio_iovcnt = 1; uio.uio_loffset = 0; uio.uio_segflg = (short)UIO_SYSSPACE; uio.uio_resid = sizeof (*lap); uio.uio_llimit = 0; uio.uio_fmode = FWRITE | FNDELAY | FNONBLOCK; error = VOP_WRITE(fp_wr->f_vnode, &uio, 0, kcred, NULL); (void) closef(fp_wr); (void) closef(fp_rd); /* * After every write we verify that the automounter still has * these files open. */ mutex_enter(&data->lav_lock); if (i_fifo_verify_rd(data) != 0) { /* * Something happened to the automounter. * Close down the communication pipe we setup. */ mutex_exit(&data->lav_lock); i_fifo_close(data); if (error != 0) return (error); return (ENOENT); } mutex_exit(&data->lav_lock); return (error); } static int i_bs_readdir(vnode_t *dvp, list_t *dir_stack, list_t *file_stack) { struct iovec iov; struct uio uio; dirent64_t *dp, *dbuf; vnode_t *vp; size_t dlen, dbuflen; int eof, error, ndirents = 64; char *nm; dlen = ndirents * (sizeof (*dbuf)); dbuf = kmem_alloc(dlen, KM_SLEEP); uio.uio_iov = &iov; uio.uio_iovcnt = 1; uio.uio_segflg = UIO_SYSSPACE; uio.uio_fmode = 0; uio.uio_extflg = UIO_COPY_CACHED; uio.uio_loffset = 0; uio.uio_llimit = MAXOFFSET_T; eof = 0; error = 0; while (!error && !eof) { uio.uio_resid = dlen; iov.iov_base = (char *)dbuf; iov.iov_len = dlen; (void) VOP_RWLOCK(dvp, V_WRITELOCK_FALSE, NULL); if (VOP_READDIR(dvp, &uio, kcred, &eof) != 0) { VOP_RWUNLOCK(dvp, V_WRITELOCK_FALSE, NULL); kmem_free(dbuf, dlen); return (-1); } VOP_RWUNLOCK(dvp, V_WRITELOCK_FALSE, NULL); if ((dbuflen = dlen - uio.uio_resid) == 0) { /* We're done. */ break; } for (dp = dbuf; ((intptr_t)dp < (intptr_t)dbuf + dbuflen); dp = (dirent64_t *)((intptr_t)dp + dp->d_reclen)) { nm = dp->d_name; if (strcmp(nm, ".") == 0 || strcmp(nm, "..") == 0) continue; if (VOP_LOOKUP(dvp, nm, &vp, NULL, 0, NULL, kcred) != 0) { kmem_free(dbuf, dlen); return (-1); } if (vp->v_type == VDIR) { if (dir_stack != NULL) { i_stack_push(dir_stack, (caddr_t)dvp, (caddr_t)vp, i_strdup(nm)); } else { VN_RELE(vp); } } else { if (file_stack != NULL) { i_stack_push(file_stack, (caddr_t)dvp, (caddr_t)vp, i_strdup(nm)); } else { VN_RELE(vp); } } } } kmem_free(dbuf, dlen); return (0); } static void i_bs_destroy(vnode_t *dvp, char *path) { list_t search_stack; list_t dir_stack; list_t file_stack; vnode_t *pdvp, *vp; char *dpath, *fpath; int ret; if (VOP_LOOKUP(dvp, path, &vp, NULL, 0, NULL, kcred) != 0) { /* A directory entry with this name doesn't actually exist. */ return; } if ((vp->v_type & VDIR) == 0) { /* Easy, the directory entry is a file so delete it. */ VN_RELE(vp); (void) VOP_REMOVE(dvp, path, kcred); return; } /* * The directory entry is a subdirectory, now we have a bit more * work to do. (We'll have to recurse into the sub directory.) * It would have been much easier to do this recursively but kernel * stacks are notoriously small. */ i_stack_init(&search_stack); i_stack_init(&dir_stack); i_stack_init(&file_stack); /* Save our newfound subdirectory into a list. */ i_stack_push(&search_stack, (caddr_t)dvp, (caddr_t)vp, i_strdup(path)); /* Do a recursive depth first search into the subdirectories. */ while (i_stack_pop(&search_stack, (caddr_t *)&pdvp, (caddr_t *)&dvp, &dpath) == 0) { /* Get a list of the subdirectories in this directory. */ if (i_bs_readdir(dvp, &search_stack, NULL) != 0) goto exit; /* Save the current directory a seperate stack. */ i_stack_push(&dir_stack, (caddr_t)pdvp, (caddr_t)dvp, dpath); } /* * Now dir_stack contains a list of directories, the deepest paths * are at the top of the list. So let's go through and process them. */ while (i_stack_pop(&dir_stack, (caddr_t *)&pdvp, (caddr_t *)&dvp, &dpath) == 0) { /* Get a list of the files in this directory. */ if (i_bs_readdir(dvp, NULL, &file_stack) != 0) { VN_RELE(dvp); i_strfree(dpath); goto exit; } /* Delete all the files in this directory. */ while (i_stack_pop(&file_stack, NULL, (caddr_t *)&vp, &fpath) == 0) { VN_RELE(vp) ret = VOP_REMOVE(dvp, fpath, kcred); i_strfree(fpath); if (ret != 0) { i_strfree(dpath); goto exit; } } /* Delete this directory. */ VN_RELE(dvp); ret = VOP_RMDIR(pdvp, dpath, pdvp, kcred); i_strfree(dpath); if (ret != 0) goto exit; } exit: while ( (i_stack_pop(&search_stack, NULL, (caddr_t *)&vp, &path) == 0) || (i_stack_pop(&dir_stack, NULL, (caddr_t *)&vp, &path) == 0) || (i_stack_pop(&file_stack, NULL, (caddr_t *)&vp, &path) == 0)) { VN_RELE(vp); i_strfree(path); } i_stack_fini(&search_stack); i_stack_fini(&dir_stack); i_stack_fini(&file_stack); } static vnode_t * i_bs_create(vnode_t *dvp, char *bs_name) { vnode_t *vp; vattr_t vattr; /* * After looking at the mkdir syscall path it seems we don't need * to initialize all of the vattr_t structure. */ bzero(&vattr, sizeof (vattr)); vattr.va_type = VDIR; vattr.va_mode = 0755; /* u+rwx,og=rx */ vattr.va_mask = AT_TYPE|AT_MODE; if (VOP_MKDIR(dvp, bs_name, &vattr, &vp, kcred) != 0) return (NULL); return (vp); } static int i_automounter_call(vnode_t *dvp, char *nm) { lx_autofs_lookup_req_t *lalr; lx_autofs_vfs_t *data; int error, dup_request; /* Get a pointer to the vfs mount data. */ data = dvp->v_vfsp->vfs_data; /* The automounter only support queries in the root directory. */ if (dvp != data->lav_root) return (ENOENT); /* * Check if the current process is in the automounters process * group. (If it is, the current process is either the autmounter * itself or one of it's forked child processes.) If so, don't * redirect this lookup back into the automounter because we'll * hang. */ mutex_enter(&pidlock); if (data->lav_pgrp == curproc->p_pgrp) { mutex_exit(&pidlock); return (ENOENT); } mutex_exit(&pidlock); /* Verify that the automount process pipe still exists. */ mutex_enter(&data->lav_lock); if (data->lav_fifo_wr == NULL) { ASSERT(data->lav_fifo_rd == NULL); mutex_exit(&data->lav_lock); return (ENOENT); } mutex_exit(&data->lav_lock); /* Allocate an automounter request structure. */ if ((lalr = i_lalr_alloc(data, &dup_request, nm)) == NULL) return (ENOENT); /* * If we were the first one to allocate this request then we * need to send it to the automounter. */ if ((!dup_request) && ((error = i_fifo_write(data, &lalr->lalr_pkt)) != 0)) { /* * Unable to send the request to the automounter. * Unblock any other threads waiting on the request * and release the request. */ i_lalr_complete(data, lalr); i_lalr_release(data, lalr); return (error); } /* Wait for someone to signal us that this request has compleated. */ mutex_enter(&lalr->lalr_lock); while (!lalr->lalr_complete) { if (cv_wait_sig(&lalr->lalr_cv, &lalr->lalr_lock) == 0) { /* We got a signal, abort this lookup. */ mutex_exit(&lalr->lalr_lock); i_lalr_abort(data, lalr); return (EINTR); } } mutex_exit(&lalr->lalr_lock); i_lalr_release(data, lalr); return (0); } static int i_automounter_ioctl(vnode_t *vp, int cmd, intptr_t arg) { lx_autofs_vfs_t *data = (lx_autofs_vfs_t *)vp->v_vfsp->vfs_data; /* * Be strict. * We only accept ioctls from the automounter process group. */ mutex_enter(&pidlock); if (data->lav_pgrp != curproc->p_pgrp) { mutex_exit(&pidlock); return (ENOENT); } mutex_exit(&pidlock); if ((cmd == LX_AUTOFS_IOC_READY) || (cmd == LX_AUTOFS_IOC_FAIL)) { lx_autofs_lookup_req_t *lalr; int id = arg; /* * We don't actually care if the request failed or succeeded. * We do the same thing either way. */ if ((lalr = i_lalr_find(data, id)) == NULL) return (ENXIO); /* Mark the request as compleate and release it. */ i_lalr_complete(data, lalr); i_lalr_release(data, lalr); return (0); } if (cmd == LX_AUTOFS_IOC_CATATONIC) { /* The automounter is shutting down. */ i_fifo_close(data); return (0); } return (ENOTSUP); } static int i_parse_mntopt(vfs_t *vfsp, lx_autofs_vfs_t *data) { char *fd_str, *pgrp_str, *minproto_str, *maxproto_str; int fd, pgrp, minproto, maxproto; file_t *fp_wr, *fp_rd; /* Require all options to be present. */ if ((vfs_optionisset(vfsp, LX_MNTOPT_FD, &fd_str) != 1) || (vfs_optionisset(vfsp, LX_MNTOPT_PGRP, &pgrp_str) != 1) || (vfs_optionisset(vfsp, LX_MNTOPT_MINPROTO, &minproto_str) != 1) || (vfs_optionisset(vfsp, LX_MNTOPT_MAXPROTO, &maxproto_str) != 1)) return (EINVAL); /* Get the values for each parameter. */ if ((i_str_to_int(fd_str, &fd) != 0) || (i_str_to_int(pgrp_str, &pgrp) != 0) || (i_str_to_int(minproto_str, &minproto) != 0) || (i_str_to_int(maxproto_str, &maxproto) != 0)) return (EINVAL); /* * We support v2 of the linux kernel automounter protocol. * Make sure the mount request we got indicates support * for this version of the protocol. */ if ((minproto > 2) || (maxproto < 2)) return (EINVAL); /* * Now we need to lookup the fifos we'll be using * to talk to the userland automounter process. */ if (i_fifo_lookup(pgrp, fd, &fp_wr, &fp_rd) != 0) return (EINVAL); /* Save the mount options and fifo pointers. */ data->lav_fd = fd; data->lav_pgrp = pgrp; data->lav_fifo_rd = fp_rd; data->lav_fifo_wr = fp_wr; return (0); } /* * VFS entry points */ static int lx_autofs_mount(vfs_t *vfsp, vnode_t *mvp, struct mounta *uap, cred_t *cr) { lx_autofs_vfs_t *data; dev_t dev; char name[40]; int error; if (secpolicy_fs_mount(cr, mvp, vfsp) != 0) return (EPERM); if (mvp->v_type != VDIR) return (ENOTDIR); if ((uap->flags & MS_OVERLAY) == 0 && (mvp->v_count > 1 || (mvp->v_flag & VROOT))) return (EBUSY); /* We don't support mountes in the global zone. */ if (getzoneid() == GLOBAL_ZONEID) return (EPERM); /* We don't support mounting on top of ourselves. */ if (vn_matchops(mvp, lx_autofs_vn_ops)) return (EPERM); /* Allocate a vfs struct. */ data = kmem_zalloc(sizeof (lx_autofs_vfs_t), KM_SLEEP); /* Parse mount options. */ if ((error = i_parse_mntopt(vfsp, data)) != 0) { kmem_free(data, sizeof (lx_autofs_vfs_t)); return (error); } /* Initialize the backing store. */ i_bs_destroy(mvp, LX_AUTOFS_BS_DIR); if ((data->lav_bs_vp = i_bs_create(mvp, LX_AUTOFS_BS_DIR)) == NULL) { kmem_free(data, sizeof (lx_autofs_vfs_t)); return (EBUSY); } data->lav_bs_name = LX_AUTOFS_BS_DIR; /* We have to hold the underlying vnode we're mounted on. */ data->lav_mvp = mvp; VN_HOLD(mvp); /* Initialize vfs fields */ vfsp->vfs_bsize = DEV_BSIZE; vfsp->vfs_fstype = lx_autofs_fstype; vfsp->vfs_data = data; /* Invent a dev_t (sigh) */ do { dev = makedevice(lx_autofs_major, atomic_add_32_nv(&lx_autofs_minor, 1) & L_MAXMIN32); } while (vfs_devismounted(dev)); vfsp->vfs_dev = dev; vfs_make_fsid(&vfsp->vfs_fsid, dev, lx_autofs_fstype); /* Create an id space arena for automounter requests. */ (void) snprintf(name, sizeof (name), "lx_autofs_id_%d", getminor(vfsp->vfs_dev)); data->lav_ids = id_space_create(name, 1, INT_MAX); /* Create hashes to keep track of automounter requests. */ mutex_init(&data->lav_lock, NULL, MUTEX_DEFAULT, NULL); (void) snprintf(name, sizeof (name), "lx_autofs_path_hash_%d", getminor(vfsp->vfs_dev)); data->lav_path_hash = mod_hash_create_strhash(name, LX_AUTOFS_VFS_PATH_HASH_SIZE, mod_hash_null_valdtor); (void) snprintf(name, sizeof (name), "lx_autofs_id_hash_%d", getminor(vfsp->vfs_dev)); data->lav_id_hash = mod_hash_create_idhash(name, LX_AUTOFS_VFS_ID_HASH_SIZE, mod_hash_null_valdtor); /* Create a hash to keep track of vnodes. */ (void) snprintf(name, sizeof (name), "lx_autofs_vn_hash_%d", getminor(vfsp->vfs_dev)); data->lav_vn_hash = mod_hash_create_ptrhash(name, LX_AUTOFS_VFS_VN_HASH_SIZE, mod_hash_null_valdtor, sizeof (vnode_t)); /* Create root vnode */ data->lav_root = i_vn_alloc(vfsp, data->lav_bs_vp); data->lav_root->v_flag |= VROOT | VNOCACHE | VNOMAP | VNOSWAP | VNOMOUNT; return (0); } static int lx_autofs_unmount(vfs_t *vfsp, int flag, struct cred *cr) { lx_autofs_vfs_t *data; if (secpolicy_fs_unmount(cr, vfsp) != 0) return (EPERM); /* We do not currently support forced unmounts. */ if (flag & MS_FORCE) return (ENOTSUP); /* * We should never have a reference count of less than 2: one for the * caller, one for the root vnode. */ ASSERT(vfsp->vfs_count >= 2); /* If there are any outstanding vnodes, we can't unmount. */ if (vfsp->vfs_count > 2) return (EBUSY); /* Check for any remaining holds on the root vnode. */ data = vfsp->vfs_data; ASSERT(data->lav_root->v_vfsp == vfsp); if (data->lav_root->v_count > 1) return (EBUSY); /* Close the fifo to the automount process. */ if (data->lav_fifo_wr != NULL) (void) closef(data->lav_fifo_wr); if (data->lav_fifo_rd != NULL) (void) closef(data->lav_fifo_rd); /* * We have to release our hold on our root vnode before we can * delete the backing store. (Since the root vnode is linked * to the backing store.) */ VN_RELE(data->lav_root); /* Cleanup the backing store. */ i_bs_destroy(data->lav_mvp, data->lav_bs_name); VN_RELE(data->lav_mvp); /* Cleanup out remaining data structures. */ mod_hash_destroy_strhash(data->lav_path_hash); mod_hash_destroy_idhash(data->lav_id_hash); mod_hash_destroy_ptrhash(data->lav_vn_hash); id_space_destroy(data->lav_ids); kmem_free(data, sizeof (lx_autofs_vfs_t)); return (0); } static int lx_autofs_root(vfs_t *vfsp, vnode_t **vpp) { lx_autofs_vfs_t *data = vfsp->vfs_data; *vpp = data->lav_root; VN_HOLD(*vpp); return (0); } static int lx_autofs_statvfs(vfs_t *vfsp, statvfs64_t *sp) { lx_autofs_vfs_t *data = vfsp->vfs_data; vnode_t *urvp = data->lav_root->v_data; dev32_t d32; int error; if ((error = VFS_STATVFS(urvp->v_vfsp, sp)) != 0) return (error); /* Update some of values before returning. */ (void) cmpldev(&d32, vfsp->vfs_dev); sp->f_fsid = d32; (void) strlcpy(sp->f_basetype, vfssw[vfsp->vfs_fstype].vsw_name, sizeof (sp->f_basetype)); sp->f_flag = vf_to_stf(vfsp->vfs_flag); bzero(sp->f_fstr, sizeof (sp->f_fstr)); return (0); } static const fs_operation_def_t lx_autofs_vfstops[] = { { VFSNAME_MOUNT, { .vfs_mount = lx_autofs_mount } }, { VFSNAME_UNMOUNT, { .vfs_unmount = lx_autofs_unmount } }, { VFSNAME_ROOT, { .vfs_root = lx_autofs_root } }, { VFSNAME_STATVFS, { .vfs_statvfs = lx_autofs_statvfs } }, { NULL, NULL } }; /* * VOP entry points - simple passthrough * * For most VOP entry points we can simply pass the request on to * the underlying filesystem we're mounted on. */ static int lx_autofs_close(vnode_t *vp, int flag, int count, offset_t offset, cred_t *cr) { vnode_t *uvp = vp->v_data; return (VOP_CLOSE(uvp, flag, count, offset, cr)); } static int lx_autofs_readdir(vnode_t *vp, uio_t *uiop, cred_t *cr, int *eofp) { vnode_t *uvp = vp->v_data; return (VOP_READDIR(uvp, uiop, cr, eofp)); } static int lx_autofs_access(vnode_t *vp, int mode, int flags, cred_t *cr) { vnode_t *uvp = vp->v_data; return (VOP_ACCESS(uvp, mode, flags, cr)); } static int lx_autofs_rwlock(struct vnode *vp, int write_lock, caller_context_t *ctp) { vnode_t *uvp = vp->v_data; return (VOP_RWLOCK(uvp, write_lock, ctp)); } static void lx_autofs_rwunlock(struct vnode *vp, int write_lock, caller_context_t *ctp) { vnode_t *uvp = vp->v_data; VOP_RWUNLOCK(uvp, write_lock, ctp); } /*ARGSUSED*/ static int lx_autofs_rmdir(vnode_t *dvp, char *nm, vnode_t *cdir, cred_t *cr) { vnode_t *udvp = dvp->v_data; /* * cdir is the calling processes current directory. * If cdir is lx_autofs vnode then get its real underlying * vnode ptr. (It seems like the only thing cdir is * ever used for is to make sure the user doesn't delete * their current directory.) */ if (vn_matchops(cdir, lx_autofs_vn_ops)) { vnode_t *ucdir = cdir->v_data; return (VOP_RMDIR(udvp, nm, ucdir, cr)); } return (VOP_RMDIR(udvp, nm, cdir, cr)); } /* * VOP entry points - special passthrough * * For some VOP entry points we will first pass the request on to * the underlying filesystem we're mounted on. If there's an error * then we immediatly return the error, but if the request succeedes * we have to do some extra work before returning. */ static int lx_autofs_open(vnode_t **vpp, int flag, cred_t *cr) { vnode_t *ovp = *vpp; vnode_t *uvp = ovp->v_data; int error; if ((error = VOP_OPEN(&uvp, flag, cr)) != 0) return (error); /* Check for clone opens. */ if (uvp == ovp->v_data) return (0); /* Deal with clone opens by returning a new vnode. */ *vpp = i_vn_alloc(ovp->v_vfsp, uvp); VN_RELE(ovp); return (0); } static int lx_autofs_getattr(vnode_t *vp, vattr_t *vap, int flags, cred_t *cr) { vnode_t *uvp = vp->v_data; int error; if ((error = VOP_GETATTR(uvp, vap, flags, cr)) != 0) return (error); /* Update the attributes with our filesystem id. */ vap->va_fsid = vp->v_vfsp->vfs_dev; return (0); } static int lx_autofs_mkdir(vnode_t *dvp, char *nm, struct vattr *vap, vnode_t **vpp, cred_t *cr) { vnode_t *udvp = dvp->v_data; vnode_t *uvp = NULL; int error; if ((error = VOP_MKDIR(udvp, nm, vap, &uvp, cr)) != 0) return (error); /* Update the attributes with our filesystem id. */ vap->va_fsid = dvp->v_vfsp->vfs_dev; /* Allocate a new vnode. */ *vpp = i_vn_alloc(dvp->v_vfsp, uvp); return (0); } /* * VOP entry points - custom */ /*ARGSUSED*/ static void lx_autofs_inactive(struct vnode *vp, struct cred *cr) { lx_autofs_vfs_t *data = vp->v_vfsp->vfs_data; /* * We need to hold the vfs lock because if we're going to free * this vnode we have to prevent anyone from looking it up * in the vnode hash. */ mutex_enter(&data->lav_lock); mutex_enter(&vp->v_lock); if (vp->v_count < 1) { panic("lx_autofs_inactive: bad v_count"); /*NOTREACHED*/ } /* Drop the temporary hold by vn_rele now. */ if (--vp->v_count > 0) { mutex_exit(&vp->v_lock); mutex_exit(&data->lav_lock); return; } /* * No one should have been blocked on this lock because we're * about to free this vnode. */ i_vn_free(vp); } static int lx_autofs_lookup(vnode_t *dvp, char *nm, vnode_t **vpp, struct pathname *pnp, int flags, vnode_t *rdir, cred_t *cr) { vnode_t *udvp = dvp->v_data; vnode_t *uvp = NULL; int error; /* First try to lookup if this path component already exitst. */ if ((error = VOP_LOOKUP(udvp, nm, &uvp, pnp, flags, rdir, cr)) == 0) { *vpp = i_vn_alloc(dvp->v_vfsp, uvp); return (0); } /* Only query the automounter if the path does not exist. */ if (error != ENOENT) return (error); /* Refer the lookup to the automounter. */ if ((error = i_automounter_call(dvp, nm)) != 0) return (error); /* Retry the lookup operation. */ if ((error = VOP_LOOKUP(udvp, nm, &uvp, pnp, flags, rdir, cr)) == 0) { *vpp = i_vn_alloc(dvp->v_vfsp, uvp); return (0); } return (error); } /*ARGSUSED*/ static int lx_autofs_ioctl(vnode_t *vp, int cmd, intptr_t arg, int mode, cred_t *cr, int *rvalp) { vnode_t *uvp = vp->v_data; /* Intercept certain ioctls. */ switch ((uint_t)cmd) { case LX_AUTOFS_IOC_READY: case LX_AUTOFS_IOC_FAIL: case LX_AUTOFS_IOC_CATATONIC: case LX_AUTOFS_IOC_EXPIRE: case LX_AUTOFS_IOC_PROTOVER: case LX_AUTOFS_IOC_SETTIMEOUT: return (i_automounter_ioctl(vp, cmd, arg)); } /* Pass any remaining ioctl on. */ return (VOP_IOCTL(uvp, cmd, arg, mode, cr, rvalp)); } /* * VOP entry points definitions */ static const fs_operation_def_t lx_autofs_tops_root[] = { { VOPNAME_OPEN, { .vop_open = lx_autofs_open } }, { VOPNAME_CLOSE, { .vop_close = lx_autofs_close } }, { VOPNAME_IOCTL, { .vop_ioctl = lx_autofs_ioctl } }, { VOPNAME_RWLOCK, { .vop_rwlock = lx_autofs_rwlock } }, { VOPNAME_RWUNLOCK, { .vop_rwunlock = lx_autofs_rwunlock } }, { VOPNAME_GETATTR, { .vop_getattr = lx_autofs_getattr } }, { VOPNAME_ACCESS, { .vop_access = lx_autofs_access } }, { VOPNAME_READDIR, { .vop_readdir = lx_autofs_readdir } }, { VOPNAME_LOOKUP, { .vop_lookup = lx_autofs_lookup } }, { VOPNAME_INACTIVE, { .vop_inactive = lx_autofs_inactive } }, { VOPNAME_MKDIR, { .vop_mkdir = lx_autofs_mkdir } }, { VOPNAME_RMDIR, { .vop_rmdir = lx_autofs_rmdir } }, { NULL } }; /* * lx_autofs_init() gets invoked via the mod_install() call in * this modules _init() routine. Therefor, the code that cleans * up the structures we allocate below is actually found in * our _fini() routine. */ /* ARGSUSED */ static int lx_autofs_init(int fstype, char *name) { int error; if ((lx_autofs_major = (major_t)space_fetch(LX_AUTOFS_SPACE_KEY_UDEV)) == 0) { if ((lx_autofs_major = getudev()) == (major_t)-1) { cmn_err(CE_WARN, "lx_autofs_init: " "can't get unique device number"); return (EAGAIN); } if (space_store(LX_AUTOFS_SPACE_KEY_UDEV, (uintptr_t)lx_autofs_major) != 0) { cmn_err(CE_WARN, "lx_autofs_init: " "can't save unique device number"); return (EAGAIN); } } lx_autofs_fstype = fstype; if ((error = vfs_setfsops( fstype, lx_autofs_vfstops, &lx_autofs_vfsops)) != 0) { cmn_err(CE_WARN, "lx_autofs_init: bad vfs ops template"); return (error); } if ((error = vn_make_ops("lx_autofs vnode ops", lx_autofs_tops_root, &lx_autofs_vn_ops)) != 0) { VERIFY(vfs_freevfsops_by_type(fstype) == 0); lx_autofs_vn_ops = NULL; return (error); } return (0); } /* * Module linkage */ static mntopt_t lx_autofs_mntopt[] = { { LX_MNTOPT_FD, NULL, 0, MO_HASVALUE }, { LX_MNTOPT_PGRP, NULL, 0, MO_HASVALUE }, { LX_MNTOPT_MINPROTO, NULL, 0, MO_HASVALUE }, { LX_MNTOPT_MAXPROTO, NULL, 0, MO_HASVALUE } }; static mntopts_t lx_autofs_mntopts = { sizeof (lx_autofs_mntopt) / sizeof (mntopt_t), lx_autofs_mntopt }; static vfsdef_t vfw = { VFSDEF_VERSION, LX_AUTOFS_NAME, lx_autofs_init, VSW_HASPROTO | VSW_VOLATILEDEV, &lx_autofs_mntopts }; extern struct mod_ops mod_fsops; static struct modlfs modlfs = { &mod_fsops, "linux autofs filesystem", &vfw }; static struct modlinkage modlinkage = { MODREV_1, (void *)&modlfs, NULL }; int _init(void) { return (mod_install(&modlinkage)); } int _info(struct modinfo *modinfop) { return (mod_info(&modlinkage, modinfop)); } int _fini(void) { int error; if ((error = mod_remove(&modlinkage)) != 0) return (error); if (lx_autofs_vn_ops != NULL) { vn_freevnodeops(lx_autofs_vn_ops); lx_autofs_vn_ops = NULL; } /* * In our init routine, if we get an error after calling * vfs_setfsops() we cleanup by calling vfs_freevfsops_by_type(). * But we don't need to call vfs_freevfsops_by_type() here * because the fs framework did this for us as part of the * mod_remove() call above. */ return (0); }