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
msg(char * fmt,...)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
warn(char * fmt,...)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
free_device(device_t * d)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
free_devices(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 *
alloc_device(void)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 *
get_enum_str(cinfo_t * cinfop,int idx)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
get_device_info(device_t * d)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
load_devices(void)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
ctype_valid(int type)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
print_control_line(FILE * sfp,col_prt_t * colp,int vopt)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
print_header(FILE * sfp,int vopt)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
print_control(FILE * sfp,device_t * d,cinfo_t * cinfop,int vopt)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
set_device_control(device_t * d,cinfo_t * cinfop,char * wstr,int vopt)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
help(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
device_devt(char * name)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 *
find_device(char * name)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
do_list_devices(int argc,char ** argv)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
do_show_device(int argc,char ** argv)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
do_show_control(int argc,char ** argv)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
do_set_control(int argc,char ** argv)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
do_save_controls(int argc,char ** argv)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
do_load_controls(int argc,char ** argv)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
mixer_walker(di_devlink_t dlink,void * arg)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
do_init_devices(int argc,char ** argv)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
main(int argc,char ** argv)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