Select the types of activity you want to include in your feed.
Process Groups
A major overhaul of the way that we execute pipelines. Here, we make sure to add all of the commands to a relevant process group which will be important for implementing asynchronous jobs later.
···11let cwd () = Eio_unix.run_in_systhread ~label:"cwd" @@ fun () -> Unix.getcwd ()
2233-let chdir p =
44- try
55- let dir =
66- Eio_unix.run_in_systhread ~label:"chdir" @@ fun () -> Unix.chdir p
77- in
88- Exit.zero dir
99- with Unix.Unix_error (Unix.ENOENT, _, _) ->
1010- Exit.nonzero_msg () "no such file or directory: %s" p
1111-123let env () =
134 Eio_unix.run_in_systhread ~label:"env" @@ fun () ->
145 Unix.environment ()
+107-53
src/lib/eval.ml
···66open Exit.Syntax
7788module Options = struct
99- type t = { noclobber : bool }
99+ type t = { noclobber : bool; pipefail : bool }
10101111- let default = { noclobber = false }
1111+ let default = { noclobber = false; pipefail = false }
12121313- let with_options ?noclobber t =
1414- { noclobber = Option.value ~default:t.noclobber noclobber }
1313+ let with_options ?noclobber ?pipefail t =
1414+ {
1515+ noclobber = Option.value ~default:t.noclobber noclobber;
1616+ pipefail = Option.value ~default:t.pipefail pipefail;
1717+ }
1518end
16191720(** An evaluator over the AST *)
···2124 It starts from point (4), completing a series of expansions on the AST,
2225 then redirection is setup, and finally functions/built-ins/commands are
2326 executed. *)
2727+2828+ module J = Job.Make (E)
24292530 class default_map =
2631 object (_)
···172177 match path with
173178 | Some p ->
174179 let fp = Fpath.append cwd (Fpath.v p) in
175175- Exit.map' (Eunix.chdir p)
176176- ~zero:(fun () -> S.set_cwd ctx.state fp)
177177- ~nonzero:(fun () -> ctx.state)
180180+ Exit.zero @@ S.set_cwd ctx.state fp
178181 | None -> (
179182 match Eunix.find_env "HOME" with
180183 | None -> Exit.nonzero_msg ctx.state "HOME not set"
···182185 in
183186 { ctx with state }
184187 | Pwd ->
185185- Fmt.pr "%s\n%!" (Eunix.cwd ());
188188+ Fmt.pr "%a\n%!" Fpath.pp (S.cwd ctx.state);
186189 Exit.zero ctx
187190 | Exit n ->
188191 let should_exit =
···204207 List.fold_left (fun acc (k, _) -> List.remove_assoc k acc) env extra
205208 |> List.append extra
206209207207- let rec execute_commands initial_ctx local_switch p =
208208- let rec loop (exit_ctx : ctx Exit.t)
209209- (stdout_of_previous : Eio_unix.source_ty Eio_unix.source option) :
210210- Ast.command list -> ctx Exit.t =
210210+ let rec execute_commands initial_ctx pipeline_switch p : ctx Exit.t =
211211+ let handle_job ~pgid j p =
212212+ match (j, p) with
213213+ | None, _ ->
214214+ Option.some
215215+ @@ J.make ~state:`Running ~bang:false pgid (Nlist.Singleton p)
216216+ | Some j, `Process p -> Option.some @@ J.add_process p j
217217+ | Some j, `Built_in p -> Option.some @@ J.add_built_in p j
218218+ in
219219+ let rec loop (ctx : ctx) (job : J.t option)
220220+ ((pgid, stdout_of_previous) :
221221+ int * Eio_unix.source_ty Eio_unix.source option) :
222222+ Ast.command list -> ctx * J.t option =
211223 fun c ->
212212- let ctx = Exit.value exit_ctx in
213224 match c with
214225 | Ast.SimpleCommand (Prefixed (prefix, None, _suffix)) :: rest ->
215226 let ctx = collect_assignments ctx prefix in
216216- loop (Exit.zero ctx) stdout_of_previous rest
227227+ loop ctx job (pgid, stdout_of_previous) rest
217228 | Ast.SimpleCommand (Prefixed (prefix, Some executable, suffix)) :: rest
218229 ->
219230 let ctx = collect_assignments ~update:false ctx prefix in
220220- loop (Exit.zero ctx) stdout_of_previous
231231+ loop ctx job (pgid, stdout_of_previous)
221232 (Ast.SimpleCommand (Named (executable, suffix)) :: rest)
222233 | Ast.SimpleCommand (Named (executable, None)) :: rest -> (
223234 let ctx, executable = expand_cst ctx executable in
···225236 Built_ins.of_args
226237 [ handle_word_components_to_string ctx executable ]
227238 with
228228- | Some bi -> handle_built_in ctx bi
239239+ | Some bi ->
240240+ let ctx = handle_built_in ctx bi in
241241+ let built_in = ctx >|= fun _ -> () in
242242+ (Exit.value ctx, handle_job ~pgid job (`Built_in built_in))
229243 | None -> (
230244 let some_read, some_write =
231231- stdout_for_pipeline ctx ~sw:local_switch rest
245245+ stdout_for_pipeline ctx ~sw:pipeline_switch rest
232246 in
233247 match stdout_of_previous with
234248 | None ->
235249 let executable =
236250 handle_word_components_to_string ctx executable
237251 in
238238- let res =
239239- E.exec ctx.executor ?stdout:some_write ~cwd:(cwd_of_ctx ctx)
240240- ~env:(get_env ~extra:ctx.local_state ())
241241- [ executable ]
242242- >|= fun () -> clear_local_state ctx
252252+ let ctx, job =
253253+ let process =
254254+ E.exec ctx.executor ?stdout:some_write ~pgid
255255+ ~sw:pipeline_switch ~cwd:(cwd_of_ctx ctx)
256256+ ~env:(get_env ~extra:ctx.local_state ())
257257+ [ executable ]
258258+ in
259259+ let job = handle_job ~pgid job (`Process process) in
260260+ (clear_local_state ctx, job)
243261 in
244262 Option.iter Eio.Flow.close some_write;
245245- loop res some_read rest
263263+ loop ctx job (pgid, some_read) rest
246264 | Some stdout ->
247265 let executable =
248266 handle_word_components_to_string ctx executable
249267 in
250250- let res =
251251- E.exec ctx.executor ~stdin:stdout ?stdout:some_write
252252- ~env:(get_env ~extra:ctx.local_state ())
253253- ~cwd:(cwd_of_ctx ctx) [ executable ]
254254- >|= fun () -> clear_local_state ctx
268268+ let ctx, job =
269269+ let process =
270270+ E.exec ctx.executor ~stdin:stdout ~pgid
271271+ ~sw:pipeline_switch ?stdout:some_write
272272+ ~env:(get_env ~extra:ctx.local_state ())
273273+ ~cwd:(cwd_of_ctx ctx) [ executable ]
274274+ in
275275+ let job = handle_job ~pgid job (`Process process) in
276276+ (clear_local_state ctx, job)
255277 in
256278 Option.iter Eio.Flow.close some_write;
257257- loop res some_read rest))
279279+ loop ctx job (pgid, some_read) rest))
258280 | Ast.SimpleCommand (Named (executable, Some suffix)) :: rest -> (
259281 let ctx, executable = expand_cst ctx executable in
260282 let ctx, suffix = expand_redirects (ctx, []) suffix in
···263285 Built_ins.of_args
264286 (handle_word_components_to_string ctx executable :: args)
265287 with
266266- | Some bi -> handle_built_in ctx bi
288288+ | Some bi ->
289289+ let ctx = handle_built_in ctx bi in
290290+ let built_in = ctx >|= fun _ -> () in
291291+ (Exit.value ctx, handle_job ~pgid job (`Built_in built_in))
267292 | None -> (
268293 let redirect =
269294 List.fold_left
270295 (fun acc -> function
271296 | Ast.Suffix_word _ -> acc
272297 | Ast.Suffix_redirect rdr ->
273273- handle_one_redirection ~sw:local_switch ctx rdr :: acc)
298298+ handle_one_redirection ~sw:pipeline_switch ctx rdr
299299+ :: acc)
274300 [] suffix
275301 |> List.rev |> List.filter_map Fun.id
276302 in
277303 let some_read, some_write =
278278- stdout_for_pipeline ~sw:local_switch ctx rest
304304+ stdout_for_pipeline ~sw:pipeline_switch ctx rest
279305 in
280306 match stdout_of_previous with
281307 | None ->
282282- let res =
283283- E.exec ~fds:redirect ctx.executor ?stdout:some_write
284284- ~cwd:(cwd_of_ctx ctx)
285285- ~env:(get_env ~extra:ctx.local_state ())
286286- (handle_word_components_to_string ctx executable :: args)
287287- >|= fun () -> clear_local_state ctx
308308+ let executable =
309309+ handle_word_components_to_string ctx executable
310310+ in
311311+ let ctx, job =
312312+ let process =
313313+ E.exec ctx.executor ?stdout:some_write ~pgid
314314+ ~sw:pipeline_switch ~fds:redirect ~cwd:(cwd_of_ctx ctx)
315315+ ~env:(get_env ~extra:ctx.local_state ())
316316+ (executable :: args)
317317+ in
318318+ let job = handle_job ~pgid job (`Process process) in
319319+ (clear_local_state ctx, job)
288320 in
289321 Option.iter Eio.Flow.close some_write;
290290- loop res some_read rest
322322+ loop ctx job (pgid, some_read) rest
291323 | Some stdout ->
292292- let res =
293293- E.exec ~fds:redirect ctx.executor ~stdin:stdout
294294- ~cwd:(cwd_of_ctx ctx) ?stdout:some_write
295295- ~env:(get_env ~extra:ctx.local_state ())
296296- (handle_word_components_to_string ctx executable :: args)
297297- >|= fun () -> clear_local_state ctx
324324+ let executable =
325325+ handle_word_components_to_string ctx executable
326326+ in
327327+ let ctx, job =
328328+ let process =
329329+ E.exec ctx.executor ~stdin:stdout ~pgid
330330+ ~sw:pipeline_switch ?stdout:some_write ~fds:redirect
331331+ ~env:(get_env ~extra:ctx.local_state ())
332332+ ~cwd:(cwd_of_ctx ctx) (executable :: args)
333333+ in
334334+ let job = handle_job ~pgid job (`Process process) in
335335+ (clear_local_state ctx, job)
298336 in
299337 Option.iter Eio.Flow.close some_write;
300300- loop res some_read rest))
338338+ loop ctx job (pgid, some_read) rest))
301339 | CompoundCommand (c, rdrs) :: _rest ->
302340 let _rdrs =
303303- List.map (handle_one_redirection ~sw:local_switch ctx) rdrs
341341+ List.map (handle_one_redirection ~sw:pipeline_switch ctx) rdrs
304342 in
305305- let ctx = handle_compound_command ctx c in
306306- ctx
343343+ (* TODO: No way this is right *)
344344+ (Exit.value @@ handle_compound_command ctx c, job)
307345 | v :: _ ->
308346 Fmt.epr "TODO: %a" Yojson.Safe.pp (Ast.command_to_yojson v);
309347 failwith "Err"
310310- | [] -> exit_ctx
348348+ | [] -> (ctx, job)
311349 in
312312- loop (Exit.zero initial_ctx) None p
350350+ (* HACK: when running the pipeline, we need a process group to
351351+ put everything in. Eio's model of execution is nice, but we cannot
352352+ safely delay execution of a process. So instead we create a ghost
353353+ process that last just until all of the processes are setup. *)
354354+ let ctx, job =
355355+ Eio.Switch.run @@ fun ghost_switch ->
356356+ let ghost_process =
357357+ E.exec ~sw:ghost_switch ~pgid:0 ~cwd:(cwd_of_ctx initial_ctx)
358358+ initial_ctx.executor [ "sleep"; "99999999" ]
359359+ in
360360+ loop initial_ctx None (E.pid ghost_process, None) p
361361+ in
362362+ match job with
363363+ | None -> Exit.zero ctx
364364+ | Some job -> J.await_exit ~pipefail:false job >|= fun () -> ctx
313365314366 and expand_cst (ctx : ctx) cst =
315367 let cst = tilde_expansion ctx cst in
···372424 in
373425 fold (Noand_or, Exit.zero ctx) c
374426375375- and handle_for_clause ctx = function
427427+ and handle_for_clause ctx v : ctx Exit.t =
428428+ match v with
376429 | Ast.For_Name_DoGroup (_, (term, sep)) -> exec ctx (term, Some sep)
377430 | Ast.For_Name_In_WordList_DoGroup (Name name, wdlist, (term, sep)) ->
378431 let wdlist = Nlist.flatten @@ Nlist.map (word_glob_expand ctx) wdlist in
···408461 | Exit.Zero ctx -> exec ctx (e2, Some sep2)
409462 | Exit.Nonzero { value = ctx; _ } -> handle_else_part ctx else_part)
410463411411- and handle_compound_command ctx = function
464464+ and handle_compound_command ctx v : ctx Exit.t =
465465+ match v with
412466 | Ast.ForClause fc -> handle_for_clause ctx fc
413467 | Ast.IfClause if_ -> handle_if_clause ctx if_
414468 | _ as c ->
+6
src/lib/import.ml
···33module Nlist = struct
44 type 'a t = Singleton of 'a | Cons of 'a * 'a t
5566+ let hd = function Singleton s -> s | Cons (s, _) -> s
77+88+ let rec length = function
99+ | Singleton _ -> 1
1010+ | Cons (_, rest) -> 1 + length rest
1111+612 let rec of_list = function
713 | [] -> invalid_arg "Empty list"
814 | [ x ] -> Singleton x
+28
src/lib/job.ml
···11+open Import
22+33+module Make (E : Types.Exec) = struct
44+ type t = {
55+ state : [ `Running ];
66+ id : int;
77+ bang : bool;
88+ (* Process list is in reverse order *)
99+ processes : [ `Process of E.process | `Built_in of unit Exit.t ] Nlist.t;
1010+ }
1111+1212+ let make ?(state = `Running) ~bang id processes =
1313+ { state; id; processes; bang }
1414+1515+ let add_process proc t =
1616+ { t with processes = Nlist.cons (`Process proc) t.processes }
1717+1818+ let add_built_in b t =
1919+ { t with processes = Nlist.cons (`Built_in b) t.processes }
2020+2121+ (* Section 2.9.2 https://pubs.opengroup.org/onlinepubs/9799919799/ *)
2222+ let await_exit ~pipefail t =
2323+ let await = function `Process p -> E.await p | `Built_in b -> b in
2424+ match (pipefail, t.bang) with
2525+ | false, false -> await (Nlist.hd t.processes)
2626+ | false, true -> await (Nlist.hd t.processes) |> Exit.not
2727+ | _ -> Fmt.failwith "TODO: pipefail"
2828+end
+6-3
src/lib/posix/exec.ml
···9191 Eio_unix.Private.Fork_action.
9292 { run = (fun k -> k (Obj.repr (action_dups, plan, blocking))) }
93939494-let spawn_unix () ~sw ?pgid ?uid ?gid ~env ~fds ~executable ~cwd args =
9494+let spawn_unix () ~sw ~fork_actions ?pgid ?uid ?gid ~env ~fds ~executable ~cwd
9595+ args =
9596 let open Eio_posix in
9697 let actions =
9798 [
···115116 | None -> actions
116117 | Some gid -> Eio_unix.Private.Fork_action.setgid gid :: actions
117118 in
119119+ let actions = actions @ fork_actions in
118120 let with_actions cwd fn =
119121 let ((dir, path) : Eio.Fs.dir_ty Eio.Path.t) = cwd in
120122 match Eio_posix__.Fs.as_posix_dir dir with
···140142141143let pp_redirections ppf (i, fd, _) = Fmt.pf ppf "(%i,%a)" i Eio_unix.Fd.pp fd
142144143143-let run ~sw _ ?stdin ?stdout ?stderr ?(fds = []) ~cwd ?env ?executable args =
145145+let run ~sw _ ?stdin ?stdout ?stderr ?(fds = []) ?(fork_actions = []) ~pgid ~cwd
146146+ ?env ?executable args =
144147 with_close_list @@ fun to_close ->
145148 let check_fd n = function
146149 | Merry.Types.Redirect (m, _, _) -> Int.equal n m
···183186 let fds = std_fds @ fds in
184187 let executable = get_executable executable ~args in
185188 let env = get_env env in
186186- spawn_unix ~sw ~cwd ~fds ~env ~executable () args
189189+ spawn_unix ~sw ~fork_actions ~cwd ~pgid ~fds ~env ~executable () args
+14-9
src/lib/posix/merry_posix.ml
···4455module Exec = struct
66 type t = { mgr : Eio_unix.Process.mgr_ty Eio_unix.Process.mgr }
77- type fork_action = unit
77+ type process = Eio_unix.Process.ty Eio_unix.Process.t
8899- let exec ?fork_actions:_ ?(fds = []) ?stdin ?stdout ?stderr ?env ~cwd t args =
1010- Eio.Switch.run @@ fun sw ->
99+ let pid = Eio.Process.pid
1010+ let signal v i = Eio.Process.signal v i
1111+1212+ let await v =
1313+ Eio.Process.await v |> function
1414+ | `Exited 0 -> Merry.Exit.zero ()
1515+ | `Exited n -> Merry.Exit.nonzero () n
1616+ | `Signaled n -> Merry.Exit.nonzero () n
1717+1818+ let exec ?(fork_actions = []) ?(fds = []) ?stdin ?stdout ?stderr ?env ~sw
1919+ ~pgid ~cwd t args : process =
1120 let env =
1221 Option.map
1322 (fun lst -> List.map (fun (a, b) -> a ^ "=" ^ b) lst |> Array.of_list)
1423 env
1524 in
1616- Exec.run ~sw ~fds ~cwd ?stdin ?stdout ?stderr ?env t args
1717- |> Eio.Process.await
1818- |> function
1919- | `Exited 0 -> Merry.Exit.zero ()
2020- | `Exited n -> Merry.Exit.nonzero () n
2121- | `Signaled n -> Merry.Exit.nonzero () n
2525+ Exec.run ~fork_actions ~sw ~fds ~pgid ~cwd ?stdin ?stdout ?stderr ?env t
2626+ args
2227end
+10-5
src/lib/types.ml
···3737 type t
3838 (** An executor for commands *)
39394040- type fork_action
4141- (** A fork action is a piece of C-code to run inbetween the fork and the exec
4242- *)
4040+ type process
4141+4242+ val signal : process -> int -> unit
4343+ val pid : process -> int
43444445 val exec :
4545- ?fork_actions:fork_action list ->
4646+ ?fork_actions:Eio_unix__.Fork_action.t list ->
4647 ?fds:redirect list ->
4748 ?stdin:_ Eio.Flow.source ->
4849 ?stdout:_ Eio.Flow.sink ->
4950 ?stderr:_ Eio.Flow.sink ->
5051 ?env:(string * string) list ->
5252+ sw:Eio.Switch.t ->
5353+ pgid:int ->
5154 cwd:Eio.Fs.dir_ty Eio.Path.t ->
5255 t ->
5356 string list ->
5454- unit Exit.t
5757+ process
5558 (** Run a command in a child process *)
5959+6060+ val await : process -> unit Exit.t
5661end
+34
test/pipelines.t
···11+Some more tricky parts of pipelines.
22+33+Under normal execution, only the very last command matters in terms of exit code!
44+55+ $ mkdir hello
66+ $ sh -c "ls -j"
77+ ls: invalid option -- 'j'
88+ Try 'ls --help' for more information.
99+ [2]
1010+ $ sh -c "ls -j | ls"
1111+ ls: invalid option -- 'j'
1212+ Try 'ls --help' for more information.
1313+ hello
1414+ $ osh -c "ls -j"
1515+ ls: invalid option -- 'j'
1616+ Try 'ls --help' for more information.
1717+ [2]
1818+ $ osh -c "ls -j | ls"
1919+ ls: invalid option -- 'j'
2020+ Try 'ls --help' for more information.
2121+ hello
2222+2323+And an exclaimation point should invert that.
2424+2525+ $ sh -c "! ls -j | ls"
2626+ ls: invalid option -- 'j'
2727+ Try 'ls --help' for more information.
2828+ hello
2929+ [1]
3030+ $ osh -c "! ls -j | ls"
3131+ ls: invalid option -- 'j'
3232+ Try 'ls --help' for more information.
3333+ hello
3434+ [1]