Blame alsactl/monitor.c

Packit Service a9274b
/*
Packit Service a9274b
 *  Advanced Linux Sound Architecture Control Program
Packit Service a9274b
 *  Copyright (c) by Takashi Iwai <tiwai@suse.de>
Packit Service a9274b
 *
Packit Service a9274b
 *   This program is free software; you can redistribute it and/or modify
Packit Service a9274b
 *   it under the terms of the GNU General Public License as published by
Packit Service a9274b
 *   the Free Software Foundation; either version 2 of the License, or
Packit Service a9274b
 *   (at your option) any later version.
Packit Service a9274b
 *
Packit Service a9274b
 *   This program is distributed in the hope that it will be useful,
Packit Service a9274b
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
Packit Service a9274b
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
Packit Service a9274b
 *   GNU General Public License for more details.
Packit Service a9274b
 *
Packit Service a9274b
 *   You should have received a copy of the GNU General Public License
Packit Service a9274b
 *   along with this program; if not, write to the Free Software
Packit Service a9274b
 *   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
Packit Service a9274b
 */
Packit Service a9274b
Packit Service a9274b
#include "aconfig.h"
Packit Service a9274b
#include "version.h"
Packit Service a9274b
#include <stdio.h>
Packit Service a9274b
#include <stdbool.h>
Packit Service a9274b
#include <string.h>
Packit Service a9274b
#include <sys/epoll.h>
Packit Service a9274b
#include <sys/inotify.h>
Packit Service a9274b
#include <limits.h>
Packit Service a9274b
#include <time.h>
Packit Service a9274b
#include <signal.h>
Packit Service a9274b
#include <sys/signalfd.h>
Packit Service a9274b
#include <alsa/asoundlib.h>
Packit Service a9274b
Packit Service a9274b
#include <stddef.h>
Packit Service a9274b
#include "list.h"
Packit Service a9274b
Packit Service a9274b
struct src_entry {
Packit Service a9274b
	snd_ctl_t *handle;
Packit Service a9274b
	char *name;
Packit Service a9274b
	unsigned int pfd_count;
Packit Service a9274b
	struct list_head list;
Packit Service a9274b
};
Packit Service a9274b
Packit Service a9274b
struct snd_card_iterator {
Packit Service a9274b
        int card;
Packit Service a9274b
        char name[16];
Packit Service a9274b
};
Packit Service a9274b
Packit Service a9274b
void snd_card_iterator_init(struct snd_card_iterator *iter)
Packit Service a9274b
{
Packit Service a9274b
        iter->card = -1;
Packit Service a9274b
        memset(iter->name, 0, sizeof(iter->name));
Packit Service a9274b
}
Packit Service a9274b
Packit Service a9274b
static const char *snd_card_iterator_next(struct snd_card_iterator *iter)
Packit Service a9274b
{
Packit Service a9274b
        if (snd_card_next(&iter->card) < 0)
Packit Service a9274b
                return NULL;
Packit Service a9274b
        if (iter->card < 0)
Packit Service a9274b
                return NULL;
Packit Service a9274b
Packit Service a9274b
        snprintf(iter->name, sizeof(iter->name), "hw:%d", iter->card);
Packit Service a9274b
Packit Service a9274b
        return (const char *)iter->name;
Packit Service a9274b
}
Packit Service a9274b
Packit Service a9274b
static void remove_source_entry(struct src_entry *entry)
Packit Service a9274b
{
Packit Service a9274b
	list_del(&entry->list);
Packit Service a9274b
	if (entry->handle)
Packit Service a9274b
		snd_ctl_close(entry->handle);
Packit Service a9274b
	free(entry->name);
Packit Service a9274b
	free(entry);
Packit Service a9274b
}
Packit Service a9274b
Packit Service a9274b
static void clear_source_list(struct list_head *srcs)
Packit Service a9274b
{
Packit Service a9274b
	struct src_entry *entry, *tmp;
Packit Service a9274b
Packit Service a9274b
	list_for_each_entry_safe(entry, tmp, srcs, list)
Packit Service a9274b
		remove_source_entry(entry);
Packit Service a9274b
}
Packit Service a9274b
Packit Service a9274b
static int insert_source_entry(struct list_head *srcs, snd_ctl_t *handle,
Packit Service a9274b
			       const char *name)
