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