/*
 * Copyright (c) 1995-1996 by Sun Microsystems, Inc.
 * All rights reserved.
 */

#pragma ident	"@(#)dev_pcmem.c	1.28	96/10/10 SMI"

/*
 * the file is volmgt's interface to possible PCMCIA slots under Solaris
 *
 * this version talks to pcmciad directly through a named pipe (rather than
 *	pcmciad using SIGHUP to talk to vold)
 *
 * the name of the named pipe to use is gotten from the config file
 * "use pcmpipe ..." line
 */


#include	<sys/types.h>
#include	<sys/stat.h>
#include	<sys/mkdev.h>
#include	<sys/dkio.h>
#include	<sys/fdio.h>
#include	<sys/cdio.h>
#include	<sys/vtoc.h>

#include	<errno.h>
#include	<string.h>
#include	<dirent.h>
#include	<stdlib.h>
#include	<unistd.h>
#include	<ctype.h>
#include	<thread.h>
#include	<synch.h>
#include	<sys/signal.h>

#include	"vold.h"
#include	"util.h"



/*
 * thread stack size
 */
#define	PCMEM_STKSIZE		(32 * 1024)

/*
 * size of buf read from named pipe
 */
#define	PCMEM_PBUF_SZ		100

/*
 * this is the seperator between params sent down the pipe
 */
#define	PCMEM_PIPE_SEP		','

/*
 * path of the named pipe that PCMCIA uses
 */
#define	PCMEM_PIPE_PATH		"/tmp/.pcmcia/pcram"

/*
 * symname used from proutine that reads from pipe as symname for pcmem
 * cards
 *
 * must match symname on "use pcmem ..." line in config file
 */
#define	PCMEM_SYMNAME		"pcmem%d"

/*
 * path to devices directories
 */
#define	PCMEM_RDSK_PATH		"/dev/rdsk"
#define	PCMEM_DSK_PATH		"/dev/dsk"

#define	PCMEM_DEVICES_DIR	"/devices/pcmcia"

#define	PCMEM_NAMEPROTO_DEFD	"%sd0s%d"
#define	PCMEM_BASEPART		DEFAULT_PARTITION

#define	PCMEM_NAMEPROTO		"%ss%d"

#define	PCMEM_MODE_BUF_LEN	10

#define	PCMEM_PIPE_ACTION	"insert"

#define	PCMEM_OVERLAP_TIMEOUT_SECS	15


static bool_t	pcmem_use(char *, char *);
static bool_t	pcmem_error(struct ve_error *);
static int	pcmem_getfd(dev_t);
static void	pcmem_devmap(vol_t *, int, int);
static void	pcmem_close(char *);
static bool_t	pcmem_testpath(char *);
static int	pcmem_check(struct devs *);
static void	pcmem_eject(struct devs *);



static struct devsw pcmemdevsw = {
	pcmem_use,		/* d_use -- use a device */
	pcmem_error,		/* d_error -- handle an error */
	pcmem_getfd,		/* d_getfd -- return open fd for drive */
	NULL,			/* d_poll -- poll device */
	pcmem_devmap,		/* d_devmap --  map device */
	pcmem_close,		/* d_close -- end use of device */
	pcmem_eject,		/* d_eject -- eject media */
	NULL,			/* d_find */
	pcmem_check,		/* d_check -- volcheck device */
	PCMEM_MTYPE,		/* d_mtype -- media type */
	DRIVE_CLASS,		/* d_dtype -- drive type */
	(ulong_t)0,		/* d_flags -- default flags */
	(uid_t)0,		/* d_uid -- default user id */
	(gid_t)0,		/* d_gid -- default group id */
	(mode_t)0,		/* d_mode -- default file mode */
	pcmem_testpath		/* d_test -- test for a named pipe */
};


struct mem_priv {
	char		*mem_blockpath;	/* block device for pcmem */
	char		*mem_rawpath;	/* character device for pcmem */
	int		mem_tid;	/* thread id of watcher thread */
	int		mem_fd;		/* real file descriptor */
	mutex_t		mem_mutex;	/* to protect most of this struct */
	mutex_t		mem_inserted_mutex; /* to protect mem_inserted */
	bool_t		mem_inserted;	/* media is in the drive */
};


/*
 * the struct passed to pcmp_thread
 */
struct pcmp_priv {
	struct mem_priv	*pcmp_mem;
	char		*pcmp_symname;
};



/*
 * this routine is called by dso_load() right after this library is loaded,
 * i.e. a line of the form "use pcmem ..." has been seen
 *
 * put any library initializatio here
 */
bool_t
dev_init(void)
{
	extern void	dev_new(struct devsw *);
	static void	pipe_thread(void);


	/* register our routines */
	dev_new(&pcmemdevsw);

	/* ensure a thread exists to watch the named pipe */
	pipe_thread();

	return (TRUE);
}


/*
 * start up a thread that watches the named pipe
 */