Packit Service a9274b
{
Packit Service a9274b
	struct src_entry *entry;
Packit Service a9274b
	int count;
Packit Service a9274b
	int err;
Packit Service a9274b
Packit Service a9274b
	entry = calloc(1, sizeof(*entry));
Packit Service a9274b
	if (!entry)
Packit Service a9274b
		return -ENOMEM;
Packit Service a9274b
	INIT_LIST_HEAD(&entry->list);
Packit Service a9274b
	entry->handle = handle;
Packit Service a9274b
Packit Service a9274b
	entry->name = strdup(name);
Packit Service a9274b
	if (!entry->name) {
Packit Service a9274b
		err = -ENOMEM;
Packit Service a9274b
		goto error;
Packit Service a9274b
	}
Packit Service a9274b
Packit Service a9274b
	count = snd_ctl_poll_descriptors_count(handle);
Packit Service a9274b
	if (count < 0) {
Packit Service a9274b
		err = count;
Packit Service a9274b
		goto error;
Packit Service a9274b
	}
Packit Service a9274b
	if (count == 0) {
Packit Service a9274b
		err = -ENXIO;
Packit Service a9274b
		goto error;
Packit Service a9274b
	}
Packit Service a9274b
	entry->pfd_count = count;
Packit Service a9274b
Packit Service a9274b
	list_add_tail(&entry->list, srcs);
Packit Service a9274b
Packit Service a9274b
	return 0;
Packit Service a9274b
error:
Packit Service a9274b
	remove_source_entry(entry);
Packit Service a9274b
	return err;
Packit Service a9274b
}
Packit Service a9274b
Packit Service a9274b
static int open_ctl(const char *name, snd_ctl_t **ctlp)
Packit Service a9274b
{
Packit Service a9274b
	snd_ctl_t *ctl;
Packit Service a9274b
	int err;
Packit Service a9274b
Packit Service a9274b
	err = snd_ctl_open(&ctl, name, SND_CTL_READONLY);
Packit Service a9274b
	if (err < 0) {
Packit Service a9274b
		fprintf(stderr, "Cannot open ctl %s\n", name);
Packit Service a9274b
		return err;
Packit Service a9274b
	}
Packit Service a9274b
	err = snd_ctl_subscribe_events(ctl, 1);
Packit Service a9274b
	if (err < 0) {
Packit Service a9274b
		fprintf(stderr, "Cannot open subscribe events to ctl %s\n", name);
Packit Service a9274b
		snd_ctl_close(ctl);
Packit Service a9274b
		return err;
Packit Service a9274b
	}
Packit Service a9274b
	*ctlp = ctl;
Packit Service a9274b
	return 0;
Packit Service a9274b
}
Packit Service a9274b
Packit Service a9274b
static inline bool seek_entry_by_name(struct list_head *srcs, const char *name)
Packit Service a9274b
{
Packit Service a9274b
	struct src_entry *entry;
Packit Service a9274b
Packit Service a9274b
	list_for_each_entry(entry, srcs, list) {
Packit Service a9274b
		if (!strcmp(entry->name, name))
Packit Service a9274b
			return true;
Packit Service a9274b
	}
Packit Service a9274b
Packit Service a9274b
	return false;
Packit Service a9274b
}
Packit Service a9274b
Packit Service a9274b
static int prepare_source_entry(struct list_head *srcs, const char *name)
Packit Service a9274b
{
Packit Service a9274b
	snd_ctl_t *handle;
Packit Service a9274b
	int err;
Packit Service a9274b
Packit Service a9274b
	if (!name) {
Packit Service a9274b
		struct snd_card_iterator iter;
Packit Service a9274b
		const char *cardname;
Packit Service a9274b
Packit Service a9274b
		snd_card_iterator_init(&iter);
Packit Service a9274b
		while ((cardname = snd_card_iterator_next(&iter))) {
Packit Service a9274b
			if (seek_entry_by_name(srcs, cardname))
Packit Service a9274b
				continue;
Packit Service a9274b
			err = open_ctl(cardname, &handle);
Packit Service a9274b
			if (err < 0)
Packit Service a9274b
				return err;
Packit Service a9274b
			err = insert_source_entry(srcs, handle, cardname);
Packit Service a9274b
			if (err < 0)
Packit Service a9274b
				return err;
Packit Service a9274b
		}
Packit Service a9274b
	} else {
Packit Service a9274b
		if (seek_entry_by_name(srcs, name))
Packit Service a9274b
			return 0;
Packit Service a9274b
		err = open_ctl(name, &handle);
Packit Service a9274b
		if (err < 0)
Packit Service a9274b
			return err;
Packit Service a9274b
		err = insert_source_entry(srcs, handle, name);
Packit Service a9274b
		if (err < 0)
Packit Service a9274b
			return err;
Packit Service a9274b
	}
Packit Service a9274b
Packit Service a9274b
	return 0;
Packit Service a9274b
}
Packit Service a9274b
Packit Service a9274b
static int check_control_cdev(int infd, bool *retry)
Packit Service a9274b
{
Packit Service a9274b
	struct inotify_event *ev;
Packit Service a9274b
	char *buf;
Packit Service a9274b
	int err = 0;
Packit Service a9274b
Packit Service a9274b
	buf = calloc(1, sizeof(*ev) + NAME_MAX);
Packit Service a9274b
	if (!buf)
Packit Service a9274b
		return -ENOMEM;
Packit Service a9274b
Packit Service a9274b
	while (1) {
Packit Service a9274b
		ssize_t len = read(infd, buf, sizeof(*ev) + NAME_MAX);
Packit Service a9274b
		if (len < 0) {
Packit Service a9274b
			if (errno != EAGAIN)
Packit Service a9274b
				err = errno;
Packit Service a9274b
			break;
Packit Service a9274b
		} else if (len == 0) {
Packit Service a9274b
			break;
Packit Service a9274b
		}
Packit Service a9274b
Packit Service a9274b
		size_t pos = 0;
Packit Service a9274b
		while (pos < len) {
Packit Service a9274b
			ev = (struct inotify_event *)(buf + pos);
Packit Service a9274b
			if ((ev->mask & IN_CREATE) &&
Packit Service a9274b
			    strstr(ev->name, "controlC") == ev->name)
Packit Service a9274b
				*retry = true;
Packit Service a9274b
			pos += sizeof(*ev) + ev->len;
Packit Service a9274b
		}
Packit Service a9274b
	}
Packit Service a9274b
Packit Service a9274b
	free(buf);
Packit Service a9274b
Packit Service a9274b
	return err;
Packit Service a9274b
}
Packit Service a9274b
Packit Service a9274b
static int print_event(snd_ctl_t *ctl, const char *name)
Packit Service a9274b
{
Packit Service a9274b
	snd_ctl_event_t *event;
Packit Service a9274b
	unsigned int mask;
Packit Service a9274b
	int err;
Packit Service a9274b
Packit Service a9274b
	snd_ctl_event_alloca(&event);
Packit Service a9274b
	err = snd_ctl_read(ctl, event);
Packit Service a9274b
	if (err < 0)
Packit Service a9274b
		return err;
Packit Service a9274b
Packit Service a9274b
	if (snd_ctl_event_get_type(event) != SND_CTL_EVENT_ELEM)
Packit Service a9274b
		return 0;
Packit Service a9274b
Packit Service a9274b
	printf("node %s, #%d (%i,%i,%i,%s,%i)",
Packit Service a9274b
	       name,
Packit Service a9274b
	       snd_ctl_event_elem_get_numid(event),
Packit Service a9274b
	       snd_ctl_event_elem_get_interface(event),
Packit Service a9274b
	       snd_ctl_event_elem_get_device(event),
Packit Service a9274b
	       snd_ctl_event_elem_get_subdevice(event),
Packit Service a9274b
	       snd_ctl_event_elem_get_name(event),
Packit Service a9274b
	       snd_ctl_event_elem_get_index(event));
Packit Service a9274b
Packit Service a9274b
	mask = snd_ctl_event_elem_get_mask(event);
Packit Service a9274b
	if (mask == SND_CTL_EVENT_MASK_REMOVE) {
Packit Service a9274b
		printf(" REMOVE\n");
Packit Service a9274b
		return 0;
Packit Service a9274b
	}
Packit Service a9274b
Packit Service a9274b
	if (mask & SND_CTL_EVENT_MASK_VALUE)
Packit Service a9274b
		printf(" VALUE");
Packit Service a9274b
	if (mask & SND_CTL_EVENT_MASK_INFO)
Packit Service a9274b
		printf(" INFO");
Packit Service a9274b
	if (mask & SND_CTL_EVENT_MASK_ADD)
Packit Service a9274b
		printf(" ADD");
Packit Service a9274b
	if (mask & SND_CTL_EVENT_MASK_TLV)
Packit Service a9274b
		printf(" TLV");
Packit Service a9274b
	printf("\n");
Packit Service a9274b
	return 0;
Packit Service a9274b
}
Packit Service a9274b
Packit Service a9274b
static int operate_dispatcher(int epfd, uint32_t op, struct epoll_event *epev,
Packit Service a9274b
			      struct src_entry *entry)
