diff options
-rw-r--r-- | .gitignore | 15 | ||||
-rw-r--r-- | LICENSE | 15 | ||||
-rw-r--r-- | Makefile | 41 | ||||
-rw-r--r-- | alsause.1 | 137 | ||||
-rw-r--r-- | alsause.c | 302 | ||||
-rw-r--r-- | config.mk | 8 |
6 files changed, 518 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..603ea03 --- /dev/null +++ b/.gitignore @@ -0,0 +1,15 @@ +*\#* +*~ +*.o +*.a +*.lo +*.su +*.so +*.so.* +*.dll +*.dylib +*.gch +*.gcov +*.gcno +*.gcda +/alsause @@ -0,0 +1,15 @@ +ISC License + +© 2024 Mattias Andrée <maandree@kth.se> + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..31cea4c --- /dev/null +++ b/Makefile @@ -0,0 +1,41 @@ +.POSIX: + +CONFIGFILE = config.mk +include $(CONFIGFILE) + +OBJ =\ + alsause.o + +HDR = + +all: alsause +$(OBJ): $(HDR) + +.c.o: + $(CC) -c -o $@ $< $(CFLAGS) $(CPPFLAGS) + +alsause: $(OBJ) + $(CC) -o $@ $(OBJ) $(LDFLAGS) + +install: alsause + mkdir -p -- "$(DESTDIR)$(PREFIX)/bin" + mkdir -p -- "$(DESTDIR)$(MANPREFIX)/man1/" + cp -- alsause "$(DESTDIR)$(PREFIX)/bin/" + cp -- alsause.1 "$(DESTDIR)$(MANPREFIX)/man1/" + +post-install: + chown -- '0:0' "$(DESTDIR)$(PREFIX)/bin/alsause" + chmod -- 4755 "$(DESTDIR)$(PREFIX)/bin/alsause" + +uninstall: + -rm -f -- "$(DESTDIR)$(PREFIX)/bin/alsause" + -rm -f -- "$(DESTDIR)$(MANPREFIX)/man1/alsause.1" + +clean: + -rm -f -- *.o *.a *.lo *.su *.so *.so.* *.gch *.gcov *.gcno *.gcda + -rm -f -- alsause + +.SUFFIXES: +.SUFFIXES: .o .c + +.PHONY: all install post-install uninstall clean diff --git a/alsause.1 b/alsause.1 new file mode 100644 index 0000000..d433d58 --- /dev/null +++ b/alsause.1 @@ -0,0 +1,137 @@ +.TH ALSAUSE 1 ALSAUSE +.SH NAME +alsause - Set default ALSA PCM device +.SH SYNPOSIS +.B alsause +.RB [( \-r +| +.I name +| +.IR card \fB:\fP device ) +.RI [ command +.RI [ argument "] ...]]" +.SH DESCRIPTION +The +.B alsause +utility lists all ALSA PCM devices or +sets, either for the user or for a program +it starts, the default ALSA PCM device. +.PP +No options or operands are specified, +.B alsause +will list all ALSA PCM devices, otherwise +it will set the default ALSA PCM device. +.PP +If +.I command +is specified, +.B alsause +will run that command with the default +ALSA PCM device set only for that process +and it's child processes. +.SH OPTIONS +The +.B alsause +utility conforms to the Base Definitions volume of POSIX.1-2017, +.IR "Section 12.2" ", " "Utility Syntax Guidelines" . +.PP +The following option is supported: +.TP +.B \-r +Remove the user-specified default ALSA PCM device, +falling back to the system-wide default ALSA PCM device. +.SH OPERANDS +The following operands shall be supported: +.TP +.IR card : device +The card number and device number to use as the default +ALSA PCM device. +.TP +.I name +The card and device, by name, to use as the default +ALSA PCM device. This string is on the form output by the +.B alsause +utility itself: +.BR "\(dq%s - %s\(dq" , +.RI < "card name" >, +.RI < "device name" >. +.TP +.I command +The name of the utility to be invoked. +.TP +.I argument +A string to pass as an argument for the invoked utility. +.SH STDIN +Not used. +.SH INPUT FILES +None. +.SH ENVIRONMENT VARIABLES +The following environment variable affects the execution of +.TP +.I HOME +Used to override the path to the user's home directroy. +.TP +.I PATH +Determine the location of the +.IR command , +as described in the Base Definitions volume of POSIX.1-2017, +.IR "Chapter 8" ", " "Environment Variables" . +.PP +Additionally, environment variables that affect ALSA takes +precedence over the default device set by the +.B alsause +utility. +.SH ASYNCHRONOUS EVENTS +Default. +.SH STDOUT +If no option or operand is specified, each ALSA PCM device is the form: +.RS + +\fB\(dq%u:%u\et%s - %s\en\(dq,\fP <\fIcard number\fP>\fB,\fP <\fIdevice number\fP>\fB,\fP <\fIcard name\fP>\fB,\fP <\fIdevice name\fP> + +.RE +Otherwise, the +.B alsause +utility does not write to standard output. +.SH STDERR +The standard error shall be used only for diagnostic messages. +.SH OUTPUT FILES +If the the default ALSA PCM device is set, the +.B alsause +utility may create, modify, replace, or unlink the file +.I .asoundrc +placed in the user's home directory. +.SH EXTENDED DESCRIPTION +None. +.SH EXIT STATUS +If +.I command +is invoked, the exit status of +.B alsause +is the exit status of +.IR command ; +otherwise, the +.B alsause +utility exits with one of the following values. +.TP +0 +The +.I alsause +utility completed successfully. +.TP +125 +An error occurred in the +.I alsause +utility. +.TP +126 +The utility specified by +.I command +was found but could not be invoked. +.TP +127 +The utility specified by +.I command +could not be found. +.SH SEE ALSO +None. 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; +} diff --git a/config.mk b/config.mk new file mode 100644 index 0000000..9284edf --- /dev/null +++ b/config.mk @@ -0,0 +1,8 @@ +PREFIX = /usr +MANPREFIX = $(PREFIX)/share/man + +CC = c99 + +CPPFLAGS = -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_XOPEN_SOURCE=700 -D_GNU_SOURCE +CFLAGS = +LDFLAGS = -lasound -lsimple |