/* -*- c-basic-offset: 4 -*- */
/*
* clickfs_vnops.cc -- Click configuration filesystem for BSD
* Nickolai Zeldovich, Luigi Rizzo, Eddie Kohler, Marko Zec
*
* Copyright (c) 2001 Massachusetts Institute of Technology
* Copyright (c) 2001-2004 International Computer Science Institute
* Copyright (c) 2004 University of Zagreb
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, subject to the conditions
* listed in the Click LICENSE file. These conditions include: you must
* preserve this copyright notice, and you cannot mention the copyright
* holders in advertising related to the Software without their permission.
* The Software is provided WITHOUT ANY WARRANTY, EXPRESS OR IMPLIED. This
* notice is a summary of the Click LICENSE file; the license in that file is
* legally binding.
*/
#include <click/config.h>
#include "modulepriv.hh"
#include "clickfs_tree.hh"
#include <click/cxxprotect.h>
CLICK_CXX_PROTECT
#include <sys/namei.h>
#include <sys/dir.h>
#include <sys/dirent.h>
#include <sys/stat.h>
#include <sys/uio.h>
CLICK_CXX_UNPROTECT
#include <click/cxxunprotect.h>
#include <click/string.hh>
#include <click/error.hh>
#define UIO_MX 32
vop_t **clickfs_vnops;
static enum vtype clickfs_vtype[] = {
VDIR, /* CLICKFS_DIRENT_DIR */
VREG, /* CLICKFS_DIRENT_HANDLE */
VLNK, /* CLICKFS_DIRENT_SYMLINK */
};
#define VTOCDE(vn) ((struct clickfs_dirent *)(vn)->v_data)
int
clickfs_rootvnode(struct mount *mp, struct vnode **vpp)
{
struct vnode *vp;
struct clickfs_dirent *de;
int error;
error = getnewvnode(VT_NON, mp, clickfs_vnops, vpp);
if (error)
return error;
de = clickfs_tree_root;
vp = *vpp;
vp->v_data = de;
vp->v_type = clickfs_vtype[de->type];
vp->v_flag = VROOT;
return 0;
}
int
clickfs_lookup(struct vop_lookup_args *ap)
{
struct componentname *cnp = ap->a_cnp;
struct vnode **vpp = ap->a_vpp;
struct vnode *dvp = ap->a_dvp;
char *pname = cnp->cn_nameptr;
int plen = cnp->cn_namelen;
struct proc *p = cnp->cn_proc;
struct clickfs_dirent *cde= VTOCDE(dvp);
int error = 0;
*vpp = NULLVP;
if (dvp->v_type != VDIR)
return ENOTDIR;
if (cnp->cn_nameiop == DELETE || cnp->cn_nameiop == RENAME)
return EROFS;
VOP_UNLOCK(dvp, 0, p);
if (plen == 1 && *pname == '.') {
*vpp = dvp;
VREF(dvp);
return (0);
}
if (cnp->cn_flags & ISDOTDOT)
cde = cde->data.dir.parent;
else
for (cde = cde->data.dir.head; cde; cde = cde->next)
if (plen == strlen(cde->name) &&
!strncmp(pname, cde->name, plen))
break;
if (!cde) {
error = (cnp->cn_nameiop == LOOKUP) ? ENOENT : EROFS;
goto done;
}
error = getnewvnode(VT_NON, dvp->v_mount, clickfs_vnops, vpp);
if (error)
goto done;
(*vpp)->v_data = cde;
(*vpp)->v_type = clickfs_vtype[cde->type];
if (cde == clickfs_tree_root)
(*vpp)->v_flag = VROOT;
vn_lock(*vpp, LK_SHARED | LK_RETRY, p);
return 0;
done:
vn_lock(dvp, LK_SHARED | LK_RETRY, p);
return error;
}
int
clickfs_getattr(struct vop_getattr_args *ap)
{
struct vnode *vp = ap->a_vp;
struct vattr *vap = ap->a_vap;
struct clickfs_dirent *cde = VTOCDE(vp);
VATTR_NULL(vap);
vap->va_type = vp->v_type;
vap->va_mode = cde->perm;
vap->va_fileid = cde->fileno;
vap->va_nlink = cde->file_refcnt;
vap->va_flags = 0;
vap->va_blocksize = PAGE_SIZE;
vap->va_fsid = vp->v_mount->mnt_stat.f_fsid.val[0];
nanotime(&vap->va_ctime);
vap->va_atime = vap->va_mtime = vap->va_ctime;
vap->va_uid = vap->va_gid = 0;
switch (cde->type) {
case CLICKFS_DIRENT_DIR:
vap->va_bytes = vap->va_size = (cde->file_refcnt-2)*sizeof(*cde);
break;
case CLICKFS_DIRENT_HANDLE:
vap->va_bytes = vap->va_size = 0;
break;
case CLICKFS_DIRENT_SYMLINK:
vap->va_bytes = vap->va_size = strlen(cde->data.slink.name);
break;
}
return 0;
}
int
clickfs_setattr(struct vop_setattr_args *ap)
{
/*
* This doesn't do anything, so we just pretend that it worked.
*/
return 0;
}
int
clickfs_reclaim(struct vop_reclaim_args *ap)
{
return 0;
}
int
clickfs_inactive(struct vop_inactive_args *ap)
{
struct vnode *vp = ap->a_vp;
struct clickfs_dirent *cde = VTOCDE(vp);
if (cde->type == CLICKFS_DIRENT_HANDLE)
cde->data.handle.r_offset = cde->data.handle.w_offset = 0;
vp->v_data = NULL;
vp->v_type = VNON;
VOP_UNLOCK(vp, 0, ap->a_p);
return 0;
}
int
clickfs_access(struct vop_access_args *ap)
{
struct vnode *vp = ap->a_vp;
struct ucred *cred = ap->a_cred;
mode_t mode = ap->a_mode;
struct clickfs_dirent *cde = VTOCDE(vp);
int perm = cde->perm;
/* XXX fixme: also allow others, not just root */
if (cred->cr_uid != 0)
return EPERM;
if ( (mode & VWRITE) && !(perm & S_IWUSR) )
return EPERM;
if ( (mode & VREAD) && !(perm & S_IRUSR) )
return EPERM;
return 0;
}
static int
clickfs_int_send_dirent(char *name, int *skip, int fileno, struct uio *uio)
{
struct dirent d;
bzero((caddr_t) &d, sizeof(d));
d.d_namlen = strlen(name);
bcopy(name, d.d_name, d.d_namlen + 1);
d.d_reclen = UIO_MX;
d.d_fileno = fileno;
d.d_type = DT_UNKNOWN;
if (*skip > 0) {
(*skip)--;
return 0;
}
uio->uio_offset += UIO_MX;
return uiomove((caddr_t) &d, UIO_MX, uio);
}
int
clickfs_readdir(struct vop_readdir_args *ap)
{
int skip, off, error = 0;
struct uio *uio = ap->a_uio;
struct vnode *vp = ap->a_vp;
struct clickfs_dirent *cde = VTOCDE(vp);
struct clickfs_dirent *de;
if (vp->v_type != VDIR)
return ENOTDIR;
off = (int) uio->uio_offset;
if (off < 0 || off % UIO_MX != 0 || uio->uio_resid < UIO_MX)
return EINVAL;
skip = (u_int) off / UIO_MX;
de = cde->data.dir.head;
error = clickfs_int_send_dirent(".", &skip, cde->fileno, uio);
if (!error)
error = clickfs_int_send_dirent("..", &skip, 2, uio);
while (de && !error) {
error = clickfs_int_send_dirent(de->name, &skip, de->fileno, uio);
de = de->next;
}
return error;
}
const Handler *
clickfs_int_get_handler(struct clickfs_dirent *cde)
{
int handle = cde->data.handle.handle;
const Handler *h = Router::handler(click_router, handle);
return h;
}
static Element *
clickfs_int_get_element(struct clickfs_dirent *cde)
{
int eindex = cde->data.handle.eindex;
Element *e = eindex >= 0 ? click_router->element(eindex) : 0;
return e;
}
int
clickfs_open(struct vop_open_args *ap)
{
struct vnode *vp = ap->a_vp;
struct clickfs_dirent *cde = VTOCDE(vp);
if (cde->type == CLICKFS_DIRENT_HANDLE) {
Element *e = clickfs_int_get_element(cde);
const Handler *h = clickfs_int_get_handler(cde);
if (!h)
return ENOENT;
}
return 0;
}
int
clickfs_read(struct vop_read_args *ap)
{
struct vnode *vp = ap->a_vp;
struct clickfs_dirent *cde = VTOCDE(vp);
struct uio *uio = ap->a_uio;
int len, off;
/*
* can only read from regular files
*/
if (ap->a_vp->v_type != VREG)
return EOPNOTSUPP;
off = uio->uio_offset - cde->data.handle.r_offset;
if (off < 0) {
/*
* seek back. Free the old string and reload.
*/
if (cde->data.handle.rbuf) {
delete cde->data.handle.rbuf;
cde->data.handle.rbuf = NULL;
}
cde->data.handle.r_offset = uio->uio_offset;
}
if (!cde->data.handle.rbuf) { /* try to read */
Element *e = clickfs_int_get_element(cde);
const Handler *h = clickfs_int_get_handler(cde);
if (!h)
return ENOENT;
if (!h->read_visible())
return EPERM;
cde->data.handle.rbuf = new String(h->call_read(e));
}
if (!cde->data.handle.rbuf || cde->data.handle.rbuf->out_of_memory()) {
delete cde->data.handle.rbuf;
cde->data.handle.rbuf = NULL;
return ENOMEM;
}
len = cde->data.handle.rbuf->length();
if (off >= len) {
/*
* no more data. Return 0, but get rid of the string
* so future reads will refresh the data.
*/
cde->data.handle.r_offset += len ;
delete cde->data.handle.rbuf;
cde->data.handle.rbuf = NULL;
return 0;
}
len = uiomove((char *)cde->data.handle.rbuf->data() + off, len - off, uio);
return len;
}
int
clickfs_write(struct vop_write_args *ap)
{
struct vnode *vp = ap->a_vp;
struct clickfs_dirent *cde = VTOCDE(vp);
struct uio *uio = ap->a_uio;
int off = uio->uio_offset - cde->data.handle.w_offset;
int len = uio->uio_resid;
/*
* can only write to regular files
*/
if (ap->a_vp->v_type != VREG)
return EOPNOTSUPP;
if (cde->data.handle.wbuf == NULL) {
const Handler *h = clickfs_int_get_handler(cde);
if (!h)
return ENOENT;
if (!h->write_visible())
return EPERM;
cde->data.handle.wbuf = new String();
if (cde->data.handle.wbuf == NULL)
return ENOMEM;
}
int last_len = cde->data.handle.wbuf->length();
int end_pos = off + len;
if (end_pos > last_len)
cde->data.handle.wbuf->append_fill(0, end_pos - last_len);
char *x = cde->data.handle.wbuf->mutable_data() + off;
if (end_pos > last_len)
memset(x, 0, end_pos - last_len);
return uiomove(x, len, uio);
}
int
clickfs_fsync_body(struct clickfs_dirent *cde)
{
int retval = 0;
if (cde->type == CLICKFS_DIRENT_HANDLE) {
const Handler *h = clickfs_int_get_handler(cde);
if (cde->data.handle.rbuf == NULL && cde->data.handle.wbuf == NULL) {
// empty write, prepare something.
cde->data.handle.wbuf = new String("");
}
if (!h || !h->writable())
retval = EINVAL;
else if (cde->data.handle.wbuf != NULL) {
Element *e = clickfs_int_get_element(cde);
String context_string = "In write handler `" + h->name() + "'";
if (e)
context_string += String(" for `") + e->declaration() + "'";
ContextErrorHandler cerrh(click_logged_errh, context_string + ":");
retval = h->call_write(*cde->data.handle.wbuf, e, true, &cerrh);
retval = (retval >= 0 ? 0 : -retval);
}
/*
* now dispose read buffer if any
*/
if (cde->data.handle.rbuf) {
delete cde->data.handle.rbuf;
cde->data.handle.rbuf = NULL;
}
/*
* skip current buffer
*/
if (cde->data.handle.wbuf) {
cde->data.handle.w_offset += cde->data.handle.wbuf->length();
delete cde->data.handle.wbuf;
cde->data.handle.wbuf = NULL;
}
}
return retval;
}
int
clickfs_close(struct vop_close_args *ap)
{
struct vnode *vp = ap->a_vp;
struct clickfs_dirent *cde = VTOCDE(vp);
int flags = ap->a_fflag;
int retval = 0;
if (flags & FWRITE)
retval = clickfs_fsync_body(cde);
return retval;
}
int
clickfs_readlink(struct vop_readlink_args *ap)
{
struct vnode *vp = ap->a_vp;
struct clickfs_dirent *cde = VTOCDE(vp);
if (cde->type != CLICKFS_DIRENT_SYMLINK)
return EINVAL;
return uiomove(cde->data.slink.name,
strlen(cde->data.slink.name), ap->a_uio);
}
int
clickfs_fsync(struct vop_fsync_args *ap)
{
struct vnode *vp = ap->a_vp;
struct clickfs_dirent *cde = VTOCDE(vp);
return(clickfs_fsync_body(cde));
}
int
clickfs_default(struct vop_generic_args *ap)
{
return(vop_defaultop(ap));
}
static struct vnodeopv_entry_desc clickfs_root_vnop_entries[] =
{
{ &vop_default_desc, (vop_t *) clickfs_default },
{ &vop_lookup_desc, (vop_t *) clickfs_lookup },
{ &vop_getattr_desc, (vop_t *) clickfs_getattr },
{ &vop_setattr_desc, (vop_t *) clickfs_setattr },
{ &vop_reclaim_desc, (vop_t *) clickfs_reclaim },
{ &vop_inactive_desc, (vop_t *) clickfs_inactive },
{ &vop_access_desc, (vop_t *) clickfs_access },
{ &vop_readdir_desc, (vop_t *) clickfs_readdir },
{ &vop_open_desc, (vop_t *) clickfs_open },
{ &vop_read_desc, (vop_t *) clickfs_read },
{ &vop_write_desc, (vop_t *) clickfs_write },
{ &vop_close_desc, (vop_t *) clickfs_close },
{ &vop_fsync_desc, (vop_t *) clickfs_fsync },
{ &vop_readlink_desc, (vop_t *) clickfs_readlink },
{ (struct vnodeop_desc *) NULL, (int (*) (void *)) NULL }
};
struct vnodeopv_desc clickfs_vnodeop_opv_desc =
{ &clickfs_vnops, clickfs_root_vnop_entries };
syntax highlighted by Code2HTML, v. 0.9.1