xref: /third_party/alsa-utils/alsactl/monitor.c (revision c72fcc34)
1/*
2 *  Advanced Linux Sound Architecture Control Program
3 *  Copyright (c) by Takashi Iwai <tiwai@suse.de>
4 *
5 *   This program is free software; you can redistribute it and/or modify
6 *   it under the terms of the GNU General Public License as published by
7 *   the Free Software Foundation; either version 2 of the License, or
8 *   (at your option) any later version.
9 *
10 *   This program is distributed in the hope that it will be useful,
11 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
12 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 *   GNU General Public License for more details.
14 *
15 *   You should have received a copy of the GNU General Public License
16 *   along with this program; if not, write to the Free Software
17 *   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
18 */
19
20#include "aconfig.h"
21#include "version.h"
22#include <stdio.h>
23#include <stdbool.h>
24#include <string.h>
25#include <sys/epoll.h>
26#include <sys/inotify.h>
27#include <limits.h>
28#include <time.h>
29#include <signal.h>
30#include <sys/signalfd.h>
31
32#include <stddef.h>
33#include "list.h"
34
35#include "alsactl.h"
36
37struct src_entry {
38	snd_ctl_t *handle;
39	char *name;
40	unsigned int pfd_count;
41	struct list_head list;
42};
43
44static void remove_source_entry(struct src_entry *entry)
45{
46	list_del(&entry->list);
47	if (entry->handle)
48		snd_ctl_close(entry->handle);
49	free(entry->name);
50	free(entry);
51}
52
53static void clear_source_list(struct list_head *srcs)
54{
55	struct src_entry *entry, *tmp;
56
57	list_for_each_entry_safe(entry, tmp, srcs, list)
58		remove_source_entry(entry);
59}
60
61static int insert_source_entry(struct list_head *srcs, snd_ctl_t *handle,
62			       const char *name)
63{
64	struct src_entry *entry;
65	int count;
66	int err;
67
68	entry = calloc(1, sizeof(*entry));
69	if (!entry)
70		return -ENOMEM;
71	INIT_LIST_HEAD(&entry->list);
72	entry->handle = handle;
73
74	entry->name = strdup(name);
75	if (!entry->name) {
76		err = -ENOMEM;
77		goto error;
78	}
79
80	count = snd_ctl_poll_descriptors_count(handle);
81	if (count < 0) {
82		err = count;
83		goto error;
84	}
85	if (count == 0) {
86		err = -ENXIO;
87		goto error;
88	}
89	entry->pfd_count = count;
90
91	list_add_tail(&entry->list, srcs);
92
93	return 0;
94error:
95	remove_source_entry(entry);
96	return err;
97}
98
99static int open_ctl(const char *name, snd_ctl_t **ctlp)
100{
101	snd_ctl_t *ctl;
102	int err;
103
104	err = snd_ctl_open(&ctl, name, SND_CTL_READONLY);
105	if (err < 0) {
106		fprintf(stderr, "Cannot open ctl %s\n", name);
107		return err;
108	}
109	err = snd_ctl_subscribe_events(ctl, 1);
110	if (err < 0) {
111		fprintf(stderr, "Cannot open subscribe events to ctl %s\n", name);
112		snd_ctl_close(ctl);
113		return err;
114	}
115	*ctlp = ctl;
116	return 0;
117}
118
119static inline bool seek_entry_by_name(struct list_head *srcs, const char *name)
120{
121	struct src_entry *entry;
122
123	list_for_each_entry(entry, srcs, list) {
124		if (!strcmp(entry->name, name))
125			return true;
126	}
127
128	return false;
129}
130
131static int prepare_source_entry(struct list_head *srcs, const char *name)
132{
133	snd_ctl_t *handle;
134	int err;
135
136	if (!name) {
137		struct snd_card_iterator iter;
138		const char *cardname;
139
140		snd_card_iterator_init(&iter, -1);
141		while ((cardname = snd_card_iterator_next(&iter))) {
142			if (seek_entry_by_name(srcs, cardname))
143				continue;
144			err = open_ctl(cardname, &handle);
145			if (err < 0)
146				return err;
147			err = insert_source_entry(srcs, handle, cardname);
148			if (err < 0)
149				return err;
150		}
151	} else {
152		if (seek_entry_by_name(srcs, name))
153			return 0;
154		err = open_ctl(name, &handle);
155		if (err < 0)
156			return err;
157		err = insert_source_entry(srcs, handle, name);
158		if (err < 0)
159			return err;
160	}
161
162	return 0;
163}
164
165static int check_control_cdev(int infd, bool *retry)
166{
167	struct inotify_event *ev;
168	char *buf;
169	int err = 0;
170
171	buf = calloc(1, sizeof(*ev) + NAME_MAX);
172	if (!buf)
173		return -ENOMEM;
174
175	while (1) {
176		ssize_t len = read(infd, buf, sizeof(*ev) + NAME_MAX);
177		if (len < 0) {
178			if (errno != EAGAIN)
179				err = errno;
180			break;
181		} else if (len == 0) {
182			break;
183		}
184
185		size_t pos = 0;
186		while (pos < (size_t)len) {
187			ev = (struct inotify_event *)(buf + pos);
188			if ((ev->mask & IN_CREATE) &&
189			    strstr(ev->name, "controlC") == ev->name)
190				*retry = true;
191			pos += sizeof(*ev) + ev->len;
192		}
193	}
194
195	free(buf);
196
197	return err;
198}
199
200static int print_event(snd_ctl_t *ctl, const char *name)
201{
202	snd_ctl_event_t *event;
203	unsigned int mask;
204	int err;
205
206	snd_ctl_event_alloca(&event);
207	err = snd_ctl_read(ctl, event);
208	if (err < 0)
209		return err;
210
211	if (snd_ctl_event_get_type(event) != SND_CTL_EVENT_ELEM)
212		return 0;
213
214	printf("node %s, #%d (%i,%i,%i,%s,%i)",
215	       name,
216	       snd_ctl_event_elem_get_numid(event),
217	       snd_ctl_event_elem_get_interface(event),
218	       snd_ctl_event_elem_get_device(event),
219	       snd_ctl_event_elem_get_subdevice(event),
220	       snd_ctl_event_elem_get_name(event),
221	       snd_ctl_event_elem_get_index(event));
222
223	mask = snd_ctl_event_elem_get_mask(event);
224	if (mask == SND_CTL_EVENT_MASK_REMOVE) {
225		printf(" REMOVE\n");
226		return 0;
227	}
228
229	if (mask & SND_CTL_EVENT_MASK_VALUE)
230		printf(" VALUE");
231	if (mask & SND_CTL_EVENT_MASK_INFO)
232		printf(" INFO");
233	if (mask & SND_CTL_EVENT_MASK_ADD)
234		printf(" ADD");
235	if (mask & SND_CTL_EVENT_MASK_TLV)
236		printf(" TLV");
237	printf("\n");
238	fflush(stdout);
239	return 0;
240}
241
242static int operate_dispatcher(int epfd, uint32_t op, struct epoll_event *epev,
243			      struct src_entry *entry)
244{
245	struct pollfd *pfds;
246	int count;
247	int i;
248	int err = 0;
249
250	pfds = calloc(entry->pfd_count, sizeof(*pfds));
251	if (!pfds)
252		return -ENOMEM;
253
254	count = snd_ctl_poll_descriptors(entry->handle, pfds, entry->pfd_count);
255	if (count < 0) {
256		err = count;
257		goto end;
258	}
259	if (count != (int)entry->pfd_count) {
260		err = -EIO;
261		goto end;
262	}
263
264	for (i = 0; i < (int)entry->pfd_count; ++i) {
265		err = epoll_ctl(epfd, op, pfds[i].fd, epev);
266		if (err < 0)
267			break;
268	}
269end:
270	free(pfds);
271	return err;
272}
273
274static int prepare_dispatcher(int epfd, int sigfd, int infd,
275			      struct list_head *srcs)
276{
277	struct epoll_event ev = {0};
278	struct src_entry *entry;
279	int err = 0;
280
281	ev.events = EPOLLIN;
282	ev.data.fd = sigfd;
283	if (epoll_ctl(epfd, EPOLL_CTL_ADD, sigfd, &ev) < 0)
284		return -errno;
285
286	ev.events = EPOLLIN;
287	ev.data.fd = infd;
288	if (epoll_ctl(epfd, EPOLL_CTL_ADD, infd, &ev) < 0)
289		return -errno;
290
291	list_for_each_entry(entry, srcs, list) {
292		ev.events = EPOLLIN;
293		ev.data.ptr = (void *)entry;
294		err = operate_dispatcher(epfd, EPOLL_CTL_ADD, &ev, entry);
295		if (err < 0)
296			break;
297	}
298
299	return err;
300}
301
302static int run_dispatcher(int epfd, int sigfd, int infd, struct list_head *srcs,
303			  bool *retry)
304{
305	struct src_entry *entry;
306	unsigned int max_ev_count;
307	struct epoll_event *epev;
308	int err = 0;
309
310	max_ev_count = 0;
311	list_for_each_entry(entry, srcs, list)
312		max_ev_count += entry->pfd_count;
313
314	epev = calloc(max_ev_count, sizeof(*epev));
315	if (!epev)
316		return -ENOMEM;
317
318	while (true) {
319		int count;
320		int i;
321
322		count = epoll_wait(epfd, epev, max_ev_count, -1);
323		if (count < 0) {
324			if (errno == EINTR)
325				continue;
326			err = count;
327			break;
328		}
329		if (count == 0)
330			continue;
331
332		for (i = 0; i < count; ++i) {
333			struct epoll_event *ev = epev + i;
334
335			if (ev->data.fd == sigfd)
336				goto end;
337
338			if (ev->data.fd == infd) {
339				err = check_control_cdev(infd, retry);
340				if (err < 0 || *retry)
341					goto end;
342				continue;
343			}
344
345			entry = ev->data.ptr;
346			if (ev->events & EPOLLIN)
347				print_event(entry->handle, entry->name);
348			if (ev->events & EPOLLERR) {
349				operate_dispatcher(epfd, EPOLL_CTL_DEL, NULL, entry);
350				remove_source_entry(entry);
351			}
352		}
353	}
354end:
355	free(epev);
356	return err;
357}
358
359static void clear_dispatcher(int epfd, int sigfd, int infd,
360			     struct list_head *srcs)
361{
362	struct src_entry *entry;
363
364	list_for_each_entry(entry, srcs, list)
365		operate_dispatcher(epfd, EPOLL_CTL_DEL, NULL, entry);
366
367	epoll_ctl(epfd, EPOLL_CTL_DEL, infd, NULL);
368
369	epoll_ctl(epfd, EPOLL_CTL_DEL, sigfd, NULL);
370}
371
372static int prepare_signalfd(int *sigfd)
373{
374	sigset_t mask;
375	int fd;
376
377	sigemptyset(&mask);
378	sigaddset(&mask, SIGINT);
379	sigaddset(&mask, SIGTERM);
380
381	if (sigprocmask(SIG_BLOCK, &mask, NULL) < 0)
382		return -errno;
383
384	fd = signalfd(-1, &mask, 0);
385	if (fd < 0)
386		return -errno;
387	*sigfd = fd;
388
389	return 0;
390}
391
392int monitor(const char *name)
393{
394	LIST_HEAD(srcs);
395	int sigfd = 0;
396	int epfd;
397	int infd;
398	int wd = 0;
399	bool retry;
400	int err = 0;
401
402	err = prepare_signalfd(&sigfd);
403	if (err < 0)
404		return err;
405
406	epfd = epoll_create(1);
407	if (epfd < 0) {
408		close(sigfd);
409		return -errno;
410	}
411
412	infd = inotify_init1(IN_NONBLOCK);
413	if (infd < 0) {
414		err = -errno;
415		goto error;
416	}
417	wd = inotify_add_watch(infd, "/dev/snd/", IN_CREATE);
418	if (wd < 0) {
419		err = -errno;
420		goto error;
421	}
422retry:
423	retry = false;
424	err = prepare_source_entry(&srcs, name);
425	if (err < 0)
426		goto error;
427
428	err = prepare_dispatcher(epfd, sigfd, infd, &srcs);
429	if (err >= 0)
430		err = run_dispatcher(epfd, sigfd, infd, &srcs, &retry);
431	clear_dispatcher(epfd, sigfd, infd, &srcs);
432
433	if (retry) {
434		// A simple makeshift for timing gap between creation of nodes
435		// by devtmpfs and chmod() by udevd.
436		struct timespec req = { .tv_sec = 1 };
437		nanosleep(&req, NULL);
438		goto retry;
439	}
440error:
441	clear_source_list(&srcs);
442
443	if (wd > 0)
444		inotify_rm_watch(infd, wd);
445	close(infd);
446
447	close(epfd);
448
449	close(sigfd);
450
451	return err;
452}
453