/* -*- 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 #include "modulepriv.hh" #include "clickfs_tree.hh" #include CLICK_CXX_PROTECT #include #include #include #include #include CLICK_CXX_UNPROTECT #include #include #include #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 };