FlatImage
A configurable Linux containerization system
Loading...
Searching...
No Matches
bwrap.hpp
Go to the documentation of this file.
1
8
9#pragma once
10
11#include <fcntl.h>
12#include <filesystem>
13#include <ranges>
14#include <sys/types.h>
15#include <pwd.h>
16#include <regex>
17
18#include "../db/bind.hpp"
23#include "../std/expected.hpp"
24#include "../std/vector.hpp"
25#include "../lib/log.hpp"
26#include "../lib/subprocess.hpp"
27#include "../lib/env.hpp"
28#include "../macro.hpp"
29
39namespace ns_bwrap
40{
41
42namespace
43{
44
45namespace fs = std::filesystem;
46
47}
48
57namespace ns_proxy
58{
59
63struct Id
64{
65 mode_t uid;
66 mode_t gid;
67};
68
69
73struct Overlay
74{
75 std::vector<fs::path> vec_path_dir_layer;
76 fs::path path_dir_upper;
77 fs::path path_dir_work;
78};
79
83struct Logs
84{
85 fs::path const path_file_apparmor;
86 Logs(fs::path const& path_dir_log) : path_file_apparmor(path_dir_log / "apparmor.log")
87 {
88 fs::create_directories(path_file_apparmor.parent_path());
89 }
90};
91
95struct User
96{
97 struct UserData
98 {
99 Id id;
100 std::string const name;
101 std::filesystem::path const path_dir_home;
102 std::filesystem::path const path_file_shell;
103 std::filesystem::path const path_file_bashrc;
104 std::filesystem::path const path_file_passwd;
110 inline operator std::string()
111 {
112 return std::format("{}:x:{}:{}:{}:{}:{}", name, id.uid, id.gid, name, path_dir_home.string(), path_file_shell.string());
113 }
114 } data;
115
121 User(UserData const& data)
122 : data(data)
123 {}
124
130 [[nodiscard]] Value<void> write_passwd(fs::path const& path_file_passwd)
131 {
132 std::ofstream file_passwd{path_file_passwd};
133 return_if(not file_passwd.is_open(), Error("E::Failed to open passwd file at {}", path_file_passwd));
134 file_passwd << std::string{data} << '\n';
135 return {};
136 }
137
143 [[nodiscard]] Value<void> write_bashrc(fs::path const& path_file_bashrc, std::string_view ps1)
144 {
145 // Open new bashrc file
146 std::ofstream file_bashrc{path_file_bashrc};
147 return_if(not file_bashrc.is_open(), Error("E::Failed to open bashrc file at {}", path_file_bashrc));
148 // Write custom ps1 or default value
149 if(ps1.empty())
150 {
151 file_bashrc << R"(export PS1="[flatimage-${FIM_DIST,,}] \W > ")";
152 }
153 else
154 {
155 file_bashrc << "export PS1=" << '"' << ps1 << '"';
156 }
157 return {};
158 }
159};
160
161} // namespace ns_proxy
162
175inline Value<void> bwrap_clean(fs::path const& path_dir_work)
176{
177 // Check if directory exists
178 if(not Try(fs::exists(path_dir_work), "E::Could not check bwrap work directory"))
179 {
180 return {};
181 }
182 // Try to change permissions
183 if(::chmod(path_dir_work.c_str(), 0755) < 0)
184 {
185 logger("D::Error to modify permissions '{}': '{}'", path_dir_work, strerror(errno));
186 }
187 // True if the file was deleted
188 // False if it did not exist
189 // Exception if it cannot be removed: cannot remove all: Read-only file system'
190 // Thus, the check is if it throws or not
191 Try(fs::remove_all(path_dir_work), "E::Failed to remove bwrap work directory");
192 // Success
193 return {};
194}
195
202[[nodiscard]] inline std::vector<fs::path> get_mounted_layers(fs::path const& path_dir_layers)
203{
204 std::vector<fs::path> vec_path_dir_layer = fs::directory_iterator(path_dir_layers)
205 | std::views::filter([](auto&& e){ return fs::is_directory(e.path()); })
206 | std::views::transform([](auto&& e){ return e.path(); })
207 | std::ranges::to<std::vector<fs::path>>();
208 std::ranges::sort(vec_path_dir_layer);
209 return vec_path_dir_layer;
210}
211
212
214using Permission = ns_reserved::ns_permissions::Permission;
215using Unshares = ns_reserved::ns_unshare::Unshares;
216using Unshare = ns_reserved::ns_unshare::Unshare;
217
218struct bwrap_run_ret_t { int code; int syscall_nr; int errno_nr; };
219
227class Bwrap
228{
229 private:
230 // Logging
231 ns_proxy::Logs m_logs;
232 // Program to run, its arguments and environment
233 fs::path m_path_file_program;
234 std::vector<std::string> m_program_args;
235 std::vector<std::string> m_program_env;
236 // Overlay
237 std::optional<ns_proxy::Overlay> m_overlay;
238 // Root directory
239 fs::path const m_path_dir_root;
240 // XDG_RUNTIME_DIR
241 fs::path m_path_dir_xdg_runtime;
242 // Arguments and environment to bwrap
243 std::vector<std::string> m_args;
244 // Run bwrap with uid and gid equal to 0
245 bool m_is_root;
246 // Bwrap native --overlay options
247 void overlay(ns_proxy::Overlay const& overlay);
248 // Set XDG_RUNTIME_DIR
249 void set_xdg_runtime_dir();
250 // Setup
251 Value<fs::path> test_and_setup(fs::path const& path_file_bwrap);
252 Bwrap& symlink_nvidia(fs::path const& path_dir_root_guest, fs::path const& path_dir_root_host);
253
254 public:
256 , ns_proxy::User user
257 , fs::path const& path_dir_root
258 , fs::path const& path_file_program
259 , std::vector<std::string> const& program_args
260 , std::vector<std::string> const& program_env);
261 ~Bwrap();
262 Bwrap(Bwrap const&) = delete;
263 Bwrap(Bwrap&&) = delete;
264 Bwrap& operator=(Bwrap const&) = delete;
265 Bwrap& operator=(Bwrap&&) = delete;
266 [[maybe_unused]] [[nodiscard]] Bwrap& with_binds(ns_db::ns_bind::Binds const& binds);
267 [[maybe_unused]] [[nodiscard]] Bwrap& bind_home();
268 [[maybe_unused]] [[nodiscard]] Bwrap& bind_media();
269 [[maybe_unused]] [[nodiscard]] Bwrap& bind_audio();
270 [[maybe_unused]] [[nodiscard]] Bwrap& bind_wayland();
271 [[maybe_unused]] [[nodiscard]] Bwrap& bind_xorg();
272 [[maybe_unused]] [[nodiscard]] Bwrap& bind_dbus_user();
273 [[maybe_unused]] [[nodiscard]] Bwrap& bind_dbus_system();
274 [[maybe_unused]] [[nodiscard]] Bwrap& bind_udev();
275 [[maybe_unused]] [[nodiscard]] Bwrap& bind_input();
276 [[maybe_unused]] [[nodiscard]] Bwrap& bind_usb();
277 [[maybe_unused]] [[nodiscard]] Bwrap& bind_network();
278 [[maybe_unused]] [[nodiscard]] Bwrap& bind_shm();
279 [[maybe_unused]] [[nodiscard]] Bwrap& bind_optical();
280 [[maybe_unused]] [[nodiscard]] Bwrap& bind_dev();
281 [[maybe_unused]] [[nodiscard]] Bwrap& with_bind_gpu(fs::path const& path_dir_root_guest, fs::path const& path_dir_root_host);
282 [[maybe_unused]] [[nodiscard]] Bwrap& with_bind(fs::path const& src, fs::path const& dst);
283 [[maybe_unused]] [[nodiscard]] Bwrap& with_bind_ro(fs::path const& src, fs::path const& dst);
284 [[maybe_unused]] void set_overlay(ns_proxy::Overlay const& overlay);
285 [[maybe_unused]] [[nodiscard]] Value<bwrap_run_ret_t> run(Permissions const& permissions
286 , Unshares const& unshares
287 , fs::path const& path_file_daemon
288 , ns_db::ns_portal::ns_dispatcher::Dispatcher const& arg1_dispatcher
289 , ns_db::ns_portal::ns_daemon::Daemon const& arg1_daemon
291 );
292};
293
305 , ns_proxy::User user
306 , fs::path const& path_dir_root
307 , fs::path const& path_file_program
308 , std::vector<std::string> const& program_args
309 , std::vector<std::string> const& program_env)
310 : m_logs(logs)
311 , m_path_file_program(path_file_program)
312 , m_program_args(program_args)
313 , m_program_env()
314 , m_overlay(std::nullopt)
315 , m_path_dir_root(path_dir_root)
316 , m_path_dir_xdg_runtime()
317 , m_args()
318 , m_is_root(user.data.id.uid == 0)
319{
320 // Push passed environment
321 std::ranges::for_each(program_env, [&](auto&& e){ logger("I::ENV: {}", e); m_program_env.push_back(e); });
322 // Configure TERM
323 m_program_env.push_back("TERM=xterm");
324 // Configure user info
326 // Configure user name
327 , "--setenv", "USER", std::string{user.data.name}
328 // Configure uid and gid
329 , "--uid", std::to_string(user.data.id.uid), "--gid", std::to_string(user.data.id.gid)
330 // Configure HOME
331 , "--setenv", "HOME", user.data.path_dir_home.string()
332 // Configure SHELL
333 , "--setenv", "SHELL", user.data.path_file_shell.string()
334 );
335 // Setup .bashrc
336 ns_env::set("BASHRC_FILE", user.data.path_file_bashrc.c_str(), ns_env::Replace::Y);
337 // System bindings
339 , "--dev", "/dev"
340 , "--proc", "/proc"
341 , "--bind", "/tmp", "/tmp"
342 , "--bind", "/sys", "/sys"
343 , "--bind-try", "/etc/group", "/etc/group"
344 );
345 // Configure passwd file, make it as a rw binding, because some package managers
346 // might try to update it.
347 ns_vector::push_back(m_args, "--bind-try", user.data.path_file_passwd, "/etc/passwd");
348 // Check if XDG_RUNTIME_DIR is set or try to set it manually
349 set_xdg_runtime_dir();
350}
351
356{
357 if(not m_overlay) { return; }
358
359 ns_bwrap::bwrap_clean(m_overlay->path_dir_work / "work").discard("E::Could not clean bwrap directory");
360}
361
369inline void Bwrap::overlay(ns_proxy::Overlay const& overlay)
370{
371 std::vector<std::string> args;
372 // Build --overlay related commands
373 for(fs::path const& path_dir_layer : overlay.vec_path_dir_layer)
374 {
375 logger("I::Overlay layer '{}'", path_dir_layer);
376 ns_vector::push_back(args, "--overlay-src", path_dir_layer);
377 } // for
378 ns_vector::push_back(args, "--overlay", overlay.path_dir_upper, overlay.path_dir_work, "/");
379 m_args.insert(m_args.begin(), args.begin(), args.end());
380}
381
385inline void Bwrap::set_xdg_runtime_dir()
386{
387 m_path_dir_xdg_runtime = ns_env::get_expected<"W">("XDG_RUNTIME_DIR").value_or(std::format("/run/user/{}", getuid()));
388 logger("I::XDG_RUNTIME_DIR: {}", m_path_dir_xdg_runtime);
389 m_program_env.push_back(std::format("XDG_RUNTIME_DIR={}", m_path_dir_xdg_runtime.string()));
390 ns_vector::push_back(m_args, "--setenv", "XDG_RUNTIME_DIR", m_path_dir_xdg_runtime);
391}
392
399inline Value<fs::path> Bwrap::test_and_setup(fs::path const& path_file_bwrap_src)
400{
401 // Test current bwrap binary
402 using enum ns_subprocess::Stream;
403 auto ret = ns_subprocess::Subprocess(path_file_bwrap_src)
404 .with_args("--bind", "/", "/", "bash", "-c", "echo")
405 .with_stdio(Pipe)
406 .spawn()->wait();
407 return_if(ret and *ret == 0, path_file_bwrap_src);
408 // Try to use bwrap installed by flatimage
409 fs::path path_file_bwrap_opt = "/opt/flatimage/bwrap";
410 ret = ns_subprocess::Subprocess(path_file_bwrap_opt)
411 .with_args("--bind", "/", "/", "bash", "-c", "echo")
412 .with_stdio(Pipe)
413 .spawn()->wait();
414 return_if(ret and *ret == 0, path_file_bwrap_opt);
415 // Error might be EACCES, try to integrate with apparmor
416 fs::path path_file_pkexec = Pop(ns_env::search_path("pkexec"));
417 fs::path path_file_bwrap_apparmor = Pop(ns_env::search_path("fim_bwrap_apparmor"));
418 Pop(ns_subprocess::Subprocess(path_file_pkexec)
419 .with_args(path_file_bwrap_apparmor, m_logs.path_file_apparmor, path_file_bwrap_src)
420 .spawn()->wait());
421 return path_file_bwrap_opt;
422}
423
431inline Bwrap& Bwrap::symlink_nvidia(fs::path const& path_dir_root_guest, fs::path const& path_dir_root_host)
432{
433 std::regex regex_exclude("gst|icudata|egl-wayland", std::regex_constants::extended);
434
435 auto f_find_and_bind = [&]<typename... Args>(fs::path const& path_dir_search, Args&&... args) -> void
436 {
437 std::vector<std::string_view> keywords{std::forward<Args>(args)...};
438 return_if(not fs::exists(path_dir_search),, "E::Search path does not exist: '{}'", path_dir_search);
439 auto f_process_entry = [&](fs::path const& path_file_entry) -> void
440 {
441 // Skip ignored matches
442 return_if(std::regex_search(path_file_entry.c_str(), regex_exclude),);
443 // Skip directories
444 return_if(fs::is_directory(path_file_entry),);
445 // Skip files that do not match keywords
446 return_if(not std::ranges::any_of(keywords, [&](auto&& f){ return path_file_entry.filename().string().contains(f); }),);
447 // Symlink target is the file and the end of the symlink chain
448 // fs::canonical throws if path_file_entry does not exist
449 auto path_file_entry_realpath = fs::canonical(path_file_entry);
450 // Create target and symlink names
451 fs::path path_link_target = path_dir_root_host / path_file_entry_realpath.relative_path();
452 fs::path path_link_name = path_dir_root_guest / path_file_entry.relative_path();
453 // File already exists in the container as a regular file or directory, skip
454 return_if(fs::exists(path_link_name) and not fs::is_symlink(path_link_name),);
455 // Create parent directories
456 fs::create_directories(path_link_name.parent_path());
457 // Remove existing link
458 fs::remove(path_link_name);
459 // Symlink
460 fs::create_symlink(path_link_target.c_str(), path_link_name.c_str());
461 // Log symlink successful
462 logger("D::PERM(NVIDIA): {} -> {}", path_link_name, path_link_target);
463 };
464 // Process entries
465 for(auto&& path_file_entry : fs::directory_iterator(path_dir_search) | std::views::transform([](auto&& e){ return e.path(); }))
466 {
467 Catch(f_process_entry(path_file_entry)).template discard();
468 } // for
469 };
470
471 // Bind files
472 f_find_and_bind("/usr/lib", "nvidia", "cuda", "nvcuvid", "nvoptix");
473 f_find_and_bind("/usr/lib/x86_64-linux-gnu", "nvidia", "cuda", "nvcuvid", "nvoptix");
474 f_find_and_bind("/usr/lib/i386-linux-gnu", "nvidia", "cuda", "nvcuvid", "nvoptix");
475 f_find_and_bind("/usr/bin", "nvidia");
476 f_find_and_bind("/usr/share", "nvidia");
477 f_find_and_bind("/usr/share/vulkan/icd.d", "nvidia");
478 f_find_and_bind("/usr/lib32", "nvidia", "cuda");
479
480 // Bind devices
481 for(auto&& entry : fs::directory_iterator("/dev")
482 | std::views::transform([](auto&& e){ return e.path(); })
483 | std::views::filter([](auto&& e){ return e.filename().string().contains("nvidia"); }))
484 {
485 ns_vector::push_back(m_args, "--dev-bind-try", entry, entry);
486 } // for
487
488 return *this;
489}
490
498{
499 using TypeBind = ns_db::ns_bind::Type;
500 // Load bindings from the filesystem if any
501 for(auto&& bind : binds.get())
502 {
503 std::string src = bind.path_src;
504 std::string dst = bind.path_dst;
505 std::string type = (bind.type == TypeBind::DEV)? "--dev-bind-try"
506 : (bind.type == TypeBind::RO)? "--ro-bind-try"
507 : "--bind-try";
508 m_args.push_back(type);
509 m_args.push_back(ns_env::expand(src).value_or(src));
510 m_args.push_back(ns_env::expand(dst).value_or(dst));
511 } // for
512 return *this;
513}
514
522inline Bwrap& Bwrap::with_bind(fs::path const& src, fs::path const& dst)
523{
524 ns_vector::push_back(m_args, "--bind-try", src, dst);
525 return *this;
526}
527
535inline Bwrap& Bwrap::with_bind_ro(fs::path const& src, fs::path const& dst)
536{
537 ns_vector::push_back(m_args, "--ro-bind-try", src, dst);
538 return *this;
539}
540
546inline void Bwrap::set_overlay(ns_proxy::Overlay const& overlay)
547{
548 m_overlay = overlay;
549}
550
557{
558 if ( m_is_root ) { return *this; }
559 logger("D::PERM(HOME)");
560 std::string str_dir_home = ({
561 auto ret = ns_env::get_expected("HOME");
562 return_if(not ret, *this, "E::HOME environment variable is unset");
563 ret.value();
564 });
565 ns_vector::push_back(m_args, "--bind-try", str_dir_home, str_dir_home);
566 return *this;
567}
568
577{
578 logger("D::PERM(MEDIA)");
579 ns_vector::push_back(m_args, "--bind-try", "/media", "/media");
580 ns_vector::push_back(m_args, "--bind-try", "/run/media", "/run/media");
581 ns_vector::push_back(m_args, "--bind-try", "/mnt", "/mnt");
582 return *this;
583}
584
593{
594 logger("D::PERM(AUDIO)");
595
596 // Try to bind pulse socket
597 fs::path path_socket_pulse = m_path_dir_xdg_runtime / "pulse/native";
598 ns_vector::push_back(m_args, "--bind-try", path_socket_pulse, path_socket_pulse);
599 ns_vector::push_back(m_args, "--setenv", "PULSE_SERVER", "unix:" + path_socket_pulse.string());
600
601 // Try to bind pipewire socket
602 fs::path path_socket_pipewire = m_path_dir_xdg_runtime / "pipewire-0";
603 ns_vector::push_back(m_args, "--bind-try", path_socket_pipewire, path_socket_pipewire);
604
605 // Other paths required to sound
606 ns_vector::push_back(m_args, "--dev-bind-try", "/dev/dsp", "/dev/dsp");
607 ns_vector::push_back(m_args, "--bind-try", "/dev/snd", "/dev/snd");
608 ns_vector::push_back(m_args, "--bind-try", "/proc/asound", "/proc/asound");
609
610 return *this;
611}
612
622{
623 logger("D::PERM(WAYLAND)");
624 // Get WAYLAND_DISPLAY
625 std::string env_wayland_display = ({
626 auto ret = ns_env::get_expected("WAYLAND_DISPLAY");
627 return_if(not ret, *this, "E::WAYLAND_DISPLAY is undefined");
628 ret.value();
629 });
630
631 // Get wayland socket
632 fs::path path_socket_wayland = m_path_dir_xdg_runtime / env_wayland_display;
633
634 // Bind
635 ns_vector::push_back(m_args, "--bind-try", path_socket_wayland, path_socket_wayland);
636 ns_vector::push_back(m_args, "--setenv", "WAYLAND_DISPLAY", env_wayland_display);
637
638 return *this;
639}
640
650{
651 logger("D::PERM(XORG)");
652 // Get DISPLAY
653 std::string env_display = ({
654 auto ret = ns_env::get_expected("DISPLAY");
655 return_if(not ret, *this, "E::DISPLAY is undefined");
656 ret.value();
657 });
658 // Get XAUTHORITY
659 std::string env_xauthority = ({
660 auto ret = ns_env::get_expected("XAUTHORITY");
661 return_if(not ret, *this, "E::XAUTHORITY is undefined");
662 ret.value();
663 });
664 // Bind
665 ns_vector::push_back(m_args, "--ro-bind-try", env_xauthority, env_xauthority);
666 ns_vector::push_back(m_args, "--setenv", "XAUTHORITY", env_xauthority);
667 ns_vector::push_back(m_args, "--setenv", "DISPLAY", env_display);
668 return *this;
669}
670
679{
680 logger("D::PERM(DBUS_USER)");
681 // Get DBUS_SESSION_BUS_ADDRESS
682 std::string env_dbus_session_bus_address = ({
683 auto ret = ns_env::get_expected("DBUS_SESSION_BUS_ADDRESS");
684 return_if(not ret, *this, "E::DBUS_SESSION_BUS_ADDRESS is undefined");
685 ret.value();
686 });
687
688 // Path to current session bus
689 std::string str_dbus_session_bus_path = env_dbus_session_bus_address;
690
691 // Variable has expected contents similar to: 'unix:path=/run/user/1000/bus,guid=bb1adf978ae9c14....'
692 // Erase until the first '=' (inclusive)
693 if ( auto pos = str_dbus_session_bus_path.find('/'); pos != std::string::npos )
694 {
695 str_dbus_session_bus_path.erase(0, pos);
696 } // if
697
698 // Erase from the first ',' (inclusive)
699 if ( auto pos = str_dbus_session_bus_path.find(','); pos != std::string::npos )
700 {
701 str_dbus_session_bus_path.erase(pos);
702 } // if
703
704 // Bind
705 ns_vector::push_back(m_args, "--setenv", "DBUS_SESSION_BUS_ADDRESS", env_dbus_session_bus_address);
706 ns_vector::push_back(m_args, "--bind-try", str_dbus_session_bus_path, str_dbus_session_bus_path);
707
708 return *this;
709}
710
719{
720 logger("D::PERM(DBUS_SYSTEM)");
721 ns_vector::push_back(m_args, "--bind-try", "/run/dbus/system_bus_socket", "/run/dbus/system_bus_socket");
722 return *this;
723}
724
733{
734 logger("D::PERM(UDEV)");
735 ns_vector::push_back(m_args, "--bind-try", "/run/udev", "/run/udev");
736 return *this;
737}
738
747{
748 logger("D::PERM(INPUT)");
749 ns_vector::push_back(m_args, "--dev-bind-try", "/dev/input", "/dev/input");
750 ns_vector::push_back(m_args, "--dev-bind-try", "/dev/uinput", "/dev/uinput");
751 return *this;
752}
753
762{
763 logger("D::PERM(USB)");
764 ns_vector::push_back(m_args, "--dev-bind-try", "/dev/bus/usb", "/dev/bus/usb");
765 ns_vector::push_back(m_args, "--dev-bind-try", "/dev/usb", "/dev/usb");
766 return *this;
767}
768
781{
782 logger("D::PERM(NETWORK)");
783 ns_vector::push_back(m_args, "--ro-bind-try", "/etc/host.conf", "/etc/host.conf");
784 ns_vector::push_back(m_args, "--ro-bind-try", "/etc/hosts", "/etc/hosts");
785 ns_vector::push_back(m_args, "--ro-bind-try", "/etc/nsswitch.conf", "/etc/nsswitch.conf");
786 ns_vector::push_back(m_args, "--ro-bind-try", "/etc/resolv.conf", "/etc/resolv.conf");
787 return *this;
788}
789
798{
799 logger("D::PERM(SHM)");
800 ns_vector::push_back(m_args, "--dev-bind-try", "/dev/shm", "/dev/shm");
801 return *this;
802}
803
814{
815 logger("D::PERM(OPTICAL)");
816 auto f_bind = [this](fs::path const& path_device) -> bool
817 {
818 auto __expected_fn = [](auto&&){ return false; };
819 if(not Try(fs::exists(path_device), "E::Error to check if file exists: {}", path_device))
820 {
821 return false;
822 }
823 ns_vector::push_back(m_args, "--dev-bind-try", path_device, path_device);
824 return true;
825 };
826 // Bind /dev/sr[0-255] and /dev/sg[0-255]
827 // Devices are sequential, so stop when both sr and sg don't exist
828 for(int i : std::views::iota(0,256))
829 {
830 bool sr_exists = f_bind(std::format("/dev/sr{}", i));
831 bool sg_exists = f_bind(std::format("/dev/sg{}", i));
832 // Stop if neither device exists
833 if (!sr_exists && !sg_exists) break;
834 }
835 return *this;
836}
837
846{
847 logger("D::PERM(DEV)");
848 ns_vector::push_back(m_args, "--dev-bind-try", "/dev", "/dev");
849 return *this;
850}
851
852
860inline Bwrap& Bwrap::with_bind_gpu(fs::path const& path_dir_root_guest, fs::path const& path_dir_root_host)
861{
862 logger("D::PERM(GPU)");
863 ns_vector::push_back(m_args, "--dev-bind-try", "/dev/dri", "/dev/dri");
864 return symlink_nvidia(path_dir_root_guest, path_dir_root_host);
865}
866
878inline Value<bwrap_run_ret_t> Bwrap::run(Permissions const& permissions
879 , Unshares const& unshares
880 , fs::path const& path_file_daemon
881 , ns_db::ns_portal::ns_dispatcher::Dispatcher const& arg1_dispatcher
882 , ns_db::ns_portal::ns_daemon::Daemon const& arg1_daemon
884{
885 // Configure bindings
886 if(permissions.contains(Permission::HOME)){ std::ignore = bind_home(); };
887 if(permissions.contains(Permission::MEDIA)){ std::ignore = bind_media(); };
888 if(permissions.contains(Permission::AUDIO)){ std::ignore = bind_audio(); };
889 if(permissions.contains(Permission::WAYLAND)){ std::ignore = bind_wayland(); };
890 if(permissions.contains(Permission::XORG)){ std::ignore = bind_xorg(); };
891 if(permissions.contains(Permission::DBUS_USER)){ std::ignore = bind_dbus_user(); };
892 if(permissions.contains(Permission::DBUS_SYSTEM)){ std::ignore = bind_dbus_system(); };
893 if(permissions.contains(Permission::UDEV)){ std::ignore = bind_udev(); };
894 if(permissions.contains(Permission::INPUT)){ std::ignore = bind_input(); };
895 if(permissions.contains(Permission::USB)){ std::ignore = bind_usb(); };
896 if(permissions.contains(Permission::NETWORK)){ std::ignore = bind_network(); };
897 if(permissions.contains(Permission::SHM)){ std::ignore = bind_shm(); };
898 if(permissions.contains(Permission::OPTICAL)){ std::ignore = bind_optical(); };
899 if(permissions.contains(Permission::DEV)){ std::ignore = bind_dev(); };
900
901 // Configure unshare namespace options
902 // Note: USER and CGROUP use '-try' variants for permissiveness
903 if(unshares.contains(Unshare::USER))
904 {
905 logger("D::UNSHARE(USER)");
906 ns_vector::push_back(m_args, "--unshare-user-try");
907 }
908 if(unshares.contains(Unshare::IPC))
909 {
910 logger("D::UNSHARE(IPC)");
911 ns_vector::push_back(m_args, "--unshare-ipc");
912 }
913 if(unshares.contains(Unshare::PID))
914 {
915 logger("D::UNSHARE(PID)");
916 ns_vector::push_back(m_args, "--unshare-pid");
917 }
918 if(unshares.contains(Unshare::NET))
919 {
920 logger("D::UNSHARE(NET)");
921 ns_vector::push_back(m_args, "--unshare-net");
922 }
923 if(unshares.contains(Unshare::UTS))
924 {
925 logger("D::UNSHARE(UTS)");
926 ns_vector::push_back(m_args, "--unshare-uts");
927 }
928 if(unshares.contains(Unshare::CGROUP))
929 {
930 logger("D::UNSHARE(CGROUP)");
931 ns_vector::push_back(m_args, "--unshare-cgroup-try");
932 }
933
934 // Search for bash
935 fs::path path_file_bash = Pop(ns_env::search_path("bash"));
936
937 // Use builtin bwrap or native if exists
938 fs::path path_file_bwrap = Pop(ns_env::search_path("bwrap"));
939
940 // Test bwrap and setup apparmor if it is required
941 // Adjust the bwrap path to the one integrated with apparmor if needed
942 path_file_bwrap = Pop(test_and_setup(path_file_bwrap));
943
944 // Pipe to receive errors from bwrap
945 int pipe_error[2];
946 return_if(pipe(pipe_error) == -1, Error("E::Could not open bwrap error pipe: {}", strerror(errno)));
947
948 // Configure pipe read end as non-blocking
949 if(fcntl(pipe_error[0], F_SETFL, fcntl(pipe_error[0], F_GETFL, 0) | O_NONBLOCK) < 0)
950 {
951 return Error("E::Could not configure bwrap pipe to be non-blocking");
952 }
953
954 // Get path to daemon
955 std::string str_arg1_dispatcher = Pop(ns_db::ns_portal::ns_dispatcher::serialize(arg1_dispatcher));
956 std::string str_arg1_daemon = Pop(ns_db::ns_portal::ns_daemon::serialize(arg1_daemon));
957 std::string str_arg2_daemon = Pop(ns_db::ns_portal::ns_daemon::ns_log::serialize(arg2_daemon));
958
959 // Pass daemon configuration via environment variables to avoid shell escaping issues
960 // The daemon will read FIM_DAEMON_CFG and FIM_DAEMON_LOG from environment
961 ns_vector::push_back(m_args, "--setenv", "FIM_DISPATCHER_CFG", str_arg1_dispatcher);
962 ns_vector::push_back(m_args, "--setenv", "FIM_DAEMON_CFG", str_arg1_daemon);
963 ns_vector::push_back(m_args, "--setenv", "FIM_DAEMON_LOG", str_arg2_daemon);
964
965 return_if(not Try(fs::exists(path_file_daemon)), Error("E::Missing portal daemon to run binary file path"));
966
967 // Configure bwrap overlay filesystem if required
968 if(m_overlay)
969 {
970 overlay(m_overlay.value());
971 }
972 else
973 {
974 ns_vector::push_front(m_args, "--bind", m_path_dir_root, "/");
975 }
976
977 // Run Bwrap
978 auto code = ns_subprocess::Subprocess(path_file_bash)
979 .with_args("-c", std::format(R"("{}" "$@")", path_file_bwrap.string()), "--")
980 .with_args("--error-fd", std::to_string(pipe_error[1]))
981 .with_args(m_args)
982 .with_args(path_file_bash, "-c", std::format(R"(&>/dev/null nohup "{}" & disown; "{}" "$@")", path_file_daemon.string(), m_path_file_program.string()), "--")
983 .with_args(m_program_args)
984 .with_env(m_program_env)
985 .spawn()->wait().value_or(125);
986
987 // Failed syscall and errno
988 int syscall_nr = -1;
989 int errno_nr = -1;
990
991 // Read possible errors if any
992 log_if(read(pipe_error[0], &syscall_nr, sizeof(syscall_nr)) < 0, "D::Could not read syscall error, success?");
993 log_if(read(pipe_error[0], &errno_nr, sizeof(errno_nr)) < 0, "D::Could not read errno number, success?");
994
995 // Close pipe
996 close(pipe_error[0]);
997 close(pipe_error[1]);
998
999 // Return value and possible errors
1000 return bwrap_run_ret_t{.code=code, .syscall_nr=syscall_nr, .errno_nr=errno_nr};
1001}
1002
1003} // namespace ns_bwrap
1004
1005/* vim: set expandtab fdm=marker ts=2 sw=2 tw=100 et :*/
Manages bubblewrap (bwrap) containerization.
Definition bwrap.hpp:228
Bwrap & bind_usb()
Binds the usb devices from the host to the guest.
Definition bwrap.hpp:761
Bwrap & with_binds(ns_db::ns_bind::Binds const &binds)
Allows to specify custom bindings from a json file.
Definition bwrap.hpp:497
Bwrap & bind_xorg()
Binds the xorg socket from the host to the guest.
Definition bwrap.hpp:649
Bwrap & bind_dbus_user()
Binds the user session bus from the host to the guest.
Definition bwrap.hpp:678
Bwrap & bind_network()
Binds the network configuration from the host to the guest.
Definition bwrap.hpp:780
Bwrap(ns_proxy::Logs logs, ns_proxy::User user, fs::path const &path_dir_root, fs::path const &path_file_program, std::vector< std::string > const &program_args, std::vector< std::string > const &program_env)
Construct a new Bwrap object.
Definition bwrap.hpp:304
Bwrap & bind_home()
Includes a binding from the host $HOME to the guest.
Definition bwrap.hpp:556
Bwrap & bind_shm()
Binds the /dev/shm directory to the containter.
Definition bwrap.hpp:797
void set_overlay(ns_proxy::Overlay const &overlay)
Enable bwrap's overlay filesystem.
Definition bwrap.hpp:546
Bwrap & bind_input()
Binds the input devices from the host to the guest.
Definition bwrap.hpp:746
Bwrap & bind_audio()
Binds the host's audio sockets and devices to the guest.
Definition bwrap.hpp:592
Bwrap & bind_dev()
Binds the /dev directory to the containter.
Definition bwrap.hpp:845
Bwrap & bind_media()
Binds the host's media directories to the guest.
Definition bwrap.hpp:576
Bwrap & with_bind_gpu(fs::path const &path_dir_root_guest, fs::path const &path_dir_root_host)
Binds the gpu device from the host to the guest.
Definition bwrap.hpp:860
~Bwrap()
Destroy the Bwrap:: Bwrap object.
Definition bwrap.hpp:355
Bwrap & bind_optical()
Binds optical devices to the container.
Definition bwrap.hpp:813
Bwrap & bind_udev()
binds the udev folder from the host to the guest
Definition bwrap.hpp:732
Bwrap & with_bind(fs::path const &src, fs::path const &dst)
Includes a binding from the host to the guest.
Definition bwrap.hpp:522
Bwrap & bind_dbus_system()
Binds the syst from the host to the guest.
Definition bwrap.hpp:718
Bwrap & with_bind_ro(fs::path const &src, fs::path const &dst)
Includes a read-only binding from the host to the guest.
Definition bwrap.hpp:535
Bwrap & bind_wayland()
Binds the wayland socket from the host to the guest.
Definition bwrap.hpp:621
Value< bwrap_run_ret_t > run(Permissions const &permissions, Unshares const &unshares, fs::path const &path_file_daemon, ns_db::ns_portal::ns_dispatcher::Dispatcher const &arg1_dispatcher, ns_db::ns_portal::ns_daemon::Daemon const &arg1_daemon, ns_db::ns_portal::ns_daemon::ns_log::Logs const &arg2_daemon)
Runs the command in the bubblewrap sandbox.
Definition bwrap.hpp:878
Manages FlatImage permissions stored in reserved space.
bool contains(Permission const &permission) const noexcept
Checks if a specific permission is enabled.
Manages FlatImage unshare options stored in reserved space.
Definition unshare.hpp:134
bool contains(Unshare const &unshare) const noexcept
Checks if a specific unshare option is enabled.
Definition unshare.hpp:214
std::unique_ptr< Child > spawn()
Spawns (forks) the child process and begins execution.
Subprocess & with_env(Args &&... args)
Includes environment variables with the format 'NAME=VALUE' in the environment.
Subprocess & with_args(Args &&... args)
Arguments forwarded as the process' arguments.
Defines a class that manages FlatImage's portal configuration.
Used to manage the bindings from the host to the sandbox.
Defines a class that manages FlatImage's portal dispatcher configuration.
Enhanced error handling framework built on std::expected.
constexpr auto __expected_fn
Lambda helper for Pop macro error returns.
Definition expected.hpp:147
A library for manipulating environment variables.
A library for file logging.
#define logger(fmt,...)
Compile-time log level dispatch macro with automatic location capture.
Definition log.hpp:682
Simplified macros for common control flow patterns with optional logging.
Bubblewrap proxy types and user configuration.
Bubblewrap sandboxing integration.
std::vector< fs::path > get_mounted_layers(fs::path const &path_dir_layers)
Get the mounted layers object.
Definition bwrap.hpp:202
Value< void > bwrap_clean(fs::path const &path_dir_work)
Cleans up the bwrap work directory.
Definition bwrap.hpp:175
Value< std::string > serialize(Logs const &logs) noexcept
Serializes a Logs class into a json string.
Definition daemon.hpp:122
Value< std::string > serialize(Daemon const &daemon) noexcept
Serializes a Daemon class into a json string.
Definition daemon.hpp:210
Value< std::string > serialize(Dispatcher const &dispatcher) noexcept
Serializes a Dispatcher class into a json string.
Value< std::string > expand(ns_concept::StringRepresentable auto &&var)
Performs variable expansion analogous to a POSIX shell.
Definition env.hpp:96
Value< std::string > get_expected(std::string_view name)
Get the value of an environment variable.
Definition env.hpp:65
Value< fs::path > search_path(std::string const &query)
Search the directories in the PATH variable for the given input file name.
Definition env.hpp:150
void set(T &&name, U &&value, Replace replace)
Sets an environment variable.
Definition env.hpp:52
Stream
Stream redirection modes for child process stdio.
void push_back(R &r, Args &&... args) noexcept
Helper to push back multiple elements into an input iterator.
Definition vector.hpp:54
void push_front(R &r, Args &&... args) noexcept
Helper to prepend multiple elements to the front of a container.
Definition vector.hpp:69
Manages the permissions reserved space.
Manages the unshare namespace options reserved space.
Enhanced expected type with integrated logging capabilities.
Definition expected.hpp:44
A pair of uid and gid mode_t values.
Definition bwrap.hpp:64
Log files used by bwrap.
Definition bwrap.hpp:84
Bwrap's native overlay related options.
Definition bwrap.hpp:74
The representation of a user in bubblewrap.
Definition bwrap.hpp:96
Value< void > write_bashrc(fs::path const &path_file_bashrc, std::string_view ps1)
Writes the bashrc file and returns its path.
Definition bwrap.hpp:143
User(UserData const &data)
Construct a new User object with the provided user data.
Definition bwrap.hpp:121
Value< void > write_passwd(fs::path const &path_file_passwd)
Writes the passwd entry to the provided file path.
Definition bwrap.hpp:130
Container for multiple bind mount configurations.
Definition bind.hpp:88
std::vector< Bind > const & get() const
Retrieves the current list of bind mounts.
Definition bind.hpp:115
A library to spawn sub-processes in linux.
Vector helpers.