/* * Copyright (c) 2014 * The President and Fellows of Harvard College. * * 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. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE UNIVERSITY AND CONTRIBUTORS ``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 UNIVERSITY OR CONTRIBUTORS 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 #include #include #include #include #include #include #include #include #include #include #include "semfs.h" //////////////////////////////////////////////////////////// // basic ops static int semfs_eachopen(struct vnode *vn, int openflags) { struct semfs_vnode *semv = vn->vn_data; if (semv->semv_semnum == SEMFS_ROOTDIR) { if ((openflags & O_ACCMODE) != O_RDONLY) { return EISDIR; } if (openflags & O_APPEND) { return EISDIR; } } return 0; } static int semfs_ioctl(struct vnode *vn, int op, userptr_t data) { (void)vn; (void)op; (void)data; return EINVAL; } static int semfs_gettype(struct vnode *vn, mode_t *ret) { struct semfs_vnode *semv = vn->vn_data; *ret = semv->semv_semnum == SEMFS_ROOTDIR ? S_IFDIR : S_IFREG; return 0; } static bool semfs_isseekable(struct vnode *vn) { struct semfs_vnode *semv = vn->vn_data; if (semv->semv_semnum != SEMFS_ROOTDIR) { /* seeking a semaphore doesn't mean anything */ return false; } return true; } static int semfs_fsync(struct vnode *vn) { (void)vn; return 0; } //////////////////////////////////////////////////////////// // semaphore ops /* * XXX fold these two together */ static struct semfs_sem * semfs_getsembynum(struct semfs *semfs, unsigned semnum) { struct semfs_sem *sem; lock_acquire(semfs->semfs_tablelock); sem = semfs_semarray_get(semfs->semfs_sems, semnum); lock_release(semfs->semfs_tablelock); return sem; } static struct semfs_sem * semfs_getsem(struct semfs_vnode *semv) { struct semfs *semfs = semv->semv_semfs; return semfs_getsembynum(semfs, semv->semv_semnum); } /* * Wakeup helper. We only need to wake up if there are sleepers, which * should only be the case if the old count is 0; and we only * potentially need to wake more than one sleeper if the new count * will be more than 1. */ static void semfs_wakeup(struct semfs_sem *sem, unsigned newcount) { if (sem->sems_count > 0 || newcount == 0) { return; } if (newcount == 1) { cv_signal(sem->sems_cv, sem->sems_lock); } else { cv_broadcast(sem->sems_cv, sem->sems_lock); } } /* * stat() for semaphore vnodes */ static int semfs_semstat(struct vnode *vn, struct stat *buf) { struct semfs_vnode *semv = vn->vn_data; struct semfs_sem *sem; sem = semfs_getsem(semv); bzero(buf, sizeof(*buf)); lock_acquire(sem->sems_lock); buf->st_size = sem->sems_count; buf->st_nlink = sem->sems_linked ? 1 : 0; lock_release(sem->sems_lock); buf->st_mode = S_IFREG | 0666; buf->st_blocks = 0; buf->st_dev = 0; buf->st_ino = semv->semv_semnum; return 0; } /* * Read. This is P(); decrease the count by the amount read. * Don't actually bother to transfer any data. */ static int semfs_read(struct vnode *vn, struct uio *uio) { struct semfs_vnode *semv = vn->vn_data; struct semfs_sem *sem; size_t consume; sem = semfs_getsem(semv); lock_acquire(sem->sems_lock); while (uio->uio_resid > 0) { if (sem->sems_count > 0) { consume = uio->uio_resid; if (consume > sem->sems_count) { consume = sem->sems_count; } DEBUG(DB_SEMFS, "semfs: sem%u: P, count %u -> %u\n", semv->semv_semnum, sem->sems_count, sem->sems_count - consume); sem->sems_count -= consume; /* don't bother advancing the uio data pointers */ uio->uio_resid -= consume; } if (uio->uio_resid == 0) { break; } if (sem->sems_count == 0) { DEBUG(DB_SEMFS, "semfs: sem%u: blocking\n", semv->semv_semnum); cv_wait(sem->sems_cv, sem->sems_lock); } } lock_release(sem->sems_lock); return 0; } /* * Write. This is V(); increase the count by the amount written. * Don't actually bother to transfer any data. */ static int semfs_write(struct vnode *vn, struct uio *uio) { struct semfs_vnode *semv = vn->vn_data; struct semfs_sem *sem; unsigned newcount; sem = semfs_getsem(semv); lock_acquire(sem->sems_lock); while (uio->uio_resid > 0) { newcount = sem->sems_count + uio->uio_resid; if (newcount < sem->sems_count) { /* overflow */ lock_release(sem->sems_lock); return EFBIG; } DEBUG(DB_SEMFS, "semfs: sem%u: V, count %u -> %u\n", semv->semv_semnum, sem->sems_count, newcount); semfs_wakeup(sem, newcount); sem->sems_count = newcount; uio->uio_resid = 0; } lock_release(sem->sems_lock); return 0; } /* * Truncate. Set the count to the specified value. * * This is slightly cheesy but it allows open(..., O_TRUNC) to reset a * semaphore as one would expect. Also it allows creating semaphors * and then initializing their counts to values other than zero. */ static int semfs_truncate(struct vnode *vn, off_t len) { /* We should just use UINT_MAX but we don't have it in the kernel */ const unsigned max = (unsigned)-1; struct semfs_vnode *semv = vn->vn_data; struct semfs_sem *sem; unsigned newcount; if (len < 0) { return EINVAL; } if (len > (off_t)max) { return EFBIG; } newcount = len; sem = semfs_getsem(semv); lock_acquire(sem->sems_lock); semfs_wakeup(sem, newcount); sem->sems_count = newcount; lock_release(sem->sems_lock); return 0; } //////////////////////////////////////////////////////////// // directory ops /* * Directory read. Note that there's only one directory (the semfs * root) that has all the semaphores in it. */ static int semfs_getdirentry(struct vnode *dirvn, struct uio *uio) { struct semfs_vnode *dirsemv = dirvn->vn_data; struct semfs *semfs = dirsemv->semv_semfs; struct semfs_direntry *dent; unsigned num, pos; int result; KASSERT(uio->uio_offset >= 0); pos = uio->uio_offset; lock_acquire(semfs->semfs_dirlock); num = semfs_direntryarray_num(semfs->semfs_dents); if (pos >= num) { /* EOF */ result = 0; } else { dent = semfs_direntryarray_get(semfs->semfs_dents, pos); result = uiomove(dent->semd_name, strlen(dent->semd_name), uio); } lock_release(semfs->semfs_dirlock); return result; } /* * stat() for dirs */ static int semfs_dirstat(struct vnode *vn, struct stat *buf) { struct semfs_vnode *semv = vn->vn_data; struct semfs *semfs = semv->semv_semfs; bzero(buf, sizeof(*buf)); lock_acquire(semfs->semfs_dirlock); buf->st_size = semfs_direntryarray_num(semfs->semfs_dents); lock_release(semfs->semfs_dirlock); buf->st_mode = S_IFDIR | 1777; buf->st_nlink = 2; buf->st_blocks = 0; buf->st_dev = 0; buf->st_ino = SEMFS_ROOTDIR; return 0; } /* * Backend for getcwd. Since we don't support subdirs, it's easy; send * back the empty string. */ static int semfs_namefile(struct vnode *vn, struct uio *uio) { (void)vn; (void)uio; return 0; } /* * Create a semaphore. */ static int semfs_creat(struct vnode *dirvn, const char *name, bool excl, mode_t mode, struct vnode **resultvn) { struct semfs_vnode *dirsemv = dirvn->vn_data; struct semfs *semfs = dirsemv->semv_semfs; struct semfs_direntry *dent; struct semfs_sem *sem; unsigned i, num, empty, semnum; int result; (void)mode; if (!strcmp(name, ".") || !strcmp(name, "..")) { return EEXIST; } lock_acquire(semfs->semfs_dirlock); num = semfs_direntryarray_num(semfs->semfs_dents); empty = num; for (i=0; isemfs_dents, i); if (dent == NULL) { if (empty == num) { empty = i; } continue; } if (!strcmp(dent->semd_name, name)) { /* found */ if (excl) { lock_release(semfs->semfs_dirlock); return EEXIST; } result = semfs_getvnode(semfs, dent->semd_semnum, resultvn); lock_release(semfs->semfs_dirlock); return result; } } /* create it */ sem = semfs_sem_create(name); if (sem == NULL) { result = ENOMEM; goto fail_unlock; } lock_acquire(semfs->semfs_tablelock); result = semfs_sem_insert(semfs, sem, &semnum); lock_release(semfs->semfs_tablelock); if (result) { goto fail_uncreate; } dent = semfs_direntry_create(name, semnum); if (dent == NULL) { goto fail_uninsert; } if (empty < num) { semfs_direntryarray_set(semfs->semfs_dents, empty, dent); } else { result = semfs_direntryarray_add(semfs->semfs_dents, dent, &empty); if (result) { goto fail_undent; } } result = semfs_getvnode(semfs, semnum, resultvn); if (result) { goto fail_undir; } sem->sems_linked = true; lock_release(semfs->semfs_dirlock); return 0; fail_undir: semfs_direntryarray_set(semfs->semfs_dents, empty, NULL); fail_undent: semfs_direntry_destroy(dent); fail_uninsert: lock_acquire(semfs->semfs_tablelock); semfs_semarray_set(semfs->semfs_sems, semnum, NULL); lock_release(semfs->semfs_tablelock); fail_uncreate: semfs_sem_destroy(sem); fail_unlock: lock_release(semfs->semfs_dirlock); return result; } /* * Unlink a semaphore. As with other files, it may not actually * go away if it's currently open. */ static int semfs_remove(struct vnode *dirvn, const char *name) { struct semfs_vnode *dirsemv = dirvn->vn_data; struct semfs *semfs = dirsemv->semv_semfs; struct semfs_direntry *dent; struct semfs_sem *sem; unsigned i, num; int result; if (!strcmp(name, ".") || !strcmp(name, "..")) { return EINVAL; } lock_acquire(semfs->semfs_dirlock); num = semfs_direntryarray_num(semfs->semfs_dents); for (i=0; isemfs_dents, i); if (dent == NULL) { continue; } if (!strcmp(name, dent->semd_name)) { /* found */ sem = semfs_getsembynum(semfs, dent->semd_semnum); lock_acquire(sem->sems_lock); KASSERT(sem->sems_linked); sem->sems_linked = false; if (sem->sems_hasvnode == false) { lock_acquire(semfs->semfs_tablelock); semfs_semarray_set(semfs->semfs_sems, dent->semd_semnum, NULL); lock_release(semfs->semfs_tablelock); lock_release(sem->sems_lock); semfs_sem_destroy(sem); } else { lock_release(sem->sems_lock); } semfs_direntryarray_set(semfs->semfs_dents, i, NULL); semfs_direntry_destroy(dent); result = 0; goto out; } } result = ENOENT; out: lock_release(semfs->semfs_dirlock); return result; } /* * Lookup: get a semaphore by name. */ static int semfs_lookup(struct vnode *dirvn, char *path, struct vnode **resultvn) { struct semfs_vnode *dirsemv = dirvn->vn_data; struct semfs *semfs = dirsemv->semv_semfs; struct semfs_direntry *dent; unsigned i, num; int result; if (!strcmp(path, ".") || !strcmp(path, "..")) { VOP_INCREF(dirvn); *resultvn = dirvn; return 0; } lock_acquire(semfs->semfs_dirlock); num = semfs_direntryarray_num(semfs->semfs_dents); for (i=0; isemfs_dents, i); if (dent == NULL) { continue; } if (!strcmp(path, dent->semd_name)) { result = semfs_getvnode(semfs, dent->semd_semnum, resultvn); lock_release(semfs->semfs_dirlock); return result; } } lock_release(semfs->semfs_dirlock); return ENOENT; } /* * Lookparent: because we don't have subdirs, just return the root * dir and copy the name. */ static int semfs_lookparent(struct vnode *dirvn, char *path, struct vnode **resultdirvn, char *namebuf, size_t bufmax) { if (strlen(path)+1 > bufmax) { return ENAMETOOLONG; } strcpy(namebuf, path); VOP_INCREF(dirvn); *resultdirvn = dirvn; return 0; } //////////////////////////////////////////////////////////// // vnode lifecycle operations /* * Destructor for semfs_vnode. */ static void semfs_vnode_destroy(struct semfs_vnode *semv) { vnode_cleanup(&semv->semv_absvn); kfree(semv); } /* * Reclaim - drop a vnode that's no longer in use. */ static int semfs_reclaim(struct vnode *vn) { struct semfs_vnode *semv = vn->vn_data; struct semfs *semfs = semv->semv_semfs; struct vnode *vn2; struct semfs_sem *sem; unsigned i, num; lock_acquire(semfs->semfs_tablelock); /* vnode refcount is protected by the vnode's ->vn_countlock */ spinlock_acquire(&vn->vn_countlock); if (vn->vn_refcount > 1) { /* consume the reference VOP_DECREF passed us */ vn->vn_refcount--; spinlock_release(&vn->vn_countlock); lock_release(semfs->semfs_tablelock); return EBUSY; } spinlock_release(&vn->vn_countlock); /* remove from the table */ num = vnodearray_num(semfs->semfs_vnodes); for (i=0; isemfs_vnodes, i); if (vn2 == vn) { vnodearray_remove(semfs->semfs_vnodes, i); break; } } if (semv->semv_semnum != SEMFS_ROOTDIR) { sem = semfs_semarray_get(semfs->semfs_sems, semv->semv_semnum); KASSERT(sem->sems_hasvnode); sem->sems_hasvnode = false; if (sem->sems_linked == false) { semfs_semarray_set(semfs->semfs_sems, semv->semv_semnum, NULL); semfs_sem_destroy(sem); } } /* done with the table */ lock_release(semfs->semfs_tablelock); /* destroy it */ semfs_vnode_destroy(semv); return 0; } /* * Vnode ops table for dirs. */ static const struct vnode_ops semfs_dirops = { .vop_magic = VOP_MAGIC, .vop_eachopen = semfs_eachopen, .vop_reclaim = semfs_reclaim, .vop_read = vopfail_uio_isdir, .vop_readlink = vopfail_uio_isdir, .vop_getdirentry = semfs_getdirentry, .vop_write = vopfail_uio_isdir, .vop_ioctl = semfs_ioctl, .vop_stat = semfs_dirstat, .vop_gettype = semfs_gettype, .vop_isseekable = semfs_isseekable, .vop_fsync = semfs_fsync, .vop_mmap = vopfail_mmap_isdir, .vop_truncate = vopfail_truncate_isdir, .vop_namefile = semfs_namefile, .vop_creat = semfs_creat, .vop_symlink = vopfail_symlink_nosys, .vop_mkdir = vopfail_mkdir_nosys, .vop_link = vopfail_link_nosys, .vop_remove = semfs_remove, .vop_rmdir = vopfail_string_nosys, .vop_rename = vopfail_rename_nosys, .vop_lookup = semfs_lookup, .vop_lookparent = semfs_lookparent, }; /* * Vnode ops table for semaphores (files). */ static const struct vnode_ops semfs_semops = { .vop_magic = VOP_MAGIC, .vop_eachopen = semfs_eachopen, .vop_reclaim = semfs_reclaim, .vop_read = semfs_read, .vop_readlink = vopfail_uio_inval, .vop_getdirentry = vopfail_uio_notdir, .vop_write = semfs_write, .vop_ioctl = semfs_ioctl, .vop_stat = semfs_semstat, .vop_gettype = semfs_gettype, .vop_isseekable = semfs_isseekable, .vop_fsync = semfs_fsync, .vop_mmap = vopfail_mmap_perm, .vop_truncate = semfs_truncate, .vop_namefile = vopfail_uio_notdir, .vop_creat = vopfail_creat_notdir, .vop_symlink = vopfail_symlink_notdir, .vop_mkdir = vopfail_mkdir_notdir, .vop_link = vopfail_link_notdir, .vop_remove = vopfail_string_notdir, .vop_rmdir = vopfail_string_notdir, .vop_rename = vopfail_rename_notdir, .vop_lookup = vopfail_lookup_notdir, .vop_lookparent = vopfail_lookparent_notdir, }; /* * Constructor for semfs vnodes. */ static struct semfs_vnode * semfs_vnode_create(struct semfs *semfs, unsigned semnum) { const struct vnode_ops *optable; struct semfs_vnode *semv; int result; if (semnum == SEMFS_ROOTDIR) { optable = &semfs_dirops; } else { optable = &semfs_semops; } semv = kmalloc(sizeof(*semv)); if (semv == NULL) { return NULL; } semv->semv_semfs = semfs; semv->semv_semnum = semnum; result = vnode_init(&semv->semv_absvn, optable, &semfs->semfs_absfs, semv); /* vnode_init doesn't actually fail */ KASSERT(result == 0); return semv; } /* * Look up the vnode for a semaphore by number; if it doesn't exist, * create it. */ int semfs_getvnode(struct semfs *semfs, unsigned semnum, struct vnode **ret) { struct vnode *vn; struct semfs_vnode *semv; struct semfs_sem *sem; unsigned i, num; int result; /* Lock the vnode table */ lock_acquire(semfs->semfs_tablelock); /* Look for it */ num = vnodearray_num(semfs->semfs_vnodes); for (i=0; isemfs_vnodes, i); semv = vn->vn_data; if (semv->semv_semnum == semnum) { VOP_INCREF(vn); lock_release(semfs->semfs_tablelock); *ret = vn; return 0; } } /* Make it */ semv = semfs_vnode_create(semfs, semnum); if (semv == NULL) { lock_release(semfs->semfs_tablelock); return ENOMEM; } result = vnodearray_add(semfs->semfs_vnodes, &semv->semv_absvn, NULL); if (result) { semfs_vnode_destroy(semv); lock_release(semfs->semfs_tablelock); return ENOMEM; } if (semnum != SEMFS_ROOTDIR) { sem = semfs_semarray_get(semfs->semfs_sems, semnum); KASSERT(sem != NULL); KASSERT(sem->sems_hasvnode == false); sem->sems_hasvnode = true; } lock_release(semfs->semfs_tablelock); *ret = &semv->semv_absvn; return 0; }