Packit Service a9274b
{
Packit Service a9274b
	struct pollfd *pfds;
Packit Service a9274b
	int count;
Packit Service a9274b
	int i;
Packit Service a9274b
	int err = 0;
Packit Service a9274b
Packit Service a9274b
	pfds = calloc(entry->pfd_count, sizeof(*pfds));
Packit Service a9274b
	if (!pfds)
Packit Service a9274b
		return -ENOMEM;
Packit Service a9274b
Packit Service a9274b
	count = snd_ctl_poll_descriptors(entry->handle, pfds, entry->pfd_count);
Packit Service a9274b
	if (count < 0) {
Packit Service a9274b
		err = count;
Packit Service a9274b
		goto end;
Packit Service a9274b
	}
Packit Service a9274b
	if (count != entry->pfd_count) {
Packit Service a9274b
		err = -EIO;
Packit Service a9274b
		goto end;
Packit Service a9274b
	}
Packit Service a9274b
Packit Service a9274b
	for (i = 0; i < entry->pfd_count; ++i) {
Packit Service a9274b
		err = epoll_ctl(epfd, op, pfds[i].fd, epev);
Packit Service a9274b
		if (err < 0)
Packit Service a9274b
			break;
Packit Service a9274b
	}
Packit Service a9274b
end:
Packit Service a9274b
	free(pfds);
Packit Service a9274b
	return err;
Packit Service a9274b
}
Packit Service a9274b
Packit Service a9274b
static int prepare_dispatcher(int epfd, int sigfd, int infd,
Packit Service a9274b
			      struct list_head *srcs)