static void
pipe_thread(void)
{
	static char		*pcmp_dirname(char *);
	static void		pcmp_thread(struct pcmp_priv *);
	void			*pcmp_thr_stk;
	struct stat		sb;
	struct mem_priv		*mem = NULL;
	char			*p = PCMEM_PIPE_PATH;
	char			*bn = NULL;
	struct pcmp_priv	*pcmp;



	debug(1, "pipe_thread: entering\n");

	/*
	 * we don't do an open for the named pipe, since pcmp_thread() does
	 * that.  Instead, we just stat the device and make sure it's
	 * there and is a reasonable type
	 */

	/*
	 * possible security hole here, make
	 * sure dirname is not a symlink, if
	 * it is remove it
	 */

	if ((bn = pcmp_dirname(p)) != NULL) {
		if (lstat(bn, &sb) < 0) {
			debug(5,
			    "pipe_thread: Stat failed %s, %m\n",
			    bn);
		} else {

			if (S_ISLNK(sb.st_mode)) {
				/* remove it */
				if (remove(bn) < 0) {
					debug(5,
					    "pipe_thd: Can't remove '%s'\n",
					    bn);
					goto dun;
				}
			}
		}
	}

	/* just take a path if they hand it to us. */
	if (stat(p, &sb) < 0) {

		if (errno != ENOENT) {
			debug(5,
			    "pipe_thread: stat failed for: %s; %m\n",
			    p);
			goto dun;
		}

		/*
		 * the pipe doesn't exist -- create it (and the directory
		 * it goes in if needed)
		 */
		if (bn != NULL) {

			if (makepath(bn, 0777) < 0) {
				debug(5,
				    "pipe_thread: makepath failed for: %s\n",
				    bn);
				goto dun;
			}
		}
		if (mknod((const char *)p, S_IFIFO|0666, NULL) < 0) {
			debug(5, "pipe_thread: mknod failed for: %s; %m\n",
			    p);
			goto dun;
		}
		if (stat(p, &sb) < 0) {
			debug(5, "pipe_thread: stat failed for: %s; %m\n",
			    p);
			goto dun;
		}
	} else {
		if (!S_ISFIFO(sb.st_mode)) {
			debug(5, "pipe_thread: not a FIFO: %s; %m\n", p);
			goto dun;
		}
	}

	/* create an empty pcmp-private data structure */
	mem = (struct mem_priv *)calloc(1, sizeof (struct mem_priv));
	mem->mem_fd = -1;

	/* stick some good stuff in the device hierarchy */
	mem->mem_rawpath = strdup(p);
	mem->mem_blockpath = mem->mem_rawpath;

	/* create the pcmp priv struct (so we only pass one param to thread) */
	pcmp = (struct pcmp_priv *)calloc(1, sizeof (struct pcmp_priv));
	pcmp->pcmp_mem = mem;
	pcmp->pcmp_symname = strdup(PCMEM_SYMNAME);

	pcmp_thr_stk = (void *)malloc(PCMEM_STKSIZE);
	if (thr_create(pcmp_thr_stk, PCMEM_STKSIZE,
	    (void *(*)(void *))pcmp_thread, (void *)pcmp, THR_BOUND,
	    (thread_t *)&mem->mem_tid) != 0) {
		warning(gettext("pcmp thread create failed; %m\n"));
		goto dun;
	}
#ifdef	DEBUG
	debug(6, "pipe_thread: pcmp_thread id %d created\n", mem->mem_tid);
#endif

dun:
	debug(5, "pipe_thread: returning\n");
}


static void
pcmp_thread(struct pcmp_priv *pcmp)
{
	static bool_t		parse_pipe_str(char *, char *, char *, int *);
	extern int		vold_running;
	extern cond_t		running_cv;
	extern mutex_t		running_mutex;
	struct mem_priv		*mem = pcmp->pcmp_mem;
	char			*symname = pcmp->pcmp_symname;
	char			pipe_buf[PCMEM_PBUF_SZ+1];
	char			dev_buf[PCMEM_PBUF_SZ+1];
	char			act_buf[PCMEM_PBUF_SZ+1];
	int			sym_num;
	char			sym_buf[PCMEM_PBUF_SZ+1];
	register char		*cp;
	int			ret_val;
	char			mode_buf[PCMEM_MODE_BUF_LEN];



	/* ensure that the main loop is ready */
	(void) mutex_lock(&running_mutex);
	while (vold_running == 0) {
		(void) cond_wait(&running_cv, &running_mutex);
	}
	(void) mutex_unlock(&running_mutex);

	debug(5, "pcmp_thread: entering for path \"%s\"\n",
	    mem->mem_rawpath);

open_again:
	/* ensure the pipe is open */
	if (mem->mem_fd >= 0) {
		(void) close(mem->mem_fd);
	}
	if ((mem->mem_fd = open(mem->mem_rawpath, O_RDONLY)) < 0) {
		warning(gettext("pcmem: can't open %s; %m\n"),
		    mem->mem_rawpath);
		goto errout;
	}

	/*CONSTCOND*/
	for (;;) {

		/*
		 * this blocks until pcmciad sends us a line
		 */

#ifdef	DEBUG
		debug(5, "pcmp_thread: reading from pipe\n");
#endif

		cp = pipe_buf;
		while ((ret_val = read(mem->mem_fd, (void *)cp, 1)) == 1) {
			if (*cp == '\n') {
				*cp = NULLC;
				break;
			}
			if (*cp == NULLC) {
				break;
			}
			cp++;
		}
		if (ret_val < 0) {
			debug(1, "pcmp_thread: read from \"%s\" failed; %m\n",
			    mem->mem_rawpath);
			goto open_again;
		}
		if (ret_val == 0) {
			debug(1, "pcmp_thread: EOF read from \"%s\"\n",
			    mem->mem_rawpath);
			goto open_again;
		}

		debug(9, "pcmp_thread: line read from pipe: \"%s\"\n",
		    pipe_buf);

		if (!parse_pipe_str(pipe_buf, act_buf, dev_buf, &sym_num)) {
			/* oh oh -- not in correct format! */
			continue;
		}

		if (strcmp(act_buf, PCMEM_PIPE_ACTION) == 0) {

			/* set up the symname to use */
			(void) sprintf(sym_buf, symname, sym_num);

			/* set up the mode */
			(void) sprintf(mode_buf, "0%o", DEFAULT_MODE);

			/* register this device */
			if (dev_use(PCMEM_MTYPE, DRIVE_CLASS, dev_buf, sym_buf,
			    DEFAULT_USER, DEFAULT_GROUP, mode_buf, FALSE,
			    FALSE) == FALSE) {
				/* may be already managed */
				debug(1,
	"pcmp_thread: warning: dev_use on \"%s\" failed (already managed?)\n",
				    dev_buf);
			}

		} else {
			warning(
			    gettext("pcmem: unknown message from pcmciad\n"));
		}
	}

	/*NOTREACHED*/

errout:
	/* this thread is exiting! */
	if (mem->mem_fd >= 0) {
		(void) close(mem->mem_fd);
		mem->mem_fd = -1;
	}
}


