blob: 127701efaf0fdf59dc8adfd336018056e7d50906 [file] [log] [blame]
/*
* Advanced Linux Sound Architecture Control Program
* Copyright (c) by Jaroslav Kysela <perex@perex.cz>
*
*
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#include "aconfig.h"
#include "version.h"
#include <getopt.h>
#include <stdarg.h>
#include <stdio.h>
#include <assert.h>
#include <errno.h>
#include <signal.h>
#include <time.h>
#include <poll.h>
#include <alsa/asoundlib.h>
#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);
for (i = 0; i < count; i++)
card_free(&cards[i]);
free(cards);
return 0;
}