Packit Service a9274b
{
Packit Service a9274b
	struct epoll_event ev = {0};
Packit Service a9274b
	struct src_entry *entry;
Packit Service a9274b
	int err = 0;
Packit Service a9274b
Packit Service a9274b
	ev.events = EPOLLIN;
Packit Service a9274b
	ev.data.fd = sigfd;
Packit Service a9274b
	if (epoll_ctl(epfd, EPOLL_CTL_ADD, sigfd, &ev) < 0)
Packit Service a9274b
		return -errno;
Packit Service a9274b
Packit Service a9274b
	ev.events = EPOLLIN;
Packit Service a9274b
	ev.data.fd = infd;
Packit Service a9274b
	if (epoll_ctl(epfd, EPOLL_CTL_ADD, infd, &ev) < 0)
Packit Service a9274b
		return -errno;
Packit Service a9274b
Packit Service a9274b
	list_for_each_entry(entry, srcs, list) {
Packit Service a9274b
		ev.events = EPOLLIN;
Packit Service a9274b
		ev.data.ptr = (void *)entry;
Packit Service a9274b
		err = operate_dispatcher(epfd, EPOLL_CTL_ADD, &ev, entry);
Packit Service a9274b
		if (err < 0)
Packit Service a9274b
			break;
Packit Service a9274b
	}
Packit Service a9274b
Packit Service a9274b
	return err;
Packit Service a9274b
}
Packit Service a9274b
Packit Service a9274b
static int run_dispatcher(int epfd, int sigfd, int infd, struct list_head *srcs,
Packit Service a9274b
			  bool *retry)
