1/* 2 * Advanced Linux Sound Architecture Control Program 3 * Copyright (c) by Abramo Bagnara <abramo@alsa-project.org> 4 * Jaroslav Kysela <perex@perex.cz> 5 * 6 * 7 * This program is free software; you can redistribute it and/or modify 8 * it under the terms of the GNU General Public License as published by 9 * the Free Software Foundation; either version 2 of the License, or 10 * (at your option) any later version. 11 * 12 * This program is distributed in the hope that it will be useful, 13 * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 * GNU General Public License for more details. 16 * 17 * You should have received a copy of the GNU General Public License 18 * along with this program; if not, write to the Free Software 19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 20 * 21 */ 22 23#include "aconfig.h" 24#include "version.h" 25#include <getopt.h> 26#include <stdarg.h> 27#include <stdio.h> 28#include <assert.h> 29#include <errno.h> 30#include <syslog.h> 31#include <sched.h> 32#include "alsactl.h" 33#include "os_compat.h" 34 35#ifndef SYS_ASOUND_DIR 36#define SYS_ASOUND_DIR "/var/lib/alsa" 37#endif 38#ifndef SYS_ASOUNDRC 39#define SYS_ASOUNDRC SYS_ASOUND_DIR "/asound.state" 40#endif 41#ifndef SYS_PIDFILE 42#define SYS_PIDFILE "/var/run/alsactl.pid" 43#endif 44#ifndef SYS_LOCKPATH 45#define SYS_LOCKPATH "/var/lock" 46#endif 47 48int debugflag = 0; 49int force_restore = 1; 50int ignore_nocards = 0; 51int do_lock = 0; 52int use_syslog = 0; 53char *command; 54char *statefile = NULL; 55char *lockpath = SYS_LOCKPATH; 56char *lockfile = SYS_LOCKFILE; 57 58#define TITLE 0x0100 59#define HEADER 0x0200 60#define FILEARG 0x0400 61#define ENVARG 0x0800 62#define INTARG 0x1000 63#define EMPCMD 0x2000 64#define CARDCMD 0x4000 65#define KILLCMD 0x8000 66 67struct arg { 68 int sarg; 69 char *larg; 70 char *comment; 71}; 72 73static struct arg args[] = { 74{ TITLE, NULL, "Usage: alsactl <options> command" }, 75{ HEADER, NULL, "global options:" }, 76{ 'h', "help", "this help" }, 77{ 'd', "debug", "debug mode" }, 78{ 'v', "version", "print version of this program" }, 79{ HEADER, NULL, "Available state options:" }, 80{ FILEARG | 'f', "file", "configuration file (default " SYS_ASOUNDRC ")" }, 81{ FILEARG | 'a', "config-dir", "boot / hotplug configuration directory (default " SYS_ASOUND_DIR ")" }, 82{ 'l', "lock", "use file locking to serialize concurrent access" }, 83{ 'L', "no-lock", "do not use file locking to serialize concurrent access" }, 84{ FILEARG | 'K', "lock-dir", "lock path (default " SYS_LOCKPATH ")" }, 85{ FILEARG | 'O', "lock-state-file", "state lock file path (default " SYS_LOCKFILE ")" }, 86{ 'F', "force", "try to restore the matching controls as much as possible" }, 87{ 0, NULL, " (default mode)" }, 88{ 'g', "ignore", "ignore 'No soundcards found' error" }, 89{ 'P', "pedantic", "do not restore mismatching controls (old default)" }, 90{ 'I', "no-init-fallback", "" }, 91{ 0, NULL, "don't initialize even if restore fails" }, 92{ FILEARG | 'r', "runstate", "save restore and init state to this file (only errors)" }, 93{ 0, NULL, " default settings is 'no file set'" }, 94{ 'R', "remove", "remove runstate file at first, otherwise append errors" }, 95{ INTARG | 'p', "period", "store period in seconds for the daemon command" }, 96{ FILEARG | 'e', "pid-file", "pathname for the process id (daemon mode)" }, 97{ HEADER, NULL, "Available init options:" }, 98{ ENVARG | 'E', "env", "set environment variable for init phase (NAME=VALUE)" }, 99{ FILEARG | 'i', "initfile", "main configuation file for init phase" }, 100{ 0, NULL, " (default " DATADIR "/init/00main)" }, 101{ 'b', "background", "run daemon in background" }, 102{ 's', "syslog", "use syslog for messages" }, 103{ INTARG | 'n', "nice", "set the process priority (see 'man nice')" }, 104{ 'c', "sched-idle", "set the process scheduling policy to idle (SCHED_IDLE)" }, 105#ifdef HAVE_ALSA_USE_CASE_H 106{ 'D', "ucm-defaults", "execute also the UCM 'defaults' section" }, 107{ 'U', "no-ucm", "don't init with UCM" }, 108#if SND_LIB_VER(1, 2, 5) < SND_LIB_VERSION 109{ 'X', "ucm-nodev", "show UCM no device errors" }, 110#endif 111#endif 112{ HEADER, NULL, "Available commands:" }, 113{ CARDCMD, "store", "save current driver setup for one or each soundcards" }, 114{ EMPCMD, NULL, " to configuration file" }, 115{ CARDCMD, "restore", "load current driver setup for one or each soundcards" }, 116{ EMPCMD, NULL, " from configuration file" }, 117{ CARDCMD, "nrestore", "like restore, but notify the daemon to rescan soundcards" }, 118{ CARDCMD, "init", "initialize driver to a default state" }, 119{ CARDCMD, "daemon", "store state periodically for one or each soundcards" }, 120{ CARDCMD, "rdaemon", "like daemon but do the state restore at first" }, 121{ KILLCMD, "kill", "notify daemon to quit, rescan or save_and_quit" }, 122{ CARDCMD, "monitor", "monitor control events" }, 123{ CARDCMD, "info", "general information" }, 124{ CARDCMD, "clean", "clean application controls" }, 125{ EMPCMD, "dump-state", "dump the state (for all cards)" }, 126{ EMPCMD, "dump-cfg", "dump the configuration (expanded, for all cards)" }, 127{ 0, NULL, NULL } 128}; 129 130static void help(void) 131{ 132 struct arg *n = args, *a; 133 char *larg, sa[4], buf[32]; 134 int sarg; 135 136 sa[0] = '-'; 137 sa[2] = ','; 138 sa[3] = '\0'; 139 while (n->comment) { 140 a = n; 141 n++; 142 sarg = a->sarg; 143 if (sarg & (HEADER|TITLE)) { 144 printf("%s%s\n", (sarg & HEADER) != 0 ? "\n" : "", 145 a->comment); 146 continue; 147 } 148 buf[0] = '\0'; 149 larg = a->larg; 150 if (sarg & (EMPCMD|CARDCMD|KILLCMD)) { 151 if (sarg & CARDCMD) 152 strcat(buf, "<card>"); 153 else if (sarg & KILLCMD) 154 strcat(buf, "<cmd>"); 155 printf(" %-10s %-6s %s\n", larg ? larg : "", 156 buf, a->comment); 157 continue; 158 } 159 sa[1] = a->sarg; 160 sprintf(buf, "%s%s%s", sa[1] ? sa : "", 161 larg ? "--" : "", larg ? larg : ""); 162 if (sarg & ENVARG) 163 strcat(buf, " #=#"); 164 else if (sarg & (FILEARG|INTARG)) 165 strcat(buf, " #"); 166 printf(" %-15s %s\n", buf, a->comment); 167 } 168} 169 170static int dump_config_tree(snd_config_t *top) 171{ 172 snd_output_t *out; 173 int err; 174 175 err = snd_output_stdio_attach(&out, stdout, 0); 176 if (err < 0) 177 return err; 178 err = snd_config_save(top, out); 179 snd_output_close(out); 180 return err; 181} 182 183static int dump_state(const char *file) 184{ 185 snd_config_t *top; 186 int err; 187 188 err = load_configuration(file, &top, NULL); 189 if (err < 0) 190 return err; 191 err = dump_config_tree(top); 192 snd_config_delete(top); 193 return err; 194} 195 196static int dump_configuration(void) 197{ 198 snd_config_t *top, *cfg2; 199 int err; 200 201 err = snd_config_update_ref(&top); 202 if (err < 0) 203 return err; 204 /* expand cards.* tree */ 205 err = snd_config_search_definition(top, "cards", "_dummy_", &cfg2); 206 if (err >= 0) 207 snd_config_delete(cfg2); 208 err = dump_config_tree(top); 209 snd_config_unref(top); 210 return err; 211} 212 213#define NO_NICE (-100000) 214 215static void do_nice(int use_nice, int sched_idle) 216{ 217 struct sched_param sched_param; 218 219 if (use_nice != NO_NICE && nice(use_nice) < 0) 220 error("nice(%i): %s", use_nice, strerror(errno)); 221 if (sched_idle) { 222 if (sched_getparam(0, &sched_param) >= 0) { 223 sched_param.sched_priority = 0; 224 if (sched_setscheduler(0, SCHED_IDLE, &sched_param) < 0) 225 error("sched_setparam failed: %s", strerror(errno)); 226 } else { 227 error("sched_getparam failed: %s", strerror(errno)); 228 } 229 } 230} 231 232int main(int argc, char *argv[]) 233{ 234 static const char *const devfiles[] = { 235 "/dev/snd/controlC", 236 "/dev/snd/pcmC", 237 "/dev/snd/midiC", 238 "/dev/snd/hwC", 239 NULL 240 }; 241 char *cfgdir = SYS_ASOUND_DIR; 242 char *cfgfile = SYS_ASOUNDRC; 243 char *initfile = DATADIR "/init/00main"; 244 char *pidfile = SYS_PIDFILE; 245 char *cardname, ncardname[21]; 246 char *cmd; 247 char *const *extra_args; 248 const char *const *tmp; 249 int removestate = 0; 250 int init_fallback = 1; /* new default behavior */ 251 int period = 5*60; 252 int background = 0; 253 int daemoncmd = 0; 254 int use_nice = NO_NICE; 255 int sched_idle = 0; 256 int initflags = 0; 257 struct arg *a; 258 struct option *o; 259 int i, j, k, res; 260 struct option *long_option; 261 char *short_option; 262 263#if SND_LIB_VER(1, 2, 5) >= SND_LIB_VERSION 264 initflags |= FLAG_UCM_NODEV; 265#endif 266 long_option = calloc(ARRAY_SIZE(args), sizeof(struct option)); 267 if (long_option == NULL) 268 exit(EXIT_FAILURE); 269 short_option = malloc(128); 270 if (short_option == NULL) { 271 free(long_option); 272 exit(EXIT_FAILURE); 273 } 274 for (i = j = k = 0; i < (int)ARRAY_SIZE(args); i++) { 275 a = &args[i]; 276 if ((a->sarg & 0xff) == 0) 277 continue; 278 o = &long_option[j]; 279 o->name = a->larg; 280 o->has_arg = (a->sarg & (ENVARG|FILEARG|INTARG)) != 0; 281 o->flag = NULL; 282 o->val = a->sarg & 0xff; 283 j++; 284 short_option[k++] = o->val; 285 if (o->has_arg) 286 short_option[k++] = ':'; 287 } 288 short_option[k] = '\0'; 289 command = argv[0]; 290 while (1) { 291 int c; 292 293 if ((c = getopt_long(argc, argv, short_option, long_option, 294 NULL)) < 0) 295 break; 296 switch (c) { 297 case 'h': 298 help(); 299 res = EXIT_SUCCESS; 300 goto out; 301 case 'f': 302 cfgfile = optarg; 303 break; 304 case 'a': 305 cfgdir = optarg; 306 break; 307 case 'l': 308 do_lock = 1; 309 break; 310 case 'L': 311 do_lock = -1; 312 break; 313 case 'K': 314 lockpath = optarg; 315 break; 316 case 'O': 317 lockfile = optarg; 318 break; 319 case 'F': 320 force_restore = 1; 321 break; 322 case 'g': 323 ignore_nocards = 1; 324 break; 325 case 'E': 326 if (putenv(optarg)) { 327 fprintf(stderr, "environment string '%s' is wrong\n", optarg); 328 res = EXIT_FAILURE; 329 goto out; 330 } 331 break; 332 case 'i': 333 initfile = optarg; 334 break; 335 case 'I': 336 init_fallback = 0; 337 break; 338 case 'D': 339 initflags |= FLAG_UCM_DEFAULTS; 340 break; 341 case 'U': 342 initflags |= FLAG_UCM_DISABLED; 343 break; 344 case 'X': 345 initflags |= FLAG_UCM_NODEV; 346 break; 347 case 'r': 348 statefile = optarg; 349 break; 350 case 'R': 351 removestate = 1; 352 break; 353 case 'P': 354 force_restore = 0; 355 break; 356 case 'p': 357 period = atoi(optarg); 358 if (period < 10) 359 period = 5*60; 360 else if (period > 24*60*60) 361 period = 24*60*60; 362 break; 363 case 'e': 364 pidfile = optarg; 365 break; 366 case 'b': 367 background = 1; 368 break; 369 case 's': 370 use_syslog = 1; 371 break; 372 case 'n': 373 use_nice = atoi(optarg); 374 if (use_nice < -20) 375 use_nice = -20; 376 else if (use_nice > 19) 377 use_nice = 19; 378 break; 379 case 'c': 380 sched_idle = 1; 381 break; 382 case 'd': 383 debugflag = 1; 384 break; 385 case 'v': 386 printf("alsactl version " SND_UTIL_VERSION_STR "\n"); 387 res = EXIT_SUCCESS; 388 goto out; 389 case '?': // error msg already printed 390 help(); 391 res = EXIT_FAILURE; 392 goto out; 393 default: // should never happen 394 fprintf(stderr, 395 "Invalid option '%c' (%d) not handled??\n", c, c); 396 } 397 } 398 free(short_option); 399 short_option = NULL; 400 free(long_option); 401 long_option = NULL; 402 if (argc - optind <= 0) { 403 fprintf(stderr, "alsactl: Specify command...\n"); 404 res = 0; 405 goto out; 406 } 407 408 cardname = argc - optind > 1 ? argv[optind + 1] : NULL; 409 for (tmp = devfiles; cardname != NULL && *tmp != NULL; tmp++) { 410 int len = strlen(*tmp); 411 if (!strncmp(cardname, *tmp, len)) { 412 long l = strtol(cardname + len, NULL, 0); 413 sprintf(ncardname, "%li", l); 414 cardname = ncardname; 415 break; 416 } 417 } 418 419 extra_args = argc - optind > 2 ? argv + optind + 2 : NULL; 420 421 /* the global system file should be always locked */ 422 if (strcmp(cfgfile, SYS_ASOUNDRC) == 0 && do_lock >= 0) 423 do_lock = 1; 424 425 /* when running in background, use syslog for reports */ 426 if (background) { 427 use_syslog = 1; 428 if (daemon(0, 0)) { 429 syslog(LOG_INFO, "alsactl " SND_UTIL_VERSION_STR " daemon cannot be started: %s", strerror(errno)); 430 res = EXIT_FAILURE; 431 goto out; 432 } 433 } 434 435 cmd = argv[optind]; 436 daemoncmd = strcmp(cmd, "daemon") == 0 || strcmp(cmd, "rdaemon") == 0; 437 438 if (use_syslog) { 439 openlog("alsactl", LOG_CONS|LOG_PID, LOG_DAEMON); 440 if (daemoncmd) 441 syslog(LOG_INFO, "alsactl " SND_UTIL_VERSION_STR " daemon started"); 442 } 443 444 snd_lib_error_set_handler(error_handler); 445 446 if (!strcmp(cmd, "init")) { 447 res = init(cfgdir, initfile, initflags | FLAG_UCM_FBOOT | FLAG_UCM_BOOT, cardname); 448 snd_config_update_free_global(); 449 } else if (!strcmp(cmd, "store")) { 450 res = save_state(cfgfile, cardname); 451 } else if (!strcmp(cmd, "restore") || 452 !strcmp(cmd, "rdaemon") || 453 !strcmp(cmd, "nrestore")) { 454 if (removestate) 455 remove(statefile); 456 res = load_state(cfgdir, cfgfile, initfile, initflags, cardname, init_fallback); 457 if (!strcmp(cmd, "rdaemon")) { 458 do_nice(use_nice, sched_idle); 459 res = state_daemon(cfgfile, cardname, period, pidfile); 460 } 461 if (!strcmp(cmd, "nrestore")) 462 res = state_daemon_kill(pidfile, "rescan"); 463 } else if (!strcmp(cmd, "daemon")) { 464 do_nice(use_nice, sched_idle); 465 res = state_daemon(cfgfile, cardname, period, pidfile); 466 } else if (!strcmp(cmd, "kill")) { 467 res = state_daemon_kill(pidfile, cardname); 468 } else if (!strcmp(cmd, "monitor")) { 469 res = monitor(cardname); 470 } else if (!strcmp(cmd, "info")) { 471 res = general_info(cardname); 472 } else if (!strcmp(cmd, "clean")) { 473 res = clean(cardname, extra_args); 474 } else if (!strcmp(cmd, "dump-state")) { 475 res = dump_state(cfgfile); 476 } else if (!strcmp(cmd, "dump-cfg")) { 477 res = dump_configuration(); 478 } else { 479 fprintf(stderr, "alsactl: Unknown command '%s'...\n", cmd); 480 res = -ENODEV; 481 } 482 483 snd_config_update_free_global(); 484 if (use_syslog) { 485 if (daemoncmd) 486 syslog(LOG_INFO, "alsactl daemon stopped"); 487 closelog(); 488 } 489 return res < 0 ? -res : 0; 490 491out: 492 free(short_option); 493 free(long_option); 494 return res; 495} 496