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