/* $NetBSD: puffs.c,v 1.92.4.4 2009/10/27 20:37:38 bouyer Exp $ */ /* * Copyright (c) 2005, 2006, 2007 Antti Kantee. All Rights Reserved. * * Development of this software was supported by the * Google Summer of Code program and the Ulla Tuominen Foundation. * The Google SoC project was mentored by Bill Studenmund. * * 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. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. */ /* TODO: We don't support PUFFS_COMFD used in original puffs_mount, * add it to the docs if any. * */ #include "fs.h" #include #if !defined(lint) __RCSID("$NetBSD: puffs.c,v 1.92.4.4 2009/10/27 20:37:38 bouyer Exp $"); #endif /* !lint */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "puffs.h" #include "puffs_priv.h" #ifdef PUFFS_WITH_THREADS #include pthread_mutex_t pu_lock = PTHREAD_MUTEX_INITIALIZER; #endif /* Declare some local functions. */ static void get_work(message *m_in); static void reply(endpoint_t who, message *m_out); /* SEF functions and variables. */ static void sef_local_startup(void); static int sef_cb_init_fresh(int type, sef_init_info_t *info); static void sef_cb_signal_handler(int signo); EXTERN int env_argc; EXTERN char **env_argv; #define PUFFS_MAX_ARGS 20 int __real_main(int argc, char* argv[]); int __wrap_main(int argc, char *argv[]) { int i; int new_argc = 0; static char* new_argv[PUFFS_MAX_ARGS]; char *name; /* SEF local startup. */ env_setargs(argc, argv); sef_local_startup(); global_kcred.pkcr_type = PUFFCRED_TYPE_INTERNAL; if (argc < 3) { panic("Unexpected arguments, use:\ mount -t fs /dev/ /dir [-o option1,option2]\n"); } name = argv[0] + strlen(argv[0]); while (*name != '/' && name != argv[0]) name--; if (name != argv[0]) name++; strcpy(fs_name, name); new_argv[new_argc] = argv[0]; new_argc++; for (i = 1; i < argc; i++) { if (new_argc >= PUFFS_MAX_ARGS) { panic("Too many arguments, change PUFFS_MAX_ARGS"); } new_argv[new_argc] = argv[i]; new_argc++; } assert(new_argc > 0); get_work(&fs_m_in); return __real_main(new_argc, new_argv); } #define FILLOP(lower, upper) \ do { \ if (pops->puffs_node_##lower) \ opmask[PUFFS_VN_##upper] = 1; \ } while (/*CONSTCOND*/0) static void fillvnopmask(struct puffs_ops *pops, uint8_t *opmask) { memset(opmask, 0, PUFFS_VN_MAX); FILLOP(create, CREATE); FILLOP(mknod, MKNOD); FILLOP(open, OPEN); FILLOP(close, CLOSE); FILLOP(access, ACCESS); FILLOP(getattr, GETATTR); FILLOP(setattr, SETATTR); FILLOP(poll, POLL); /* XXX: not ready in kernel */ FILLOP(mmap, MMAP); FILLOP(fsync, FSYNC); FILLOP(seek, SEEK); FILLOP(remove, REMOVE); FILLOP(link, LINK); FILLOP(rename, RENAME); FILLOP(mkdir, MKDIR); FILLOP(rmdir, RMDIR); FILLOP(symlink, SYMLINK); FILLOP(readdir, READDIR); FILLOP(readlink, READLINK); FILLOP(reclaim, RECLAIM); FILLOP(inactive, INACTIVE); FILLOP(print, PRINT); FILLOP(read, READ); FILLOP(write, WRITE); FILLOP(abortop, ABORTOP); } #undef FILLOP /*ARGSUSED*/ static void puffs_defaulterror(struct puffs_usermount *pu, uint8_t type, int error, const char *str, puffs_cookie_t cookie) { lpuffs_debug("abort: type %d, error %d, cookie %p (%s)\n", type, error, cookie, str); abort(); } int puffs_getstate(struct puffs_usermount *pu) { return pu->pu_state & PU_STATEMASK; } void puffs_setstacksize(struct puffs_usermount *pu, size_t ss) { long psize, minsize; int stackshift; int bonus; assert(puffs_getstate(pu) == PUFFS_STATE_BEFOREMOUNT); psize = sysconf(_SC_PAGESIZE); minsize = 4*psize; if (ss < minsize || ss == PUFFS_STACKSIZE_MIN) { if (ss != PUFFS_STACKSIZE_MIN) lpuffs_debug("puffs_setstacksize: adjusting " "stacksize to minimum %ld\n", minsize); ss = 4*psize; } stackshift = -1; bonus = 0; while (ss) { if (ss & 0x1) bonus++; ss >>= 1; stackshift++; } if (bonus > 1) { stackshift++; lpuffs_debug("puffs_setstacksize: using next power of two: " "%d\n", 1<pu_cc_stackshift = stackshift; } struct puffs_pathobj * puffs_getrootpathobj(struct puffs_usermount *pu) { struct puffs_node *pnr; pnr = pu->pu_pn_root; if (pnr == NULL) { errno = ENOENT; return NULL; } return &pnr->pn_po; } void puffs_setroot(struct puffs_usermount *pu, struct puffs_node *pn) { pu->pu_pn_root = pn; } struct puffs_node * puffs_getroot(struct puffs_usermount *pu) { return pu->pu_pn_root; } void puffs_setrootinfo(struct puffs_usermount *pu, enum vtype vt, vsize_t vsize, dev_t rdev) { struct puffs_kargs *pargs = pu->pu_kargp; if (puffs_getstate(pu) != PUFFS_STATE_BEFOREMOUNT) { warnx("puffs_setrootinfo: call has effect only " "before mount\n"); return; } pargs->pa_root_vtype = vt; pargs->pa_root_vsize = vsize; pargs->pa_root_rdev = rdev; } void * puffs_getspecific(struct puffs_usermount *pu) { return pu->pu_privdata; } void puffs_setspecific(struct puffs_usermount *pu, void *privdata) { pu->pu_privdata = privdata; } void puffs_setmntinfo(struct puffs_usermount *pu, const char *mntfromname, const char *puffsname) { struct puffs_kargs *pargs = pu->pu_kargp; (void)strlcpy(pargs->pa_mntfromname, mntfromname, sizeof(pargs->pa_mntfromname)); (void)strlcpy(pargs->pa_typename, puffsname, sizeof(pargs->pa_typename)); } size_t puffs_getmaxreqlen(struct puffs_usermount *pu) { return pu->pu_maxreqlen; } void puffs_setmaxreqlen(struct puffs_usermount *pu, size_t reqlen) { if (puffs_getstate(pu) != PUFFS_STATE_BEFOREMOUNT) warnx("puffs_setmaxreqlen: call has effect only " "before mount\n"); pu->pu_kargp->pa_maxmsglen = reqlen; } void puffs_setfhsize(struct puffs_usermount *pu, size_t fhsize, int flags) { if (puffs_getstate(pu) != PUFFS_STATE_BEFOREMOUNT) warnx("puffs_setfhsize: call has effect only before mount\n"); pu->pu_kargp->pa_fhsize = fhsize; pu->pu_kargp->pa_fhflags = flags; } void puffs_setncookiehash(struct puffs_usermount *pu, int nhash) { if (puffs_getstate(pu) != PUFFS_STATE_BEFOREMOUNT) warnx("puffs_setfhsize: call has effect only before mount\n"); pu->pu_kargp->pa_nhashbuckets = nhash; } void puffs_set_pathbuild(struct puffs_usermount *pu, pu_pathbuild_fn fn) { pu->pu_pathbuild = fn; } void puffs_set_pathtransform(struct puffs_usermount *pu, pu_pathtransform_fn fn) { pu->pu_pathtransform = fn; } void puffs_set_pathcmp(struct puffs_usermount *pu, pu_pathcmp_fn fn) { pu->pu_pathcmp = fn; } void puffs_set_pathfree(struct puffs_usermount *pu, pu_pathfree_fn fn) { pu->pu_pathfree = fn; } void puffs_set_namemod(struct puffs_usermount *pu, pu_namemod_fn fn) { pu->pu_namemod = fn; } void puffs_set_errnotify(struct puffs_usermount *pu, pu_errnotify_fn fn) { pu->pu_errnotify = fn; } void puffs_set_cmap(struct puffs_usermount *pu, pu_cmap_fn fn) { pu->pu_cmap = fn; } void puffs_ml_setloopfn(struct puffs_usermount *pu, puffs_ml_loop_fn lfn) { pu->pu_ml_lfn = lfn; } void puffs_ml_settimeout(struct puffs_usermount *pu, struct timespec *ts) { if (ts == NULL) { pu->pu_ml_timep = NULL; } else { pu->pu_ml_timeout = *ts; pu->pu_ml_timep = &pu->pu_ml_timeout; } } void puffs_set_prepost(struct puffs_usermount *pu, pu_prepost_fn pre, pu_prepost_fn pst) { pu->pu_oppre = pre; pu->pu_oppost = pst; } int puffs_mount(struct puffs_usermount *pu, const char *dir, int mntflags, puffs_cookie_t cookie) { endpoint_t src; int error, ind; pu->pu_kargp->pa_root_cookie = cookie; src = fs_m_in.m_source; error = OK; caller_uid = INVAL_UID; /* To trap errors */ caller_gid = INVAL_GID; req_nr = fs_m_in.m_type; if (req_nr < VFS_BASE) { fs_m_in.m_type += VFS_BASE; req_nr = fs_m_in.m_type; } ind = req_nr - VFS_BASE; assert(ind == REQ_READ_SUPER); if (ind < 0 || ind >= NREQS) { error = EINVAL; } else { error = (*fs_call_vec[ind])(); } fs_m_out.m_type = error; if (IS_VFS_FS_TRANSID(last_request_transid)) { /* If a transaction ID was set, reset it */ fs_m_out.m_type = TRNS_ADD_ID(fs_m_out.m_type, last_request_transid); } reply(src, &fs_m_out); if (error) { free(pu->pu_kargp); pu->pu_kargp = NULL; errno = error; return -1; } PU_SETSTATE(pu, PUFFS_STATE_RUNNING); return 0; } /*ARGSUSED*/ struct puffs_usermount * _puffs_init(int dummy, struct puffs_ops *pops, const char *mntfromname, const char *puffsname, void *priv, uint32_t pflags) { struct puffs_usermount *pu; struct puffs_kargs *pargs; int sverrno; if (puffsname == PUFFS_DEFER) puffsname = "n/a"; if (mntfromname == PUFFS_DEFER) mntfromname = "n/a"; if (priv == PUFFS_DEFER) priv = NULL; pu = malloc(sizeof(struct puffs_usermount)); if (pu == NULL) goto failfree; memset(pu, 0, sizeof(struct puffs_usermount)); pargs = pu->pu_kargp = malloc(sizeof(struct puffs_kargs)); if (pargs == NULL) goto failfree; memset(pargs, 0, sizeof(struct puffs_kargs)); pargs->pa_vers = PUFFSDEVELVERS | PUFFSVERSION; pargs->pa_flags = PUFFS_FLAG_KERN(pflags); fillvnopmask(pops, pargs->pa_vnopmask); puffs_setmntinfo(pu, mntfromname, puffsname); puffs_zerostatvfs(&pargs->pa_svfsb); pargs->pa_root_cookie = NULL; pargs->pa_root_vtype = VDIR; pargs->pa_root_vsize = 0; pargs->pa_root_rdev = 0; pargs->pa_maxmsglen = 0; pu->pu_flags = pflags; buildpath = pu->pu_flags & PUFFS_FLAG_BUILDPATH; /* XXX */ pu->pu_ops = *pops; free(pops); /* XXX */ pu->pu_privdata = priv; pu->pu_cc_stackshift = PUFFS_CC_STACKSHIFT_DEFAULT; LIST_INIT(&pu->pu_pnodelst); LIST_INIT(&pu->pu_pnode_removed_lst); LIST_INIT(&pu->pu_ios); LIST_INIT(&pu->pu_ios_rmlist); LIST_INIT(&pu->pu_ccmagazin); TAILQ_INIT(&pu->pu_sched); /* defaults for some user-settable translation functions */ pu->pu_cmap = NULL; /* identity translation */ pu->pu_pathbuild = puffs_stdpath_buildpath; pu->pu_pathfree = puffs_stdpath_freepath; pu->pu_pathcmp = puffs_stdpath_cmppath; pu->pu_pathtransform = NULL; pu->pu_namemod = NULL; pu->pu_errnotify = puffs_defaulterror; PU_SETSTATE(pu, PUFFS_STATE_BEFOREMOUNT); global_pu = pu; return pu; failfree: /* can't unmount() from here for obvious reasons */ sverrno = errno; free(pu); errno = sverrno; return NULL; } void puffs_cancel(struct puffs_usermount *pu, int error) { assert(puffs_getstate(pu) < PUFFS_STATE_RUNNING); free(pu); } /*ARGSUSED1*/ int puffs_exit(struct puffs_usermount *pu, int force) { struct puffs_node *pn; lpuffs_debug("puffs_exit\n"); while ((pn = LIST_FIRST(&pu->pu_pnodelst)) != NULL) puffs_pn_put(pn); while ((pn = LIST_FIRST(&pu->pu_pnode_removed_lst)) != NULL) puffs_pn_put(pn); puffs__cc_exit(pu); if (pu->pu_state & PU_HASKQ) close(pu->pu_kq); free(pu); return 0; /* always succesful for now, WILL CHANGE */ } /* * Actual mainloop. This is called from a context which can block. * It is called either from puffs_mainloop (indirectly, via * puffs_cc_continue() or from puffs_cc_yield()). */ void puffs__theloop(struct puffs_cc *pcc) { struct puffs_usermount *pu = pcc->pcc_pu; int error, ind; while (!unmountdone || !exitsignaled) { endpoint_t src; /* * Schedule existing requests. */ while ((pcc = TAILQ_FIRST(&pu->pu_sched)) != NULL) { lpuffs_debug("scheduling existing tasks\n"); TAILQ_REMOVE(&pu->pu_sched, pcc, pcc_schedent); puffs__goto(pcc); } if (pu->pu_ml_lfn) { lpuffs_debug("Calling user mainloop handler\n"); pu->pu_ml_lfn(pu); } /* Wait for request message. */ get_work(&fs_m_in); src = fs_m_in.m_source; error = OK; caller_uid = INVAL_UID; /* To trap errors */ caller_gid = INVAL_GID; req_nr = fs_m_in.m_type; if (req_nr < VFS_BASE) { fs_m_in.m_type += VFS_BASE; req_nr = fs_m_in.m_type; } ind = req_nr - VFS_BASE; if (ind < 0 || ind >= NREQS) { error = EINVAL; } else { error = (*fs_call_vec[ind])(); } fs_m_out.m_type = error; if (IS_VFS_FS_TRANSID(last_request_transid)) { /* If a transaction ID was set, reset it */ fs_m_out.m_type = TRNS_ADD_ID(fs_m_out.m_type, last_request_transid); } reply(src, &fs_m_out); } if (puffs__cc_restoremain(pu) == -1) warn("cannot restore main context. impending doom"); /* May get here, if puffs_fakecc is set to 1. Currently librefuse sets it. * Now we just return to the caller. */ } int puffs_mainloop(struct puffs_usermount *pu) { struct puffs_cc *pcc; int sverrno; assert(puffs_getstate(pu) >= PUFFS_STATE_RUNNING); pu->pu_state |= PU_HASKQ | PU_INLOOP; /* * Create alternate execution context and jump to it. Note * that we come "out" of savemain twice. Where we come out * of it depends on the architecture. If the return address is * stored on the stack, we jump out from puffs_cc_continue(), * for a register return address from puffs__cc_savemain(). * PU_MAINRESTORE makes sure we DTRT in both cases. */ if (puffs__cc_create(pu, puffs__theloop, &pcc) == -1) { goto out; } if (puffs__cc_savemain(pu) == -1) { goto out; } if ((pu->pu_state & PU_MAINRESTORE) == 0) puffs_cc_continue(pcc); errno = 0; out: /* store the real error for a while */ sverrno = errno; errno = sverrno; if (errno) return -1; else return 0; } /*===========================================================================* * sef_local_startup * *===========================================================================*/ static void sef_local_startup() { /* Register init callbacks. */ sef_setcb_init_fresh(sef_cb_init_fresh); sef_setcb_init_restart(sef_cb_init_fail); /* No live update support for now. */ /* Register signal callbacks. */ sef_setcb_signal_handler(sef_cb_signal_handler); /* Let SEF perform startup. */ sef_startup(); } /*===========================================================================* * sef_cb_init_fresh * *===========================================================================*/ static int sef_cb_init_fresh(int type, sef_init_info_t *info) { /* Initialize the Minix file server. */ SELF_E = getprocnr(); return(OK); } /*===========================================================================* * sef_cb_signal_handler * *===========================================================================*/ static void sef_cb_signal_handler(int signo) { /* Only check for termination signal, ignore anything else. */ if (signo != SIGTERM) return; exitsignaled = 1; fs_sync(); /* If unmounting has already been performed, exit immediately. * We might not get another message. */ if (unmountdone) { if (puffs__cc_restoremain(global_pu) == -1) warn("cannot restore main context. impending doom"); /* May happen if puffs_fakecc is set to 1. Currently librefuse sets it. * There is a chance, that main loop hangs in receive() and we will * never get any new message, so we have to exit() here. */ exit(0); } } /*===========================================================================* * get_work * *===========================================================================*/ static void get_work(m_in) message *m_in; /* pointer to message */ { int r, srcok = 0; endpoint_t src; do { if ((r = sef_receive(ANY, m_in)) != OK) /* wait for message */ panic("sef_receive failed: %d", r); src = m_in->m_source; if(src == VFS_PROC_NR) { if(unmountdone) lpuffs_debug("libpuffs: unmounted: unexpected message from FS\n"); else srcok = 1; /* Normal FS request. */ } else lpuffs_debug("libpuffs: unexpected source %d\n", src); } while(!srcok); assert((src == VFS_PROC_NR && !unmountdone)); last_request_transid = TRNS_GET_ID(fs_m_in.m_type); fs_m_in.m_type = TRNS_DEL_ID(fs_m_in.m_type); if (fs_m_in.m_type == 0) { assert(!IS_VFS_FS_TRANSID(last_request_transid)); fs_m_in.m_type = last_request_transid; /* Backwards compat. */ last_request_transid = 0; } else assert(IS_VFS_FS_TRANSID(last_request_transid)); } /*===========================================================================* * reply * *===========================================================================*/ static void reply( endpoint_t who, message *m_out /* report result */ ) { if (OK != send(who, m_out)) /* send the message */ lpuffs_debug("libpuffs(%d) was unable to send reply\n", SELF_E); last_request_transid = 0; }