dl: Add support for dlopen
Change-Id: I3fd109860a1eb0a945ddc3a0376ade248a4b2f75
Signed-off-by: William A. Kennington III <wak@google.com>
diff --git a/meson_options.txt b/meson_options.txt
index f96c48c..37c428a 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -1,3 +1,4 @@
+option('dl', type: 'feature', description: 'libdl wrapper support')
option('fd', type: 'feature', description: 'Managed file descriptor support')
option('io_uring', type: 'feature', description: 'io_uring wrapper support')
option('tests', type: 'feature', description: 'Build tests')
diff --git a/src/meson.build b/src/meson.build
index 2a0169a..ec981a5 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -43,6 +43,30 @@
'stdplus/signal.cpp',
]
+has_dl = false
+if not get_option('dl').disabled()
+ dl_dep = meson.get_compiler('cpp').find_library('dl', required: false)
+ has_dl = meson.get_compiler('cpp').links('
+ #include <dlfcn.h>
+ int main() { dlopen("", 0); }
+ ', dependencies: dl_dep)
+endif
+if has_dl
+ stdplus_deps += [
+ dl_dep,
+ ]
+
+ stdplus_srcs += [
+ 'stdplus/dl.cpp',
+ ]
+
+ install_headers(
+ 'stdplus/dl.hpp',
+ subdir: 'stdplus')
+elif get_option('dl').enabled()
+ error('libdl support required')
+endif
+
has_fd = false
if not get_option('fd').disabled() and has_span
has_fd = true
diff --git a/src/stdplus/dl.cpp b/src/stdplus/dl.cpp
new file mode 100644
index 0000000..659389c
--- /dev/null
+++ b/src/stdplus/dl.cpp
@@ -0,0 +1,45 @@
+#include <dlfcn.h>
+#include <fmt/format.h>
+
+#include <stdplus/dl.hpp>
+
+namespace stdplus
+{
+
+Dl::Dl(const char* file, DlOpenFlags flags) :
+ handle(open(file, static_cast<int>(flags)))
+{
+}
+
+struct link_map* Dl::linkMap()
+{
+ struct link_map* ret;
+ info(RTLD_DI_LINKMAP, &ret);
+ return ret;
+}
+
+void Dl::info(int request, void* info)
+{
+ if (::dlinfo(*handle, request, info) != 0)
+ {
+ throw std::runtime_error("dlinfo");
+ };
+}
+
+void* Dl::open(const char* file, int flags)
+{
+ void* ret = ::dlopen(file, flags);
+ if (ret == nullptr)
+ {
+ throw std::runtime_error(fmt::format(
+ "dlopen `{}`: {}", file ? file : "<nullptr>", dlerror()));
+ }
+ return ret;
+}
+
+void Dl::close(void*&& handle)
+{
+ ::dlclose(handle);
+}
+
+} // namespace stdplus
diff --git a/src/stdplus/dl.hpp b/src/stdplus/dl.hpp
new file mode 100644
index 0000000..0526391
--- /dev/null
+++ b/src/stdplus/dl.hpp
@@ -0,0 +1,55 @@
+#pragma once
+#include <dlfcn.h>
+#include <link.h>
+
+#include <stdplus/flags.hpp>
+#include <stdplus/handle/managed.hpp>
+
+namespace stdplus
+{
+
+enum class DlOpenType : int
+{
+ Lazy = RTLD_LAZY,
+ Now = RTLD_NOW,
+};
+
+enum class DlOpenFlag : int
+{
+ Global = RTLD_GLOBAL,
+ Local = RTLD_LOCAL,
+ NoDelete = RTLD_NODELETE,
+ NoLoad = RTLD_NOLOAD,
+ DeepBind = RTLD_DEEPBIND,
+};
+
+class DlOpenFlags : public stdplus::BitFlags<int, DlOpenFlag>
+{
+ public:
+ inline DlOpenFlags(DlOpenType type) :
+ BitFlags<int, DlOpenFlag>(static_cast<int>(type))
+ {
+ }
+
+ inline DlOpenFlags(BitFlags<int, DlOpenFlag> flags) :
+ BitFlags<int, DlOpenFlag>(flags)
+ {
+ }
+};
+
+class Dl
+{
+ public:
+ Dl(const char* filename, DlOpenFlags flags);
+
+ struct link_map* linkMap();
+
+ private:
+ void info(int request, void* info);
+
+ static void* open(const char* filename, int flags);
+ static void close(void*&& handle);
+ stdplus::Managed<void*>::Handle<close> handle;
+};
+
+} // namespace stdplus
diff --git a/test/dl.cpp b/test/dl.cpp
new file mode 100644
index 0000000..cc16293
--- /dev/null
+++ b/test/dl.cpp
@@ -0,0 +1,21 @@
+#include <gtest/gtest.h>
+#include <stdplus/dl.hpp>
+
+namespace stdplus
+{
+
+TEST(Dl, FailedOpen)
+{
+ EXPECT_THROW(Dl("nodl.so", DlOpenFlags(DlOpenType::Now)),
+ std::runtime_error);
+}
+
+TEST(Dl, GetLinkMap)
+{
+ Dl dl(nullptr, DlOpenFlags(DlOpenType::Now)
+ .set(DlOpenFlag::Global)
+ .set(DlOpenFlag::NoLoad));
+ EXPECT_NE(nullptr, dl.linkMap());
+}
+
+} // namespace stdplus
diff --git a/test/meson.build b/test/meson.build
index b303032..7d26297 100644
--- a/test/meson.build
+++ b/test/meson.build
@@ -29,6 +29,16 @@
'util/string',
]
+if has_dl
+ gtests += [
+ 'dl',
+ ]
+elif build_tests.enabled()
+ error('Not testing libdl feature')
+else
+ warning('Not testing libdl feature')
+endif
+
if has_fd
gtests += [
'fd/dupable',