xref: /openbsd-src/usr.bin/sndioctl/sndioctl.c (revision 4aaef610e0a6825076a40a5cd314cfc98aa2ffb1)
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 
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_display(struct info *);
51 void print_desc(struct info *, int);
52 void print_num(struct info *);
53 void print_ent(struct info *, char *);
54 void print_val(struct info *, int);
55 void print_par(struct info *, int);
56 int parse_name(char **, char *);
57 int parse_unit(char **, int *);
58 int parse_val(char **, float *);
59 int parse_node(char **, char *, int *);
60 int parse_modeval(char **, int *, float *);
61 void dump(void);
62 int cmd(char *);
63 void commit(void);
64 void list(void);
65 void ondesc(void *, struct sioctl_desc *, int);
66 void onctl(void *, unsigned, unsigned);
67 
68 struct sioctl_hdl *hdl;
69 struct info *infolist;
70 int i_flag = 0, v_flag = 0, m_flag = 0, n_flag = 0, q_flag = 0;
71 
72 static inline int
isname(int c)73 isname(int c)
74 {
75 	return (c == '_') ||
76 	    (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') ||
77 	    (c >= '0' && c <= '9');
78 }
79 
80 static int
ftoi(float f)81 ftoi(float f)
82 {
83 	return f + 0.5;
84 }
85 
86 /*
87  * compare two sioctl_desc structures, used to sort infolist
88  */
89 int
cmpdesc(struct sioctl_desc * d1,struct sioctl_desc * d2)90 cmpdesc(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  */
123 int
isdiag(struct info * e)124 isdiag(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  */
134 struct info *
vecent(struct info * i,char * vstr,int vunit)135 vecent(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  */
149 struct info *
nextfunc(struct info * i)150 nextfunc(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  */
169 struct info *
nextpar(struct info * i)170 nextpar(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  */
194 struct info *
firstent(struct info * g,char * vstr)195 firstent(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  */
221 struct info *
nextent(struct info * i,int mono)222 nextent(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  */
247 int
matchpar(struct info * i,char * astr,int aunit)248 matchpar(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  */
265 int
matchent(struct info * i,char * vstr,int vunit)266 matchent(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  */
283 int
ismono(struct info * g)284 ismono(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  */
328 void
print_node(struct sioctl_node * c,int mono)329 print_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  */
340 void
print_display(struct info * p)341 print_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  */
361 void
print_desc(struct info * p,int mono)362 print_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 
396 void
print_num(struct info * p)397 print_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  */
413 void
print_ent(struct info * e,char * comment)414 print_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  */
445 void
print_val(struct info * p,int mono)446 print_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  */
492 void
print_par(struct info * p,int mono)493 print_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  */
513 int
parse_name(char ** line,char * name)514 parse_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  */
540 int
parse_unit(char ** line,int * num)541 parse_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 
560 int
parse_val(char ** line,float * num)561 parse_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  */
583 int
parse_node(char ** line,char * str,int * unit)584 parse_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  */
610 int
parse_modeval(char ** line,int * rmode,float * rval)611 parse_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  */
644 void
dump(void)645 dump(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  */
675 int
cmd(char * line)676 cmd(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  */
819 void
commit(void)820 commit(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  */
855 void
list(void)856 list(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  */
884 void
ondesc(void * arg,struct sioctl_desc * d,int curval)885 ondesc(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  */
943 void
onctl(void * arg,unsigned addr,unsigned val)944 onctl(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 
978 int
main(int argc,char ** argv)979 main(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