xref: /third_party/alsa-utils/alsactl/alsactl.c (revision c72fcc34)
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