@@ -21,11 +21,7 @@ const c = @cImport({
2121export 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
@@ -38,7 +34,10 @@ export fn __wrap_dlclose(handle: ?*anyopaque) callconv(.c) c_int {
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+ 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,7 +64,7 @@ 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+ export fn __wrap_dlerror () callconv ( .c ) ? [* :0 ]const u8 {
6968 const result = err ;
7069 err = null ;
7170 return result ;
@@ -76,7 +75,7 @@ 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+ 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,111 @@ 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+ 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 ,
132+ strategy ,
133+ });
134+ return result ;
135+ }
136+
137+ // XXX: wait is this usize or c_int? i think it's supposed to be cint, i think in general the linux
138+ // namespace functions may ahve different types due to just being the syscalls...
139+ // XXX: not getting called...
140+ /// Pipewire uses `open` to open the client config file. If it requests that path we fake the call,
141+ /// otherwise we pass it through as is. See `__wrap_access` for more information.
142+ export fn __wrap_open (
143+ path_c : [* :0 ]const u8 ,
144+ flags : std.os.linux.O ,
145+ mode : std.os.linux.mode_t ,
146+ ) callconv (.c ) usize {
147+ const path = std .mem .span (path_c );
148+
149+ const result , const strategy = b : {
150+ if (std .mem .eql (u8 , path , client_config_path )) {
151+ if (client_config_fd != 0 ) @panic ("client_config_path already open" );
152+ client_config_fd = std .os .linux .open ("/dev/null" , flags , mode );
153+ break :b .{ client_config_fd , "faked" };
154+ } else {
155+ break :b .{ std .os .linux .open (path_c , flags , mode ), "real" };
156+ }
157+ };
158+ // XXX: change to debug
159+ log .info ("open(\" {f}\" , {f}, {}) -> {} ({s})" , .{
160+ std .zig .fmtString (path ),
161+ fmtFlags (flags ),
162+ mode ,
163+ result ,
106164 strategy ,
107165 });
108166 return result ;
109167}
110168
169+ // XXX: get list of wrapped functions or defines from this file in the build script autoamtically? could
170+ // even just find stuff prefixed iwth __wrap if we wanted
171+ // XXX: make exports public too cause why not?
172+ /// We wrap close just to null out `client_config_fd` when closed. See `__wrap_access` for more
173+ /// info.
174+ export fn __wrap_close (fd : i32 ) callconv (.c ) usize {
175+ if (fd == client_config_fd ) client_config_fd = 0 ;
176+ const result = std .os .linux .close (fd );
177+ // XXX: change to debug
178+ log .err ("close({}) -> {}" , .{ fd , result });
179+ return result ;
180+ }
181+
182+ // XXX: wrap close to close client config fd
183+
184+ // XXX: ...
185+ // /// Pipewire uses `fstat` to check a second time if a config file exists, after already having
186+ // /// checked it with `access`. We stub this out as well.
187+ // export fn fstat(fd: i32, stat_buf: *std.os.linux.Stat) usize
188+ // const path = std.mem.span(path_c);
189+
190+ // const result, const strategy = b: {
191+ // if (mode == std.os.linux.R_OK and
192+ // std.mem.eql(u8, path, "pipewire-0.3/confdata/client.conf"))
193+ // {
194+ // break :b .{ 0, "faked" };
195+ // } else {
196+ // break :b .{ std.os.linux.access(path, mode), "real" };
197+ // }
198+ // };
199+ // log.debug("access(\"{f}\", {}) -> {} ({s})", .{
200+ // std.zig.fmtString(path),
201+ // mode,
202+ // result,
203+ // strategy,
204+ // });
205+ // return result;
206+ // }
207+
111208/// A dynamic library made static.
112209const Lib = struct {
113210 /// You're allowed to pass null as the path to dlopen, in which case you're supposed to get a
@@ -354,6 +451,10 @@ const fops = struct {
354451 }
355452};
356453
454+ pub fn fmtFlags (val : anytype ) FmtFlags (@TypeOf (val )) {
455+ return .init (val );
456+ }
457+
357458/// Formats flags, skipping any values that are 0 for brevity.
358459fn FmtFlags (T : type ) type {
359460 return struct {
0 commit comments