/* * Advanced Linux Sound Architecture Control Program * Copyright (c) by Jaroslav Kysela * * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #include "aconfig.h" #include "version.h" #include #include #include #include #include #include #include #include #include #include "alsactl.h" struct id_list { snd_ctl_elem_id_t **list; int size; }; struct card { int index; int pfds; snd_ctl_t *handle; struct id_list whitelist; struct id_list blacklist; }; static int quit = 0; static int rescan = 0; static int save_now = 0; static void signal_handler_quit(int sig) { quit = 1; signal(sig, signal_handler_quit); } static void signal_handler_save_and_quit(int sig) { quit = save_now = 1; signal(sig, signal_handler_quit); } static void signal_handler_rescan(int sig) { rescan = 1; signal(sig, signal_handler_rescan); } static void free_list(struct id_list *list) { int i; for (i = 0; i < list->size; i++) free(list->list[i]); free(list->list); } static void card_free(struct card **card) { struct card *c = *card; free_list(&c->blacklist); free_list(&c->whitelist); if (c->handle) snd_ctl_close(c->handle); free(c); *card = NULL; } static void add_card(struct card ***cards, int *count, const char *cardname) { struct card *card, **cc; int i, index, findex; char device[16]; index = snd_card_get_index(cardname); if (index < 0) return; for (i = 0, findex = -1; i < *count; i++) { if ((*cards)[i] == NULL) { findex = i; } else { if ((*cards)[i]->index == index) return; } } card = calloc(1, sizeof(*card)); if (card == NULL) return; card->index = index; sprintf(device, "hw:%i", index); if (snd_ctl_open(&card->handle, device, SND_CTL_READONLY|SND_CTL_NONBLOCK) < 0) { card_free(&card); return; } card->pfds = snd_ctl_poll_descriptors_count(card->handle); if (card->pfds < 0) { card_free(&card); return; } if (snd_ctl_subscribe_events(card->handle, 1) < 0) { card_free(&card); return; } if (findex >= 0) { (*cards)[findex] = card; } else { cc = realloc(*cards, sizeof(void *) * (*count + 1)); if (cc == NULL) { card_free(&card); return; } cc[*count] = card; *count = *count + 1; *cards = cc; } } static void add_cards(struct card ***cards, int *count) { int card = -1; char cardname[16]; while (1) { if (snd_card_next(&card) < 0) break; if (card < 0) break; if (card >= 0) { sprintf(cardname, "%i", card); add_card(cards, count, cardname); } } } static int compare_ids(snd_ctl_elem_id_t *id1, snd_ctl_elem_id_t *id2) { if (id1 == NULL || id2 == NULL) return 0; return snd_ctl_elem_id_get_interface(id1) == snd_ctl_elem_id_get_interface(id2) && snd_ctl_elem_id_get_index(id1) == snd_ctl_elem_id_get_index(id2) && strcmp(snd_ctl_elem_id_get_name(id1), snd_ctl_elem_id_get_name(id2)) == 0 && snd_ctl_elem_id_get_device(id1) == snd_ctl_elem_id_get_device(id2) && snd_ctl_elem_id_get_subdevice(id1) == snd_ctl_elem_id_get_subdevice(id2); } static int in_list(struct id_list *list, snd_ctl_elem_id_t *id) { int i; snd_ctl_elem_id_t *id1; for (i = 0; i < list->size; i++) { id1 = list->list[i]; if (id1 == NULL) continue; if (compare_ids(id, id1)) return 1; } return 0; } static void remove_from_list(struct id_list *list, snd_ctl_elem_id_t *id) { int i; for (i = 0; i < list->size; i++) { if (compare_ids(id, list->list[i])) { free(list->list[i]); list->list[i] = NULL; } } } static void add_to_list(struct id_list *list, snd_ctl_elem_id_t *id) { snd_ctl_elem_id_t *id1; snd_ctl_elem_id_t **n; int i; if (snd_ctl_elem_id_malloc(&id1)) return; snd_ctl_elem_id_copy(id1, id); for (i = 0; i < list->size; i++) { if (list->list[i] == NULL) { list->list[i] = id1; return; } } n = realloc(list->list, sizeof(void *) * (list->size + 1)); if (n == NULL) return; n[list->size] = id1; list->size++; list->list = n; } static int check_lists(struct card *card, snd_ctl_elem_id_t *id) { snd_ctl_elem_info_t *info; snd_ctl_elem_info_alloca(&info); if (in_list(&card->blacklist, id)) return 0; if (in_list(&card->whitelist, id)) return 1; snd_ctl_elem_info_set_id(info, id); if (snd_ctl_elem_info(card->handle, info) < 0) return 0; if (snd_ctl_elem_info_is_writable(info) || snd_ctl_elem_info_is_tlv_writable(info)) { add_to_list(&card->whitelist, id); return 1; } else { add_to_list(&card->blacklist, id); return 0; } } static int card_events(struct card *card) { int res = 0; snd_ctl_event_t *ev; snd_ctl_event_type_t type; unsigned int mask; snd_ctl_elem_id_t *id; snd_ctl_event_alloca(&ev); snd_ctl_elem_id_alloca(&id); while (snd_ctl_read(card->handle, ev) == 1) { type = snd_ctl_event_get_type(ev); if (type != SND_CTL_EVENT_ELEM) continue; mask = snd_ctl_event_elem_get_mask(ev); snd_ctl_event_elem_get_id(ev, id); if (mask == SND_CTL_EVENT_MASK_REMOVE) { remove_from_list(&card->whitelist, id); remove_from_list(&card->blacklist, id); continue; } if (mask & SND_CTL_EVENT_MASK_INFO) { remove_from_list(&card->whitelist, id); remove_from_list(&card->blacklist, id); } if (mask & (SND_CTL_EVENT_MASK_VALUE| SND_CTL_EVENT_MASK_ADD| SND_CTL_EVENT_MASK_TLV)) { if (check_lists(card, id)) res = 1; } } return res; } static long read_pid_file(const char *pidfile) { int fd, err; char pid_txt[12]; fd = open(pidfile, O_RDONLY); if (fd >= 0) { err = read(fd, pid_txt, 11); if (err != 11) err = err < 0 ? -errno : -EIO; close(fd); pid_txt[11] = '\0'; return atol(pid_txt); } else { return -errno; } } static int write_pid_file(const char *pidfile) { int fd, err; char pid_txt[12]; sprintf(pid_txt, "%10li\n", (long)getpid()); fd = open(pidfile, O_WRONLY|O_CREAT|O_EXCL, 0600); if (fd >= 0) { err = write(fd, pid_txt, 11); if (err != 11) { err = err < 0 ? -errno : -EIO; unlink(pidfile); } else { err = 0; } close(fd); } else { err = -errno; } return err; } int state_daemon_kill(const char *pidfile, const char *cmd) { long pid; int sig = SIGHUP; if (cmd == NULL) { error("Specify kill command (quit, rescan or save_and_quit)"); return -EINVAL; } if (strcmp(cmd, "rescan") == 0) sig = SIGUSR1; else if (strcmp(cmd, "save_and_quit") == 0) sig = SIGUSR2; else if (strcmp(cmd, "quit") == 0) sig = SIGTERM; if (sig == SIGHUP) { error("Unknown kill command '%s'", cmd); return -EINVAL; } pid = read_pid_file(pidfile); if (pid > 0) { if (kill(pid, sig) >= 0) return 0; return -errno; } return 0; } static int check_another_instance(const char *pidfile) { long pid; pid = read_pid_file(pidfile); if (pid >= 0) { /* invoke new card rescan */ if (kill(pid, SIGUSR1) >= 0) { usleep(1000); pid = read_pid_file(pidfile); if (pid >= 0) return 1; } } return 0; } int state_daemon(const char *file, const char *cardname, int period, const char *pidfile) { int count = 0, pcount, psize = 0, i, j, k, changed = 0; time_t last_write, now; unsigned short revents; struct card **cards = NULL; struct pollfd *pfd = NULL, *pfdn; if (check_another_instance(pidfile)) return 0; rescan = 1; signal(SIGABRT, signal_handler_quit); signal(SIGTERM, signal_handler_quit); signal(SIGINT, signal_handler_quit); signal(SIGUSR1, signal_handler_rescan); signal(SIGUSR2, signal_handler_save_and_quit); write_pid_file(pidfile); time(&last_write); while (!quit || save_now) { if (save_now) goto save; if (rescan) { if (cardname) { add_card(&cards, &count, cardname); } else { add_cards(&cards, &count); } snd_config_update_free_global(); rescan = 0; } for (i = pcount = 0; i < count; i++) { if (cards[i] == NULL) continue; pcount += cards[i]->pfds; } if (pcount > psize) { pfdn = realloc(pfd, sizeof(struct pollfd) * pcount); if (pfdn) { psize = pcount; pfd = pfdn; } else { error("No enough memory..."); goto out; } } for (i = j = 0; i < count; i++) { if (cards[i] == NULL) continue; k = snd_ctl_poll_descriptors(cards[i]->handle, pfd + j, pcount - j); if (k != cards[i]->pfds) { error("poll prepare failed: %i", k); goto out; } j += k; } i = poll(pfd, j, (period / 2) * 1000); if (i < 0 && errno == EINTR) continue; if (i < 0) { error("poll failed: %s", strerror(errno)); break; } time(&now); for (i = j = 0; i < count; i++) { if (cards[i] == NULL) continue; k = snd_ctl_poll_descriptors_revents(cards[i]->handle, pfd + j, cards[i]->pfds, &revents); if (k < 0) { error("poll post failed: %i\n", k); goto out; } j += cards[i]->pfds; if (revents & (POLLERR|POLLNVAL)) { card_free(&cards[i]); } else if (revents & POLLIN) { if (card_events(cards[i])) { /* delay the write */ if (!changed) last_write = now; changed = 1; } } } if ((now - last_write >= period && changed) || save_now) { save: changed = save_now = 0; save_state(file, cardname); } } out: free(pfd); remove(pidfile); if (cards) { for (i = 0; i < count; i++) card_free(&cards[i]); free(cards); } return 0; }