Subject: bug#40832: Audacity does not work with PulseAudio

From: Leo Famulari <[email-masked]>
Date: Fri, 8 May 2020 18:45:18 -0400

An update on this:

To begin, I installed alsa-plugins and alsa-plugins:pulseaudio and
configured the build of alsa-lib like this:

"--with-plugindir=/var/guix/profiles/per-user/leo/guix-profile/lib/alsa-lib"

Everything worked that way, but obviously it's not a solution.

Now, I am working on making alsa-lib respect ALSA_PLUGIN_DIRS, which
would be a search path specified by packages that provide ALSA plugins,
such as alsa-plugins. However, so far my attempt fails in another part
of alsa-lib, like this:

------
$ mpg123 -o alsa ~/file.mp3
ALSA lib dlmisc.c:204:(snd_dlsym_verify) unable to verify version for symbol conf_pulse_hook_load_if_running
ALSA lib conf.c:3686:(snd_config_hooks_call) symbol conf_pulse_hook_load_if_running is not defined inside libasound_module_conf_pulse.so
ALSA lib pcm.c:2685:(snd_pcm_open_noupdate) Unknown PCM default
[src/libout123/modules/alsa.c:181] error: cannot open device default
[src/libout123/libout123.c:455] error: Found no driver out of [alsa] working with device <default>.
main: [src/mpg123.c:314] error: out123 error 3: failure loading driver module
------

I don't know why snd_dlsym_verify fails with my patch but succeeds when
using '--with-plugindir'.

My current patch is attached...
From 2dc0cf223a71d2a22ca19eff6c59d55d72028c64 Mon Sep 17 00:00:00 2001
From: Leo Famulari <[email-masked]>
Date: Sun, 26 Apr 2020 20:13:01 -0400
Subject: [PATCH 1/3] gnu: alsa-plugins: Add ALSA_PLUGIN_DIRS search path
 specification.

* gnu/packages/linux.scm (alsa-plugins)[native-search-paths]: New field.
---
 gnu/packages/linux.scm | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/gnu/packages/linux.scm b/gnu/packages/linux.scm
index 40323a85d6..b451f591ea 100644
--- a/gnu/packages/linux.scm
+++ b/gnu/packages/linux.scm
@@ -1858,6 +1858,10 @@ MIDI functionality to the Linux-based operating system.")
               (base32
                "0z9k3ssbfk2ky2w13avgyf202j1drsz9sv3834bp33cj1i2hc3qw"))))
     (build-system gnu-build-system)
