Imported Upstream version 1.0.28
[platform/upstream/alsa-utils.git] / alsactl / alsactl.c
index 1792747..bcbc187 100644 (file)
 #include <stdio.h>
 #include <assert.h>
 #include <errno.h>
+#include <syslog.h>
+#include <sched.h>
 #include <alsa/asoundlib.h>
 #include "alsactl.h"
 
 #ifndef SYS_ASOUNDRC
 #define SYS_ASOUNDRC "/var/lib/alsa/asound.state"
 #endif
+#ifndef SYS_PIDFILE
+#define SYS_PIDFILE "/var/run/alsactl.pid"
+#endif
+#ifndef SYS_LOCKPATH
+#define SYS_LOCKPATH "/var/lock"
+#endif
 
 int debugflag = 0;
 int force_restore = 1;
 int ignore_nocards = 0;
+int do_lock = 0;
+int use_syslog = 0;
 char *command;
 char *statefile = NULL;
+char *lockfile = SYS_LOCKFILE;
+
+#define TITLE  0x0100
+#define HEADER 0x0200
+#define FILEARG 0x0400
+#define ENVARG 0x0800
+#define INTARG  0x1000
+#define EMPCMD 0x2000
+#define CARDCMD 0x4000
+#define KILLCMD 0x8000
+
+struct arg {
+       int sarg;
+       char *larg;
+       char *comment;
+};
+
+static struct arg args[] = {
+{ TITLE, NULL, "Usage: alsactl <options> command" },
+{ HEADER, NULL, "global options:" },
+{ 'h', "help", "this help" },
+{ 'd', "debug", "debug mode" },
+{ 'v', "version", "print version of this program" },
+{ HEADER, NULL, "Available state options:" },
+{ FILEARG | 'f', "file", "configuration file (default " SYS_ASOUNDRC ")" },
+{ 'l', "lock", "use file locking to serialize concurrent access" },
+{ 'L', "no-lock", "do not use file locking to serialize concurrent access" },
+{ FILEARG | 'O', "lock-state-file", "state lock file path (default " SYS_LOCKFILE ")" },
+{ 'F', "force", "try to restore the matching controls as much as possible" },
+{ 0, NULL, "  (default mode)" },
+{ 'g', "ignore", "ignore 'No soundcards found' error" },
+{ 'P', "pedantic", "do not restore mismatching controls (old default)" },
+{ 'I', "no-init-fallback", "" },
+{ 0, NULL, "don't initialize even if restore fails" },
+{ FILEARG | 'r', "runstate", "save restore and init state to this file (only errors)" },
+{ 0, NULL, "  default settings is 'no file set'" },
+{ 'R', "remove", "remove runstate file at first, otherwise append errors" },
+{ INTARG | 'p', "period", "store period in seconds for the daemon command" },
+{ FILEARG | 'e', "pid-file", "pathname for the process id (daemon mode)" },
+{ HEADER, NULL, "Available init options:" },
+{ ENVARG | 'E', "env", "set environment variable for init phase (NAME=VALUE)" },
+{ FILEARG | 'i', "initfile", "main configuation file for init phase" },
+{ 0, NULL, "  (default " DATADIR "/init/00main)" },
+{ 'b', "background", "run daemon in background" },
+{ 's', "syslog", "use syslog for messages" },
+{ INTARG | 'n', "nice", "set the process priority (see 'man nice')" },
+{ 'c', "sched-idle", "set the process scheduling policy to idle (SCHED_IDLE)" },
+{ HEADER, NULL, "Available commands:" },
+{ CARDCMD, "store", "save current driver setup for one or each soundcards" },
+{ EMPCMD, NULL, "  to configuration file" },
+{ CARDCMD, "restore", "load current driver setup for one or each soundcards" },
+{ EMPCMD, NULL, "  from configuration file" },
+{ CARDCMD, "nrestore", "like restore, but notify the daemon to rescan soundcards" },
+{ CARDCMD, "init", "initialize driver to a default state" },
+{ CARDCMD, "daemon", "store state periodically for one or each soundcards" },
+{ CARDCMD, "rdaemon", "like daemon but do the state restore at first" },
+{ KILLCMD, "kill", "notify daemon to quit, rescan or save_and_quit" },
+{ CARDCMD, "monitor", "monitor control events" },
+{ 0, NULL, NULL }
+};
 
 static void help(void)
 {
-       printf("Usage: alsactl <options> command\n");
-       printf("\nAvailable global options:\n");
-       printf("  -h,--help        this help\n");
-       printf("  -d,--debug       debug mode\n");
-       printf("  -v,--version     print version of this program\n");
-       printf("\nAvailable state options:\n");
-       printf("  -f,--file #      configuration file (default " SYS_ASOUNDRC ")\n");
-       printf("  -F,--force       try to restore the matching controls as much as possible\n");
-       printf("                   (default mode)\n");
-       printf("  -g,--ignore      ignore 'No soundcards found' error\n");
-       printf("  -P,--pedantic    do not restore mismatching controls (old default)\n");
-       printf("  -I,--no-init-fallback\n"
-              "                   don't initialize even if restore fails\n");
-       printf("  -r,--runstate #  save restore and init state to this file (only errors)\n");
-       printf("                   default settings is 'no file set'\n");
-       printf("  -R,--remove      remove runstate file at first, otherwise append errors\n");
-       printf("\nAvailable init options:\n");
-       printf("  -E,--env #=#     set environment variable for init phase (NAME=VALUE)\n");
-       printf("  -i,--initfile #  main configuation file for init phase (default " DATADIR "/init/00main)\n");
-       printf("\n");
-       printf("\nAvailable commands:\n");
-       printf("  store   <card #> save current driver setup for one or each soundcards\n");
-       printf("                   to configuration file\n");
-       printf("  restore <card #> load current driver setup for one or each soundcards\n");
-       printf("                   from configuration file\n");
-       printf("  init    <card #> initialize driver to a default state\n");
+       struct arg *n = args, *a;
+       char *larg, sa[4], buf[32];
+       int sarg;
+
+       sa[0] = '-';
+       sa[2] = ',';
+       sa[3] = '\0';
+       while (n->comment) {
+               a = n;
+               n++;
+               sarg = a->sarg;
+               if (sarg & (HEADER|TITLE)) {
+                       printf("%s%s\n", (sarg & HEADER) != 0 ? "\n" : "",
+                                                               a->comment);
+                       continue;
+               }
+               buf[0] = '\0';
+               larg = a->larg;
+               if (sarg & (EMPCMD|CARDCMD|KILLCMD)) {
+                       if (sarg & CARDCMD)
+                               strcat(buf, "<card>");
+                       else if (sarg & KILLCMD)
+                               strcat(buf, "<cmd>");
+                       printf("  %-8s  %-6s  %s\n", larg ? larg : "",
+                                                       buf, a->comment);
+                       continue;
+               }
+               sa[1] = a->sarg;
+               sprintf(buf, "%s%s%s", sa[1] ? sa : "",
+                               larg ? "--" : "", larg ? larg : "");
+               if (sarg & ENVARG)
+                       strcat(buf, " #=#");
+               else if (sarg & (FILEARG|INTARG))
+                       strcat(buf, " #");
+               printf("  %-15s  %s\n", buf, a->comment);
+       }
+}
+
+#define NO_NICE (-100000)
+
+static void do_nice(int use_nice, int sched_idle)
+{
+       struct sched_param sched_param;
+
+       if (use_nice != NO_NICE && nice(use_nice) < 0)
+               error("nice(%i): %s", use_nice, strerror(errno));
+       if (sched_idle) {
+               if (sched_getparam(0, &sched_param) >= 0) {
+                       sched_param.sched_priority = 0;
+                       if (!sched_setscheduler(0, SCHED_RR, &sched_param))
+                               error("sched_setparam failed: %s", strerror(errno));
+               } else {
+                       error("sched_getparam failed: %s", strerror(errno));
+               }
+       }
 }
 
 int main(int argc, char *argv[])
 {
-       static const struct option long_option[] =
-       {
-               {"help", 0, NULL, 'h'},
-               {"file", 1, NULL, 'f'},
-               {"env", 1, NULL, 'E'},
-               {"initfile", 1, NULL, 'i'},
-               {"no-init-fallback", 0, NULL, 'I'},
-               {"force", 0, NULL, 'F'},
-               {"ignore", 0, NULL, 'g'},
-               {"pedantic", 0, NULL, 'P'},
-               {"runstate", 0, NULL, 'r'},
-               {"remove", 0, NULL, 'R'},
-               {"debug", 0, NULL, 'd'},
-               {"version", 0, NULL, 'v'},
-               {NULL, 0, NULL, 0},
-       };
        static const char *const devfiles[] = {
                "/dev/snd/controlC",
                "/dev/snd/pcmC",
@@ -97,25 +180,70 @@ int main(int argc, char *argv[])
        };
        char *cfgfile = SYS_ASOUNDRC;
        char *initfile = DATADIR "/init/00main";
+       char *pidfile = SYS_PIDFILE;
        char *cardname, ncardname[16];
+       char *cmd;
        const char *const *tmp;
        int removestate = 0;
        int init_fallback = 1; /* new default behavior */
-       int res;
+       int period = 5*60;
+       int background = 0;
+       int daemoncmd = 0;
+       int use_nice = NO_NICE;
+       int sched_idle = 0;
+       struct arg *a;
+       struct option *o;
+       int i, j, k, res;
+       struct option *long_option;
+       char *short_option;
 
+       long_option = calloc(ARRAY_SIZE(args), sizeof(struct option));
+       if (long_option == NULL)
+               exit(EXIT_FAILURE);
+       short_option = malloc(128);
+       if (short_option == NULL) {
+               free(long_option);
+               exit(EXIT_FAILURE);
+       }
+       for (i = j = k = 0; i < ARRAY_SIZE(args); i++) {
+               a = &args[i];
+               if ((a->sarg & 0xff) == 0)
+                       continue;
+               o = &long_option[j];
+               o->name = a->larg;
+               o->has_arg = (a->sarg & (ENVARG|FILEARG|INTARG)) != 0;
+               o->flag = NULL;
+               o->val = a->sarg & 0xff;
+               j++;
+               short_option[k++] = o->val;
+               if (o->has_arg)
+                       short_option[k++] = ':';
+       }
+       short_option[k] = '\0';
        command = argv[0];
        while (1) {
                int c;
 
-               if ((c = getopt_long(argc, argv, "hdvf:FgE:i:IPr:R", long_option, NULL)) < 0)
+               if ((c = getopt_long(argc, argv, short_option, long_option,
+                                                                 NULL)) < 0)
                        break;
                switch (c) {
                case 'h':
                        help();
-                       return EXIT_SUCCESS;
+                       res = EXIT_SUCCESS;
+                       goto out;
                case 'f':
                        cfgfile = optarg;
                        break;
+               case 'l':
+                       do_lock = 1;
+                       break;
+               case 'L':
+                       do_lock = -1;
+                       break;
+               case 'O':
+                       lockfile = optarg;
+                       break;
                case 'F':
                        force_restore = 1;
                        break;
@@ -125,7 +253,8 @@ int main(int argc, char *argv[])
                case 'E':
                        if (putenv(optarg)) {
                                fprintf(stderr, "environment string '%s' is wrong\n", optarg);
-                               return EXIT_FAILURE;
+                               res = EXIT_FAILURE;
+                               goto out;
                        }
                        break;
                case 'i':
@@ -143,24 +272,56 @@ int main(int argc, char *argv[])
                case 'P':
                        force_restore = 0;
                        break;
+               case 'p':
+                       period = atoi(optarg);
+                       if (period < 10)
+                               period = 5*60;
+                       else if (period > 24*60*60)
+                               period = 24*60*60;
+                       break;
+               case 'e':
+                       pidfile = optarg;
+                       break;
+               case 'b':
+                       background = 1;
+                       break;
+               case 's':
+                       use_syslog = 1;
+                       break;
+               case 'n':
+                       use_nice = atoi(optarg);
+                       if (use_nice < -20)
+                               use_nice = -20;
+                       else if (use_nice > 19)
+                               use_nice = 19;
+                       break;
+               case 'c':
+                       sched_idle = 1;
+                       break;
                case 'd':
                        debugflag = 1;
                        break;
                case 'v':
                        printf("alsactl version " SND_UTIL_VERSION_STR "\n");
-                       return EXIT_SUCCESS;
+                       res = EXIT_SUCCESS;
+                       goto out;
                case '?':               // error msg already printed
                        help();
-                       return EXIT_FAILURE;
-                       break;
+                       res = EXIT_FAILURE;
+                       goto out;
                default:                // should never happen
                        fprintf(stderr, 
                        "Invalid option '%c' (%d) not handled??\n", c, c);
                }
        }
+       free(short_option);
+       short_option = NULL;
+       free(long_option);
+       long_option = NULL;
        if (argc - optind <= 0) {
                fprintf(stderr, "alsactl: Specify command...\n");
-               return 0;
+               res = 0;
+               goto out;
        }
 
        cardname = argc - optind > 1 ? argv[optind + 1] : NULL;
@@ -174,20 +335,64 @@ int main(int argc, char *argv[])
                }
        }
 
