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