Commit ec2465542c

Veikka Tuominen <git@vexu.eu>
2024-03-16 23:01:28
std: adjust DynLib API
The cross-platform functions now use an error set that contains all possible errors on every platform.
1 parent 7628785
Changed files (2)
lib/std/c.zig
@@ -1881,6 +1881,7 @@ pub const FILE = opaque {};
 pub extern "c" fn dlopen(path: [*:0]const u8, mode: c_int) ?*anyopaque;
 pub extern "c" fn dlclose(handle: *anyopaque) c_int;
 pub extern "c" fn dlsym(handle: ?*anyopaque, symbol: [*:0]const u8) ?*anyopaque;
+pub extern "c" fn dlerror() ?[*:0]u8;
 
 pub extern "c" fn sync() void;
 pub extern "c" fn syncfs(fd: c_int) c_int;
lib/std/dynamic_library.zig
@@ -7,14 +7,41 @@ const elf = std.elf;
 const windows = std.os.windows;
 const system = std.os.system;
 
-pub const DynLib = switch (builtin.os.tag) {
-    .linux => if (!builtin.link_libc or builtin.abi == .musl and builtin.link_mode == .static)
-        ElfDynLib
-    else
-        DlDynLib,
-    .windows => WindowsDynLib,
-    .macos, .tvos, .watchos, .ios, .freebsd, .netbsd, .openbsd, .dragonfly, .solaris, .illumos => DlDynLib,
-    else => void,
+/// Cross-platform dynamic library loading and symbol lookup.
+/// Platform-specific functionality is available through the `inner` field.
+pub const DynLib = struct {
+    const InnerType = switch (builtin.os.tag) {
+        .linux => if (!builtin.link_libc or builtin.abi == .musl and builtin.link_mode == .static)
+            ElfDynLib
+        else
+            DlDynLib,
+        .windows => WindowsDynLib,
+        .macos, .tvos, .watchos, .ios, .freebsd, .netbsd, .openbsd, .dragonfly, .solaris, .illumos => DlDynLib,
+        else => @compileError("unsupported platform"),
+    };
+
+    inner: InnerType,
+
+    pub const Error = ElfDynLib.Error || DlDynLib.Error || WindowsDynLib.Error;
+
+    /// Trusts the file. Malicious file will be able to execute arbitrary code.
+    pub fn open(path: []const u8) Error!DynLib {
+        return .{ .inner = try InnerType.open(path) };
+    }
+
+    /// Trusts the file. Malicious file will be able to execute arbitrary code.
+    pub fn openZ(path_c: [*:0]const u8) Error!DynLib {
+        return .{ .inner = try InnerType.open(path_c) };
+    }
+
+    /// Trusts the file.
+    pub fn close(self: *DynLib) void {
+        return self.inner.close();
+    }
+
+    pub fn lookup(self: *DynLib, comptime T: type, name: [:0]const u8) ?T {
+        return self.inner.lookup(T, name);
+    }
 };
 
 // The link_map structure is not completely specified beside the fields
@@ -59,12 +86,12 @@ pub fn get_DYNAMIC() ?[*]elf.Dyn {
     return @extern([*]elf.Dyn, .{ .name = "_DYNAMIC", .linkage = .weak });
 }
 
-pub fn linkmap_iterator(phdrs: []elf.Phdr) !LinkMap.Iterator {
+pub fn linkmap_iterator(phdrs: []elf.Phdr) error{InvalidExe}!LinkMap.Iterator {
     _ = phdrs;
     const _DYNAMIC = get_DYNAMIC() orelse {
         // No PT_DYNAMIC means this is either a statically-linked program or a
         // badly corrupted dynamically-linked one.
-        return LinkMap.Iterator{ .current = null };
+        return .{ .current = null };
     };
 
     const link_map_ptr = init: {
@@ -89,10 +116,10 @@ pub fn linkmap_iterator(phdrs: []elf.Phdr) !LinkMap.Iterator {
                 else => {},
             }
         }
-        return LinkMap.Iterator{ .current = null };
+        return .{ .current = null };
     };
 
-    return LinkMap.Iterator{ .current = link_map_ptr };
+    return .{ .current = link_map_ptr };
 }
 
 pub const ElfDynLib = struct {
@@ -239,7 +266,7 @@ pub const ElfDynLib = struct {
             }
         }
 
-        return ElfDynLib{
+        return .{
             .memory = all_loaded_mem,
             .strings = maybe_strings orelse return error.ElfStringSectionNotFound,
             .syms = maybe_syms orelse return error.ElfSymSectionNotFound,
@@ -260,7 +287,7 @@ pub const ElfDynLib = struct {
         self.* = undefined;
     }
 
-    pub fn lookup(self: *ElfDynLib, comptime T: type, name: [:0]const u8) ?T {
+    pub fn lookup(self: *const ElfDynLib, comptime T: type, name: [:0]const u8) ?T {
         if (self.lookupAddress("", name)) |symbol| {
             return @as(T, @ptrFromInt(symbol));
         } else {
@@ -268,6 +295,7 @@ pub const ElfDynLib = struct {
         }
     }
 
+    /// ElfDynLib specific
     /// Returns the address of the symbol
     pub fn lookupAddress(self: *const ElfDynLib, vername: []const u8, name: []const u8) ?usize {
         const maybe_versym = if (self.verdef == null) null else self.versym;
@@ -334,6 +362,8 @@ pub const WindowsDynLib = struct {
         return openEx(path, .none);
     }
 
+    /// WindowsDynLib specific
+    /// Opens dynamic library with specified library loading flags.
     pub fn openEx(path: []const u8, flags: windows.LoadLibraryFlags) Error!WindowsDynLib {
         const path_w = windows.sliceToPrefixedFileW(null, path) catch return error.InvalidPath;
         return openExW(path_w.span().ptr, flags);
@@ -343,15 +373,20 @@ pub const WindowsDynLib = struct {
         return openExZ(path_c, .none);
     }
 
+    /// WindowsDynLib specific
+    /// Opens dynamic library with specified library loading flags.
     pub fn openExZ(path_c: [*:0]const u8, flags: windows.LoadLibraryFlags) Error!WindowsDynLib {
         const path_w = try windows.cStrToPrefixedFileW(null, path_c);
         return openExW(path_w.span().ptr, flags);
     }
 
+    /// WindowsDynLib specific
     pub fn openW(path_w: [*:0]const u16) Error!WindowsDynLib {
         return openExW(path_w, .none);
     }
 
+    /// WindowsDynLib specific
+    /// Opens dynamic library with specified library loading flags.
     pub fn openExW(path_w: [*:0]const u16, flags: windows.LoadLibraryFlags) Error!WindowsDynLib {
         var offset: usize = 0;
         if (path_w[0] == '\\' and path_w[1] == '?' and path_w[2] == '?' and path_w[3] == '\\') {
@@ -359,7 +394,7 @@ pub const WindowsDynLib = struct {
             offset = 4;
         }
 
-        return WindowsDynLib{
+        return .{
             .dll = try windows.LoadLibraryExW(path_w + offset, flags),
         };
     }
@@ -389,7 +424,7 @@ pub const DlDynLib = struct {
     }
 
     pub fn openZ(path_c: [*:0]const u8) Error!DlDynLib {
-        return DlDynLib{
+        return .{
             .handle = system.dlopen(path_c, system.RTLD.LAZY) orelse {
                 return error.FileNotFound;
             },
@@ -397,7 +432,10 @@ pub const DlDynLib = struct {
     }
 
     pub fn close(self: *DlDynLib) void {
-        _ = system.dlclose(self.handle);
+        switch (std.os.errno(system.dlclose(self.handle))) {
+            .SUCCESS => return,
+            else => unreachable,
+        }
         self.* = undefined;
     }
 
@@ -410,6 +448,13 @@ pub const DlDynLib = struct {
             return null;
         }
     }
+
+    /// DlDynLib specific
+    /// Returns human readable string describing most recent error than occurred from `lookup`
+    /// or `null` if no error has occurred since initialization or when `getError` was last called.
+    pub fn getError() ?[:0]const u8 {
+        return mem.span(system.dlerror());
+    }
 };
 
 test "dynamic_library" {