alsactl: handle detection of new sound card

At present, plug-and-play is not supported in a mode of 'monitor',
thus new sound card is not handled during runtime. This is not happy.

This commit uses Linux-specific inotify(7) to monitor '/dev/snd'
directory. When some files are newly added to the directory,
event dispatcher is suspended. Event sources are scanned again and the
dispatcher continue to run.

Signed-off-by: Takashi Sakamoto <o-takashi@sakamocchi.jp>
Signed-off-by: Jaroslav Kysela <perex@perex.cz>
This commit is contained in:
Takashi Sakamoto 2018-10-14 23:36:33 +09:00 committed by Jaroslav Kysela
parent 440ea7d38d
commit e57a0a0809

View file

@ -23,6 +23,9 @@
#include <stdbool.h> #include <stdbool.h>
#include <string.h> #include <string.h>
#include <sys/epoll.h> #include <sys/epoll.h>
#include <sys/inotify.h>
#include <limits.h>
#include <time.h>
#include <alsa/asoundlib.h> #include <alsa/asoundlib.h>
#include <stddef.h> #include <stddef.h>
@ -133,6 +136,18 @@ static int open_ctl(const char *name, snd_ctl_t **ctlp)
return 0; return 0;
} }
static inline bool seek_entry_by_name(struct list_head *srcs, const char *name)
{
struct src_entry *entry;
list_for_each_entry(entry, srcs, list) {
if (!strcmp(entry->name, name))
return true;
}
return false;
}
static int prepare_source_entry(struct list_head *srcs, const char *name) static int prepare_source_entry(struct list_head *srcs, const char *name)
{ {
snd_ctl_t *handle; snd_ctl_t *handle;
@ -144,6 +159,8 @@ static int prepare_source_entry(struct list_head *srcs, const char *name)
snd_card_iterator_init(&iter); snd_card_iterator_init(&iter);
while ((cardname = snd_card_iterator_next(&iter))) { while ((cardname = snd_card_iterator_next(&iter))) {
if (seek_entry_by_name(srcs, cardname))
continue;
err = open_ctl(cardname, &handle); err = open_ctl(cardname, &handle);
if (err < 0) if (err < 0)
return err; return err;
@ -152,6 +169,8 @@ static int prepare_source_entry(struct list_head *srcs, const char *name)
return err; return err;
} }
} else { } else {
if (seek_entry_by_name(srcs, name))
return 0;
err = open_ctl(name, &handle); err = open_ctl(name, &handle);
if (err < 0) if (err < 0)
return err; return err;
@ -163,6 +182,41 @@ static int prepare_source_entry(struct list_head *srcs, const char *name)
return 0; return 0;
} }
static int check_control_cdev(int infd, bool *retry)
{
struct inotify_event *ev;
char *buf;
int err = 0;
buf = calloc(1, sizeof(*ev) + NAME_MAX);
if (!buf)
return -ENOMEM;
while (1) {
ssize_t len = read(infd, buf, sizeof(*ev) + NAME_MAX);
if (len < 0) {
if (errno != EAGAIN)
err = errno;
break;
} else if (len == 0) {
break;
}
size_t pos = 0;
while (pos < len) {
ev = (struct inotify_event *)(buf + pos);
if ((ev->mask & IN_CREATE) &&
strstr(ev->name, "controlC") == ev->name)
*retry = true;
pos += sizeof(*ev) + ev->len;
}
}
free(buf);
return err;
}
static int print_event(snd_ctl_t *ctl, const char *name) static int print_event(snd_ctl_t *ctl, const char *name)
{ {
snd_ctl_event_t *event; snd_ctl_event_t *event;
@ -236,13 +290,18 @@ end:
return err; return err;
} }
static int prepare_dispatcher(int epfd, struct list_head *srcs) static int prepare_dispatcher(int epfd, int infd, struct list_head *srcs)
{ {
struct epoll_event ev = {0};
struct src_entry *entry; struct src_entry *entry;
int err = 0; int err = 0;
ev.events = EPOLLIN;
ev.data.fd = infd;
if (epoll_ctl(epfd, EPOLL_CTL_ADD, infd, &ev) < 0)
return -errno;
list_for_each_entry(entry, srcs, list) { list_for_each_entry(entry, srcs, list) {
struct epoll_event ev;
ev.events = EPOLLIN; ev.events = EPOLLIN;
ev.data.ptr = (void *)entry; ev.data.ptr = (void *)entry;
err = operate_dispatcher(epfd, EPOLL_CTL_ADD, &ev, entry); err = operate_dispatcher(epfd, EPOLL_CTL_ADD, &ev, entry);
@ -253,7 +312,8 @@ static int prepare_dispatcher(int epfd, struct list_head *srcs)
return err; return err;
} }
static int run_dispatcher(int epfd, struct list_head *srcs) static int run_dispatcher(int epfd, int infd, struct list_head *srcs,
bool *retry)
{ {
struct src_entry *entry; struct src_entry *entry;
unsigned int max_ev_count; unsigned int max_ev_count;
@ -282,7 +342,15 @@ static int run_dispatcher(int epfd, struct list_head *srcs)
for (i = 0; i < count; ++i) { for (i = 0; i < count; ++i) {
struct epoll_event *ev = epev + i; struct epoll_event *ev = epev + i;
struct src_entry *entry = (struct src_entry *)ev->data.ptr;
if (ev->data.fd == infd) {
err = check_control_cdev(infd, retry);
if (err < 0 || *retry)
goto end;
continue;
}
entry = ev->data.ptr;
if (ev->events & EPOLLIN) if (ev->events & EPOLLIN)
print_event(entry->handle, entry->name); print_event(entry->handle, entry->name);
if (ev->events & EPOLLERR) { if (ev->events & EPOLLERR) {
@ -290,42 +358,72 @@ static int run_dispatcher(int epfd, struct list_head *srcs)
remove_source_entry(entry); remove_source_entry(entry);
} }
} }
if (err < 0)
break;
} }
end:
free(epev); free(epev);
return err; return err;
} }
static void clear_dispatcher(int epfd, struct list_head *srcs) static void clear_dispatcher(int epfd, int infd, struct list_head *srcs)
{ {
struct src_entry *entry; struct src_entry *entry;
list_for_each_entry(entry, srcs, list) list_for_each_entry(entry, srcs, list)
operate_dispatcher(epfd, EPOLL_CTL_DEL, NULL, entry); operate_dispatcher(epfd, EPOLL_CTL_DEL, NULL, entry);
epoll_ctl(epfd, EPOLL_CTL_DEL, infd, NULL);
} }
int monitor(const char *name) int monitor(const char *name)
{ {
LIST_HEAD(srcs); LIST_HEAD(srcs);
int epfd; int epfd;
int infd;
int wd = 0;
bool retry;
int err = 0; int err = 0;
epfd = epoll_create(1); epfd = epoll_create(1);
if (epfd < 0) if (epfd < 0)
return -errno; return -errno;
infd = inotify_init1(IN_NONBLOCK);
if (infd < 0) {
err = -errno;
goto error;
}
wd = inotify_add_watch(infd, "/dev/snd/", IN_CREATE);
if (wd < 0) {
err = -errno;
goto error;
}
retry:
retry = false;
err = prepare_source_entry(&srcs, name); err = prepare_source_entry(&srcs, name);
if (err < 0) if (err < 0)
goto error; goto error;
err = prepare_dispatcher(epfd, &srcs); err = prepare_dispatcher(epfd, infd, &srcs);
if (err >= 0) if (err >= 0)
err = run_dispatcher(epfd, &srcs); err = run_dispatcher(epfd, infd, &srcs, &retry);
clear_dispatcher(epfd, &srcs); clear_dispatcher(epfd, infd, &srcs);
if (retry) {
// A simple makeshift for timing gap between creation of nodes
// by devtmpfs and chmod() by udevd.
struct timespec req = { .tv_sec = 1 };
nanosleep(&req, NULL);
goto retry;
}
error: error:
clear_source_list(&srcs); clear_source_list(&srcs);
if (wd > 0)
inotify_rm_watch(infd, wd);
close(infd);
close(epfd); close(epfd);
return err; return err;