/* See LICENSE file for copyright and license details. */ #include #include #include #include #include NUSAGE(125, "[(-r | name | card:device) [command [argument] ...]]"); static char * get_config_path(void) { const char *home = getenv("HOME"); char *ret; if (!home) { struct passwd *pw; errno = 0; pw = getpwuid(getuid()); if (!pw || !pw->pw_dir) eprintf("pwgetuid : %s", (!errno || errno == EIO) ? "User does not exist" : strerror(errno)); home = pw->pw_dir; } if (strlen(home) > SIZE_MAX - sizeof("/.asoundrc") || !(ret = malloc(strlen(home) + sizeof("/.asoundrc")))) { errno = ENOMEM; eprintf("malloc:"); } stpcpy(stpcpy(ret, home), "/.asoundrc"); return ret; } #if defined(__GNUC__) __attribute__((__pure__)) #endif static int is_name(const char *s) { if (!isdigit(*s)) return 1; if (*s == '0' && s[1] != ':') return 1; while (isdigit(*s)) s++; if (*s != ':') return 1; s++; if (!isdigit(*s)) return 1; if (*s == '0' && s[1]) return 1; while (isdigit(*s)) s++; if (*s) return 1; return 0; } static int list_outputs(const char *find, int *card_out, int *device_out) { snd_ctl_t *ctl; snd_ctl_card_info_t *info; snd_pcm_info_t *pcminfo; char dev[sizeof("hw:") + 3 * sizeof(int)]; int r, card, device, failed = 0; snd_ctl_card_info_alloca(&info); snd_pcm_info_alloca(&pcminfo); for (card = -1;;) { r = snd_card_next(&card); if (r < 0) { weprintf("snd_card_next: %s", snd_strerror(r)); return -1; } if (card < 0) break; sprintf(dev, "hw:%i", card); r = snd_ctl_open(&ctl, dev, 0); if (r < 0) { weprintf("snd_ctl_open %s 0: %s", dev, snd_strerror(r)); failed = 1; continue; } r = snd_ctl_card_info(ctl, info); if (r < 0) { weprintf("snd_ctl_card_info <%s>: %s", dev, snd_strerror(r)); failed = 1; goto close_card; } for (device = -1;;) { r = snd_ctl_pcm_next_device(ctl, &device); if (r < 0) { weprintf("snd_ctl_pcm_next_device <%s>: %s", dev, snd_strerror(r)); failed = 1; goto close_card; } if (device < 0) break; snd_pcm_info_set_device(pcminfo, (unsigned int)device); snd_pcm_info_set_subdevice(pcminfo, 0); snd_pcm_info_set_stream(pcminfo, SND_PCM_STREAM_PLAYBACK); r = snd_ctl_pcm_info(ctl, pcminfo); if (r < 0) { if (r == -ENOENT) continue; weprintf("snd_ctl_pcm_info <%s>: %s (%i)", dev, snd_strerror(r), r); failed = 1; goto close_card; } if (find) { const char *s, *n = find; s = snd_ctl_card_info_get_name(info); if (strncmp(n, s, strlen(s))) continue; n = &n[strlen(s)]; if (strncmp(n, " - ", 3)) continue; n = &n[3]; s = snd_pcm_info_get_name(pcminfo); if (strcmp(n, s)) continue; *card_out = snd_ctl_card_info_get_card(info); *device_out = device; snd_ctl_close(ctl); return 0; } else { printf("%i:%i\t%s - %s\n", snd_ctl_card_info_get_card(info), device, snd_ctl_card_info_get_name(info), snd_pcm_info_get_name(pcminfo)); } } close_card: snd_ctl_close(ctl); } if (find) { *card_out = -1; *device_out = -1; } return -failed; } static int writeall(int fd, void *data, size_t len) { char *text = data; ssize_t r; while (len) { r = write(fd, text, len); if (r < 0) { if (errno == EINTR) continue; return -1; } text = &text[r]; len -= (size_t)r; } return 0; } int main(int argc, char *argv[]) { int reset = 0; libsimple_default_failure_exit = 125; ARGBEGIN { case 'r': reset = 1; break; default: usage(); } ARGEND; if (!argc) { if (reset) { char *path = get_config_path(); if (unlink(path) && errno != ENOENT) eprintf("unlink %s:", path); free(path); } else { if (list_outputs(NULL, NULL, NULL)) return libsimple_default_failure_exit; } } else { char *path = get_config_path(); char *text, temppath[sizeof("/tmp/alsause.") + 3 * sizeof(uintmax_t)]; int len, fd, treefd; if (reset) { text = NULL; len = 0; } else if (is_name(argv[0])) { int card, device; if (list_outputs(argv[0], &card, &device)) return 1; if (card == -1 || device == -1) eprintf("audio output \"%s\" not found", argv[0]); len = snprintf(NULL, 0, "defaults.pcm.card %i\ndefaults.pcm.device %i\n", card, device); if (len < 0) eprintf("snprintf NULL 0:"); text = malloc((size_t)len + 1U); if (!text) eprintf("malloc:"); if (sprintf(text, "defaults.pcm.card %i\ndefaults.pcm.device %i\n", card, device) != len) abort(); argc--; argv++; } else { char *card = argv[0]; char *device = strchr(card, ':'); if (!device) usage(); *device++ = '\0'; if (strchr(device, ':')) usage(); len = snprintf(NULL, 0, "defaults.pcm.card %s\ndefaults.pcm.device %s\n", card, device); if (len < 0) eprintf("snprintf NULL 0:"); text = malloc((size_t)len + 1U); if (!text) eprintf("malloc:"); if (sprintf(text, "defaults.pcm.card %s\ndefaults.pcm.device %s\n", card, device) != len) abort(); argc--; argv++; } if (!argc) { fd = open(path, O_CREAT | O_TRUNC | O_WRONLY, 0666); if (fd < 0) eprintf("open %s O_CREAT|O_TRUNC|O_WRONLY 0666:", path); if (fchown(fd, getuid(), getgid())) weprintf("fchown %s :", path); if (writeall(fd, text, (size_t)len) || (close(fd) && (errno != EINTR))) eprintf("write %s:", path); free(text); free(path); return 0; } if (unshare(CLONE_NEWNS)) eprintf("unshare CLONE_NEWNS:"); if (mount("none", "/", NULL, MS_REC | MS_SLAVE, NULL)) eprintf("mount none / NULL MS_REC|MS_SLAVE NULL:"); fd = open(path, O_CREAT | O_EXCL | O_WRONLY, 0666); if (fd >= 0) { fchown(fd, getuid(), getgid()); close(fd); } sprintf(temppath, "/tmp/alsause.%ju", (uintmax_t)getpid()); unlink(temppath); fd = open(temppath, O_CREAT | O_EXCL | O_WRONLY, 0666); if (fd < 0) eprintf("open %s O_CREAT|O_EXCL|O_WRONLY 0666:", temppath); if (fchown(fd, getuid(), getgid())) weprintf("fchown %s :", temppath); if (writeall(fd, text, (size_t)len) || (close(fd) && (errno != EINTR))) eprintf("write %s:", temppath); free(text); treefd = open_tree(AT_FDCWD, temppath, OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC); if (treefd < 0) { weprintf("open_tree AT_FDCWD %s OPEN_TREE_CLONE:", temppath); unlink(temppath); exit(libsimple_default_failure_exit); } if (move_mount(treefd, "", AT_FDCWD, path, MOVE_MOUNT_F_EMPTY_PATH)) { weprintf("move_mount %s \"\" AT_FDCWD %s MOVE_MOUNT_F_EMPTY_PATH:", temppath, path); unlink(temppath); exit(libsimple_default_failure_exit); } if (unlink(temppath)) weprintf("unlink %s:", temppath); if (setegid(getgid())) eprintf("setegid :"); if (seteuid(getuid())) eprintf("seteuid :"); execvp(argv[0], argv); enprintf(errno == ENOENT ? 127 : 126, "execvp %s:", argv[0]); } return 0; }