/*
 * return the directory part of a path (like dirname(1))
 *
 * assume we have a well formed path, i.e. no "/abc/" or "/"
 */
static char *
pcmp_dirname(char *path)
{
	char		*s;
	char		*res = NULL;
	register char	*cpi;
	register char	*cpo;


	/* find the last slash in the path */
	if ((s = strrchr(path, '/')) != NULL) {
		res = malloc((size_t)(s - path + 1));
		for (cpi = path, cpo = res; cpi < s; cpi++, cpo++) {
			*cpo = *cpi;
		}
		*cpo = NULLC;
	}

	return (res);
}



/*
 * called because a line of the form "use pcmem ... PATH_REGEX ..." was
 * found in the config file, and a path has been found that
 * matches PATH_REGEX
 *
 * return true if the path points at a pcmem device,
 * as it's understood by this code
 */
static bool_t
pcmem_testpath(char *p)
{
	struct stat		sb;
	int			fd = -1;
	char			*rp = NULL;
	struct dk_cinfo		dkc;
	int			res = FALSE;



	debug(7, "pcmem_testpath: scanning \"%s\"\n", p);

	/* stat it (if we can) */
	if (stat(p, &sb) < 0) {
		debug(5, "pcmem_testpath: stat of \"%s\"; %m\n", p);
		goto mem_dun;
	}

	/* see if device already being used */
	if (dev_getdp(sb.st_rdev)) {
		debug(5, "pcmem_testpath: \"%s\" already being managed\n", p);
		goto mem_dun;
	}

	/* make sure our path is a raw device */
	if ((rp = rawpath(p)) == NULL) {
		debug(5, "pcmem_testpath: can't rawpath(): \"%s\"\n", p);
		goto mem_dun;
	}

	/* try to open it */
	if ((fd = open(p, O_RDONLY|O_NDELAY)) < 0) {
		debug(5, "pcmem_testpath: open of \"%s\"; %m\n", rp);
		goto mem_dun;
	}

	/* check for memird octl handling */
	if (ioctl(fd, DKIOCINFO, &dkc) < 0) {
		debug(5, "pcmem_testpath: ioctl(DKIOCINFO) on \"%s\"; %m\n",
		    rp);
		goto mem_dun;
	}
	if (dkc.dki_ctype != DKC_PCMCIA_MEM) {
		debug(5, "pcmem_testpath: type != PCMEM on \"%s\"\n", rp);
		goto mem_dun;
	}

	/* we suceeded */
	res = TRUE;
	debug(5, "pcmem_testpath: we've got a pcmem \"%s\"\n", p);

mem_dun:
	/* clean up if needed */
	if (fd >= 0) {
		(void) close(fd);
	}
	if (rp) {
		free(rp);
	}
	return (res);
}


/*
 * called for one of two reasons:
 * (1) the pcmcia daemon has sent a message down the named pipe
 *	saying to use a particular device, or
 * (2) pcmem_testpath() has found a suitable pcmem pathname
 */
