• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
34 struct id_list {
35 	snd_ctl_elem_id_t **list;
36 	int size;
37 };
38 
39 struct card {
40 	int index;
41 	int pfds;
42 	snd_ctl_t *handle;
43 	struct id_list whitelist;
44 	struct id_list blacklist;
45 };
46 
47 static int quit = 0;
48 static int rescan = 0;
49 static int save_now = 0;
50 
signal_handler_quit(int sig)51 static void signal_handler_quit(int sig)
52 {
53 	quit = 1;
54 	signal(sig, signal_handler_quit);
55 }
56 
signal_handler_save_and_quit(int sig)57 static void signal_handler_save_and_quit(int sig)
58 {
59 	quit = save_now = 1;
60 	signal(sig, signal_handler_quit);
61 }
62 
signal_handler_rescan(int sig)63 static void signal_handler_rescan(int sig)
64 {
65 	rescan = 1;
66 	signal(sig, signal_handler_rescan);
67 }
68 
free_list(struct id_list * list)69 static 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 
card_free(struct card ** card)78 static 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 
add_card(struct card *** cards,int * count,const char * cardname)90 static 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 
add_cards(struct card *** cards,int * count)139 static 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 
compare_ids(snd_ctl_elem_id_t * id1,snd_ctl_elem_id_t * id2)156 static 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 
in_list(struct id_list * list,snd_ctl_elem_id_t * id)167 static 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 
remove_from_list(struct id_list * list,snd_ctl_elem_id_t * id)182 static 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 
add_to_list(struct id_list * list,snd_ctl_elem_id_t * id)194 static 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 
check_lists(struct card * card,snd_ctl_elem_id_t * id)217 static 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 
card_events(struct card * card)239 static 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 
read_pid_file(const char * pidfile)274 static 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 
write_pid_file(const char * pidfile)292 static 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 
state_daemon_kill(const char * pidfile,const char * cmd)314 int 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 
check_another_instance(const char * pidfile)342 static 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 
state_daemon(const char * file,const char * cardname,int period,const char * pidfile)359 int 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) {
445 save:
446 			changed = save_now = 0;
447 			save_state(file, cardname);
448 		}
449 	}
450 out:
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