Imported Upstream version 1.0.28
[platform/upstream/alsa-utils.git] / alsactl / daemon.c
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., 59 Temple Place, Suite 330, Boston, MA  02111-1307 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
52 static void signal_handler_quit(int sig)
53 {
54         quit = 1;
55         signal(sig, signal_handler_quit);
56 }
57
58 static void signal_handler_save_and_quit(int sig)
59 {
60         quit = save_now = 1;
61         signal(sig, signal_handler_quit);
62 }
63
64 static void signal_handler_rescan(int sig)
65 {
66         rescan = 1;
67         signal(sig, signal_handler_rescan);
68 }
69
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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         for (i = 0; i < count; i++)
455                 card_free(&cards[i]);
456         free(cards);
457         return 0;
458 }