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