Edit

IABSD.fr/src/usr.bin/sndiod/sndiod.c

Branch :

  • Show log

    Commit

  • Author : ratchov
    Date : 2026-05-26 14:50:52
    Hash : 05c105ac
    Message : sndiod: Make the device sample rate and buffer sizes global

  • usr.bin/sndiod/sndiod.c
  • /*	$OpenBSD: sndiod.c,v 1.55 2026/05/26 14:50:52 ratchov Exp $	*/
    /*
     * Copyright (c) 2008-2012 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/stat.h>
    #include <sys/types.h>
    #include <sys/resource.h>
    #include <sys/socket.h>
    
    #include <err.h>
    #include <errno.h>
    #include <fcntl.h>
    #include <grp.h>
    #include <limits.h>
    #include <pwd.h>
    #include <signal.h>
    #include <sndio.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <unistd.h>
    
    #include "amsg.h"
    #include "defs.h"
    #include "dev.h"
    #include "fdpass.h"
    #include "file.h"
    #include "listen.h"
    #include "midi.h"
    #include "opt.h"
    #include "sock.h"
    #include "utils.h"
    
    /*
     * unprivileged user name
     */
    #ifndef SNDIO_USER
    #define SNDIO_USER	"_sndio"
    #endif
    
    /*
     * privileged user name
     */
    #ifndef SNDIO_PRIV_USER
    #define SNDIO_PRIV_USER	"_sndiop"
    #endif
    
    /*
     * priority when run as root
     */
    #ifndef SNDIO_PRIO
    #define SNDIO_PRIO	(-20)
    #endif
    
    /*
     * sample rate if no ``-r'' is used
     */
    #ifndef DEFAULT_RATE
    #define DEFAULT_RATE	48000
    #endif
    
    /*
     * block size if neither ``-z'' nor ``-b'' is used
     */
    #ifndef DEFAULT_ROUND
    #define DEFAULT_ROUND	480
    #endif
    
    /*
     * buffer size if neither ``-z'' nor ``-b'' is used
     */
    #ifndef DEFAULT_BUFSZ
    #define DEFAULT_BUFSZ	7680
    #endif
    
    /*
     * default device precision
     */
    #ifndef DEFAULT_BITS
    #define DEFAULT_BITS	16
    #endif
    
    void sigint(int);
    void sighup(int);
    void opt_ch(int *, int *);
    void opt_enc(struct aparams *);
    int opt_mmc(void);
    int opt_onoff(void);
    int getword(char *, char **);
    unsigned int opt_mode(void);
    void getbasepath(char *);
    void setsig(void);
    void unsetsig(void);
    struct dev *mkdev(char *, struct aparams *, int, int);
    struct port *mkport(char *, int);
    struct opt *mkopt(char *, struct dev *, struct opt_alt *,
        int, int, int, int, int, int, int, int);
    
    unsigned int log_level = 0;
    volatile sig_atomic_t quit_flag = 0, reopen_flag = 0;
    
    char usagestr[] = "usage: sndiod [-d] [-a flag] [-b nframes] "
        "[-C min:max] [-c min:max]\n\t"
        "[-e enc] [-F device] [-f device] [-j flag] [-L addr] [-m mode]\n\t"
        "[-Q port] [-q port] [-r rate] [-s name] [-t mode] [-U unit]\n\t"
        "[-v volume] [-w flag] [-z nframes]\n";
    
    /*
     * default audio devices
     */
    static char *default_devs[] = {
    	"rsnd/0", "rsnd/1", "rsnd/2", "rsnd/3",
    	NULL
    };
    
    /*
     * default MIDI ports
     */
    static char *default_ports[] = {
    	"rmidi/0", "rmidi/1", "rmidi/2", "rmidi/3",
    	"rmidi/4", "rmidi/5", "rmidi/6", "rmidi/7",
    	NULL
    };
    
    /*
     * 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;
    }
    
    /*
     * SIGHUP handler, it raises the reopen flag, which requests devices
     * to be reopened.
     */
    void
    sighup(int s)
    {
    	reopen_flag = 1;
    }
    
    void
    opt_ch(int *rcmin, int *rcmax)
    {
    	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;
    	*rcmin = cmin;
    	*rcmax = 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_mmc(void)
    {
    	if (strcmp("off", optarg) == 0)
    		return 0;
    	if (strcmp("slave", optarg) == 0)
    		return 1;
    	errx(1, "%s: off/slave expected", optarg);
    }
    
    int
    opt_onoff(void)
    {
    	if (strcmp("off", optarg) == 0)
    		return 0;
    	if (strcmp("on", optarg) == 0)
    		return 1;
    	errx(1, "%s: on/off expected", optarg);
    }
    
    int
    getword(char *word, char **str)
    {
    	char *p = *str;
    
    	for (;;) {
    		if (*word == '\0')
    			break;
    		if (*word++ != *p++)
    			return 0;
    	}
    	if (*p == ',' || *p == '\0') {
    		*str = p;
    		return 1;
    	}
    	return 0;
    }
    
    unsigned int
    opt_mode(void)
    {
    	unsigned int mode = 0;
    	char *p = optarg;
    
    	for (;;) {
    		if (getword("play", &p)) {
    			mode |= MODE_PLAY;
    		} else if (getword("rec", &p)) {
    			mode |= MODE_REC;
    		} else if (getword("mon", &p)) {
    			mode |= MODE_MON;
    		} else if (getword("midi", &p)) {
    			mode |= MODE_MIDIMASK;
    		} else
    			errx(1, "%s: bad mode", optarg);
    		if (*p == '\0')
    			break;
    		p++;
    	}
    	if (mode == 0)
    		errx(1, "empty mode");
    	return mode;
    }
    
    /*
     * Open all devices. Possibly switch to the new devices if they have higher
     * priorities than the current ones.
     */
    static void
    reopen_devs(void)
    {
    	struct opt *o;
    	struct opt_alt *a;
    	struct dev *d;
    
    	for (o = opt_list; o != NULL; o = o->next) {
    
    		/* skip unused logical devices and ones with fixed hardware */
    		if (o->refcnt == 0 || strcmp(o->name, o->dev->name) == 0)
    			continue;
    
    		/* switch to the first working one, in pririty order */
    		for (a = o->alt_list; a->dev != NULL; a = a->next) {
    			if (opt_setdev(o, a->dev))
    				break;
    		}
    	}
    
    	/*
    	 * retry to open the remaining devices that are not used but need
    	 * to stay open (ex. '-a on')
    	 */
    	for (d = dev_list; d != NULL; d = d->next) {
    		if (d->refcnt > 0 && d->pstate == DEV_CFG)
    			dev_open(d);
    	}
    }
    
    /*
     * For each port, open the alt with the highest priority and switch to it
     */
    static void
    reopen_ports(void)
    {
    	struct port *p, *a, *apri;
    	int inuse;
    
    	for (p = port_list; p != NULL; p = a->next) {
    
    		/* skip unused ports */
    		inuse = 0;
    		a = p;
    		while (1) {
    			if (midi_rxmask(a->midi) || a->midi->txmask)
    				inuse = 1;
    			if (a->alt_next == p)
    				break;
    			a = a->alt_next;
    		}
    		if (!inuse)
    			continue;
    
    		/* open the alt with the highest prio */
    		apri = port_alt_ref(p->num);
    
    		/* switch to it */
    		a = p;
    		while (1) {
    			if (a != apri) {
    				midi_migrate(a->midi, apri->midi);
    				port_unref(a);
    			}
    			if (a->alt_next == p)
    				break;
    			a = a->alt_next;
    		}
    	}
    }
    
    void
    setsig(void)
    {
    	struct sigaction sa;
    
    	quit_flag = 0;
    	reopen_flag = 0;
    	sigfillset(&sa.sa_mask);
    	sa.sa_flags = SA_RESTART;
    	sa.sa_handler = sigint;
    	if (sigaction(SIGINT, &sa, NULL) == -1)
    		err(1, "sigaction(int) failed");
    	if (sigaction(SIGTERM, &sa, NULL) == -1)
    		err(1, "sigaction(term) failed");
    	sa.sa_handler = sighup;
    	if (sigaction(SIGHUP, &sa, NULL) == -1)
    		err(1, "sigaction(hup) failed");
    }
    
    void
    unsetsig(void)
    {
    	struct sigaction sa;
    
    	sigfillset(&sa.sa_mask);
    	sa.sa_flags = SA_RESTART;
    	sa.sa_handler = SIG_DFL;
    	if (sigaction(SIGHUP, &sa, NULL) == -1)
    		err(1, "unsetsig(hup): sigaction failed");
    	if (sigaction(SIGTERM, &sa, NULL) == -1)
    		err(1, "unsetsig(term): sigaction failed");
    	if (sigaction(SIGINT, &sa, NULL) == -1)
    		err(1, "unsetsig(int): sigaction failed");
    }
    
    struct dev *
    mkdev(char *path, struct aparams *par, int hold, int autovol)
    {
    	struct dev *d;
    
    	for (d = dev_list; d != NULL; d = d->next) {
    		if (strcmp(d->path, path) == 0)
    			return d;
    	}
    	d = dev_new(path, par, hold, autovol);
    	if (d == NULL)
    		exit(1);
    	return d;
    }
    
    struct port *
    mkport(char *path, int hold)
    {
    	struct port *c;
    
    	for (c = port_list; c != NULL; c = c->next) {
    		if (strcmp(c->path, path) == 0)
    			return c;
    	}
    	c = port_new(path, MODE_MIDIMASK, hold);
    	if (c == NULL)
    		exit(1);
    	return c;
    }
    
    struct opt *
    mkopt(char *path, struct dev *d, struct opt_alt *alt_list,
        int pmin, int pmax, int rmin, int rmax,
        int mode, int vol, int mmc, int dup)
    {
    	struct opt *o;
    	struct opt_alt *a;
    
    	o = opt_new(d, path, pmin, pmax, rmin, rmax,
    	    MIDI_TO_ADATA(vol), mmc, dup, mode);
    	if (o == NULL)
    		return NULL;
    	dev_adjpar(d, o->pmax, o->rmax);
    	for (a = alt_list; a != NULL; a = a->next)
    		opt_setalt(o, a->dev);
    	return o;
    }
    
    static void
    dounveil(char *name, char *prefix, char *path_prefix)
    {
    	size_t prefix_len;
    	char path[PATH_MAX];
    
    	prefix_len = strlen(prefix);
    
    	if (strncmp(name, prefix, prefix_len) != 0)
    		errx(1, "%s: unsupported device or port format", name);
    	snprintf(path, sizeof(path), "%s%s", path_prefix, name + prefix_len);
    	if (unveil(path, "rw") == -1)
    		err(1, "unveil %s", path);
    }
    
    static int
    start_helper(int background)
    {
    	struct dev *d;
    	struct port *p;
    	struct passwd *pw;
    	int s[2];
    	pid_t pid;
    
    	if (geteuid() == 0) {
    		if ((pw = getpwnam(SNDIO_PRIV_USER)) == NULL)
    			errx(1, "unknown user %s", SNDIO_PRIV_USER);
    	} else
    		pw = NULL;
    	if (socketpair(AF_UNIX, SOCK_STREAM, 0, s) == -1) {
    		perror("socketpair");
    		return 0;
    	}
    	pid = fork();
    	if (pid	== -1) {
    		perror("fork");
    		return 0;
    	}
    	if (pid == 0) {
    		setproctitle("helper");
    		close(s[0]);
    		if (fdpass_new(s[1], &helper_fileops) == NULL)
    			return 0;
    		if (background) {
    			log_flush();
    			log_level = 0;
    			if (daemon(0, 0) == -1)
    				err(1, "daemon");
    		}
    		if (pw != NULL) {
    			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");
    		}
    		for (d = dev_list; d != NULL; d = d->next) {
    			dounveil(d->path, "rsnd/", "/dev/audio");
    			dounveil(d->path, "rsnd/", "/dev/audioctl");
    		}
    		for (p = port_list; p != NULL; p = p->next) {
    			dounveil(p->path, "rmidi/", "/dev/rmidi");
    		}
    		if (pledge("stdio sendfd rpath wpath", NULL) == -1)
    			err(1, "pledge");
    		while (file_poll())
    			; /* nothing */
    		exit(0);
    	} else {
    		close(s[1]);
    		if (fdpass_new(s[0], &worker_fileops) == NULL)
    			return 0;
    	}
    	return 1;
    }
    
    static void
    stop_helper(void)
    {
    	if (fdpass_peer)
    		fdpass_close(fdpass_peer);
    }
    
    int
    main(int argc, char **argv)
    {
    	int c, i, background, unit;
    	int pmin, pmax, rmin, rmax;
    	unsigned int mode, dup, mmc, vol;
    	unsigned int hold, autovol;
    	const char *str;
    	struct aparams par;
    	struct opt *o;
    	struct dev *d;
    	struct opt_alt *a, **pa, *alt_list;
    	struct port *p, *port_first, *port_next;
    	struct listen *l;
    	struct passwd *pw;
    	struct tcpaddr {
    		char *host;
    		struct tcpaddr *next;
    	} *tcpaddr_list, *ta;
    
    	atexit(log_flush);
    
    	/*
    	 * global options defaults
    	 */
    	dev_rate = DEFAULT_RATE;
    	vol = 127;
    	dup = 1;
    	mmc = 0;
    	hold = 0;
    	autovol = 0;
    	unit = 0;
    	background = 1;
    	pmin = 0;
    	pmax = 1;
    	rmin = 0;
    	rmax = 1;
    	par.bits = DEFAULT_BITS;
    	par.bps = APARAMS_BPS(par.bits);
    	par.le = ADATA_LE;
    	par.sig = 1;
    	par.msb = 0;
    	mode = MODE_PLAY | MODE_REC;
    	alt_list = NULL;
    	port_first = port_next = NULL;
    	tcpaddr_list = NULL;
    	d = NULL;
    	p = NULL;
    
    	while ((c = getopt(argc, argv,
    	    "a:b:c:C:de:F:f:j:L:m:Q:q:r:s:t:U:v:w:x:z:")) != -1) {
    		switch (c) {
    		case 'd':
    			log_level++;
    			background = 0;
    			break;
    		case 'U':
    			unit = strtonum(optarg, 0, 15, &str);
    			if (str)
    				errx(1, "%s: unit number is %s", optarg, str);
    			break;
    		case 'L':
    			ta = xmalloc(sizeof(struct tcpaddr));
    			ta->host = optarg;
    			ta->next = tcpaddr_list;
    			tcpaddr_list = ta;
    			break;
    		case 'm':
    			mode = opt_mode();
    			break;
    		case 'j':
    			dup = opt_onoff();
    			break;
    		case 't':
    			mmc = opt_mmc();
    			break;
    		case 'c':
    			opt_ch(&pmin, &pmax);
    			break;
    		case 'C':
    			opt_ch(&rmin, &rmax);
    			break;
    		case 'e':
    			opt_enc(&par);
    			break;
    		case 'r':
    			dev_rate = strtonum(optarg, RATE_MIN, RATE_MAX, &str);
    			if (str)
    				errx(1, "%s: rate is %s", optarg, str);
    			break;
    		case 'v':
    			vol = strtonum(optarg, 0, MIDI_MAXCTL, &str);
    			if (str)
    				errx(1, "%s: volume is %s", optarg, str);
    			break;
    		case 's':
    			if (d == NULL) {
    				for (i = 0; default_devs[i] != NULL; i++) {
    					mkdev(default_devs[i], &par, 0, autovol);
    				}
    				d = dev_list;
    			}
    			if (mkopt(optarg, d, alt_list, pmin, pmax, rmin, rmax,
    				mode, vol, mmc, dup) == NULL)
    				return 1;
    			break;
    		case 'q':
    			p = mkport(optarg, hold);
    			/* create new circulate list */
    			port_first = port_next = p;
    			break;
    		case 'Q':
    			if (p == NULL)
    				errx(1, "-Q %s: no ports defined", optarg);
    			p = mkport(optarg, hold);
    			/* add to circulate list */
    			p->alt_next = port_next;
    			port_first->alt_next = p;
    			port_next = p;
    			break;
    		case 'a':
    			hold = opt_onoff();
    			break;
    		case 'w':
    			autovol = opt_onoff();
    			break;
    		case 'b':
    			dev_bufsz = strtonum(optarg, 1, RATE_MAX, &str);
    			if (str)
    				errx(1, "%s: buffer size is %s", optarg, str);
    			break;
    		case 'z':
    			dev_round = strtonum(optarg, 1, SHRT_MAX, &str);
    			if (str)
    				errx(1, "%s: block size is %s", optarg, str);
    			break;
    		case 'f':
    			d = mkdev(optarg, &par, hold, autovol);
    			while ((a = alt_list) != NULL) {
    				alt_list = a->next;
    				xfree(a);
    			}
    			break;
    		case 'F':
    			if (d == NULL)
    				errx(1, "-F %s: no devices defined", optarg);
    			a = xmalloc(sizeof(struct opt_alt));
    			a->dev = mkdev(optarg, &par, hold, autovol);
    			for (pa = &alt_list; *pa != NULL; pa = &(*pa)->next)
    				;
    			a->next = NULL;
    			*pa = a;
    			break;
    		default:
    			fputs(usagestr, stderr);
    			return 1;
    		}
    	}
    	argc -= optind;
    	argv += optind;
    	if (argc > 0) {
    		fputs(usagestr, stderr);
    		return 1;
    	}
    
    	if (!dev_bufsz && !dev_round) {
    		dev_round = DEFAULT_ROUND;
    		dev_bufsz = DEFAULT_BUFSZ;
    	} else if (!dev_bufsz) {
    		dev_bufsz = dev_round * 2;
    	} else if (!dev_round) {
    		dev_round = dev_bufsz / 2;
    	}
    
    	if (port_list == NULL) {
    		for (i = 0; default_ports[i] != NULL; i++)
    			mkport(default_ports[i], 0);
    	}
    	if (dev_list == NULL) {
    		for (i = 0; default_devs[i] != NULL; i++) {
    			mkdev(default_devs[i], &par, 0, autovol);
    		}
    	}
    
    	/*
    	 * Add default sub-device (if none) backed by the last device
    	 */
    	o = opt_byname("default");
    	if (o == NULL) {
    		o = mkopt("default", dev_list, alt_list, pmin, pmax, rmin, rmax,
    		    mode, vol, 0, dup);
    		if (o == NULL)
    			return 1;
    	}
    
    	/*
    	 * For each device create an anonymous sub-device using
    	 * the "default" sub-device as template
    	 */
    	for (d = dev_list; d != NULL; d = d->next) {
    		if (opt_new(d, NULL, o->pmin, o->pmax, o->rmin, o->rmax,
    			o->maxweight, o->mtc != NULL, o->dup, o->mode) == NULL)
    			return 1;
    		dev_adjpar(d, o->pmax, o->rmax);
    	}
    
    	while ((a = alt_list) != NULL) {
    		alt_list = a->next;
    		xfree(a);
    	}
    
    	setsig();
    	filelist_init();
    
    	if (!start_helper(background))
    		return 1;
    
    	if (geteuid() == 0) {
    		if ((pw = getpwnam(SNDIO_USER)) == NULL)
    			errx(1, "unknown user %s", SNDIO_USER);
    	} else
    		pw = NULL;
    	if (!listen_new_un(unit))
    		return 1;
    	for (ta = tcpaddr_list; ta != NULL; ta = ta->next) {
    		if (!listen_new_tcp(ta->host, AUCAT_PORT + unit))
    			return 1;
    	}
    	for (l = listen_list; l != NULL; l = l->next) {
    		if (!listen_init(l))
    			return 1;
    	}
    	midi_init();
    	for (p = port_list; p != NULL; p = p->next) {
    		if (!port_init(p))
    			return 1;
    	}
    	for (d = dev_list; d != NULL; d = d->next) {
    		if (!dev_init(d))
    			return 1;
    	}
    	for (o = opt_list; o != NULL; o = o->next)
    		opt_init(o);
    	if (background) {
    		log_flush();
    		log_level = 0;
    		if (daemon(0, 0) == -1)
    			err(1, "daemon");
    	}
    	if (pw != NULL) {
    		if (setpriority(PRIO_PROCESS, 0, SNDIO_PRIO) == -1)
    			err(1, "setpriority");
    		if (chroot(pw->pw_dir) == -1 || chdir("/") == -1)
    			err(1, "cannot chroot to %s", pw->pw_dir);
    		if (setgroups(1, &pw->pw_gid) == -1 ||
    		    setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) == -1 ||
    		    setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid) == -1 )
    			err(1, "cannot drop privileges");
    	}
    	if (tcpaddr_list) {
    		if (pledge("stdio audio recvfd unix inet", NULL) == -1)
    			err(1, "pledge");
    	} else {
    		if (pledge("stdio audio recvfd unix", NULL) == -1)
    			err(1, "pledge");
    	}
    
    	for (;;) {
    		if (quit_flag)
    			break;
    		if (reopen_flag) {
    			reopen_flag = 0;
    			reopen_devs();
    			reopen_ports();
    		}
    		if (!fdpass_peer)
    			break;
    		if (!file_poll())
    			break;
    	}
    	stop_helper();
    	while (listen_list != NULL)
    		listen_close(listen_list);
    	while (sock_list != NULL)
    		sock_close(sock_list);
    	for (o = opt_list; o != NULL; o = o->next)
    		opt_done(o);
    	for (d = dev_list; d != NULL; d = d->next)
    		dev_done(d);
    	for (p = port_list; p != NULL; p = p->next)
    		port_done(p);
    	while (file_poll())
    		; /* nothing */
    	midi_done();
    
    	while (opt_list)
    		opt_del(opt_list);
    	while (dev_list)
    		dev_del(dev_list);
    	while (port_list)
    		port_del(port_list);
    	while (tcpaddr_list) {
    		ta = tcpaddr_list;
    		tcpaddr_list = ta->next;
    		xfree(ta);
    	}
    	filelist_done();
    	unsetsig();
    	return 0;
    }