1/*	$OpenBSD: sndioctl.c,v 1.21 2024/05/24 15:10:27 ratchov Exp $	*/
2/*
3 * Copyright (c) 2014-2020 Alexandre Ratchov <alex@caoua.org>
4 *
5 * Permission to use, copy, modify, and distribute this software for any
6 * purpose with or without fee is hereby granted, provided that the above
7 * copyright notice and this permission notice appear in all copies.
8 *
9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16 */
17#include <errno.h>
18#include <poll.h>
19#include <sndio.h>
20#include <stdlib.h>
21#include <stdio.h>
22#include <string.h>
23#include <unistd.h>
24
25struct info {
26	struct info *next;
27	struct sioctl_desc desc;
28	unsigned ctladdr;
29#define MODE_IGNORE	0	/* ignore this value */
30#define MODE_PRINT	1	/* print-only, don't change value */
31#define MODE_SET	2	/* set to newval value */
32#define MODE_ADD	3	/* increase current value by newval */
33#define MODE_SUB	4	/* decrease current value by newval */
34#define MODE_TOGGLE	5	/* toggle current value */
35	unsigned mode;
36	int curval, newval;
37};
38
39int cmpdesc(struct sioctl_desc *, struct sioctl_desc *);
40int isdiag(struct info *);
41struct info *vecent(struct info *, char *, int);
42struct info *nextfunc(struct info *);
43struct info *nextpar(struct info *);
44struct info *firstent(struct info *, char *);
45struct info *nextent(struct info *, int);
46int matchpar(struct info *, char *, int);
47int matchent(struct info *, char *, int);
48int ismono(struct info *);
49void print_node(struct sioctl_node *, int);
50void print_display(struct info *);
51void print_desc(struct info *, int);
52void print_num(struct info *);
53void print_ent(struct info *, char *);
54void print_val(struct info *, int);
55void print_par(struct info *, int);
56int parse_name(char **, char *);
57int parse_unit(char **, int *);
58int parse_val(char **, float *);
59int parse_node(char **, char *, int *);
60int parse_modeval(char **, int *, float *);
61void dump(void);
62int cmd(char *);
63void commit(void);
64void list(void);
65void ondesc(void *, struct sioctl_desc *, int);
66void onctl(void *, unsigned, unsigned);
67
68struct sioctl_hdl *hdl;
69struct info *infolist;
70int i_flag = 0, v_flag = 0, m_flag = 0, n_flag = 0, q_flag = 0;
71
72static inline int
73isname(int c)
74{
75	return (c == '_') ||
76	    (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') ||
77	    (c >= '0' && c <= '9');
78}
79
80static int
81ftoi(float f)
82{
83	return f + 0.5;
84}
85
86/*
87 * compare two sioctl_desc structures, used to sort infolist
88 */
89int
90cmpdesc(struct sioctl_desc *d1, struct sioctl_desc *d2)
91{
92	int res;
93
94	res = strcmp(d1->group, d2->group);
95	if (res != 0)
96		return res;
97	res = strcmp(d1->node0.name, d2->node0.name);
98	if (res != 0)
99		return res;
100	res = d1->type - d2->type;
101	if (res != 0)
102		return res;
103	res = strcmp(d1->func, d2->func);
104	if (res != 0)
105		return res;
106	res = d1->node0.unit - d2->node0.unit;
107	if (d1->type == SIOCTL_SEL ||
108	    d1->type == SIOCTL_VEC ||
109	    d1->type == SIOCTL_LIST) {
110		if (res != 0)
111			return res;
112		res = strcmp(d1->node1.name, d2->node1.name);
113		if (res != 0)
114			return res;
115		res = d1->node1.unit - d2->node1.unit;
116	}
117	return res;
118}
119
120/*
121 * return true of the vector entry is diagonal
122 */
123int
124isdiag(struct info *e)
125{
126	if (e->desc.node0.unit < 0 || e->desc.node1.unit < 0)
127		return 1;
128	return e->desc.node1.unit == e->desc.node0.unit;
129}
130
131/*
132 * find the selector or vector entry with the given name and channels
133 */
134struct info *
135vecent(struct info *i, char *vstr, int vunit)
136{
137	while (i != NULL) {
138		if ((strcmp(i->desc.node1.name, vstr) == 0) &&
139		    (vunit < 0 || i->desc.node1.unit == vunit))
140			break;
141		i = i->next;
142	}
143	return i;
144}
145
146/*
147 * skip all parameters with the same group, name, and func
148 */
149struct info *
150nextfunc(struct info *i)
151{
152	char *str, *group, *func;
153
154	group = i->desc.group;
155	func = i->desc.func;
156	str = i->desc.node0.name;
157	for (i = i->next; i != NULL; i = i->next) {
158		if (strcmp(i->desc.group, group) != 0 ||
159		    strcmp(i->desc.node0.name, str) != 0 ||
160		    strcmp(i->desc.func, func) != 0)
161			return i;
162	}
163	return NULL;
164}
165
166/*
167 * find the next parameter with the same group, name, func
168 */
169struct info *
170nextpar(struct info *i)
171{
172	char *str, *group, *func;
173	int unit;
174
175	group = i->desc.group;
176	func = i->desc.func;
177	str = i->desc.node0.name;
178	unit = i->desc.node0.unit;
179	for (i = i->next; i != NULL; i = i->next) {
180		if (strcmp(i->desc.group, group) != 0 ||
181		    strcmp(i->desc.node0.name, str) != 0 ||
182		    strcmp(i->desc.func, func) != 0)
183			break;
184		/* XXX: need to check for -1 ? */
185		if (i->desc.node0.unit != unit)
186			return i;
187	}
188	return NULL;
189}
190
191/*
192 * return the first vector entry with the given name
193 */
194struct info *
195firstent(struct info *g, char *vstr)
196{
197	char *astr, *group, *func;
198	struct info *i;
199
200	group = g->desc.group;
201	astr = g->desc.node0.name;
202	func = g->desc.func;
203	for (i = g; i != NULL; i = i->next) {
204		if (strcmp(i->desc.group, group) != 0 ||
205		    strcmp(i->desc.node0.name, astr) != 0 ||
206		    strcmp(i->desc.func, func) != 0)
207			break;
208		if (!isdiag(i))
209			continue;
210		if (strcmp(i->desc.node1.name, vstr) == 0)
211			return i;
212	}
213	return NULL;
214}
215
216/*
217 * find the next entry of the given vector, if the mono flag
218 * is set then the whole group is searched and off-diagonal entries are
219 * skipped
220 */
221struct info *
222nextent(struct info *i, int mono)
223{
224	char *str, *group, *func;
225	int unit;
226
227	group = i->desc.group;
228	func = i->desc.func;
229	str = i->desc.node0.name;
230	unit = i->desc.node0.unit;
231	for (i = i->next; i != NULL; i = i->next) {
232		if (strcmp(i->desc.group, group) != 0 ||
233		    strcmp(i->desc.node0.name, str) != 0 ||
234		    strcmp(i->desc.func, func) != 0)
235			return NULL;
236		if (mono)
237			return i;
238		if (i->desc.node0.unit == unit)
239			return i;
240	}
241	return NULL;
242}
243
244/*
245 * return true if parameter matches the given name and channel
246 */
247int
248matchpar(struct info *i, char *astr, int aunit)
249{
250	if (strcmp(i->desc.node0.name, astr) != 0)
251		return 0;
252	if (aunit < 0)
253		return 1;
254	else if (i->desc.node0.unit < 0) {
255		fprintf(stderr, "unit used for parameter with no unit\n");
256		exit(1);
257	}
258	return i->desc.node0.unit == aunit;
259}
260
261/*
262 * return true if selector or vector entry matches the given name and
263 * channel range
264 */
265int
266matchent(struct info *i, char *vstr, int vunit)
267{
268	if (strcmp(i->desc.node1.name, vstr) != 0)
269		return 0;
270	if (vunit < 0)
271		return 1;
272	else if (i->desc.node1.unit < 0) {
273		fprintf(stderr, "unit used for parameter with no unit\n");
274		exit(1);
275	}
276	return i->desc.node1.unit == vunit;
277}
278
279/*
280 * return true if the given group can be represented as a signle mono
281 * parameter
282 */
283int
284ismono(struct info *g)
285{
286	struct info *p1, *p2;
287	struct info *e1, *e2;
288
289	p1 = g;
290	switch (g->desc.type) {
291	case SIOCTL_NUM:
292	case SIOCTL_SW:
293		for (p2 = g; p2 != NULL; p2 = nextpar(p2)) {
294			if (p2->curval != p1->curval)
295				return 0;
296		}
297		break;
298	case SIOCTL_SEL:
299	case SIOCTL_VEC:
300	case SIOCTL_LIST:
301		for (p2 = g; p2 != NULL; p2 = nextpar(p2)) {
302			for (e2 = p2; e2 != NULL; e2 = nextent(e2, 0)) {
303				if (!isdiag(e2)) {
304					if (e2->curval != 0)
305						return 0;
306				} else {
307					e1 = vecent(p1,
308					    e2->desc.node1.name,
309					    p1->desc.node0.unit);
310					if (e1 == NULL)
311						continue;
312					if (e1->curval != e2->curval)
313						return 0;
314					if (strcmp(e1->desc.display,
315						e2->desc.display) != 0)
316						return 0;
317				}
318			}
319		}
320		break;
321	}
322	return 1;
323}
324
325/*
326 * print a sub-stream, eg. "spkr[4]"
327 */
328void
329print_node(struct sioctl_node *c, int mono)
330{
331	printf("%s", c->name);
332	if (!mono && c->unit >= 0)
333		printf("[%d]", c->unit);
334}
335
336/*
337 * print display string, with '(' and ')' and non-printable chars removed
338 * in order to match command syntax
339 */
340void
341print_display(struct info *p)
342{
343	char buf[SIOCTL_NAMEMAX], *s, *d;
344	unsigned int c;
345
346	s = p->desc.display;
347	d = buf;
348	while ((c = *s++) != 0) {
349		if (c == '(' || c == ')' || c < ' ')
350			continue;
351		*d++ = c;
352	}
353	*d = 0;
354	if (buf[0] != 0)
355		printf("(%s)", buf);
356}
357
358/*
359 * print info about the parameter
360 */
361void
362print_desc(struct info *p, int mono)
363{
364	struct info *e;
365	int more;
366
367	switch (p->desc.type) {
368	case SIOCTL_NUM:
369	case SIOCTL_SW:
370		printf("*");
371		print_display(p);
372		break;
373	case SIOCTL_SEL:
374	case SIOCTL_VEC:
375	case SIOCTL_LIST:
376		more = 0;
377		for (e = p; e != NULL; e = nextent(e, mono)) {
378			if (mono) {
379				if (!isdiag(e))
380					continue;
381				if (e != firstent(p, e->desc.node1.name))
382					continue;
383			}
384			if (more)
385				printf(",");
386			print_node(&e->desc.node1, mono);
387			if (p->desc.type != SIOCTL_SEL)
388				printf(":*");
389			if (e->desc.display[0] != 0)
390				print_display(e);
391			more = 1;
392		}
393	}
394}
395
396void
397print_num(struct info *p)
398{
399	if (p->desc.maxval == 1)
400		printf("%d", p->curval);
401	else {
402		/*
403		 * For now, maxval is always 127 or 255,
404		 * so three decimals is always ideal.
405		 */
406		printf("%.3f", p->curval / (float)p->desc.maxval);
407	}
408}
409
410/*
411 * print a single control
412 */
413void
414print_ent(struct info *e, char *comment)
415{
416	if (e->desc.group[0] != 0) {
417		printf("%s", e->desc.group);
418		printf("/");
419	}
420	print_node(&e->desc.node0, 0);
421	printf(".%s=", e->desc.func);
422	switch (e->desc.type) {
423	case SIOCTL_NONE:
424		printf("<removed>\n");
425		break;
426	case SIOCTL_SEL:
427	case SIOCTL_VEC:
428	case SIOCTL_LIST:
429		print_node(&e->desc.node1, 0);
430		printf(":");
431		/* FALLTHROUGH */
432	case SIOCTL_SW:
433	case SIOCTL_NUM:
434		print_num(e);
435	}
436	print_display(e);
437	if (comment)
438		printf("\t# %s", comment);
439	printf("\n");
440}
441
442/*
443 * print parameter value
444 */
445void
446print_val(struct info *p, int mono)
447{
448	struct info *e;
449	int more;
450
451	switch (p->desc.type) {
452	case SIOCTL_NUM:
453	case SIOCTL_SW:
454		print_num(p);
455		print_display(p);
456		break;
457	case SIOCTL_SEL:
458	case SIOCTL_VEC:
459	case SIOCTL_LIST:
460		more = 0;
461		for (e = p; e != NULL; e = nextent(e, mono)) {
462			if (mono) {
463				if (!isdiag(e))
464					continue;
465				if (e != firstent(p, e->desc.node1.name))
466					continue;
467			}
468			if (e->desc.maxval == 1) {
469				if (e->curval) {
470					if (more)
471						printf(",");
472					print_node(&e->desc.node1, mono);
473					print_display(e);
474					more = 1;
475				}
476			} else {
477				if (more)
478					printf(",");
479				print_node(&e->desc.node1, mono);
480				printf(":");
481				print_num(e);
482				print_display(e);
483				more = 1;
484			}
485		}
486	}
487}
488
489/*
490 * print ``<parameter>=<value>'' string (including '\n')
491 */
492void
493print_par(struct info *p, int mono)
494{
495	if (!n_flag) {
496		if (p->desc.group[0] != 0) {
497			printf("%s", p->desc.group);
498			printf("/");
499		}
500		print_node(&p->desc.node0, mono);
501		printf(".%s=", p->desc.func);
502	}
503	if (i_flag)
504		print_desc(p, mono);
505	else
506		print_val(p, mono);
507	printf("\n");
508}
509
510/*
511 * parse a stream name or parameter name
512 */
513int
514parse_name(char **line, char *name)
515{
516	char *p = *line;
517	unsigned len = 0;
518
519	if (!isname(*p)) {
520		fprintf(stderr, "letter or digit expected near '%s'\n", p);
521		return 0;
522	}
523	while (isname(*p)) {
524		if (len >= SIOCTL_NAMEMAX - 1) {
525			name[SIOCTL_NAMEMAX - 1] = '\0';
526			fprintf(stderr, "%s...: too long\n", name);
527			return 0;
528		}
529		name[len++] = *p;
530		p++;
531	}
532	name[len] = '\0';
533	*line = p;
534	return 1;
535}
536
537/*
538 * parse a decimal integer
539 */
540int
541parse_unit(char **line, int *num)
542{
543	char *p = *line;
544	unsigned int val;
545	int n;
546
547	if (sscanf(p, "%u%n", &val, &n) != 1) {
548		fprintf(stderr, "number expected near '%s'\n", p);
549		return 0;
550	}
551	if (val >= 255) {
552		fprintf(stderr, "%d: too large\n", val);
553		return 0;
554	}
555	*num = val;
556	*line = p + n;
557	return 1;
558}
559
560int
561parse_val(char **line, float *num)
562{
563	char *p = *line;
564	float val;
565	int n;
566
567	if (sscanf(p, "%g%n", &val, &n) != 1) {
568		fprintf(stderr, "number expected near '%s'\n", p);
569		return 0;
570	}
571	if (val < 0 || val > 1) {
572		fprintf(stderr, "%g: expected number between 0 and 1\n", val);
573		return 0;
574	}
575	*num = val;
576	*line = p + n;
577	return 1;
578}
579
580/*
581 * parse a sub-stream, eg. "spkr[7]"
582 */
583int
584parse_node(char **line, char *str, int *unit)
585{
586	char *p = *line;
587
588	if (!parse_name(&p, str))
589		return 0;
590	if (*p != '[') {
591		*unit = -1;
592		*line = p;
593		return 1;
594	}
595	p++;
596	if (!parse_unit(&p, unit))
597		return 0;
598	if (*p != ']') {
599		fprintf(stderr, "']' expected near '%s'\n", p);
600		return 0;
601	}
602	p++;
603	*line = p;
604	return 1;
605}
606
607/*
608 * parse a decimal prefixed by the optional mode
609 */
610int
611parse_modeval(char **line, int *rmode, float *rval)
612{
613	char *p = *line;
614	unsigned mode;
615
616	switch (*p) {
617	case '+':
618		mode = MODE_ADD;
619		p++;
620		break;
621	case '-':
622		mode = MODE_SUB;
623		p++;
624		break;
625	case '!':
626		mode = MODE_TOGGLE;
627		p++;
628		break;
629	default:
630		mode = MODE_SET;
631	}
632	if (mode != MODE_TOGGLE) {
633		if (!parse_val(&p, rval))
634			return 0;
635	}
636	*line = p;
637	*rmode = mode;
638	return 1;
639}
640
641/*
642 * dump the whole controls list, useful for debugging
643 */
644void
645dump(void)
646{
647	struct info *i;
648
649	for (i = infolist; i != NULL; i = i->next) {
650		printf("%03u:", i->ctladdr);
651		print_node(&i->desc.node0, 0);
652		printf(".%s", i->desc.func);
653		printf("=");
654		switch (i->desc.type) {
655		case SIOCTL_NUM:
656		case SIOCTL_SW:
657			printf("0..%d (%u)", i->desc.maxval, i->curval);
658			break;
659		case SIOCTL_SEL:
660			print_node(&i->desc.node1, 0);
661			break;
662		case SIOCTL_VEC:
663		case SIOCTL_LIST:
664			print_node(&i->desc.node1, 0);
665			printf(":0..%d (%u)", i->desc.maxval, i->curval);
666		}
667		print_display(i);
668		printf("\n");
669	}
670}
671
672/*
673 * parse and execute a command ``<parameter>[=<value>]''
674 */
675int
676cmd(char *line)
677{
678	char *pos, *group;
679	struct info *i, *e, *g;
680	char func[SIOCTL_NAMEMAX];
681	char astr[SIOCTL_NAMEMAX], vstr[SIOCTL_NAMEMAX];
682	int aunit, vunit;
683	unsigned npar = 0, nent = 0;
684	int comma, mode;
685	float val;
686
687	pos = strrchr(line, '/');
688	if (pos != NULL) {
689		group = line;
690		pos[0] = 0;
691		pos++;
692	} else {
693		group = "";
694		pos = line;
695	}
696	if (!parse_node(&pos, astr, &aunit))
697		return 0;
698	if (*pos != '.') {
699		fprintf(stderr, "'.' expected near '%s'\n", pos);
700		return 0;
701	}
702	pos++;
703	if (!parse_name(&pos, func))
704		return 0;
705	for (g = infolist;; g = g->next) {
706		if (g == NULL) {
707			fprintf(stderr, "%s.%s: no such control\n", astr, func);
708			return 0;
709		}
710		if (strcmp(g->desc.group, group) == 0 &&
711		    strcmp(g->desc.func, func) == 0 &&
712		    strcmp(g->desc.node0.name, astr) == 0)
713			break;
714	}
715	g->mode = MODE_PRINT;
716	if (*pos != '=') {
717		if (*pos != '\0') {
718			fprintf(stderr, "junk at end of command\n");
719			return 0;
720		}
721		return 1;
722	}
723	pos++;
724	if (i_flag) {
725		printf("can't set values in info mode\n");
726		return 0;
727	}
728	npar = 0;
729	switch (g->desc.type) {
730	case SIOCTL_NUM:
731	case SIOCTL_SW:
732		if (!parse_modeval(&pos, &mode, &val))
733			return 0;
734		for (i = g; i != NULL; i = nextpar(i)) {
735			if (!matchpar(i, astr, aunit))
736				continue;
737			i->mode = mode;
738			i->newval = ftoi(val * i->desc.maxval);
739			npar++;
740		}
741		break;
742	case SIOCTL_SEL:
743		if (*pos == '\0') {
744			fprintf(stderr, "%s.%s: expects value\n", astr, func);
745			exit(1);
746		}
747		/* FALLTHROUGH */
748	case SIOCTL_VEC:
749	case SIOCTL_LIST:
750		for (i = g; i != NULL; i = nextpar(i)) {
751			if (!matchpar(i, astr, aunit))
752				continue;
753			for (e = i; e != NULL; e = nextent(e, 0)) {
754				e->newval = 0;
755				e->mode = MODE_SET;
756			}
757			npar++;
758		}
759		comma = 0;
760		for (;;) {
761			if (*pos == '\0')
762				break;
763			if (comma) {
764				if (*pos != ',')
765					break;
766				pos++;
767			}
768			if (!parse_node(&pos, vstr, &vunit))
769				return 0;
770			if (*pos == ':') {
771				pos++;
772				if (!parse_modeval(&pos, &mode, &val))
773					return 0;
774			} else {
775				val = 1.;
776				mode = MODE_SET;
777			}
778			nent = 0;
779			for (i = g; i != NULL; i = nextpar(i)) {
780				if (!matchpar(i, astr, aunit))
781					continue;
782				for (e = i; e != NULL; e = nextent(e, 0)) {
783					if (matchent(e, vstr, vunit)) {
784						e->newval = ftoi(val * e->desc.maxval);
785						e->mode = mode;
786						nent++;
787					}
788				}
789			}
790			if (*pos == '(') {
791				while (*pos != 0) {
792					if (*pos++ == ')')
793						break;
794				}
795			}
796			if (nent == 0) {
797				/* XXX: use print_node()-like routine */
798				fprintf(stderr, "%s[%d]: invalid value\n", vstr, vunit);
799				print_par(g, 0);
800				exit(1);
801			}
802			comma = 1;
803		}
804	}
805	if (npar == 0) {
806		fprintf(stderr, "%s: invalid parameter\n", line);
807		exit(1);
808	}
809	if (*pos != '\0') {
810		printf("%s: junk at end of command\n", pos);
811		exit(1);
812	}
813	return 1;
814}
815
816/*
817 * write the controls with the ``set'' flag on the device
818 */
819void
820commit(void)
821{
822	struct info *i;
823	int val;
824
825	for (i = infolist; i != NULL; i = i->next) {
826		val = 0xdeadbeef;
827		switch (i->mode) {
828		case MODE_IGNORE:
829		case MODE_PRINT:
830			continue;
831		case MODE_SET:
832			val = i->newval;
833			break;
834		case MODE_ADD:
835			val = i->curval + i->newval;
836			if (val > i->desc.maxval)
837				val = i->desc.maxval;
838			break;
839		case MODE_SUB:
840			val = i->curval - i->newval;
841			if (val < 0)
842				val = 0;
843			break;
844		case MODE_TOGGLE:
845			val = i->curval ? 0 : i->desc.maxval;
846		}
847		sioctl_setval(hdl, i->ctladdr, val);
848		i->curval = val;
849	}
850}
851
852/*
853 * print all parameters
854 */
855void
856list(void)
857{
858	struct info *p, *g;
859
860	for (g = infolist; g != NULL; g = nextfunc(g)) {
861		if (g->mode == MODE_IGNORE)
862			continue;
863		if (i_flag) {
864			if (v_flag) {
865				for (p = g; p != NULL; p = nextpar(p))
866					print_par(p, 0);
867			} else
868				print_par(g, 1);
869		} else {
870			if (v_flag || !ismono(g)) {
871				for (p = g; p != NULL; p = nextpar(p))
872					print_par(p, 0);
873			} else
874				print_par(g, 1);
875		}
876	}
877}
878
879/*
880 * register a new knob/button, called from the poll() loop.  this may be
881 * called when label string changes, in which case we update the
882 * existing label widget rather than inserting a new one.
883 */
884void
885ondesc(void *arg, struct sioctl_desc *d, int curval)
886{
887	struct info *i, **pi;
888	int cmp;
889
890	if (d == NULL)
891		return;
892
893	/*
894	 * delete control
895	 */
896	for (pi = &infolist; (i = *pi) != NULL; pi = &i->next) {
897		if (d->addr == i->desc.addr) {
898			if (m_flag)
899				print_ent(i, "deleted");
900			*pi = i->next;
901			free(i);
902			break;
903		}
904	}
905
906	switch (d->type) {
907	case SIOCTL_NUM:
908	case SIOCTL_SW:
909	case SIOCTL_VEC:
910	case SIOCTL_LIST:
911	case SIOCTL_SEL:
912		break;
913	default:
914		return;
915	}
916
917	/*
918	 * find the right position to insert the new widget
919	 */
920	for (pi = &infolist; (i = *pi) != NULL; pi = &i->next) {
921		cmp = cmpdesc(d, &i->desc);
922		if (cmp <= 0)
923			break;
924	}
925	i = malloc(sizeof(struct info));
926	if (i == NULL) {
927		perror("malloc");
928		exit(1);
929	}
930	i->desc = *d;
931	i->ctladdr = d->addr;
932	i->curval = i->newval = curval;
933	i->mode = MODE_IGNORE;
934	i->next = *pi;
935	*pi = i;
936	if (m_flag)
937		print_ent(i, "added");
938}
939
940/*
941 * update a knob/button state, called from the poll() loop
942 */
943void
944onctl(void *arg, unsigned addr, unsigned val)
945{
946	struct info *i, *j;
947
948	i = infolist;
949	for (;;) {
950		if (i == NULL)
951			return;
952		if (i->ctladdr == addr)
953			break;
954		i = i->next;
955	}
956
957	if (i->curval == val) {
958		print_ent(i, "eq");
959		return;
960	}
961
962	if (i->desc.type == SIOCTL_SEL) {
963		for (j = infolist; j != NULL; j = j->next) {
964			if (strcmp(i->desc.group, j->desc.group) != 0 ||
965			    strcmp(i->desc.node0.name, j->desc.node0.name) != 0 ||
966			    strcmp(i->desc.func, j->desc.func) != 0 ||
967			    i->desc.node0.unit != j->desc.node0.unit)
968				continue;
969			j->curval = (i->ctladdr == j->ctladdr);
970		}
971	} else
972		i->curval = val;
973
974	if (m_flag)
975		print_ent(i, "changed");
976}
977
978int
979main(int argc, char **argv)
980{
981	char *devname = SIO_DEVANY;
982	int i, c, d_flag = 0;
983	struct info *g;
984	struct pollfd *pfds;
985	int nfds, revents;
986
987	while ((c = getopt(argc, argv, "df:imnqv")) != -1) {
988		switch (c) {
989		case 'd':
990			d_flag = 1;
991			break;
992		case 'f':
993			devname = optarg;
994			break;
995		case 'i':
996			i_flag = 1;
997			break;
998		case 'm':
999			m_flag = 1;
1000			break;
1001		case 'n':
1002			n_flag = 1;
1003			break;
1004		case 'q':
1005			q_flag = 1;
1006			break;
1007		case 'v':
1008			v_flag++;
1009			break;
1010		default:
1011			fprintf(stderr, "usage: sndioctl "
1012			    "[-dimnqv] [-f device] [command ...]\n");
1013			exit(1);
1014		}
1015	}
1016	argc -= optind;
1017	argv += optind;
1018
1019	hdl = sioctl_open(devname, SIOCTL_READ | SIOCTL_WRITE, 0);
1020	if (hdl == NULL) {
1021		fprintf(stderr, "%s: can't open control device\n", devname);
1022		exit(1);
1023	}
1024
1025	if (pledge("stdio audio", NULL) == -1) {
1026		fprintf(stderr, "%s: pledge: %s\n", getprogname(),
1027		    strerror(errno));
1028		exit(1);
1029	}
1030
1031	if (!sioctl_ondesc(hdl, ondesc, NULL)) {
1032		fprintf(stderr, "%s: can't get device description\n", devname);
1033		exit(1);
1034	}
1035	sioctl_onval(hdl, onctl, NULL);
1036
1037	if (d_flag) {
1038		if (argc > 0) {
1039			fprintf(stderr,
1040			    "commands are not allowed with -d option\n");
1041			exit(1);
1042		}
1043		dump();
1044	} else {
1045		if (argc == 0) {
1046			for (g = infolist; g != NULL; g = nextfunc(g))
1047				g->mode = MODE_PRINT;
1048		} else {
1049			for (i = 0; i < argc; i++) {
1050				if (!cmd(argv[i]))
1051					return 1;
1052			}
1053		}
1054		commit();
1055		if (!q_flag)
1056			list();
1057	}
1058	if (m_flag) {
1059		pfds = malloc(sizeof(struct pollfd) * sioctl_nfds(hdl));
1060		if (pfds == NULL) {
1061			perror("malloc");
1062			exit(1);
1063		}
1064		for (;;) {
1065                	fflush(stdout);
1066			nfds = sioctl_pollfd(hdl, pfds, POLLIN);
1067			if (nfds == 0)
1068				break;
1069			while (poll(pfds, nfds, -1) < 0) {
1070				if (errno != EINTR) {
1071					perror("poll");
1072					exit(1);
1073				}
1074			}
1075			revents = sioctl_revents(hdl, pfds);
1076			if (revents & POLLHUP) {
1077				fprintf(stderr, "disconnected\n");
1078				break;
1079			}
1080		}
1081		free(pfds);
1082	}
1083	sioctl_close(hdl);
1084	return 0;
1085}
1086