···3030 Eio_posix.run @@ fun env ->
3131 let cmd_file = Option.map (Eio.Path.( / ) env#fs) cmd_file in
3232 let dir = state_dir env#fs "shelter" in
3333- Main.main config env#fs env#clock env#process_mgr dir cmd_file
3333+ let stdout = (env#stdout :> Eio.Flow.sink_ty Eio.Flow.sink) in
3434+ Main.main config ~stdout env#fs env#clock env#process_mgr dir cmd_file
3435 in
3536 let t = Term.(const run $ Shelter_main.config_term $ cmd_file) in
3637 let man =
···4950 Eio_posix.run @@ fun env ->
5051 let cmd_file = Option.map (Eio.Path.( / ) env#fs) cmd_file in
5152 let dir = state_dir env#fs "passthrough" in
5252- Pass.main config env#fs env#clock env#process_mgr dir cmd_file
5353+ let stdout = (env#stdout :> Eio.Flow.sink_ty Eio.Flow.sink) in
5454+ Pass.main config ~stdout env#fs env#clock env#process_mgr dir cmd_file
5355 in
5456 let t = Term.(const run $ Shelter_passthrough.config_term $ cmd_file) in
5557 let info = Cmd.info "passthrough" in
···3131 ("options", `List (List.map (fun x -> `String x) options));
3232 ]
33333434+ type mount = { ty : [ `Bind ]; src : string; dst : string; readonly : bool }
3535+3636+ let user_mounts =
3737+ List.map @@ fun { ty; src; dst; readonly } ->
3838+ assert (ty = `Bind);
3939+ let options = [ "bind"; "nosuid"; "nodev" ] in
4040+ mount ~ty:"bind" ~src dst
4141+ ~options:(if readonly then "ro" :: options else options)
4242+3443 let strings xs = `List (List.map (fun x -> `String x) xs)
3544 let namespace x = `Assoc [ ("type", `String x) ]
3645···112121 network : string list;
113122 user : int * int;
114123 env : string list;
124124+ mounts : mount list;
115125 entrypoint : string option;
116126 }
117127118118- let make { cwd; argv; hostname; network; user; env; entrypoint } ~config_dir
119119- ~results_dir : Yojson.Safe.t =
128128+ let make { cwd; argv; hostname; network; user; env; mounts; entrypoint }
129129+ ~config_dir ~results_dir : Yojson.Safe.t =
120130 assert (entrypoint = None);
121131 let user =
122132 let uid, gid = user in
···161171 `Assoc
162172 [
163173 ("type", `String "RLIMIT_NOFILE");
164164- ("hard", `Int 1024);
165165- ("soft", `Int 1024);
174174+ ("hard", `Int 10_024);
175175+ ("soft", `Int 10_024);
176176+ ];
177177+ `Assoc
178178+ [
179179+ ("type", `String "RLIMIT_MEMLOCK");
180180+ ("hard", `Int 1_000_000);
181181+ ("soft", `Int 1_000_000);
166182 ];
167183 ] );
168184 ("noNewPrivileges", `Bool false);
···180196 ~options:
181197 [ (* TODO: copy to others? *) "nosuid"; "noexec"; "nodev" ]
182198 ~ty:"proc" ~src:"proc"
183183- :: mount "/dev" ~ty:"tmpfs" ~src:"tmpfs"
184184- ~options:[ "nosuid"; "strictatime"; "mode=755"; "size=65536k" ]
185185- :: mount "/dev/pts" ~ty:"devpts" ~src:"devpts"
186186- ~options:
187187- [
188188- "nosuid";
189189- "noexec";
190190- "newinstance";
191191- "ptmxmode=0666";
192192- "mode=0620";
193193- "gid=5";
194194- (* tty *)
195195- ]
196196- :: mount
197197- "/sys"
198198- (* This is how Docker does it. runc's default is a bit different. *)
199199- ~ty:"sysfs" ~src:"sysfs"
200200- ~options:[ "nosuid"; "noexec"; "nodev"; "ro" ]
201201- :: mount "/sys/fs/cgroup" ~ty:"cgroup" ~src:"cgroup"
202202- ~options:[ "ro"; "nosuid"; "noexec"; "nodev" ]
203203- :: mount "/dev/shm" ~ty:"tmpfs" ~src:"shm"
204204- ~options:
205205- [ "nosuid"; "noexec"; "nodev"; "mode=1777"; "size=65536k" ]
206206- :: mount "/dev/mqueue" ~ty:"mqueue" ~src:"mqueue"
207207- ~options:[ "nosuid"; "noexec"; "nodev" ]
208208- :: mount "/etc/hosts" ~ty:"bind" ~src:(config_dir // "hosts")
209209- ~options:[ "ro"; "rbind"; "rprivate" ]
210210- ::
211211- (if network = [ "host" ] then
212212- [
213213- mount "/etc/resolv.conf" ~ty:"bind" ~src:"/etc/resolv.conf"
214214- ~options:[ "ro"; "rbind"; "rprivate" ];
215215- ]
216216- else [])) );
199199+ :: mount "/dev" ~ty:"tmpfs" ~src:"tmpfs"
200200+ ~options:
201201+ [ "nosuid"; "strictatime"; "mode=755"; "size=65536k" ]
202202+ :: mount "/dev/pts" ~ty:"devpts" ~src:"devpts"
203203+ ~options:
204204+ [
205205+ "nosuid";
206206+ "noexec";
207207+ "newinstance";
208208+ "ptmxmode=0666";
209209+ "mode=0620";
210210+ "gid=5";
211211+ (* tty *)
212212+ ]
213213+ :: mount
214214+ "/sys"
215215+ (* This is how Docker does it. runc's default is a bit different. *)
216216+ ~ty:"sysfs" ~src:"sysfs"
217217+ ~options:[ "nosuid"; "noexec"; "nodev"; "ro" ]
218218+ :: mount "/sys/fs/cgroup" ~ty:"cgroup" ~src:"cgroup"
219219+ ~options:[ "ro"; "nosuid"; "noexec"; "nodev" ]
220220+ :: mount "/dev/shm" ~ty:"tmpfs" ~src:"shm"
221221+ ~options:
222222+ [ "nosuid"; "noexec"; "nodev"; "mode=1777"; "size=65536k" ]
223223+ :: mount "/dev/mqueue" ~ty:"mqueue" ~src:"mqueue"
224224+ ~options:[ "nosuid"; "noexec"; "nodev" ]
225225+ :: mount "/etc/hosts" ~ty:"bind" ~src:(config_dir // "hosts")
226226+ ~options:[ "ro"; "rbind"; "rprivate" ]
227227+ ::
228228+ (if network = [ "host" ] then
229229+ [
230230+ mount "/etc/resolv.conf" ~ty:"bind" ~src:"/etc/resolv.conf"
231231+ ~options:[ "ro"; "rbind"; "rprivate" ];
232232+ ]
233233+ else [])
234234+ @ user_mounts mounts) );
217235 ( "linux",
218236 `Assoc
219237 [
···248266249267let next_id = ref 0
250268251251-let spawn ~sw fs proc config dir =
269269+let to_other_sink_as_well ~other
270270+ (Eio.Resource.T (t, handler) : Eio.Flow.sink_ty Eio.Flow.sink) =
271271+ let module Sink = (val Eio.Resource.get handler Eio.Flow.Pi.Sink) in
272272+ let copy_buf = Buffer.create 128 in
273273+ let copy () ~src =
274274+ Eio.Flow.copy src (Eio.Flow.buffer_sink copy_buf);
275275+ Eio.Flow.copy_string (Buffer.contents copy_buf) other;
276276+ Sink.copy t ~src:(Buffer.contents copy_buf |> Eio.Flow.string_source);
277277+ Buffer.clear copy_buf
278278+ in
279279+ let single_write () x =
280280+ let _ : int = Eio.Flow.single_write other x in
281281+ Sink.single_write t x
282282+ in
283283+ let module T = struct
284284+ type t = unit
285285+286286+ let single_write = single_write
287287+ let copy = copy
288288+ end in
289289+ Eio.Resource.T ((), Eio.Flow.Pi.sink (module T))
290290+291291+let spawn ~sw log env config dir =
252292 let tmp = Filename.temp_dir ~perms:0o700 "shelter-run-" "" in
253253- let eio_tmp = Eio.Path.(fs / tmp) in
293293+ let eio_tmp = Eio.Path.(env#fs / tmp) in
254294 let json_config = Json_config.make config ~config_dir:tmp ~results_dir:dir in
255295 Eio.Path.save ~create:(`If_missing 0o644) (eio_tmp / "config.json")
256296 (Yojson.Safe.pretty_to_string json_config ^ "\n");
···259299 let id = string_of_int !next_id in
260300 incr next_id;
261301 let cmd = [ "runc"; "--root"; "runc"; "run"; id ] in
262262- Eio.Process.spawn ~sw proc ~cwd:eio_tmp cmd
302302+ let stdout =
303303+ to_other_sink_as_well ~other:env#stdout
304304+ (log :> Eio.Flow.sink_ty Eio.Flow.sink)
305305+ in
306306+ Eio.Process.spawn ~stdout ~sw env#proc ~cwd:eio_tmp cmd
263307264308(*
265309 Apache License
+294-169
src/lib/shelter/shelter_main.ml
···1111 | "R" -> Void.R | "RW" -> Void.RW | _ -> failwith "Malformed Void.mode")
1212 (function Void.R -> "R" | Void.RW -> "RW")
13131414- type t = {
1414+ type post = { diff : Diff.t; time : int64 } [@@deriving repr]
1515+1616+ type pre = {
1517 mode : mode;
1618 build : Store.Build.t;
1719 args : string list;
1818- time : int64;
1920 env : string list;
2021 cwd : string;
2122 user : int * int;
2222- diff : Diff.t;
2323 }
2424 [@@deriving repr]
2525+ (** Needed for execution *)
2626+2727+ type t = { pre : pre; post : post } [@@deriving repr]
25282629 let merge = Irmin.Merge.(default (Repr.option t))
2730end
···3336type entry = History.t
34373538type action =
3939+ (* Change modes *)
3640 | Set_mode of History.mode
4141+ (* Fork a new branch from an existing one,
4242+ or switch to a branch if it exists *)
3743 | Set_session of string
4444+ (* Run a command *)
3845 | Exec of string list
3939- | Info
4646+ (* Undo the last command *)
4047 | Undo
4141- | Fork of string
4848+ (* Replay the current branch onto another *)
4249 | Replay of string
5050+ (* Display info *)
5151+ | Info of [ `Current | `History ]
5252+ (* Error state *)
4353 | Unknown of string list
4444- | History
4554[@@deriving repr]
46554756let split_and_remove_empty s =
···51605261let shelter_action = function
5362 | "mode" :: [ "r" ] -> Set_mode R
5454- | "mode" :: [ "rw" ] -> Set_mode R
6363+ | "mode" :: [ "rw" ] -> Set_mode RW
5564 | "session" :: [ m ] -> Set_session m
5656- | "fork" :: [ m ] -> Fork m
5765 | "replay" :: [ onto ] -> Replay onto
5858- | [ "info" ] -> Info
6666+ | [ "info" ] -> Info `Current
5967 | [ "undo" ] -> Undo
6060- | [ "history" ] -> History
6868+ | [ "history" ] -> Info `History
6169 | other -> Unknown other
62706371let action_of_command cmd =
···6977let history_key = [ "history" ]
7078let key clock = history_key @ [ string_of_float @@ Eio.Time.now clock ]
71797272-let list (H.Store ((module S), store) : entry H.t) =
7373- match S.list store history_key with
7474- | [] -> []
7575- | xs ->
7676- let rec loop acc = function
7777- | (s, `Contents (v, _meta)) :: next -> loop ((s, v) :: acc) next
7878- | _ :: next -> loop acc next
7979- | [] -> List.rev acc
8080+let history (H.Store ((module S), store) : entry H.t) =
8181+ let repo = S.repo store in
8282+ match S.Head.find store with
8383+ | None -> []
8484+ | Some hd ->
8585+ let rec linearize c =
8686+ match S.Commit.parents c |> List.map (S.Commit.of_hash repo) with
8787+ | [ Some p ] -> c :: linearize p
8888+ | _ -> [ c ]
8089 in
8181- loop [] (List.map (fun (v, tree) -> (v, S.Tree.to_concrete tree)) xs)
8282- |> List.stable_sort (fun (s1, _) (s2, _) ->
8383- Float.compare (Float.of_string s1) (Float.of_string s2))
8484- |> List.rev
9090+ let commits = linearize hd in
9191+ let get_diff_content t1 t2 =
9292+ match S.Tree.diff t1 t2 with
9393+ | [ (_, `Added (c, _)) ] -> c
9494+ | lst ->
9595+ let pp_diff =
9696+ Repr.pp (Irmin.Diff.t (Repr.pair History.t S.metadata_t))
9797+ in
9898+ Fmt.epr "Get diff (%i) content %a%!" (List.length lst)
9999+ Fmt.(list ~sep:Fmt.comma (Fmt.pair (Repr.pp S.path_t) pp_diff))
100100+ lst;
101101+ invalid_arg "Get diff should only have a single difference."
102102+ in
103103+ let hash c = S.Commit.hash c |> S.Hash.to_raw_string in
104104+ let rec diff_calc = function
105105+ | [] -> []
106106+ | [ x ] ->
107107+ let diff = get_diff_content (S.Tree.empty ()) (S.Commit.tree x) in
108108+ [ (hash x, diff) ]
109109+ | c :: p :: rest ->
110110+ let diff = get_diff_content (S.Commit.tree p) (S.Commit.tree c) in
111111+ (hash c, diff) :: diff_calc (p :: rest)
112112+ in
113113+ diff_calc commits
8511486115let with_latest ~default s f =
8787- match list s with [] -> default () | hd :: _ -> f hd
116116+ match history s with [] -> default () | (_, hd) :: _ -> f hd
8811789118let text c = Fmt.(styled (`Fg c) string)
90119···100129 let repo = S.repo session in
101130 let heads = List.map (fun b -> (S.Branch.find repo b, b)) branches in
102131 let head = S.Head.find session in
103103- List.assoc_opt head heads
132132+ let head_hash =
133133+ Option.map
134134+ (fun hash -> String.sub (Fmt.str "%a" S.Commit.pp_hash hash) 0 7)
135135+ head
136136+ in
137137+ (head_hash, List.assoc_opt head heads)
104138105139(* Reset the head of the current session by one commit *)
106140let reset_hard ((H.Store ((module S), session) : entry H.t) as s) =
···127161 Ok store
128162129163(* Fork a new session from an existing one *)
130130-let display_history (H.Store ((module S), session) : entry H.t) =
131131- let history = S.history ~depth:max_int session in
132132- let content c =
133133- H.Store ((module S), S.of_commit c) |> list |> List.hd |> snd
134134- in
164164+let display_history (s : entry H.t) =
135165 let pp_diff fmt d =
136166 if d = [] then () else Fmt.pf fmt "\n %a" (Repr.pp Diff.t) d
137167 in
138168 let pp_entry fmt (e : entry) =
139169 Fmt.pf fmt "%-10s %s%a"
140140- Fmt.(str "%a" (styled (`Fg `Yellow) uint64_ns_span) e.time)
141141- (String.concat " " e.args) pp_diff e.diff
170170+ Fmt.(str "%a" (styled (`Fg `Yellow) uint64_ns_span) e.post.time)
171171+ (String.concat " " e.pre.args)
172172+ pp_diff e.post.diff
142173 in
143143- let linearize =
144144- S.History.fold_vertex (fun c v -> content c :: v) history [] |> List.rev
145145- in
146146- List.iter (fun c -> Fmt.pr "%a\n%!" pp_entry c) linearize
174174+ let entries = history s |> List.rev in
175175+ List.iter (fun (_hash, c) -> Fmt.pr "%a\n%!" pp_entry c) entries
147176148177let prompt status ((H.Store ((module S), _session) : entry H.t) as store) =
149149- let sesh = Option.value ~default:"main" (which_branch store) in
178178+ let head, sesh = which_branch store in
179179+ let sesh = Option.value ~default:"main" sesh in
150180 let prompt () =
151181 Fmt.(styled (`Fg `Yellow) string) Format.str_formatter "shelter> ";
152182 Format.flush_str_formatter ()
153183 in
154154- let pp_sesh fmt sesh = Fmt.pf fmt "[%a]" (text `Green) sesh in
184184+ let pp_head fmt = function
185185+ | None -> Fmt.nop fmt ()
186186+ | Some h -> Fmt.pf fmt "#%a" (text `Magenta) h
187187+ in
188188+ let pp_sesh fmt sesh = Fmt.pf fmt "[%a%a]" (text `Green) sesh pp_head head in
155189 let pp_status fmt = function
156190 | `Exited 0 -> Fmt.nop fmt ()
157191 | `Exited n -> Fmt.pf fmt "%a " (text `Red) (string_of_int n)
158192 | _ -> Fmt.nop fmt ()
159193 in
160160- let prompt_entry (_, (e : entry)) =
194194+ let prompt_entry (e : entry) =
161195 Fmt.pf Format.str_formatter "%a%a%a : { mode: %a }> " pp_status status
162196 (text `Yellow) "shelter" pp_sesh sesh (text `Red)
163163- (if e.mode = R then "r" else "rw");
197197+ (if e.pre.mode = R then "r" else "rw");
164198 Format.flush_str_formatter ()
165199 in
166200 with_latest store ~default:prompt prompt_entry
167201168168-type ctx = Store.t
202202+type ctx = { store : Store.t; tool_dir : string }
203203+204204+let tools = [ ("opentrace", Tools.opentrace) ]
169205170206let init fs proc s =
171207 let store = Store.init fs proc "shelter" in
172208 List.iter
173173- (fun (_, { History.args; _ }) ->
209209+ (fun (_, { History.pre = { History.args; _ }; _ }) ->
174210 LNoise.history_add (String.concat " " args) |> ignore)
175175- (list s);
176176- store
211211+ (history s);
212212+ let tool_cid = Store.cid (String.concat ":" (List.map snd tools)) in
213213+ let tools =
214214+ Store.Run.with_tool store tool_cid @@ fun tool_dir ->
215215+ Eio.Fiber.List.iter
216216+ (fun (toolname, content) ->
217217+ let new_path = Eio.Path.(fs / tool_dir / toolname) in
218218+ Eio.Path.save ~create:(`If_missing 0o755) new_path content)
219219+ tools;
220220+ tool_dir
221221+ in
222222+ { store; tool_dir = tools }
177223178178-let run (config : config) fs clock proc
179179- (((H.Store ((module S), store) : entry H.t) as s), ctx) = function
224224+let run (config : config) ~stdout fs clock proc
225225+ (((H.Store ((module S), store) : entry H.t) as s), (ctx : ctx)) = function
180226 | Set_mode mode ->
181181- with_latest ~default:(fun _ -> Ok (s, ctx)) s @@ fun (_, entry) ->
182182- commit ~message:"mode change" clock s { entry with mode };
227227+ with_latest ~default:(fun _ -> Ok (s, ctx)) s @@ fun entry ->
228228+ commit ~message:"mode change" clock s
229229+ { entry with pre = { entry.pre with mode } };
183230 Ok (s, ctx)
184184- | Set_session m ->
185185- with_latest ~default:(fun _ -> Ok (s, ctx)) s @@ fun (_, entry) ->
186186- let new_store = S.of_branch (S.repo store) m in
187187- let new_full_store = H.Store ((module S), new_store) in
188188- commit ~message:"new session" clock new_full_store entry;
189189- Ok (new_full_store, ctx)
231231+ | Set_session m -> (
232232+ (* Either set the session if the branch exists or create a new branch
233233+ from the latest commit of the current branch *)
234234+ let sessions = sessions s in
235235+ match List.exists (String.equal m) sessions with
236236+ | true ->
237237+ let sesh = S.of_branch (S.repo store) m in
238238+ Ok (H.Store ((module S), sesh), ctx)
239239+ | false -> (
240240+ match fork s m with
241241+ | Error err ->
242242+ Fmt.pr "[fork]: %a\n%!" (text `Red) err;
243243+ Ok (s, ctx)
244244+ | Ok store -> Ok (store, ctx)))
190245 | Unknown args ->
191246 Fmt.epr "%a: %s\n%!" (text `Red) "Unknown Shelter Action"
192247 (String.concat " " args);
193248 Ok (s, ctx)
194194- | Info ->
249249+ | Info `Current ->
195250 let sessions = sessions s in
196196- let sesh = Option.value ~default:"main" (which_branch s) in
197197- let history = S.history store in
251251+ let sesh = Option.value ~default:"main" (snd (which_branch s)) in
252252+ let history = history s in
198253 let pp_commit fmt (hash, msg) =
199254 Fmt.pf fmt "[%a]: %s" (text `Yellow) hash msg
200255 in
256256+ let repo = S.repo store in
201257 let commits =
202202- S.History.fold_vertex
203203- (fun commit acc ->
258258+ List.fold_left
259259+ (fun acc (commit, _) ->
260260+ let commit =
261261+ S.Hash.unsafe_of_raw_string commit
262262+ |> S.Commit.of_hash repo |> Option.get
263263+ in
204264 let info = S.Commit.info commit |> S.Info.message in
205265 let hash = S.Commit.hash commit |> Repr.to_string S.Hash.t in
206266 (String.sub hash 0 7, info) :: acc)
207207- history []
267267+ [] history
208268 in
209269 let latest =
210270 with_latest
211271 ~default:(fun () -> None)
212272 s
213213- (fun (_, e) -> Some (Repr.to_string Store.Build.t e.build))
273273+ (fun e -> Some (Repr.to_string Store.Build.t e.pre.build))
214274 in
215275 Fmt.pr "Sessions: %a\nCurrent: %a\nHash: %a\nCommits:@. %a\n%!"
216276 Fmt.(list ~sep:(Fmt.any ", ") string)
···222282 Ok (s, ctx)
223283 | Exec [] -> Ok (s, ctx)
224284 | Undo -> Ok (reset_hard s, ctx)
225225- | Fork new_branch -> (
226226- match fork s new_branch with
227227- | Error err ->
228228- Fmt.pr "[fork]: %a\n%!" (text `Red) err;
229229- Ok (s, ctx)
230230- | Ok store -> Ok (store, ctx))
231285 | Replay _ -> Ok (s, ctx)
232232- | History ->
286286+ | Info `History ->
233287 display_history s;
234288 Ok (s, ctx)
235289 | Exec command -> (
···238292 ~default:(fun () ->
239293 History.
240294 {
241241- mode = Void.RW;
242242- build = Store.Build.Image config.image;
243243- args = command;
244244- time = 0L;
245245- diff = [];
246246- (* TODO: extract with fetch *)
247247- env = [];
248248- cwd = "/";
249249- user = (0, 0);
295295+ pre =
296296+ {
297297+ mode = Void.RW;
298298+ build = Store.Build.Image config.image;
299299+ args = command;
300300+ (* TODO: extract with fetch *)
301301+ env = [];
302302+ cwd = "/";
303303+ user = (0, 0);
304304+ };
305305+ post = { diff = []; time = 0L };
250306 })
251307 s
252252- @@ fun (_, e) -> e
308308+ @@ fun e -> e
253309 in
254310 let build, env, (uid, gid) =
255255- match entry.build with
311311+ match entry.pre.build with
256312 | Store.Build.Image img ->
257257- let build, env, user = Store.fetch ctx img in
313313+ let build, env, user = Store.fetch ctx.store img in
258314 (build, env, Option.value ~default:(0, 0) user)
259259- | Store.Build.Build cid -> (cid, entry.env, entry.user)
315315+ | Store.Build.Build cid -> (cid, entry.pre.env, entry.pre.user)
316316+ in
317317+ let hash_entry =
318318+ {
319319+ entry with
320320+ pre = { entry.pre with build = Build build; args = command };
321321+ }
260322 in
261261- let hash_entry = { entry with build = Build build; args = command } in
262262- let new_cid = Store.cid (Repr.to_string History.t hash_entry) in
323323+ (* Store things under History.pre, this makes it possible to rediscover
324324+ the hash for something purely from the arguments needed to execute something
325325+ rather than needing, for example, the time it took to execute! *)
326326+ let new_cid = Store.cid (Repr.to_string History.pre_t hash_entry.pre) in
263327 let with_rootfs fn =
264264- if entry.mode = R then (Store.Run.with_build ctx build fn, [])
265265- else Store.Run.with_clone ctx ~src:build new_cid fn
328328+ if entry.pre.mode = R then (Store.Run.with_build ctx.store build fn, [])
329329+ else Store.Run.with_clone ctx.store ~src:build new_cid fn
266330 in
267331 try
268332 let new_entry, diff =
269269- with_rootfs @@ fun rootfs ->
270270- let spawn sw =
271271- if config.no_runc then
272272- let rootfs = Filename.concat rootfs "rootfs" in
273273- let void =
274274- Void.empty
275275- |> Void.rootfs ~mode:entry.mode rootfs
276276- |> Void.cwd entry.cwd
277277- (* TODO: Support UIDs |> Void.uid 1000 *)
278278- |> Void.exec ~env
279279- [
280280- config.shell;
281281- "-c";
282282- String.concat " " command ^ " && env > /tmp/shelter-env";
283283- ]
333333+ with_rootfs @@ function
334334+ | `Exists path ->
335335+ (* Copy the stdout log to stdout *)
336336+ let () =
337337+ Eio.Path.(with_open_in (fs / (path :> string) / "log"))
338338+ @@ fun ic -> Eio.Flow.copy ic stdout
284339 in
285285- `Void (Void.spawn ~sw void |> Void.exit_status)
286286- else
287287- let config =
288288- Runc.Json_config.
289289- {
290290- cwd = entry.cwd;
291291- argv =
292292- [
293293- config.shell;
294294- "-c";
295295- String.concat " " command ^ " && env > /tmp/shelter-env";
296296- ];
297297- hostname = "";
298298- network = [];
299299- user = (uid, gid);
300300- env = entry.env;
301301- entrypoint = None;
302302- }
340340+ let repo = S.repo store in
341341+ let c =
342342+ Eio.Path.(load (fs / (path :> string) / "hash"))
343343+ |> S.Hash.unsafe_of_raw_string |> S.Commit.of_hash repo
344344+ in
345345+ Ok (`Reset c)
346346+ | `Build rootfs ->
347347+ let spawn sw log =
348348+ if config.no_runc then
349349+ let rootfs = Filename.concat rootfs "rootfs" in
350350+ let void =
351351+ Void.empty
352352+ |> Void.rootfs ~mode:entry.pre.mode rootfs
353353+ |> Void.cwd entry.pre.cwd
354354+ (* TODO: Support UIDs |> Void.uid 1000 *)
355355+ |> Void.exec ~env
356356+ [
357357+ config.shell;
358358+ "-c";
359359+ String.concat " " command
360360+ ^ " && env > /tmp/shelter-env";
361361+ ]
362362+ in
363363+ `Void (Void.spawn ~sw void |> Void.exit_status)
364364+ else
365365+ let tool_mount : Runc.Json_config.mount =
366366+ {
367367+ ty = `Bind;
368368+ src = ctx.tool_dir;
369369+ dst = "/shelter-tools";
370370+ readonly = true;
371371+ }
372372+ in
373373+ let config =
374374+ Runc.Json_config.
375375+ {
376376+ cwd = entry.pre.cwd;
377377+ argv =
378378+ [
379379+ config.shell;
380380+ "-c";
381381+ String.concat " " command
382382+ ^ " && env > /tmp/shelter-env";
383383+ ];
384384+ hostname = "";
385385+ network = [ "host" ];
386386+ user = (uid, gid);
387387+ env = entry.pre.env;
388388+ mounts = [ tool_mount ];
389389+ entrypoint = None;
390390+ }
391391+ in
392392+ let env =
393393+ object
394394+ method fs = fs
395395+ method proc = proc
396396+ method stdout = stdout
397397+ end
398398+ in
399399+ `Runc (Runc.spawn ~sw log env config rootfs)
400400+ in
401401+ Switch.run @@ fun sw ->
402402+ let log =
403403+ Eio.Path.open_out ~sw ~create:(`If_missing 0o644)
404404+ Eio.Path.(fs / rootfs / "log")
405405+ in
406406+ let res = spawn sw log in
407407+ let start = Mtime_clock.now () in
408408+ let res =
409409+ match res with
410410+ | `Runc r -> Eio.Process.await r
411411+ | `Void v -> Void.to_eio_status (Eio.Promise.await v)
412412+ in
413413+ let stop = Mtime_clock.now () in
414414+ let span = Mtime.span start stop in
415415+ let time = Mtime.Span.to_uint64_ns span in
416416+ (* Add command to history regardless of exit status *)
417417+ let _ : (unit, string) result =
418418+ LNoise.history_add (String.concat " " command)
303419 in
304304- `Runc (Runc.spawn ~sw fs proc config rootfs)
305305- in
306306- Switch.run @@ fun sw ->
307307- let res = spawn sw in
308308- let start = Mtime_clock.now () in
309309- let res =
310310- match res with
311311- | `Runc r -> Eio.Process.await r
312312- | `Void v -> Void.to_eio_status (Eio.Promise.await v)
313313- in
314314- let stop = Mtime_clock.now () in
315315- let span = Mtime.span start stop in
316316- let time = Mtime.Span.to_uint64_ns span in
317317- (* Add command to history regardless of exit status *)
318318- let _ : (unit, string) result =
319319- LNoise.history_add (String.concat " " command)
320320- in
321321- if res = `Exited 0 then (
322322- (* Extract env *)
323323- let env_path =
324324- Eio.Path.(fs / rootfs / "rootfs" / "tmp" / "shelter-env")
325325- in
326326- let env =
327327- Eio.Path.(load env_path)
328328- |> String.split_on_char '\n'
329329- |> List.filter (fun s -> not (String.equal "" s))
330330- in
331331- Eio.Path.unlink env_path;
332332- let cwd =
333333- List.find_map
334334- (fun v ->
335335- match Astring.String.cut ~sep:"=" v with
336336- | Some ("PWD", dir) -> Some dir
337337- | _ -> None)
338338- env
339339- |> Option.value ~default:hash_entry.cwd
340340- in
341341- if entry.mode = RW then
342342- Ok
343343- {
344344- hash_entry with
345345- build = Build new_cid;
346346- time;
347347- env;
348348- cwd;
349349- user = (uid, gid);
350350- }
351351- else Ok { hash_entry with time; cwd; env; user = (uid, gid) })
352352- else Error (Eio.Process.Child_error res)
420420+ if res = `Exited 0 then (
421421+ (* Extract env *)
422422+ let env_path =
423423+ Eio.Path.(fs / rootfs / "rootfs" / "tmp" / "shelter-env")
424424+ in
425425+ let env =
426426+ Eio.Path.(load env_path)
427427+ |> String.split_on_char '\n'
428428+ |> List.filter (fun s -> not (String.equal "" s))
429429+ in
430430+ Eio.Path.unlink env_path;
431431+ let cwd =
432432+ List.find_map
433433+ (fun v ->
434434+ match Astring.String.cut ~sep:"=" v with
435435+ | Some ("PWD", dir) -> Some dir
436436+ | _ -> None)
437437+ env
438438+ |> Option.value ~default:hash_entry.pre.cwd
439439+ in
440440+ if entry.pre.mode = RW then
441441+ Ok
442442+ (`Entry
443443+ ( {
444444+ hash_entry with
445445+ History.pre =
446446+ {
447447+ hash_entry.pre with
448448+ build = Build new_cid;
449449+ env;
450450+ cwd;
451451+ user = (uid, gid);
452452+ };
453453+ },
454454+ rootfs ))
455455+ else
456456+ Ok
457457+ (`Entry
458458+ ( {
459459+ pre =
460460+ { hash_entry.pre with cwd; env; user = (uid, gid) };
461461+ post = { hash_entry.post with time };
462462+ },
463463+ rootfs )))
464464+ else Error (Eio.Process.Child_error res)
353465 in
354466 match new_entry with
355467 | Error e -> Error e
356356- | Ok entry ->
468468+ | Ok (`Reset None) ->
469469+ Fmt.epr "Resetting to existing entry failed...\n%!";
470470+ Ok (s, ctx)
471471+ | Ok (`Reset (Some c)) ->
472472+ S.Head.set store c;
473473+ Ok (s, ctx)
474474+ | Ok (`Entry (entry, path)) ->
357475 (* Set diff *)
358358- let entry = { entry with diff } in
476476+ let entry = { entry with post = { entry.post with diff } } in
359477 (* Commit if RW *)
360360- if entry.mode = RW then
478478+ if entry.pre.mode = RW then (
361479 commit
362480 ~message:("exec " ^ String.concat " " command)
363481 clock s entry;
482482+ (* Save the commit hash for easy restoring later *)
483483+ let hash =
484484+ S.Head.get store |> S.Commit.hash |> S.Hash.to_raw_string
485485+ in
486486+ Eio.Path.save ~create:(`If_missing 0o644)
487487+ Eio.Path.(fs / path / "hash")
488488+ hash);
364489 Ok (s, ctx)
365490 with Eio.Exn.Io (Eio.Process.E e, _) -> Error e)
+6-3
src/lib/shelter/shelter_main.mli
···11module Store = Store
2233module History : sig
44- type t = {
44+ type post = { diff : Diff.t; time : int64 } [@@deriving repr]
55+66+ type pre = {
57 mode : Void.mode;
68 build : Store.Build.t;
79 args : string list;
88- time : int64;
910 env : string list;
1011 cwd : string;
1112 user : int * int;
1212- diff : Diff.t;
1313 }
1414 [@@deriving repr]
1515+ (** Needed for execution *)
1616+1717+ type t = { pre : pre; post : post } [@@deriving repr]
15181619 include Irmin.Contents.S with type t := t
1720end
+19-5
src/lib/shelter/store.ml
···3030 val builds : string -> dataset
3131 val build : string -> string -> dataset
3232 val snapshot : dataset -> snapshot
3333+ val tools : string -> dataset
3434+ val tool : string -> string -> dataset
3335end = struct
3436 type dataset = string
3537 type snapshot = string
···3840 let builds pool : dataset = pool / "builds"
3941 let build pool path : dataset = builds pool / path
4042 let snapshot ds = ds ^ "@snappy"
4343+ let tools pool : dataset = pool / "tools"
4444+ let tool pool path : dataset = tools pool / path
4145end
42464347let with_dataset ?(typ = Zfs.Types.filesystem) t dataset f =
···7882 }
7983 in
8084 create_and_mount t (Datasets.builds t.pool);
8585+ create_and_mount t (Datasets.tools t.pool);
8186 t
82878388let snapshot t (snap : Datasets.snapshot) =
···160165 let ds = Datasets.build t.pool (Cid.to_string cid) in
161166 Fun.protect ~finally:(fun () -> unmount_dataset t ds) @@ fun () ->
162167 mount_dataset t ds;
168168+ fn (`Build ("/" ^ (ds :> string)))
169169+170170+ let with_tool t cid fn =
171171+ let ds = Datasets.tool t.pool (Cid.to_string cid) in
172172+ Fun.protect ~finally:(fun () -> unmount_dataset t ds) @@ fun () ->
173173+ mount_dataset t ds;
163174 fn ("/" ^ (ds :> string))
164175165176 let with_clone t ~src new_cid fn =
···167178 let tgt = Datasets.build t.pool (Cid.to_string new_cid) in
168179 let src_snap = Datasets.snapshot ds in
169180 let tgt_snap = Datasets.snapshot tgt in
170170- clone t src_snap tgt;
171171- let v = with_build t new_cid fn in
172172- snapshot t tgt_snap;
173173- let d = diff t src_snap tgt_snap in
174174- (v, d)
181181+ if Zfs.exists t.zfs (tgt :> string) Zfs.Types.dataset then
182182+ (fn (`Exists ("/" ^ (tgt :> string))), diff t src_snap tgt_snap)
183183+ else (
184184+ clone t src_snap tgt;
185185+ let v = with_build t new_cid fn in
186186+ snapshot t tgt_snap;
187187+ let d = diff t src_snap tgt_snap in
188188+ (v, d))
175189end