Packit Service a9274b
{
Packit Service a9274b
	struct src_entry *entry;
Packit Service a9274b
	unsigned int max_ev_count;
Packit Service a9274b
	struct epoll_event *epev;
Packit Service a9274b
	int err = 0;
Packit Service a9274b
Packit Service a9274b
	max_ev_count = 0;
Packit Service a9274b
	list_for_each_entry(entry, srcs, list)
Packit Service a9274b
		max_ev_count += entry->pfd_count;
Packit Service a9274b
Packit Service a9274b
	epev = calloc(max_ev_count, sizeof(*epev));
Packit Service a9274b
	if (!epev)
Packit Service a9274b
		return -ENOMEM;
Packit Service a9274b
Packit Service a9274b
	while (true) {
Packit Service a9274b
		int count;
Packit Service a9274b
		int i;
Packit Service a9274b
Packit Service a9274b
		count = epoll_wait(epfd, epev, max_ev_count, -1);
Packit Service a9274b
		if (count < 0) {
Packit Service a9274b
			if (errno == EINTR)
Packit Service a9274b
				continue;
Packit Service a9274b
			err = count;
Packit Service a9274b
			break;
Packit Service a9274b
		}
Packit Service a9274b
		if (count == 0)
Packit Service a9274b
			continue;
Packit Service a9274b
Packit Service a9274b
		for (i = 0; i < count; ++i) {
Packit Service a9274b
			struct epoll_event *ev = epev + i;
Packit Service a9274b
Packit Service a9274b
			if (ev->data.fd == sigfd)
Packit Service a9274b
				goto end;
Packit Service a9274b
Packit Service a9274b
			if (ev->data.fd == infd) {
Packit Service a9274b
				err = check_control_cdev(infd, retry);
Packit Service a9274b
				if (err < 0 || *retry)
Packit Service a9274b
					goto end;
Packit Service a9274b
				continue;
Packit Service a9274b
			}
Packit Service a9274b
Packit Service a9274b
			entry = ev->data.ptr;
Packit Service a9274b
			if (ev->events & EPOLLIN)
Packit Service a9274b
				print_event(entry->handle, entry->name);
Packit Service a9274b
			if (ev->events & EPOLLERR) {
Packit Service a9274b
				operate_dispatcher(epfd, EPOLL_CTL_DEL, NULL, entry);
Packit Service a9274b
				remove_source_entry(entry);
Packit Service a9274b
			}
Packit Service a9274b
		}
Packit Service a9274b
	}
Packit Service a9274b
end:
Packit Service a9274b
	free(epev);
Packit Service a9274b
	return err;
Packit Service a9274b
}
Packit Service a9274b
Packit Service a9274b
static void clear_dispatcher(int epfd, int sigfd, int infd,
Packit Service a9274b
			     struct list_head *srcs)
