diff --git a/src/common/process.cpp b/src/common/process.cpp
new file mode 100644
index 00000000..4732edc7
--- /dev/null
+++ b/src/common/process.cpp
@@ -0,0 +1,70 @@
+// yabridge: a Wine VST bridge
+// Copyright (C) 2020-2022 Robbert van der Helm
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+#include "process.h"
+
+#include
+
+ProcessEnvironment::ProcessEnvironment(char** initial_env) {
+ // We'll need to read all strings from `initial_env`. They _should_ all be
+ // zero-terminated strings, with a null pointer to indicate the end of the
+ // array.
+ assert(initial_env);
+ while (*initial_env) {
+ variables_.push_back(*initial_env);
+ initial_env++;
+ }
+}
+
+bool ProcessEnvironment::contains(const std::string_view& key) const {
+ for (const auto& variable : variables_) {
+ if (variable.starts_with(key) && variable.size() > key.size() &&
+ variable[key.size()] == '=') {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+std::optional ProcessEnvironment::get(
+ const std::string_view& key) const {
+ for (const auto& variable : variables_) {
+ if (variable.starts_with(key) && variable.size() > key.size() &&
+ variable[key.size()] == '=') {
+ return std::string_view(variable).substr(key.size() + 1);
+ }
+ }
+
+ return std::nullopt;
+}
+
+// NOLINTNEXTLINE(bugprone-easily-swappable-parameters)
+void ProcessEnvironment::insert(const std::string& key,
+ const std::string& value) {
+ variables_.push_back(key + "=" + value);
+}
+
+char* const* ProcessEnvironment::make_environ() const {
+ recreated_environ_.clear();
+
+ for (const auto& variable : variables_) {
+ recreated_environ_.push_back(variable.c_str());
+ }
+ recreated_environ_.push_back(nullptr);
+
+ return const_cast(recreated_environ_.data());
+}
diff --git a/src/common/process.h b/src/common/process.h
new file mode 100644
index 00000000..55e4f2bb
--- /dev/null
+++ b/src/common/process.h
@@ -0,0 +1,77 @@
+// yabridge: a Wine VST bridge
+// Copyright (C) 2020-2022 Robbert van der Helm
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+#pragma once
+
+#include
+#include
+#include
+
+// A minimal API akin to Boost.Process for launching and managing processes
+// using plain Linux APIs. Needed so we can implement our chainloader without
+// pulling in Boost.Process' Boost.Filesystem dependency (which would defeat the
+// entire purpose).
+
+/**
+ * Helper to create an `environ`-like environment object for passing to the
+ * `exec*e()` family of functions.
+ */
+class ProcessEnvironment {
+ public:
+ /**
+ * Create a new environment object based on an existing environment
+ * described by an array of null-terminated strings, terminated by a null
+ * pointer. You'll want to pass `environ` here.
+ */
+ ProcessEnvironment(char** initial_env);
+
+ /**
+ * Check if an environment variable exists within this environment. Mostly
+ * useful for debugging.
+ */
+ bool contains(const std::string_view& key) const;
+
+ /**
+ * Get the value for an environment variable, if it exists in this
+ * environment. Mostly useful for debugging.
+ */
+ std::optional get(const std::string_view& key) const;
+
+ /**
+ * Add an environment variable to the environment or overwrite an
+ * existing one.
+ */
+ void insert(const std::string& key, const std::string& value);
+
+ /**
+ * Create an environ-like object from the updated environment that can be
+ * passed to the `exec*e()` functions. These pointers will be invalidated
+ * when this object changes or when gets dropped.
+ */
+ char* const* make_environ() const;
+
+ private:
+ /**
+ * All environment variables read from the constructor argument and those
+ * inserted through `insert()`. These should be in `key=value` format.
+ */
+ std::vector variables_;
+ /**
+ * Contains pointers to the strings in `variables`, so we can return a
+ * `char**` in `make_environ()`.
+ */
+ mutable std::vector recreated_environ_;
+};
diff --git a/src/plugin/meson.build b/src/plugin/meson.build
index 7c8cbe41..aeaee5dc 100644
--- a/src/plugin/meson.build
+++ b/src/plugin/meson.build
@@ -10,6 +10,7 @@ vst2_plugin_sources = files(
'../common/logging/vst2.cpp',
'../common/audio-shm.cpp',
'../common/plugins.cpp',
+ '../common/process.cpp',
'../common/utils.cpp',
'bridges/vst2.cpp',
'host-process.cpp',
@@ -79,6 +80,7 @@ vst3_plugin_sources = files(
'../common/audio-shm.cpp',
'../common/configuration.cpp',
'../common/plugins.cpp',
+ '../common/process.cpp',
'../common/utils.cpp',
'bridges/vst3.cpp',
'bridges/vst3-impls/plugin-factory-proxy.cpp',
diff --git a/src/wine-host/meson.build b/src/wine-host/meson.build
index 3ca29216..ed357ae5 100644
--- a/src/wine-host/meson.build
+++ b/src/wine-host/meson.build
@@ -64,6 +64,7 @@ host_common_sources = files(
'../common/logging/vst2.cpp',
'../common/audio-shm.cpp',
'../common/plugins.cpp',
+ '../common/process.cpp',
'../common/utils.cpp',
'bridges/common.cpp',
'bridges/vst2.cpp',