aboutsummaryrefslogtreecommitdiffstats
path: root/alsause.c
diff options
context:
space:
mode:
Diffstat (limited to 'alsause.c')
-rw-r--r--alsause.c302
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;
+}