/*ARGSUSED*/
static bool_t
pcmem_use(char *path, char *symname)
{
	static void		pcmem_thread(struct devs *);
	void			*pcmem_thr_stk;
	struct stat		statbuf;
	char			full_path[MAXPATHLEN+1];
	char			*path_trunc = path; /* "path" gets truncated */
	char			namebuf[MAXPATHLEN+1]; /* name buffer */
	char			namebuf1[MAXPATHLEN+1];	/* 2nd name buffer */
	struct devs		*dp;
	struct mem_priv		*mem;
	char			*s;
	char			*p;
	vvnode_t		*bvn;
	vvnode_t		*rvn;
	char			path_save[MAXPATHLEN+1];
	bool_t			res = FALSE;
	struct dk_cinfo		dkc;
	int			fd;



	info(gettext("pcmem_use: %s\n"), path);

	/*
	 * we don't do an open for the pcmem because it returns ENODEV
	 * if there isn't a device there.  Instead, we just stat the
	 * device and make sure it's there and is a reasonable type
	 */

	/* just take a path if they hand it to us */
	if (stat(path, &statbuf) < 0) {
		/*
		 * We expect a path of the form:
		 * 	/dev/{rdsk, dsk}/c#t#
		 * We fill in the rest.
		 */
		(void) sprintf(full_path, PCMEM_NAMEPROTO_DEFD, path,
		    PCMEM_BASEPART);
		if (stat(full_path, &statbuf) < 0) {
			debug(1, "pcmem_use: stat of \"%s\"; %m\n", full_path);
			goto dun;
		}
		/* the device was found */
	} else {

		/*
		 * got a good path -- truncate the "slice" part of the name
		 *
		 * XXX: assume all PCMEM pathnames end in "sN"
		 */
		(void) strcpy(full_path, path);
		if ((s = strrchr(path, 's')) == 0) {
			/* the full path didn't have a "s" in it! */
			warning(gettext("pcmem: %s is an invalid path\n"),
			    path);
			goto dun;
		}
		/* XXX: should make sure a slice number follows */
		*s = NULLC;
#ifdef	DEBUG
		debug(1, "pcmem_use: path_trunc=\"%s\"\n", path_trunc);
#endif
	}

	/* check to see if this guy is already configured */
	if (dev_getdp(statbuf.st_rdev)) {
		debug(1, "pcmem_use: \"%s\" already being managed\n",
		    full_path);
		goto dun;
	}

	/* ensure it's a block or char spcl device */
	if (!S_ISCHR(statbuf.st_mode) && !S_ISBLK(statbuf.st_mode)) {
		warning(gettext(
		    "pcmem: %s not block or char device (mode 0x%x)\n"),
		    full_path, statbuf.st_mode);
		goto dun;
	}

	/* create an empty memory-private data structure */
	mem = (struct mem_priv *)calloc(1, sizeof (struct mem_priv));
	mem->mem_fd = -1;
	mem->mem_inserted = FALSE;

	/* save a copy of the pathname */
	(void) strcpy(path_save, full_path);

	/* stick some good stuff in the device hierarchy */
	if ((s = strstr(path_trunc, "rdsk")) != 0) {

		/* got a raw path (i.e. "rdsk" in it) */

		/* save a pointer to the raw vv-node */
		rvn = dev_dirpath(path_trunc);

		/* create the name for rawpath */
		(void) sprintf(namebuf, PCMEM_NAMEPROTO, path_trunc,
		    PCMEM_BASEPART);
		mem->mem_rawpath = strdup(namebuf);

		/* get the block path now from the raw one */

		/* skip past "rdsk/" */
		if ((p = strchr(s, '/')) != 0) {
			p++;
			(void) sprintf(namebuf, "%s/%s", PCMEM_DSK_PATH, p);
		} else {
			/* no slash after rdsk? */
			debug(1,
		"pcmem_use: using malformed pathname (no '/') \"%s\"\n",
			    path_trunc);
			/* what else can we do? */
			(void) strcpy(namebuf, path_trunc);
		}

		/* get the block vv-node */
		bvn = dev_dirpath(namebuf);

		/* create the name for blockpath */
		(void) sprintf(namebuf1, PCMEM_NAMEPROTO, namebuf,
		    PCMEM_BASEPART);
		mem->mem_blockpath = strdup(namebuf1);

	} else if (s = strstr(path_trunc, "dsk")) {

		/* he gave us the block path (i.e. "dsk" in it) */

		/* save pointer to block vv-node */
		bvn = dev_dirpath(path_trunc);

		/* create the name for blockpath */
		(void) sprintf(namebuf, PCMEM_NAMEPROTO, path_trunc,
		    PCMEM_BASEPART);
		mem->mem_blockpath = strdup(namebuf);

		/* get the chr patch now from the block one */

		/* skip past "dsk/" */
		if ((p = strchr(s, '/')) != 0) {
			p++;
			(void) sprintf(namebuf, "%s/%s", PCMEM_RDSK_PATH, p);
		} else {
			/* no slash after "dsk"? */
			debug(1,
		"pcmem_use: using malformed pathname (no '/') \"%s\"\n",
			    path);
			/* what else can we do? */
			(void) strcpy(namebuf, path);
		}

		/* save a pointer to the raw vv-node */
		rvn = dev_dirpath(namebuf);

		/* create the name for rawpath */
		(void) sprintf(namebuf1, PCMEM_NAMEPROTO, namebuf,
		    PCMEM_BASEPART);
		mem->mem_rawpath = strdup(namebuf1);

	} else {
		debug(1,
		    "pcmem_use: malformed pathname (no '[r]dsk') \"%s\"\n",
		    path);
		goto dun;
	}

	if ((dp = dev_makedp(&pcmemdevsw, mem->mem_rawpath)) == NULL) {
		debug(1, "pcmem_use: dev_makedp failed for \"%s\"\n",
		    mem->mem_rawpath);
		goto dun;
	}

	dp->dp_priv = (void *)mem;
	dp->dp_bvn = bvn;
	dp->dp_rvn = rvn;

	/*
	 * Get socket number information which is from
	 *	dki_unit in DKIOCINFO structure
	 */
	if ((fd = open(path_save, O_RDONLY)) < 0) {
		warning(gettext("pcmem: can't open %s; %m\n"),
		    path_save);
		goto dun;
	}

	if (ioctl(fd, DKIOCINFO, &dkc) < 0) {
		noise(gettext("pcmem: Failed DKIOCINFO ioctl on %s\n"),
			path_save);
		(void) close(fd);
		goto dun;
	}

	debug(6, "pcmem_use: Found socket number %d\n", dkc.dki_unit);
	(void) sprintf(namebuf, PCMEM_SYMNAME, dkc.dki_unit);
	dp->dp_symname = strdup(namebuf);
	(void) close(fd);

	/* create a thread to manage this device */
	(void) mutex_init(&mem->mem_mutex, USYNC_THREAD, 0);
	(void) mutex_init(&mem->mem_inserted_mutex, USYNC_THREAD, 0);
	pcmem_thr_stk = (void *)malloc(PCMEM_STKSIZE);
	if (thr_create(pcmem_thr_stk, PCMEM_STKSIZE,
	    (void *(*)(void *))pcmem_thread, (void *)dp, THR_BOUND,
	    (thread_t *)&mem->mem_tid) != 0) {
		warning(gettext("pcmem thread create failed; %m\n"));
		goto dun;
	}
#ifdef	DEBUG
	debug(6, "pcmem_use: pcmem_thread id %d created\n", mem->mem_tid);
#endif

	res = TRUE;
dun:
	return (res);
}


