lib/libpuffs/puffs.c
/*	$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 <sys/cdefs.h>
#if !defined(lint)
__RCSID("$NetBSD: puffs.c,v 1.92.4.4 2009/10/27 20:37:38 bouyer Exp $");
#endif /* !lint */

#include <sys/param.h>
#include <sys/mount.h>

#include <minix/endpoint.h>
#include <minix/vfsif.h>

#include <assert.h>
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <paths.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
#include <unistd.h>

#include "puffs.h"
#include "puffs_priv.h"

#ifdef PUFFS_WITH_THREADS
#include <pthread.h>
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<<stackshift);
	}

	pu->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;
}

