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 }