[BACK]Return to aucat.c CVS log [TXT][DIR] Up to [local] / src / usr.bin / aucat

File: [local] / src / usr.bin / aucat / aucat.c (download)

Revision 1.115, Thu May 26 07:18:40 2011 UTC (13 years ago) by ratchov
Branch: MAIN
Changes since 1.114: +9 -4 lines

add a new -w flag to control whether master volume is automatically
adjusted when new streams are connected and disconnected. Disabling
automatic volume adjustment makes sense if all streams are recorded
with properly lowered volumes.

/*	$OpenBSD: aucat.c,v 1.115 2011/05/26 07:18:40 ratchov Exp $	*/
/*
 * Copyright (c) 2008 Alexandre Ratchov <alex@caoua.org>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */
#include <sys/param.h>
#include <sys/queue.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/resource.h>

#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <pwd.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include "abuf.h"
#include "amsg.h"
#include "aparams.h"
#include "aproc.h"
#include "conf.h"
#include "dev.h"
#include "listen.h"
#include "midi.h"
#include "opt.h"
#include "wav.h"
#ifdef DEBUG
#include "dbg.h"
#endif

/*
 * unprivileged user name
 */
#define SNDIO_USER	"_sndio"

/*
 * priority when run as root
 */
#define SNDIO_PRIO	(-20)

#define PROG_AUCAT	"aucat"
#define PROG_MIDICAT	"midicat"

/*
 * sample rate if no ``-r'' is used
 */
#ifndef DEFAULT_RATE
#define DEFAULT_RATE	44100
#endif

#ifdef DEBUG
volatile sig_atomic_t debug_level = 0;
#endif
volatile sig_atomic_t quit_flag = 0;

/*
 * SIGINT handler, it raises the quit flag. If the flag is already set,
 * that means that the last SIGINT was not handled, because the process
 * is blocked somewhere, so exit.
 */
void
sigint(int s)
{
	if (quit_flag)
		_exit(1);
	quit_flag = 1;
}

#ifdef DEBUG
/*
 * Increase debug level on SIGUSR1.
 */
void
sigusr1(int s)
{
	if (debug_level < 4)
		debug_level++;
}

/*
 * Decrease debug level on SIGUSR2.
 */
void
sigusr2(int s)
{
	if (debug_level > 0)
		debug_level--;
}
#endif

void
opt_ch(struct aparams *par)
{
	char *next, *end;
	long cmin, cmax;

	errno = 0;
	cmin = strtol(optarg, &next, 10);
	if (next == optarg || *next != ':')
		goto failed;
	cmax = strtol(++next, &end, 10);
	if (end == next || *end != '\0')
		goto failed;
	if (cmin < 0 || cmax < cmin || cmax > NCHAN_MAX)
		goto failed;
	par->cmin = cmin;
	par->cmax = cmax;
	return;
failed:
	errx(1, "%s: bad channel range", optarg);
}

void
opt_enc(struct aparams *par)
{
	int len;

	len = aparams_strtoenc(par, optarg);
	if (len == 0 || optarg[len] != '\0')
		errx(1, "%s: bad encoding", optarg);
}

int
opt_hdr(void)
{
	if (strcmp("auto", optarg) == 0)
		return HDR_AUTO;
	if (strcmp("raw", optarg) == 0)
		return HDR_RAW;
	if (strcmp("wav", optarg) == 0)
		return HDR_WAV;
	errx(1, "%s: bad header specification", optarg);
}

int
opt_mmc(void)
{
	if (strcmp("off", optarg) == 0)
		return 0;
	if (strcmp("slave", optarg) == 0)
		return 1;
	errx(1, "%s: bad MMC mode", optarg);
}

int
opt_onoff(void)
{
	if (strcmp("off", optarg) == 0)
		return 0;
	if (strcmp("on", optarg) == 0)
		return 1;
	errx(1, "%s: bad join/expand setting", optarg);
}

int
opt_xrun(void)
{
	if (strcmp("ignore", optarg) == 0)
		return XRUN_IGNORE;
	if (strcmp("sync", optarg) == 0)
		return XRUN_SYNC;
	if (strcmp("error", optarg) == 0)
		return XRUN_ERROR;
	errx(1, "%s: bad underrun/overrun policy", optarg);
}