/*ARGSUSED*/
static void
pcmem_devmap(vol_t *v, int part, int off)
{
	struct devs	*dp;
	struct mem_priv	*mem;


	dp = dev_getdp(v->v_basedev);
	mem = (struct mem_priv *)dp->dp_priv;
	/*
	 * don't really need to lock mem here, since rawpath doesn't
	 * change much
	 */
	v->v_devmap[off].dm_path = strdup(mem->mem_rawpath);
	debug(1, "pcmem_devmap: v->v_devmap[%d].dm_path = \"%s\"\n",
	    off, v->v_devmap[off].dm_path);
}


static int
pcmem_getfd(dev_t dev)
{
	struct devs	*dp;
	struct mem_priv	*mem;
	int		fd;


	dp = dev_getdp(dev);
	ASSERT(dp != NULL);
	mem = (struct mem_priv *)dp->dp_priv;
	ASSERT(mem->mem_fd != -1);
	(void) mutex_lock(&mem->mem_mutex);
	fd = mem->mem_fd;
	(void) mutex_unlock(&mem->mem_mutex);
	debug(1, "pcmem_devmap: mem->mem_fd=%d\n", fd);
	return (fd);
}


/*ARGSUSED*/
static bool_t
pcmem_error(struct ve_error *vie)
{
	debug(1, "pcmem_error\n");
	return (TRUE);
}


/*
 * State that must be cleaned up:
 *	name in the name space
 *	the "dp"
 *	any pointers to the media
 *	eject any existing media
 *	the priv structure
 */
/*
 * XXX: a bug still exists here.  we have a thread polling on this
 * XXX: device in the kernel, we need to get rid of this also.
 * XXX: since we're going to move the waiter thread up to the
 * XXX: user level, it'll be easier to kill off as part of the
 * XXX: cleanup of the device private data.
 */

static void
pcmem_close(char *path)
{
	extern bool_t		dev_present(struct devs *);
	char			namebuf[MAXPATHLEN+1];
	struct	stat		sb;
	struct devs		*dp;
	struct mem_priv		*mem;



	debug(1, "pcmem_close(): entering for \"%s\"\n", path);

	if (stat(path, &sb) < 0) {
		(void) sprintf(namebuf, PCMEM_NAMEPROTO, path,
		    PCMEM_BASEPART);
		if (stat(namebuf, &sb) < 0) {
			warning(gettext("pcmem_close: %s; %m\n"), namebuf);
			return;
		}
	}

	if ((dp = dev_getdp(sb.st_rdev)) == NULL) {
		debug(1, "pcmem_close: \"%s\" not being managed\n", path);
		return;
	}

	/* get our private data */
	mem = (struct mem_priv *)dp->dp_priv;

	/* take care of the listner thread */
	(void) mutex_lock(&mem->mem_mutex);
	(void) thr_kill(mem->mem_tid, SIGUSR1);
	/* apparently we have to kick it out of the cond_wait */
	(void) mutex_unlock(&mem->mem_mutex);
	(void) thr_join(mem->mem_tid, 0, 0);
	debug(1, "pcmem thread (id %d) reaped\n", mem->mem_tid);


	/*
	 * XXX: NOTE: we still have the pipe thread running
	 */

	/*
	 * there is no longer a listener thread, so the mutex
	 * no longer needs to be acquired
	 */

	/* if there is a volume inserted in this device ... */
	if (dev_present(dp)) {
		/*
		 * clean up the name space and the device maps
		 * to remove references to any volume that might
		 * be in the device right now
		 *
		 * this crap with the flags is to keep the
		 * "poll" from being relaunched by this function
		 *
		 * yes, its a hack and there should be a better way.
		 */
		if (dp->dp_dsw->d_flags & D_POLL) {
			dp->dp_dsw->d_flags &= ~D_POLL;
			dev_eject(dp->dp_vol, TRUE);
			dp->dp_dsw->d_flags |= D_POLL;
		} else {
			dev_eject(dp->dp_vol, TRUE);
		}
		(void) ioctl(mem->mem_fd, DKIOCEJECT, 0);
	}

	/* clean up the names in the name space */
	node_unlink(dp->dp_bvn);
	node_unlink(dp->dp_rvn);

	/* close the file descriptor we're holding open */
	(void) close(mem->mem_fd);

	/* free the private data we've allocated */
	if (mem->mem_blockpath) {
		free(mem->mem_blockpath);
	}
	if (mem->mem_rawpath) {
		free(mem->mem_rawpath);
	}
	free(mem);

	/* free the dp, so no one points at us anymore */
	dev_freedp(dp);
}


static void
pcmem_eject(struct devs *dp)
{
	struct mem_priv	*mem = (struct mem_priv *)dp->dp_priv;


	debug(10, "pcmem_eject: clearing inserted flag\n");
	(void) mutex_lock(&mem->mem_inserted_mutex);
	mem->mem_inserted = FALSE;
	(void) mutex_unlock(&mem->mem_inserted_mutex);
}


/*
 * check for a PCMCIA memory card
 *
 * return:
 *	0 if we didn't find any media
 *	1 if we already knew media was there
 *	2 if we found media and generated an event
 *
 * NOTE: this routine hogs the mem mutex for it's whole existence,
 * so be sure not to call any routine that may need it (with a little
 * work this could hog the mutex a lot less).
 */