+    (native-search-paths
+      (list (search-path-specification
+              (variable "ALSA_PLUGIN_DIRS")
+              (files '("lib/alsa-lib")))))
     ;; TODO: Split libavcodec and speex if possible. It looks like they can not
     ;; be split, there are references to both in files.
     ;; TODO: Remove OSS related plugins, they add support to run native
-- 
2.26.2

From a9ce47575add9eeb015eb6c605bde01948c5e341 Mon Sep 17 00:00:00 2001
From: Leo Famulari <[email-masked]>
Date: Mon, 27 Apr 2020 01:05:41 -0400
Subject: [PATCH 2/3] gnu: Add the old Nix alsa-lib patch with a graft.

* gnu/packages/linux.scm (alsa-lib)[replacement]: New field.
(alsa-lib/fixed): New variable.
* gnu/packages/patches/alsa-lib.patch: New file.
* gnu/local.mk (dist_patch_DATA): Add it.
---
 gnu/local.mk                        |   1 +
 gnu/packages/linux.scm              |  10 +++
 gnu/packages/patches/alsa-lib.patch | 110 ++++++++++++++++++++++++++++
 3 files changed, 121 insertions(+)
 create mode 100644 gnu/packages/patches/alsa-lib.patch

diff --git a/gnu/local.mk b/gnu/local.mk
index 39267f2765..de17670beb 100644
--- a/gnu/local.mk
+++ b/gnu/local.mk
@@ -741,6 +741,7 @@ dist_patch_DATA =						\
   %D%/packages/patches/akonadi-not-relocatable.patch		\
   %D%/packages/patches/akonadi-timestamps.patch		\
   %D%/packages/patches/allegro-mesa-18.2.5-and-later.patch	\
+  %D%/packages/patches/alsa-lib.patch				\
   %D%/packages/patches/amule-crypto-6.patch			\
   %D%/packages/patches/anki-mpv-args.patch			\
   %D%/packages/patches/antiword-CVE-2014-8123.patch			\
diff --git a/gnu/packages/linux.scm b/gnu/packages/linux.scm
index b451f591ea..e0ed8c040d 100644
--- a/gnu/packages/linux.scm
+++ b/gnu/packages/linux.scm
@@ -1771,6 +1771,7 @@ intercept and print the system calls executed by the program.")
 (define-public alsa-lib
   (package
     (name "alsa-lib")
+    (replacement alsa-lib/fixed)
     (version "1.2.2")
     (source (origin
              (method url-fetch)
@@ -1792,6 +1793,15 @@ intercept and print the system calls executed by the program.")
 MIDI functionality to the Linux-based operating system.")
     (license license:lgpl2.1+)))
 
+(define alsa-lib/fixed
+  (package
+    (inherit alsa-lib)
+    (source (origin
+              (inherit (package-source alsa-lib))
+              (patches (append
+                         (origin-patches (package-source alsa-lib))
+                         (search-patches "alsa-lib.patch")))))))
+
 (define-public alsa-utils
   (package
     (name "alsa-utils")
diff --git a/gnu/packages/patches/alsa-lib.patch b/gnu/packages/patches/alsa-lib.patch
new file mode 100644
index 0000000000..3cee02aa0c
--- /dev/null
+++ b/gnu/packages/patches/alsa-lib.patch
@@ -0,0 +1,110 @@
+diff --git a/src/pcm/pcm.c b/src/pcm/pcm.c
+index 74d1d1a..17ffb12 100644
+--- a/src/pcm/pcm.c
++++ b/src/pcm/pcm.c
+@@ -2042,6 +2042,19 @@ static const char *const build_in_pcms[] = {
+ 	NULL
+ };
+ 
++
++// helper funcion used below
++int file_exists(const char * filename)
++{
++	FILE * file;
++	if (file = fopen(filename, "r"))
++	{
++		fclose(file);
++		return 1;
++	}
++	return 0;
++}
++
+ static int snd_pcm_open_conf(snd_pcm_t **pcmp, const char *name,
+ 			     snd_config_t *pcm_root, snd_config_t *pcm_conf,
+ 			     snd_pcm_stream_t stream, int mode)
+@@ -2141,8 +2154,38 @@ static int snd_pcm_open_conf(snd_pcm_t **pcmp, const char *name,
+ 				err = -ENOMEM;
+ 				goto _err;
+ 			}
+-			lib = buf1;
+ 			sprintf(buf1, "%s/libasound_module_pcm_%s.so", ALSA_PLUGIN_DIR, str);
++			if (!file_exists(buf1)){
++				// try to locate plugin in one of ALSA_PLUGIN_DIRS which is colon separated list of paths
++				char * pdirs = getenv("ALSA_PLUGIN_DIRS");
++
++				if (pdirs){ // env var set?
++					char * saveptr;
++					while (1) {
++						char * dir_tok = strtok_r(pdirs, "::::", &saveptr); // "::::" to work around bug in glibc and -O2 ? ":" seems to cause a segfault
++						if (dir_tok == NULL)
++                            break;
++						char * so_file = malloc(strlen(str) + strlen(dir_tok) + 32);
++						if (so_file == NULL) {
++							err = -ENOMEM;
++							goto _err;
++						}
++
++						sprintf(so_file, "%s/libasound_module_pcm_%s.so", dir_tok, str);
++
++						if (file_exists(so_file)){
++
++							free(buf1);
++							buf1 = so_file;
++							break;
++						} else {
++							free (so_file);
++						}
++						pdirs = NULL;
++					}
++				}
++			}
++			lib = buf1;
+ 		}
+ 	}
+ #ifndef PIC
+
+
+diff --git a/src/control/control.c b/src/control/control.c
+index c090797..137fe57 100644
+--- a/src/control/control.c
++++ b/src/control/control.c
+@@ -854,8 +854,38 @@ static int snd_ctl_open_conf(snd_ctl_t **ctlp, const char *name,
+ 				err = -ENOMEM;
+ 				goto _err;
+ 			}
++			sprintf(buf1, "%s/libasound_module_pcm_%s.so", ALSA_PLUGIN_DIR, str);
++			if (!file_exists(buf1)){
++				// try to locate plugin in one of ALSA_PLUGIN_DIRS which is colon separated list of paths
++				char * pdirs = getenv("ALSA_PLUGIN_DIRS");
++
++				if (pdirs){ // env var set?
++					char * saveptr;
++					while (1) {
++						char * dir_tok = strtok_r(pdirs, "::::", &saveptr); // "::::" to work around bug in glibc and -O2 ? ":" seems to cause a segfault
++						if (dir_tok == NULL)
++                            break;
++						char * so_file = malloc(strlen(str) + strlen(dir_tok) + 32);
++						if (so_file == NULL) {
++							err = -ENOMEM;
++							goto _err;
++						}
++
++						sprintf(so_file, "%s/libasound_module_ctl_%s.so", dir_tok, str);
++
++						if (file_exists(so_file)){
++
++							free(buf1);
++							buf1 = so_file;
++							break;
++						} else {
++							free (so_file);
++						}
++						pdirs = NULL;
++					}
++				}
++			}
+ 			lib = buf1;
+-			sprintf(buf1, "%s/libasound_module_ctl_%s.so", ALSA_PLUGIN_DIR, str);
+ 		}
+ 	}
+ #ifndef PIC
-- 
2.26.2

From 71c5ccb8f24165bea6154d566056b2474adeebc7 Mon Sep 17 00:00:00 2001
From: Leo Famulari <[email-masked]>
Date: Fri, 8 May 2020 18:27:00 -0400
Subject: [PATCH 3/3] WIP: Make alsa-lib look for ALSA plugins at run-time.

Currently it doesn't work :(

Test on foreign distro with `mpg123 -o alsa ~/file.mp3`.

* gnu/packages/patches/alsa-lib.patch: Make alsa-lib look up plugins in
the directories named in $ALSA_PLUGIN_DIRS.
---
 gnu/packages/patches/alsa-lib.patch | 76 +++++++++++++++++++++++++++++
 1 file changed, 76 insertions(+)

diff --git a/gnu/packages/patches/alsa-lib.patch b/gnu/packages/patches/alsa-lib.patch
index 3cee02aa0c..7ca6b7960a 100644
--- a/gnu/packages/patches/alsa-lib.patch
+++ b/gnu/packages/patches/alsa-lib.patch
@@ -108,3 +108,79 @@ index c090797..137fe57 100644
  		}
  	}
  #ifndef PIC
+diff --git a/src/dlmisc.c b/src/dlmisc.c
+index 8c8f3ff7..274d4b84 100644
+--- a/src/dlmisc.c
++++ b/src/dlmisc.c
+@@ -82,19 +82,52 @@ void *snd_dlopen(const char *name, int mode, char *errbuf, size_t errbuflen)
+ 	char *filename = NULL;
+ 
+ 	if (name && name[0] != '/') {
+-		filename = alloca(sizeof(ALSA_PLUGIN_DIR) + 1 + strlen(name) + 1);
+-		if (filename) {
+-			strcpy(filename, ALSA_PLUGIN_DIR);
+-			strcat(filename, "/");
+-			strcat(filename, name);
+-			handle = dlopen(filename, mode);
+-			if (!handle) {
+-				/* if the filename exists and cannot be opened */
+-				/* return immediately */
+-				if (access(filename, X_OK) == 0)
+-					goto errpath;
++                // 'name' is the full library.so name, e.g. libasound_module_conf_pulse.so
++		fprintf(stderr, "XXX name is %s\n", name);
++		char * plugindirs = getenv("ALSA_PLUGIN_DIRS");
++		fprintf(stderr, "XXX plugindirs are %s\n" ,plugindirs);
++		if (plugindirs) {
++			char * saveptr;
++			while (1) {
++				// See comment in src/control/control.c about "::::"
++				char * dir_tok = strtok_r(plugindirs, "::::", &saveptr);
++				if (dir_tok == NULL)
++					break;
++				fprintf(stderr, "XXX dir token is %s\n" ,dir_tok);
++				char * so_file = malloc(strlen(name) + strlen(dir_tok) + 32);
++				sprintf(so_file, "%s/%s" ,dir_tok, name);
++				// TODO Check if so_file == NULL here
++				if (file_exists(so_file)) {
++					fprintf(stderr, "XXX Found the library %s\n" ,so_file);
++					handle = dlopen(filename, mode);
++					if (!handle) {
++						/* if the filename exists and cannot be opened */
++						/* return immediately */
++						if (access(filename, X_OK) == 0)
++							goto errpath;
++					}
++					break;
++				} else {
++					fprintf(stderr, "XXX Did not find the library %s\n" ,so_file);
++					break;
++				}
+ 			}
+ 		}
++// Cut here:
++//		filename = alloca(sizeof(ALSA_PLUGIN_DIR) + 1 + strlen(name) + 1);
++//		if (filename) {
++//			strcpy(filename, ALSA_PLUGIN_DIR);
++//			strcat(filename, "/");
++//			strcat(filename, name);
++//			handle = dlopen(filename, mode);
++//			if (!handle) {
++//				/* if the filename exists and cannot be opened */
++//				/* return immediately */
++//				if (access(filename, X_OK) == 0)
++//					goto errpath;
++//			}
++//		}
++// ... to here.
+ 	}
+ 	if (!handle) {
+ 		handle = dlopen(name, mode);
+@@ -104,6 +137,7 @@ void *snd_dlopen(const char *name, int mode, char *errbuf, size_t errbuflen)
+ 	return handle;
+ errpath:
+ 	if (errbuf)
++		fprintf(stderr, "XXX couldn't find your thing!\n");
+ 		snprintf(errbuf, errbuflen, "%s: %s", filename, dlerror());
+ #endif
+ 	return NULL;
-- 
2.26.2