Repositories / jai.git

jai.git

Clone (read-only): git clone http://git.guha-anderson.com/git/jai.git

Branch

Compatibility for Ubuntu 24.04

Author
Arjun Guha <a.guha@northeastern.edu>
Date
2026-03-28 17:37:50 -0400
Commit
fcd63d2bc9524458a9f8be9e3101c94f42bfcc72
complete.cc
index 9f09b5a..36e249c 100644
--- a/complete.cc
+++ b/complete.cc
@@ -1,8 +1,8 @@
 #include "jai.h"
+#include "print_compat.h"
 
 #include <cassert>
 #include <dirent.h>
-#include <print>
 
 using Completions = Options::Completions;
 
@@ -87,14 +87,14 @@ Config::complete(Completions c)
 {
   using enum Completions::Disposition;
   if (c.kind >= 0) {
-    std::println("_command_offset {}", c.kind - 1);
+    print_compat::println("_command_offset {}", c.kind - 1);
     return 0;
   }
   if (c.kind == kNoCompletions)
     return 1;
   else if (c.kind == kRawCompletions) {
     for (const auto &v : c.vals)
-      std::println("{}", v);
+      print_compat::println("{}", v);
     return 0;
   }
   assert(c.kind == kArgCompletions);
@@ -124,7 +124,7 @@ Config::complete(Completions c)
     complete_env(cs, false);
 
   for (const auto comp : cs.out_)
-    std::println("{}", comp);
+    print_compat::println("{}", comp);
 
   return 0;
 }
defer.h
index 879085b..4952f02 100644
--- a/defer.h
+++ b/defer.h
@@ -40,8 +40,10 @@ struct RaiiHelper {
   }
 
   explicit operator bool() const noexcept { return t_ != Empty; }
-  decltype(auto) operator*(this auto &&self) noexcept { return (self.t_); }
-  auto addr(this auto &&self) noexcept { return std::addressof(self); }
+  T &operator*() & noexcept { return t_; }
+  const T &operator*() const & noexcept { return t_; }
+  T &&operator*() && noexcept { return std::move(t_); }
+  const T &&operator*() const && noexcept { return std::move(t_); }
 
   // For legacy libraries that want a T**, return that type for &
   template<std::same_as<T> U = T> requires std::is_pointer_v<U>
@@ -55,7 +57,7 @@ struct RaiiHelper {
   {
     return t_;
   }
-  decltype(auto) operator->(this auto &&self) noexcept { return (self.t_); }
+  T operator->() const noexcept requires std::is_pointer_v<T> { return t_; }
 
   T release() noexcept { return std::exchange(t_, Empty); }
 
fs.h
index 3c5aaac..ef45de6 100644
--- a/fs.h
+++ b/fs.h
@@ -57,6 +57,10 @@ struct PathLess {
 using PathSet = std::set<path, PathLess>;
 using PathMultiset = std::multiset<path, PathLess>;
 
+struct BoolValidator {
+  template<typename T> bool operator()(const T &v) const { return bool(v); }
+};
+
 // Return a range for a subtree rooted at root.  root itself will be
 // returned only if it does not contain a trailing slash.
 inline auto
@@ -184,7 +188,7 @@ Fd open_lockfile(int dfd, const path &file);
 //      return std::move(*r);
 //    // now you continue to hold the lock until r is destroyed
 template<typename Get,
-         typename Validate = decltype([](auto &&v) { return bool(v); })>
+         typename Validate = BoolValidator>
 std::expected<std::decay_t<std::invoke_result_t<Get>>, Defer>
 lock_or_validate(int dfd, path lockfile, Get get, Validate validate = {})
     requires requires {
jai.cc
index eaa9e01..a1f393b 100644
--- a/jai.cc
+++ b/jai.cc
@@ -1,4 +1,5 @@
 #include "jai.h"
+#include "print_compat.h"
 
 #include <cassert>
 #include <csignal>
@@ -6,7 +7,6 @@
 #include <cstring>
 #include <filesystem>
 #include <linux/prctl.h>
-#include <print>
 
 #include <acl/libacl.h>
 #include <poll.h>
@@ -697,10 +697,10 @@ Config::make_env()
     setenv_.try_emplace(std::string(sv), *e);
   }
 
-  std::vector<const char *> ret(std::from_range,
-                                setenv_ | std::views::transform([](auto &kv) {
-                                  return kv.second.c_str();
-                                }));
+  std::vector<const char *> ret;
+  ret.reserve(setenv_.size() + 1);
+  for (auto &kv : setenv_ | std::views::values)
+    ret.push_back(kv.c_str());
   ret.push_back(nullptr);
   return ret;
 }
@@ -1111,18 +1111,18 @@ std::string option_help;
 usage(int status)
 {
   if (status)
-    std::println(stderr, "Try {} --help for more information.",
-                 prog.filename().string());
+    print_compat::println(stderr, "Try {} --help for more information.",
+                          prog.filename().string());
   else
-    std::print(stdout, "usage: {0} [OPTIONS] [CMD [ARG...]]\n{1}",
-               prog.filename().string(), option_help);
+    print_compat::print(stdout, "usage: {0} [OPTIONS] [CMD [ARG...]]\n{1}",
+                        prog.filename().string(), option_help);
   exit(status);
 }
 
 [[noreturn]] static void
 version()
 {
-  std::println(R"({}
+  print_compat::println(R"({}
 {}
 Untrusted user for strict mode: {}
 
@@ -1130,7 +1130,7 @@ Copyright (C) 2026 David Mazieres
 This program comes with NO WARRANTY, to the extent permitted by law.
 You may redistribute it under the terms of the GNU General Public License
 version 3 or later; see the file named COPYING for details.)",
-               PACKAGE_STRING, PACKAGE_URL, kUntrustedUser);
+                        PACKAGE_STRING, PACKAGE_URL, kUntrustedUser);
   exit(0);
 }
 
