xref: /openbsd-src/usr.bin/sndiod/sndiod.c (revision 7b6392009e6e5a7f8e494c162a4d259ea5e13a62)
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