static int
pcmem_check(struct devs *dp)
{
	extern void		vol_event(struct vioc_event *);
	static bool_t		reopen_pcmem(struct mem_priv *);
	struct mem_priv		*mem = (struct mem_priv *)dp->dp_priv;
	struct vioc_event	vie;
	extern int		vold_running;
	extern cond_t 		running_cv;
	extern mutex_t		running_mutex;
	int			rval = 0;
	int			ret_val = 0; /* default return value is 0 */



#ifdef	DEBUG
	debug(1, "pcmem_check: entering for \"%s\"\n",
	    mem->mem_rawpath ? mem->mem_rawpath : "<null ptr>");
#endif

	/* ensure the vold main loop is running */
	(void) mutex_lock(&running_mutex);
	while (vold_running == 0) {
		(void) cond_wait(&running_cv, &running_mutex);
	}
	(void) mutex_unlock(&running_mutex);

	/* if we know the media is there there's no need to check again */
	(void) mutex_lock(&mem->mem_inserted_mutex);
	if (mem->mem_inserted) {
		(void) mutex_unlock(&mem->mem_inserted_mutex);
		debug(9, "pcmem_check: already inserted\n");
		ret_val = 1;
		goto out;
	}
	(void) mutex_unlock(&mem->mem_inserted_mutex);

	/* ensure media is open */
	(void) mutex_lock(&mem->mem_mutex);
	if (mem->mem_fd < 0) {
		dp->dp_writeprot = reopen_pcmem(mem);
		if (mem->mem_fd < 0) {
			/* an error [re]opening */
			(void) mutex_unlock(&mem->mem_mutex);
			goto out;
		}
	}

	/* try to see if media is present */
	if (ioctl(mem->mem_fd, FDGETCHANGE, &rval) < 0) {
		(void) mutex_unlock(&mem->mem_mutex);
		debug(1, "pcmem_check: ioctl(DKIOCSTATE) failed; %m\n");
		goto out;
	}

	(void) mutex_unlock(&mem->mem_mutex);

#ifdef	DEBUG
	debug(5, "pcmem_check: FDGETCHANGE return value: %#x\n", rval);
#endif

	if ((rval & FDGC_CURRENT) == 0) {
		/*
		 * A memory card is IN the drive
		 * and we didn't know therewas  anything there ...
		 */
		ret_val = 2;		/* we generated an event */
		(void) mutex_lock(&mem->mem_inserted_mutex);
		mem->mem_inserted = TRUE;
		(void) mutex_unlock(&mem->mem_inserted_mutex);
		(void) memset(&vie, 0, sizeof (struct vioc_event));
		vie.vie_type = VIE_INSERT;
		vie.vie_insert.viei_dev = dp->dp_dev;
		vol_event(&vie);
		dp->dp_writeprot = (rval & FDGC_CURWPROT) ? TRUE : FALSE;
		debug(5, "pcmem_check: generated INSERT event\n");
	}

out:

#ifdef	DEBUG
	debug(1, "pcmem_check: returning %d\n", ret_val);
#endif

	return (ret_val);
}


