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                         /*
 503                          * Apparently the driver gave us bogus data for this
 504                          * control, so we will ignore it.
 505                          *
 506                          * Don't confuse the user by returning an error status.
 507                          */
 508                         return (0);
 509                 }
 510 
 511                 (void) snprintf(valbuf, sizeof (valbuf), "%s", str);
 512                 break;
 513 
 514         default:
 515                 return (0);
 516         }
 517 
 518         /*
 519          * possible control values (range/selection)
 520          */
 521         switch (cinfop->ci.type) {
 522         case MIXT_ONOFF:
 523                 (void) snprintf(selbuf, sizeof (selbuf), _("on,off"));
 524                 break;
 525 
 526         case MIXT_MONOSLIDER:
 527                 (void) snprintf(selbuf, sizeof (selbuf), "%d-%d",
 528                     cinfop->ci.minvalue, cinfop->ci.maxvalue);
 529                 break;
 530         case MIXT_STEREOSLIDER:
 531                 (void) snprintf(selbuf, sizeof (selbuf), "%d-%d:%d-%d",
 532                     cinfop->ci.minvalue, cinfop->ci.maxvalue,
 533                     cinfop->ci.minvalue, cinfop->ci.maxvalue);
 534                 break;
 535 
 536         case MIXT_ENUM:
 537                 /*
 538                  * display the first choice on the same line, then display
 539                  * the rest on multiple lines
 540                  */
 541                 selbuf[0] = 0;
 542                 for (i = 0; i < cinfop->ci.maxvalue; i++) {
 543                         str = get_enum_str(cinfop, i);
 544                         if (str == NULL)
 545                                 continue;
 546 
 547                         if ((strlen(str) + 1 + strlen(selbuf)) >=
 548                             sizeof (selbuf)) {
 549                                 break;
 550                         }
 551                         if (strlen(selbuf)) {
 552                                 (void) strlcat(selbuf, ",", sizeof (selbuf));
 553                         }
 554 
 555                         (void) strlcat(selbuf, str, sizeof (selbuf));
 556                 }
 557                 idx = i;
 558                 break;
 559 
 560         default:
 561                 (void) snprintf(selbuf, sizeof (selbuf), "-");
 562         }
 563 
 564         col.col_dv = devnm;
 565         col.col_nm = strlen(cinfop->ci.extname) ?
 566             cinfop->ci.extname : cinfop->ci.id;
 567         while (strchr(col.col_nm, '_') != NULL) {
 568                 col.col_nm = strchr(col.col_nm, '_') + 1;
 569         }
 570         col.col_val = valbuf;
 571         col.col_sel = selbuf;
 572         print_control_line(sfp, &col, vopt);
 573 
 574         /* non-verbose mode prints don't display the enum values */
 575         if ((!vopt) || (sfp != NULL)) {
 576                 return (0);
 577         }
 578 
 579         /* print leftover enum value selections */
 580         while ((idx >= 0) && (idx < cinfop->ci.maxvalue)) {
 581                 selbuf[0] = 0;
 582                 for (i = idx; i < cinfop->ci.maxvalue; i++) {
 583                         str = get_enum_str(cinfop, i);
 584                         if (str == NULL)
 585                                 continue;
 586 
 587                         if ((strlen(str) + 1 + strlen(selbuf)) >=
 588                             sizeof (selbuf)) {
 589                                 break;
 590                         }
 591                         if (strlen(selbuf)) {
 592                                 (void) strlcat(selbuf, ",", sizeof (selbuf));
 593                         }
 594 
 595                         (void) strlcat(selbuf, str, sizeof (selbuf));
 596                 }
 597                 idx = i;
 598                 col.col_dv = NULL;
 599                 col.col_nm = NULL;
 600                 col.col_val = NULL;
 601                 col.col_sel = selbuf;
 602                 print_control_line(sfp, &col, vopt);
 603         }
 604 
 605         return (0);
 606 }
 607 
 608 
 609 static int
 610 set_device_control(device_t *d, cinfo_t *cinfop, char *wstr, int vopt)
 611 {
 612         int mfd = d->mfd;
 613         oss_mixer_value cval;
 614         int wlen = strlen(wstr);
 615         int lval, rval;
 616         char *lstr, *rstr;
 617         char *str;
 618         int i;
 619         int rv = -1;
 620 
 621         cval.dev = -1;
 622         cval.ctrl = cinfop->ci.ctrl;
 623         cval.value = 0;
 624 
 625         switch (cinfop->ci.type) {
 626         case MIXT_ONOFF:
 627                 cval.value = (strncmp(_("on"), wstr, wlen) == 0) ? 1 : 0;
 628                 break;
 629 
 630         case MIXT_MONOSLIDER:
 631                 cval.value = atoi(wstr);
 632                 break;
 633 
 634         case MIXT_STEREOSLIDER:
 635                 lstr = wstr;
 636                 rstr = strchr(wstr, ':');
 637                 if (rstr != NULL) {
 638                         *rstr = '\0';
 639                         rstr++;
 640 
 641                         rval = atoi(rstr);
 642                         lval = atoi(lstr);
 643 
 644                         rstr--;
 645                         *rstr = ':';
 646                 } else {
 647                         lval = atoi(lstr);
 648                         rval = lval;
 649                 }
 650 
 651                 cval.value = AUDIO_CTRL_STEREO_VAL(lval, rval);
 652                 break;
 653 
 654         case MIXT_ENUM:
 655                 for (i = 0; i < cinfop->ci.maxvalue; i++) {
 656                         str = get_enum_str(cinfop, i);
 657                         if (str == NULL)
 658                                 continue;
 659 
 660                         if (strncmp(wstr, str, wlen) == 0) {
 661                                 cval.value = i;
 662                                 break;
 663                         }
 664                 }
 665 
 666                 if (i >= cinfop->ci.maxvalue) {
 667                         warn(_("Invalid enumeration value\n"));
 668                         return (EINVAL);
 669                 }
 670                 break;
 671 
 672         default:
 673                 warn(_("Unsupported control type: %d\n"), cinfop->ci.type);
 674                 return (EINVAL);
 675         }
 676 
 677         if (vopt) {
 678                 msg(_("%s: '%s' set to '%s'\n"), d->card.shortname,
 679                     cinfop->ci.extname, wstr);
 680         }
 681 
 682         if (ioctl(mfd, SNDCTL_MIX_WRITE, &cval) < 0) {
 683                 rv = errno;
 684                 perror(_("Error writing control"));
 685                 return (rv);
 686         }
 687 
 688         rv = 0;
 689         return (rv);
 690 }
 691 
 692 
 693 static void
 694 help(void)
 695 {
 696 #define HELP_STR        _(                                              \
 697 "audioctl list-devices\n"                                               \
 698 "       list all audio devices\n"                                       \
 699 "\n"                                                                    \
 700 "audioctl show-device [ -v ] [ -d <device> ]\n"                           \
 701 "       display information about an audio device\n"                    \
 702 "\n"                                                                    \
 703 "audioctl show-control [ -v ] [ -d <device> ] [ <control> ... ]\n"  \
 704 "       get the value of a specific control (all if not specified)\n"   \
 705 "\n"                                                                    \
 706 "audioctl set-control [ -v ] [ -d <device> ] <control> <value>\n"     \
 707 "       set the value of a specific control\n"                          \
 708 "\n"                                                                    \
 709 "audioctl save-controls [ -d <device> ] [ -f ] <file>\n"            \
 710 "       save all control settings for the device to a file\n"           \
 711 "\n"                                                                    \
 712 "audioctl load-controls [ -d <device> ] <file>\n"                   \
 713 "       restore previously saved control settings to device\n"          \
 714 "\n"                                                                    \
 715 "audioctl help\n"                                                       \
 716 "       show this message.\n")
 717 
 718         (void) fprintf(stderr, HELP_STR);
 719 }
 720 
 721 dev_t
 722 device_devt(char *name)
 723 {
 724         struct stat     sbuf;
 725 
 726         if ((stat(name, &sbuf) != 0) ||
 727             ((sbuf.st_mode & S_IFCHR) == 0)) {
 728                 /* Not a device node! */
 729                 return (0);
 730         }
 731 
 732         return (makedev(major(sbuf.st_rdev),
 733             minor(sbuf.st_rdev) & ~(AUDIO_MN_TYPE_MASK)));
 734 }
 735 
 736 static device_t *
 737 find_device(char *name)
 738 {
 739         dev_t           devt;
 740         device_t        *d;
 741 
 742         /*
 743          * User may have specified:
 744          *
 745          * /dev/dsp[<num>]
 746          * /dev/mixer[<num>]
 747          * /dev/audio[<num>9]
 748          * /dev/audioctl[<num>]
 749          * /dev/sound/<num>{,ctl,dsp,mixer}
 750          * /dev/sound/<driver>:<num>{,ctl,dsp,mixer}
 751          *
 752          * We can canonicalize these by looking at the dev_t though.
 753          */
 754 
 755         if (load_devices() != 0) {
 756                 return (NULL);
 757         }
 758 
 759         if (name == NULL)
 760                 name = getenv("AUDIODEV");
 761 
 762         if ((name == NULL) ||
 763             (strcmp(name, "/dev/mixer") == 0)) {
 764                 /* /dev/mixer node doesn't point to real hw */
 765                 name = "/dev/dsp";
 766         }
 767 
 768         if (*name == '/') {
 769                 /* if we have a full path, convert to the devt */
 770                 if ((devt = device_devt(name)) == 0) {
 771                         warn(_("No such audio device.\n"));
 772                         return (NULL);
 773                 }
 774                 name = NULL;
 775         }
 776 
 777         for (d = devices; d != NULL; d = d->nextp) {
 778                 oss_card_info *card = &d->card;
 779 
 780                 if ((name) && (strcmp(name, card->shortname) == 0)) {
 781                         return (d);
 782                 }
 783                 if (devt == d->devt) {
 784                         return (d);
 785                 }
 786         }
 787 
 788         warn(_("No such audio device.\n"));
 789         return (NULL);
 790 }
 791 
 792 int
 793 do_list_devices(int argc, char **argv)
 794 {
 795         int             optc;
 796         int             verbose = 0;
 797         device_t        *d;
 798 
 799         while ((optc = getopt(argc, argv, "v")) != EOF) {
 800                 switch (optc) {
 801                 case 'v':
 802                         verbose++;
 803                         break;
 804                 default:
 805                         help();
 806                         return (-1);
 807                 }
 808         }
 809         argc -= optind;
 810         argv += optind;
 811         if (argc != 0) {
 812                 help();
 813                 return (-1);
 814         }
 815 
 816         if (load_devices() != 0) {
 817                 return (-1);
 818         }
 819 
 820         for (d = devices; d != NULL; d = d->nextp) {
 821 
 822                 if ((d->mixer.enabled == 0) && (!verbose))
 823                         continue;
 824 
 825                 if (verbose) {
 826                         msg(_("%s (%s)\n"), d->card.shortname,
 827                             d->mixer.devnode);
 828                 } else {
 829                         msg(_("%s\n"), d->card.shortname);
 830                 }
 831         }
 832 
 833         return (0);
 834 }
 835 
 836 int
 837 do_show_device(int argc, char **argv)
 838 {
 839         int             optc;
 840         char            *devname = NULL;
 841         char            *p, *n;
 842         const char      *m;
 843         device_t        *d;
 844 
 845         while ((optc = getopt(argc, argv, "d:v")) != EOF) {
 846                 switch (optc) {
 847                 case 'd':
 848                         devname = optarg;
 849                         break;
 850                 case 'v':
 851                         break;
 852                 default:
 853                         help();
 854                         return (-1);
 855                 }
 856         }
 857         argc -= optind;
 858         argv += optind;
 859         if (argc != 0) {
 860                 help();
 861                 return (-1);
 862         }
 863 
 864         if ((d = find_device(devname)) == NULL) {
 865                 return (ENODEV);
 866         }
 867 
 868         msg(_("Device: %s\n"), d->mixer.devnode);
 869         msg(_("  Name    = %s\n"), d->card.shortname);
 870         msg(_("  Config  = %s\n"), d->card.longname);
 871 
 872         for (m = "  HW Info = %s\n", p = d->card.hw_info;
 873             p != NULL && strlen(p) != 0;
 874             m = "\t    %s\n", p = n) {
 875                 if ((n = strchr(p, '\n')) != NULL)
 876                         *n++ = '\0';
 877                 msg(_(m), p);
 878         }
 879 
 880         return (0);
 881 }
 882 
 883 int
 884 do_show_control(int argc, char **argv)
 885 {
 886         int             optc;
 887         int             rval = 0;
 888         int             verbose = 0;
 889         device_t        *d;
 890         char            *devname = NULL;
 891         int             i;
 892         int             j;
 893         int             rv;
 894         char            *n;
 895         cinfo_t         *cinfop;
 896 
 897         while ((optc = getopt(argc, argv, "d:v")) != EOF) {
 898                 switch (optc) {
 899                 case 'd':
 900                         devname = optarg;
 901                         break;
 902                 case 'v':
 903                         verbose++;
 904                         break;
 905                 default:
 906                         help();
 907                         return (-1);
 908                 }
 909         }
 910         argc -= optind;
 911         argv += optind;
 912 
 913         if ((d = find_device(devname)) == NULL) {
 914                 return (ENODEV);
 915         }
 916 
 917         print_header(NULL, verbose);
 918         if (argc == 0) {
 919                 /* do them all! */
 920                 for (i = 0; i < d->cmax; i++) {
 921 
 922                         cinfop = &d->controls[i];
 923                         rv = print_control(NULL, d, cinfop, verbose);
 924                         rval = rval ? rval : rv;
 925                 }
 926                 return (rval);
 927         }
 928 
 929         for (i = 0; i < argc; i++) {
 930                 for (j = 0; j < d->cmax; j++) {
 931                         cinfop = &d->controls[j];
 932                         n = strrchr(cinfop->ci.extname, '_');
 933                         n = n ? n + 1 : cinfop->ci.extname;
 934                         if (strcmp(argv[i], n) == 0) {
 935                                 rv = print_control(NULL, d, cinfop, verbose);
 936                                 rval = rval ? rval : rv;
 937                                 break;
 938                         }
 939                 }
 940                 /* Didn't find requested control */
 941                 if (j == d->cmax) {
 942                         warn(_("No such control: %s\n"), argv[i]);
 943                         rval = rval ? rval : ENODEV;
 944                 }
 945         }
 946 
 947         return (rval);
 948 }
 949 
 950 int
 951 do_set_control(int argc, char **argv)
 952 {
 953         int             optc;
 954         int             rval = 0;
 955         int             verbose = 0;
 956         device_t        *d;
 957         char            *devname = NULL;
 958         char            *cname;
 959         char            *value;
 960         int             i;
 961         int             found;
 962         int             rv;
 963         char            *n;
 964         cinfo_t         *cinfop;
 965 
 966         while ((optc = getopt(argc, argv, "d:v")) != EOF) {
 967                 switch (optc) {
 968                 case 'd':
 969                         devname = optarg;
 970                         break;
 971                 case 'v':
 972                         verbose = 1;
 973                         break;
 974                 default:
 975                         help();
 976                         return (-1);
 977                 }
 978         }
 979         argc -= optind;
 980         argv += optind;
 981 
 982         if (argc != 2) {
 983                 help();
 984                 return (-1);
 985         }
 986         cname = argv[0];
 987         value = argv[1];
 988 
 989         if ((d = find_device(devname)) == NULL) {
 990                 return (ENODEV);
 991         }
 992 
 993         for (i = 0, found = 0; i < d->cmax; i++) {
 994                 cinfop = &d->controls[i];
 995                 n = strrchr(cinfop->ci.extname, '_');
 996                 n = n ? n + 1 : cinfop->ci.extname;
 997                 if (strcmp(cname, n) != 0) {
 998                         continue;
 999                 }
1000                 found = 1;
1001                 rv = set_device_control(d, cinfop, value, verbose);
1002                 rval = rval ? rval : rv;
1003         }
1004         if (!found) {
1005                 warn(_("No such control: %s\n"), cname);
1006         }
1007 
1008         return (rval);
1009 }
1010 
1011 int
1012 do_save_controls(int argc, char **argv)
1013 {
1014         int             optc;
1015         int             rval = 0;
1016         device_t        *d;
1017         char            *devname = NULL;
1018         char            *fname;
1019         int             i;
1020         int             rv;
1021         cinfo_t         *cinfop;
1022         FILE            *fp;
1023         int             fd;
1024         int             mode;
1025         char            *p, *n;
1026         const char      *m;
1027 
1028         mode = O_WRONLY | O_CREAT | O_EXCL;
1029 
1030         while ((optc = getopt(argc, argv, "d:f")) != EOF) {
1031                 switch (optc) {
1032                 case 'd':
1033                         devname = optarg;
1034                         break;
1035                 case 'f':
1036                         mode &= ~O_EXCL;
1037                         mode |= O_TRUNC;
1038                         break;
1039                 default:
1040                         help();
1041                         return (-1);
1042                 }
1043         }
1044         argc -= optind;
1045         argv += optind;
1046 
1047         if (argc != 1) {
1048                 help();
1049                 return (-1);
1050         }
1051         fname = argv[0];
1052 
1053         if ((d = find_device(devname)) == NULL) {
1054                 return (ENODEV);
1055         }
1056 
1057         if ((fd = open(fname, mode, 0666)) < 0) {
1058                 perror(_("Failed to create file"));
1059                 return (errno);
1060         }
1061 
1062         if ((fp = fdopen(fd, "w")) == NULL) {
1063                 perror(_("Unable to open file\n"));
1064                 (void) close(fd);
1065                 (void) unlink(fname);
1066                 return (errno);
1067         }
1068 
1069         (void) fprintf(fp, "# Device: %s\n", d->mixer.devnode);
1070         (void) fprintf(fp, "# Name    = %s\n", d->card.shortname);
1071         (void) fprintf(fp, "# Config  = %s\n", d->card.longname);
1072 
1073         for (m = "# HW Info = %s\n", p = d->card.hw_info;
1074             p != NULL && strlen(p) != 0;
1075             m = "#\t    %s\n", p = n) {
1076                 if ((n = strchr(p, '\n')) != NULL)
1077                         *n++ = '\0';
1078                 fprintf(fp, m, p);
1079         }
1080         (void) fprintf(fp, "#\n");
1081 
1082         print_header(fp, 0);
1083 
1084         for (i = 0; i < d->cmax; i++) {
1085                 cinfop = &d->controls[i];
1086                 rv = print_control(fp, d, cinfop, 0);
1087                 rval = rval ? rval : rv;
1088         }
1089 
1090         (void) fclose(fp);
1091 
1092         return (rval);
1093 }
1094 
1095 int
1096 do_load_controls(int argc, char **argv)
1097 {
1098         int     optc;
1099         int     rval = 0;
1100         device_t        *d;
1101         char            *devname = NULL;
1102         char    *fname;
1103         char    *cname;
1104         char    *value;
1105         int     i;
1106         int     rv;
1107         cinfo_t *cinfop;
1108         FILE    *fp;
1109         char    linebuf[MAXLINE];
1110         int     lineno = 0;
1111         int     found;
1112 
1113         while ((optc = getopt(argc, argv, "d:")) != EOF) {
1114                 switch (optc) {
1115                 case 'd':
1116                         devname = optarg;
1117                         break;
1118                 default:
1119                         help();
1120                         return (-1);
1121                 }
1122         }
1123         argc -= optind;
1124         argv += optind;
1125 
1126         if (argc != 1) {
1127                 help();
1128                 return (-1);
1129         }
1130         fname = argv[0];
1131 
1132         if ((d = find_device(devname)) == NULL) {
1133                 return (ENODEV);
1134         }
1135 
1136         if ((fp = fopen(fname, "r")) == NULL) {
1137                 perror(_("Unable to open file"));
1138                 return (errno);
1139         }
1140 
1141         while (fgets(linebuf, sizeof (linebuf), fp) != NULL) {
1142                 lineno++;
1143                 if (linebuf[strlen(linebuf) - 1] != '\n') {
1144                         warn(_("Warning: line too long at line %d\n"), lineno);
1145                         /* read in the rest of the line and discard it */
1146                         while (fgets(linebuf, sizeof (linebuf), fp) != NULL &&
1147                             (linebuf[strlen(linebuf) - 1] != '\n')) {
1148                                 continue;
1149                         }
1150                         continue;
1151                 }
1152 
1153                 /* we have a good line ... */
1154                 cname = strtok(linebuf, " \t\n");
1155                 /* skip comments and blank lines */
1156                 if ((cname == NULL) || (cname[0] == '#')) {
1157                         continue;
1158                 }
1159                 value = strtok(NULL, " \t\n");
1160                 if ((value == NULL) || (*cname == 0)) {
1161                         warn(_("Warning: missing value at line %d\n"), lineno);
1162                         continue;
1163                 }
1164 
1165                 for (i = 0, found = 0; i < d->cmax; i++) {
1166                         /* save and restore requires an exact match */
1167                         cinfop = &d->controls[i];
1168                         if (strcmp(cinfop->ci.extname, cname) != 0) {
1169                                 continue;
1170                         }
1171                         found = 1;
1172                         rv = set_device_control(d, cinfop, value, 0);
1173                         rval = rval ? rval : rv;
1174                 }
1175                 if (!found) {
1176                         warn(_("No such control: %s\n"), cname);
1177                 }
1178         }
1179         (void) fclose(fp);
1180 
1181         return (rval);
1182 }
1183 
1184 int
1185 mixer_walker(di_devlink_t dlink, void *arg)
1186 {
1187         const char      *link;
1188         int             num;
1189         int             fd;
1190         int             verbose = *(int *)arg;
1191         int             num_offset;
1192 
1193         num_offset = sizeof ("/dev/mixer") - 1;
1194 
1195         link = di_devlink_path(dlink);
1196 
1197         if ((link == NULL) ||
1198             (strncmp(link, "/dev/mixer", num_offset) != 0) ||
1199             (!isdigit(link[num_offset]))) {
1200                 return (DI_WALK_CONTINUE);
1201         }
1202 
1203         num = atoi(link + num_offset);
1204         if ((fd = open(link, O_RDWR)) < 0) {
1205                 if (verbose) {
1206                         if (errno == ENOENT) {
1207                                 msg(_("Device %s not present.\n"), link);
1208                         } else {
1209                                 msg(_("Unable to open device %s: %s\n"),
1210                                     link, strerror(errno));
1211                         }
1212                 }
1213                 return (DI_WALK_CONTINUE);
1214         }
1215 
1216         if (verbose) {
1217                 msg(_("Initializing link %s: "), link);
1218         }
1219         if (ioctl(fd, SNDCTL_SUN_SEND_NUMBER, &num) != 0) {
1220                 if (verbose) {
1221                         msg(_("failed: %s\n"), strerror(errno));
1222                 }
1223         } else {
1224                 if (verbose) {
1225                         msg(_("done.\n"));
1226                 }
1227         }
1228         (void) close(fd);
1229         return (DI_WALK_CONTINUE);
1230 }
1231 
1232 int
1233 do_init_devices(int argc, char **argv)
1234 {
1235         int                     optc;
1236         di_devlink_handle_t     dlh;
1237         int                     verbose = 0;
1238 
1239         while ((optc = getopt(argc, argv, "v")) != EOF) {
1240                 switch (optc) {
1241                 case 'v':
1242                         verbose = 1;
1243                         break;
1244                 default:
1245                         help();
1246                         return (-1);
1247                 }
1248         }
1249         argc -= optind;
1250         argv += optind;
1251 
1252         if (argc != 0) {
1253                 help();
1254                 return (-1);
1255         }
1256 
1257         dlh = di_devlink_init(NULL, 0);
1258         if (dlh == NULL) {
1259                 perror(_("Unable to initialize devlink handle"));
1260                 return (-1);
1261         }
1262 
1263         if (di_devlink_walk(dlh, "^mixer", NULL, 0, &verbose,
1264             mixer_walker) != 0) {
1265                 perror(_("Unable to walk devlinks"));
1266                 return (-1);
1267         }
1268         return (0);
1269 }
1270 
1271 int
1272 main(int argc, char **argv)
1273 {
1274         int rv = 0;
1275         int opt;
1276 
1277         (void) setlocale(LC_ALL, "");
1278         (void) textdomain(TEXT_DOMAIN);
1279 
1280         while ((opt = getopt(argc, argv, "h")) != EOF) {
1281                 switch (opt) {
1282                 case 'h':
1283                         help();
1284                         rv = 0;
1285                         goto OUT;
1286                 default:
1287                         rv = EINVAL;
1288                         break;
1289                 }
1290         }
1291 
1292         if (rv) {
1293                 goto OUT;
1294         }
1295 
1296         argc -= optind;
1297         argv += optind;
1298 
1299         if (argc < 1) {
1300                 help();
1301                 rv = EINVAL;
1302         } else if (strcmp(argv[0], "help") == 0) {
1303                 help();
1304                 rv = 0;
1305         } else if (strcmp(argv[0], "list-devices") == 0) {
1306                 rv = do_list_devices(argc, argv);
1307         } else if (strcmp(argv[0], "show-device") == 0) {
1308                 rv = do_show_device(argc, argv);
1309         } else if (strcmp(argv[0], "show-control") == 0) {
1310                 rv = do_show_control(argc, argv);
1311         } else if (strcmp(argv[0], "set-control") == 0) {
1312                 rv = do_set_control(argc, argv);
1313         } else if (strcmp(argv[0], "load-controls") == 0) {
1314                 rv = do_load_controls(argc, argv);
1315         } else if (strcmp(argv[0], "save-controls") == 0) {
1316                 rv = do_save_controls(argc, argv);
1317         } else if (strcmp(argv[0], "init-devices") == 0) {
1318                 rv = do_init_devices(argc, argv);
1319         } else {
1320                 help();
1321                 rv = EINVAL;
1322         }
1323 
1324 OUT:
1325         free_devices();
1326         return (rv);
1327 }