unsigned
opt_mode(void)
{
	unsigned mode = 0;
	char *p = optarg;
	size_t len;

	for (p = optarg; *p != '\0'; p++) {
		len = strcspn(p, ",");
		if (strncmp("play", p, len) == 0) {
			mode |= MODE_PLAY;
		} else if (strncmp("rec", p, len) == 0) {
			mode |= MODE_REC;
		} else if (strncmp("mon", p, len) == 0) {
			mode |= MODE_MON;
		} else if (strncmp("duplex", p, len) == 0) {
			/* XXX: backward compat, remove this */
			mode |= MODE_REC | MODE_PLAY;
		} else 
			errx(1, "%s: bad mode", optarg);
		p += len;
		if (*p == '\0')
			break;
	}
	if (mode == 0)
		errx(1, "empty mode");
	return mode;
}

/*
 * stream configuration
 */
struct cfstr {
	SLIST_ENTRY(cfstr) entry;
	unsigned mode;			/* bitmap of MODE_XXX */
	struct aparams ipar;		/* input (read) parameters */
	struct aparams opar;		/* output (write) parameters */
	unsigned vol;			/* last requested volume */
	int hdr;			/* header format */
	int xrun;			/* overrun/underrun policy */
	int mmc;			/* MMC mode */
	int join;			/* join/expand enabled */
	char *path;			/* static path (no need to copy it) */
};

SLIST_HEAD(cfstrlist, cfstr);

/*
 * midi device (control stream)
 */
struct cfmid {
	SLIST_ENTRY(cfmid) entry;
	char *path;			/* static path (no need to copy it) */
};

SLIST_HEAD(cfmidlist, cfmid);

/*
 * audio device configuration
 */
struct cfdev {
	SLIST_ENTRY(cfdev) entry;
	struct cfstrlist ins;		/* files to play */
	struct cfstrlist outs;		/* files to record */
	struct cfstrlist opts;		/* subdevices to expose */
	struct cfmidlist mids;		/* midi ports to subscribe */
	struct aparams ipar;		/* input (read) parameters */
	struct aparams opar;		/* output (write) parameters */
	unsigned hold;			/* open immediately */
	unsigned autovol;		/* adjust volumes */
	unsigned bufsz;			/* par.bufsz for sio device */
	unsigned round;			/* par.round for sio device */
	unsigned mode;			/* bitmap of MODE_XXX */
	char *path;			/* static path (no need to copy it) */
};

SLIST_HEAD(cfdevlist, cfdev);

/*
 * local network addresse to listen on
 */
struct cfnet {
	SLIST_ENTRY(cfnet) entry;
	char *addr;
};

SLIST_HEAD(cfnetlist, cfnet);

void
cfdev_add(struct cfdevlist *list, struct cfdev *templ, char *path)
{
	struct cfdev *cd;

	cd = malloc(sizeof(struct cfdev));
	if (cd == NULL) {
		perror("malloc");
		abort();
	}
	*cd = *templ;
	cd->path = path;
	SLIST_INSERT_HEAD(list, cd, entry);
	SLIST_INIT(&templ->ins);
	SLIST_INIT(&templ->outs);
	SLIST_INIT(&templ->opts);
	SLIST_INIT(&templ->mids);
}

void
cfstr_add(struct cfstrlist *list, struct cfstr *templ, char *path)
{
	size_t len;
	struct cfstr *cs;
	unsigned hdr;

	if (templ->hdr == HDR_AUTO) {
		len = strlen(path);
		if (len >= 4 && strcasecmp(path + len - 4, ".wav") == 0)
			hdr = HDR_WAV;
		else
			hdr = HDR_RAW;
	} else
		hdr = templ->hdr;
	cs = malloc(sizeof(struct cfstr));
	if (cs == NULL) {
		perror("malloc");
		abort();
	}
	*cs = *templ;
	cs->path = path;
	cs->hdr = hdr;
	SLIST_INSERT_HEAD(list, cs, entry);
}

void
cfmid_add(struct cfmidlist *list, char *path)
{
	struct cfmid *cm;
	
	cm = malloc(sizeof(struct cfmid));
	if (cm == NULL) {
		perror("malloc");
		abort();
	}
	cm->path = path;
	SLIST_INSERT_HEAD(list, cm, entry);
}

void
cfnet_add(struct cfnetlist *list, char *addr)
{
	struct cfnet *cn;

	cn = malloc(sizeof(struct cfnet));
	if (cn == NULL) {
		perror("malloc");
		abort();
	}
	cn->addr = addr;
	SLIST_INSERT_HEAD(list, cn, entry);
}

