From b8d439246ad49047aa9683c72f8395d34c886af1 Mon Sep 17 00:00:00 2001 From: Jorijn van der Graaf Date: Tue, 8 Sep 2026 20:06:58 +0200 Subject: [PATCH 1/2] media: resolve the audio user once, thread-safely MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit SpawnPw() looked the desktop user up with getpwnam() — one static buffer — from the mic thread and the playout thread at the same instant. When the race made both lookups fail, uid stayed 0 and both pw helpers were started with XDG_RUNTIME_DIR=/run/user/0, where no PipeWire session lives: pw-record hit EOF at once, pw-play never connected, and a call that was up with RTP flowing both ways was answered into static (tx_mic=0 rx_played=0 qdrop=883, dev phone 2026-09-08 20:01, first time in years of calls — timing). AudioUid() resolves AUDIO_USER once under std::call_once with getpwnam_r and a private buffer, complains when the user does not exist, and logs the session the helpers are aimed at on every call, so a field journal answers "where did the audio go" directly. --- implementations/media.cpp | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/implementations/media.cpp b/implementations/media.cpp index 1dffd0a..b7d5964 100644 --- a/implementations/media.cpp +++ b/implementations/media.cpp @@ -410,6 +410,28 @@ std::size_t RtpPayloadOffset(std::span pkt) { return off; } +// The desktop user's uid, resolved ONCE and thread-safely. getpwnam() hands +// out one static buffer, and the mic and playout threads spawn their pw +// helpers at the same instant — the race sent both into /run/user/0, where +// no PipeWire session lives, and a call was answered into static (dev phone, +// 2026-09-08 20:01: tx_mic=0 rx_played=0 qdrop=883). Logged per call so a +// journal shows which session the helpers were aimed at. +uid_t AudioUid() { + static std::once_flag once; + static uid_t uid = 0; + std::call_once(once, [] { + std::string user = EnvOr("AUDIO_USER", "user"); + passwd pw{}; + passwd* res = nullptr; + char buf[4096]; + if (getpwnam_r(user.c_str(), &pw, buf, sizeof buf, &res) == 0 && res) uid = res->pw_uid; + else + std::println(std::cerr, "imsd-media: AUDIO_USER '{}' not found; no PipeWire session for uid 0", user); + std::println(std::cerr, "imsd-media: audio session: {} -> /run/user/{}", user, uid); + }); + return uid; +} + // Spawn pw-record/pw-play in the desktop user's PipeWire session (sudo -u // when run as root). AUDIO_USER names the session owner; the default "user" // is postmarketOS's standard account. `toChild` true = we write the child's @@ -421,10 +443,8 @@ Child SpawnPw(bool play, bool toChild, int rate) { std::vector argv; if (geteuid() == 0) { std::string user = EnvOr("AUDIO_USER", "user"); - uid_t uid = 0; - if (passwd* pw = getpwnam(user.c_str())) uid = pw->pw_uid; argv = {"sudo", "-u", user, "env", - std::format("XDG_RUNTIME_DIR=/run/user/{}", uid)}; + std::format("XDG_RUNTIME_DIR=/run/user/{}", AudioUid())}; } const char* tool = play ? "pw-play" : "pw-record"; const char* lat = play ? "40ms" : "20ms"; From 8526b9af0f3bd0b5a48b6b3e7f6f37e7d4ce0b5b Mon Sep 17 00:00:00 2001 From: Jorijn van der Graaf Date: Tue, 8 Sep 2026 20:06:58 +0200 Subject: [PATCH 2/2] imsd 0.3.3 0.3.2 plus the media fix: the audio user is resolved once and thread-safely, so an answered call can no longer land its PipeWire helpers in the wrong runtime directory and play static. --- implementations/main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/implementations/main.cpp b/implementations/main.cpp index 8e4f6bb..0ee566c 100644 --- a/implementations/main.cpp +++ b/implementations/main.cpp @@ -41,7 +41,7 @@ import Imsd; namespace { // ---- static config (env-overridable, same knobs as imsd.py) --------------- -constexpr const char* Version = "0.3.2"; +constexpr const char* Version = "0.3.3"; constexpr const char* BusName = "net.catcrafts.IMS1"; constexpr const char* ObjPath = "/net/catcrafts/IMS1"; constexpr const char* Iface = "net.catcrafts.IMS1";