[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.88, Sat Apr 24 14:33:46 2010 UTC (14 years, 1 month ago) by ratchov
Branch: MAIN
Changes since 1.87: +17 -16 lines

if there are no files to play in legacy mode, print the usage message

/*	$OpenBSD: aucat.c,v 1.88 2010/04/24 14:33:46 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 <varargs.h>

#include "abuf.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"

#ifdef DEBUG
int debug_level = 0;
#endif
volatile int 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;
	if (errno == ERANGE && (cmin == LONG_MIN || cmin == LONG_MAX))
		goto failed;
	cmax = strtol(++next, &end, 10);
	if (end == next || *end != '\0')
		goto failed;
	if (errno == ERANGE && (cmax == LONG_MIN || cmax == LONG_MAX))
		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_join(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 != NULL; 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;
}

/*
 * Arguments of -i, -o and -s options are stored in a list.
 */
struct farg {
	SLIST_ENTRY(farg) entry;
	struct aparams ipar;	/* input (read) parameters */
	struct aparams opar;	/* output (write) parameters */
	unsigned vol;		/* last requested volume */
	char *name;		/* optarg pointer (no need to copy it */
	int hdr;		/* header format */
	int xrun;		/* overrun/underrun policy */
	int mmc;		/* MMC mode */
	int join;		/* join/expand enabled */
	unsigned mode;
};

SLIST_HEAD(farglist, farg);

/*
 * Add a farg entry to the given list, corresponding
 * to the given file name.
 */
void
farg_add(struct farglist *list,
    struct aparams *ipar, struct aparams *opar, unsigned vol,
    int hdr, int xrun, int mmc, int join, unsigned mode, char *name)
{
	struct farg *fa;
	size_t namelen;

	fa = malloc(sizeof(struct farg));
	if (fa == NULL)
		err(1, "%s", name);

	if (hdr == HDR_AUTO) {
		if (name != NULL && (namelen = strlen(name)) >= 4 &&
		    strcasecmp(name + namelen - 4, ".wav") == 0) {
			fa->hdr = HDR_WAV;
		} else {
			fa->hdr = HDR_RAW;
		}
	} else
		fa->hdr = hdr;
	if (mmc && xrun == XRUN_IGNORE)
		xrun = XRUN_SYNC;
	fa->xrun = xrun;
	fa->ipar = *ipar;
	fa->opar = *opar;
	fa->vol = vol;
	fa->name = name; 
	fa->mmc = mmc;
	fa->join = join;
	fa->mode = mode;
	SLIST_INSERT_HEAD(list, fa, 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)
		err(1, "getpwnam");
	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
stopall(char *base)
{
	struct file *f;

  restart:
	LIST_FOREACH(f, &file_list, entry) {
		/*
		 * skip connected streams (handled by dev_done())
		 */
		if (APROC_OK(dev_mix) && f->rproc &&
		    aproc_depend(dev_mix, f->rproc))
			continue;
		if (APROC_OK(dev_sub) && f->wproc &&
		    aproc_depend(f->wproc, dev_sub))
			continue;
		if (APROC_OK(dev_midi)) {
			if (f->rproc && aproc_depend(dev_midi, f->rproc))
				continue;
			if (f->wproc && aproc_depend(f->wproc, dev_midi))
				continue;
		}
		/*
		 * kill anything else
		 */
		file_close(f);
		goto restart;
	}
}

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

int
aucat_main(int argc, char **argv)
{
	int c, u_flag, d_flag, l_flag, n_flag, hdr, xrun, unit;
	struct farg *fa;
	struct farglist ifiles, ofiles, sfiles, qfiles;
	struct aparams ipar, opar, dipar, dopar;
	char base[PATH_MAX], path[PATH_MAX], *file;
	unsigned bufsz, round, mode;
	char *devpath;
	const char *str;
	unsigned volctl;
	int mmc, autostart, join;

	aparams_init(&ipar, 0, 1, 44100);
	aparams_init(&opar, 0, 1, 44100);
	u_flag = 0;
	d_flag = 0;
	l_flag = 0;
	n_flag = 0;
	unit = -1;
	mmc = 0;
	devpath = NULL;
	SLIST_INIT(&ifiles);
	SLIST_INIT(&ofiles);
	SLIST_INIT(&sfiles);
	SLIST_INIT(&qfiles);
	hdr = HDR_AUTO;
	xrun = XRUN_IGNORE;
	volctl = MIDI_MAXCTL;
	mode = MODE_PLAY | MODE_REC;
	bufsz = 0;
	round = 0;
	autostart = 1;
	join = 1;

	while ((c = getopt(argc, argv, "dnb:c:C:e:r:h:x:v:i:o:f:m:luq:s:U: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 'm':
			mode = opt_mode();
			break;
		case 'h':
			hdr = opt_hdr();
			break;
		case 'x':
			xrun = opt_xrun();
			break;
		case 'j':
			join = opt_join();
			break;
		case 't':
			mmc = opt_mmc();
			if (mmc)
				autostart = 0;
			break;
		case 'c':
			opt_ch(&ipar);
			break;
		case 'C':
			opt_ch(&opar);
			break;
		case 'e':
			opt_enc(&ipar);
			aparams_copyenc(&opar, &ipar);
			break;
		case 'r':
			ipar.rate = strtonum(optarg, RATE_MIN, RATE_MAX, &str);
			if (str)
				errx(1, "%s: rate is %s", optarg, str);
			opar.rate = ipar.rate;
			break;
		case 'v':
			volctl = strtonum(optarg, 0, MIDI_MAXCTL, &str);
			if (str)
				errx(1, "%s: volume is %s", optarg, str);
			break;
		case 'i':
			file = optarg;
			if (strcmp(file, "-") == 0)
				file = NULL;
			farg_add(&ifiles, &ipar, &opar, volctl,
			    hdr, xrun, mmc, join, mode & MODE_PLAY, file);
			break;
		case 'o':
			file = optarg;
			if (strcmp(file, "-") == 0)
				file = NULL;
			farg_add(&ofiles, &ipar, &opar, volctl,
			    hdr, xrun, mmc, join, mode & MODE_RECMASK, file);
			break;
		case 's':
			farg_add(&sfiles, &ipar, &opar, volctl,
			    hdr, xrun, mmc, join, mode, optarg);
			break;
		case 'q':
			farg_add(&qfiles, &aparams_none, &aparams_none,
			    0, HDR_RAW, 0, 0, 0, 0, optarg);
			break;
		case 'f':
			if (devpath)
				err(1, "only one -f allowed");
			devpath = optarg;
			dipar = opar;
			dopar = ipar;
			break;
		case 'l':
			l_flag = 1;
			autostart = 0;
			break;
		case 'u':
			u_flag = 1;
			break;
		case 'b':
			bufsz = strtonum(optarg, 1, RATE_MAX * 5, &str);
			if (str)
				errx(1, "%s: buffer size is %s", optarg, str);
			break;
		case 'U':
			unit = strtonum(optarg, 0, MIDI_MAXCTL, &str);
			if (str)
				errx(1, "%s: device number is %s", optarg, str);
			break;
		case 'z':
			round = strtonum(optarg, 1, SHRT_MAX, &str);
			if (str)
				errx(1, "%s: block size is %s", optarg, str);
			break;
		default:
			aucat_usage();
			exit(1);
		}
	}
	argc -= optind;
	argv += optind;

	if (!devpath) {
		dopar = ipar;
		dipar = opar;
	}
	if (!l_flag && SLIST_EMPTY(&ifiles) && SLIST_EMPTY(&ofiles)) {
		if (argc > 0) {
			/*
			 * Legacy mode: if no -i or -o options are provided, and
			 * there are arguments then assume the arguments are files
			 * to play.
			 */
			for (c = 0; c < argc; c++)
				if (legacy_play(devpath, argv[c]) != 0) {
					errx(1, "%s: could not play\n", argv[c]);
				}
			exit(0);
		} else {
			aucat_usage();
			exit(1);
		}
	}

	if (!l_flag && (!SLIST_EMPTY(&sfiles) || unit >= 0))
		errx(1, "can't use -s or -U without -l");
	if (l_flag && (!SLIST_EMPTY(&ofiles) || !SLIST_EMPTY(&ifiles)))
		errx(1, "can't use -l, and -s with -o or -i");
	if (n_flag) {
		if (devpath != NULL || !SLIST_EMPTY(&qfiles) ||
		    l_flag || !autostart)
			errx(1, "can't use -n with -f, -q, -t or -l");
		if (SLIST_EMPTY(&ifiles) || SLIST_EMPTY(&ofiles))
			errx(1, "both -i and -o are required with -n");
	}

	/*
	 * If there are no sockets paths provided use the default.
	 */
	if (l_flag && SLIST_EMPTY(&sfiles)) {
		farg_add(&sfiles, &ipar, &opar,
		    volctl, HDR_RAW, XRUN_IGNORE, mmc, join, mode, DEFAULT_OPT);
	}

	/*
	 * Check modes and calculate "best" device parameters. Iterate over all
	 * inputs and outputs and find the maximum sample rate and channel
	 * number.
	 */
	mode = 0;
	aparams_init(&dipar, dipar.cmin, dipar.cmax, dipar.rate);
	aparams_init(&dopar, dopar.cmin, dopar.cmax, dopar.rate);
	SLIST_FOREACH(fa, &ifiles, entry) {
		if (fa->mode == 0)
			errx(1, "%s: not in play mode", fa->name);
		mode |= fa->mode;
		if (!u_flag)
			aparams_grow(&dopar, &fa->ipar);
	}
	SLIST_FOREACH(fa, &ofiles, entry) {
		if (fa->mode == 0)
			errx(1, "%s: not in rec/mon mode", fa->name);
		if ((fa->mode & MODE_REC) && (fa->mode & MODE_MON))
			errx(1, "%s: can't record and monitor", fa->name);
		mode |= fa->mode;
		if (!u_flag)
			aparams_grow(&dipar, &fa->opar);
	}
	SLIST_FOREACH(fa, &sfiles, entry) {
		if ((fa->mode & MODE_REC) && (fa->mode & MODE_MON))
			errx(1, "%s: can't record and monitor", fa->name);
		mode |= fa->mode;
		if (!u_flag) {
			aparams_grow(&dopar, &fa->ipar);
			aparams_grow(&dipar, &fa->opar);
		}
	}

	if (!round)
		round = ((mode & MODE_REC) ? dipar.rate : dopar.rate) / 15;
	if (!bufsz)
		bufsz = ((mode & MODE_REC) ? dipar.rate : dopar.rate) * 4 / 15;

	if (l_flag) {
		getbasepath(base, sizeof(base));
		if (unit < 0)
			unit = 0;
	}
	setsig();
	filelist_init();

	/*
	 * Open the device. Give half of the buffer to the device,
	 * the other half is for the socket/files.
	 */
	if (n_flag) {
		if (mode & MODE_MON)
			errx(1, "monitoring not allowed in loopback mode");
		dev_loopinit(&dipar, &dopar, bufsz);
	} else {
		if ((mode & MODE_MON) && !(mode & MODE_PLAY))
			errx(1, "no playback stream to monitor");
		if (!dev_init(devpath, mode, &dipar, &dopar, bufsz, round)) {
			errx(1, "%s: can't open device", 
			    devpath ? devpath : "<default>");
		}
	}

	/*
	 * Create buffers for all input and output pipes.
	 */
	while (!SLIST_EMPTY(&qfiles)) {
		fa = SLIST_FIRST(&qfiles);
		SLIST_REMOVE_HEAD(&qfiles, entry);
		if (!dev_thruadd(fa->name, 1, 1))
			errx(1, "%s: can't open device", fa->name);
		free(fa);
	}
	while (!SLIST_EMPTY(&ifiles)) {
		fa = SLIST_FIRST(&ifiles);
		SLIST_REMOVE_HEAD(&ifiles, entry);
		if (!wav_new_in(&wav_ops, fa->mode, fa->name,
			fa->hdr, &fa->ipar, fa->xrun, fa->vol, fa->mmc,
			fa->join))
			exit(1);
		free(fa);
	}
	while (!SLIST_EMPTY(&ofiles)) {
		fa = SLIST_FIRST(&ofiles);
		SLIST_REMOVE_HEAD(&ofiles, entry);
		if (!wav_new_out(&wav_ops, fa->mode, fa->name,
			fa->hdr, &fa->opar, fa->xrun, fa->mmc,
			fa->join))
		free(fa);
	}
	while (!SLIST_EMPTY(&sfiles)) {
		fa = SLIST_FIRST(&sfiles);
		SLIST_REMOVE_HEAD(&sfiles, entry);
		opt_new(fa->name, &fa->opar, &fa->ipar,
		    MIDI_TO_ADATA(fa->vol), fa->mmc, fa->join, fa->mode);
		free(fa);
	}
	if (l_flag) {
		snprintf(path, sizeof(path), "%s/%s%u", base,
		    DEFAULT_SOFTAUDIO, unit);
		listen_new(&listen_ops, path);
		if (geteuid() == 0)
			privdrop();
		if (!d_flag && daemon(0, 0) < 0)
			err(1, "daemon");
	}
	if (autostart) {
		/*
		 * inject artificial mmc start
		 */
		ctl_start(dev_midi);
	}
	if (l_flag)
		dev_prime();

	/*
	 * Loop, start audio.
	 */
	for (;;) {
		if (quit_flag) {
			break;
		}
		if ((APROC_OK(dev_mix) && LIST_EMPTY(&dev_mix->outs)) ||
		    (APROC_OK(dev_sub) && LIST_EMPTY(&dev_sub->ins))) {
			fprintf(stderr, "device disappeared, terminating\n");
			break;
		}
		if (!l_flag && ctl_idle(dev_midi))
			break;
		if (!file_poll())
			break;
		if ((!APROC_OK(dev_mix)    || dev_mix->u.mix.idle > 2 * dev_bufsz) &&
		    (!APROC_OK(dev_sub)    || dev_sub->u.sub.idle > 2 * dev_bufsz) &&
		    (!APROC_OK(dev_submon) || dev_submon->u.sub.idle > 2 * dev_bufsz) &&
		    (!APROC_OK(dev_midi)   || dev_midi->u.ctl.tstate != CTL_RUN)) {
		    	if (dev_pstate == DEV_RUN) {
				dev_pstate = DEV_INIT;
				dev_stop();
				dev_clear();
				/*
				 * priming buffer in non-server mode is not
				 * ok, because it will insert silence and
				 * break synchronization
				 */
				if (l_flag)
					dev_prime();
			}
		}
		/*
		 * move device state machine
		 * XXX: move this to dev.c
		 */
		if (dev_pstate == DEV_START) {
			dev_pstate = DEV_RUN;
			dev_start();
		}
	}
	stopall(base);
	dev_done();
	filelist_done();
	if (l_flag) {
		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] [-o file] [-q device] [-U unit]\n",
	    stderr);
}
int
midicat_main(int argc, char **argv)
{
	int c, d_flag, l_flag, unit, fd;
	struct farglist dfiles, ifiles, ofiles;
	char base[PATH_MAX], path[PATH_MAX];
	struct farg *fa;
	struct file *stdx;
	struct aproc *p;
	struct abuf *buf;
	const char *str;

	d_flag = 0;
	l_flag = 0;
	unit = -1;
	SLIST_INIT(&dfiles);
	SLIST_INIT(&ifiles);
	SLIST_INIT(&ofiles);

	while ((c = getopt(argc, argv, "di:o:lf:q:U:")) != -1) {
		switch (c) {
		case 'd':
#ifdef DEBUG
			if (d_flag)
				debug_level++;
#endif
			d_flag = 1;
			break;
		case 'i':
			farg_add(&ifiles, &aparams_none, &aparams_none,
			    0, HDR_RAW, 0, 0, 0, 0, optarg);
			break;
		case 'o':
			farg_add(&ofiles, &aparams_none, &aparams_none,
			    0, HDR_RAW, 0, 0, 0, 0, optarg);
			break;
			/* XXX: backward compat, remove this */
		case 'f':	
		case 'q':
			farg_add(&dfiles, &aparams_none, &aparams_none,
			    0, HDR_RAW, 0, 0, 0, 0, optarg);
			break;
		case 'l':
			l_flag = 1;
			break;
		case 'U':
			unit = strtonum(optarg, 0, MIDI_MAXCTL, &str);
			if (str)
				errx(1, "%s: device number is %s", optarg, str);
			break;
		default:
			midicat_usage();
			exit(1);
		}
	}
	argc -= optind;
	argv += optind;

	if (argc > 0 || (SLIST_EMPTY(&ifiles) && SLIST_EMPTY(&ofiles) &&
	    !l_flag)) {
		midicat_usage();
		exit(1);
	}
	if (!l_flag && unit >= 0)
		errx(1, "can't use -U without -l");
	if (l_flag) {
		if (!SLIST_EMPTY(&ifiles) || !SLIST_EMPTY(&ofiles))
			errx(1, "can't use -i or -o with -l");
		getbasepath(base, sizeof(path));
		if (unit < 0)
			unit = 0;
	}
	setsig();
	filelist_init();

	dev_thruinit();
	if (!l_flag && APROC_OK(dev_midi))
		dev_midi->flags |= APROC_QUIT;
	if ((!SLIST_EMPTY(&ifiles) || !SLIST_EMPTY(&ofiles)) && 
	    SLIST_EMPTY(&dfiles)) {
		farg_add(&dfiles, &aparams_none, &aparams_none,
		    0, HDR_RAW, 0, 0, 0, 0, NULL);
	}
	while (!SLIST_EMPTY(&dfiles)) {
		fa = SLIST_FIRST(&dfiles);
		SLIST_REMOVE_HEAD(&dfiles, entry);
		if (!dev_thruadd(fa->name, 
			!SLIST_EMPTY(&ofiles) || l_flag,
			!SLIST_EMPTY(&ifiles) || l_flag)) {
			errx(1, "%s: can't open device", 
			    fa->name ? fa->name : "<default>");
		}
		free(fa);
	}
	if (l_flag) {
		snprintf(path, sizeof(path), "%s/%s%u", base,
		    DEFAULT_MIDITHRU, unit);
		listen_new(&listen_ops, path);
		if (geteuid() == 0)
			privdrop();
		if (!d_flag && daemon(0, 0) < 0)
			err(1, "daemon");
	}
	while (!SLIST_EMPTY(&ifiles)) {
		fa = SLIST_FIRST(&ifiles);
		SLIST_REMOVE_HEAD(&ifiles, entry);
		if (strcmp(fa->name, "-") == 0) {
			fd = STDIN_FILENO;
			if (fcntl(fd, F_SETFL, O_NONBLOCK) < 0)
				warn("stdin");
		} else {
			fd = open(fa->name, O_RDONLY | O_NONBLOCK, 0666);
			if (fd < 0)
				err(1, "%s", fa->name);
		}
		stdx = (struct file *)pipe_new(&pipe_ops, fd, fa->name);
		p = rfile_new(stdx);
		buf = abuf_new(MIDI_BUFSZ, &aparams_none);
		aproc_setout(p, buf);
		dev_midiattach(buf, NULL);
		free(fa);
	}
	while (!SLIST_EMPTY(&ofiles)) {
		fa = SLIST_FIRST(&ofiles);
		SLIST_REMOVE_HEAD(&ofiles, entry);
		if (strcmp(fa->name, "-") == 0) {
			fd = STDOUT_FILENO;
			if (fcntl(fd, F_SETFL, O_NONBLOCK) < 0)
				warn("stdout");
		} else {
			fd = open(fa->name,
			    O_WRONLY | O_TRUNC | O_CREAT | O_NONBLOCK, 0666);
			if (fd < 0)
				err(1, "%s", fa->name);
		}
		stdx = (struct file *)pipe_new(&pipe_ops, fd, fa->name);
		p = wfile_new(stdx);
		buf = abuf_new(MIDI_BUFSZ, &aparams_none);
		aproc_setin(p, buf);
		dev_midiattach(NULL, buf);
		free(fa);
	}
	/*
	 * loop, start processing
	 */
	for (;;) {
		if (quit_flag) {
			break;
		}
		if (!file_poll())
			break;
	}
	stopall(base);
	dev_done();
	filelist_done();
	if (l_flag) {
		if (rmdir(base) < 0 && errno != ENOTEMPTY && errno != EPERM)
			warn("rmdir(\"%s\")", base);
	}
	unsetsig();
	return 0;
}


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

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