alsa-utils/alsactl/daemon.c

462 lines
9.8 KiB
C
Raw Permalink Normal View History

/*
* 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 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 "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;
alsactl: don't free a card pointing NULL alsactl distributed as part of Fedora 40 got a SEGV: # journalctl ... May 17 00:55:58 dev64.localdomain kernel: alsactl[1923]: segfault at 28 ip 00005600705b3373 sp 00007ffd9712bef0 error 4 in alsactl[5600705af000+13000] likely on CPU 5 (core 8, socket 0) ... As the following output of the debug session, card_free() tried a card pointing NULL: $ sudo coredumpctl debug alsactl PID: 1923 (alsactl) UID: 0 (root) GID: 0 (root) Signal: 11 (SEGV) Timestamp: Fri 2024-05-17 00:55:58 JST (3h 34min ago) Command Line: /usr/sbin/alsactl -s -n 19 -c -E ALSA_CONFIG_PATH=/etc/alsa/alsactl.conf --initfile=/lib/alsa/init/00main rdaemon Executable: /usr/sbin/alsactl Control Group: /system.slice/alsa-state.service Unit: alsa-state.service Slice: system.slice Boot ID: 241b5a2ef86f4940bb3d340583c80d88 Machine ID: 437365709a8c488c9481ee4b6651c2ec Hostname: dev64.localdomain Storage: /var/lib/systemd/coredump/core.alsactl.0.241b5a2ef86f4940bb3d340583c80d88.1923.1715874958000000.zst (present) Size on Disk: 81.7K Package: alsa-utils/1.2.11-1.fc40 build-id: 3b6fec58b3566d666d6e9fd48e8fcf04f03f0152 Message: Process 1923 (alsactl) of user 0 dumped core. Module libasound.so.2 from rpm alsa-lib-1.2.11-2.fc40.x86_64 Module alsactl from rpm alsa-utils-1.2.11-1.fc40.x86_64 Stack trace of thread 1923: #0 0x00005600705b3373 card_free (alsactl + 0xa373) #1 0x00005600705c0e54 state_daemon (alsactl + 0x17e54) #2 0x00005600705b2339 main (alsactl + 0x9339) #3 0x00007f4c0b9b7088 __libc_start_call_main (libc.so.6 + 0x2a088) #4 0x00007f4c0b9b714b __libc_start_main_impl (libc.so.6 + 0x2a14b) #5 0x00005600705b2df5 _start (alsactl + 0x9df5) ELF object binary architecture: AMD x86-64 GNU gdb (Fedora Linux) 14.2-1.fc40 Copyright (C) 2023 Free Software Foundation, Inc. License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html> This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. Type "show copying" and "show warranty" for details. This GDB was configured as "x86_64-redhat-linux-gnu". Type "show configuration" for configuration details. For bug reporting instructions, please see: <https://www.gnu.org/software/gdb/bugs/>. Find the GDB manual and other documentation resources online at: <http://www.gnu.org/software/gdb/documentation/>. For help, type "help". Type "apropos word" to search for commands related to "word"... Reading symbols from /usr/sbin/alsactl... Reading symbols from /usr/lib/debug/usr/sbin/alsactl-1.2.11-1.fc40.x86_64.debug... [New LWP 1923] [Thread debugging using libthread_db enabled] Using host libthread_db library "/lib64/libthread_db.so.1". Core was generated by `/usr/sbin/alsactl -s -n 19 -c -E ALSA_CONFIG_PATH=/etc/alsa/alsactl.conf --init'. Program terminated with signal SIGSEGV, Segmentation fault. #0 free_list (list=0x20) at /usr/src/debug/alsa-utils-1.2.11-1.fc40.x86_64/alsactl/daemon.c:73 73 for (i = 0; i < list->size; i++) (gdb) where #0 free_list (list=0x20) at /usr/src/debug/alsa-utils-1.2.11-1.fc40.x86_64/alsactl/daemon.c:73 #1 card_free (card=card@entry=0x5600707455f0) at /usr/src/debug/alsa-utils-1.2.11-1.fc40.x86_64/alsactl/daemon.c:82 #2 0x00005600705c0e54 in state_daemon (file=file@entry=0x5600705c31a1 "/var/lib/alsa/asound.state", cardname=cardname@entry=0x0, period=period@entry=300, pidfile=pidfile@entry=0x5600705c3170 "/var/run/alsactl.pid") at /usr/src/debug/alsa-utils-1.2.11-1.fc40.x86_64/alsactl/daemon.c:455 #3 0x00005600705b2339 in main (argc=<optimized out>, argv=<optimized out>) at /usr/src/debug/alsa-utils-1.2.11-1.fc40.x86_64/alsactl/alsactl.c:459 (gdb) list 68 69 static void free_list(struct id_list *list) 70 { 71 int i; 72 73 for (i = 0; i < list->size; i++) 74 free(list->list[i]); 75 free(list->list); 76 } 77 (gdb) up #1 card_free (card=card@entry=0x5600707455f0) at /usr/src/debug/alsa-utils-1.2.11-1.fc40.x86_64/alsactl/daemon.c:82 82 free_list(&c->blacklist); (gdb) p c $1 = (struct card *) 0x0 (gdb) Closes: https://github.com/alsa-project/alsa-utils/pull/267 Signed-off-by: Masatake YAMATO <yamato@redhat.com> Signed-off-by: Jaroslav Kysela <perex@perex.cz>
2024-05-16 21:29:58 +02:00
if (c == NULL)
return;
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 err < 0 ? err : atol(pid_txt);
} else {
return -errno;
}
}
static int write_pid_file(const char *pidfile)
{
int fd, err;
char pid_txt[14];
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;
}