diff options
Diffstat (limited to 'alsause.c')
-rw-r--r-- | alsause.c | 302 |
1 files changed, 302 insertions, 0 deletions
diff --git a/alsause.c b/alsause.c new file mode 100644 index 0000000..e157e71 --- /dev/null +++ b/alsause.c @@ -0,0 +1,302 @@ +/* See LICENSE file for copyright and license details. */ +#include <sys/mount.h> +#include <sched.h> +#include <alsa/asoundlib.h> +#include <libsimple.h> +#include <libsimple-arg.h> + +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 <real user>: %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 <read user> <real path>:", 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 <read user> <real path>:", 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 <real group>:"); + if (seteuid(getuid())) + eprintf("seteuid <real user>:"); + + execvp(argv[0], argv); + enprintf(errno == ENOENT ? 127 : 126, "execvp %s:", argv[0]); + } + + return 0; +} |