@@ -1176,7 +1176,8 @@ The default is CMD.conf if it exists, otherwise default.conf)",
 
   std::vector<char *> cmd;
   try {
-    cmd.assign_range(opts->parse_argv(argc, argv));
+    auto args = opts->parse_argv(argc, argv);
+    cmd.assign(args.begin(), args.end());
   } catch (Options::Error &e) {
     warn("{}", e.what());
     usage(2);
@@ -1192,9 +1193,10 @@ The default is CMD.conf if it exists, otherwise default.conf)",
   if (opt_init) {
     ensure_file(conf.storage(), "default.jail", default_jail, 0600,
                 create_warn);
-    std::println("You can edit the configuration defaults in {}/.defaults.",
-                 conf.homejaipath_.string());
-    std::println(
+    print_compat::println(
+        "You can edit the configuration defaults in {}/.defaults.",
+        conf.homejaipath_.string());
+    print_compat::println(
         "Run {} --print-defaults to see the original contents of that file.",
         prog.filename().string());
     return 0;
@@ -1202,7 +1204,8 @@ The default is CMD.conf if it exists, otherwise default.conf)",
 
   if (opt_u) {
     if (!conf.grant_cwd_ || !conf.grant_directories_.empty() || !cmd.empty()) {
-      std::println(stderr, "-u is not compatible with -d, -D, or a command");
+      print_compat::println(stderr,
+                            "-u is not compatible with -d, -D, or a command");
       usage(2);
     }
     restore.reset();
options.h
index d1fed5a..b1d03b5 100644
--- a/options.h
+++ b/options.h
@@ -87,6 +87,7 @@
 #pragma once
 
 #include "err.h"
+#include "print_compat.h"
 
 #include <cassert>
 #include <charconv>
@@ -96,7 +97,6 @@
 #include <initializer_list>
 #include <map>
 #include <memory>
-#include <print>
 #include <span>
 #include <utility>
 #include <vector>
print_compat.h
new file mode 100644
index 0000000..0ffef26
--- /dev/null
+++ b/print_compat.h
@@ -0,0 +1,39 @@
+// -*-C++-*-
+
+#pragma once
+
+#include <cstdio>
+#include <format>
+#include <string>
+#include <utility>
+
+namespace print_compat {
+
+template<typename... Args>
+void print(FILE *stream, std::format_string<Args...> fmt, Args &&...args)
+{
+  std::string out = std::format(fmt, std::forward<Args>(args)...);
+  std::fwrite(out.data(), 1, out.size(), stream);
+}
+
+template<typename... Args>
+void println(FILE *stream, std::format_string<Args...> fmt, Args &&...args)
+{
+  std::string out = std::format(fmt, std::forward<Args>(args)...);
+  out.push_back('\n');
+  std::fwrite(out.data(), 1, out.size(), stream);
+}
+
+template<typename... Args>
+void print(std::format_string<Args...> fmt, Args &&...args)
+{
+  print(stdout, fmt, std::forward<Args>(args)...);
+}
+
+template<typename... Args>
+void println(std::format_string<Args...> fmt, Args &&...args)
+{
+  println(stdout, fmt, std::forward<Args>(args)...);
+}
+
+} // namespace print_compat