tarina

git clone https://git.tarina.org/tarina
Log | Files | Refs | README | LICENSE

alsactl.c (10678B)


      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 configuration 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 }