• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
37 struct src_entry {
38 	snd_ctl_t *handle;
39 	char *name;
40 	unsigned int pfd_count;
41 	struct list_head list;
42 };
43 
remove_source_entry(struct src_entry * entry)44 static 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 
clear_source_list(struct list_head * srcs)53 static 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 
insert_source_entry(struct list_head * srcs,snd_ctl_t * handle,const char * name)61 static 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;
94 error:
95 	remove_source_entry(entry);
96 	return err;
97 }
98 
open_ctl(const char * name,snd_ctl_t ** ctlp)99 static 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 
seek_entry_by_name(struct list_head * srcs,const char * name)119 static 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 
prepare_source_entry(struct list_head * srcs,const char * name)131 static 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 
check_control_cdev(int infd,bool * retry)165 static 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 < 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 
print_event(snd_ctl_t * ctl,const char * name)200 static 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 	return 0;
239 }
240 
operate_dispatcher(int epfd,uint32_t op,struct epoll_event * epev,struct src_entry * entry)241 static int operate_dispatcher(int epfd, uint32_t op, struct epoll_event *epev,
242 			      struct src_entry *entry)
243 {
244 	struct pollfd *pfds;
245 	int count;
246 	int i;
247 	int err = 0;
248 
249 	pfds = calloc(entry->pfd_count, sizeof(*pfds));
250 	if (!pfds)
251 		return -ENOMEM;
252 
253 	count = snd_ctl_poll_descriptors(entry->handle, pfds, entry->pfd_count);
254 	if (count < 0) {
255 		err = count;
256 		goto end;
257 	}
258 	if (count != entry->pfd_count) {
259 		err = -EIO;
260 		goto end;
261 	}
262 
263 	for (i = 0; i < entry->pfd_count; ++i) {
264 		err = epoll_ctl(epfd, op, pfds[i].fd, epev);
265 		if (err < 0)
266 			break;
267 	}
268 end:
269 	free(pfds);
270 	return err;
271 }
272 
prepare_dispatcher(int epfd,int sigfd,int infd,struct list_head * srcs)273 static int prepare_dispatcher(int epfd, int sigfd, int infd,
274 			      struct list_head *srcs)
275 {
276 	struct epoll_event ev = {0};
277 	struct src_entry *entry;
278 	int err = 0;
279 
280 	ev.events = EPOLLIN;
281 	ev.data.fd = sigfd;
282 	if (epoll_ctl(epfd, EPOLL_CTL_ADD, sigfd, &ev) < 0)
283 		return -errno;
284 
285 	ev.events = EPOLLIN;
286 	ev.data.fd = infd;
287 	if (epoll_ctl(epfd, EPOLL_CTL_ADD, infd, &ev) < 0)
288 		return -errno;
289 
290 	list_for_each_entry(entry, srcs, list) {
291 		ev.events = EPOLLIN;
292 		ev.data.ptr = (void *)entry;
293 		err = operate_dispatcher(epfd, EPOLL_CTL_ADD, &ev, entry);
294 		if (err < 0)
295 			break;
296 	}
297 
298 	return err;
299 }
300 
run_dispatcher(int epfd,int sigfd,int infd,struct list_head * srcs,bool * retry)301 static int run_dispatcher(int epfd, int sigfd, int infd, struct list_head *srcs,
302 			  bool *retry)
303 {
304 	struct src_entry *entry;
305 	unsigned int max_ev_count;
306 	struct epoll_event *epev;
307 	int err = 0;
308 
309 	max_ev_count = 0;
310 	list_for_each_entry(entry, srcs, list)
311 		max_ev_count += entry->pfd_count;
312 
313 	epev = calloc(max_ev_count, sizeof(*epev));
314 	if (!epev)
315 		return -ENOMEM;
316 
317 	while (true) {
318 		int count;
319 		int i;
320 
321 		count = epoll_wait(epfd, epev, max_ev_count, -1);
322 		if (count < 0) {
323 			if (errno == EINTR)
324 				continue;
325 			err = count;
326 			break;
327 		}
328 		if (count == 0)
329 			continue;
330 
331 		for (i = 0; i < count; ++i) {
332 			struct epoll_event *ev = epev + i;
333 
334 			if (ev->data.fd == sigfd)
335 				goto end;
336 
337 			if (ev->data.fd == infd) {
338 				err = check_control_cdev(infd, retry);
339 				if (err < 0 || *retry)
340 					goto end;
341 				continue;
342 			}
343 
344 			entry = ev->data.ptr;
345 			if (ev->events & EPOLLIN)
346 				print_event(entry->handle, entry->name);
347 			if (ev->events & EPOLLERR) {
348 				operate_dispatcher(epfd, EPOLL_CTL_DEL, NULL, entry);
349 				remove_source_entry(entry);
350 			}
351 		}
352 	}
353 end:
354 	free(epev);
355 	return err;
356 }
357 
clear_dispatcher(int epfd,int sigfd,int infd,struct list_head * srcs)358 static void clear_dispatcher(int epfd, int sigfd, int infd,
359 			     struct list_head *srcs)
360 {
361 	struct src_entry *entry;
362 
363 	list_for_each_entry(entry, srcs, list)
364 		operate_dispatcher(epfd, EPOLL_CTL_DEL, NULL, entry);
365 
366 	epoll_ctl(epfd, EPOLL_CTL_DEL, infd, NULL);
367 
368 	epoll_ctl(epfd, EPOLL_CTL_DEL, sigfd, NULL);
369 }
370 
prepare_signalfd(int * sigfd)371 static int prepare_signalfd(int *sigfd)
372 {
373 	sigset_t mask;
374 	int fd;
375 
376 	sigemptyset(&mask);
377 	sigaddset(&mask, SIGINT);
378 	sigaddset(&mask, SIGTERM);
379 
380 	if (sigprocmask(SIG_BLOCK, &mask, NULL) < 0)
381 		return -errno;
382 
383 	fd = signalfd(-1, &mask, 0);
384 	if (fd < 0)
385 		return -errno;
386 	*sigfd = fd;
387 
388 	return 0;
389 }
390 
monitor(const char * name)391 int monitor(const char *name)
392 {
393 	LIST_HEAD(srcs);
394 	int sigfd = 0;
395 	int epfd;
396 	int infd;
397 	int wd = 0;
398 	bool retry;
399 	int err = 0;
400 
401 	err = prepare_signalfd(&sigfd);
402 	if (err < 0)
403 		return err;
404 
405 	epfd = epoll_create(1);
406 	if (epfd < 0) {
407 		close(sigfd);
408 		return -errno;
409 	}
410 
411 	infd = inotify_init1(IN_NONBLOCK);
412 	if (infd < 0) {
413 		err = -errno;
414 		goto error;
415 	}
416 	wd = inotify_add_watch(infd, "/dev/snd/", IN_CREATE);
417 	if (wd < 0) {
418 		err = -errno;
419 		goto error;
420 	}
421 retry:
422 	retry = false;
423 	err = prepare_source_entry(&srcs, name);
424 	if (err < 0)
425 		goto error;
426 
427 	err = prepare_dispatcher(epfd, sigfd, infd, &srcs);
428 	if (err >= 0)
429 		err = run_dispatcher(epfd, sigfd, infd, &srcs, &retry);
430 	clear_dispatcher(epfd, sigfd, infd, &srcs);
431 
432 	if (retry) {
433 		// A simple makeshift for timing gap between creation of nodes
434 		// by devtmpfs and chmod() by udevd.
435 		struct timespec req = { .tv_sec = 1 };
436 		nanosleep(&req, NULL);
437 		goto retry;
438 	}
439 error:
440 	clear_source_list(&srcs);
441 
442 	if (wd > 0)
443 		inotify_rm_watch(infd, wd);
444 	close(infd);
445 
446 	close(epfd);
447 
448 	close(sigfd);
449 
450 	return err;
451 }
452