Packit Service a9274b
{
Packit Service a9274b
	struct src_entry *entry;
Packit Service a9274b
Packit Service a9274b
	list_for_each_entry(entry, srcs, list)
Packit Service a9274b
		operate_dispatcher(epfd, EPOLL_CTL_DEL, NULL, entry);
Packit Service a9274b
Packit Service a9274b
	epoll_ctl(epfd, EPOLL_CTL_DEL, infd, NULL);
Packit Service a9274b
Packit Service a9274b
	epoll_ctl(epfd, EPOLL_CTL_DEL, sigfd, NULL);
Packit Service a9274b
}
Packit Service a9274b
Packit Service a9274b
static int prepare_signalfd(int *sigfd)
Packit Service a9274b
{
Packit Service a9274b
	sigset_t mask;
Packit Service a9274b
	int fd;
Packit Service a9274b
Packit Service a9274b
	sigemptyset(&mask);
Packit Service a9274b
	sigaddset(&mask, SIGINT);
Packit Service a9274b
	sigaddset(&mask, SIGTERM);
Packit Service a9274b
Packit Service a9274b
	if (sigprocmask(SIG_BLOCK, &mask, NULL) < 0)
Packit Service a9274b
		return -errno;
Packit Service a9274b
Packit Service a9274b
	fd = signalfd(-1, &mask, 0);
Packit Service a9274b
	if (fd < 0)
Packit Service a9274b
		return -errno;
Packit Service a9274b
	*sigfd = fd;
Packit Service a9274b
Packit Service a9274b
	return 0;
Packit Service a9274b
}
Packit Service a9274b
Packit Service a9274b
int monitor(const char *name)
Packit Service a9274b
{
Packit Service a9274b
	LIST_HEAD(srcs);
Packit Service a9274b
	int sigfd = 0;
Packit Service a9274b
	int epfd;
Packit Service a9274b
	int infd;
Packit Service a9274b
	int wd = 0;
Packit Service a9274b
	bool retry;
Packit Service a9274b
	int err = 0;
Packit Service a9274b
Packit Service a9274b
	err = prepare_signalfd(&sigfd);
Packit Service a9274b
	if (err < 0)
Packit Service a9274b
		return err;
Packit Service a9274b
Packit Service a9274b
	epfd = epoll_create(1);
Packit Service a9274b
	if (epfd < 0) {
Packit Service a9274b
		close(sigfd);
Packit Service a9274b
		return -errno;
Packit Service a9274b
	}
Packit Service a9274b
Packit Service a9274b
	infd = inotify_init1(IN_NONBLOCK);
Packit Service a9274b
	if (infd < 0) {
Packit Service a9274b
		err = -errno;
Packit Service a9274b
		goto error;
Packit Service a9274b
	}
Packit Service a9274b
	wd = inotify_add_watch(infd, "/dev/snd/", IN_CREATE);
Packit Service a9274b
	if (wd < 0) {
Packit Service a9274b
		err = -errno;
Packit Service a9274b
		goto error;
Packit Service a9274b
	}
Packit Service a9274b
retry:
Packit Service a9274b
	retry = false;
Packit Service a9274b
	err = prepare_source_entry(&srcs, name);
Packit Service a9274b
	if (err < 0)
Packit Service a9274b
		goto error;
Packit Service a9274b
Packit Service a9274b
	err = prepare_dispatcher(epfd, sigfd, infd, &srcs);
Packit Service a9274b
	if (err >= 0)
Packit Service a9274b
		err = run_dispatcher(epfd, sigfd, infd, &srcs, &retry);
Packit Service a9274b
	clear_dispatcher(epfd, sigfd, infd, &srcs);
Packit Service a9274b
Packit Service a9274b
	if (retry) {
Packit Service a9274b
		// A simple makeshift for timing gap between creation of nodes
Packit Service a9274b
		// by devtmpfs and chmod() by udevd.
Packit Service a9274b
		struct timespec req = { .tv_sec = 1 };
Packit Service a9274b
		nanosleep(&req, NULL);
Packit Service a9274b
		goto retry;
Packit Service a9274b
	}
Packit Service a9274b
error:
Packit Service a9274b
	clear_source_list(&srcs);
Packit Service a9274b
Packit Service a9274b
	if (wd > 0)
Packit Service a9274b
		inotify_rm_watch(infd, wd);
Packit Service a9274b
	close(infd);
Packit Service a9274b
Packit Service a9274b
	close(epfd);
Packit Service a9274b
Packit Service a9274b
	close(sigfd);
Packit Service a9274b
Packit Service a9274b
	return err;
Packit Service a9274b
}