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