alsactl.c (10677B)
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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 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 <alsa/asoundlib.h> 33 #include "alsactl.h" 34 35 #ifndef SYS_ASOUNDRC 36 #define SYS_ASOUNDRC "/var/lib/alsa/asound.state" 37 #endif 38 #ifndef SYS_PIDFILE 39 #define SYS_PIDFILE "/var/run/alsactl.pid" 40 #endif 41 #ifndef SYS_LOCKPATH 42 #define SYS_LOCKPATH "/var/lock" 43 #endif 44 45 int debugflag = 0; 46 int force_restore = 1; 47 int ignore_nocards = 0; 48 int do_lock = 0; 49 int use_syslog = 0; 50 char *command; 51 char *statefile = NULL; 52 char *lockfile = SYS_LOCKFILE; 53 54 #define TITLE 0x0100 55 #define HEADER 0x0200 56 #define FILEARG 0x0400 57 #define ENVARG 0x0800 58 #define INTARG 0x1000 59 #define EMPCMD 0x2000 60 #define CARDCMD 0x4000 61 #define KILLCMD 0x8000 62 63 struct arg { 64 int sarg; 65 char *larg; 66 char *comment; 67 }; 68 69 static struct arg args[] = { 70 { TITLE, NULL, "Usage: alsactl <options> command" }, 71 { HEADER, NULL, "global options:" }, 72 { 'h', "help", "this help" }, 73 { 'd', "debug", "debug mode" }, 74 { 'v', "version", "print version of this program" }, 75 { HEADER, NULL, "Available state options:" }, 76 { FILEARG | 'f', "file", "configuration file (default " SYS_ASOUNDRC ")" }, 77 { 'l', "lock", "use file locking to serialize concurrent access" }, 78 { 'L', "no-lock", "do not use file locking to serialize concurrent access" }, 79 { FILEARG | 'O', "lock-state-file", "state lock file path (default " SYS_LOCKFILE ")" }, 80 { 'F', "force", "try to restore the matching controls as much as possible" }, 81 { 0, NULL, " (default mode)" }, 82 { 'g', "ignore", "ignore 'No soundcards found' error" }, 83 { 'P', "pedantic", "do not restore mismatching controls (old default)" }, 84 { 'I', "no-init-fallback", "" }, 85 { 0, NULL, "don't initialize even if restore fails" }, 86 { FILEARG | 'r', "runstate", "save restore and init state to this file (only errors)" }, 87 { 0, NULL, " default settings is 'no file set'" }, 88 { 'R', "remove", "remove runstate file at first, otherwise append errors" }, 89 { INTARG | 'p', "period", "store period in seconds for the daemon command" }, 90 { FILEARG | 'e', "pid-file", "pathname for the process id (daemon mode)" }, 91 { HEADER, NULL, "Available init options:" }, 92 { ENVARG | 'E', "env", "set environment variable for init phase (NAME=VALUE)" }, 93 { FILEARG | 'i', "initfile", "main configuation file for init phase" }, 94 { 0, NULL, " (default " DATADIR "/init/00main)" }, 95 { 'b', "background", "run daemon in background" }, 96 { 's', "syslog", "use syslog for messages" }, 97 { INTARG | 'n', "nice", "set the process priority (see 'man nice')" }, 98 { 'c', "sched-idle", "set the process scheduling policy to idle (SCHED_IDLE)" }, 99 { HEADER, NULL, "Available commands:" }, 100 { CARDCMD, "store", "save current driver setup for one or each soundcards" }, 101 { EMPCMD, NULL, " to configuration file" }, 102 { CARDCMD, "restore", "load current driver setup for one or each soundcards" }, 103 { EMPCMD, NULL, " from configuration file" }, 104 { CARDCMD, "nrestore", "like restore, but notify the daemon to rescan soundcards" }, 105 { CARDCMD, "init", "initialize driver to a default state" }, 106 { CARDCMD, "daemon", "store state periodically for one or each soundcards" }, 107 { CARDCMD, "rdaemon", "like daemon but do the state restore at first" }, 108 { KILLCMD, "kill", "notify daemon to quit, rescan or save_and_quit" }, 109 { CARDCMD, "monitor", "monitor control events" }, 110 { 0, NULL, NULL } 111 }; 112 113 static void help(void) 114 { 115 struct arg *n = args, *a; 116 char *larg, sa[4], buf[32]; 117 int sarg; 118 119 sa[0] = '-'; 120 sa[2] = ','; 121 sa[3] = '\0'; 122 while (n->comment) { 123 a = n; 124 n++; 125 sarg = a->sarg; 126 if (sarg & (HEADER|TITLE)) { 127 printf("%s%s\n", (sarg & HEADER) != 0 ? "\n" : "", 128 a->comment); 129 continue; 130 } 131 buf[0] = '\0'; 132 larg = a->larg; 133 if (sarg & (EMPCMD|CARDCMD|KILLCMD)) { 134 if (sarg & CARDCMD) 135 strcat(buf, "<card>"); 136 else if (sarg & KILLCMD) 137 strcat(buf, "<cmd>"); 138 printf(" %-8s %-6s %s\n", larg ? larg : "", 139 buf, a->comment); 140 continue; 141 } 142 sa[1] = a->sarg; 143 sprintf(buf, "%s%s%s", sa[1] ? sa : "", 144 larg ? "--" : "", larg ? larg : ""); 145 if (sarg & ENVARG) 146 strcat(buf, " #=#"); 147 else if (sarg & (FILEARG|INTARG)) 148 strcat(buf, " #"); 149 printf(" %-15s %s\n", buf, a->comment); 150 } 151 } 152 153 #define NO_NICE (-100000) 154 155 static void do_nice(int use_nice, int sched_idle) 156 { 157 struct sched_param sched_param; 158 159 if (use_nice != NO_NICE && nice(use_nice) < 0) 160 error("nice(%i): %s", use_nice, strerror(errno)); 161 if (sched_idle) { 162 if (sched_getparam(0, &sched_param) >= 0) { 163 sched_param.sched_priority = 0; 164 if (!sched_setscheduler(0, SCHED_RR, &sched_param)) 165 error("sched_setparam failed: %s", strerror(errno)); 166 } else { 167 error("sched_getparam failed: %s", strerror(errno)); 168 } 169 } 170 } 171 172 int main(int argc, char *argv[]) 173 { 174 static const char *const devfiles[] = { 175 "/dev/snd/controlC", 176 "/dev/snd/pcmC", 177 "/dev/snd/midiC", 178 "/dev/snd/hwC", 179 NULL 180 }; 181 char *cfgfile = SYS_ASOUNDRC; 182 char *initfile = DATADIR "/init/00main"; 183 char *pidfile = SYS_PIDFILE; 184 char *cardname, ncardname[16]; 185 char *cmd; 186 const char *const *tmp; 187 int removestate = 0; 188 int init_fallback = 1; /* new default behavior */ 189 int period = 5*60; 190 int background = 0; 191 int daemoncmd = 0; 192 int use_nice = NO_NICE; 193 int sched_idle = 0; 194 struct arg *a; 195 struct option *o; 196 int i, j, k, res; 197 struct option *long_option; 198 char *short_option; 199 200 long_option = calloc(ARRAY_SIZE(args), sizeof(struct option)); 201 if (long_option == NULL) 202 exit(EXIT_FAILURE); 203 short_option = malloc(128); 204 if (short_option == NULL) { 205 free(long_option); 206 exit(EXIT_FAILURE); 207 } 208 for (i = j = k = 0; i < ARRAY_SIZE(args); i++) { 209 a = &args[i]; 210 if ((a->sarg & 0xff) == 0) 211 continue; 212 o = &long_option[j]; 213 o->name = a->larg; 214 o->has_arg = (a->sarg & (ENVARG|FILEARG|INTARG)) != 0; 215 o->flag = NULL; 216 o->val = a->sarg & 0xff; 217 j++; 218 short_option[k++] = o->val; 219 if (o->has_arg) 220 short_option[k++] = ':'; 221 } 222 short_option[k] = '\0'; 223 command = argv[0]; 224 while (1) { 225 int c; 226 227 if ((c = getopt_long(argc, argv, short_option, long_option, 228 NULL)) < 0) 229 break; 230 switch (c) { 231 case 'h': 232 help(); 233 res = EXIT_SUCCESS; 234 goto out; 235 case 'f': 236 cfgfile = optarg; 237 break; 238 case 'l': 239 do_lock = 1; 240 break; 241 case 'L': 242 do_lock = -1; 243 break; 244 case 'O': 245 lockfile = optarg; 246 break; 247 case 'F': 248 force_restore = 1; 249 break; 250 case 'g': 251 ignore_nocards = 1; 252 break; 253 case 'E': 254 if (putenv(optarg)) { 255 fprintf(stderr, "environment string '%s' is wrong\n", optarg); 256 res = EXIT_FAILURE; 257 goto out; 258 } 259 break; 260 case 'i': 261 initfile = optarg; 262 break; 263 case 'I': 264 init_fallback = 0; 265 break; 266 case 'r': 267 statefile = optarg; 268 break; 269 case 'R': 270 removestate = 1; 271 break; 272 case 'P': 273 force_restore = 0; 274 break; 275 case 'p': 276 period = atoi(optarg); 277 if (period < 10) 278 period = 5*60; 279 else if (period > 24*60*60) 280 period = 24*60*60; 281 break; 282 case 'e': 283 pidfile = optarg; 284 break; 285 case 'b': 286 background = 1; 287 break; 288 case 's': 289 use_syslog = 1; 290 break; 291 case 'n': 292 use_nice = atoi(optarg); 293 if (use_nice < -20) 294 use_nice = -20; 295 else if (use_nice > 19) 296 use_nice = 19; 297 break; 298 case 'c': 299 sched_idle = 1; 300 break; 301 case 'd': 302 debugflag = 1; 303 break; 304 case 'v': 305 printf("alsactl version " SND_UTIL_VERSION_STR "\n"); 306 res = EXIT_SUCCESS; 307 goto out; 308 case '?': // error msg already printed 309 help(); 310 res = EXIT_FAILURE; 311 goto out; 312 default: // should never happen 313 fprintf(stderr, 314 "Invalid option '%c' (%d) not handled??\n", c, c); 315 } 316 } 317 free(short_option); 318 short_option = NULL; 319 free(long_option); 320 long_option = NULL; 321 if (argc - optind <= 0) { 322 fprintf(stderr, "alsactl: Specify command...\n"); 323 res = 0; 324 goto out; 325 } 326 327 cardname = argc - optind > 1 ? argv[optind + 1] : NULL; 328 for (tmp = devfiles; cardname != NULL && *tmp != NULL; tmp++) { 329 int len = strlen(*tmp); 330 if (!strncmp(cardname, *tmp, len)) { 331 long l = strtol(cardname + len, NULL, 0); 332 sprintf(ncardname, "%li", l); 333 cardname = ncardname; 334 break; 335 } 336 } 337 338 /* the global system file should be always locked */ 339 if (strcmp(cfgfile, SYS_ASOUNDRC) == 0 && do_lock >= 0) 340 do_lock = 1; 341 342 /* when running in background, use syslog for reports */ 343 if (background) { 344 use_syslog = 1; 345 daemon(0, 0); 346 } 347 348 cmd = argv[optind]; 349 daemoncmd = strcmp(cmd, "daemon") == 0 || strcmp(cmd, "rdaemon") == 0; 350 351 if (use_syslog) { 352 openlog("alsactl", LOG_CONS|LOG_PID, LOG_DAEMON); 353 if (daemoncmd) 354 syslog(LOG_INFO, "alsactl " SND_UTIL_VERSION_STR " daemon started"); 355 } 356 357 if (!strcmp(cmd, "init")) { 358 res = init(initfile, cardname); 359 snd_config_update_free_global(); 360 } else if (!strcmp(cmd, "store")) { 361 res = save_state(cfgfile, cardname); 362 } else if (!strcmp(cmd, "restore") || 363 !strcmp(cmd, "rdaemon") || 364 !strcmp(cmd, "nrestore")) { 365 if (removestate) 366 remove(statefile); 367 res = load_state(cfgfile, initfile, cardname, init_fallback); 368 if (!strcmp(cmd, "rdaemon")) { 369 do_nice(use_nice, sched_idle); 370 res = state_daemon(cfgfile, cardname, period, pidfile); 371 } 372 if (!strcmp(cmd, "nrestore")) 373 res = state_daemon_kill(pidfile, "rescan"); 374 } else if (!strcmp(cmd, "daemon")) { 375 do_nice(use_nice, sched_idle); 376 res = state_daemon(cfgfile, cardname, period, pidfile); 377 } else if (!strcmp(cmd, "kill")) { 378 res = state_daemon_kill(pidfile, cardname); 379 } else if (!strcmp(cmd, "monitor")) { 380 res = monitor(cardname); 381 } else { 382 fprintf(stderr, "alsactl: Unknown command '%s'...\n", cmd); 383 res = -ENODEV; 384 } 385 386 snd_config_update_free_global(); 387 if (use_syslog) { 388 if (daemoncmd) 389 syslog(LOG_INFO, "alsactl daemon stopped"); 390 closelog(); 391 } 392 return res < 0 ? -res : 0; 393 394 out: 395 free(short_option); 396 free(long_option); 397 return res; 398 }