1/* 2 * Advanced Linux Sound Architecture Control Program 3 * Copyright (c) by Takashi Iwai <tiwai@suse.de> 4 * 5 * This program is free software; you can redistribute it and/or modify 6 * it under the terms of the GNU General Public License as published by 7 * the Free Software Foundation; either version 2 of the License, or 8 * (at your option) any later version. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License 16 * along with this program; if not, write to the Free Software 17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 18 */ 19 20#include "aconfig.h" 21#include "version.h" 22#include <stdio.h> 23#include <stdbool.h> 24#include <string.h> 25#include <sys/epoll.h> 26#include <sys/inotify.h> 27#include <limits.h> 28#include <time.h> 29#include <signal.h> 30#include <sys/signalfd.h> 31 32#include <stddef.h> 33#include "list.h" 34 35#include "alsactl.h" 36 37struct src_entry { 38 snd_ctl_t *handle; 39 char *name; 40 unsigned int pfd_count; 41 struct list_head list; 42}; 43 44static void remove_source_entry(struct src_entry *entry) 45{ 46 list_del(&entry->list); 47 if (entry->handle) 48 snd_ctl_close(entry->handle); 49 free(entry->name); 50 free(entry); 51} 52 53static void clear_source_list(struct list_head *srcs) 54{ 55 struct src_entry *entry, *tmp; 56 57 list_for_each_entry_safe(entry, tmp, srcs, list) 58 remove_source_entry(entry); 59} 60 61static int insert_source_entry(struct list_head *srcs, snd_ctl_t *handle, 62 const char *name) 63{ 64 struct src_entry *entry; 65 int count; 66 int err; 67 68 entry = calloc(1, sizeof(*entry)); 69 if (!entry) 70 return -ENOMEM; 71 INIT_LIST_HEAD(&entry->list); 72 entry->handle = handle; 73 74 entry->name = strdup(name); 75 if (!entry->name) { 76 err = -ENOMEM; 77 goto error; 78 } 79 80 count = snd_ctl_poll_descriptors_count(handle); 81 if (count < 0) { 82 err = count; 83 goto error; 84 } 85 if (count == 0) { 86 err = -ENXIO; 87 goto error; 88 } 89 entry->pfd_count = count; 90 91 list_add_tail(&entry->list, srcs); 92 93 return 0; 94error: 95 remove_source_entry(entry); 96 return err; 97} 98 99static int open_ctl(const char *name, snd_ctl_t **ctlp) 100{ 101 snd_ctl_t *ctl; 102 int err; 103 104 err = snd_ctl_open(&ctl, name, SND_CTL_READONLY); 105 if (err < 0) { 106 fprintf(stderr, "Cannot open ctl %s\n", name); 107 return err; 108 } 109 err = snd_ctl_subscribe_events(ctl, 1); 110 if (err < 0) { 111 fprintf(stderr, "Cannot open subscribe events to ctl %s\n", name); 112 snd_ctl_close(ctl); 113 return err; 114 } 115 *ctlp = ctl; 116 return 0; 117} 118 119static inline bool seek_entry_by_name(struct list_head *srcs, const char *name) 120{ 121 struct src_entry *entry; 122 123 list_for_each_entry(entry, srcs, list) { 124 if (!strcmp(entry->name, name)) 125 return true; 126 } 127 128 return false; 129} 130 131static int prepare_source_entry(struct list_head *srcs, const char *name) 132{ 133 snd_ctl_t *handle; 134 int err; 135 136 if (!name) { 137 struct snd_card_iterator iter; 138 const char *cardname; 139 140 snd_card_iterator_init(&iter, -1); 141 while ((cardname = snd_card_iterator_next(&iter))) { 142 if (seek_entry_by_name(srcs, cardname)) 143 continue; 144 err = open_ctl(cardname, &handle); 145 if (err < 0) 146 return err; 147 err = insert_source_entry(srcs, handle, cardname); 148 if (err < 0) 149 return err; 150 } 151 } else { 152 if (seek_entry_by_name(srcs, name)) 153 return 0; 154 err = open_ctl(name, &handle); 155 if (err < 0) 156 return err; 157 err = insert_source_entry(srcs, handle, name); 158 if (err < 0) 159 return err; 160 } 161 162 return 0; 163} 164 165static int check_control_cdev(int infd, bool *retry) 166{ 167 struct inotify_event *ev; 168 char *buf; 169 int err = 0; 170 171 buf = calloc(1, sizeof(*ev) + NAME_MAX); 172 if (!buf) 173 return -ENOMEM; 174 175 while (1) { 176 ssize_t len = read(infd, buf, sizeof(*ev) + NAME_MAX); 177 if (len < 0) { 178 if (errno != EAGAIN) 179 err = errno; 180 break; 181 } else if (len == 0) { 182 break; 183 } 184 185 size_t pos = 0; 186 while (pos < (size_t)len) { 187 ev = (struct inotify_event *)(buf + pos); 188 if ((ev->mask & IN_CREATE) && 189 strstr(ev->name, "controlC") == ev->name) 190 *retry = true; 191 pos += sizeof(*ev) + ev->len; 192 } 193 } 194 195 free(buf); 196 197 return err; 198} 199 200static int print_event(snd_ctl_t *ctl, const char *name) 201{ 202 snd_ctl_event_t *event; 203 unsigned int mask; 204 int err; 205 206 snd_ctl_event_alloca(&event); 207 err = snd_ctl_read(ctl, event); 208 if (err < 0) 209 return err; 210 211 if (snd_ctl_event_get_type(event) != SND_CTL_EVENT_ELEM) 212 return 0; 213 214 printf("node %s, #%d (%i,%i,%i,%s,%i)", 215 name, 216 snd_ctl_event_elem_get_numid(event), 217 snd_ctl_event_elem_get_interface(event), 218 snd_ctl_event_elem_get_device(event), 219 snd_ctl_event_elem_get_subdevice(event), 220 snd_ctl_event_elem_get_name(event), 221 snd_ctl_event_elem_get_index(event)); 222 223 mask = snd_ctl_event_elem_get_mask(event); 224 if (mask == SND_CTL_EVENT_MASK_REMOVE) { 225 printf(" REMOVE\n"); 226 return 0; 227 } 228 229 if (mask & SND_CTL_EVENT_MASK_VALUE) 230 printf(" VALUE"); 231 if (mask & SND_CTL_EVENT_MASK_INFO) 232 printf(" INFO"); 233 if (mask & SND_CTL_EVENT_MASK_ADD) 234 printf(" ADD"); 235 if (mask & SND_CTL_EVENT_MASK_TLV) 236 printf(" TLV"); 237 printf("\n"); 238 fflush(stdout); 239 return 0; 240} 241 242static int operate_dispatcher(int epfd, uint32_t op, struct epoll_event *epev, 243 struct src_entry *entry) 244{ 245 struct pollfd *pfds; 246 int count; 247 int i; 248 int err = 0; 249 250 pfds = calloc(entry->pfd_count, sizeof(*pfds)); 251 if (!pfds) 252 return -ENOMEM; 253 254 count = snd_ctl_poll_descriptors(entry->handle, pfds, entry->pfd_count); 255 if (count < 0) { 256 err = count; 257 goto end; 258 } 259 if (count != (int)entry->pfd_count) { 260 err = -EIO; 261 goto end; 262 } 263 264 for (i = 0; i < (int)entry->pfd_count; ++i) { 265 err = epoll_ctl(epfd, op, pfds[i].fd, epev); 266 if (err < 0) 267 break; 268 } 269end: 270 free(pfds); 271 return err; 272} 273 274static int prepare_dispatcher(int epfd, int sigfd, int infd, 275 struct list_head *srcs) 276{ 277 struct epoll_event ev = {0}; 278 struct src_entry *entry; 279 int err = 0; 280 281 ev.events = EPOLLIN; 282 ev.data.fd = sigfd; 283 if (epoll_ctl(epfd, EPOLL_CTL_ADD, sigfd, &ev) < 0) 284 return -errno; 285 286 ev.events = EPOLLIN; 287 ev.data.fd = infd; 288 if (epoll_ctl(epfd, EPOLL_CTL_ADD, infd, &ev) < 0) 289 return -errno; 290 291 list_for_each_entry(entry, srcs, list) { 292 ev.events = EPOLLIN; 293 ev.data.ptr = (void *)entry; 294 err = operate_dispatcher(epfd, EPOLL_CTL_ADD, &ev, entry); 295 if (err < 0) 296 break; 297 } 298 299 return err; 300} 301 302static int run_dispatcher(int epfd, int sigfd, int infd, struct list_head *srcs, 303 bool *retry) 304{ 305 struct src_entry *entry; 306 unsigned int max_ev_count; 307 struct epoll_event *epev; 308 int err = 0; 309 310 max_ev_count = 0; 311 list_for_each_entry(entry, srcs, list) 312 max_ev_count += entry->pfd_count; 313 314 epev = calloc(max_ev_count, sizeof(*epev)); 315 if (!epev) 316 return -ENOMEM; 317 318 while (true) { 319 int count; 320 int i; 321 322 count = epoll_wait(epfd, epev, max_ev_count, -1); 323 if (count < 0) { 324 if (errno == EINTR) 325 continue; 326 err = count; 327 break; 328 } 329 if (count == 0) 330 continue; 331 332 for (i = 0; i < count; ++i) { 333 struct epoll_event *ev = epev + i; 334 335 if (ev->data.fd == sigfd) 336 goto end; 337 338 if (ev->data.fd == infd) { 339 err = check_control_cdev(infd, retry); 340 if (err < 0 || *retry) 341 goto end; 342 continue; 343 } 344 345 entry = ev->data.ptr; 346 if (ev->events & EPOLLIN) 347 print_event(entry->handle, entry->name); 348 if (ev->events & EPOLLERR) { 349 operate_dispatcher(epfd, EPOLL_CTL_DEL, NULL, entry); 350 remove_source_entry(entry); 351 } 352 } 353 } 354end: 355 free(epev); 356 return err; 357} 358 359static void clear_dispatcher(int epfd, int sigfd, int infd, 360 struct list_head *srcs) 361{ 362 struct src_entry *entry; 363 364 list_for_each_entry(entry, srcs, list) 365 operate_dispatcher(epfd, EPOLL_CTL_DEL, NULL, entry); 366 367 epoll_ctl(epfd, EPOLL_CTL_DEL, infd, NULL); 368 369 epoll_ctl(epfd, EPOLL_CTL_DEL, sigfd, NULL); 370} 371 372static int prepare_signalfd(int *sigfd) 373{ 374 sigset_t mask; 375 int fd; 376 377 sigemptyset(&mask); 378 sigaddset(&mask, SIGINT); 379 sigaddset(&mask, SIGTERM); 380 381 if (sigprocmask(SIG_BLOCK, &mask, NULL) < 0) 382 return -errno; 383 384 fd = signalfd(-1, &mask, 0); 385 if (fd < 0) 386 return -errno; 387 *sigfd = fd; 388 389 return 0; 390} 391 392int monitor(const char *name) 393{ 394 LIST_HEAD(srcs); 395 int sigfd = 0; 396 int epfd; 397 int infd; 398 int wd = 0; 399 bool retry; 400 int err = 0; 401 402 err = prepare_signalfd(&sigfd); 403 if (err < 0) 404 return err; 405 406 epfd = epoll_create(1); 407 if (epfd < 0) { 408 close(sigfd); 409 return -errno; 410 } 411 412 infd = inotify_init1(IN_NONBLOCK); 413 if (infd < 0) { 414 err = -errno; 415 goto error; 416 } 417 wd = inotify_add_watch(infd, "/dev/snd/", IN_CREATE); 418 if (wd < 0) { 419 err = -errno; 420 goto error; 421 } 422retry: 423 retry = false; 424 err = prepare_source_entry(&srcs, name); 425 if (err < 0) 426 goto error; 427 428 err = prepare_dispatcher(epfd, sigfd, infd, &srcs); 429 if (err >= 0) 430 err = run_dispatcher(epfd, sigfd, infd, &srcs, &retry); 431 clear_dispatcher(epfd, sigfd, infd, &srcs); 432 433 if (retry) { 434 // A simple makeshift for timing gap between creation of nodes 435 // by devtmpfs and chmod() by udevd. 436 struct timespec req = { .tv_sec = 1 }; 437 nanosleep(&req, NULL); 438 goto retry; 439 } 440error: 441 clear_source_list(&srcs); 442 443 if (wd > 0) 444 inotify_rm_watch(infd, wd); 445 close(infd); 446 447 close(epfd); 448 449 close(sigfd); 450 451 return err; 452} 453