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 --- alsactl/monitor.c | 118 ++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 108 insertions(+), 10 deletions(-)
diff --git a/alsactl/monitor.c b/alsactl/monitor.c index 559fb4c..a3c3ea4 100644 --- a/alsactl/monitor.c +++ b/alsactl/monitor.c @@ -23,6 +23,9 @@ #include <stdbool.h> #include <string.h> #include <sys/epoll.h> +#include <sys/inotify.h> +#include <limits.h> +#include <time.h> #include <alsa/asoundlib.h>
#include <stddef.h> @@ -133,6 +136,18 @@ static int open_ctl(const char *name, snd_ctl_t **ctlp) 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) { 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); while ((cardname = snd_card_iterator_next(&iter))) { + if (seek_entry_by_name(srcs, cardname)) + continue; err = open_ctl(cardname, &handle); if (err < 0) return err; @@ -152,6 +169,8 @@ static int prepare_source_entry(struct list_head *srcs, const char *name) return err; } } else { + if (seek_entry_by_name(srcs, name)) + return 0; err = open_ctl(name, &handle); if (err < 0) return err; @@ -163,6 +182,41 @@ static int prepare_source_entry(struct list_head *srcs, const char *name) 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) { snd_ctl_event_t *event; @@ -236,13 +290,18 @@ end: 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; 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) { - struct epoll_event ev; ev.events = EPOLLIN; ev.data.ptr = (void *)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; }
-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; 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) { 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) print_event(entry->handle, entry->name); if (ev->events & EPOLLERR) { @@ -290,42 +358,72 @@ static int run_dispatcher(int epfd, struct list_head *srcs) remove_source_entry(entry); } } + if (err < 0) + break; } - +end: free(epev); - 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;
list_for_each_entry(entry, srcs, list) operate_dispatcher(epfd, EPOLL_CTL_DEL, NULL, entry); + + epoll_ctl(epfd, EPOLL_CTL_DEL, infd, NULL); }
int monitor(const char *name) { LIST_HEAD(srcs); int epfd; + int infd; + int wd = 0; + bool retry; int err = 0;
epfd = epoll_create(1); if (epfd < 0) 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); if (err < 0) goto error;
- err = prepare_dispatcher(epfd, &srcs); + err = prepare_dispatcher(epfd, infd, &srcs); if (err >= 0) - err = run_dispatcher(epfd, &srcs); - clear_dispatcher(epfd, &srcs); + err = run_dispatcher(epfd, infd, &srcs, &retry); + 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: clear_source_list(&srcs);
+ if (wd > 0) + inotify_rm_watch(infd, wd); + close(infd); + close(epfd);
return err;