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 
48 int debugflag = 0;
49 int force_restore = 1;
50 int ignore_nocards = 0;
51 int do_lock = 0;
52 int use_syslog = 0;
53 char *command;
54 char *statefile = NULL;
55 char *lockpath = SYS_LOCKPATH;
56 char *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 
67 struct arg {
68 	int sarg;
69 	char *larg;
70 	char *comment;
71 };
72 
73 static 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 
help(void)130 static 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 
dump_config_tree(snd_config_t *top)170 static 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 
dump_state(const char *file)183 static 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 
dump_configuration(void)196 static 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 
do_nice(int use_nice, int sched_idle)215 static 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 
main(int argc, char *argv[])232 int 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 
491 out:
492 	free(short_option);
493 	free(long_option);
494 	return res;
495 }
496