void
setsig(void)
{
	struct sigaction sa;

	quit_flag = 0;
	sigfillset(&sa.sa_mask);
	sa.sa_flags = SA_RESTART;
	sa.sa_handler = sigint;
	if (sigaction(SIGINT, &sa, NULL) < 0)
		err(1, "sigaction(int) failed");
	if (sigaction(SIGTERM, &sa, NULL) < 0)
		err(1, "sigaction(term) failed");
	if (sigaction(SIGHUP, &sa, NULL) < 0)
		err(1, "sigaction(hup) failed");
#ifdef DEBUG
	sa.sa_handler = sigusr1;
	if (sigaction(SIGUSR1, &sa, NULL) < 0)
		err(1, "sigaction(usr1) failed");
	sa.sa_handler = sigusr2;
	if (sigaction(SIGUSR2, &sa, NULL) < 0)
		err(1, "sigaction(usr2) failed1n");
#endif
}

void
unsetsig(void)
{
	struct sigaction sa;

	sigfillset(&sa.sa_mask);
	sa.sa_flags = SA_RESTART;
	sa.sa_handler = SIG_DFL;
#ifdef DEBUG
	if (sigaction(SIGUSR2, &sa, NULL) < 0)
		err(1, "unsetsig(usr2): sigaction failed");
	if (sigaction(SIGUSR1, &sa, NULL) < 0)
		err(1, "unsetsig(usr1): sigaction failed");
#endif
	if (sigaction(SIGHUP, &sa, NULL) < 0)
		err(1, "unsetsig(hup): sigaction failed\n");
	if (sigaction(SIGTERM, &sa, NULL) < 0)
		err(1, "unsetsig(term): sigaction failed\n");
	if (sigaction(SIGINT, &sa, NULL) < 0)
		err(1, "unsetsig(int): sigaction failed\n");
}

void
getbasepath(char *base, size_t size)
{
	uid_t uid;
	struct stat sb;
	mode_t mask;

	uid = geteuid();
	if (uid == 0) {
		mask = 022;
		snprintf(base, PATH_MAX, "/tmp/aucat");
	} else {
		mask = 077;
		snprintf(base, PATH_MAX, "/tmp/aucat-%u", uid);
	}
	if (mkdir(base, 0777 & ~mask) < 0) {
		if (errno != EEXIST)
			err(1, "mkdir(\"%s\")", base);
	}
	if (stat(base, &sb) < 0)
		err(1, "stat(\"%s\")", base);
	if (sb.st_uid != uid || (sb.st_mode & mask) != 0)
		errx(1, "%s has wrong permissions", base);
}

void
privdrop(void)
{
	struct passwd *pw;
	struct stat sb;

	if ((pw = getpwnam(SNDIO_USER)) == NULL)
		errx(1, "unknown user %s", SNDIO_USER);
	if (stat(pw->pw_dir, &sb) < 0)
		err(1, "stat(\"%s\")", pw->pw_dir);
	if (sb.st_uid != 0 || (sb.st_mode & 022) != 0)
		errx(1, "%s has wrong permissions", pw->pw_dir);
	if (setpriority(PRIO_PROCESS, 0, SNDIO_PRIO) < 0)
		err(1, "setpriority");
	if (setgroups(1, &pw->pw_gid) ||
	    setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) ||
	    setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid))
		err(1, "cannot drop privileges");
}

void
aucat_usage(void)
{
	(void)fputs("usage: " PROG_AUCAT " [-dlnu] [-a flag] [-b nframes] "
	    "[-C min:max] [-c min:max] [-e enc]\n\t"
	    "[-f device] [-h fmt] [-i file] [-j flag] [-L addr] [-m mode] "
	    "[-o file]\n\t"
	    "[-q device] [-r rate] [-s name] [-t mode] [-U unit] "
	    "[-v volume]\n\t"
	    "[-w flag] [-x policy] [-z nframes]\n",
	    stderr);
}

