aboutsummaryrefslogblamecommitdiffstats
path: root/alsause.c
blob: e157e716c0d9e69154889b632cf4d20ac7421be1 (plain) (tree)













































































































































































































































































































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