/* 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;
}