int
aucat_main(int argc, char **argv)
{
	struct cfdevlist cfdevs;
	struct cfnetlist cfnets;
	struct cfmid *cm;
	struct cfstr *cs;
	struct cfdev *cd;
	struct cfnet *cn;
	int c, u_flag, d_flag, l_flag, n_flag, unit;
	char base[PATH_MAX], path[PATH_MAX];
	unsigned mode, rate;
	const char *str;
	int autostart;
	struct dev *d, *dnext;
	unsigned active;
	unsigned nsock, nfile;

	/*
	 * global options defaults
	 */
	unit = -1;
	u_flag = 0;
	d_flag = 0;
	l_flag = 0;
	n_flag = 0;
	SLIST_INIT(&cfdevs);
	SLIST_INIT(&cfnets);
	nfile = nsock = 0;

	/*
	 * default stream params
	 */
	cs = malloc(sizeof(struct cfstr));
	if (cs == NULL) {
		perror("malloc");
		exit(1);
	}
	aparams_init(&cs->ipar, 0, 1, DEFAULT_RATE);
	aparams_init(&cs->opar, 0, 1, DEFAULT_RATE);
	cs->mmc = 0;
	cs->hdr = HDR_AUTO;
	cs->xrun = XRUN_IGNORE;
	cs->vol = MIDI_MAXCTL;
	cs->mode = MODE_PLAY | MODE_REC;
	cs->join = 1;

	/*
	 * default device
	 */
	cd = malloc(sizeof(struct cfdev));
	if (cd == NULL) {
		perror("malloc");
		exit(1);
	}
	aparams_init(&cd->ipar, 0, 1, DEFAULT_RATE);
	aparams_init(&cd->opar, 0, 1, DEFAULT_RATE);
	SLIST_INIT(&cd->ins);
	SLIST_INIT(&cd->outs);
	SLIST_INIT(&cd->opts);
	SLIST_INIT(&cd->mids);
	cd->path = NULL;
	cd->bufsz = 0;
	cd->round = 0;
	cd->hold = 1;
	cd->autovol = 1;

	while ((c = getopt(argc, argv, "a:w:dnb:c:C:e:r:h:x:v:i:o:f:m:luq:s:U:L:t:j:z:")) != -1) {
		switch (c) {
		case 'd':
#ifdef DEBUG
			if (d_flag)
				debug_level++;
#endif
			d_flag = 1;
			break;
		case 'n':
			n_flag = 1;
			break;
		case 'u':
			u_flag = 1;
			break;
		case 'U':
			unit = strtonum(optarg, 0, MIDI_MAXCTL, &str);
			if (str)
				errx(1, "%s: unit number is %s", optarg, str);
			break;
		case 'L':
			cfnet_add(&cfnets, optarg);
			break;
		case 'm':
			cs->mode = opt_mode();
			cd->mode = cs->mode;
			break;
		case 'h':
			cs->hdr = opt_hdr();
			break;
		case 'x':
			cs->xrun = opt_xrun();
			break;
		case 'j':
			cs->join = opt_onoff();
			break;
		case 't':
			cs->mmc = opt_mmc();
			break;
		case 'c':
			opt_ch(&cs->ipar);
			cd->opar.cmin = cs->ipar.cmin;
			cd->opar.cmax = cs->ipar.cmax;
			break;
		case 'C':
			opt_ch(&cs->opar);
			cd->ipar.cmin = cs->opar.cmin;
			cd->ipar.cmax = cs->opar.cmax;
			break;
		case 'e':
			opt_enc(&cs->ipar);
			aparams_copyenc(&cs->opar, &cs->ipar);
			break;
		case 'r':
			rate = strtonum(optarg, RATE_MIN, RATE_MAX, &str);
			if (str)
				errx(1, "%s: rate is %s", optarg, str);
			cs->opar.rate = cs->ipar.rate = rate;
			cd->ipar.rate = cd->opar.rate = rate;
			break;
		case 'v':
			cs->vol = strtonum(optarg, 0, MIDI_MAXCTL, &str);
			if (str)
				errx(1, "%s: volume is %s", optarg, str);
			break;
		case 'i':
			cfstr_add(&cd->ins, cs, optarg);
			nfile++;
			break;
		case 'o':
			cfstr_add(&cd->outs, cs, optarg);
			nfile++;
			break;
		case 's':
			cfstr_add(&cd->opts, cs, optarg);
			nsock++;
			break;
		case 'a':
			cd->hold = opt_onoff();
			break;
		case 'w':
			cd->autovol = opt_onoff();
			break;
		case 'q':
			cfmid_add(&cd->mids, optarg);
			break;
		case 'b':
			cd->bufsz = strtonum(optarg, 1, RATE_MAX * 5, &str);
			if (str)
				errx(1, "%s: buffer size is %s", optarg, str);
			break;
		case 'z':
			cd->round = strtonum(optarg, 1, SHRT_MAX, &str);
			if (str)
				errx(1, "%s: block size is %s", optarg, str);
			break;
		case 'f':
			if (SLIST_EMPTY(&cd->opts) &&
			    SLIST_EMPTY(&cd->ins) &&
			    SLIST_EMPTY(&cd->outs)) {
				cfstr_add(&cd->opts, cs, DEFAULT_OPT);
				nsock++;
			}
			cfdev_add(&cfdevs, cd, optarg);
			break;
		case 'l':
			l_flag = 1;
			autostart = 0;
			break;
		default:
			aucat_usage();
			exit(1);
		}
	}
	argc -= optind;
	argv += optind;

#ifdef DEBUG
	if (debug_level == 0)
		debug_level = 1;
#endif
	if (argc > 0) {
		aucat_usage();
		exit(1);
	}

	/*
	 * Check constraints specific to -n option
	 */
	if (n_flag) {
		if (!SLIST_EMPTY(&cfdevs) ||
		    !SLIST_EMPTY(&cd->mids) ||
		    !SLIST_EMPTY(&cd->opts))
			errx(1, "-f, -s, and -q not allowed in loopback mode");
		if (SLIST_EMPTY(&cd->ins) || SLIST_EMPTY(&cd->outs))
			errx(1, "-i and -o are required in loopback mode");
	}

	/*
	 * If there's no device specified, do as if the default
	 * device is specified as last argument.
	 */
	if (SLIST_EMPTY(&cfdevs)) {
		if (SLIST_EMPTY(&cd->opts) &&
		    SLIST_EMPTY(&cd->ins) &&
		    SLIST_EMPTY(&cd->outs)) {
			cfstr_add(&cd->opts, cs, DEFAULT_OPT);
			nsock++;
		}
		if (!cd->hold)
			errx(1, "-a off not compatible with default device");
		cfdev_add(&cfdevs, cd, "default");
	}
	if ((cs = SLIST_FIRST(&cd->opts)) ||
	    (cs = SLIST_FIRST(&cd->ins)) ||
	    (cs = SLIST_FIRST(&cd->outs)))
		errx(1, "%s: no device to attach the stream to", cs->path);

	/*
	 * Check modes and calculate "best" device parameters. Iterate over all
	 * inputs and outputs and find the maximum sample rate and channel
	 * number.
	 */
	SLIST_FOREACH(cd, &cfdevs, entry) {
		mode = 0;
		SLIST_FOREACH(cs, &cd->ins, entry) {
			if (cs->mode == 0)
				errx(1, "%s: not in play mode", cs->path);
			mode |= (cs->mode & MODE_PLAY);
			if (!u_flag)
				aparams_grow(&cd->opar, &cs->ipar);
		}
		SLIST_FOREACH(cs, &cd->outs, entry) {
			if (cs->mode == 0)
				errx(1, "%s: not in rec/mon mode", cs->path);
			if ((cs->mode & MODE_REC) && (cs->mode & MODE_MON))
				errx(1, "%s: can't rec and mon", cs->path);
			mode |= (cs->mode & MODE_RECMASK);
			if (!u_flag)
				aparams_grow(&cd->ipar, &cs->opar);
		}
		SLIST_FOREACH(cs, &cd->opts, entry) {
			if ((cs->mode & MODE_REC) && (cs->mode & MODE_MON))
				errx(1, "%s: can't rec and mon", cs->path);
			mode |= (cs->mode & (MODE_RECMASK | MODE_PLAY));
			if (!u_flag) {
				aparams_grow(&cd->opar, &cs->ipar);
				aparams_grow(&cd->ipar, &cs->opar);
			}
		}
		if ((mode & MODE_MON) && !(mode & MODE_PLAY))
			errx(1, "no playback stream to monitor");
		if (n_flag && (mode & MODE_MON))
			errx(1, "-m mon not allowed in loopback mode");	
		rate = (mode & MODE_REC) ? cd->ipar.rate : cd->opar.rate;
		if (!cd->round)
			cd->round = rate / 15;
		if (!cd->bufsz)
			cd->bufsz = rate / 15 * 4;
		cd->mode = mode;
	}
	if (nsock > 0) {
		getbasepath(base, sizeof(base));
		if (unit < 0)
			unit = 0;
	}
	setsig();
	filelist_init();

	/*
	 * Open devices
	 */
	while (!SLIST_EMPTY(&cfdevs)) {
		cd = SLIST_FIRST(&cfdevs);
		SLIST_REMOVE_HEAD(&cfdevs, entry);

		if (n_flag) {
			d = dev_new_loop(&cd->ipar, &cd->opar, cd->bufsz);
		} else {
			d = dev_new_sio(cd->path, cd->mode | MODE_MIDIMASK,
			    &cd->ipar, &cd->opar, cd->bufsz, cd->round,
			    cd->hold, cd->autovol);
		}
		if (d == NULL)
			errx(1, "%s: can't open device", cd->path);

		/*
		 * register midi devices
		 */
		while (!SLIST_EMPTY(&cd->mids)) {
			cm = SLIST_FIRST(&cd->mids);
			SLIST_REMOVE_HEAD(&cd->mids, entry);
			if (!dev_thruadd(d, cm->path, 1, 1))
				errx(1, "%s: can't open device", cm->path);
			free(cm);
		}

		/*
		 * register files
		 */
		autostart = 0;
		while (!SLIST_EMPTY(&cd->ins)) {
			cs = SLIST_FIRST(&cd->ins);
			SLIST_REMOVE_HEAD(&cd->ins, entry);
			if (!cs->mmc)
				autostart = 1;
			if (strcmp(cs->path, "-") == 0)
				cs->path = NULL;
			if (!wav_new_in(&wav_ops, d, cs->mode & MODE_PLAY,
				cs->path, cs->hdr, &cs->ipar, cs->xrun,
				cs->vol, cs->mmc, cs->join))
				exit(1);
			free(cs);
		}
		while (!SLIST_EMPTY(&cd->outs)) {
			cs = SLIST_FIRST(&cd->outs);
			SLIST_REMOVE_HEAD(&cd->outs, entry);
			if (!cs->mmc)
				autostart = 1;
			if (strcmp(cs->path, "-") == 0)
				cs->path = NULL;
			if (!wav_new_out(&wav_ops, d, cs->mode & MODE_RECMASK,
				cs->path, cs->hdr, &cs->opar, cs->xrun,
				cs->mmc, cs->join))
				exit(1);
			free(cs);
		}
		while (!SLIST_EMPTY(&cd->opts)) {
			cs = SLIST_FIRST(&cd->opts);
			SLIST_REMOVE_HEAD(&cd->opts, entry);
			opt_new(cs->path, d, &cs->opar, &cs->ipar,
			    MIDI_TO_ADATA(cs->vol), cs->mmc,
			    cs->join, cs->mode | MODE_MIDIMASK);
			free(cs);
		}
		free(cd);
		if (autostart) {
			/*
			 * inject artificial mmc start
			 */
			ctl_start(d->midi);
		}
	}
	if (nsock > 0) {
		snprintf(path, sizeof(path), "%s/%s%u", base,
		    AUCAT_PATH, unit);
		listen_new_un(path);
		while (!SLIST_EMPTY(&cfnets)) {
			cn = SLIST_FIRST(&cfnets);
			SLIST_REMOVE_HEAD(&cfnets, entry);
			listen_new_tcp(cn->addr, AUCAT_PORT + unit);
		}
	}
	if (geteuid() == 0)
		privdrop();
	if (l_flag) {
#ifdef DEBUG
		debug_level = 0;
		dbg_flush();
#endif
		if (daemon(0, 0) < 0)
			err(1, "daemon");
	}

	/*
	 * Loop, start audio.
	 */
	for (;;) {
		if (quit_flag)
			break;
		active = 0;
		for (d = dev_list; d != NULL; d = dnext) {
			dnext = d->next;
			if (!dev_run(d))
				goto fatal;
			if (d->pstate != DEV_CLOSED && !ctl_idle(d->midi))
				active = 1;
		}
		if (dev_list == NULL)
			break;
		if (nsock == 0 && !active)
			break;
		if (!file_poll())
			break;
	}
  fatal:
	listen_closeall();

	/*
	 * give a chance to drain
	 */
	for (d = dev_list; d != NULL; d = d->next)
		dev_drain(d);
	while (file_poll())
		; /* nothing */

	while (dev_list)
		dev_del(dev_list);
	filelist_done();
	if (nsock > 0) {
		if (rmdir(base) < 0 && errno != ENOTEMPTY && errno != EPERM)
			warn("rmdir(\"%s\")", base);
	}
	unsetsig();
	return 0;
}