static void
pcmem_thread(struct devs *dp)
{
	static bool_t		reopen_pcmem(struct mem_priv *);
	extern void		vol_event(struct vioc_event *);
	extern bool_t		dev_present(struct devs *);
	static void		wait_til_signalled(struct devs *);
#ifdef	DEBUG
	static char		*state_to_str(enum dkio_state);
#endif
	extern int		vold_running;
	extern cond_t		running_cv;
	extern mutex_t		running_mutex;
	struct mem_priv		*mem = (struct mem_priv *)dp->dp_priv;
	struct dk_cinfo		dkc;
	enum dkio_state		mem_state;
	struct vioc_event	vie;
	struct fd_drive		fdchar;
	dev_t			dev;



	/* ensure that the main loop is ready */
	(void) mutex_lock(&running_mutex);
	while (vold_running == 0) {
		(void) cond_wait(&running_cv, &running_mutex);
	}
	(void) mutex_unlock(&running_mutex);

	/* ensure the memory card is open */
	(void) mutex_lock(&mem->mem_mutex);
	dp->dp_writeprot = reopen_pcmem(mem);
	if (mem->mem_fd < 0) {
		(void) mutex_unlock(&mem->mem_mutex);
		goto errout;
	}

	/* check to make sure this is a PCMCIA memory card */
	if (ioctl(mem->mem_fd, DKIOCINFO, &dkc) < 0) {
		noise(gettext("pcmem: Failed DKIOCINFO ioctl on %s; %m\n"),
		    mem->mem_rawpath);
		(void) mutex_unlock(&mem->mem_mutex);
		goto errout;
	}
	(void) mutex_unlock(&mem->mem_mutex);

	if (dkc.dki_ctype != DKC_PCMCIA_MEM) {
		noise(gettext("pcmem: %s is not a PCMCIA memory card\n"),
		    mem->mem_rawpath);
		goto errout;
	}

	/* see how device ejects */
	(void) mutex_lock(&mem->mem_mutex);
	if (ioctl(mem->mem_fd, FDGETDRIVECHAR, &fdchar) < 0) {
		(void) mutex_unlock(&mem->mem_mutex);
		debug(1, "pcmem_thread: FDGETDRIVECHAR; %m\n");
	} else {
		(void) mutex_unlock(&mem->mem_mutex);
		if (fdchar.fdd_ejectable == 0) {
			debug(1, "pcmem_thread: \"%s\" manually ejectable\n",
			    mem->mem_rawpath);
			dp->dp_dsw->d_flags |= D_MEJECTABLE;
		}
	}

	mem_state = DKIO_NONE;

	/*CONSTCOND*/
	while (1) {

		/*
		 * this ioctl blocks until state changes
		 */
#ifdef	DEBUG
		debug(3, "pcmem_thread: ioctl(DKIOCSTATE, \"%s\") on \"%s\"\n",
		    state_to_str(mem_state), mem->mem_rawpath);
#else
		debug(3, "pcmem_thread: ioctl(DKIOCSTATE) on \"%s\"\n",
		    mem->mem_rawpath);
#endif

		/* normally we are blocked here waiting for state to change */
		if (ioctl(mem->mem_fd, DKIOCSTATE, &mem_state) < 0) {
			debug(1, "pcmem_thread: DKIOCSTATE; %m\n");
			if (errno == ENOTTY) {
				break;
			}
			(void) sleep(1);		/* forever ?? */
			continue;
		}

#ifdef  DEBUG
		debug(5, "pcmem_thread: new state = \"%s\"\n",
		    state_to_str(mem_state));
#endif

		if (mem_state == DKIO_NONE) {
			continue;	/* steady state -- ignore */
		}

		/* prepare to create an event */
		(void) memset(&vie, 0, sizeof (struct vioc_event));

		(void) mutex_lock(&mem->mem_inserted_mutex);

		if ((mem_state == DKIO_INSERTED) && !mem->mem_inserted) {

			/*
			 * We have media in the drive -- generate
			 *  an "insert" event
			 */
			mem->mem_inserted = TRUE;
			(void) mutex_unlock(&mem->mem_inserted_mutex);

			(void) mutex_lock(&mem->mem_mutex);
			dp->dp_writeprot = reopen_pcmem(mem);
			if (mem->mem_fd < 0) {
				/*
				 * [re]open failed -- could be because
				 * card was removed *immediately* after
				 * insertion
				 */
				(void) mutex_unlock(&mem->mem_mutex);
				(void) mutex_lock(&mem->mem_inserted_mutex);
				mem->mem_inserted = TRUE;
				(void) mutex_unlock(&mem->mem_inserted_mutex);
				break;
			}
			(void) mutex_unlock(&mem->mem_mutex);

			/* generate an insert event */
			dev = dp->dp_dev;
			debug(2,
			"pcmem_thread: generating INSERT event for (%d,%d)\n",
			    major(dev), minor(dev));
			vie.vie_type = VIE_INSERT;
			vie.vie_insert.viei_dev = dev;
			vol_event(&vie);

		} else if (mem_state == DKIO_EJECTED) {

			/*
			 * We have NO media in the drive (it's just
			 *  been ejected), so generate an "eject" event
			 * if we already know about the ejection,
			 *  then just continue on our happy loop
			 */
#ifdef	DEBUG
			debug(1,
	"pcmem_thread: handling EJECT state: dp=%#x, dp->dp_vol = %#x\n",
			    (char *)dp, (char *)(dp->dp_vol));
#endif

			/* keep track of removal */
			mem->mem_inserted = FALSE;
			(void) mutex_unlock(&mem->mem_inserted_mutex);

			if (dev_present(dp)) {
				/* generate an eject event */
				dev = dp->dp_vol->v_devmap[0].dm_voldev;
				debug(2,
			"pcmem_thread: generating EJECT event for (%d,%d)\n",
				    major(dev), minor(dev));
				vie.vie_type = VIE_EJECT;
				vie.vie_eject.viej_force = TRUE;
				vie.vie_eject.viej_unit = minor(dev);
				vol_event(&vie);

				/* wait til ejection is done */
				wait_til_signalled(dp);

				/* check for all okay */
				if (dev_present(dp)) {
					/* timeout or other problem */
					debug(1,
				"pcmem_thread: EJECT didn't complete\n");
					continue;	/* try again */
				}

			} else {
				debug(2,
				"pcmem_thread: EJECT but vol already gone\n");
			}
		}

	}

errout:
	/* this thread is exiting! */
	debug(10, "pcmem_thread: thread is exiting!\n");
	(void) mutex_lock(&mem->mem_mutex);
	if (mem->mem_fd >= 0) {
		(void) close(mem->mem_fd);
		mem->mem_fd = -1;
	}
	(void) mutex_unlock(&mem->mem_mutex);

	/* clean up the names in the name space */
	node_unlink(dp->dp_bvn);
	node_unlink(dp->dp_rvn);

	/* free the dp, so no one points at us anymore */
	dev_freedp(dp);

	/* XXX: should we free "mem" ? */
}


/*
 * wait until signaled (or timeout occurs) that the dev is no longer
 * prsent
 */
static void
wait_til_signalled(struct devs *dp)
{
	extern int		cond_timedwait(cond_t *, mutex_t *,
					timestruc_t *);
	extern bool_t		dev_present(struct devs *);
	timestruc_t		ts;
	struct timeval		tv;
	int			ret_val;


	(void) mutex_lock(&dp->dp_lock->dp_vol_vg_mutex);

	/* if dev is already done then we're already done */
	if (!dev_present(dp)) {
		(void) mutex_unlock(&dp->dp_lock->dp_vol_vg_mutex);
		return;
	}

	/* get the current time (for timed wait) */
	if (gettimeofday(&tv, NULL) < 0) {
		warning(gettext("pcmem: can't get time of day; %m\n"));
		(void) cond_wait(&dp->dp_lock->dp_vol_vg_cv,
		    &dp->dp_lock->dp_vol_vg_mutex);
		(void) mutex_unlock(&dp->dp_lock->dp_vol_vg_mutex);
		debug(10, "wait_til_signalled: cond_wait succeeded\n");
		return;
	}

	/* set up time delay */
	(void) memset(&ts, 0, sizeof (timestruc_t));
	ts.tv_sec = tv.tv_sec + PCMEM_OVERLAP_TIMEOUT_SECS;

	debug(10,
	    "wait_til_signalled: waiting for ejection (%d sec timeout)\n",
	    PCMEM_OVERLAP_TIMEOUT_SECS);

	if ((ret_val = cond_timedwait(&dp->dp_lock->dp_vol_vg_cv,
	    &dp->dp_lock->dp_vol_vg_mutex, &ts)) != 0) {
		(void) mutex_unlock(&dp->dp_lock->dp_vol_vg_mutex);
		debug(10, "wait_til_signalled: cond_timedwait error: %d\n",
		    ret_val);
		return;
	}

	(void) mutex_unlock(&dp->dp_lock->dp_vol_vg_mutex);

	debug(10, "wait_til_signalled: cond_timedwait succeeded\n");
}


