1*7b639200Sratchov /* $OpenBSD: sndiod.c,v 1.50 2024/12/20 07:35:56 ratchov Exp $ */ 287bc9f6aSratchov /* 387bc9f6aSratchov * Copyright (c) 2008-2012 Alexandre Ratchov <alex@caoua.org> 487bc9f6aSratchov * 587bc9f6aSratchov * Permission to use, copy, modify, and distribute this software for any 687bc9f6aSratchov * purpose with or without fee is hereby granted, provided that the above 787bc9f6aSratchov * copyright notice and this permission notice appear in all copies. 887bc9f6aSratchov * 987bc9f6aSratchov * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 1087bc9f6aSratchov * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 1187bc9f6aSratchov * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 1287bc9f6aSratchov * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 1387bc9f6aSratchov * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 1487bc9f6aSratchov * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 1587bc9f6aSratchov * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 1687bc9f6aSratchov */ 1787bc9f6aSratchov #include <sys/stat.h> 1887bc9f6aSratchov #include <sys/types.h> 1987bc9f6aSratchov #include <sys/resource.h> 20395f8c55Sratchov #include <sys/socket.h> 2187bc9f6aSratchov 2287bc9f6aSratchov #include <err.h> 2387bc9f6aSratchov #include <errno.h> 2487bc9f6aSratchov #include <fcntl.h> 2587bc9f6aSratchov #include <grp.h> 2687bc9f6aSratchov #include <limits.h> 2787bc9f6aSratchov #include <pwd.h> 2887bc9f6aSratchov #include <signal.h> 2987bc9f6aSratchov #include <sndio.h> 3087bc9f6aSratchov #include <stdio.h> 3187bc9f6aSratchov #include <stdlib.h> 3287bc9f6aSratchov #include <string.h> 3387bc9f6aSratchov #include <unistd.h> 3487bc9f6aSratchov 3587bc9f6aSratchov #include "amsg.h" 3687bc9f6aSratchov #include "defs.h" 3787bc9f6aSratchov #include "dev.h" 38395f8c55Sratchov #include "fdpass.h" 3987bc9f6aSratchov #include "file.h" 4087bc9f6aSratchov #include "listen.h" 4187bc9f6aSratchov #include "midi.h" 4287bc9f6aSratchov #include "opt.h" 4387bc9f6aSratchov #include "sock.h" 4487bc9f6aSratchov #include "utils.h" 4587bc9f6aSratchov 4687bc9f6aSratchov /* 4787bc9f6aSratchov * unprivileged user name 4887bc9f6aSratchov */ 4987bc9f6aSratchov #ifndef SNDIO_USER 5087bc9f6aSratchov #define SNDIO_USER "_sndio" 5187bc9f6aSratchov #endif 5287bc9f6aSratchov 5387bc9f6aSratchov /* 54395f8c55Sratchov * privileged user name 55395f8c55Sratchov */ 56395f8c55Sratchov #ifndef SNDIO_PRIV_USER 57395f8c55Sratchov #define SNDIO_PRIV_USER "_sndiop" 58395f8c55Sratchov #endif 59395f8c55Sratchov 60395f8c55Sratchov /* 6187bc9f6aSratchov * priority when run as root 6287bc9f6aSratchov */ 6387bc9f6aSratchov #ifndef SNDIO_PRIO 6487bc9f6aSratchov #define SNDIO_PRIO (-20) 6587bc9f6aSratchov #endif 6687bc9f6aSratchov 6787bc9f6aSratchov /* 6887bc9f6aSratchov * sample rate if no ``-r'' is used 6987bc9f6aSratchov */ 7087bc9f6aSratchov #ifndef DEFAULT_RATE 7187bc9f6aSratchov #define DEFAULT_RATE 48000 7287bc9f6aSratchov #endif 7387bc9f6aSratchov 7487bc9f6aSratchov /* 7587bc9f6aSratchov * block size if neither ``-z'' nor ``-b'' is used 7687bc9f6aSratchov */ 7787bc9f6aSratchov #ifndef DEFAULT_ROUND 78937646b7Sratchov #define DEFAULT_ROUND 480 7987bc9f6aSratchov #endif 8087bc9f6aSratchov 8187bc9f6aSratchov /* 8287bc9f6aSratchov * buffer size if neither ``-z'' nor ``-b'' is used 8387bc9f6aSratchov */ 8487bc9f6aSratchov #ifndef DEFAULT_BUFSZ 8518fa537dSdcoppa #define DEFAULT_BUFSZ 7680 8687bc9f6aSratchov #endif 8787bc9f6aSratchov 8819e766d9Sratchov /* 8919e766d9Sratchov * default device precision 9019e766d9Sratchov */ 9119e766d9Sratchov #ifndef DEFAULT_BITS 9219e766d9Sratchov #define DEFAULT_BITS 16 9319e766d9Sratchov #endif 9419e766d9Sratchov 95fcda7a7eSratchov void sigint(int); 96731605d7Sratchov void sighup(int); 97fcda7a7eSratchov void opt_ch(int *, int *); 98fcda7a7eSratchov void opt_enc(struct aparams *); 99fcda7a7eSratchov int opt_mmc(void); 100fcda7a7eSratchov int opt_onoff(void); 101fcda7a7eSratchov int getword(char *, char **); 102fcda7a7eSratchov unsigned int opt_mode(void); 1034182f7f9Sratchov void getbasepath(char *); 104fcda7a7eSratchov void setsig(void); 105fcda7a7eSratchov void unsetsig(void); 106fcda7a7eSratchov struct dev *mkdev(char *, struct aparams *, 107fcda7a7eSratchov int, int, int, int, int, int); 108ea4468e3Sratchov struct port *mkport(char *, int); 109fcda7a7eSratchov struct opt *mkopt(char *, struct dev *, 110fcda7a7eSratchov int, int, int, int, int, int, int, int); 111fcda7a7eSratchov 11287bc9f6aSratchov unsigned int log_level = 0; 113731605d7Sratchov volatile sig_atomic_t quit_flag = 0, reopen_flag = 0; 11487bc9f6aSratchov 11587bc9f6aSratchov char usagestr[] = "usage: sndiod [-d] [-a flag] [-b nframes] " 116731605d7Sratchov "[-C min:max] [-c min:max]\n\t" 117731605d7Sratchov "[-e enc] [-F device] [-f device] [-j flag] [-L addr] [-m mode]\n\t" 118731605d7Sratchov "[-Q port] [-q port] [-r rate] [-s name] [-t mode] [-U unit]\n\t" 119731605d7Sratchov "[-v volume] [-w flag] [-z nframes]\n"; 12087bc9f6aSratchov 12187bc9f6aSratchov /* 122d45714e8Sratchov * default audio devices 123d45714e8Sratchov */ 124d45714e8Sratchov static char *default_devs[] = { 125d45714e8Sratchov "rsnd/0", "rsnd/1", "rsnd/2", "rsnd/3", 126d45714e8Sratchov NULL 127d45714e8Sratchov }; 128d45714e8Sratchov 129d45714e8Sratchov /* 130efc9ab16Sratchov * default MIDI ports 131efc9ab16Sratchov */ 132efc9ab16Sratchov static char *default_ports[] = { 133efc9ab16Sratchov "rmidi/0", "rmidi/1", "rmidi/2", "rmidi/3", 134efc9ab16Sratchov "rmidi/4", "rmidi/5", "rmidi/6", "rmidi/7", 135efc9ab16Sratchov NULL 136efc9ab16Sratchov }; 137efc9ab16Sratchov 138efc9ab16Sratchov /* 13987bc9f6aSratchov * SIGINT handler, it raises the quit flag. If the flag is already set, 14087bc9f6aSratchov * that means that the last SIGINT was not handled, because the process 14187bc9f6aSratchov * is blocked somewhere, so exit. 14287bc9f6aSratchov */ 14387bc9f6aSratchov void 14487bc9f6aSratchov sigint(int s) 14587bc9f6aSratchov { 14687bc9f6aSratchov if (quit_flag) 14787bc9f6aSratchov _exit(1); 14887bc9f6aSratchov quit_flag = 1; 14987bc9f6aSratchov } 15087bc9f6aSratchov 151731605d7Sratchov /* 152731605d7Sratchov * SIGHUP handler, it raises the reopen flag, which requests devices 153731605d7Sratchov * to be reopened. 154731605d7Sratchov */ 155731605d7Sratchov void 156731605d7Sratchov sighup(int s) 157731605d7Sratchov { 158731605d7Sratchov reopen_flag = 1; 159731605d7Sratchov } 160731605d7Sratchov 16187bc9f6aSratchov void 16287bc9f6aSratchov opt_ch(int *rcmin, int *rcmax) 16387bc9f6aSratchov { 16487bc9f6aSratchov char *next, *end; 16587bc9f6aSratchov long cmin, cmax; 16687bc9f6aSratchov 16787bc9f6aSratchov errno = 0; 16887bc9f6aSratchov cmin = strtol(optarg, &next, 10); 16987bc9f6aSratchov if (next == optarg || *next != ':') 17087bc9f6aSratchov goto failed; 17187bc9f6aSratchov cmax = strtol(++next, &end, 10); 17287bc9f6aSratchov if (end == next || *end != '\0') 17387bc9f6aSratchov goto failed; 17487bc9f6aSratchov if (cmin < 0 || cmax < cmin || cmax >= NCHAN_MAX) 17587bc9f6aSratchov goto failed; 17687bc9f6aSratchov *rcmin = cmin; 17787bc9f6aSratchov *rcmax = cmax; 17887bc9f6aSratchov return; 17987bc9f6aSratchov failed: 18087bc9f6aSratchov errx(1, "%s: bad channel range", optarg); 18187bc9f6aSratchov } 18287bc9f6aSratchov 18387bc9f6aSratchov void 18487bc9f6aSratchov opt_enc(struct aparams *par) 18587bc9f6aSratchov { 18687bc9f6aSratchov int len; 18787bc9f6aSratchov 18887bc9f6aSratchov len = aparams_strtoenc(par, optarg); 18987bc9f6aSratchov if (len == 0 || optarg[len] != '\0') 19087bc9f6aSratchov errx(1, "%s: bad encoding", optarg); 19187bc9f6aSratchov } 19287bc9f6aSratchov 19387bc9f6aSratchov int 19487bc9f6aSratchov opt_mmc(void) 19587bc9f6aSratchov { 19687bc9f6aSratchov if (strcmp("off", optarg) == 0) 19787bc9f6aSratchov return 0; 19887bc9f6aSratchov if (strcmp("slave", optarg) == 0) 19987bc9f6aSratchov return 1; 20087bc9f6aSratchov errx(1, "%s: off/slave expected", optarg); 20187bc9f6aSratchov } 20287bc9f6aSratchov 20387bc9f6aSratchov int 20487bc9f6aSratchov opt_onoff(void) 20587bc9f6aSratchov { 20687bc9f6aSratchov if (strcmp("off", optarg) == 0) 20787bc9f6aSratchov return 0; 20887bc9f6aSratchov if (strcmp("on", optarg) == 0) 20987bc9f6aSratchov return 1; 21087bc9f6aSratchov errx(1, "%s: on/off expected", optarg); 21187bc9f6aSratchov } 21287bc9f6aSratchov 2131083120eSratchov int 2141083120eSratchov getword(char *word, char **str) 2151083120eSratchov { 2161083120eSratchov char *p = *str; 2171083120eSratchov 2181083120eSratchov for (;;) { 2191083120eSratchov if (*word == '\0') 2201083120eSratchov break; 2211083120eSratchov if (*word++ != *p++) 2221083120eSratchov return 0; 2231083120eSratchov } 2241083120eSratchov if (*p == ',' || *p == '\0') { 2251083120eSratchov *str = p; 2261083120eSratchov return 1; 2271083120eSratchov } 2281083120eSratchov return 0; 2291083120eSratchov } 2301083120eSratchov 23187bc9f6aSratchov unsigned int 23287bc9f6aSratchov opt_mode(void) 23387bc9f6aSratchov { 23487bc9f6aSratchov unsigned int mode = 0; 23587bc9f6aSratchov char *p = optarg; 23687bc9f6aSratchov 2371083120eSratchov for (;;) { 2381083120eSratchov if (getword("play", &p)) { 23987bc9f6aSratchov mode |= MODE_PLAY; 2401083120eSratchov } else if (getword("rec", &p)) { 24187bc9f6aSratchov mode |= MODE_REC; 2421083120eSratchov } else if (getword("mon", &p)) { 24387bc9f6aSratchov mode |= MODE_MON; 2441083120eSratchov } else if (getword("midi", &p)) { 24587bc9f6aSratchov mode |= MODE_MIDIMASK; 24687bc9f6aSratchov } else 24787bc9f6aSratchov errx(1, "%s: bad mode", optarg); 24887bc9f6aSratchov if (*p == '\0') 24987bc9f6aSratchov break; 2501083120eSratchov p++; 25187bc9f6aSratchov } 25287bc9f6aSratchov if (mode == 0) 25387bc9f6aSratchov errx(1, "empty mode"); 25487bc9f6aSratchov return mode; 25587bc9f6aSratchov } 25687bc9f6aSratchov 2571342ff69Sratchov /* 2581342ff69Sratchov * Open all devices. Possibly switch to the new devices if they have higher 2591342ff69Sratchov * priorities than the current ones. 2601342ff69Sratchov */ 2611342ff69Sratchov static void 2621342ff69Sratchov reopen_devs(void) 2631342ff69Sratchov { 2641342ff69Sratchov struct opt *o; 2651342ff69Sratchov struct dev *d, *a; 2661342ff69Sratchov 2671342ff69Sratchov for (o = opt_list; o != NULL; o = o->next) { 2681342ff69Sratchov 2691342ff69Sratchov /* skip unused logical devices and ones with fixed hardware */ 2701342ff69Sratchov if (o->refcnt == 0 || strcmp(o->name, o->dev->name) == 0) 2711342ff69Sratchov continue; 2721342ff69Sratchov 2731342ff69Sratchov /* circulate to the device with the highest prio */ 2741342ff69Sratchov a = o->alt_first; 2751342ff69Sratchov for (d = a; d->alt_next != a; d = d->alt_next) { 2761342ff69Sratchov if (d->num > o->alt_first->num) 2771342ff69Sratchov o->alt_first = d; 2781342ff69Sratchov } 2791342ff69Sratchov 2801342ff69Sratchov /* switch to the first working one, in pririty order */ 2811342ff69Sratchov d = o->alt_first; 2821342ff69Sratchov while (d != o->dev) { 2831342ff69Sratchov if (opt_setdev(o, d)) 2841342ff69Sratchov break; 2851342ff69Sratchov d = d->alt_next; 2861342ff69Sratchov } 2871342ff69Sratchov } 2881342ff69Sratchov 2891342ff69Sratchov /* 2901342ff69Sratchov * retry to open the remaining devices that are not used but need 2911342ff69Sratchov * to stay open (ex. '-a on') 2921342ff69Sratchov */ 2931342ff69Sratchov for (d = dev_list; d != NULL; d = d->next) { 2941342ff69Sratchov if (d->refcnt > 0 && d->pstate == DEV_CFG) 2951342ff69Sratchov dev_open(d); 2961342ff69Sratchov } 2971342ff69Sratchov } 2981342ff69Sratchov 2991342ff69Sratchov /* 3001342ff69Sratchov * For each port, open the alt with the highest priority and switch to it 3011342ff69Sratchov */ 3021342ff69Sratchov static void 3031342ff69Sratchov reopen_ports(void) 3041342ff69Sratchov { 3051342ff69Sratchov struct port *p, *a, *apri; 3061342ff69Sratchov int inuse; 3071342ff69Sratchov 3081342ff69Sratchov for (p = port_list; p != NULL; p = a->next) { 3091342ff69Sratchov 3101342ff69Sratchov /* skip unused ports */ 3111342ff69Sratchov inuse = 0; 3121342ff69Sratchov a = p; 3131342ff69Sratchov while (1) { 3141342ff69Sratchov if (midi_rxmask(a->midi) || a->midi->txmask) 3151342ff69Sratchov inuse = 1; 3161342ff69Sratchov if (a->alt_next == p) 3171342ff69Sratchov break; 3181342ff69Sratchov a = a->alt_next; 3191342ff69Sratchov } 3201342ff69Sratchov if (!inuse) 3211342ff69Sratchov continue; 3221342ff69Sratchov 3231342ff69Sratchov /* open the alt with the highest prio */ 3241342ff69Sratchov apri = port_alt_ref(p->num); 3251342ff69Sratchov 3261342ff69Sratchov /* switch to it */ 3271342ff69Sratchov a = p; 3281342ff69Sratchov while (1) { 3291342ff69Sratchov if (a != apri) { 3301342ff69Sratchov midi_migrate(a->midi, apri->midi); 3311342ff69Sratchov port_unref(a); 3321342ff69Sratchov } 3331342ff69Sratchov if (a->alt_next == p) 3341342ff69Sratchov break; 3351342ff69Sratchov a = a->alt_next; 3361342ff69Sratchov } 3371342ff69Sratchov } 3381342ff69Sratchov } 3391342ff69Sratchov 34087bc9f6aSratchov void 34187bc9f6aSratchov setsig(void) 34287bc9f6aSratchov { 34387bc9f6aSratchov struct sigaction sa; 34487bc9f6aSratchov 34587bc9f6aSratchov quit_flag = 0; 346731605d7Sratchov reopen_flag = 0; 34787bc9f6aSratchov sigfillset(&sa.sa_mask); 34887bc9f6aSratchov sa.sa_flags = SA_RESTART; 34987bc9f6aSratchov sa.sa_handler = sigint; 350f933f6d7Sratchov if (sigaction(SIGINT, &sa, NULL) == -1) 35187bc9f6aSratchov err(1, "sigaction(int) failed"); 352f933f6d7Sratchov if (sigaction(SIGTERM, &sa, NULL) == -1) 35387bc9f6aSratchov err(1, "sigaction(term) failed"); 354731605d7Sratchov sa.sa_handler = sighup; 355f933f6d7Sratchov if (sigaction(SIGHUP, &sa, NULL) == -1) 35687bc9f6aSratchov err(1, "sigaction(hup) failed"); 35787bc9f6aSratchov } 35887bc9f6aSratchov 35987bc9f6aSratchov void 36087bc9f6aSratchov unsetsig(void) 36187bc9f6aSratchov { 36287bc9f6aSratchov struct sigaction sa; 36387bc9f6aSratchov 36487bc9f6aSratchov sigfillset(&sa.sa_mask); 36587bc9f6aSratchov sa.sa_flags = SA_RESTART; 36687bc9f6aSratchov sa.sa_handler = SIG_DFL; 367f933f6d7Sratchov if (sigaction(SIGHUP, &sa, NULL) == -1) 368e5a0b362Sratchov err(1, "unsetsig(hup): sigaction failed"); 369f933f6d7Sratchov if (sigaction(SIGTERM, &sa, NULL) == -1) 370e5a0b362Sratchov err(1, "unsetsig(term): sigaction failed"); 371f933f6d7Sratchov if (sigaction(SIGINT, &sa, NULL) == -1) 372e5a0b362Sratchov err(1, "unsetsig(int): sigaction failed"); 37387bc9f6aSratchov } 37487bc9f6aSratchov 37587bc9f6aSratchov void 3764182f7f9Sratchov getbasepath(char *base) 37787bc9f6aSratchov { 37887bc9f6aSratchov uid_t uid; 37987bc9f6aSratchov struct stat sb; 3805b528006Sratchov mode_t mask, omask; 38187bc9f6aSratchov 38287bc9f6aSratchov uid = geteuid(); 38387bc9f6aSratchov if (uid == 0) { 38487bc9f6aSratchov mask = 022; 385dadd32d9Sratchov snprintf(base, SOCKPATH_MAX, SOCKPATH_DIR); 38687bc9f6aSratchov } else { 38787bc9f6aSratchov mask = 077; 388dadd32d9Sratchov snprintf(base, SOCKPATH_MAX, SOCKPATH_DIR "-%u", uid); 38987bc9f6aSratchov } 3905b528006Sratchov omask = umask(mask); 391f933f6d7Sratchov if (mkdir(base, 0777) == -1) { 39287bc9f6aSratchov if (errno != EEXIST) 39387bc9f6aSratchov err(1, "mkdir(\"%s\")", base); 39487bc9f6aSratchov } 3955b528006Sratchov umask(omask); 396f933f6d7Sratchov if (stat(base, &sb) == -1) 39787bc9f6aSratchov err(1, "stat(\"%s\")", base); 398c0625b96Sratchov if (!S_ISDIR(sb.st_mode)) 399c0625b96Sratchov errx(1, "%s is not a directory", base); 40087bc9f6aSratchov if (sb.st_uid != uid || (sb.st_mode & mask) != 0) 40187bc9f6aSratchov errx(1, "%s has wrong permissions", base); 40287bc9f6aSratchov } 40387bc9f6aSratchov 40487bc9f6aSratchov struct dev * 40587bc9f6aSratchov mkdev(char *path, struct aparams *par, 40687bc9f6aSratchov int mode, int bufsz, int round, int rate, int hold, int autovol) 40787bc9f6aSratchov { 40887bc9f6aSratchov struct dev *d; 40987bc9f6aSratchov 41087bc9f6aSratchov for (d = dev_list; d != NULL; d = d->next) { 41136355b88Sratchov if (strcmp(d->path, path) == 0) 41287bc9f6aSratchov return d; 41387bc9f6aSratchov } 41487bc9f6aSratchov if (!bufsz && !round) { 41587bc9f6aSratchov round = DEFAULT_ROUND; 41687bc9f6aSratchov bufsz = DEFAULT_BUFSZ; 41787bc9f6aSratchov } else if (!bufsz) { 41887bc9f6aSratchov bufsz = round * 2; 41987bc9f6aSratchov } else if (!round) 42087bc9f6aSratchov round = bufsz / 2; 42187bc9f6aSratchov d = dev_new(path, par, mode, bufsz, round, rate, hold, autovol); 42287bc9f6aSratchov if (d == NULL) 42387bc9f6aSratchov exit(1); 42487bc9f6aSratchov return d; 42587bc9f6aSratchov } 42687bc9f6aSratchov 427ea4468e3Sratchov struct port * 428ea4468e3Sratchov mkport(char *path, int hold) 429ea4468e3Sratchov { 430ea4468e3Sratchov struct port *c; 431ea4468e3Sratchov 432ea4468e3Sratchov for (c = port_list; c != NULL; c = c->next) { 43336355b88Sratchov if (strcmp(c->path, path) == 0) 434ea4468e3Sratchov return c; 435ea4468e3Sratchov } 436ea4468e3Sratchov c = port_new(path, MODE_MIDIMASK, hold); 437ea4468e3Sratchov if (c == NULL) 438ea4468e3Sratchov exit(1); 439ea4468e3Sratchov return c; 440ea4468e3Sratchov } 441ea4468e3Sratchov 44287bc9f6aSratchov struct opt * 44387bc9f6aSratchov mkopt(char *path, struct dev *d, 44487bc9f6aSratchov int pmin, int pmax, int rmin, int rmax, 44587bc9f6aSratchov int mode, int vol, int mmc, int dup) 44687bc9f6aSratchov { 44787bc9f6aSratchov struct opt *o; 44887bc9f6aSratchov 449db7ff504Sratchov o = opt_new(d, path, pmin, pmax, rmin, rmax, 45087bc9f6aSratchov MIDI_TO_ADATA(vol), mmc, dup, mode); 45187bc9f6aSratchov if (o == NULL) 452b9b781a0Sratchov return NULL; 4534182f7f9Sratchov dev_adjpar(d, o->mode, o->pmax, o->rmax); 45487bc9f6aSratchov return o; 45587bc9f6aSratchov } 45687bc9f6aSratchov 457d3baeec1Sratchov static void 458d3baeec1Sratchov dounveil(char *name, char *prefix, char *path_prefix) 459d3baeec1Sratchov { 460d3baeec1Sratchov size_t prefix_len; 461d3baeec1Sratchov char path[PATH_MAX]; 462d3baeec1Sratchov 463d3baeec1Sratchov prefix_len = strlen(prefix); 464d3baeec1Sratchov 465d3baeec1Sratchov if (strncmp(name, prefix, prefix_len) != 0) 466d3baeec1Sratchov errx(1, "%s: unsupported device or port format", name); 467d3baeec1Sratchov snprintf(path, sizeof(path), "%s%s", path_prefix, name + prefix_len); 468f933f6d7Sratchov if (unveil(path, "rw") == -1) 469bc5a8259Sbeck err(1, "unveil %s", path); 470d3baeec1Sratchov } 471d3baeec1Sratchov 47255f67083Sratchov static int 47355f67083Sratchov start_helper(int background) 47455f67083Sratchov { 475d3baeec1Sratchov struct dev *d; 476d3baeec1Sratchov struct port *p; 47755f67083Sratchov struct passwd *pw; 47855f67083Sratchov int s[2]; 47955f67083Sratchov pid_t pid; 48055f67083Sratchov 48155f67083Sratchov if (geteuid() == 0) { 48255f67083Sratchov if ((pw = getpwnam(SNDIO_PRIV_USER)) == NULL) 48355f67083Sratchov errx(1, "unknown user %s", SNDIO_PRIV_USER); 48455f67083Sratchov } else 48555f67083Sratchov pw = NULL; 486f933f6d7Sratchov if (socketpair(AF_UNIX, SOCK_STREAM, 0, s) == -1) { 48755f67083Sratchov perror("socketpair"); 48855f67083Sratchov return 0; 48955f67083Sratchov } 49055f67083Sratchov pid = fork(); 49155f67083Sratchov if (pid == -1) { 492*7b639200Sratchov perror("fork"); 49355f67083Sratchov return 0; 49455f67083Sratchov } 49555f67083Sratchov if (pid == 0) { 49655f67083Sratchov setproctitle("helper"); 49755f67083Sratchov close(s[0]); 49855f67083Sratchov if (fdpass_new(s[1], &helper_fileops) == NULL) 49955f67083Sratchov return 0; 50055f67083Sratchov if (background) { 50155f67083Sratchov log_flush(); 50255f67083Sratchov log_level = 0; 503f933f6d7Sratchov if (daemon(0, 0) == -1) 50455f67083Sratchov err(1, "daemon"); 50555f67083Sratchov } 50655f67083Sratchov if (pw != NULL) { 50755f67083Sratchov if (setgroups(1, &pw->pw_gid) || 50855f67083Sratchov setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) || 50955f67083Sratchov setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid)) 51055f67083Sratchov err(1, "cannot drop privileges"); 51155f67083Sratchov } 512731605d7Sratchov for (d = dev_list; d != NULL; d = d->next) { 51336355b88Sratchov dounveil(d->path, "rsnd/", "/dev/audio"); 51436355b88Sratchov dounveil(d->path, "rsnd/", "/dev/audioctl"); 515731605d7Sratchov } 516731605d7Sratchov for (p = port_list; p != NULL; p = p->next) { 51736355b88Sratchov dounveil(p->path, "rmidi/", "/dev/rmidi"); 518731605d7Sratchov } 519f933f6d7Sratchov if (pledge("stdio sendfd rpath wpath", NULL) == -1) 52055f67083Sratchov err(1, "pledge"); 52155f67083Sratchov while (file_poll()) 52255f67083Sratchov ; /* nothing */ 52355f67083Sratchov exit(0); 52455f67083Sratchov } else { 52555f67083Sratchov close(s[1]); 52655f67083Sratchov if (fdpass_new(s[0], &worker_fileops) == NULL) 52755f67083Sratchov return 0; 52855f67083Sratchov } 52955f67083Sratchov return 1; 53055f67083Sratchov } 53155f67083Sratchov 53255f67083Sratchov static void 53355f67083Sratchov stop_helper(void) 53455f67083Sratchov { 53555f67083Sratchov if (fdpass_peer) 53655f67083Sratchov fdpass_close(fdpass_peer); 53755f67083Sratchov } 53855f67083Sratchov 53987bc9f6aSratchov int 54087bc9f6aSratchov main(int argc, char **argv) 54187bc9f6aSratchov { 5424e3adc96Sratchov int c, i, background, unit; 54387bc9f6aSratchov int pmin, pmax, rmin, rmax; 544a78786c8Sratchov char base[SOCKPATH_MAX], path[SOCKPATH_MAX]; 54587bc9f6aSratchov unsigned int mode, dup, mmc, vol; 54687bc9f6aSratchov unsigned int hold, autovol, bufsz, round, rate; 54787bc9f6aSratchov const char *str; 54887bc9f6aSratchov struct aparams par; 54936355b88Sratchov struct opt *o; 55036355b88Sratchov struct dev *d, *dev_first, *dev_next; 55136355b88Sratchov struct port *p, *port_first, *port_next; 55287bc9f6aSratchov struct listen *l; 5530a32abc1Sratchov struct passwd *pw; 554a78786c8Sratchov struct tcpaddr { 555a78786c8Sratchov char *host; 556a78786c8Sratchov struct tcpaddr *next; 557a78786c8Sratchov } *tcpaddr_list, *ta; 55887bc9f6aSratchov 55987bc9f6aSratchov atexit(log_flush); 56087bc9f6aSratchov 56187bc9f6aSratchov /* 56287bc9f6aSratchov * global options defaults 56387bc9f6aSratchov */ 564344f1121Sjcs vol = 127; 56587bc9f6aSratchov dup = 1; 56687bc9f6aSratchov mmc = 0; 56787bc9f6aSratchov hold = 0; 568344f1121Sjcs autovol = 0; 56987bc9f6aSratchov bufsz = 0; 57087bc9f6aSratchov round = 0; 57187bc9f6aSratchov rate = DEFAULT_RATE; 57287bc9f6aSratchov unit = 0; 57387bc9f6aSratchov background = 1; 57487bc9f6aSratchov pmin = 0; 57587bc9f6aSratchov pmax = 1; 57687bc9f6aSratchov rmin = 0; 57787bc9f6aSratchov rmax = 1; 57819e766d9Sratchov par.bits = DEFAULT_BITS; 57919e766d9Sratchov par.bps = APARAMS_BPS(par.bits); 58019e766d9Sratchov par.le = ADATA_LE; 58119e766d9Sratchov par.sig = 1; 58219e766d9Sratchov par.msb = 0; 58387bc9f6aSratchov mode = MODE_PLAY | MODE_REC; 58436355b88Sratchov dev_first = dev_next = NULL; 58536355b88Sratchov port_first = port_next = NULL; 586a78786c8Sratchov tcpaddr_list = NULL; 5874e3adc96Sratchov d = NULL; 5884e3adc96Sratchov p = NULL; 58987bc9f6aSratchov 5900600d38bSratchov slot_array_init(); 5910600d38bSratchov 592731605d7Sratchov while ((c = getopt(argc, argv, 593731605d7Sratchov "a:b:c:C:de:F:f:j:L:m:Q:q:r:s:t:U:v:w:x:z:")) != -1) { 59487bc9f6aSratchov switch (c) { 59587bc9f6aSratchov case 'd': 59687bc9f6aSratchov log_level++; 59787bc9f6aSratchov background = 0; 59887bc9f6aSratchov break; 59987bc9f6aSratchov case 'U': 60087bc9f6aSratchov unit = strtonum(optarg, 0, 15, &str); 60187bc9f6aSratchov if (str) 60287bc9f6aSratchov errx(1, "%s: unit number is %s", optarg, str); 60387bc9f6aSratchov break; 60487bc9f6aSratchov case 'L': 605a78786c8Sratchov ta = xmalloc(sizeof(struct tcpaddr)); 606a78786c8Sratchov ta->host = optarg; 607a78786c8Sratchov ta->next = tcpaddr_list; 608a78786c8Sratchov tcpaddr_list = ta; 60987bc9f6aSratchov break; 61087bc9f6aSratchov case 'm': 61187bc9f6aSratchov mode = opt_mode(); 61287bc9f6aSratchov break; 61387bc9f6aSratchov case 'j': 61487bc9f6aSratchov dup = opt_onoff(); 61587bc9f6aSratchov break; 61687bc9f6aSratchov case 't': 61787bc9f6aSratchov mmc = opt_mmc(); 61887bc9f6aSratchov break; 61987bc9f6aSratchov case 'c': 62087bc9f6aSratchov opt_ch(&pmin, &pmax); 62187bc9f6aSratchov break; 62287bc9f6aSratchov case 'C': 62387bc9f6aSratchov opt_ch(&rmin, &rmax); 62487bc9f6aSratchov break; 62587bc9f6aSratchov case 'e': 62687bc9f6aSratchov opt_enc(&par); 62787bc9f6aSratchov break; 62887bc9f6aSratchov case 'r': 62987bc9f6aSratchov rate = strtonum(optarg, RATE_MIN, RATE_MAX, &str); 63087bc9f6aSratchov if (str) 63187bc9f6aSratchov errx(1, "%s: rate is %s", optarg, str); 63287bc9f6aSratchov break; 63387bc9f6aSratchov case 'v': 63487bc9f6aSratchov vol = strtonum(optarg, 0, MIDI_MAXCTL, &str); 63587bc9f6aSratchov if (str) 63687bc9f6aSratchov errx(1, "%s: volume is %s", optarg, str); 63787bc9f6aSratchov break; 63887bc9f6aSratchov case 's': 6394e3adc96Sratchov if (d == NULL) { 6404e3adc96Sratchov for (i = 0; default_devs[i] != NULL; i++) { 6414e3adc96Sratchov mkdev(default_devs[i], &par, 0, 6424e3adc96Sratchov bufsz, round, rate, 0, autovol); 6434e3adc96Sratchov } 6444e3adc96Sratchov d = dev_list; 64587bc9f6aSratchov } 646b9b781a0Sratchov if (mkopt(optarg, d, pmin, pmax, rmin, rmax, 647b9b781a0Sratchov mode, vol, mmc, dup) == NULL) 648b9b781a0Sratchov return 1; 64987bc9f6aSratchov break; 65087bc9f6aSratchov case 'q': 6514e3adc96Sratchov p = mkport(optarg, hold); 65236355b88Sratchov /* create new circulate list */ 65336355b88Sratchov port_first = port_next = p; 65487bc9f6aSratchov break; 655731605d7Sratchov case 'Q': 6564e3adc96Sratchov if (p == NULL) 657731605d7Sratchov errx(1, "-Q %s: no ports defined", optarg); 65836355b88Sratchov p = mkport(optarg, hold); 65936355b88Sratchov /* add to circulate list */ 66036355b88Sratchov p->alt_next = port_next; 66136355b88Sratchov port_first->alt_next = p; 66236355b88Sratchov port_next = p; 663731605d7Sratchov break; 66487bc9f6aSratchov case 'a': 66587bc9f6aSratchov hold = opt_onoff(); 66687bc9f6aSratchov break; 66787bc9f6aSratchov case 'w': 66887bc9f6aSratchov autovol = opt_onoff(); 66987bc9f6aSratchov break; 67087bc9f6aSratchov case 'b': 67187bc9f6aSratchov bufsz = strtonum(optarg, 1, RATE_MAX, &str); 67287bc9f6aSratchov if (str) 67387bc9f6aSratchov errx(1, "%s: buffer size is %s", optarg, str); 67487bc9f6aSratchov break; 67587bc9f6aSratchov case 'z': 67687bc9f6aSratchov round = strtonum(optarg, 1, SHRT_MAX, &str); 67787bc9f6aSratchov if (str) 67887bc9f6aSratchov errx(1, "%s: block size is %s", optarg, str); 67987bc9f6aSratchov break; 68087bc9f6aSratchov case 'f': 6814e3adc96Sratchov d = mkdev(optarg, &par, 0, bufsz, round, 68226308fb1Sratchov rate, hold, autovol); 68336355b88Sratchov /* create new circulate list */ 68436355b88Sratchov dev_first = dev_next = d; 68587bc9f6aSratchov break; 686731605d7Sratchov case 'F': 6874e3adc96Sratchov if (d == NULL) 688731605d7Sratchov errx(1, "-F %s: no devices defined", optarg); 68936355b88Sratchov d = mkdev(optarg, &par, 0, bufsz, round, 69036355b88Sratchov rate, hold, autovol); 69136355b88Sratchov /* add to circulate list */ 69236355b88Sratchov d->alt_next = dev_next; 69336355b88Sratchov dev_first->alt_next = d; 69436355b88Sratchov dev_next = d; 695731605d7Sratchov break; 69687bc9f6aSratchov default: 69787bc9f6aSratchov fputs(usagestr, stderr); 69887bc9f6aSratchov return 1; 69987bc9f6aSratchov } 70087bc9f6aSratchov } 70187bc9f6aSratchov argc -= optind; 70287bc9f6aSratchov argv += optind; 70387bc9f6aSratchov if (argc > 0) { 70487bc9f6aSratchov fputs(usagestr, stderr); 70587bc9f6aSratchov return 1; 70687bc9f6aSratchov } 707efc9ab16Sratchov if (port_list == NULL) { 708efc9ab16Sratchov for (i = 0; default_ports[i] != NULL; i++) 709efc9ab16Sratchov mkport(default_ports[i], 0); 710efc9ab16Sratchov } 7114e3adc96Sratchov if (dev_list == NULL) { 7124e3adc96Sratchov for (i = 0; default_devs[i] != NULL; i++) { 713d45714e8Sratchov mkdev(default_devs[i], &par, 0, 714d45714e8Sratchov bufsz, round, rate, 0, autovol); 715d45714e8Sratchov } 716d45714e8Sratchov } 71736355b88Sratchov 71836355b88Sratchov /* 71936355b88Sratchov * Add default sub-device (if none) backed by the last device 72036355b88Sratchov */ 72136355b88Sratchov o = opt_byname("default"); 72236355b88Sratchov if (o == NULL) { 72336355b88Sratchov o = mkopt("default", dev_list, pmin, pmax, rmin, rmax, 72436355b88Sratchov mode, vol, 0, dup); 72536355b88Sratchov if (o == NULL) 726b9b781a0Sratchov return 1; 72787bc9f6aSratchov } 7280a32abc1Sratchov 72936355b88Sratchov /* 73036355b88Sratchov * For each device create an anonymous sub-device using 73136355b88Sratchov * the "default" sub-device as template 73236355b88Sratchov */ 73336355b88Sratchov for (d = dev_list; d != NULL; d = d->next) { 73436355b88Sratchov if (opt_new(d, NULL, o->pmin, o->pmax, o->rmin, o->rmax, 73536355b88Sratchov o->maxweight, o->mtc != NULL, o->dup, o->mode) == NULL) 73636355b88Sratchov return 1; 73736355b88Sratchov dev_adjpar(d, o->mode, o->pmax, o->rmax); 73836355b88Sratchov } 73936355b88Sratchov 7400a32abc1Sratchov setsig(); 7410a32abc1Sratchov filelist_init(); 7420a32abc1Sratchov 74355f67083Sratchov if (!start_helper(background)) 74455f67083Sratchov return 1; 74555f67083Sratchov 74655f67083Sratchov if (geteuid() == 0) { 7473022334cSratchov if ((pw = getpwnam(SNDIO_USER)) == NULL) 7483022334cSratchov errx(1, "unknown user %s", SNDIO_USER); 74955f67083Sratchov } else 75055f67083Sratchov pw = NULL; 7514182f7f9Sratchov getbasepath(base); 75255f67083Sratchov snprintf(path, SOCKPATH_MAX, "%s/" SOCKPATH_FILE "%u", base, unit); 753a447b73fSratchov if (!listen_new_un(path)) 754a447b73fSratchov return 1; 755a78786c8Sratchov for (ta = tcpaddr_list; ta != NULL; ta = ta->next) { 756a78786c8Sratchov if (!listen_new_tcp(ta->host, AUCAT_PORT + unit)) 757a447b73fSratchov return 1; 758a447b73fSratchov } 759395f8c55Sratchov for (l = listen_list; l != NULL; l = l->next) { 760395f8c55Sratchov if (!listen_init(l)) 761395f8c55Sratchov return 1; 762395f8c55Sratchov } 76387bc9f6aSratchov midi_init(); 76487bc9f6aSratchov for (p = port_list; p != NULL; p = p->next) { 76587bc9f6aSratchov if (!port_init(p)) 76687bc9f6aSratchov return 1; 76787bc9f6aSratchov } 76887bc9f6aSratchov for (d = dev_list; d != NULL; d = d->next) { 76987bc9f6aSratchov if (!dev_init(d)) 77087bc9f6aSratchov return 1; 77187bc9f6aSratchov } 77236355b88Sratchov for (o = opt_list; o != NULL; o = o->next) 77336355b88Sratchov opt_init(o); 77487bc9f6aSratchov if (background) { 77587bc9f6aSratchov log_flush(); 77687bc9f6aSratchov log_level = 0; 777f933f6d7Sratchov if (daemon(0, 0) == -1) 77887bc9f6aSratchov err(1, "daemon"); 77987bc9f6aSratchov } 78055f67083Sratchov if (pw != NULL) { 781f933f6d7Sratchov if (setpriority(PRIO_PROCESS, 0, SNDIO_PRIO) == -1) 782395f8c55Sratchov err(1, "setpriority"); 783f933f6d7Sratchov if (chroot(pw->pw_dir) == -1 || chdir("/") == -1) 78455f67083Sratchov err(1, "cannot chroot to %s", pw->pw_dir); 785f933f6d7Sratchov if (setgroups(1, &pw->pw_gid) == -1 || 786f933f6d7Sratchov setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) == -1 || 787f933f6d7Sratchov setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid) == -1 ) 788395f8c55Sratchov err(1, "cannot drop privileges"); 789395f8c55Sratchov } 790a78786c8Sratchov if (tcpaddr_list) { 791f4078b0cSratchov if (pledge("stdio audio recvfd unix inet", NULL) == -1) 792f4078b0cSratchov err(1, "pledge"); 793f4078b0cSratchov } else { 794f4078b0cSratchov if (pledge("stdio audio recvfd unix", NULL) == -1) 795f4078b0cSratchov err(1, "pledge"); 796f4078b0cSratchov } 7971342ff69Sratchov 79887bc9f6aSratchov for (;;) { 79987bc9f6aSratchov if (quit_flag) 80087bc9f6aSratchov break; 801731605d7Sratchov if (reopen_flag) { 802731605d7Sratchov reopen_flag = 0; 8031342ff69Sratchov reopen_devs(); 8041342ff69Sratchov reopen_ports(); 805731605d7Sratchov } 806395f8c55Sratchov if (!fdpass_peer) 807395f8c55Sratchov break; 80887bc9f6aSratchov if (!file_poll()) 80987bc9f6aSratchov break; 81087bc9f6aSratchov } 81155f67083Sratchov stop_helper(); 81287bc9f6aSratchov while (listen_list != NULL) 81387bc9f6aSratchov listen_close(listen_list); 81487bc9f6aSratchov while (sock_list != NULL) 81587bc9f6aSratchov sock_close(sock_list); 81636355b88Sratchov for (o = opt_list; o != NULL; o = o->next) 81736355b88Sratchov opt_done(o); 81887bc9f6aSratchov for (d = dev_list; d != NULL; d = d->next) 81987bc9f6aSratchov dev_done(d); 82087bc9f6aSratchov for (p = port_list; p != NULL; p = p->next) 82187bc9f6aSratchov port_done(p); 82287bc9f6aSratchov while (file_poll()) 82387bc9f6aSratchov ; /* nothing */ 824395f8c55Sratchov midi_done(); 82555f67083Sratchov 8265684d550Sratchov while (opt_list) 8275684d550Sratchov opt_del(opt_list); 82887bc9f6aSratchov while (dev_list) 82987bc9f6aSratchov dev_del(dev_list); 83087bc9f6aSratchov while (port_list) 83187bc9f6aSratchov port_del(port_list); 832a78786c8Sratchov while (tcpaddr_list) { 833a78786c8Sratchov ta = tcpaddr_list; 834a78786c8Sratchov tcpaddr_list = ta->next; 835a78786c8Sratchov xfree(ta); 836a78786c8Sratchov } 8370a32abc1Sratchov filelist_done(); 83887bc9f6aSratchov unsetsig(); 83987bc9f6aSratchov return 0; 84087bc9f6aSratchov } 841