void
midicat_usage(void)
{
	(void)fputs("usage: " PROG_MIDICAT " [-dl] "
	    "[-i file] [-L addr] [-o file] [-q port] [-s name] [-U unit]\n",
	    stderr);
}

int
midicat_main(int argc, char **argv)
{
	struct cfdevlist cfdevs;
	struct cfnetlist cfnets;
	struct cfmid *cm;
	struct cfstr *cs;
	struct cfdev *cd;
	struct cfnet *cn;
	int c, d_flag, l_flag, unit, fd;
	char base[PATH_MAX], path[PATH_MAX];
	struct file *stdx;
	struct aproc *p;
	struct abuf *buf;
	const char *str;
	struct dev *d, *dnext;
	unsigned nsock;

	/*
	 * global options defaults
	 */
	unit = -1;
	d_flag = 0;
	l_flag = 0;
	SLIST_INIT(&cfdevs);
	SLIST_INIT(&cfnets);
	nsock = 0;
	
	/*
	 * default stream params
	 */
	cs = malloc(sizeof(struct cfstr));
	if (cs == NULL) {
		perror("malloc");
		exit(1);
	}
	cs->hdr = HDR_RAW;

	/*
	 * default device
	 */
	cd = malloc(sizeof(struct cfdev));
	if (cd == NULL) {
		perror("malloc");
		exit(1);
	}
	SLIST_INIT(&cd->ins);
	SLIST_INIT(&cd->outs);
	SLIST_INIT(&cd->opts);
	SLIST_INIT(&cd->mids);
	cd->path = NULL;


	while ((c = getopt(argc, argv, "di:o:ls:q:U:L:")) != -1) {
		switch (c) {
		case 'd':
#ifdef DEBUG
			if (d_flag)
				debug_level++;
#endif
			d_flag = 1;
			break;
		case 'i':
			cfstr_add(&cd->ins, cs, optarg);
			break;
		case 'o':
			cfstr_add(&cd->outs, cs, optarg);
			break;
		case 'q':
			cfmid_add(&cd->mids, optarg);
			break;
		case 's':
			cfstr_add(&cd->opts, cs, optarg);
			cfdev_add(&cfdevs, cd, optarg);
			nsock++;
			break;
		case 'l':
			l_flag = 1;
			break;
		case 'U':
			unit = strtonum(optarg, 0, MIDI_MAXCTL, &str);
			if (str)
				errx(1, "%s: unit number is %s", optarg, str);
			break;
		case 'L':
			cfnet_add(&cfnets, optarg);
			break;
		default:
			midicat_usage();
			exit(1);
		}
	}
	argc -= optind;
	argv += optind;

#ifdef DEBUG
	if (debug_level == 0)
		debug_level = 1;
#endif
	if (argc > 0) {
		midicat_usage();
		exit(1);
	}

	/*
	 * If there's no device specified (-s), then create one with
	 * reasonable defaults:
	 *
	 *  - if there are no streams (-ioq) defined, assume server mode
	 *    and expose the "defaut" option
	 *
	 *  - if there are files (-io) but no ports (-q) to send/receive
	 *    from, add the default sndio(7) MIDI port
	 */
	if (SLIST_EMPTY(&cfdevs)) {
		if (SLIST_EMPTY(&cd->mids)) {
			if (!SLIST_EMPTY(&cd->ins) || !SLIST_EMPTY(&cd->outs))
			    	cfmid_add(&cd->mids, "default");
			else {
				cfstr_add(&cd->opts, cs, DEFAULT_OPT);
				nsock++;
			}
		}
		cfdev_add(&cfdevs, cd, "default");
	}
	if (nsock > 0) {
		getbasepath(base, sizeof(path));
		if (unit < 0)
			unit = 0;
	}
	setsig();
	filelist_init();

	while (!SLIST_EMPTY(&cfdevs)) {
		cd = SLIST_FIRST(&cfdevs);
		SLIST_REMOVE_HEAD(&cfdevs, entry);

		d = dev_new_thru();
		if (d == NULL)
			errx(1, "%s: can't open device", cd->path);
		if (!dev_ref(d))
			errx(1, "couldn't open midi thru box");
		if (SLIST_EMPTY(&cd->opts) && APROC_OK(d->midi))
			d->midi->flags |= APROC_QUIT;

		/*
		 * register midi ports
		 */
		while (!SLIST_EMPTY(&cd->mids)) {
			cm = SLIST_FIRST(&cd->mids);
			SLIST_REMOVE_HEAD(&cd->mids, entry);
			if (!dev_thruadd(d, cm->path, 1, 1))
				errx(1, "%s: can't open device", cm->path);
			free(cm);
		}

		/*
		 * register files
		 */
		while (!SLIST_EMPTY(&cd->ins)) {
			cs = SLIST_FIRST(&cd->ins);
			SLIST_REMOVE_HEAD(&cd->ins, entry);
			if (strcmp(cs->path, "-") == 0) {
				fd = STDIN_FILENO;
				if (fcntl(fd, F_SETFL, O_NONBLOCK) < 0)
					warn("stdin");
			} else {
				fd = open(cs->path, O_RDONLY | O_NONBLOCK, 0666);
				if (fd < 0)
					err(1, "%s", cs->path);
			}
			stdx = (struct file *)pipe_new(&pipe_ops, fd, cs->path);
			p = rfile_new(stdx);
			buf = abuf_new(MIDI_BUFSZ, &aparams_none);
			aproc_setout(p, buf);
			dev_midiattach(d, buf, NULL);
			free(cs);
		}
		while (!SLIST_EMPTY(&cd->outs)) {
			cs = SLIST_FIRST(&cd->outs);
			SLIST_REMOVE_HEAD(&cd->outs, entry);
			if (strcmp(cs->path, "-") == 0) {
				fd = STDOUT_FILENO;
				if (fcntl(fd, F_SETFL, O_NONBLOCK) < 0)
					warn("stdout");
			} else {
				fd = open(cs->path,
				    O_WRONLY | O_TRUNC | O_CREAT | O_NONBLOCK, 0666);
				if (fd < 0)
					err(1, "%s", cs->path);
			}
			stdx = (struct file *)pipe_new(&pipe_ops, fd, cs->path);
			p = wfile_new(stdx);
			buf = abuf_new(MIDI_BUFSZ, &aparams_none);
			aproc_setin(p, buf);
			dev_midiattach(d, NULL, buf);
			free(cs);
		}
		while (!SLIST_EMPTY(&cd->opts)) {
			cs = SLIST_FIRST(&cd->opts);
			SLIST_REMOVE_HEAD(&cd->opts, entry);
			opt_new(cs->path, d, NULL, NULL, 0, 0, 0, MODE_MIDIMASK);
			free(cs);
		}
		free(cd);
	}
	if (nsock > 0) {
		snprintf(path, sizeof(path), "%s/%s%u", base,
		    MIDICAT_PATH, unit);
		listen_new_un(path);
		while (!SLIST_EMPTY(&cfnets)) {
			cn = SLIST_FIRST(&cfnets);
			SLIST_REMOVE_HEAD(&cfnets, entry);
			listen_new_tcp(cn->addr, MIDICAT_PORT + unit);
		}
	}
	if (geteuid() == 0)
		privdrop();
	if (l_flag) {
#ifdef DEBUG
		debug_level = 0;
		dbg_flush();
#endif
		if (daemon(0, 0) < 0)
			err(1, "daemon");
	}

	/*
	 * loop, start processing
	 */
	for (;;) {
		if (quit_flag)
			break;
		for (d = dev_list; d != NULL; d = dnext) {
			dnext = d->next;
			if (!dev_run(d))
				goto fatal;
		}
		if (!file_poll())
			break;
	}
  fatal:
	listen_closeall();

	/*
	 * give a chance to drain
	 */
	for (d = dev_list; d != NULL; d = d->next)
		dev_drain(d);
	while (file_poll())
		; /* nothing */

	while (dev_list)
		dev_del(dev_list);
	filelist_done();
	if (nsock > 0) {
		if (rmdir(base) < 0 && errno != ENOTEMPTY && errno != EPERM)
			warn("rmdir(\"%s\")", base);
	}
	unsetsig();
	return 0;
}

int
main(int argc, char **argv)
{
	char *prog;

#ifdef DEBUG
	atexit(dbg_flush);
#endif
	prog = strrchr(argv[0], '/');
	if (prog == NULL)
		prog = argv[0];
	else
		prog++;
	if (strcmp(prog, PROG_AUCAT) == 0) {
		return aucat_main(argc, argv);
	} else if (strcmp(prog, PROG_MIDICAT) == 0) {
		return midicat_main(argc, argv);
	} else {
		fprintf(stderr, "%s: can't determine program to run\n", prog);
	}
	return 1;
}