/*
 * [re]open the memory card, returning whether or not it's write protected
 *
 * NOTE: should normally be called with the memory structure locked
 */
static bool_t
reopen_pcmem(struct mem_priv *mem)
{
	bool_t		rdonly = FALSE;
	bool_t		try_again = TRUE;


	/*
	 * when a pcmem card is openend O_NDELAY and no memory card is there,
	 * but then a read-only card is inserted, this is needed to
	 * reopen the devicd
	 */

#ifdef	DEBUG
	debug(10, "reopen_pcmem: entering (fd = %d)\n", mem->mem_fd);
#endif

	/* ensure it's closed to start with */
	if (mem->mem_fd >= 0) {
		(void) close(mem->mem_fd);
	}

again:
	/* try a blocking open, in read-write mode */
	if ((mem->mem_fd = open(mem->mem_rawpath,
	    O_RDWR|O_NDELAY|O_EXCL)) < 0) {
		/* try read-only mode */
		if (errno == EROFS) {
			mem->mem_fd = open(mem->mem_rawpath,
			    O_RDONLY|O_NDELAY|O_EXCL);
			rdonly = TRUE;
		}
	}

	if (mem->mem_fd < 0) {
		warning(gettext("pcmem: open error on %s; %m\n"),
		    mem->mem_rawpath);
		/* if we get EAGAIN then try again -- once */
		if (errno == EAGAIN) {
			if (try_again) {
				(void) sleep(1);
				try_again = FALSE;
				goto again;
			}
		}
	} else {
		(void) fcntl(mem->mem_fd, F_SETFD, 1);	/* close-on-exec */
	}

#ifdef	DEBUG
	debug(10, "reopen_pcmem: fd=%d, rdonly=%s\n", mem->mem_fd,
	    rdonly ? "TRUE" : "FALSE");
#endif

	return (rdonly);
}


#ifdef  DEBUG

static char *
state_to_str(enum dkio_state st)
{
	static char		state_buf[30];



	switch (st) {
	case DKIO_NONE:
		(void) sprintf(state_buf, "DKIO_NONE");
		break;
	case DKIO_INSERTED:
		(void) sprintf(state_buf, "DKIO_INSERTED");
		break;
	case DKIO_EJECTED:
		(void) sprintf(state_buf, "DKIO_EJECTED");
		break;
	default:
		(void) sprintf(state_buf, "?unknown? (%d)", (int)st);
		break;
	}

	return (state_buf);
}

#endif  /* DEBUG */



/*
 * parse the line read from the pipe, in the format:
 *		"ACTION, PATH, SOCKET_NUM"
 * e.g.:
 *		"insert, /dev/rdsk/c1t0d0s2, 0"
 */
static bool_t
parse_pipe_str(char *pipe_buf, char *act_buf, char *path_buf,
    int *socket_num_ptr)
{
	register char	*cpi = pipe_buf;
	register char	*cpo;
	bool_t		res = FALSE;


	/* ensure there's a seperator */
	if (strchr(cpi, PCMEM_PIPE_SEP) == NULL) {
		debug(1,
	"parse_pipe_str: malformed pipe str \"%s\" (no '%c' seperator)\n",
		    cpi, PCMEM_PIPE_SEP);
		goto dun;
	}

	/* copy up to the seperator into the action buf */
	for (cpo = act_buf; *cpi != NULLC; cpi++, cpo++) {
		if (*cpi == PCMEM_PIPE_SEP) {
			*cpo = NULLC;
			cpi++;			/* skip the seperator */
			break;
		}
		*cpo = *cpi;
	}

	/* skip white space */
	while (*cpi != NULLC) {
		if (!isspace(*cpi)) {
			break;
		}
		cpi++;
	}
	if (*cpi == NULLC) {
		debug(1,
		"parse_pipe_str: malformed pipe str \"%s\" (premature EOF)\n",
		    pipe_buf);
		goto dun;
	}

	/* ensure there's a seperator */
	if (strchr(cpi, PCMEM_PIPE_SEP) == NULL) {
		debug(1,
	"parse_pipe_str: malformed pipe str \"%s\" (no '%c' seperator)\n",
		    cpi, PCMEM_PIPE_SEP);
		goto dun;
	}

	/* copy up to the seperator into the action buf */
	for (cpo = path_buf; *cpi != NULLC; cpi++, cpo++) {
		if (*cpi == PCMEM_PIPE_SEP) {
			*cpo = NULLC;
			cpi++;			/* skip the seperator */
			break;
		}
		*cpo = *cpi;
	}

	/* skip white space */
	while (*cpi != NULLC) {
		if (!isspace(*cpi)) {
			break;
		}
		cpi++;
	}
	if (*cpi == NULLC) {
		debug(1,
		"parse_pipe_str: malformed pipe str \"%s\" (premature EOF)\n",
		    pipe_buf);
		goto dun;
	}

	/* get the last param: socket number */
	if ((*socket_num_ptr = atoi(cpi)) < 0L) {
		debug(1,
		"parse_pipe_str: malformed pipe str \"%s\" (no socket num?)\n",
		    pipe_buf);
		goto dun;
	}

	/* we have the info! */
	res = TRUE;

dun:
#ifdef	DEBUG
	if (res) {
		debug(1, "parse_pipe_str: returning (\"%s\", \"%s\", %d)\n",
		    act_buf, path_buf, *socket_num_ptr);
	}
#endif
	return (res);
}
