1 /*
2  *  Advanced Linux Sound Architecture Control Program
3  *  Copyright (c) by Jaroslav Kysela <perex@perex.cz>
4  *
5  *
6  *   This program is free software; you can redistribute it and/or modify
7  *   it under the terms of the GNU General Public License as published by
8  *   the Free Software Foundation; either version 2 of the License, or
9  *   (at your option) any later version.
10  *
11  *   This program is distributed in the hope that it will be useful,
12  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  *   GNU General Public License for more details.
15  *
16  *   You should have received a copy of the GNU General Public License
17  *   along with this program; if not, write to the Free Software
18  *   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
19  *
20  */
21 
22 #include "aconfig.h"
23 #include "version.h"
24 #include <getopt.h>
25 #include <stdarg.h>
26 #include <stdio.h>
27 #include <assert.h>
28 #include <errno.h>
29 #include <signal.h>
30 #include <time.h>
31 #include <poll.h>
32 #include <alsa/asoundlib.h>
33 #include "alsactl.h"
34 
35 struct id_list {
36 	snd_ctl_elem_id_t **list;
37 	int size;
38 };
39 
40 struct card {
41 	int index;
42 	int pfds;
43 	snd_ctl_t *handle;
44 	struct id_list whitelist;
45 	struct id_list blacklist;
46 };
47 
48 static int quit = 0;
49 static int rescan = 0;
50 static int save_now = 0;
51 
signal_handler_quit(int sig)52 static void signal_handler_quit(int sig)
53 {
54 	quit = 1;
55 	signal(sig, signal_handler_quit);
56 }
57 
signal_handler_save_and_quit(int sig)58 static void signal_handler_save_and_quit(int sig)
59 {
60 	quit = save_now = 1;
61 	signal(sig, signal_handler_quit);
62 }
63 
signal_handler_rescan(int sig)64 static void signal_handler_rescan(int sig)
65 {
66 	rescan = 1;
67 	signal(sig, signal_handler_rescan);
68 }
69 
free_list(struct id_list * list)70 static void free_list(struct id_list *list)
71 {
72 	int i;
73 
74 	for (i = 0; i < list->size; i++)
75 		free(list->list[i]);
76 	free(list->list);
77 }
78 
card_free(struct card ** card)79 static void card_free(struct card **card)
80 {
81 	struct card *c = *card;
82 
83 	free_list(&c->blacklist);
84 	free_list(&c->whitelist);
85 	if (c->handle)
86 		snd_ctl_close(c->handle);
87 	free(c);
88 	*card = NULL;
89 }
90 
add_card(struct card *** cards,int * count,const char * cardname)91 static void add_card(struct card ***cards, int *count, const char *cardname)
92 {
93 	struct card *card, **cc;
94 	int i, index, findex;
95 	char device[16];
96 
97 	index = snd_card_get_index(cardname);
98 	if (index < 0)
99 		return;
100 	for (i = 0, findex = -1; i < *count; i++) {
101 		if ((*cards)[i] == NULL) {
102 			findex = i;
103 		} else {
104 			if ((*cards)[i]->index == index)
105 				return;
106 		}
107 	}
108 	card = calloc(1, sizeof(*card));
109 	if (card == NULL)
110 		return;
111 	card->index = index;
112 	sprintf(device, "hw:%i", index);
113 	if (snd_ctl_open(&card->handle, device, SND_CTL_READONLY|SND_CTL_NONBLOCK) < 0) {
114 		card_free(&card);
115 		return;
116 	}
117 	card->pfds = snd_ctl_poll_descriptors_count(card->handle);
118 	if (card->pfds < 0) {
119 		card_free(&card);
120 		return;
121 	}
122 	if (snd_ctl_subscribe_events(card->handle, 1) < 0) {
123 		card_free(&card);
124 		return;
125 	}
126 	if (findex >= 0) {
127 		(*cards)[findex] = card;
128 	} else {
129 		cc = realloc(*cards, sizeof(void *) * (*count + 1));
130 		if (cc == NULL) {
131 			card_free(&card);
132 			return;
133 		}
134 		cc[*count] = card;
135 		*count = *count + 1;
136 		*cards = cc;
137 	}
138 }
139 
add_cards(struct card *** cards,int * count)140 static void add_cards(struct card ***cards, int *count)
141 {
142 	int card = -1;
143 	char cardname[16];
144 
145 	while (1) {
146 		if (snd_card_next(&card) < 0)
147 			break;
148 		if (card < 0)
149 			break;
150 		if (card >= 0) {
151 			sprintf(cardname, "%i", card);
152 			add_card(cards, count, cardname);
153 		}
154 	}
155 }
156 
compare_ids(snd_ctl_elem_id_t * id1,snd_ctl_elem_id_t * id2)157 static int compare_ids(snd_ctl_elem_id_t *id1, snd_ctl_elem_id_t *id2)
158 {
159 	if (id1 == NULL || id2 == NULL)
160 		return 0;
161 	return snd_ctl_elem_id_get_interface(id1) == snd_ctl_elem_id_get_interface(id2) &&
162 	       snd_ctl_elem_id_get_index(id1) == snd_ctl_elem_id_get_index(id2) &&
163 	       strcmp(snd_ctl_elem_id_get_name(id1), snd_ctl_elem_id_get_name(id2)) == 0 &&
164 	       snd_ctl_elem_id_get_device(id1) == snd_ctl_elem_id_get_device(id2) &&
165 	       snd_ctl_elem_id_get_subdevice(id1) == snd_ctl_elem_id_get_subdevice(id2);
166 }
167 
in_list(struct id_list * list,snd_ctl_elem_id_t * id)168 static int in_list(struct id_list *list, snd_ctl_elem_id_t *id)
169 {
170 	int i;
171 	snd_ctl_elem_id_t *id1;
172 
173 	for (i = 0; i < list->size; i++) {
174 		id1 = list->list[i];
175 		if (id1 == NULL)
176 			continue;
177 		if (compare_ids(id, id1))
178 			return 1;
179 	}
180 	return 0;
181 }
182 
remove_from_list(struct id_list * list,snd_ctl_elem_id_t * id)183 static void remove_from_list(struct id_list *list, snd_ctl_elem_id_t *id)
184 {
185 	int i;
186 
187 	for (i = 0; i < list->size; i++) {
188 		if (compare_ids(id, list->list[i])) {
189 			free(list->list[i]);
190 			list->list[i] = NULL;
191 		}
192 	}
193 }
194 
add_to_list(struct id_list * list,snd_ctl_elem_id_t * id)195 static void add_to_list(struct id_list *list, snd_ctl_elem_id_t *id)
196 {
197 	snd_ctl_elem_id_t *id1;
198 	snd_ctl_elem_id_t **n;
199 	int i;
200 
201 	if (snd_ctl_elem_id_malloc(&id1))
202 		return;
203 	snd_ctl_elem_id_copy(id1, id);
204 	for (i = 0; i < list->size; i++) {
205 		if (list->list[i] == NULL) {
206 			list->list[i] = id1;
207 			return;
208 		}
209 	}
210 	n = realloc(list->list, sizeof(void *) * (list->size + 1));
211 	if (n == NULL)
212 		return;
213 	n[list->size] = id1;
214 	list->size++;
215 	list->list = n;
216 }
217 
check_lists(struct card * card,snd_ctl_elem_id_t * id)218 static int check_lists(struct card *card, snd_ctl_elem_id_t *id)
219 {
220 	snd_ctl_elem_info_t *info;
221 	snd_ctl_elem_info_alloca(&info);
222 
223 	if (in_list(&card->blacklist, id))
224 		return 0;
225 	if (in_list(&card->whitelist, id))
226 		return 1;
227 	snd_ctl_elem_info_set_id(info, id);
228 	if (snd_ctl_elem_info(card->handle, info) < 0)
229 		return 0;
230 	if (snd_ctl_elem_info_is_writable(info) ||
231 	    snd_ctl_elem_info_is_tlv_writable(info)) {
232 		add_to_list(&card->whitelist, id);
233 		return 1;
234 	} else {
235 		add_to_list(&card->blacklist, id);
236 		return 0;
237 	}
238 }
239 
card_events(struct card * card)240 static int card_events(struct card *card)
241 {
242 	int res = 0;
243 	snd_ctl_event_t *ev;
244 	snd_ctl_event_type_t type;
245 	unsigned int mask;
246 	snd_ctl_elem_id_t *id;
247 	snd_ctl_event_alloca(&ev);
248 	snd_ctl_elem_id_alloca(&id);
249 
250 	while (snd_ctl_read(card->handle, ev) == 1) {
251 		type = snd_ctl_event_get_type(ev);
252 		if (type != SND_CTL_EVENT_ELEM)
253 			continue;
254 		mask = snd_ctl_event_elem_get_mask(ev);
255 		snd_ctl_event_elem_get_id(ev, id);
256 		if (mask == SND_CTL_EVENT_MASK_REMOVE) {
257 			remove_from_list(&card->whitelist, id);
258 			remove_from_list(&card->blacklist, id);
259 			continue;
260 		}
261 		if (mask & SND_CTL_EVENT_MASK_INFO) {
262 			remove_from_list(&card->whitelist, id);
263 			remove_from_list(&card->blacklist, id);
264 		}
265 		if (mask & (SND_CTL_EVENT_MASK_VALUE|
266 			    SND_CTL_EVENT_MASK_ADD|
267 			    SND_CTL_EVENT_MASK_TLV)) {
268 			if (check_lists(card, id))
269 				res = 1;
270 		}
271 	}
272 	return res;
273 }
274 
read_pid_file(const char * pidfile)275 static long read_pid_file(const char *pidfile)
276 {
277 	int fd, err;
278 	char pid_txt[12];
279 
280 	fd = open(pidfile, O_RDONLY);
281 	if (fd >= 0) {
282 		err = read(fd, pid_txt, 11);
283 		if (err != 11)
284 			err = err < 0 ? -errno : -EIO;
285 		close(fd);
286 		pid_txt[11] = '\0';
287 		return atol(pid_txt);
288 	} else {
289 		return -errno;
290 	}
291 }
292 
write_pid_file(const char * pidfile)293 static int write_pid_file(const char *pidfile)
294 {
295 	int fd, err;
296 	char pid_txt[12];
297 
298 	sprintf(pid_txt, "%10li\n", (long)getpid());
299 	fd = open(pidfile, O_WRONLY|O_CREAT|O_EXCL, 0600);
300 	if (fd >= 0) {
301 		err = write(fd, pid_txt, 11);
302 		if (err != 11) {
303 			err = err < 0 ? -errno : -EIO;
304 			unlink(pidfile);
305 		} else {
306 			err = 0;
307 		}
308 		close(fd);
309 	} else {
310 		err = -errno;
311 	}
312 	return err;
313 }
314 
state_daemon_kill(const char * pidfile,const char * cmd)315 int state_daemon_kill(const char *pidfile, const char *cmd)
316 {
317 	long pid;
318 	int sig = SIGHUP;
319 
320 	if (cmd == NULL) {
321 		error("Specify kill command (quit, rescan or save_and_quit)");
322 		return -EINVAL;
323 	}
324 	if (strcmp(cmd, "rescan") == 0)
325 		sig = SIGUSR1;
326 	else if (strcmp(cmd, "save_and_quit") == 0)
327 		sig = SIGUSR2;
328 	else if (strcmp(cmd, "quit") == 0)
329 		sig = SIGTERM;
330 	if (sig == SIGHUP) {
331 		error("Unknown kill command '%s'", cmd);
332 		return -EINVAL;
333 	}
334 	pid = read_pid_file(pidfile);
335 	if (pid > 0) {
336 		if (kill(pid, sig) >= 0)
337 			return 0;
338 		return -errno;
339 	}
340 	return 0;
341 }
342 
check_another_instance(const char * pidfile)343 static int check_another_instance(const char *pidfile)
344 {
345 	long pid;
346 
347 	pid = read_pid_file(pidfile);
348 	if (pid >= 0) {
349 		/* invoke new card rescan */
350 		if (kill(pid, SIGUSR1) >= 0) {
351 			usleep(1000);
352 			pid = read_pid_file(pidfile);
353 			if (pid >= 0)
354 				return 1;
355 		}
356 	}
357 	return 0;
358 }
359 
state_daemon(const char * file,const char * cardname,int period,const char * pidfile)360 int state_daemon(const char *file, const char *cardname, int period,
361 		 const char *pidfile)
362 {
363 	int count = 0, pcount, psize = 0, i, j, k, changed = 0;
364 	time_t last_write, now;
365 	unsigned short revents;
366 	struct card **cards = NULL;
367 	struct pollfd *pfd = NULL, *pfdn;
368 
369 	if (check_another_instance(pidfile))
370 		return 0;
371 	rescan = 1;
372 	signal(SIGABRT, signal_handler_quit);
373 	signal(SIGTERM, signal_handler_quit);
374 	signal(SIGINT, signal_handler_quit);
375 	signal(SIGUSR1, signal_handler_rescan);
376 	signal(SIGUSR2, signal_handler_save_and_quit);
377 	write_pid_file(pidfile);
378 	time(&last_write);
379 	while (!quit || save_now) {
380 		if (save_now)
381 			goto save;
382 		if (rescan) {
383 			if (cardname) {
384 				add_card(&cards, &count, cardname);
385 			} else {
386 				add_cards(&cards, &count);
387 			}
388 			snd_config_update_free_global();
389 			rescan = 0;
390 		}
391 		for (i = pcount = 0; i < count; i++) {
392 			if (cards[i] == NULL)
393 				continue;
394 			pcount += cards[i]->pfds;
395 		}
396 		if (pcount > psize) {
397 			pfdn = realloc(pfd, sizeof(struct pollfd) * pcount);
398 			if (pfdn) {
399 				psize = pcount;
400 				pfd = pfdn;
401 			} else {
402 				error("No enough memory...");
403 				goto out;
404 			}
405 		}
406 		for (i = j = 0; i < count; i++) {
407 			if (cards[i] == NULL)
408 				continue;
409 			k = snd_ctl_poll_descriptors(cards[i]->handle, pfd + j, pcount - j);
410 			if (k != cards[i]->pfds) {
411 				error("poll prepare failed: %i", k);
412 				goto out;
413 			}
414 			j += k;
415 		}
416 		i = poll(pfd, j, (period / 2) * 1000);
417 		if (i < 0 && errno == EINTR)
418 			continue;
419 		if (i < 0) {
420 			error("poll failed: %s", strerror(errno));
421 			break;
422 		}
423 		time(&now);
424 		for (i = j = 0; i < count; i++) {
425 			if (cards[i] == NULL)
426 				continue;
427 			k = snd_ctl_poll_descriptors_revents(cards[i]->handle,
428 					pfd + j, cards[i]->pfds, &revents);
429 			if (k < 0) {
430 				error("poll post failed: %i\n", k);
431 				goto out;
432 			}
433 			j += cards[i]->pfds;
434 			if (revents & (POLLERR|POLLNVAL)) {
435 				card_free(&cards[i]);
436 			} else if (revents & POLLIN) {
437 				if (card_events(cards[i])) {
438 					/* delay the write */
439 					if (!changed)
440 						last_write = now;
441 					changed = 1;
442 				}
443 			}
444 		}
445 		if ((now - last_write >= period && changed) || save_now) {
446 save:
447 			changed = save_now = 0;
448 			save_state(file, cardname);
449 		}
450 	}
451 out:
452 	free(pfd);
453 	remove(pidfile);
454 	if (cards) {
455 		for (i = 0; i < count; i++)
456 			card_free(&cards[i]);
457 		free(cards);
458 	}
459 	return 0;
460 }
461