xref: /titanic_51/usr/src/cmd/audio/audioctl/audioctl.c (revision 58dbc5075c2263a035b497f4bb080ab9bfb6aab4)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 /*
22  * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  */
25 
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <errno.h>
29 #include <string.h>
30 #include <strings.h>
31 #include <locale.h>
32 #include <libintl.h>
33 #include <stdarg.h>
34 #include <stddef.h>
35 #include <sys/types.h>
36 #include <sys/stat.h>
37 #include <sys/mkdev.h>
38 #include <fcntl.h>
39 #include <unistd.h>
40 #include <ctype.h>
41 #include <sys/param.h>
42 #include <sys/soundcard.h>
43 
44 #if !defined(TEXT_DOMAIN)	/* Should be defined by cc -D */
45 #define	TEXT_DOMAIN "SYS_TEST"	/* Use this only if it weren't */
46 #endif
47 
48 #define	_(s)	gettext(s)
49 
50 #define	MAXLINE	1024
51 
52 #define	AUDIO_CTRL_STEREO_LEFT(v)	((uint8_t)((v) & 0xff))
53 #define	AUDIO_CTRL_STEREO_RIGHT(v)	((uint8_t)(((v) >> 8) & 0xff))
54 #define	AUDIO_CTRL_STEREO_VAL(l, r)	(((l) & 0xff) | (((r) & 0xff) << 8))
55 
56 /*
57  * These are borrowed from sys/audio/audio_common.h, where the values
58  * are protected by _KERNEL.
59  */
60 #define	AUDIO_MN_TYPE_NBITS	(4)
61 #define	AUDIO_MN_TYPE_MASK	((1U << AUDIO_MN_TYPE_NBITS) - 1)
62 #define	AUDIO_MINOR_MIXER	(0)
63 
64 
65 /*
66  * Column display information
67  * All are related to the types enumerated in col_t and any change should be
68  * reflected in the corresponding indices and offsets for all the variables
69  * accordingly.  Most tweaks to the display can be done by adjusting the
70  * values here.
71  */
72 
73 /* types of columns displayed */
74 typedef enum { COL_DV = 0, COL_NM, COL_VAL, COL_SEL} col_t;
75 
76 /* corresponding sizes of columns; does not include trailing null */
77 #define	COL_DV_SZ	16
78 #define	COL_NM_SZ	24
79 #define	COL_VAL_SZ	10
80 #define	COL_SEL_SZ	20
81 #define	COL_MAX_SZ	64
82 
83 /* corresponding sizes of columns, indexed by col_t value */
84 static int col_sz[] = {
85 	COL_DV_SZ, COL_NM_SZ, COL_VAL_SZ, COL_SEL_SZ
86 };
87 
88 /* used by callers of the printing function */
89 typedef struct col_prt {
90 	char *col_dv;
91 	char *col_nm;
92 	char *col_val;
93 	char *col_sel;
94 } col_prt_t;
95 
96 /* columns displayed in order with vopt = 0 */
97 static int col_dpy[] = {COL_NM, COL_VAL};
98 static int col_dpy_len = sizeof (col_dpy) / sizeof (*col_dpy);
99 
100 /* tells the printing function what members to use; follows col_dpy[] */
101 static size_t col_dpy_prt[] = {
102 	offsetof(col_prt_t, col_nm),
103 	offsetof(col_prt_t, col_val),
104 };
105 
106 /* columns displayed in order with vopt = 1 */
107 static int col_dpy_vopt[] = { COL_DV, COL_NM, COL_VAL, COL_SEL};
108 static int col_dpy_vopt_len = sizeof (col_dpy_vopt) / sizeof (*col_dpy_vopt);
109 
110 /* tells the printing function what members to use; follows col_dpy_vopt[] */
111 static size_t col_dpy_prt_vopt[] = {
112 	offsetof(col_prt_t, col_dv),
113 	offsetof(col_prt_t, col_nm),
114 	offsetof(col_prt_t, col_val),
115 	offsetof(col_prt_t, col_sel)
116 };
117 
118 /* columns displayed in order with tofile = 1 */
119 static int col_dpy_tofile[] = { COL_NM, COL_VAL};
120 static int col_dpy_tofile_len = sizeof (col_dpy_tofile) /
121     sizeof (*col_dpy_tofile);
122 
123 /* tells the printing function what members to use; follows col_dpy_tofile[] */
124 static size_t col_dpy_prt_tofile[] = {
125 	offsetof(col_prt_t, col_nm),
126 	offsetof(col_prt_t, col_val)
127 };
128 
129 
130 /*
131  * mixer and control accounting
132  */
133 
134 typedef struct cinfo {
135 	oss_mixext ci;
136 	oss_mixer_enuminfo *enump;
137 } cinfo_t;
138 
139 typedef struct device {
140 	oss_card_info	card;
141 	oss_mixerinfo	mixer;
142 
143 	int		cmax;
144 	cinfo_t		*controls;
145 
146 	int		mfd;
147 	dev_t		devt;
148 
149 	struct device	*nextp;
150 } device_t;
151 
152 static device_t	*devices = NULL;
153 
154 /*PRINTFLIKE1*/
155 static void
156 msg(char *fmt, ...)
157 {
158 	va_list ap;
159 
160 	va_start(ap, fmt);
161 	(void) vprintf(fmt, ap);
162 	va_end(ap);
163 }
164 
165 /*PRINTFLIKE1*/
166 static void
167 warn(char *fmt, ...)
168 {
169 	va_list ap;
170 
171 	va_start(ap, fmt);
172 	(void) vfprintf(stderr, fmt, ap);
173 	va_end(ap);
174 }
175 
176 static void
177 free_device(device_t *d)
178 {
179 	int		i;
180 	device_t	**dpp;
181 
182 	dpp = &devices;
183 	while ((*dpp) && ((*dpp) != d)) {
184 		dpp = &((*dpp)->nextp);
185 	}
186 	if (*dpp) {
187 		*dpp = d->nextp;
188 	}
189 	for (i = 0; i < d->cmax; i++) {
190 		if (d->controls[i].enump != NULL)
191 			free(d->controls[i].enump);
192 	}
193 
194 	if (d->mfd >= 0)
195 		(void) close(d->mfd);
196 
197 	free(d);
198 }
199 
200 static void
201 free_devices(void)
202 {
203 	device_t *d = devices;
204 
205 	while ((d = devices) != NULL) {
206 		free_device(d);
207 	}
208 
209 	devices = NULL;
210 }
211 
212 
213 /*
214  * adds to the end of global devices and returns a pointer to the new entry
215  */
216 static device_t *
217 alloc_device(void)
218 {
219 	device_t *p;
220 	device_t *d = calloc(1, sizeof (*d));
221 
222 	d->card.card = -1;
223 	d->mixer.dev = -1;
224 	d->mfd = -1;
225 
226 	if (devices == NULL) {
227 		devices = d;
228 	} else {
229 		for (p = devices; p->nextp != NULL; p = p->nextp) {}
230 
231 		p->nextp = d;
232 	}
233 	return (d);
234 }
235 
236 
237 /*
238  * cinfop->enump needs to be present
239  * idx should be: >= 0 to < cinfop->ci.maxvalue
240  */
241 static char *
242 get_enum_str(cinfo_t *cinfop, int idx)
243 {
244 	int sz = sizeof (*cinfop->ci.enum_present) * 8;
245 
246 	if (cinfop->ci.enum_present[idx / sz] & (1 << (idx % sz)))
247 		return (cinfop->enump->strings + cinfop->enump->strindex[idx]);
248 
249 	return (NULL);
250 }
251 
252 
253 /*
254  * caller fills in d->mixer.devnode; func fills in the rest
255  */
256 static int
257 get_device_info(device_t *d)
258 {
259 	int fd = -1;
260 	int i;
261 	cinfo_t *ci;
262 
263 	if ((fd = open(d->mixer.devnode, O_RDWR)) < 0) {
264 		perror(_("Error opening device"));
265 		return (errno);
266 	}
267 	d->mfd = fd;
268 
269 	d->cmax = -1;
270 	if (ioctl(fd, SNDCTL_MIX_NREXT, &d->cmax) < 0) {
271 		perror(_("Error getting control count"));
272 		return (errno);
273 	}
274 
275 	d->controls = calloc(d->cmax, sizeof (*d->controls));
276 
277 	for (i = 0; i < d->cmax; i++) {
278 		ci = &d->controls[i];
279 
280 		ci->ci.dev = -1;
281 		ci->ci.ctrl = i;
282 
283 		if (ioctl(fd, SNDCTL_MIX_EXTINFO, &ci->ci) < 0) {
284 			perror(_("Error getting control info"));
285 			return (errno);
286 		}
287 
288 		if (ci->ci.type == MIXT_ENUM) {
289 			ci->enump = calloc(1, sizeof (*ci->enump));
290 			ci->enump->dev = -1;
291 			ci->enump->ctrl = ci->ci.ctrl;
292 
293 			if (ioctl(fd, SNDCTL_MIX_ENUMINFO, ci->enump) < 0) {
294 				perror(_("Error getting enum info"));
295 				return (errno);
296 			}
297 		}
298 	}
299 
300 	return (0);
301 }
302 
303 
304 static int
305 load_devices(void)
306 {
307 	int rv = -1;
308 	int fd = -1;
309 	int i;
310 	oss_sysinfo si;
311 	device_t *d;
312 
313 	if ((fd = open("/dev/mixer", O_RDWR)) < 0) {
314 		rv = errno;
315 		warn(_("Error opening mixer\n"));
316 		goto OUT;
317 	}
318 
319 	if (ioctl(fd, SNDCTL_SYSINFO, &si) < 0) {
320 		rv = errno;
321 		perror(_("Error getting system information"));
322 		goto OUT;
323 	}
324 
325 	for (i = 0; i < si.nummixers; i++) {
326 
327 		struct stat sbuf;
328 
329 		d = alloc_device();
330 		d->mixer.dev = i;
331 
332 		if (ioctl(fd, SNDCTL_MIXERINFO, &d->mixer) != 0) {
333 			continue;
334 		}
335 
336 		d->card.card = d->mixer.card_number;
337 
338 		if ((ioctl(fd, SNDCTL_CARDINFO, &d->card) != 0) ||
339 		    (stat(d->mixer.devnode, &sbuf) != 0) ||
340 		    ((sbuf.st_mode & S_IFCHR) == 0)) {
341 			warn(_("Device present: %s\n"), d->mixer.devnode);
342 			free_device(d);
343 			continue;
344 		}
345 		d->devt = makedev(major(sbuf.st_rdev),
346 		    minor(sbuf.st_rdev) & ~(AUDIO_MN_TYPE_MASK));
347 
348 		if ((rv = get_device_info(d)) != 0) {
349 			free_device(d);
350 			goto OUT;
351 		}
352 	}
353 
354 	rv = 0;
355 
356 OUT:
357 	if (fd >= 0)
358 		(void) close(fd);
359 	return (rv);
360 }
361 
362 
363 static int
364 ctype_valid(int type)
365 {
366 	switch (type) {
367 	case MIXT_ONOFF:
368 	case MIXT_ENUM:
369 	case MIXT_MONOSLIDER:
370 	case MIXT_STEREOSLIDER:
371 		return (1);
372 	default:
373 		return (0);
374 	}
375 }
376 
377 
378 static void
379 print_control_line(FILE *sfp, col_prt_t *colp, int vopt)
380 {
381 	int i;
382 	size_t *col_prtp;
383 	int *col_dpyp;
384 	int col_cnt;
385 	int col_type;
386 	int width;
387 	char *colstr;
388 	char cbuf[COL_MAX_SZ + 1];
389 	char line[128];
390 	char *colsep =  " ";
391 
392 	if (sfp != NULL) {
393 		col_prtp = col_dpy_prt_tofile;
394 		col_dpyp = col_dpy_tofile;
395 		col_cnt = col_dpy_tofile_len;
396 	} else if (vopt) {
397 		col_prtp = col_dpy_prt_vopt;
398 		col_dpyp = col_dpy_vopt;
399 		col_cnt = col_dpy_vopt_len;
400 	} else {
401 		col_prtp = col_dpy_prt;
402 		col_dpyp = col_dpy;
403 		col_cnt = col_dpy_len;
404 	}
405 
406 	line[0] = '\0';
407 
408 	for (i = 0; i < col_cnt; i++) {
409 		col_type = col_dpyp[i];
410 		width = col_sz[col_type];
411 		colstr = *(char **)(((size_t)colp) + col_prtp[i]);
412 
413 		(void) snprintf(cbuf, sizeof (cbuf), "%- *s",
414 		    width > 0 ? width : 1,
415 		    (colstr == NULL) ? "" : colstr);
416 
417 		(void) strlcat(line, cbuf, sizeof (line));
418 		if (i < col_cnt - 1)
419 			(void) strlcat(line, colsep, sizeof (line));
420 	}
421 
422 	(void) fprintf(sfp ? sfp : stdout, "%s\n", line);
423 }
424 
425 
426 static void
427 print_header(FILE *sfp, int vopt)
428 {
429 	col_prt_t col;
430 
431 	if (sfp) {
432 		col.col_nm = _("#CONTROL");
433 		col.col_val = _("VALUE");
434 	} else {
435 		col.col_dv = _("DEVICE");
436 		col.col_nm = _("CONTROL");
437 		col.col_val = _("VALUE");
438 		col.col_sel = _("POSSIBLE");
439 	}
440 	print_control_line(sfp, &col, vopt);
441 }
442 
443 
444 static int
445 print_control(FILE *sfp, device_t *d, cinfo_t *cinfop, int vopt)
446 {
447 	int mfd = d->mfd;
448 	char *devnm = d->card.shortname;
449 	oss_mixer_value cval;
450 	char *str;
451 	int i;
452 	int idx = -1;
453 	int rv = -1;
454 	char valbuf[COL_VAL_SZ + 1];
455 	char selbuf[COL_SEL_SZ + 1];
456 	col_prt_t col;
457 
458 	cval.dev = -1;
459 	cval.ctrl = cinfop->ci.ctrl;
460 
461 	if (ctype_valid(cinfop->ci.type)) {
462 		if (ioctl(mfd, SNDCTL_MIX_READ, &cval) < 0) {
463 			rv = errno;
464 			perror(_("Error reading control\n"));
465 			return (rv);
466 		}
467 	} else {
468 		return (0);
469 	}
470 
471 	/*
472 	 * convert the control value into a string
473 	 */
474 	switch (cinfop->ci.type) {
475 	case MIXT_ONOFF:
476 		(void) snprintf(valbuf, sizeof (valbuf), "%s",
477 		    cval.value ? _("on") : _("off"));
478 		break;
479 
480 	case MIXT_MONOSLIDER:
481 		(void) snprintf(valbuf, sizeof (valbuf), "%d",
482 		    cval.value & 0xff);
483 		break;
484 
485 	case MIXT_STEREOSLIDER:
486 		(void) snprintf(valbuf, sizeof (valbuf), "%d:%d",
487 		    (int)AUDIO_CTRL_STEREO_LEFT(cval.value),
488 		    (int)AUDIO_CTRL_STEREO_RIGHT(cval.value));
489 		break;
490 
491 	case MIXT_ENUM:
492 		str = get_enum_str(cinfop, cval.value);
493 		if (str == NULL) {
494 			warn(_("Bad enum index %d for control '%s'\n"),
495 			    cval.value, cinfop->ci.extname);
496 			return (EINVAL);
497 		}
498 
499 		(void) snprintf(valbuf, sizeof (valbuf), "%s", str);
500 		break;
501 
502 	default:
503 		return (0);
504 	}
505 
506 	/*
507 	 * possible control values (range/selection)
508 	 */
509 	switch (cinfop->ci.type) {
510 	case MIXT_ONOFF:
511 		(void) snprintf(selbuf, sizeof (selbuf), _("on,off"));
512 		break;
513 
514 	case MIXT_MONOSLIDER:
515 		(void) snprintf(selbuf, sizeof (selbuf), "%d-%d",
516 		    cinfop->ci.minvalue, cinfop->ci.maxvalue);
517 		break;
518 	case MIXT_STEREOSLIDER:
519 		(void) snprintf(selbuf, sizeof (selbuf), "%d-%d:%d-%d",
520 		    cinfop->ci.minvalue, cinfop->ci.maxvalue,
521 		    cinfop->ci.minvalue, cinfop->ci.maxvalue);
522 		break;
523 
524 	case MIXT_ENUM:
525 		/*
526 		 * display the first choice on the same line, then display
527 		 * the rest on multiple lines
528 		 */
529 		selbuf[0] = 0;
530 		for (i = 0; i < cinfop->ci.maxvalue; i++) {
531 			str = get_enum_str(cinfop, i);
532 			if (str == NULL)
533 				continue;
534 
535 			if ((strlen(str) + 1 + strlen(selbuf)) >=
536 			    sizeof (selbuf)) {
537 				break;
538 			}
539 			if (strlen(selbuf)) {
540 				(void) strlcat(selbuf, ",", sizeof (selbuf));
541 			}
542 
543 			(void) strlcat(selbuf, str, sizeof (selbuf));
544 		}
545 		idx = i;
546 		break;
547 
548 	default:
549 		(void) snprintf(selbuf, sizeof (selbuf), "-");
550 	}
551 
552 	col.col_dv = devnm;
553 	col.col_nm = strlen(cinfop->ci.extname) ?
554 	    cinfop->ci.extname : cinfop->ci.id;
555 	while (strchr(col.col_nm, '_') != NULL) {
556 		col.col_nm = strchr(col.col_nm, '_') + 1;
557 	}
558 	col.col_val = valbuf;
559 	col.col_sel = selbuf;
560 	print_control_line(sfp, &col, vopt);
561 
562 	/* print leftover enum value selections */
563 	while ((sfp == NULL) && (idx >= 0) && (idx < cinfop->ci.maxvalue)) {
564 		selbuf[0] = 0;
565 		for (i = idx; i < cinfop->ci.maxvalue; i++) {
566 			str = get_enum_str(cinfop, i);
567 			if (str == NULL)
568 				continue;
569 
570 			if ((strlen(str) + 1 + strlen(selbuf)) >=
571 			    sizeof (selbuf)) {
572 				break;
573 			}
574 			if (strlen(selbuf)) {
575 				(void) strlcat(selbuf, ",", sizeof (selbuf));
576 			}
577 
578 			(void) strlcat(selbuf, str, sizeof (selbuf));
579 		}
580 		idx = i;
581 		col.col_dv = NULL;
582 		col.col_nm = NULL;
583 		col.col_val = NULL;
584 		col.col_sel = selbuf;
585 		print_control_line(sfp, &col, vopt);
586 	}
587 
588 	return (0);
589 }
590 
591 
592 static int
593 set_device_control(device_t *d, cinfo_t *cinfop, char *wstr, int vopt)
594 {
595 	int mfd = d->mfd;
596 	oss_mixer_value cval;
597 	int wlen = strlen(wstr);
598 	int lval, rval;
599 	char *lstr, *rstr;
600 	char *str;
601 	int i;
602 	int rv = -1;
603 
604 	cval.dev = -1;
605 	cval.ctrl = cinfop->ci.ctrl;
606 	cval.value = 0;
607 
608 	switch (cinfop->ci.type) {
609 	case MIXT_ONOFF:
610 		cval.value = (strncmp(_("on"), wstr, wlen) == 0) ? 1 : 0;
611 		break;
612 
613 	case MIXT_MONOSLIDER:
614 		cval.value = atoi(wstr);
615 		break;
616 
617 	case MIXT_STEREOSLIDER:
618 		lstr = wstr;
619 		rstr = strchr(wstr, ':');
620 		if (rstr != NULL) {
621 			*rstr = '\0';
622 			rstr++;
623 
624 			rval = atoi(rstr);
625 			lval = atoi(lstr);
626 
627 			rstr--;
628 			*rstr = ':';
629 		} else {
630 			lval = atoi(lstr);
631 			rval = lval;
632 		}
633 
634 		cval.value = AUDIO_CTRL_STEREO_VAL(lval, rval);
635 		break;
636 
637 	case MIXT_ENUM:
638 		for (i = 0; i < cinfop->ci.maxvalue; i++) {
639 			str = get_enum_str(cinfop, i);
640 			if (str == NULL)
641 				continue;
642 
643 			if (strncmp(wstr, str, wlen) == 0) {
644 				cval.value = i;
645 				break;
646 			}
647 		}
648 
649 		if (i >= cinfop->ci.maxvalue) {
650 			warn(_("Invalid enumeration value\n"));
651 			return (EINVAL);
652 		}
653 		break;
654 
655 	default:
656 		warn(_("Unsupported control type: %d\n"), cinfop->ci.type);
657 		return (EINVAL);
658 	}
659 
660 	if (vopt) {
661 		msg(_("%s: '%s' set to '%s'\n"), d->card.shortname,
662 		    cinfop->ci.extname, wstr);
663 	}
664 
665 	if (ioctl(mfd, SNDCTL_MIX_WRITE, &cval) < 0) {
666 		rv = errno;
667 		perror(_("Error writing control"));
668 		return (rv);
669 	}
670 
671 	rv = 0;
672 	return (rv);
673 }
674 
675 
676 static void
677 help(void)
678 {
679 #define	HELP_STR	_(						\
680 "audioctl list-devices\n"						\
681 "	list all audio devices\n"					\
682 "\n"									\
683 "audioctl show-device [ -v ] [ -d <device> ]\n"				\
684 "	display information about an audio device\n"			\
685 "\n"									\
686 "audioctl show-control [ -v ] [ -d <device> ] [ <control> ... ]\n"	\
687 "	get the value of a specific control (all if not specified)\n"	\
688 "\n"									\
689 "audioctl set-control [ -v ] [ -d <device> ] <control> <value>\n"	\
690 "	set the value of a specific control\n"				\
691 "\n"									\
692 "audioctl save-controls [ -d <device> ] [ -f ] <file>\n"		\
693 "	save all control settings for the device to a file\n"		\
694 "\n"									\
695 "audioctl load-controls [ -d <device> ] <file>\n"			\
696 "	restore previously saved control settings to device\n"		\
697 "\n"									\
698 "audioctl help\n"							\
699 "	show this message.\n")
700 
701 	(void) fprintf(stderr, HELP_STR);
702 }
703 
704 dev_t
705 device_devt(char *name)
706 {
707 	struct stat	sbuf;
708 
709 	if ((stat(name, &sbuf) != 0) ||
710 	    ((sbuf.st_mode & S_IFCHR) == 0)) {
711 		/* Not a device node! */
712 		return (0);
713 	}
714 
715 	return (makedev(major(sbuf.st_rdev),
716 	    minor(sbuf.st_rdev) & ~(AUDIO_MN_TYPE_MASK)));
717 }
718 
719 static device_t *
720 find_device(char *name)
721 {
722 	dev_t		devt;
723 	device_t	*d;
724 
725 	/*
726 	 * User may have specified:
727 	 *
728 	 * /dev/dsp[<num>]
729 	 * /dev/mixer[<num>]
730 	 * /dev/audio[<num>9]
731 	 * /dev/audioctl[<num>]
732 	 * /dev/sound/<num>{,ctl,dsp,mixer}
733 	 * /dev/sound/<driver>:<num>{,ctl,dsp,mixer}
734 	 *
735 	 * We can canonicalize these by looking at the dev_t though.
736 	 */
737 
738 	if (name == NULL)
739 		name = getenv("AUDIODEV");
740 
741 	if ((name == NULL) ||
742 	    (strcmp(name, "/dev/mixer") == 0)) {
743 		/* /dev/mixer node doesn't point to real hw */
744 		name = "/dev/dsp";
745 	}
746 
747 	if (*name == '/') {
748 		/* if we have a full path, convert to the devt */
749 		if ((devt = device_devt(name)) == 0) {
750 			warn(_("No such audio device.\n"));
751 			return (NULL);
752 		}
753 		name = NULL;
754 	}
755 
756 	for (d = devices; d != NULL; d = d->nextp) {
757 		oss_card_info *card = &d->card;
758 
759 		if ((name) && (strcmp(name, card->shortname) == 0)) {
760 			return (d);
761 		}
762 		if (devt == d->devt) {
763 			return (d);
764 		}
765 	}
766 
767 	warn(_("No such audio device.\n"));
768 	return (NULL);
769 }
770 
771 int
772 do_list_devices(int argc, char **argv)
773 {
774 	int		optc;
775 	int		verbose = 0;
776 	device_t	*d;
777 
778 	while ((optc = getopt(argc, argv, "v")) != EOF) {
779 		switch (optc) {
780 		case 'v':
781 			verbose++;
782 			break;
783 		default:
784 			help();
785 			return (-1);
786 		}
787 	}
788 	argc -= optind;
789 	argv += optind;
790 	if (argc != 0) {
791 		help();
792 		return (-1);
793 	}
794 
795 	for (d = devices; d != NULL; d = d->nextp) {
796 
797 		if ((d->mixer.enabled == 0) && (!verbose))
798 			continue;
799 
800 		if (verbose) {
801 			msg(_("%s (%s)\n"), d->card.shortname,
802 			    d->mixer.devnode);
803 		} else {
804 			msg(_("%s\n"), d->card.shortname);
805 		}
806 	}
807 
808 	return (0);
809 }
810 
811 int
812 do_show_device(int argc, char **argv)
813 {
814 	int		optc;
815 	char		*devname = NULL;
816 	device_t	*d;
817 
818 	while ((optc = getopt(argc, argv, "d:v")) != EOF) {
819 		switch (optc) {
820 		case 'd':
821 			devname = optarg;
822 			break;
823 		case 'v':
824 			break;
825 		default:
826 			help();
827 			return (-1);
828 		}
829 	}
830 	argc -= optind;
831 	argv += optind;
832 	if (argc != 0) {
833 		help();
834 		return (-1);
835 	}
836 
837 	if ((d = find_device(devname)) == NULL) {
838 		return (ENODEV);
839 	}
840 
841 	msg(_("Device: %s\n"), d->mixer.devnode);
842 	msg(_("  Name    = %s\n"), d->card.shortname);
843 	msg(_("  Config  = %s\n"), d->card.longname);
844 
845 	if (strlen(d->card.hw_info)) {
846 		msg(_("  HW Info = %s"), d->card.hw_info);
847 	}
848 
849 	return (0);
850 }
851 
852 int
853 do_show_control(int argc, char **argv)
854 {
855 	int		optc;
856 	int		rval = 0;
857 	int		verbose = 0;
858 	device_t	*d;
859 	char		*devname = NULL;
860 	int		i;
861 	int		j;
862 	int		rv;
863 	char		*n;
864 	cinfo_t		*cinfop;
865 
866 	while ((optc = getopt(argc, argv, "d:v")) != EOF) {
867 		switch (optc) {
868 		case 'd':
869 			devname = optarg;
870 			break;
871 		case 'v':
872 			verbose++;
873 			break;
874 		default:
875 			help();
876 			return (-1);
877 		}
878 	}
879 	argc -= optind;
880 	argv += optind;
881 
882 	if ((d = find_device(devname)) == NULL) {
883 		return (ENODEV);
884 	}
885 
886 	print_header(NULL, verbose);
887 	if (argc == 0) {
888 		/* do them all! */
889 		for (i = 0; i < d->cmax; i++) {
890 
891 			cinfop = &d->controls[i];
892 			rv = print_control(NULL, d, cinfop, verbose);
893 			rval = rval ? rval : rv;
894 		}
895 		return (rval);
896 	}
897 
898 	for (i = 0; i < argc; i++) {
899 		for (j = 0; j < d->cmax; j++) {
900 			cinfop = &d->controls[j];
901 			n = strrchr(cinfop->ci.extname, '_');
902 			n = n ? n + 1 : cinfop->ci.extname;
903 			if (strcmp(argv[i], n) == 0) {
904 				rv = print_control(NULL, d, cinfop, verbose);
905 				rval = rval ? rval : rv;
906 				break;
907 			}
908 		}
909 		/* Didn't find requested control */
910 		if (j == d->cmax) {
911 			warn(_("No such control: %s\n"), argv[i]);
912 			rval = rval ? rval : ENODEV;
913 		}
914 	}
915 
916 	return (rval);
917 }
918 
919 int
920 do_set_control(int argc, char **argv)
921 {
922 	int		optc;
923 	int		rval = 0;
924 	int		verbose = 0;
925 	device_t	*d;
926 	char		*devname = NULL;
927 	char		*cname;
928 	char		*value;
929 	int		i;
930 	int		found;
931 	int		rv;
932 	char		*n;
933 	cinfo_t		*cinfop;
934 
935 	while ((optc = getopt(argc, argv, "d:v")) != EOF) {
936 		switch (optc) {
937 		case 'd':
938 			devname = optarg;
939 			break;
940 		case 'v':
941 			verbose = 1;
942 			break;
943 		default:
944 			help();
945 			return (-1);
946 		}
947 	}
948 	argc -= optind;
949 	argv += optind;
950 
951 	if (argc != 2) {
952 		help();
953 		return (-1);
954 	}
955 	cname = argv[0];
956 	value = argv[1];
957 
958 	if ((d = find_device(devname)) == NULL) {
959 		return (ENODEV);
960 	}
961 
962 	for (i = 0, found = 0; i < d->cmax; i++) {
963 		cinfop = &d->controls[i];
964 		n = strrchr(cinfop->ci.extname, '_');
965 		n = n ? n + 1 : cinfop->ci.extname;
966 		if (strcmp(cname, n) != 0) {
967 			continue;
968 		}
969 		found = 1;
970 		rv = set_device_control(d, cinfop, value, verbose);
971 		rval = rval ? rval : rv;
972 	}
973 	if (!found) {
974 		warn(_("No such control: %s\n"), cname);
975 	}
976 
977 	return (rval);
978 }
979 
980 int
981 do_save_controls(int argc, char **argv)
982 {
983 	int		optc;
984 	int		rval = 0;
985 	device_t	*d;
986 	char		*devname = NULL;
987 	char		*fname;
988 	int		i;
989 	int		rv;
990 	cinfo_t		*cinfop;
991 	FILE		*fp;
992 	int		fd;
993 	int		mode;
994 
995 	mode = O_WRONLY | O_CREAT | O_EXCL;
996 
997 	while ((optc = getopt(argc, argv, "d:f")) != EOF) {
998 		switch (optc) {
999 		case 'd':
1000 			devname = optarg;
1001 			break;
1002 		case 'f':
1003 			mode &= ~O_EXCL;
1004 			mode |= O_TRUNC;
1005 			break;
1006 		default:
1007 			help();
1008 			return (-1);
1009 		}
1010 	}
1011 	argc -= optind;
1012 	argv += optind;
1013 
1014 	if (argc != 1) {
1015 		help();
1016 		return (-1);
1017 	}
1018 	fname = argv[0];
1019 
1020 	if ((d = find_device(devname)) == NULL) {
1021 		return (ENODEV);
1022 	}
1023 
1024 	if ((fd = open(fname, mode, 0666)) < 0) {
1025 		perror(_("Failed to create file"));
1026 		return (errno);
1027 	}
1028 
1029 	if ((fp = fdopen(fd, "w")) == NULL) {
1030 		perror(_("Unable to open file\n"));
1031 		(void) close(fd);
1032 		(void) unlink(fname);
1033 		return (errno);
1034 	}
1035 
1036 	(void) fprintf(fp, "# Device: %s\n", d->mixer.devnode);
1037 	(void) fprintf(fp, "# Name    = %s\n", d->card.shortname);
1038 	(void) fprintf(fp, "# Config  = %s\n", d->card.longname);
1039 
1040 	if (strlen(d->card.hw_info)) {
1041 		(void) fprintf(fp, "# HW Info = %s", d->card.hw_info);
1042 	}
1043 	(void) fprintf(fp, "#\n");
1044 
1045 	print_header(fp, 0);
1046 
1047 	for (i = 0; i < d->cmax; i++) {
1048 		cinfop = &d->controls[i];
1049 		rv = print_control(fp, d, cinfop, 0);
1050 		rval = rval ? rval : rv;
1051 	}
1052 
1053 	(void) fclose(fp);
1054 
1055 	return (rval);
1056 }
1057 
1058 int
1059 do_load_controls(int argc, char **argv)
1060 {
1061 	int	optc;
1062 	int	rval = 0;
1063 	device_t	*d;
1064 	char		*devname = NULL;
1065 	char	*fname;
1066 	char	*cname;
1067 	char	*value;
1068 	int	i;
1069 	int	rv;
1070 	cinfo_t	*cinfop;
1071 	FILE	*fp;
1072 	char	linebuf[MAXLINE];
1073 	int	lineno = 0;
1074 	int	found;
1075 
1076 	while ((optc = getopt(argc, argv, "d:")) != EOF) {
1077 		switch (optc) {
1078 		case 'd':
1079 			devname = optarg;
1080 			break;
1081 		default:
1082 			help();
1083 			return (-1);
1084 		}
1085 	}
1086 	argc -= optind;
1087 	argv += optind;
1088 
1089 	if (argc != 1) {
1090 		help();
1091 		return (-1);
1092 	}
1093 	fname = argv[0];
1094 
1095 	if ((d = find_device(devname)) == NULL) {
1096 		return (ENODEV);
1097 	}
1098 
1099 	if ((fp = fopen(fname, "r")) == NULL) {
1100 		perror(_("Unable to open file"));
1101 		return (errno);
1102 	}
1103 
1104 	while (fgets(linebuf, sizeof (linebuf), fp) != NULL) {
1105 		lineno++;
1106 		if (linebuf[strlen(linebuf) - 1] != '\n') {
1107 			warn(_("Warning: line too long at line %d\n"), lineno);
1108 			/* read in the rest of the line and discard it */
1109 			while (fgets(linebuf, sizeof (linebuf), fp) != NULL &&
1110 			    (linebuf[strlen(linebuf) - 1] != '\n')) {
1111 				continue;
1112 			}
1113 			continue;
1114 		}
1115 
1116 		/* we have a good line ... */
1117 		cname = strtok(linebuf, " \t\n");
1118 		/* skip comments and blank lines */
1119 		if ((cname == NULL) || (cname[0] == '#')) {
1120 			continue;
1121 		}
1122 		value = strtok(NULL, " \t\n");
1123 		if ((value == NULL) || (*cname == 0)) {
1124 			warn(_("Warning: missing value at line %d\n"), lineno);
1125 			continue;
1126 		}
1127 
1128 		for (i = 0, found = 0; i < d->cmax; i++) {
1129 			/* save and restore requires an exact match */
1130 			cinfop = &d->controls[i];
1131 			if (strcmp(cinfop->ci.extname, cname) != 0) {
1132 				continue;
1133 			}
1134 			found = 1;
1135 			rv = set_device_control(d, cinfop, value, 0);
1136 			rval = rval ? rval : rv;
1137 		}
1138 		if (!found) {
1139 			warn(_("No such control: %s\n"), cname);
1140 		}
1141 	}
1142 	(void) fclose(fp);
1143 
1144 	return (rval);
1145 }
1146 
1147 int
1148 main(int argc, char **argv)
1149 {
1150 	int rv = 0;
1151 	int opt;
1152 
1153 	(void) setlocale(LC_ALL, "");
1154 	(void) textdomain(TEXT_DOMAIN);
1155 
1156 	while ((opt = getopt(argc, argv, "h")) != EOF) {
1157 		switch (opt) {
1158 		case 'h':
1159 			help();
1160 			rv = 0;
1161 			goto OUT;
1162 		default:
1163 			rv = EINVAL;
1164 			break;
1165 		}
1166 	}
1167 
1168 	if (rv) {
1169 		goto OUT;
1170 	}
1171 
1172 	argc -= optind;
1173 	argv += optind;
1174 
1175 	rv = load_devices();
1176 	if (rv != 0) {
1177 		goto OUT;
1178 	}
1179 
1180 	if (argc < 1) {
1181 		help();
1182 		rv = EINVAL;
1183 	} else if (strcmp(argv[0], "help") == 0) {
1184 		help();
1185 		rv = 0;
1186 	} else if (strcmp(argv[0], "list-devices") == 0) {
1187 		rv = do_list_devices(argc, argv);
1188 	} else if (strcmp(argv[0], "show-device") == 0) {
1189 		rv = do_show_device(argc, argv);
1190 	} else if (strcmp(argv[0], "show-control") == 0) {
1191 		rv = do_show_control(argc, argv);
1192 	} else if (strcmp(argv[0], "set-control") == 0) {
1193 		rv = do_set_control(argc, argv);
1194 	} else if (strcmp(argv[0], "load-controls") == 0) {
1195 		rv = do_load_controls(argc, argv);
1196 	} else if (strcmp(argv[0], "save-controls") == 0) {
1197 		rv = do_save_controls(argc, argv);
1198 	} else {
1199 		help();
1200 		rv = EINVAL;
1201 	}
1202 
1203 OUT:
1204 	free_devices();
1205 	return (rv);
1206 }
1207