this repo has no description

vendor

+22
vendor/void/.github/workflows/test.yml
···
+
name: Void Test
+
on:
+
- push
+
- pull_request
+
permissions: read-all
+
jobs:
+
build:
+
strategy:
+
fail-fast: false
+
runs-on: ubuntu-latest
+
steps:
+
- name: Checkout tree
+
uses: actions/checkout@v4
+
+
- name: Set-up OCaml
+
uses: ocaml/setup-ocaml@v3
+
with:
+
ocaml-compiler: 5
+
+
- run: opam install . --deps-only --with-test
+
- run: opam exec -- dune build
+
- run: sudo ./_build/default/examples/hey.exe
+2
vendor/void/.gitignore
···
+
_build
+
+1
vendor/void/.ocamlformat
···
+
version=0.27.0
+14
vendor/void/LICENSE.md
···
+
Copyright (C) 2024 Patrick Ferris
+
+
Permission to use, copy, modify, and distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.
+
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+
Much of the codebase is borrowed directly from Eio, with the following license:
+
+
Copyright (C) 2021 Anil Madhavapeddy
+
Copyright (C) 2022 Thomas Leonard
+
+
Permission to use, copy, modify, and distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.
+
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+5
vendor/void/README.md
···
+
Void
+
----
+
+
Void processes for Eio.
+
+29
vendor/void/dune-project
···
+
(lang dune 3.15)
+
+
(name void)
+
+
(generate_opam_files true)
+
+
(source
+
(github patricoferris/void))
+
+
(authors "Patrick Ferris")
+
+
(maintainers "Patrick Ferris")
+
+
(license MIT)
+
+
(documentation https://ocaml.org/p/void)
+
+
(package
+
(name void)
+
(synopsis "Void Processes in Eio_linux")
+
(description "A longer description")
+
(depends
+
ocaml
+
dune
+
eio_posix)
+
(tags
+
(linux process spawn)))
+
+
; See the complete stanza docs at https://dune.readthedocs.io/en/stable/dune-files.html#dune-project
+69
vendor/void/examples/alpine.ml
···
+
open Eio.Std
+
+
let ( / ) = Eio.Path.( / )
+
+
let test_data fs data =
+
let tempdir = Filename.temp_dir "void-" "-alpine" in
+
Eio.Path.(save ~create:(`If_missing 0o644) (fs / tempdir / "data.txt") data);
+
Eio.traceln "Test data in %s" tempdir;
+
tempdir
+
+
let get_alpine_image ~fs ~proc =
+
let tmpdir = Filename.temp_dir "void" "alpine" in
+
Eio.traceln "Extracting alpine to %s..." tmpdir;
+
let container_id =
+
Eio.Process.parse_out proc Eio.Buf_read.take_all
+
[ "docker"; "run"; "-d"; "alpine" ]
+
|> String.trim
+
in
+
Eio.traceln "Container %s" container_id;
+
let () =
+
Eio.Process.run proc
+
[
+
"docker";
+
"export";
+
container_id;
+
"-o";
+
Filename.concat tmpdir "alpine.tar.gz";
+
]
+
in
+
Eio.traceln "Untarring...";
+
Eio.Path.mkdir ~perm:0o777 (fs / tmpdir / "rootfs");
+
let () =
+
Eio.Process.run proc
+
[
+
"tar";
+
"-xf";
+
Filename.concat tmpdir "alpine.tar.gz";
+
"-C";
+
Filename.concat tmpdir "rootfs";
+
]
+
in
+
Filename.concat tmpdir "rootfs"
+
+
(* This example read-only mounts a copy of busybox
+
into the root-filesystem of the process. *)
+
let () =
+
Eio_posix.run @@ fun env ->
+
Switch.run @@ fun sw ->
+
let fs = env#fs in
+
let proc = env#process_mgr in
+
let alpine_img = get_alpine_image ~fs ~proc in
+
let mount_src = test_data fs "Hello, World!" in
+
let open Void in
+
let args =
+
let l = Array.length Sys.argv in
+
if l <= 1 then
+
[
+
"/bin/ash"; "-c"; "/bin/echo hello > /hello.txt && /bin/cat /hello.txt";
+
]
+
else Array.sub Sys.argv 1 (l - 1) |> Array.to_list
+
in
+
let void =
+
empty |> rootfs ~mode:RW alpine_img
+
|> mount ~mode:R ~src:mount_src ~tgt:"data"
+
|> exec args
+
in
+
let t = Void.spawn ~sw void in
+
let status = Promise.await (Void.exit_status t) in
+
Eio.traceln "Status: %s" (Void.exit_status_to_string status)
+55
vendor/void/examples/curl.ml
···
+
open Eio.Std
+
+
let ( / ) = Eio.Path.( / )
+
+
let get_alpine_image ~fs ~proc =
+
let tmpdir = Filename.temp_dir "void-" "-alpine-curl" in
+
Eio.traceln "Extracting alpine/curl to %s..." tmpdir;
+
let container_id =
+
Eio.Process.parse_out proc Eio.Buf_read.take_all
+
[ "docker"; "run"; "-d"; "alpine/curl" ]
+
|> String.trim
+
in
+
Eio.traceln "Container %s" container_id;
+
let () =
+
Eio.Process.run proc
+
[
+
"docker";
+
"export";
+
container_id;
+
"-o";
+
Filename.concat tmpdir "alpine-curl.tar.gz";
+
]
+
in
+
Eio.traceln "Untarring...";
+
Eio.Path.mkdir ~perm:0o777 (fs / tmpdir / "rootfs");
+
let () =
+
Eio.Process.run proc
+
[
+
"tar";
+
"-xf";
+
Filename.concat tmpdir "alpine-curl.tar.gz";
+
"-C";
+
Filename.concat tmpdir "rootfs";
+
]
+
in
+
Filename.concat tmpdir "rootfs"
+
+
(* This example read-only mounts a copy of busybox
+
into the root-filesystem of the process. *)
+
let () =
+
Eio_posix.run @@ fun env ->
+
Switch.run @@ fun sw ->
+
let fs = env#fs in
+
let proc = env#process_mgr in
+
let alpine_img = get_alpine_image ~fs ~proc in
+
let open Void in
+
let args =
+
let l = Array.length Sys.argv in
+
if l <= 1 then [ "/bin/ls"; "-l" ]
+
else Array.sub Sys.argv 1 (l - 1) |> Array.to_list
+
in
+
let void = empty |> rootfs ~mode:R alpine_img |> exec args in
+
let t = Void.spawn ~sw void in
+
let status = Promise.await (Void.exit_status t) in
+
Eio.traceln "Status: %s" (Void.exit_status_to_string status)
+3
vendor/void/examples/dune
···
+
(executables
+
(names hey curl alpine)
+
(libraries void eio_posix))
+11
vendor/void/examples/empty.ml
···
+
open Eio.Std
+
+
let () =
+
Eio_linux.run @@ fun _env ->
+
Switch.run @@ fun sw ->
+
let open Void in
+
let void = empty |> exec [] in
+
Eio.traceln "Spawning the empty void...";
+
let t = Void.spawn ~sw void in
+
let status = Promise.await (Void.exit_status t) in
+
Eio.traceln "Status: %s" (Void.exit_status_to_string status)
vendor/void/examples/hey

This is a binary file and will not be displayed.

+11
vendor/void/examples/hey.c
···
+
// Based off of Docker's hello-world
+
// This is standalone program
+
#include <sys/syscall.h>
+
#include <unistd.h>
+
+
const char message[] = "Hello from the Void!\n";
+
+
int main() {
+
syscall(SYS_write, STDOUT_FILENO, message, sizeof(message) - 1);
+
return 0;
+
}
+22
vendor/void/examples/hey.ml
···
+
open Eio.Std
+
+
let ( / ) = Eio.Path.( / )
+
+
let copy_hey fs =
+
let temp_dir = Filename.temp_dir "void-" "-world" in
+
let hey = Eio.Path.load (fs / "./examples/hey") in
+
Eio.Path.save ~create:(`If_missing 0o755) (fs / temp_dir / "hey") hey;
+
temp_dir
+
+
(* This mounts the hello-world into the void process. *)
+
let () =
+
Eio_posix.run @@ fun env ->
+
Switch.run @@ fun sw ->
+
let hey_dir = copy_hey env#fs in
+
let void =
+
let open Void in
+
empty |> mount ~mode:R ~src:hey_dir ~tgt:"say" |> exec [ "/say/hey" ]
+
in
+
let t = Void.spawn ~sw void in
+
let status = Promise.await (Void.exit_status t) in
+
Eio.traceln "Void process: %s" (Void.exit_status_to_string status)
+13
vendor/void/src/dune
···
+
(rule
+
(targets config.ml)
+
(action
+
(run ./include/discover.exe)))
+
+
(library
+
(name void)
+
(public_name void)
+
(foreign_stubs
+
(language c)
+
(flags -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64)
+
(names void_action))
+
(libraries eio_posix eio.unix))
+57
vendor/void/src/include/discover.ml
···
+
module C = Configurator.V1
+
+
let () =
+
C.main ~name:"discover" (fun c ->
+
let defs, mount_flags =
+
C.C_define.import c ~c_flags:[ "-D_GNU_SOURCE" ]
+
~includes:[ "linux/sched.h"; "sys/mount.h" ]
+
C.C_define.Type.
+
[
+
(* Clone3 Flags *)
+
("CLONE_PIDFD", Int);
+
("CLONE_NEWPID", Int);
+
("CLONE_NEWCGROUP", Int);
+
("CLONE_NEWNS", Int);
+
("CLONE_NEWIPC", Int);
+
("CLONE_NEWNET", Int);
+
("CLONE_NEWTIME", Int);
+
("CLONE_NEWUSER", Int);
+
("CLONE_NEWUTS", Int);
+
(* Mount Flags *)
+
("MS_RDONLY", Int);
+
("MS_REMOUNT", Int);
+
("MS_BIND", Int);
+
("MS_SHARED", Int);
+
]
+
|> List.fold_left
+
(fun (cls, mnts) -> function
+
| name, C.C_define.Value.Int v ->
+
let t = (String.lowercase_ascii name, v) in
+
if String.starts_with ~prefix:"CLONE" name then
+
(t :: cls, mnts)
+
else (cls, t :: mnts)
+
| _ -> assert false)
+
([], [])
+
in
+
let sigs vs =
+
List.map (fun (name, _) -> Printf.sprintf " val %s : t" name) vs
+
in
+
let structs vs =
+
List.map (fun (name, v) -> Printf.sprintf " let %s = 0x%x" name v) vs
+
in
+
let flags_nspace =
+
List.filter (String.starts_with ~prefix:"clone_new") (List.map fst defs)
+
in
+
let mount =
+
[ "module Mount_flags : sig"; " type t = int" ]
+
@ sigs mount_flags
+
@ [ "end = struct"; " type t = int" ]
+
@ structs mount_flags @ [ "end" ]
+
in
+
C.Flags.write_lines "config.ml"
+
([ "module Clone_flags : sig"; " type t = int" ]
+
@ sigs defs @ [ "val all : t list" ]
+
@ [ "end = struct"; " type t = int" ]
+
@ structs defs
+
@ [ "let all = [" ^ String.concat ";" flags_nspace ^ "]" ]
+
@ [ "end" ] @ mount))
+4
vendor/void/src/include/dune
···
+
(executable
+
(name discover)
+
(modules discover)
+
(libraries dune-configurator))
+202
vendor/void/src/void.ml
···
+
open Eio.Std
+
module Process = Eio_posix.Low_level.Process
+
module Trace = Eio.Private.Trace
+
module Fd = Eio_unix.Fd
+
module Rcfd = Eio_unix.Private.Rcfd
+
module Fork_action = Eio_unix.Private.Fork_action
+
+
type mode = R | RW
+
+
type void = {
+
args : string list;
+
rootfs : (string * mode) option;
+
mounts : mount list;
+
}
+
+
and mount = { src : string; tgt : string; mode : int [@warning "-69"] }
+
+
(* Actions for namespacing *)
+
module Mount = struct
+
module Flags = struct
+
include Config.Mount_flags
+
+
let empty : t = 0
+
let ( + ) = ( lor )
+
end
+
+
module Types = struct
+
type t = string
+
+
let btrfs = "btrfs"
+
let ext4 = "ext4"
+
let auto = "auto"
+
end
+
end
+
+
external action_mount : unit -> Fork_action.fork_fn = "void_fork_mount"
+
+
let action_mount = action_mount ()
+
+
let _mount ~(src : string) ~(target : string) (type_ : Mount.Types.t)
+
(flags : Mount.Flags.t) =
+
Fork_action.
+
{ run = (fun k -> k (Obj.repr (action_mount, src, target, type_, flags))) }
+
+
external action_pivot_root : unit -> Fork_action.fork_fn
+
= "void_fork_pivot_root"
+
+
let action_pivot_root = action_pivot_root ()
+
+
let pivot_root (new_root : string) (new_root_flags : Mount.Flags.t)
+
(tmpfs : bool) (mounts : mount list) =
+
Fork_action.
+
{
+
run =
+
(fun k ->
+
k
+
(Obj.repr
+
(action_pivot_root, new_root, new_root_flags, tmpfs, mounts)));
+
}
+
+
external action_map_uid_gid : unit -> Fork_action.fork_fn
+
= "void_fork_map_uid_gid"
+
+
let action_map_uid_gid = action_map_uid_gid ()
+
+
let map_uid_gid ~uid ~gid =
+
Fork_action.{ run = (fun k -> k (Obj.repr (action_map_uid_gid, uid, gid))) }
+
+
module Flags = struct
+
include Config.Clone_flags
+
+
let ( + ) = ( lor )
+
end
+
+
external eio_spawn :
+
Unix.file_descr ->
+
Flags.t ->
+
Eio_unix.Private.Fork_action.c_action list ->
+
int * Unix.file_descr = "caml_void_clone3"
+
+
type t = {
+
pid : int;
+
pid_fd : Fd.t;
+
exit_status : Unix.process_status Promise.t;
+
}
+
+
let exit_status t = t.exit_status
+
let pid t = t.pid
+
+
(* Read a (typically short) error message from a child process. *)
+
let rec read_response fd =
+
let buf = Cstruct.create 256 in
+
match Eio_posix.Low_level.readv fd [| buf |] with
+
| 0 | (exception End_of_file) -> ""
+
| len -> Cstruct.to_string buf ~len ^ read_response fd
+
+
let void_flags = List.fold_left Flags.( + ) 0 Flags.all
+
+
type path = string
+
+
let empty = { args = []; rootfs = None; mounts = [] }
+
+
let actions v : Fork_action.t list =
+
let root, tmpfs, root_mode =
+
match v.rootfs with
+
| None -> (Filename.temp_dir "void-" "-tmpdir", true, R)
+
| Some (s, m) -> (s, false, m)
+
in
+
let args = match v.args with [] -> failwith "No exec" | args -> args in
+
let e =
+
Process.Fork_action.execve (List.hd args) ~env:[||]
+
~argv:(Array.of_list args)
+
in
+
(* Process mount point points *)
+
let mounts =
+
List.map
+
(fun mnt ->
+
let src = Filename.concat "/.old_root" mnt.src in
+
let tgt = Filename.concat "/" mnt.tgt in
+
{ mnt with tgt; src })
+
v.mounts
+
in
+
let root_flags =
+
if root_mode = R then Mount.Flags.ms_rdonly else Mount.Flags.empty
+
in
+
let mounts = pivot_root root root_flags tmpfs mounts in
+
let uid, gid = Unix.(getuid (), getgid ()) in
+
let user_namespace = map_uid_gid ~uid ~gid in
+
[ user_namespace; mounts; e ]
+
+
let rootfs ~mode path v = { v with rootfs = Some (path, mode) }
+
let exec args v = { v with args }
+
+
let mount ~mode ~src ~tgt v =
+
let mode = if mode = R then Mount.Flags.ms_rdonly else Mount.Flags.empty in
+
{ v with mounts = { src; tgt; mode } :: v.mounts }
+
+
(* From eio_linux/eio_posix *)
+
let with_pipe fn =
+
Switch.run @@ fun sw ->
+
let r, w = Eio_posix.Low_level.pipe ~sw in
+
fn r w
+
+
external pidfd_send_signal : Unix.file_descr -> int -> unit
+
= "caml_void_pidfd_send_signal"
+
+
let signal t signum =
+
Fd.use t.pid_fd ~if_closed:Fun.id @@ fun pid_fd ->
+
pidfd_send_signal pid_fd signum
+
+
let rec waitpid pid =
+
match Unix.waitpid [] pid with
+
| p, status ->
+
assert (p = pid);
+
status
+
| exception Unix.Unix_error (EINTR, _, _) -> waitpid pid
+
+
let spawn ~sw v =
+
with_pipe @@ fun errors_r errors_w ->
+
Eio_unix.Private.Fork_action.with_actions (actions v) @@ fun c_actions ->
+
Switch.check sw;
+
let exit_status, set_exit_status = Promise.create () in
+
let t =
+
let pid, pid_fd =
+
Fd.use_exn "errors-w" errors_w @@ fun errors_w ->
+
Eio.Private.Trace.with_span "spawn" @@ fun () ->
+
let flags = Flags.(clone_pidfd + void_flags) in
+
eio_spawn errors_w flags c_actions
+
in
+
let pid_fd = Fd.of_unix ~sw ~seekable:false ~close_unix:true pid_fd in
+
{ pid; pid_fd; exit_status }
+
in
+
Fd.close errors_w;
+
Fiber.fork_daemon ~sw (fun () ->
+
let cleanup () =
+
Fd.close t.pid_fd;
+
Promise.resolve set_exit_status (waitpid t.pid);
+
`Stop_daemon
+
in
+
match Eio_posix.Low_level.await_readable "void_spawn" t.pid_fd with
+
| () -> Eio.Cancel.protect cleanup
+
| exception Eio.Cancel.Cancelled _ ->
+
Eio.Cancel.protect (fun () ->
+
Printf.eprintf "Cancelled?";
+
signal t Sys.sigkill;
+
Eio_posix.Low_level.await_readable "void_spawn" t.pid_fd;
+
cleanup ()));
+
(* Check for errors starting the process. *)
+
match read_response errors_r with
+
| "" -> t (* Success! Execing the child closed [errors_w] and we got EOF. *)
+
| err -> failwith err
+
+
let to_eio_status t =
+
match t with
+
| Unix.WEXITED i -> `Exited i
+
| Unix.WSIGNALED i -> `Signaled i
+
| Unix.WSTOPPED _ -> assert false
+
+
let exit_status_to_string = function
+
| Unix.WEXITED n -> Printf.sprintf "Exited with %i" n
+
| Unix.WSTOPPED n -> Printf.sprintf "Stopped with %i" n
+
| Unix.WSIGNALED n -> Printf.sprintf "Signalled with %i" n
+61
vendor/void/src/void.mli
···
+
(** {1 Void}
+
+
Void is a library to provide {e void processes}. A void process is an
+
{e empty} process, one in which most global resources have been removed. As
+
a user, you can add back in precisely those pieces you need for your
+
process.
+
+
Void uses Eio's [fork_action]s to provide this mechanism, thus it is only
+
available with Eio. *)
+
+
module Mount : sig
+
module Flags : sig
+
type t = private int
+
+
val ms_remount : t
+
val ms_bind : t
+
val ms_shared : t
+
val ( + ) : t -> t -> t
+
end
+
+
module Types : sig
+
type t = private string
+
+
val btrfs : t
+
val ext4 : t
+
val auto : t
+
end
+
end
+
+
type t
+
(** A void process *)
+
+
type path = string
+
(** File paths *)
+
+
type mode = R | RW
+
(* Mounting modes *)
+
+
type void
+
(** A configuration for a void process *)
+
+
val empty : void
+
(** The empty void *)
+
+
val rootfs : mode:mode -> path -> void -> void
+
(** Add a new root filesystem *)
+
+
val mount : mode:mode -> src:path -> tgt:path -> void -> void
+
+
val exec : string list -> void -> void
+
(** Make a void configuration ready to be spawned *)
+
+
val spawn : sw:Eio.Switch.t -> void -> t
+
(** Spawn a void process *)
+
+
val pid : t -> int
+
(** The pid of a running void process *)
+
+
val exit_status : t -> Unix.process_status Eio.Promise.t
+
val exit_status_to_string : Unix.process_status -> string
+
val to_eio_status : Unix.process_status -> Eio.Process.exit_status
+399
vendor/void/src/void_action.c
···
+
#define _GNU_SOURCE
+
#define _FILE_OFFSET_BITS 64
+
#include <linux/sched.h>
+
+
#include <sys/stat.h>
+
#include <sys/types.h>
+
#include <sys/eventfd.h>
+
#if __GLIBC__ > 2 || __GLIBC_MINOR__ > 24
+
#include <sys/random.h>
+
#endif
+
#include <sys/syscall.h>
+
#include <sys/wait.h>
+
#include <sys/mount.h>
+
#include <limits.h>
+
#include <errno.h>
+
#include <dirent.h>
+
#include <fcntl.h>
+
#include <signal.h>
+
#include <unistd.h>
+
#include <string.h>
+
+
+
#include <caml/mlvalues.h>
+
#include <caml/unixsupport.h>
+
#include <caml/memory.h>
+
#include <caml/custom.h>
+
#include <caml/fail.h>
+
+
// From Eio
+
#include <include/fork_action.h>
+
+
#ifndef SYS_pidfd_send_signal
+
#define SYS_pidfd_send_signal 424
+
#endif
+
+
// struct clone_args isn't defined in linux-lts headers, so define it here
+
// Note that this struct is versioned by size. See linux/sched.h for details
+
struct caml_void_clone_args
+
{
+
uint64_t flags;
+
uint64_t pidfd;
+
uint64_t child_tid;
+
uint64_t parent_tid;
+
uint64_t exit_signal;
+
uint64_t stack;
+
uint64_t stack_size;
+
uint64_t tls;
+
};
+
+
static int
+
pidfd_send_signal (int pidfd, int sig, siginfo_t *info, unsigned int flags)
+
{
+
return syscall (SYS_pidfd_send_signal, pidfd, sig, info, flags);
+
}
+
+
CAMLprim value
+
caml_void_pidfd_send_signal (value v_pidfd, value v_signal)
+
{
+
CAMLparam0 ();
+
int res;
+
+
res =
+
pidfd_send_signal (Int_val (v_pidfd),
+
caml_convert_signal_number (Int_val (v_signal)), NULL,
+
0);
+
if (res == -1)
+
uerror ("pidfd_send_signal", Nothing);
+
CAMLreturn (Val_unit);
+
}
+
+
static pid_t
+
clone3_no_fallback (struct caml_void_clone_args *cl_args)
+
{
+
int *pidfd = (int *) (uintptr_t) cl_args->pidfd;
+
pid_t child_pid =
+
syscall (SYS_clone3, cl_args, sizeof (struct caml_void_clone_args));
+
+
if (child_pid >= 0)
+
return child_pid; /* Success! */
+
+
if (errno != ENOSYS && errno != EPERM)
+
{
+
uerror ("clone3", Nothing); /* Unknown error */
+
}
+
+
uerror ("clone3", Nothing);
+
}
+
+
CAMLprim value
+
caml_void_clone3 (value v_errors, value v_flags, value v_actions)
+
{
+
CAMLparam1 (v_actions);
+
CAMLlocal1 (v_result);
+
pid_t child_pid;
+
int pidfd = -1; /* Is automatically close-on-exec */
+
+
struct caml_void_clone_args cl_args = {
+
.flags = Int_val (v_flags),
+
.pidfd = (uintptr_t) & pidfd,
+
.exit_signal = SIGCHLD, /* Needed for wait4 to work if we exit before exec */
+
.stack = (uintptr_t) NULL, /* Use copy-on-write parent stack */
+
.stack_size = 0,
+
};
+
+
child_pid = clone3_no_fallback (&cl_args);
+
if (child_pid == 0)
+
{
+
/* Run child actions (doesn't return) */
+
eio_unix_run_fork_actions (Int_val (v_errors), v_actions);
+
}
+
+
v_result = caml_alloc_tuple (2);
+
Store_field (v_result, 0, Val_long (child_pid));
+
Store_field (v_result, 1, Val_int (pidfd));
+
+
CAMLreturn (v_result);
+
}
+
+
+
// Actions
+
+
// MOUNT/UNMOUNT
+
static void
+
action_mount (int errors, value v_config)
+
{
+
value v_src = Field (v_config, 1);
+
value v_tgt = Field (v_config, 2);
+
value v_type = Field (v_config, 3);
+
value v_flags = Field (v_config, 4);
+
+
int r;
+
+
r =
+
mount (String_val (v_src), String_val (v_tgt), String_val (v_type),
+
Int_val (v_flags), NULL);
+
+
if (r != 0)
+
{
+
eio_unix_fork_error (errors, "mount", strerror (errno));
+
_exit (1);
+
}
+
}
+
+
CAMLprim value
+
void_fork_mount (value v_unit)
+
{
+
return Val_fork_fn (action_mount);
+
}
+
+
// Writes a single line to a file
+
static int
+
put_line (const char *filename, const char *line)
+
{
+
int fd;
+
int written;
+
+
fd = open (filename, O_WRONLY | O_CLOEXEC | O_CREAT | O_TRUNC, 0644);
+
+
if (fd < 0)
+
{
+
return fd;
+
}
+
+
written = write (fd, line, strlen (line));
+
+
close (fd);
+
+
if (written != strlen (line))
+
{
+
return -1;
+
}
+
+
return 0;
+
}
+
+
// MAP UID/GID to root
+
static void
+
action_map_uid_gid (int errors, value v_config)
+
{
+
value v_uid = Field (v_config, 1);
+
value v_gid = Field (v_config, 2);
+
int result;
+
char uid_line[30];
+
char gid_line[30];
+
+
// We map root onto the calling UID
+
snprintf (uid_line, sizeof (uid_line), "0 %i 1\n", Int_val (v_uid));
+
result = put_line ("/proc/self/uid_map", uid_line);
+
+
if (result < 0)
+
{
+
eio_unix_fork_error (errors, "map_uid_gid-uid", strerror (errno));
+
_exit (1);
+
}
+
+
/* From user_namespaces(7)
+
*
+
* Writing "deny" to the /proc/pid/setgroups file before writing to
+
* /proc/pid/gid_map will permanently disable setgroups(2) in a user
+
* namespace and allow writing to /proc/pid/gid_map without having
+
* the CAP_SETGID capability in the parent user namespace.
+
*
+
* See also: https://lwn.net/Articles/626665/ */
+
+
put_line ("/proc/self/setgroups", "deny\n");
+
+
if (result < 0)
+
{
+
eio_unix_fork_error (errors, "map_uid_gid-setgroups", strerror (errno));
+
_exit (1);
+
}
+
+
result =
+
snprintf (gid_line, sizeof (gid_line), "0 %i 1\n", Int_val (v_gid));
+
put_line ("/proc/self/gid_map", gid_line);
+
+
if (result < 0)
+
{
+
eio_unix_fork_error (errors, "map_uid_gid-gid", strerror (errno));
+
_exit (1);
+
}
+
}
+
+
+
CAMLprim value
+
void_fork_map_uid_gid (value v_unit)
+
{
+
return Val_fork_fn (action_map_uid_gid);
+
}
+
+
// PIVOT ROOT
+
//
+
static int
+
pivot_root (const char *new_root, const char *put_old)
+
{
+
return syscall (SYS_pivot_root, new_root, put_old);
+
}
+
+
// Is there too much OCaml stuff going on here for a fork_action ?
+
static void
+
action_pivot_root (int errors, value v_config)
+
{
+
value v_new_root = Field (v_config, 1);
+
value v_root_flags = Field (v_config, 2);
+
value v_no_root = Field (v_config, 3);
+
value v_mounts = Field (v_config, 4);
+
char path[PATH_MAX];
+
char old_root_path[PATH_MAX];
+
char *new_root = String_val (v_new_root);
+
const char *put_old = ".old_root";
+
+
// From pivot_root example: We want to change the propagation type
+
// of root to be private so we can pivot it.
+
if (mount (NULL, "/", NULL, MS_REC | MS_PRIVATE, NULL) == -1)
+
{
+
eio_unix_fork_error (errors, "pivot_root-private", strerror (errno));
+
_exit (1);
+
}
+
+
// If no pivot_root was given, then we tmpfs the tmpdir we assume was passed.
+
if (Bool_val (v_no_root))
+
{
+
// Make a temporary directory... can't because it allocates ?
+
//if (mkdtemp(new_root) != NULL) {
+
// eio_unix_fork_error(errors, new_root, strerror(errno));
+
// _exit(1);
+
//}
+
+
if (mount ("tmpfs", new_root, "tmpfs", 0, NULL) <= -1)
+
{
+
eio_unix_fork_error (errors, "pivot_root-tmpfs", strerror (errno));
+
_exit (1);
+
}
+
}
+
else
+
{
+
// From pivot_root example: we check that new_root is indeed a mountpoint
+
if (mount (new_root, new_root, NULL, MS_BIND, NULL) <= -1)
+
{
+
eio_unix_fork_error (errors, "pivot_root-new_root",
+
strerror (errno));
+
_exit (1);
+
}
+
}
+
+
// Make the place to pivot the old root too, under the new root
+
snprintf (old_root_path, sizeof (path), "%s/%s", new_root, put_old);
+
+
if (mkdir (old_root_path, 0777) == -1)
+
{
+
eio_unix_fork_error (errors, "pivot_root-mkdir-put_old",
+
strerror (errno));
+
_exit (1);
+
}
+
+
// Pivot the root
+
if (pivot_root (new_root, old_root_path))
+
{
+
eio_unix_fork_error (errors, "pivot_root", strerror (errno));
+
_exit (1);
+
}
+
+
// Add mounts
+
value current_mount = v_mounts;
+
int mount_result;
+
int mode;
+
while (current_mount != Val_emptylist)
+
{
+
// TODO: Mode for mounting
+
mode = Int_val (Field (Field (current_mount, 0), 2));
+
+
// A mount is a record {src; tgt; mode}, we first create the mount point
+
// directory target
+
if (mkdir (String_val (Field (Field (current_mount, 0), 1)), 0777) ==
+
-1)
+
{
+
eio_unix_fork_error (errors, "pivot_root-mkdir-mount",
+
strerror (errno));
+
_exit (1);
+
}
+
+
mount_result = mount (String_val (Field (Field (current_mount, 0), 0)),
+
String_val (Field (Field (current_mount, 0), 1)),
+
NULL, MS_REC | MS_BIND, NULL);
+
+
// Fail early if a mount fails...
+
if (mount_result < 0)
+
{
+
char error[PATH_MAX];
+
snprintf (error, sizeof (error), "mount failed: (%s->%s)",
+
String_val (Field (Field (current_mount, 0), 0)),
+
String_val (Field (Field (current_mount, 0), 1)));
+
eio_unix_fork_error (errors, error, strerror (errno));
+
_exit (1);
+
}
+
+
// After mounting for the first time, we can come back and add any
+
// extra modes that may have been specified, for example RDONLY.
+
if (mode != 0)
+
{
+
mount_result =
+
mount (String_val (Field (Field (current_mount, 0), 0)),
+
String_val (Field (Field (current_mount, 0), 1)), NULL,
+
MS_REMOUNT | MS_BIND | mode, NULL);
+
+
if (mount_result < 0)
+
{
+
eio_unix_fork_error (errors, "remount for mode",
+
strerror (errno));
+
_exit (1);
+
}
+
}
+
+
// Next mount in the list
+
current_mount = Field (current_mount, 1);
+
}
+
+
+
// Change to the 'new' root
+
if (chdir ("/") == -1)
+
{
+
eio_unix_fork_error (errors, "pivot_root-chdir", strerror (errno));
+
_exit (1);
+
}
+
+
// Unmount the old root and remove it
+
if (umount2 (put_old, MNT_DETACH) == -1)
+
{
+
eio_unix_fork_error (errors, put_old, strerror (errno));
+
_exit (1);
+
}
+
+
// Remove the old root
+
if (rmdir (put_old) == -1)
+
{
+
eio_unix_fork_error (errors, put_old, strerror (errno));
+
_exit (1);
+
}
+
+
+
// Apply any flags to the new root, e.g. RDONLY
+
if (Int_val (v_root_flags))
+
{
+
if (mount
+
("/", "/", NULL, (MS_REMOUNT | MS_BIND | Int_val (v_root_flags)),
+
NULL) <= -1)
+
{
+
eio_unix_fork_error (errors, "pivot_root-rootflags",
+
strerror (errno));
+
_exit (1);
+
}
+
}
+
}
+
+
CAMLprim value
+
void_fork_pivot_root (value v_unit)
+
{
+
return Val_fork_fn (action_pivot_root);
+
}
+3
vendor/void/test/dune
···
+
(executable
+
(name main)
+
(libraries void))
+20
vendor/void/test/main.ml
···
+
open Eio.Std
+
+
let _root_filesystem =
+
"/obuilder-zfs/result/fe532e693c6a86db16b50547aae1345b3515c727b8ed668b3e0c33c1e9a895f9/rootfs"
+
+
let () =
+
Eio_posix.run @@ fun _ ->
+
Switch.run @@ fun sw ->
+
let open Void in
+
let void =
+
empty
+
|> mount ~mode:R ~src:"/tmp/test" ~tgt:"bin"
+
|> exec [ "/bin/busybox"; "ls" ]
+
in
+
let t = Void.spawn ~sw void in
+
match Promise.await (Void.exit_status t) with
+
| Unix.WEXITED 0 -> print_endline "done"
+
| Unix.WEXITED n -> Printf.printf "Exited with %i\n%!" n
+
| Unix.WSTOPPED n -> Printf.printf "Stopped with %i\n%!" n
+
| Unix.WSIGNALED n -> Printf.printf "Signalled with %i\n%!" n
+32
vendor/void/void.opam
···
+
# This file is generated by dune, edit dune-project instead
+
opam-version: "2.0"
+
synopsis: "Void Processes in Eio_linux"
+
description: "A longer description"
+
maintainer: ["Patrick Ferris"]
+
authors: ["Patrick Ferris"]
+
license: "MIT"
+
tags: ["linux" "process" "spawn"]
+
homepage: "https://github.com/patricoferris/void"
+
doc: "https://ocaml.org/p/void"
+
bug-reports: "https://github.com/patricoferris/void/issues"
+
depends: [
+
"ocaml"
+
"dune" {>= "3.15"}
+
"eio_posix"
+
"odoc" {with-doc}
+
]
+
build: [
+
["dune" "subst"] {dev}
+
[
+
"dune"
+
"build"
+
"-p"
+
name
+
"-j"
+
jobs
+
"@install"
+
"@runtest" {with-test}
+
"@doc" {with-doc}
+
]
+
]
+
dev-repo: "git+https://github.com/patricoferris/void.git"
+2
vendor/zfs/.gitignore
···
+
_build
+
_opam
+1
vendor/zfs/.ocamlformat
···
+
version=0.27.0
+12
vendor/zfs/README.md
···
+
ocaml-zfs
+
---------
+
+
Some very partial and very probably broken bindings to `libzfs`.
+
+
<!-- $MDX file=example/main.ml -->
+
```ocaml
+
let () =
+
let handle = Zfs.init () in
+
let props = [ ("compression", `String "lz4") ] in
+
Zfs.create ~props handle "obuilder-zfs/hello" Zfs.Types.filesystem
+
```
+1
vendor/zfs/dune
···
+
(mdx)
+31
vendor/zfs/dune-project
···
+
(lang dune 3.15)
+
(using mdx 0.4)
+
(using ctypes 0.3)
+
+
(name zfs)
+
+
(generate_opam_files true)
+
+
(source
+
(github patricoferris/ocaml-zfs))
+
+
(authors "Patrick Ferris <patrick@sirref.org>")
+
+
(maintainers "Patrick Ferris <patrick@sirref.org>")
+
+
(license ISC)
+
+
+
(package
+
(name zfs)
+
(synopsis "libzfs bindings")
+
(description "OCaml bindings to libzfs")
+
(depends
+
ocaml
+
dune
+
ctypes
+
(mdx :with-test))
+
(tags
+
("filesystem" "zfs")))
+
+
; See the complete stanza docs at https://dune.readthedocs.io/en/stable/dune-files.html#dune-project
+3
vendor/zfs/example/dune
···
+
(executable
+
(name main)
+
(libraries zfs))
+4
vendor/zfs/example/main.ml
···
+
let () =
+
let handle = Zfs.init () in
+
let props = [ ("compression", `String "lz4") ] in
+
Zfs.create ~props handle "obuilder-zfs/hello" Zfs.Types.filesystem
+38
vendor/zfs/src/dune
···
+
(rule
+
(targets config.ml c_flags.sexp c_library_flags.sexp)
+
(action
+
(run ./include/discover.exe)))
+
+
(library
+
(name zfs)
+
(public_name zfs)
+
(libraries unix)
+
(c_library_flags
+
(:include c_library_flags.sexp))
+
(flags
+
(:standard -w -9-27))
+
(ctypes
+
(external_library_name libzfs)
+
(build_flags_resolver
+
(vendored
+
(c_flags
+
:standard
+
-D_GNU_SOURCE
+
(:include c_flags.sexp))))
+
(headers
+
(include
+
"unistd.h"
+
"stdio.h"
+
"stdint.h"
+
"stdbool.h"
+
"libzfs_core.h"
+
"libzfs.h"))
+
(type_description
+
(instance Types)
+
(functor Type_description))
+
(function_description
+
(concurrency unlocked)
+
(instance Functions)
+
(functor Function_description))
+
(generated_types Types_generated)
+
(generated_entry_point C)))
+99
vendor/zfs/src/function_description.ml
···
+
open Ctypes
+
+
(* This Types_generated module is an instantiation of the Types
+
functor defined in the type_description.ml file. It's generated by
+
a C program that Dune creates and runs behind the scenes. *)
+
module Types = Types_generated
+
+
module Functions (F : Ctypes.FOREIGN) = struct
+
open F
+
+
let init = foreign "libzfs_init" (void @-> returning Types.libzfs_handle_t)
+
let errno = foreign "libzfs_errno" (Types.libzfs_handle_t @-> returning int)
+
+
let debug =
+
foreign "libzfs_print_on_error"
+
(Types.libzfs_handle_t @-> bool @-> returning void)
+
+
module Zpool = struct
+
let open_ =
+
foreign "zpool_open"
+
(Types.libzfs_handle_t @-> string @-> returning Types.zpool_handle_t)
+
+
let close = foreign "zpool_close" (Types.zpool_handle_t @-> returning void)
+
+
let get_name =
+
foreign "zpool_get_name" (Types.zpool_handle_t @-> returning string)
+
+
let get_state =
+
foreign "zpool_get_state" (Types.zpool_handle_t @-> returning int)
+
end
+
+
let create_ancestors =
+
foreign "zfs_create_ancestors" (Types.libzfs_handle_t @-> string @-> returning int)
+
+
let create =
+
foreign "zfs_create"
+
(Types.libzfs_handle_t @-> string @-> int @-> ptr Types.nvlist_t
+
@-> returning int)
+
+
let open_ =
+
foreign "zfs_open"
+
(Types.libzfs_handle_t @-> string @-> int @-> returning Types.zfs_handle_t)
+
+
let mount =
+
foreign "zfs_mount" (Types.zfs_handle_t @-> string @-> int @-> returning int)
+
+
let unmount =
+
foreign "zfs_unmount"
+
(Types.zfs_handle_t @-> string @-> int @-> returning int)
+
+
let close = foreign "zfs_close" (Types.zfs_handle_t @-> returning void)
+
let get_type = foreign "zfs_get_type" (Types.zfs_handle_t @-> returning int)
+
+
module Nvlist = struct
+
let alloc =
+
foreign "nvlist_alloc"
+
(ptr (ptr Types.nvlist_t) @-> int @-> int @-> returning int)
+
+
let free = foreign "nvlist_free" (ptr Types.nvlist_t @-> returning void)
+
+
let add_bool =
+
foreign "nvlist_add_boolean_value"
+
(ptr Types.nvlist_t @-> string @-> bool @-> returning int)
+
+
let add_string =
+
foreign "nvlist_add_string"
+
(ptr Types.nvlist_t @-> string @-> string @-> returning int)
+
+
let add_byte =
+
foreign "nvlist_add_byte"
+
(ptr Types.nvlist_t @-> string @-> uchar @-> returning int)
+
+
let add_int64 =
+
foreign "nvlist_add_int64"
+
(ptr Types.nvlist_t @-> string @-> int64_t @-> returning int)
+
end
+
+
let clone =
+
foreign "zfs_clone"
+
(Types.zfs_handle_t @-> string @-> ptr Types.nvlist_t @-> returning int)
+
+
let snapshot =
+
foreign "zfs_snapshot"
+
(Types.libzfs_handle_t @-> string @-> bool @-> ptr Types.nvlist_t
+
@-> returning int)
+
+
let exists =
+
foreign "zfs_dataset_exists"
+
(Types.libzfs_handle_t @-> string @-> int @-> returning bool)
+
+
let is_mounted =
+
foreign "is_mounted"
+
(Types.libzfs_handle_t @-> string @-> ptr string @-> returning bool)
+
+
let diff =
+
foreign "zfs_show_diffs"
+
(Types.zfs_handle_t @-> int @-> string @-> string_opt @-> int
+
@-> returning int)
+
end
+209
vendor/zfs/src/include/discover.ml
···
+
module C = Configurator.V1
+
+
let starts_with ~prefix s =
+
try
+
String.iteri
+
(fun i c -> if Char.equal (String.get s i) c then () else raise Not_found)
+
prefix;
+
let l = String.length s in
+
let o = String.length prefix in
+
Some (String.sub s o (l - o))
+
with Not_found -> None
+
+
let () =
+
C.main ~name:"discover" (fun c ->
+
let pkgconf =
+
C.Pkg_config.get c |> function
+
| Some p -> p
+
| None -> failwith "Need pkgconfig"
+
in
+
match C.Pkg_config.query pkgconf ~package:"libzfs" with
+
| None -> failwith "Pkgconfig failed to find libzfs"
+
| Some conf ->
+
C.Flags.write_sexp "c_flags.sexp" conf.cflags;
+
C.Flags.write_sexp "c_library_flags.sexp" conf.libs;
+
let errs, props, types =
+
C.C_define.import c
+
~c_flags:("-D_GNU_SOURCE" :: conf.cflags)
+
~includes:
+
[
+
"unistd.h";
+
"stdio.h";
+
"stdint.h";
+
"stdbool.h";
+
"libzfs_core.h";
+
"libzfs.h";
+
]
+
C.C_define.Type.
+
[
+
(* Errors *)
+
("EZFS_SUCCESS", Int);
+
("EZFS_NOMEM", Int);
+
("EZFS_BADPROP", Int);
+
("EZFS_PROPREADONLY", Int);
+
("EZFS_PROPTYPE", Int);
+
("EZFS_PROPNONINHERIT", Int);
+
("EZFS_PROPSPACE", Int);
+
("EZFS_BADTYPE", Int);
+
("EZFS_BUSY", Int);
+
("EZFS_EXISTS", Int);
+
("EZFS_NOENT", Int);
+
("EZFS_BADSTREAM", Int);
+
("EZFS_DSREADONLY", Int);
+
("EZFS_VOLTOOBIG", Int);
+
("EZFS_INVALIDNAME", Int);
+
("EZFS_BADRESTORE", Int);
+
("EZFS_BADBACKUP", Int);
+
("EZFS_BADTARGET", Int);
+
("EZFS_NODEVICE", Int);
+
("EZFS_BADDEV", Int);
+
("EZFS_NOREPLICAS", Int);
+
("EZFS_RESILVERING", Int);
+
("EZFS_BADVERSION", Int);
+
("EZFS_POOLUNAVAIL", Int);
+
("EZFS_DEVOVERFLOW", Int);
+
("EZFS_BADPATH", Int);
+
("EZFS_CROSSTARGET", Int);
+
("EZFS_ZONED", Int);
+
("EZFS_MOUNTFAILED", Int);
+
("EZFS_UMOUNTFAILED", Int);
+
("EZFS_UNSHARENFSFAILED", Int);
+
("EZFS_SHARENFSFAILED", Int);
+
("EZFS_PERM", Int);
+
("EZFS_NOSPC", Int);
+
("EZFS_FAULT", Int);
+
("EZFS_IO", Int);
+
("EZFS_INTR", Int);
+
("EZFS_ISSPARE", Int);
+
("EZFS_INVALCONFIG", Int);
+
("EZFS_RECURSIVE", Int);
+
("EZFS_NOHISTORY", Int);
+
("EZFS_POOLPROPS", Int);
+
("EZFS_POOL_NOTSUP", Int);
+
("EZFS_POOL_INVALARG", Int);
+
("EZFS_NAMETOOLONG", Int);
+
("EZFS_OPENFAILED", Int);
+
("EZFS_NOCAP", Int);
+
("EZFS_LABELFAILED", Int);
+
("EZFS_BADWHO", Int);
+
("EZFS_BADPERM", Int);
+
("EZFS_BADPERMSET", Int);
+
("EZFS_NODELEGATION", Int);
+
("EZFS_UNSHARESMBFAILED", Int);
+
("EZFS_SHARESMBFAILED", Int);
+
("EZFS_BADCACHE", Int);
+
("EZFS_ISL2CACHE", Int);
+
("EZFS_VDEVNOTSUP", Int);
+
("EZFS_NOTSUP", Int);
+
("EZFS_ACTIVE_SPARE", Int);
+
("EZFS_UNPLAYED_LOGS", Int);
+
("EZFS_REFTAG_RELE", Int);
+
("EZFS_REFTAG_HOLD", Int);
+
("EZFS_TAGTOOLONG", Int);
+
("EZFS_PIPEFAILED", Int);
+
("EZFS_THREADCREATEFAILED", Int);
+
("EZFS_POSTSPLIT_ONLINE", Int);
+
("EZFS_SCRUBBING", Int);
+
("EZFS_ERRORSCRUBBING", Int);
+
("EZFS_ERRORSCRUB_PAUSED", Int);
+
("EZFS_NO_SCRUB", Int);
+
("EZFS_DIFF", Int);
+
("EZFS_DIFFDATA", Int);
+
("EZFS_POOLREADONLY", Int);
+
("EZFS_SCRUB_PAUSED", Int);
+
("EZFS_SCRUB_PAUSED_TO_CANCEL", Int);
+
("EZFS_ACTIVE_POOL", Int);
+
("EZFS_CRYPTOFAILED", Int);
+
("EZFS_NO_PENDING", Int);
+
("EZFS_CHECKPOINT_EXISTS", Int);
+
("EZFS_DISCARDING_CHECKPOINT", Int);
+
("EZFS_NO_CHECKPOINT", Int);
+
("EZFS_DEVRM_IN_PROGRESS", Int);
+
("EZFS_VDEV_TOO_BIG", Int);
+
("EZFS_IOC_NOTSUPPORTED", Int);
+
("EZFS_TOOMANY", Int);
+
("EZFS_INITIALIZING", Int);
+
("EZFS_NO_INITIALIZE", Int);
+
("EZFS_WRONG_PARENT", Int);
+
("EZFS_TRIMMING", Int);
+
("EZFS_NO_TRIM", Int);
+
("EZFS_TRIM_NOTSUP", Int);
+
("EZFS_NO_RESILVER_DEFER", Int);
+
("EZFS_EXPORT_IN_PROGRESS", Int);
+
("EZFS_REBUILDING", Int);
+
("EZFS_VDEV_NOTSUP", Int);
+
("EZFS_NOT_USER_NAMESPACE", Int);
+
("EZFS_CKSUM", Int);
+
("EZFS_RESUME_EXISTS", Int);
+
("EZFS_SHAREFAILED", Int);
+
("EZFS_UNKNOWN", Int);
+
(* Properties *)
+
("ZFS_PROP_CREATION", Int);
+
("ZFS_PROP_USED", Int);
+
("ZFS_PROP_AVAILABLE", Int);
+
("ZFS_PROP_REFERENCED", Int);
+
("ZFS_PROP_COMPRESSRATIO", Int);
+
("ZFS_PROP_COMPRESSION", Int);
+
("ZFS_PROP_SNAPDIR", Int);
+
("ZFS_PROP_ENCRYPTION", Int);
+
(* Types *)
+
("ZFS_TYPE_INVALID", Int);
+
("ZFS_TYPE_FILESYSTEM", Int);
+
("ZFS_TYPE_SNAPSHOT", Int);
+
("ZFS_TYPE_VOLUME", Int);
+
("ZFS_TYPE_POOL", Int);
+
("ZFS_TYPE_BOOKMARK", Int);
+
("ZFS_TYPE_VDEV", Int);
+
("ZFS_TYPE_DATASET", Int);
+
]
+
|> List.fold_left
+
(fun (errs, props, types) -> function
+
| name, C.C_define.Value.Int v -> (
+
let type_ name =
+
Printf.sprintf "val %s : t"
+
(String.lowercase_ascii name)
+
in
+
let definition name =
+
Printf.sprintf "let %s : t = 0x%x"
+
(String.lowercase_ascii name)
+
v
+
in
+
match starts_with ~prefix:"EZFS_" name with
+
| Some r ->
+
((type_ r, definition r) :: errs, props, types)
+
| None -> (
+
match starts_with ~prefix:"ZFS_PROP_" name with
+
| Some r ->
+
(errs, (type_ r, definition r) :: props, types)
+
| None -> (
+
match starts_with ~prefix:"ZFS_TYPE_" name with
+
| Some t ->
+
( errs,
+
props,
+
(type_ t, definition t) :: types )
+
| None -> failwith "Unknown ZFS static value")))
+
| _ -> assert false)
+
([], [], [])
+
in
+
let with_module ~name defs =
+
[ Printf.sprintf "module %s = struct\n type t = int\n" name ]
+
@ defs @ [ "end" ]
+
in
+
let with_module_type ~name defs =
+
[
+
Printf.sprintf "module type %s = sig\n type t = private int\n"
+
name;
+
]
+
@ defs @ [ "end" ]
+
in
+
let defs =
+
with_module ~name:"Error" (List.map snd errs)
+
@ with_module ~name:"Props" (List.map snd props)
+
@ with_module ~name:"Types" (List.map snd types)
+
in
+
let types =
+
with_module_type ~name:"ERROR" (List.map fst errs)
+
@ with_module_type ~name:"PROPS" (List.map fst props)
+
@ with_module_type ~name:"TYPES" (List.map fst types)
+
in
+
C.Flags.write_lines "config.ml" (defs @ types))
+4
vendor/zfs/src/include/dune
···
+
(executable
+
(name discover)
+
(modules discover)
+
(libraries dune-configurator))
+31
vendor/zfs/src/type_description.ml
···
+
module Types (F : Ctypes.TYPE) = struct
+
open F
+
+
type libzfs_handle_t
+
+
let libzfs_handle_t :
+
libzfs_handle_t Ctypes_static.structure Ctypes_static.ptr typ =
+
ptr @@ structure "libzfs_handle"
+
+
type zpool_handle_t
+
+
let zpool_handle_t :
+
zpool_handle_t Ctypes_static.structure Ctypes_static.ptr typ =
+
ptr @@ structure "zpool_handle"
+
+
type zfs_handle_t
+
+
let zfs_handle_t : zfs_handle_t Ctypes_static.structure Ctypes_static.ptr typ
+
=
+
ptr @@ structure "zfs_handle"
+
+
type nvlist_t
+
+
let nvlist_t : nvlist_t Ctypes_static.structure typ = structure "nvlist"
+
let nvl_version = field nvlist_t "nvl_version" int32_t
+
let nvl_nvflag = field nvlist_t "nvl_nvflag" uint32_t
+
let nvl_priv = field nvlist_t "nvl_priv" uint64_t
+
let nvl_flag = field nvlist_t "nvl_flag" uint32_t
+
let nvl_pad = field nvlist_t "nvl_pad" int32_t
+
let () = seal nvlist_t
+
end
+142
vendor/zfs/src/zfs.ml
···
+
module Error = struct
+
include Config.Error
+
end
+
+
module Flags = struct
+
type t = int
+
+
let empty = 0
+
let of_int x = x
+
let ( + ) = ( lor )
+
let mem a b = a land b = a
+
end
+
+
module Types = struct
+
include Flags
+
+
let vdev = Config.Types.vdev
+
let pool = Config.Types.pool
+
let volume = Config.Types.volume
+
let invalid = Config.Types.invalid
+
let bookmark = Config.Types.bookmark
+
let snapshot = Config.Types.snapshot
+
let filesystem = Config.Types.filesystem
+
let dataset = Config.Types.dataset
+
end
+
+
module Handle = struct
+
type t = C.Types.libzfs_handle_t Ctypes_static.structure Ctypes_static.ptr
+
end
+
+
let init : unit -> Handle.t = C.Functions.init
+
let debug : Handle.t -> bool -> unit = C.Functions.debug
+
let errno : Handle.t -> int = C.Functions.errno
+
+
module Zpool = struct
+
type t = C.Types.zpool_handle_t Ctypes_static.structure Ctypes_static.ptr
+
+
let open_ = C.Functions.Zpool.open_
+
let close = C.Functions.Zpool.close
+
let get_name = C.Functions.Zpool.get_name
+
end
+
+
module Nvlist = struct
+
type t = C.Types.nvlist_t Ctypes_static.structure Ctypes_static.ptr
+
+
type nvlist =
+
(string
+
* [ `Bool of bool
+
| `String of string
+
| `Byte of Unsigned.uchar
+
| `Int64 of int64 ])
+
list
+
+
let check_return i =
+
if i = 22 then invalid_arg "Nvlist.v: add bool" else assert (i = 0)
+
+
let v (schema : nvlist) : t =
+
let open Ctypes in
+
let finalise v = C.Functions.Nvlist.free !@v in
+
let nv_pp =
+
allocate ~finalise (ptr C.Types.nvlist_t)
+
(from_voidp C.Types.nvlist_t null)
+
in
+
(* TODO: Unique names or not... *)
+
C.Functions.Nvlist.alloc nv_pp 0x1 0 |> check_return;
+
let rec aux = function
+
| [] -> !@nv_pp
+
| (k, `Bool b) :: rest ->
+
C.Functions.Nvlist.add_bool !@nv_pp k b |> check_return;
+
aux rest
+
| (k, `String s) :: rest ->
+
C.Functions.Nvlist.add_string !@nv_pp k s |> check_return;
+
aux rest
+
| (k, `Int64 i64) :: rest ->
+
C.Functions.Nvlist.add_int64 !@nv_pp k i64 |> check_return;
+
aux rest
+
| (k, `Byte u) :: rest ->
+
C.Functions.Nvlist.add_byte !@nv_pp k u |> check_return;
+
aux rest
+
| _ -> assert false
+
in
+
aux schema
+
+
let empty = Ctypes.(coerce (ptr void) (ptr C.Types.nvlist_t) null)
+
end
+
+
type t = C.Types.zfs_handle_t Ctypes_static.structure Ctypes_static.ptr
+
+
let create_ancestors handle path =
+
let i = C.Functions.create_ancestors handle path in
+
if i != 0 then failwith "Failed to create ancestors" else ()
+
+
let create ?(props = []) handle path (type_ : Types.t) =
+
let i = C.Functions.create handle path type_ (Nvlist.v props) in
+
if i != 0 then failwith "Failed to create" else ()
+
+
let open_ handle path (type_ : Types.t) = C.Functions.open_ handle path type_
+
let close : t -> unit = C.Functions.close
+
let get_type : t -> Types.t = C.Functions.get_type
+
+
let clone ?(options = Nvlist.empty) handle path =
+
let res = C.Functions.clone handle path options in
+
if res = 0 then () else invalid_arg "clone"
+
+
let snapshot ?(options = Nvlist.empty) handle path b =
+
let res = C.Functions.snapshot handle path b options in
+
if res = 0 then () else invalid_arg "snapshot"
+
+
let exists handle path (type_ : Types.t) = C.Functions.exists handle path type_
+
+
let is_mounted handle path =
+
let where = Ctypes.allocate Ctypes.string "" in
+
let v = C.Functions.is_mounted handle path where in
+
if not v then None else Some (Ctypes.( !@ ) where)
+
+
let null_string = Ctypes.(coerce (ptr void) (ptr char) null)
+
+
let mount ?mount_opts ?(mount_flags = 0) dataset =
+
let opts =
+
Option.value
+
~default:(Ctypes.string_from_ptr null_string ~length:0)
+
mount_opts
+
in
+
let res = C.Functions.mount dataset opts mount_flags in
+
if res <> 0 then invalid_arg "mounting dataset"
+
+
let unmount ?mount_opts ?(mount_flags = 0) dataset =
+
let opts =
+
Option.value
+
~default:(Ctypes.string_from_ptr null_string ~length:0)
+
mount_opts
+
in
+
let res = C.Functions.unmount dataset opts mount_flags in
+
if res <> 0 then invalid_arg "unmounting dataset"
+
+
let show_diff ?to_ handle ~from_ (fd : Unix.file_descr) =
+
(* TODO: Other Diff Flags https://github.com/openzfs/zfs/blob/5b0c27cd14bbc07d50304c97735cc105d0258673/include/libzfs.h#L917? *)
+
let res = C.Functions.diff handle (Obj.magic fd : int) from_ to_ 1 in
+
if res = 0 then () else begin
+
Format.printf "Diff got %i\n%!" res;
+
invalid_arg "show_diff"
+
end
+206
vendor/zfs/src/zfs.mli
···
+
module Types : sig
+
type t = private int
+
+
val empty : int
+
val of_int : 'a -> 'a
+
val ( + ) : int -> int -> int
+
val mem : int -> int -> bool
+
val vdev : t
+
val pool : t
+
val volume : t
+
val invalid : t
+
val bookmark : t
+
val snapshot : t
+
val filesystem : t
+
val dataset : t
+
end
+
+
module Handle : sig
+
type t
+
(** An instance handle for the ZFS library *)
+
end
+
+
val init : unit -> Handle.t
+
(** Initialise the library *)
+
+
val debug : Handle.t -> bool -> unit
+
(** Enable/disable printing on error from ZFS *)
+
+
val errno : Handle.t -> int
+
(** Check for errors on the handle *)
+
+
module Zpool : sig
+
type t
+
(** A Zpool handle *)
+
+
val open_ : Handle.t -> string -> t
+
(** Open a Zpool *)
+
+
val close : t -> unit
+
(** Close an open Zpool *)
+
+
val get_name : t -> string
+
(** The name of an open Zpool *)
+
end
+
+
module Nvlist : sig
+
type t
+
(** Generic name-value lists used by ZFS *)
+
+
type nvlist =
+
(string
+
* [ `Bool of bool
+
| `Byte of Unsigned.uchar
+
| `String of string
+
| `Int64 of int64 ])
+
list
+
(** A partial OCaml representation of an NV list *)
+
+
val v : nvlist -> t
+
(** Convert the OCaml representation to the C representation *)
+
end
+
+
type t
+
(** A ZFS Dataset *)
+
+
val create_ancestors : Handle.t -> string -> unit
+
(** Often called before {! create} *)
+
+
val create : ?props:Nvlist.nvlist -> Handle.t -> string -> Types.t -> unit
+
(** Create a new ZFS dataset *)
+
+
val open_ : Handle.t -> string -> Types.t -> t
+
(** Open an existing ZFS dataset *)
+
+
val close : t -> unit
+
(** Close a dataset *)
+
+
val exists : Handle.t -> string -> Types.t -> bool
+
(** Check if a dataset of a specific type exists *)
+
+
val is_mounted : Handle.t -> string -> string option
+
(** [is_mounted h d = None] if [d] is not mounted, otherwise
+
[is_mounted h d = Some mountpoint]. *)
+
+
val mount : ?mount_opts:string -> ?mount_flags:int -> t -> unit
+
(** Mount a dataset *)
+
+
val unmount : ?mount_opts:string -> ?mount_flags:int -> t -> unit
+
(** Unmount a dataset *)
+
+
val get_type : t -> Types.t
+
(** Get the type of the dataset *)
+
+
val clone : ?options:Nvlist.t -> t -> string -> unit
+
(** Clone an open dataset *)
+
+
val snapshot : ?options:Nvlist.t -> Handle.t -> string -> bool -> unit
+
(** Snapshot a dataset *)
+
+
val show_diff : ?to_:string -> t -> from_:string -> Unix.file_descr -> unit
+
(** Output diff to the file descriptor *)
+
+
module Error : sig
+
type t = int
+
+
val unknown : t
+
val sharefailed : t
+
val resume_exists : t
+
val cksum : t
+
val not_user_namespace : t
+
val vdev_notsup : t
+
val rebuilding : t
+
val export_in_progress : t
+
val no_resilver_defer : t
+
val trim_notsup : t
+
val no_trim : t
+
val trimming : t
+
val wrong_parent : t
+
val no_initialize : t
+
val initializing : t
+
val toomany : t
+
val ioc_notsupported : t
+
val vdev_too_big : t
+
val devrm_in_progress : t
+
val no_checkpoint : t
+
val discarding_checkpoint : t
+
val checkpoint_exists : t
+
val no_pending : t
+
val cryptofailed : t
+
val active_pool : t
+
val scrub_paused_to_cancel : t
+
val scrub_paused : t
+
val poolreadonly : t
+
val diffdata : t
+
val diff : t
+
val no_scrub : t
+
val errorscrub_paused : t
+
val errorscrubbing : t
+
val scrubbing : t
+
val postsplit_online : t
+
val threadcreatefailed : t
+
val pipefailed : t
+
val tagtoolong : t
+
val reftag_hold : t
+
val reftag_rele : t
+
val unplayed_logs : t
+
val active_spare : t
+
val notsup : t
+
val vdevnotsup : t
+
val isl2cache : t
+
val badcache : t
+
val sharesmbfailed : t
+
val unsharesmbfailed : t
+
val nodelegation : t
+
val badpermset : t
+
val badperm : t
+
val badwho : t
+
val labelfailed : t
+
val nocap : t
+
val openfailed : t
+
val nametoolong : t
+
val pool_invalarg : t
+
val pool_notsup : t
+
val poolprops : t
+
val nohistory : t
+
val recursive : t
+
val invalconfig : t
+
val isspare : t
+
val intr : t
+
val io : t
+
val fault : t
+
val nospc : t
+
val perm : t
+
val sharenfsfailed : t
+
val unsharenfsfailed : t
+
val umountfailed : t
+
val mountfailed : t
+
val zoned : t
+
val crosstarget : t
+
val badpath : t
+
val devoverflow : t
+
val poolunavail : t
+
val badversion : t
+
val resilvering : t
+
val noreplicas : t
+
val baddev : t
+
val nodevice : t
+
val badtarget : t
+
val badbackup : t
+
val badrestore : t
+
val invalidname : t
+
val voltoobig : t
+
val dsreadonly : t
+
val badstream : t
+
val noent : t
+
val exists : t
+
val busy : t
+
val badtype : t
+
val propspace : t
+
val propnoninherit : t
+
val proptype : t
+
val propreadonly : t
+
val badprop : t
+
val nomem : t
+
val success : t
+
end
+150
vendor/zfs/src/zfs_stubs.c
···
+
/*
+
* Copyright (C) 2020-2021 Anil Madhavapeddy
+
* Copyright (C) 2020-2021 Sadiq Jaffer
+
* Copyright (C) 2022 Christiano Haesbaert
+
*
+
* Permission to use, copy, modify, and distribute this software for any
+
* purpose with or without fee is hereby granted, provided that the above
+
* copyright notice and this permission notice appear in all copies.
+
*
+
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
*/
+
+
#include <unistd.h>
+
#include <stdio.h>
+
#include <stdint.h>
+
#include <stdbool.h>
+
+
#include <libzfs_core.h>
+
#include <libzfs.h>
+
#include <caml/alloc.h>
+
#include <caml/bigarray.h>
+
#include <caml/callback.h>
+
#include <caml/custom.h>
+
#include <caml/fail.h>
+
#include <caml/memory.h>
+
#include <caml/mlvalues.h>
+
#include <caml/signals.h>
+
#include <caml/unixsupport.h>
+
#include <caml/socketaddr.h>
+
+
#undef ZFS_DEBUG
+
#ifdef ZFS_DEBUG
+
#define dprintf(fmt, ...) fprintf(stderr, fmt, ##__VA_ARGS__)
+
#else
+
#define dprintf(fmt, ...) ((void)0)
+
#endif
+
+
value ocaml_zfs_prop_is_string(value v_prop){
+
int res;
+
res = zfs_prop_is_string(Int_val(v_prop));
+
if (res < 0) {
+
caml_failwith("Error occurred!");
+
}
+
return Val_bool(res);
+
}
+
+
#define Zfs_list_val(v) (*((struct nv_list **) Data_custom_val(v)))
+
#define Zfs_handle_val(v) (*((libzfs_handle_t **) Data_custom_val(v)))
+
#define Zfs_pool_val(v) (*((zpool_handle_t **) Data_custom_val(v)))
+
+
static void finalize_zfs_list(value v) {
+
caml_stat_free(Zfs_list_val(v));
+
Zfs_list_val(v) = NULL;
+
}
+
+
static struct custom_operations zfs_list_ops = {
+
"zfs.zfs_list_ops",
+
finalize_zfs_list,
+
custom_compare_default,
+
custom_hash_default,
+
custom_serialize_default,
+
custom_deserialize_default,
+
custom_compare_ext_default,
+
custom_fixed_length_default
+
};
+
+
static void finalize_zfs_handle(value v) {
+
caml_stat_free(Zfs_handle_val(v));
+
Zfs_handle_val(v) = NULL;
+
}
+
+
static struct custom_operations zfs_handle_ops = {
+
"zfs.zfs_handle",
+
finalize_zfs_handle,
+
custom_compare_default,
+
custom_hash_default,
+
custom_serialize_default,
+
custom_deserialize_default,
+
custom_compare_ext_default,
+
custom_fixed_length_default
+
};
+
+
static void finalize_zfs_pool(value v) {
+
caml_stat_free(Zfs_pool_val(v));
+
Zfs_pool_val(v) = NULL;
+
}
+
+
static struct custom_operations zfs_pool_ops = {
+
"zfs.zfs_pool",
+
finalize_zfs_pool,
+
custom_compare_default,
+
custom_hash_default,
+
custom_serialize_default,
+
custom_deserialize_default,
+
custom_compare_ext_default,
+
custom_fixed_length_default
+
};
+
+
// ZFS Initialisation
+
+
value
+
ocaml_zfs_init(value v_unit) {
+
CAMLparam0();
+
libzfs_handle_t* res;
+
CAMLlocal1(v_handle);
+
+
v_handle = caml_alloc_custom_mem(&zfs_handle_ops, sizeof(libzfs_handle_t*), 64);
+
res = libzfs_init();
+
Zfs_handle_val(v_handle) = res;
+
+
CAMLreturn(v_handle);
+
}
+
+
// ZFS Pools
+
+
value
+
ocaml_zfs_pool_open(value v_handle, value v_path) {
+
CAMLparam2(v_handle, v_path);
+
zpool_handle_t* res;
+
CAMLlocal1(v_pool);
+
+
if (!caml_string_is_c_safe(v_path))
+
caml_invalid_argument("ocaml_zfs_pool_open: path is not C-safe");
+
+
v_pool = caml_alloc_custom_mem(&zfs_pool_ops, sizeof(zpool_handle_t*), 64);
+
res = zpool_open(Zfs_handle_val(v_handle), String_val(v_path));
+
Zfs_pool_val(v_handle) = res;
+
+
CAMLreturn(v_handle);
+
}
+
+
value
+
ocaml_zfs_pool_get_name(value v_pool) {
+
CAMLparam1(v_pool);
+
CAMLlocal1(v_path);
+
const char* result;
+
+
result = zpool_get_name(Zfs_pool_val(v_pool));
+
v_path = caml_copy_string(result);
+
+
CAMLreturn(v_path);
+
}
+
+
+32
vendor/zfs/zfs.opam
···
+
# This file is generated by dune, edit dune-project instead
+
opam-version: "2.0"
+
synopsis: "libzfs bindings"
+
description: "OCaml bindings to libzfs"
+
maintainer: ["Patrick Ferris <patrick@sirref.org>"]
+
authors: ["Patrick Ferris <patrick@sirref.org>"]
+
license: "ISC"
+
tags: ["filesystem" "zfs"]
+
homepage: "https://github.com/patricoferris/ocaml-zfs"
+
bug-reports: "https://github.com/patricoferris/ocaml-zfs/issues"
+
depends: [
+
"ocaml"
+
"dune" {>= "3.15"}
+
"ctypes"
+
"mdx" {with-test}
+
"odoc" {with-doc}
+
]
+
build: [
+
["dune" "subst"] {dev}
+
[
+
"dune"
+
"build"
+
"-p"
+
name
+
"-j"
+
jobs
+
"@install"
+
"@runtest" {with-test}
+
"@doc" {with-doc}
+
]
+
]
+
dev-repo: "git+https://github.com/patricoferris/ocaml-zfs.git"