os161/kern/fs/semfs/semfs_vnops.c
2015-12-23 00:50:04 +00:00

800 lines
18 KiB
C

/*
* 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 <types.h>
#include <kern/errno.h>
#include <kern/fcntl.h>
#include <stat.h>
#include <uio.h>
#include <synch.h>
#include <thread.h>
#include <proc.h>
#include <current.h>
#include <vfs.h>
#include <vnode.h>
#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_offset += consume;
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_offset += uio->uio_resid;
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 semaphores
* 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; i<num; i++) {
dent = semfs_direntryarray_get(semfs->semfs_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; i<num; i++) {
dent = semfs_direntryarray_get(semfs->semfs_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; i<num; i++) {
dent = semfs_direntryarray_get(semfs->semfs_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; i<num; i++) {
vn2 = vnodearray_get(semfs->semfs_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; i<num; i++) {
vn = vnodearray_get(semfs->semfs_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;
}