this repo has no description

Lots of fixes

+4 -2
src/bin/main.ml
···
Eio_posix.run @@ fun env ->
let cmd_file = Option.map (Eio.Path.( / ) env#fs) cmd_file in
let dir = state_dir env#fs "shelter" in
-
Main.main config env#fs env#clock env#process_mgr dir cmd_file
+
let stdout = (env#stdout :> Eio.Flow.sink_ty Eio.Flow.sink) in
+
Main.main config ~stdout env#fs env#clock env#process_mgr dir cmd_file
in
let t = Term.(const run $ Shelter_main.config_term $ cmd_file) in
let man =
···
Eio_posix.run @@ fun env ->
let cmd_file = Option.map (Eio.Path.( / ) env#fs) cmd_file in
let dir = state_dir env#fs "passthrough" in
-
Pass.main config env#fs env#clock env#process_mgr dir cmd_file
+
let stdout = (env#stdout :> Eio.Flow.sink_ty Eio.Flow.sink) in
+
Pass.main config ~stdout env#fs env#clock env#process_mgr dir cmd_file
in
let t = Term.(const run $ Shelter_passthrough.config_term $ cmd_file) in
let info = Cmd.info "passthrough" in
+1
src/lib/engine.ml
···
val run :
config ->
+
stdout:Eio.Flow.sink_ty Eio.Flow.sink ->
Eio.Fs.dir_ty Eio.Path.t ->
_ Eio.Time.clock ->
Eio_unix.Process.mgr_ty Eio_unix.Process.mgr ->
+1 -1
src/lib/passthrough/shelter_passthrough.ml
···
in
List.iter (fun v -> LNoise.history_add v |> ignore) entries
-
let run (() : config) _fs clock proc
+
let run (() : config) ~stdout:_ _fs clock proc
( ((Shelter.History.Store ((module S), store) : entry Shelter.History.t) as
full_store),
() ) (Exec command) =
+7 -5
src/lib/shelter.ml
···
module Make (H : History.S) (Engine : Engine.S with type entry = H.t) = struct
module Store = Irmin_fs_unix.KV.Make (H)
-
let run config fs clock proc store =
+
let run config ~stdout fs clock proc store =
let store = History.Store ((module Store), store) in
let initial_ctx = Engine.init fs proc store in
let rec loop store ctx exit_code =
···
| None -> ()
| Some input -> (
let action = Engine.action_of_command input in
-
match Engine.run config fs clock proc (store, ctx) action with
+
match Engine.run config ~stdout fs clock proc (store, ctx) action with
| Error (Eio.Process.Child_error exit_code) ->
Fmt.epr "%a\n%!" Eio.Process.pp_status exit_code;
loop store ctx exit_code
···
Eio.Path.load cf |> String.split_on_char '\n'
|> List.map Engine.action_of_command
-
let main config fs clock proc directory command_file =
+
let main config ~stdout fs clock proc directory command_file =
Irmin_fs.run directory @@ fun () ->
let conf = Irmin_fs.config (Eio.Path.native_exn directory) in
let repo = Store.Repo.v conf in
···
let folder (store, ctx, exit_code) action =
if exit_code <> `Exited 0 then (store, ctx, exit_code)
else
-
match Engine.run config fs clock proc (store, ctx) action with
+
match
+
Engine.run config ~stdout fs clock proc (store, ctx) action
+
with
| Error (Eio.Process.Child_error exit_code) ->
Fmt.epr "%a\n%!" Eio.Process.pp_status exit_code;
(store, ctx, exit_code)
···
| `Exited n | `Signaled n ->
Fmt.epr "%a\n%!" Eio.Process.pp_status exit_code;
exit n)
-
| None -> run config fs clock proc store
+
| None -> run config ~stdout fs clock proc store
end
+8 -1
src/lib/shelter/dune
···
+
; (rule
+
; (target opentrace)
+
; (action
+
; (with-stdout-to opentrace (run echo hello))))
+
(library
(name shelter_main)
(public_name shelter.main)
+
(preprocessor_deps
+
(file opentrace))
(preprocess
-
(pps ppx_repr))
+
(pps ppx_repr ppx_blob))
(libraries shelter cid void zfs))
src/lib/shelter/opentrace

This is a binary file and will not be displayed.

+85 -41
src/lib/shelter/runc.ml
···
("options", `List (List.map (fun x -> `String x) options));
]
+
type mount = { ty : [ `Bind ]; src : string; dst : string; readonly : bool }
+
+
let user_mounts =
+
List.map @@ fun { ty; src; dst; readonly } ->
+
assert (ty = `Bind);
+
let options = [ "bind"; "nosuid"; "nodev" ] in
+
mount ~ty:"bind" ~src dst
+
~options:(if readonly then "ro" :: options else options)
+
let strings xs = `List (List.map (fun x -> `String x) xs)
let namespace x = `Assoc [ ("type", `String x) ]
···
network : string list;
user : int * int;
env : string list;
+
mounts : mount list;
entrypoint : string option;
}
-
let make { cwd; argv; hostname; network; user; env; entrypoint } ~config_dir
-
~results_dir : Yojson.Safe.t =
+
let make { cwd; argv; hostname; network; user; env; mounts; entrypoint }
+
~config_dir ~results_dir : Yojson.Safe.t =
assert (entrypoint = None);
let user =
let uid, gid = user in
···
`Assoc
[
("type", `String "RLIMIT_NOFILE");
-
("hard", `Int 1024);
-
("soft", `Int 1024);
+
("hard", `Int 10_024);
+
("soft", `Int 10_024);
+
];
+
`Assoc
+
[
+
("type", `String "RLIMIT_MEMLOCK");
+
("hard", `Int 1_000_000);
+
("soft", `Int 1_000_000);
];
] );
("noNewPrivileges", `Bool false);
···
~options:
[ (* TODO: copy to others? *) "nosuid"; "noexec"; "nodev" ]
~ty:"proc" ~src:"proc"
-
:: mount "/dev" ~ty:"tmpfs" ~src:"tmpfs"
-
~options:[ "nosuid"; "strictatime"; "mode=755"; "size=65536k" ]
-
:: mount "/dev/pts" ~ty:"devpts" ~src:"devpts"
-
~options:
-
[
-
"nosuid";
-
"noexec";
-
"newinstance";
-
"ptmxmode=0666";
-
"mode=0620";
-
"gid=5";
-
(* tty *)
-
]
-
:: mount
-
"/sys"
-
(* This is how Docker does it. runc's default is a bit different. *)
-
~ty:"sysfs" ~src:"sysfs"
-
~options:[ "nosuid"; "noexec"; "nodev"; "ro" ]
-
:: mount "/sys/fs/cgroup" ~ty:"cgroup" ~src:"cgroup"
-
~options:[ "ro"; "nosuid"; "noexec"; "nodev" ]
-
:: mount "/dev/shm" ~ty:"tmpfs" ~src:"shm"
-
~options:
-
[ "nosuid"; "noexec"; "nodev"; "mode=1777"; "size=65536k" ]
-
:: mount "/dev/mqueue" ~ty:"mqueue" ~src:"mqueue"
-
~options:[ "nosuid"; "noexec"; "nodev" ]
-
:: mount "/etc/hosts" ~ty:"bind" ~src:(config_dir // "hosts")
-
~options:[ "ro"; "rbind"; "rprivate" ]
-
::
-
(if network = [ "host" ] then
-
[
-
mount "/etc/resolv.conf" ~ty:"bind" ~src:"/etc/resolv.conf"
-
~options:[ "ro"; "rbind"; "rprivate" ];
-
]
-
else [])) );
+
:: mount "/dev" ~ty:"tmpfs" ~src:"tmpfs"
+
~options:
+
[ "nosuid"; "strictatime"; "mode=755"; "size=65536k" ]
+
:: mount "/dev/pts" ~ty:"devpts" ~src:"devpts"
+
~options:
+
[
+
"nosuid";
+
"noexec";
+
"newinstance";
+
"ptmxmode=0666";
+
"mode=0620";
+
"gid=5";
+
(* tty *)
+
]
+
:: mount
+
"/sys"
+
(* This is how Docker does it. runc's default is a bit different. *)
+
~ty:"sysfs" ~src:"sysfs"
+
~options:[ "nosuid"; "noexec"; "nodev"; "ro" ]
+
:: mount "/sys/fs/cgroup" ~ty:"cgroup" ~src:"cgroup"
+
~options:[ "ro"; "nosuid"; "noexec"; "nodev" ]
+
:: mount "/dev/shm" ~ty:"tmpfs" ~src:"shm"
+
~options:
+
[ "nosuid"; "noexec"; "nodev"; "mode=1777"; "size=65536k" ]
+
:: mount "/dev/mqueue" ~ty:"mqueue" ~src:"mqueue"
+
~options:[ "nosuid"; "noexec"; "nodev" ]
+
:: mount "/etc/hosts" ~ty:"bind" ~src:(config_dir // "hosts")
+
~options:[ "ro"; "rbind"; "rprivate" ]
+
::
+
(if network = [ "host" ] then
+
[
+
mount "/etc/resolv.conf" ~ty:"bind" ~src:"/etc/resolv.conf"
+
~options:[ "ro"; "rbind"; "rprivate" ];
+
]
+
else [])
+
@ user_mounts mounts) );
( "linux",
`Assoc
[
···
let next_id = ref 0
-
let spawn ~sw fs proc config dir =
+
let to_other_sink_as_well ~other
+
(Eio.Resource.T (t, handler) : Eio.Flow.sink_ty Eio.Flow.sink) =
+
let module Sink = (val Eio.Resource.get handler Eio.Flow.Pi.Sink) in
+
let copy_buf = Buffer.create 128 in
+
let copy () ~src =
+
Eio.Flow.copy src (Eio.Flow.buffer_sink copy_buf);
+
Eio.Flow.copy_string (Buffer.contents copy_buf) other;
+
Sink.copy t ~src:(Buffer.contents copy_buf |> Eio.Flow.string_source);
+
Buffer.clear copy_buf
+
in
+
let single_write () x =
+
let _ : int = Eio.Flow.single_write other x in
+
Sink.single_write t x
+
in
+
let module T = struct
+
type t = unit
+
+
let single_write = single_write
+
let copy = copy
+
end in
+
Eio.Resource.T ((), Eio.Flow.Pi.sink (module T))
+
+
let spawn ~sw log env config dir =
let tmp = Filename.temp_dir ~perms:0o700 "shelter-run-" "" in
-
let eio_tmp = Eio.Path.(fs / tmp) in
+
let eio_tmp = Eio.Path.(env#fs / tmp) in
let json_config = Json_config.make config ~config_dir:tmp ~results_dir:dir in
Eio.Path.save ~create:(`If_missing 0o644) (eio_tmp / "config.json")
(Yojson.Safe.pretty_to_string json_config ^ "\n");
···
let id = string_of_int !next_id in
incr next_id;
let cmd = [ "runc"; "--root"; "runc"; "run"; id ] in
-
Eio.Process.spawn ~sw proc ~cwd:eio_tmp cmd
+
let stdout =
+
to_other_sink_as_well ~other:env#stdout
+
(log :> Eio.Flow.sink_ty Eio.Flow.sink)
+
in
+
Eio.Process.spawn ~stdout ~sw env#proc ~cwd:eio_tmp cmd
(*
Apache License
+294 -169
src/lib/shelter/shelter_main.ml
···
| "R" -> Void.R | "RW" -> Void.RW | _ -> failwith "Malformed Void.mode")
(function Void.R -> "R" | Void.RW -> "RW")
-
type t = {
+
type post = { diff : Diff.t; time : int64 } [@@deriving repr]
+
+
type pre = {
mode : mode;
build : Store.Build.t;
args : string list;
-
time : int64;
env : string list;
cwd : string;
user : int * int;
-
diff : Diff.t;
}
[@@deriving repr]
+
(** Needed for execution *)
+
+
type t = { pre : pre; post : post } [@@deriving repr]
let merge = Irmin.Merge.(default (Repr.option t))
end
···
type entry = History.t
type action =
+
(* Change modes *)
| Set_mode of History.mode
+
(* Fork a new branch from an existing one,
+
or switch to a branch if it exists *)
| Set_session of string
+
(* Run a command *)
| Exec of string list
-
| Info
+
(* Undo the last command *)
| Undo
-
| Fork of string
+
(* Replay the current branch onto another *)
| Replay of string
+
(* Display info *)
+
| Info of [ `Current | `History ]
+
(* Error state *)
| Unknown of string list
-
| History
[@@deriving repr]
let split_and_remove_empty s =
···
let shelter_action = function
| "mode" :: [ "r" ] -> Set_mode R
-
| "mode" :: [ "rw" ] -> Set_mode R
+
| "mode" :: [ "rw" ] -> Set_mode RW
| "session" :: [ m ] -> Set_session m
-
| "fork" :: [ m ] -> Fork m
| "replay" :: [ onto ] -> Replay onto
-
| [ "info" ] -> Info
+
| [ "info" ] -> Info `Current
| [ "undo" ] -> Undo
-
| [ "history" ] -> History
+
| [ "history" ] -> Info `History
| other -> Unknown other
let action_of_command cmd =
···
let history_key = [ "history" ]
let key clock = history_key @ [ string_of_float @@ Eio.Time.now clock ]
-
let list (H.Store ((module S), store) : entry H.t) =
-
match S.list store history_key with
-
| [] -> []
-
| xs ->
-
let rec loop acc = function
-
| (s, `Contents (v, _meta)) :: next -> loop ((s, v) :: acc) next
-
| _ :: next -> loop acc next
-
| [] -> List.rev acc
+
let history (H.Store ((module S), store) : entry H.t) =
+
let repo = S.repo store in
+
match S.Head.find store with
+
| None -> []
+
| Some hd ->
+
let rec linearize c =
+
match S.Commit.parents c |> List.map (S.Commit.of_hash repo) with
+
| [ Some p ] -> c :: linearize p
+
| _ -> [ c ]
in
-
loop [] (List.map (fun (v, tree) -> (v, S.Tree.to_concrete tree)) xs)
-
|> List.stable_sort (fun (s1, _) (s2, _) ->
-
Float.compare (Float.of_string s1) (Float.of_string s2))
-
|> List.rev
+
let commits = linearize hd in
+
let get_diff_content t1 t2 =
+
match S.Tree.diff t1 t2 with
+
| [ (_, `Added (c, _)) ] -> c
+
| lst ->
+
let pp_diff =
+
Repr.pp (Irmin.Diff.t (Repr.pair History.t S.metadata_t))
+
in
+
Fmt.epr "Get diff (%i) content %a%!" (List.length lst)
+
Fmt.(list ~sep:Fmt.comma (Fmt.pair (Repr.pp S.path_t) pp_diff))
+
lst;
+
invalid_arg "Get diff should only have a single difference."
+
in
+
let hash c = S.Commit.hash c |> S.Hash.to_raw_string in
+
let rec diff_calc = function
+
| [] -> []
+
| [ x ] ->
+
let diff = get_diff_content (S.Tree.empty ()) (S.Commit.tree x) in
+
[ (hash x, diff) ]
+
| c :: p :: rest ->
+
let diff = get_diff_content (S.Commit.tree p) (S.Commit.tree c) in
+
(hash c, diff) :: diff_calc (p :: rest)
+
in
+
diff_calc commits
let with_latest ~default s f =
-
match list s with [] -> default () | hd :: _ -> f hd
+
match history s with [] -> default () | (_, hd) :: _ -> f hd
let text c = Fmt.(styled (`Fg c) string)
···
let repo = S.repo session in
let heads = List.map (fun b -> (S.Branch.find repo b, b)) branches in
let head = S.Head.find session in
-
List.assoc_opt head heads
+
let head_hash =
+
Option.map
+
(fun hash -> String.sub (Fmt.str "%a" S.Commit.pp_hash hash) 0 7)
+
head
+
in
+
(head_hash, List.assoc_opt head heads)
(* Reset the head of the current session by one commit *)
let reset_hard ((H.Store ((module S), session) : entry H.t) as s) =
···
Ok store
(* Fork a new session from an existing one *)
-
let display_history (H.Store ((module S), session) : entry H.t) =
-
let history = S.history ~depth:max_int session in
-
let content c =
-
H.Store ((module S), S.of_commit c) |> list |> List.hd |> snd
-
in
+
let display_history (s : entry H.t) =
let pp_diff fmt d =
if d = [] then () else Fmt.pf fmt "\n %a" (Repr.pp Diff.t) d
in
let pp_entry fmt (e : entry) =
Fmt.pf fmt "%-10s %s%a"
-
Fmt.(str "%a" (styled (`Fg `Yellow) uint64_ns_span) e.time)
-
(String.concat " " e.args) pp_diff e.diff
+
Fmt.(str "%a" (styled (`Fg `Yellow) uint64_ns_span) e.post.time)
+
(String.concat " " e.pre.args)
+
pp_diff e.post.diff
in
-
let linearize =
-
S.History.fold_vertex (fun c v -> content c :: v) history [] |> List.rev
-
in
-
List.iter (fun c -> Fmt.pr "%a\n%!" pp_entry c) linearize
+
let entries = history s |> List.rev in
+
List.iter (fun (_hash, c) -> Fmt.pr "%a\n%!" pp_entry c) entries
let prompt status ((H.Store ((module S), _session) : entry H.t) as store) =
-
let sesh = Option.value ~default:"main" (which_branch store) in
+
let head, sesh = which_branch store in
+
let sesh = Option.value ~default:"main" sesh in
let prompt () =
Fmt.(styled (`Fg `Yellow) string) Format.str_formatter "shelter> ";
Format.flush_str_formatter ()
in
-
let pp_sesh fmt sesh = Fmt.pf fmt "[%a]" (text `Green) sesh in
+
let pp_head fmt = function
+
| None -> Fmt.nop fmt ()
+
| Some h -> Fmt.pf fmt "#%a" (text `Magenta) h
+
in
+
let pp_sesh fmt sesh = Fmt.pf fmt "[%a%a]" (text `Green) sesh pp_head head in
let pp_status fmt = function
| `Exited 0 -> Fmt.nop fmt ()
| `Exited n -> Fmt.pf fmt "%a " (text `Red) (string_of_int n)
| _ -> Fmt.nop fmt ()
in
-
let prompt_entry (_, (e : entry)) =
+
let prompt_entry (e : entry) =
Fmt.pf Format.str_formatter "%a%a%a : { mode: %a }> " pp_status status
(text `Yellow) "shelter" pp_sesh sesh (text `Red)
-
(if e.mode = R then "r" else "rw");
+
(if e.pre.mode = R then "r" else "rw");
Format.flush_str_formatter ()
in
with_latest store ~default:prompt prompt_entry
-
type ctx = Store.t
+
type ctx = { store : Store.t; tool_dir : string }
+
+
let tools = [ ("opentrace", Tools.opentrace) ]
let init fs proc s =
let store = Store.init fs proc "shelter" in
List.iter
-
(fun (_, { History.args; _ }) ->
+
(fun (_, { History.pre = { History.args; _ }; _ }) ->
LNoise.history_add (String.concat " " args) |> ignore)
-
(list s);
-
store
+
(history s);
+
let tool_cid = Store.cid (String.concat ":" (List.map snd tools)) in
+
let tools =
+
Store.Run.with_tool store tool_cid @@ fun tool_dir ->
+
Eio.Fiber.List.iter
+
(fun (toolname, content) ->
+
let new_path = Eio.Path.(fs / tool_dir / toolname) in
+
Eio.Path.save ~create:(`If_missing 0o755) new_path content)
+
tools;
+
tool_dir
+
in
+
{ store; tool_dir = tools }
-
let run (config : config) fs clock proc
-
(((H.Store ((module S), store) : entry H.t) as s), ctx) = function
+
let run (config : config) ~stdout fs clock proc
+
(((H.Store ((module S), store) : entry H.t) as s), (ctx : ctx)) = function
| Set_mode mode ->
-
with_latest ~default:(fun _ -> Ok (s, ctx)) s @@ fun (_, entry) ->
-
commit ~message:"mode change" clock s { entry with mode };
+
with_latest ~default:(fun _ -> Ok (s, ctx)) s @@ fun entry ->
+
commit ~message:"mode change" clock s
+
{ entry with pre = { entry.pre with mode } };
Ok (s, ctx)
-
| Set_session m ->
-
with_latest ~default:(fun _ -> Ok (s, ctx)) s @@ fun (_, entry) ->
-
let new_store = S.of_branch (S.repo store) m in
-
let new_full_store = H.Store ((module S), new_store) in
-
commit ~message:"new session" clock new_full_store entry;
-
Ok (new_full_store, ctx)
+
| Set_session m -> (
+
(* Either set the session if the branch exists or create a new branch
+
from the latest commit of the current branch *)
+
let sessions = sessions s in
+
match List.exists (String.equal m) sessions with
+
| true ->
+
let sesh = S.of_branch (S.repo store) m in
+
Ok (H.Store ((module S), sesh), ctx)
+
| false -> (
+
match fork s m with
+
| Error err ->
+
Fmt.pr "[fork]: %a\n%!" (text `Red) err;
+
Ok (s, ctx)
+
| Ok store -> Ok (store, ctx)))
| Unknown args ->
Fmt.epr "%a: %s\n%!" (text `Red) "Unknown Shelter Action"
(String.concat " " args);
Ok (s, ctx)
-
| Info ->
+
| Info `Current ->
let sessions = sessions s in
-
let sesh = Option.value ~default:"main" (which_branch s) in
-
let history = S.history store in
+
let sesh = Option.value ~default:"main" (snd (which_branch s)) in
+
let history = history s in
let pp_commit fmt (hash, msg) =
Fmt.pf fmt "[%a]: %s" (text `Yellow) hash msg
in
+
let repo = S.repo store in
let commits =
-
S.History.fold_vertex
-
(fun commit acc ->
+
List.fold_left
+
(fun acc (commit, _) ->
+
let commit =
+
S.Hash.unsafe_of_raw_string commit
+
|> S.Commit.of_hash repo |> Option.get
+
in
let info = S.Commit.info commit |> S.Info.message in
let hash = S.Commit.hash commit |> Repr.to_string S.Hash.t in
(String.sub hash 0 7, info) :: acc)
-
history []
+
[] history
in
let latest =
with_latest
~default:(fun () -> None)
s
-
(fun (_, e) -> Some (Repr.to_string Store.Build.t e.build))
+
(fun e -> Some (Repr.to_string Store.Build.t e.pre.build))
in
Fmt.pr "Sessions: %a\nCurrent: %a\nHash: %a\nCommits:@. %a\n%!"
Fmt.(list ~sep:(Fmt.any ", ") string)
···
Ok (s, ctx)
| Exec [] -> Ok (s, ctx)
| Undo -> Ok (reset_hard s, ctx)
-
| Fork new_branch -> (
-
match fork s new_branch with
-
| Error err ->
-
Fmt.pr "[fork]: %a\n%!" (text `Red) err;
-
Ok (s, ctx)
-
| Ok store -> Ok (store, ctx))
| Replay _ -> Ok (s, ctx)
-
| History ->
+
| Info `History ->
display_history s;
Ok (s, ctx)
| Exec command -> (
···
~default:(fun () ->
History.
{
-
mode = Void.RW;
-
build = Store.Build.Image config.image;
-
args = command;
-
time = 0L;
-
diff = [];
-
(* TODO: extract with fetch *)
-
env = [];
-
cwd = "/";
-
user = (0, 0);
+
pre =
+
{
+
mode = Void.RW;
+
build = Store.Build.Image config.image;
+
args = command;
+
(* TODO: extract with fetch *)
+
env = [];
+
cwd = "/";
+
user = (0, 0);
+
};
+
post = { diff = []; time = 0L };
})
s
-
@@ fun (_, e) -> e
+
@@ fun e -> e
in
let build, env, (uid, gid) =
-
match entry.build with
+
match entry.pre.build with
| Store.Build.Image img ->
-
let build, env, user = Store.fetch ctx img in
+
let build, env, user = Store.fetch ctx.store img in
(build, env, Option.value ~default:(0, 0) user)
-
| Store.Build.Build cid -> (cid, entry.env, entry.user)
+
| Store.Build.Build cid -> (cid, entry.pre.env, entry.pre.user)
+
in
+
let hash_entry =
+
{
+
entry with
+
pre = { entry.pre with build = Build build; args = command };
+
}
in
-
let hash_entry = { entry with build = Build build; args = command } in
-
let new_cid = Store.cid (Repr.to_string History.t hash_entry) in
+
(* Store things under History.pre, this makes it possible to rediscover
+
the hash for something purely from the arguments needed to execute something
+
rather than needing, for example, the time it took to execute! *)
+
let new_cid = Store.cid (Repr.to_string History.pre_t hash_entry.pre) in
let with_rootfs fn =
-
if entry.mode = R then (Store.Run.with_build ctx build fn, [])
-
else Store.Run.with_clone ctx ~src:build new_cid fn
+
if entry.pre.mode = R then (Store.Run.with_build ctx.store build fn, [])
+
else Store.Run.with_clone ctx.store ~src:build new_cid fn
in
try
let new_entry, diff =
-
with_rootfs @@ fun rootfs ->
-
let spawn sw =
-
if config.no_runc then
-
let rootfs = Filename.concat rootfs "rootfs" in
-
let void =
-
Void.empty
-
|> Void.rootfs ~mode:entry.mode rootfs
-
|> Void.cwd entry.cwd
-
(* TODO: Support UIDs |> Void.uid 1000 *)
-
|> Void.exec ~env
-
[
-
config.shell;
-
"-c";
-
String.concat " " command ^ " && env > /tmp/shelter-env";
-
]
+
with_rootfs @@ function
+
| `Exists path ->
+
(* Copy the stdout log to stdout *)
+
let () =
+
Eio.Path.(with_open_in (fs / (path :> string) / "log"))
+
@@ fun ic -> Eio.Flow.copy ic stdout
in
-
`Void (Void.spawn ~sw void |> Void.exit_status)
-
else
-
let config =
-
Runc.Json_config.
-
{
-
cwd = entry.cwd;
-
argv =
-
[
-
config.shell;
-
"-c";
-
String.concat " " command ^ " && env > /tmp/shelter-env";
-
];
-
hostname = "";
-
network = [];
-
user = (uid, gid);
-
env = entry.env;
-
entrypoint = None;
-
}
+
let repo = S.repo store in
+
let c =
+
Eio.Path.(load (fs / (path :> string) / "hash"))
+
|> S.Hash.unsafe_of_raw_string |> S.Commit.of_hash repo
+
in
+
Ok (`Reset c)
+
| `Build rootfs ->
+
let spawn sw log =
+
if config.no_runc then
+
let rootfs = Filename.concat rootfs "rootfs" in
+
let void =
+
Void.empty
+
|> Void.rootfs ~mode:entry.pre.mode rootfs
+
|> Void.cwd entry.pre.cwd
+
(* TODO: Support UIDs |> Void.uid 1000 *)
+
|> Void.exec ~env
+
[
+
config.shell;
+
"-c";
+
String.concat " " command
+
^ " && env > /tmp/shelter-env";
+
]
+
in
+
`Void (Void.spawn ~sw void |> Void.exit_status)
+
else
+
let tool_mount : Runc.Json_config.mount =
+
{
+
ty = `Bind;
+
src = ctx.tool_dir;
+
dst = "/shelter-tools";
+
readonly = true;
+
}
+
in
+
let config =
+
Runc.Json_config.
+
{
+
cwd = entry.pre.cwd;
+
argv =
+
[
+
config.shell;
+
"-c";
+
String.concat " " command
+
^ " && env > /tmp/shelter-env";
+
];
+
hostname = "";
+
network = [ "host" ];
+
user = (uid, gid);
+
env = entry.pre.env;
+
mounts = [ tool_mount ];
+
entrypoint = None;
+
}
+
in
+
let env =
+
object
+
method fs = fs
+
method proc = proc
+
method stdout = stdout
+
end
+
in
+
`Runc (Runc.spawn ~sw log env config rootfs)
+
in
+
Switch.run @@ fun sw ->
+
let log =
+
Eio.Path.open_out ~sw ~create:(`If_missing 0o644)
+
Eio.Path.(fs / rootfs / "log")
+
in
+
let res = spawn sw log in
+
let start = Mtime_clock.now () in
+
let res =
+
match res with
+
| `Runc r -> Eio.Process.await r
+
| `Void v -> Void.to_eio_status (Eio.Promise.await v)
+
in
+
let stop = Mtime_clock.now () in
+
let span = Mtime.span start stop in
+
let time = Mtime.Span.to_uint64_ns span in
+
(* Add command to history regardless of exit status *)
+
let _ : (unit, string) result =
+
LNoise.history_add (String.concat " " command)
in
-
`Runc (Runc.spawn ~sw fs proc config rootfs)
-
in
-
Switch.run @@ fun sw ->
-
let res = spawn sw in
-
let start = Mtime_clock.now () in
-
let res =
-
match res with
-
| `Runc r -> Eio.Process.await r
-
| `Void v -> Void.to_eio_status (Eio.Promise.await v)
-
in
-
let stop = Mtime_clock.now () in
-
let span = Mtime.span start stop in
-
let time = Mtime.Span.to_uint64_ns span in
-
(* Add command to history regardless of exit status *)
-
let _ : (unit, string) result =
-
LNoise.history_add (String.concat " " command)
-
in
-
if res = `Exited 0 then (
-
(* Extract env *)
-
let env_path =
-
Eio.Path.(fs / rootfs / "rootfs" / "tmp" / "shelter-env")
-
in
-
let env =
-
Eio.Path.(load env_path)
-
|> String.split_on_char '\n'
-
|> List.filter (fun s -> not (String.equal "" s))
-
in
-
Eio.Path.unlink env_path;
-
let cwd =
-
List.find_map
-
(fun v ->
-
match Astring.String.cut ~sep:"=" v with
-
| Some ("PWD", dir) -> Some dir
-
| _ -> None)
-
env
-
|> Option.value ~default:hash_entry.cwd
-
in
-
if entry.mode = RW then
-
Ok
-
{
-
hash_entry with
-
build = Build new_cid;
-
time;
-
env;
-
cwd;
-
user = (uid, gid);
-
}
-
else Ok { hash_entry with time; cwd; env; user = (uid, gid) })
-
else Error (Eio.Process.Child_error res)
+
if res = `Exited 0 then (
+
(* Extract env *)
+
let env_path =
+
Eio.Path.(fs / rootfs / "rootfs" / "tmp" / "shelter-env")
+
in
+
let env =
+
Eio.Path.(load env_path)
+
|> String.split_on_char '\n'
+
|> List.filter (fun s -> not (String.equal "" s))
+
in
+
Eio.Path.unlink env_path;
+
let cwd =
+
List.find_map
+
(fun v ->
+
match Astring.String.cut ~sep:"=" v with
+
| Some ("PWD", dir) -> Some dir
+
| _ -> None)
+
env
+
|> Option.value ~default:hash_entry.pre.cwd
+
in
+
if entry.pre.mode = RW then
+
Ok
+
(`Entry
+
( {
+
hash_entry with
+
History.pre =
+
{
+
hash_entry.pre with
+
build = Build new_cid;
+
env;
+
cwd;
+
user = (uid, gid);
+
};
+
},
+
rootfs ))
+
else
+
Ok
+
(`Entry
+
( {
+
pre =
+
{ hash_entry.pre with cwd; env; user = (uid, gid) };
+
post = { hash_entry.post with time };
+
},
+
rootfs )))
+
else Error (Eio.Process.Child_error res)
in
match new_entry with
| Error e -> Error e
-
| Ok entry ->
+
| Ok (`Reset None) ->
+
Fmt.epr "Resetting to existing entry failed...\n%!";
+
Ok (s, ctx)
+
| Ok (`Reset (Some c)) ->
+
S.Head.set store c;
+
Ok (s, ctx)
+
| Ok (`Entry (entry, path)) ->
(* Set diff *)
-
let entry = { entry with diff } in
+
let entry = { entry with post = { entry.post with diff } } in
(* Commit if RW *)
-
if entry.mode = RW then
+
if entry.pre.mode = RW then (
commit
~message:("exec " ^ String.concat " " command)
clock s entry;
+
(* Save the commit hash for easy restoring later *)
+
let hash =
+
S.Head.get store |> S.Commit.hash |> S.Hash.to_raw_string
+
in
+
Eio.Path.save ~create:(`If_missing 0o644)
+
Eio.Path.(fs / path / "hash")
+
hash);
Ok (s, ctx)
with Eio.Exn.Io (Eio.Process.E e, _) -> Error e)
+6 -3
src/lib/shelter/shelter_main.mli
···
module Store = Store
module History : sig
-
type t = {
+
type post = { diff : Diff.t; time : int64 } [@@deriving repr]
+
+
type pre = {
mode : Void.mode;
build : Store.Build.t;
args : string list;
-
time : int64;
env : string list;
cwd : string;
user : int * int;
-
diff : Diff.t;
}
[@@deriving repr]
+
(** Needed for execution *)
+
+
type t = { pre : pre; post : post } [@@deriving repr]
include Irmin.Contents.S with type t := t
end
+19 -5
src/lib/shelter/store.ml
···
val builds : string -> dataset
val build : string -> string -> dataset
val snapshot : dataset -> snapshot
+
val tools : string -> dataset
+
val tool : string -> string -> dataset
end = struct
type dataset = string
type snapshot = string
···
let builds pool : dataset = pool / "builds"
let build pool path : dataset = builds pool / path
let snapshot ds = ds ^ "@snappy"
+
let tools pool : dataset = pool / "tools"
+
let tool pool path : dataset = tools pool / path
end
let with_dataset ?(typ = Zfs.Types.filesystem) t dataset f =
···
}
in
create_and_mount t (Datasets.builds t.pool);
+
create_and_mount t (Datasets.tools t.pool);
t
let snapshot t (snap : Datasets.snapshot) =
···
let ds = Datasets.build t.pool (Cid.to_string cid) in
Fun.protect ~finally:(fun () -> unmount_dataset t ds) @@ fun () ->
mount_dataset t ds;
+
fn (`Build ("/" ^ (ds :> string)))
+
+
let with_tool t cid fn =
+
let ds = Datasets.tool t.pool (Cid.to_string cid) in
+
Fun.protect ~finally:(fun () -> unmount_dataset t ds) @@ fun () ->
+
mount_dataset t ds;
fn ("/" ^ (ds :> string))
let with_clone t ~src new_cid fn =
···
let tgt = Datasets.build t.pool (Cid.to_string new_cid) in
let src_snap = Datasets.snapshot ds in
let tgt_snap = Datasets.snapshot tgt in
-
clone t src_snap tgt;
-
let v = with_build t new_cid fn in
-
snapshot t tgt_snap;
-
let d = diff t src_snap tgt_snap in
-
(v, d)
+
if Zfs.exists t.zfs (tgt :> string) Zfs.Types.dataset then
+
(fn (`Exists ("/" ^ (tgt :> string))), diff t src_snap tgt_snap)
+
else (
+
clone t src_snap tgt;
+
let v = with_build t new_cid fn in
+
snapshot t tgt_snap;
+
let d = diff t src_snap tgt_snap in
+
(v, d))
end
+1
src/lib/shelter/tools.ml
···
+
let opentrace = [%blob "./opentrace"]