-       if (!strcmp(argv[optind], "init")) {
+       /* the global system file should be always locked */
+       if (strcmp(cfgfile, SYS_ASOUNDRC) == 0 && do_lock >= 0)
+               do_lock = 1;
+
+       /* when running in background, use syslog for reports */
+       if (background) {
+               use_syslog = 1;
+               daemon(0, 0);
+       }
+
+       cmd = argv[optind];
+       daemoncmd = strcmp(cmd, "daemon") == 0 || strcmp(cmd, "rdaemon") == 0;
+
+       if (use_syslog) {
+               openlog("alsactl", LOG_CONS|LOG_PID, LOG_DAEMON);
+               if (daemoncmd)
+                       syslog(LOG_INFO, "alsactl " SND_UTIL_VERSION_STR " daemon started");
+       }
+
+       if (!strcmp(cmd, "init")) {
                res = init(initfile, cardname);
-       } else if (!strcmp(argv[optind], "store")) {
+               snd_config_update_free_global();
+       } else if (!strcmp(cmd, "store")) {
                res = save_state(cfgfile, cardname);
-       } else if (!strcmp(argv[optind], "restore")) {
+       } else if (!strcmp(cmd, "restore") ||
+                   !strcmp(cmd, "rdaemon") ||
+                  !strcmp(cmd, "nrestore")) {
                if (removestate)
                        remove(statefile);
                res = load_state(cfgfile, initfile, cardname, init_fallback);
+               if (!strcmp(cmd, "rdaemon")) {
+                       do_nice(use_nice, sched_idle);
+                       res = state_daemon(cfgfile, cardname, period, pidfile);
+               }
+               if (!strcmp(cmd, "nrestore"))
+                       res = state_daemon_kill(pidfile, "rescan");
+       } else if (!strcmp(cmd, "daemon")) {
+               do_nice(use_nice, sched_idle);
+               res = state_daemon(cfgfile, cardname, period, pidfile);
+       } else if (!strcmp(cmd, "kill")) {
+               res = state_daemon_kill(pidfile, cardname);
+       } else if (!strcmp(cmd, "monitor")) {
+               res = monitor(cardname);
        } else {
-               fprintf(stderr, "alsactl: Unknown command '%s'...\n", 
-                       argv[optind]);
+               fprintf(stderr, "alsactl: Unknown command '%s'...\n", cmd);
                res = -ENODEV;
        }
 
        snd_config_update_free_global();
+       if (use_syslog) {
+               if (daemoncmd)
+                       syslog(LOG_INFO, "alsactl daemon stopped");
+               closelog();
+       }
        return res < 0 ? -res : 0;
+
+out:
+       free(short_option);
+       free(long_option);
+       return res;
 }