22//! dynamic linker. Since the goal of this project is to statically link pipewire, we need to stub
33//! these out.
44//!
5- //! This file exports a number of `__wrap_*` functions, and the pipewire build uses the preprocessor
5+ //! This file pub exports a number of `__wrap_*` functions, and the pipewire build uses the preprocessor
66//! to redirect those calls to here.
77
88const std = @import ("std" );
@@ -18,27 +18,26 @@ const c = @cImport({
1818
1919/// Since we're statically linked, we don't support `dlopen`. Instead, we look up "libraries" in the
2020/// hard coded `libs` table.
21- export fn __wrap_dlopen (path : ? [* :0 ]const u8 , mode : std.c.RTLD ) callconv (.c ) ? * anyopaque {
21+ pub export fn __wrap_dlopen (path : ? [* :0 ]const u8 , mode : std.c.RTLD ) callconv (.c ) ? * anyopaque {
2222 const span = if (path ) | p | std .mem .span (p ) else Lib .main_program_name ;
2323 const lib = if (libs .getIndex (span )) | index | & libs .kvs .values [index ] else null ;
24- log .debug ("dlopen(\" {f}\" , {f}) -> {?f}" , .{
25- std .zig .fmtString (span ),
26- FmtFlags (std .c .RTLD ).init (mode ),
27- lib ,
28- });
24+ log .debug ("dlopen(\" {f}\" , {f}) -> {?f}" , .{ std .zig .fmtString (span ), fmtFlags (mode ), lib });
2925 return @ptrCast (@constCast (lib ));
3026}
3127
3228/// Since `dlopen` just returns handles from `libs`, `dlclose` is a noop.
33- export fn __wrap_dlclose (handle : ? * anyopaque ) callconv (.c ) c_int {
29+ pub export fn __wrap_dlclose (handle : ? * anyopaque ) callconv (.c ) c_int {
3430 const lib : * const Lib = @ptrCast (@alignCast (handle .? ));
3531 log .debug ("dlclose({f})" , .{lib });
3632 return 0 ;
3733}
3834
3935/// Since `dlopen` just returns handles from `libs`, `dlsym` retrieves symbols from the correct part
4036/// of that table.
41- export fn __wrap_dlsym (noalias handle : ? * anyopaque , noalias name : [* :0 ]u8 ) ? * anyopaque {
37+ pub export fn __wrap_dlsym (
38+ noalias handle : ? * anyopaque ,
39+ noalias name : [* :0 ]u8 ,
40+ ) callconv (.c ) ? * anyopaque {
4241 const lib : * const Lib = @ptrCast (@alignCast (handle .? ));
4342 const span = std .mem .span (name );
4443 var msg : ? [:0 ]const u8 = null ;
@@ -65,18 +64,18 @@ export fn __wrap_dlsym(noalias handle: ?*anyopaque, noalias name: [*:0]u8) ?*any
6564/// Since `dlopen` is allowed to return null on success if the symbol is zero, `dlerror` is a
6665/// necessary part of the `dlopen` interface.
6766var err : ? [* :0 ]const u8 = null ;
68- export fn __wrap_dlerror () ? [* :0 ]const u8 {
67+ pub export fn __wrap_dlerror () callconv ( .c ) ? [* :0 ]const u8 {
6968 const result = err ;
7069 err = null ;
7170 return result ;
7271}
7372
7473/// We don't support `dlinfo` as pipewire doesn't currently use it. If it's called, crash.
75- export fn __wrap_dlinfo (
74+ pub export fn __wrap_dlinfo (
7675 noalias handle : ? * anyopaque ,
7776 request : c_int ,
7877 noalias info : ? * anyopaque ,
79- ) c_int {
78+ ) callconv ( .c ) c_int {
8079 const lib : * const Lib = @ptrCast (@alignCast (handle .? ));
8180 log .debug ("dlinfo({f}, {}, {x})" , .{ lib , request , @intFromPtr (info ) });
8281 @panic ("unimplemented" );
@@ -86,9 +85,8 @@ export fn __wrap_dlinfo(
8685/// exist, we wrap `stat` to pretend that they do so it doesn't fail prematurely.
8786///
8887/// We do this by faking any stat calls whose paths end with `.so`. All other stat calls from
89- /// pipewire are forwarded to the standard implementation as is, though in practice, there probably
90- /// shouldn't be any others.
91- export fn __wrap_stat (pathname_c : [* :0 ]const u8 , statbuf : * std.os.linux.Stat ) usize {
88+ /// pipewire are forwarded to the standard implementation as is.
89+ pub export fn __wrap_stat (pathname_c : [* :0 ]const u8 , statbuf : * std.os.linux.Stat ) callconv (.c ) usize {
9290 const pathname = std .mem .span (pathname_c );
9391 const result , const strategy = b : {
9492 if (std .mem .endsWith (u8 , pathname , ".so" )) {
@@ -102,12 +100,79 @@ export fn __wrap_stat(pathname_c: [*:0]const u8, statbuf: *std.os.linux.Stat) us
102100 std .zig .fmtString (pathname ),
103101 statbuf ,
104102 result ,
105- FmtFlags (std .os .linux .Stat ).init (statbuf .* ),
103+ fmtFlags (statbuf .* ),
104+ strategy ,
105+ });
106+ return result ;
107+ }
108+
109+ /// The path pipewire looks for client config at.
110+ const client_config_path = "pipewire-0.3/confdata/client.conf" ;
111+ var client_config_fd : usize = 0 ;
112+
113+ /// Pipewire uses `access` to check for the presence of `client.conf`. We stub this out so that
114+ /// pipewire believes it exists expected path, allowing us to later provide it directly instead of
115+ /// requiring it be available on the user's computer or at a path relative to the working directory.
116+ pub export fn __wrap_access (path_c : [* :0 ]const u8 , mode : u32 ) callconv (.c ) usize {
117+ const path = std .mem .span (path_c );
118+
119+ const result , const strategy = b : {
120+ if (mode == std .os .linux .R_OK and
121+ std .mem .eql (u8 , path , client_config_path ))
122+ {
123+ break :b .{ 0 , "faked" };
124+ } else {
125+ break :b .{ std .os .linux .access (path , mode ), "real" };
126+ }
127+ };
128+ log .debug ("access(\" {f}\" , {}) -> {} ({s})" , .{
129+ std .zig .fmtString (path ),
130+ mode ,
131+ result ,
106132 strategy ,
107133 });
108134 return result ;
109135}
110136
137+ // XXX: wrong signature--look at the lib cinterface, not the linux one which mathces syscall types.
138+ // same issue in some other functions I think.
139+ /// Pipewire uses `open` to open the client config file. If it requests that path we fake the call,
140+ /// otherwise we pass it through as is. See `__wrap_access` for more information.
141+ pub export fn __wrap_open (
142+ path_c : [* :0 ]const u8 ,
143+ flags : std.os.linux.O ,
144+ mode : std.os.linux.mode_t ,
145+ ) callconv (.c ) usize {
146+ const path = std .mem .span (path_c );
147+
148+ const result , const strategy = b : {
149+ if (std .mem .eql (u8 , path , client_config_path )) {
150+ if (client_config_fd != 0 ) @panic ("client_config_path already open" );
151+ client_config_fd = std .os .linux .open ("/dev/null" , flags , mode );
152+ break :b .{ client_config_fd , "faked" };
153+ } else {
154+ break :b .{ std .os .linux .open (path_c , flags , mode ), "real" };
155+ }
156+ };
157+ log .debug ("open(\" {f}\" , {f}, {}) -> {} ({s})" , .{
158+ std .zig .fmtString (path ),
159+ fmtFlags (flags ),
160+ mode ,
161+ result ,
162+ strategy ,
163+ });
164+ return result ;
165+ }
166+
167+ /// We wrap close just to null out `client_config_fd` when closed. See `__wrap_access` for more
168+ /// info.
169+ pub export fn __wrap_close (fd : i32 ) callconv (.c ) usize {
170+ if (fd == client_config_fd ) client_config_fd = 0 ;
171+ const result = std .os .linux .close (fd );
172+ log .debug ("close({}) -> {}" , .{ fd , result });
173+ return result ;
174+ }
175+
111176/// A dynamic library made static.
112177const Lib = struct {
113178 /// You're allowed to pass null as the path to dlopen, in which case you're supposed to get a
@@ -354,6 +419,10 @@ const fops = struct {
354419 }
355420};
356421
422+ pub fn fmtFlags (val : anytype ) FmtFlags (@TypeOf (val )) {
423+ return .init (val );
424+ }
425+
357426/// Formats flags, skipping any values that are 0 for brevity.
358427fn FmtFlags (T : type ) type {
359428 return struct {
0 commit comments