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