/* $OpenBSD: aucat.c,v 1.114 2011/05/10 06:26:34 ratchov Exp $ */ /* * Copyright (c) 2008 Alexandre Ratchov * * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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 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" "[-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; while ((c = getopt(argc, argv, "a: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 '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); } 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; }