diff --git a/src/lib/posix/dune b/src/lib/posix/dune deleted file mode 100644 index 014f6e0..0000000 --- a/src/lib/posix/dune +++ /dev/null @@ -1,4 +0,0 @@ -(library - (name merry_posix) - (public_name merry.posix) - (libraries merry eio_posix eio.unix)) diff --git a/src/lib/posix/exec.ml b/src/lib/posix/exec.ml deleted file mode 100644 index ecd50e5..0000000 --- a/src/lib/posix/exec.ml +++ /dev/null @@ -1,315 +0,0 @@ -(* Much of this code is from Eio_posix. - - 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. *) - -open Eio.Std - -module Process = struct - type t = { - pid : int; - exit_status : Unix.process_status Promise.t; - lock : Mutex.t; - } - (* When [lock] is unlocked, [exit_status] is resolved iff the process has been reaped. *) - - let exit_status t = t.exit_status - let pid t = t.pid - - module Fork_action = Eio_unix.Private.Fork_action - - (* Read a (typically short) error message from a child process. *) - let read_response fd = - let buf = Bytes.create 256 in - match Eio_posix.Low_level.read fd buf 0 (Bytes.length buf) with - | 0 -> "" - | n -> Bytes.sub_string buf 0 n - - let with_pipe fn = - Switch.run ~name:"process-pipe" @@ fun sw -> - let r, w = Eio_posix.Low_level.pipe ~sw in - fn r w - - let signal t signal = - (* We need the lock here so that one domain can't signal the process exactly as another is reaping it. *) - Mutex.lock t.lock; - Fun.protect ~finally:(fun () -> Mutex.unlock t.lock) @@ fun () -> - if not (Promise.is_resolved t.exit_status) then Unix.kill t.pid signal - (* else process has been reaped and t.pid is invalid *) - - external eio_spawn : - Unix.file_descr -> Eio_unix.Private.Fork_action.c_action list -> int - = "caml_eio_posix_spawn" - - (* Wait for [pid] to exit and then resolve [exit_status] to its status. *) - let reap t exit_status = - Eio.Condition.loop_no_mutex Eio_unix.Process.sigchld (fun () -> - Mutex.lock t.lock; - match Unix.waitpid [ WNOHANG ] t.pid with - | 0, _ -> - Mutex.unlock t.lock; - None (* Not ready; wait for next SIGCHLD *) - | p, status -> - assert (p = t.pid); - Promise.resolve exit_status status; - Mutex.unlock t.lock; - Some ()) - - let iter_switch ~f = function - | Merry.Types.Async -> () - | Merry.Types.Switched sw -> f sw - - let spawn ?delay_reap ~mode actions = - with_pipe @@ fun errors_r errors_w -> - Eio_unix.Private.Fork_action.with_actions actions @@ fun c_actions -> - iter_switch ~f:Switch.check mode; - let exit_status, set_exit_status = Promise.create ~label:"spawn" () in - let t = - let pid = - Eio_unix.Fd.use_exn "errors-w" errors_w @@ fun errors_w -> - Eio.Private.Trace.with_span "spawn" @@ fun () -> - eio_spawn errors_w c_actions - in - Eio_unix.Fd.close errors_w; - { pid; exit_status; lock = Mutex.create () } - in - let () = - iter_switch - ~f:(fun sw -> - let hook = - Switch.on_release_cancellable sw (fun () -> - (* Kill process (if still running) *) - signal t Sys.sigkill; - (* The switch is being released, so either the daemon fiber got - cancelled or it hasn't started yet (and never will start). *) - if not (Promise.is_resolved t.exit_status) then - (* Do a (non-cancellable) waitpid here to reap the child. *) - reap t set_exit_status) - in - Fiber.fork_daemon ~sw (fun () -> - Merry.Import.Trace.name "reap-daemon"; - Option.iter Eio.Promise.await delay_reap; - reap t set_exit_status; - Switch.remove_hook hook; - `Stop_daemon)) - mode - in - (* 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 -end - -module Process_impl = struct - type t = Process.t - type tag = [ `Generic | `Unix ] - - let pid = Process.pid - - let await t = - match Eio.Promise.await @@ Process.exit_status t with - | Unix.WEXITED i -> `Exited i - | Unix.WSIGNALED i -> `Signaled (128 + Sys.signal_to_int i) - | Unix.WSTOPPED _ -> assert false - - let signal = Process.signal -end - -let process = - let handler = Eio.Process.Pi.process (module Process_impl) in - fun proc -> Eio.Resource.T (proc, handler) - -let read_of_fd ~mode ~default ~to_close ~pipe v = - match (mode, v) with - | Merry.Types.Async, _ | _, None -> default - | Merry.Types.Switched sw, Some f -> ( - match Eio_unix.Resource.fd_opt f with - | Some fd -> fd - | None -> - let r, w = pipe sw in - Fiber.fork ~sw (fun () -> - Merry.Import.Trace.name "read_of_fd"; - Eio.Flow.copy f w; - Eio.Flow.close w); - let r = Eio_unix.Resource.fd r in - to_close := r :: !to_close; - r) - -let write_of_fd ~mode ~default ~to_close ~pipe v = - match (mode, v) with - | Merry.Types.Async, _ | _, None -> default - | Merry.Types.Switched sw, Some f -> ( - match Eio_unix.Resource.fd_opt f with - | Some fd -> fd - | None -> - let r, w = pipe sw in - Fiber.fork ~sw (fun () -> - Merry.Import.Trace.name "write_of_fd"; - Eio.Flow.copy r f; - Eio.Flow.close r); - let w = Eio_unix.Resource.fd w in - to_close := w :: !to_close; - w) - -let with_close_list fn = - let to_close = ref [] in - let close () = List.iter Eio_unix.Fd.close !to_close in - match fn to_close with - | x -> - close (); - x - | exception ex -> - let bt = Printexc.get_raw_backtrace () in - close (); - Printexc.raise_with_backtrace ex bt - -let get_executable ~args = function - | Some exe -> exe - | None -> ( - match args with - | [] -> invalid_arg "Arguments list is empty and no executable given!" - | x :: _ -> x) - -let get_env = function Some e -> e | None -> Unix.environment () - -external action_dups : unit -> Eio_unix.Private.Fork_action.fork_fn - = "eio_unix_fork_dups" - -let action_dups = action_dups () - -let rec with_fds mapping k = - match mapping with - | [] -> k [] - | (dst, src, _) :: xs -> - Eio_unix.Fd.use ~if_closed:(fun () -> with_fds xs @@ fun xs -> k xs) src - @@ fun src -> - with_fds xs @@ fun xs -> k ((dst, (Obj.magic src : int)) :: xs) - -let inherit_fds m = - let blocking = - m - |> List.filter_map (fun (dst, _, flags) -> - match flags with - | `Blocking -> Some (dst, true) - | `Nonblocking -> Some (dst, false) - | `Preserve_blocking -> None) - in - with_fds m @@ fun m -> - (* TODO: investigate -- the plan from Eio seems to also invert the list of redirections. - This is problematic for redirections, so we have copied the entire action here. *) - let plan = Eio_unix__.Inherit_fds.plan m in - Eio_unix.Private.Fork_action. - { run = (fun k -> k (Obj.repr (action_dups, plan, blocking))) } - -let spawn_unix () ?delay_reap ~mode ~fork_actions ?pgid ?uid ?gid ~env ~fds - ~executable ~cwd args = - let open Eio_posix in - let actions = - [ - inherit_fds fds; - Low_level.Process.Fork_action.execve executable ~argv:(Array.of_list args) - ~env; - ] - in - let actions = - match pgid with - | None -> actions - | Some pgid -> Low_level.Process.Fork_action.setpgid pgid :: actions - in - let actions = - match uid with - | None -> actions - | Some uid -> Eio_unix.Private.Fork_action.setuid uid :: actions - in - let actions = - match gid with - | None -> actions - | Some gid -> Eio_unix.Private.Fork_action.setgid gid :: actions - in - let actions = actions @ fork_actions in - let with_actions cwd fn = - let ((dir, path) : Eio.Fs.dir_ty Eio.Path.t) = cwd in - match Eio_posix__.Fs.as_posix_dir dir with - | None -> Fmt.invalid_arg "cwd is not an OS directory!" - | Some dirfd -> - Switch.run ~name:"spawn_unix" @@ fun launch_sw -> - let cwd = - Eio_posix__.Err.run - (fun () -> - let flags = Low_level.Open_flags.(rdonly + directory) in - Low_level.openat ~sw:launch_sw ~mode:0 dirfd path flags) - () - in - fn (Low_level.Process.Fork_action.fchdir cwd :: actions) - in - with_actions cwd @@ fun actions -> - process (Process.spawn ?delay_reap ~mode actions) - -let fd_equal_int fd i = - Eio_unix.Fd.use ~if_closed:(fun () -> true) fd @@ fun ufd -> - let ufd_int = (Obj.magic ufd : int) in - Int.equal i ufd_int - -let pp_redirections ppf (i, fd, _) = Fmt.pf ppf "(%i,%a)" i Eio_unix.Fd.pp fd - -let run ~mode ?delay_reap _ ?stdin ?stdout ?stderr ?(fds = []) - ?(fork_actions = []) ~pgid ~cwd ~pipe ?env ?executable args = - Sys.set_signal Sys.sigint Sys.Signal_default; - with_close_list @@ fun to_close -> - let check_fd n = function - | Merry.Types.Redirect (_, m, _, _) -> Int.equal n m - | Merry.Types.Close fd -> fd_equal_int fd n - in - let fd_exists n = List.exists (check_fd n) fds in - let std_fds = - (if fd_exists 0 then [] - else - [ - ( 0, - read_of_fd ~mode ~pipe stdin ~default:Eio_unix.Fd.stdin ~to_close, - `Blocking ); - ]) - @ (if fd_exists 1 then [] - else begin - [ - ( 1, - write_of_fd ~mode ~pipe stdout ~default:Eio_unix.Fd.stdout - ~to_close, - `Blocking ); - ] - end) - @ - if fd_exists 2 then [] - else - [ - ( 2, - write_of_fd ~mode stderr ~pipe ~default:Eio_unix.Fd.stderr ~to_close, - `Blocking ); - ] - in - let need_close, fds = - List.fold_left - (fun (cs, fs) -> function - | Merry.Types.Redirect (false, a, b, c) -> (cs, (a, b, c) :: fs) - | Close fd -> (fd :: cs, fs) - | _ -> (cs, fs)) - ([], []) fds - |> fun (cs, fs) -> (List.rev cs, List.rev fs) - in - List.iter Eio_unix.Fd.close need_close; - let fds = std_fds @ fds in - let executable = get_executable executable ~args in - let env = get_env env in - spawn_unix ?delay_reap ~mode ~fork_actions ~cwd ~pgid ~fds ~env ~executable () - args diff --git a/src/lib/posix/merry_posix.ml b/src/lib/posix/merry_posix.ml deleted file mode 100644 index 710836d..0000000 --- a/src/lib/posix/merry_posix.ml +++ /dev/null @@ -1,27 +0,0 @@ -(* A set of modules for writing shell's that adhere, as past they can, - to the POSIX standard: https://pubs.opengroup.org/onlinepubs/9699919799/ *) -module State = State - -module Exec = struct - type t = { mgr : Eio_unix.Process.mgr_ty Eio_unix.Process.mgr } - type process = Eio_unix.Process.ty Eio_unix.Process.t - - let pid = Eio.Process.pid - let signal v i = Eio.Process.signal v i - - let await v = - Eio.Process.await v |> function - | `Exited 0 -> Merry.Exit.zero () - | `Exited n -> Merry.Exit.nonzero () n - | `Signaled n -> Merry.Exit.nonzero () n - - let exec ?delay_reap ?(fork_actions = []) ?(fds = []) ?stdin ?stdout ?stderr - ?env ~mode ~pgid ~cwd ~pipe ~executable t args = - try - Ok - (Exec.run ?delay_reap ~pipe ~fork_actions ~mode ~fds ~pgid ~cwd ?stdin - ?stdout ?stderr ?env t ~executable args) - with Eio.Io (Eio.Process.E (Eio.Process.Executable_not_found m), _ctx) -> - Fmt.epr "msh: command not found: %s\n%!" m; - Error 127 -end diff --git a/src/lib/posix/state.ml b/src/lib/posix/state.ml deleted file mode 100644 index b0151bc..0000000 --- a/src/lib/posix/state.ml +++ /dev/null @@ -1,216 +0,0 @@ -open Merry -module Variables = Map.Make (String) -module Stack = Merry.Stack - -type attributes = { readonly : bool; id : string } - -let default_attribute = { readonly = false; id = "id" } - -type scopes = (attributes * Ast.fragments) Variables.t Stack.t - -type t = { - cwd : Fpath.t; - functions : Merry.Function.t list; - root : int; - outermost : bool; - home : string; - variables : (attributes * Ast.fragments) Variables.t; - exports : (attributes * Ast.fragments) Variables.t; - path : string list; - scopes : scopes; -} - -type variable = Ast.fragments - -let pp_attr ppf attr = - Fmt.pf ppf "{ readonly = %b; id = %a }" attr.readonly - Fmt.(quote string) - attr.id - -let pp_variable ppf (k, (attr, v)) = - Fmt.pf ppf "%s={ value = %a; attr = %a }" k - Import.Fmt.(lst Ast.Fragment.pp) - v pp_attr attr - -let pp_variables ppf vs = - Merry.Import.Fmt.(lst pp_variable) ppf (Variables.to_list vs) - -let update ?(id = "") ?(export = false) ?(readonly = false) ?(local = false) t - ~param (v : Ast.fragments) = - let add_non_local param = - (* First check if the variable has been defined locally *) - let is_local scopes = - match Stack.pop scopes with - | Some (h, rest) -> ( - match Variables.find_opt param h with - | Some v -> Some (v, h, rest) - | None -> None) - | None -> None - in - match is_local t.scopes with - | Some ((attr, _), vs, rest) -> - let variables' = Variables.add param (attr, v) vs in - Ok { t with scopes = Stack.push variables' rest } - | None -> ( - match Variables.find_opt param t.variables with - | Some ({ readonly = true; _ }, _) -> - Error (Fmt.str "%s: readonly variable" param) - | _ -> ( - match param with - | "PATH" -> - let path = - Ast.Fragment.join_list ~sep:"" v |> String.split_on_char ':' - in - Ok { t with path } - | _ -> - let attr = { readonly; id } in - if export then - let variables' = Variables.add param (attr, v) t.exports in - Ok { t with exports = variables' } - else - let variables' = Variables.add param (attr, v) t.variables in - Ok { t with variables = variables' })) - in - let add_local param = - let attr = default_attribute in - match Stack.pop t.scopes with - | Some (vs, rest) -> - let variables' = Variables.add param (attr, v) vs in - Ok { t with scopes = Stack.push variables' rest } - | None -> - let variables' = Variables.add param (attr, v) Variables.empty in - Ok { t with scopes = Stack.push variables' t.scopes } - in - if local then add_local param else add_non_local param - -let seed_env () = - let env = Merry.Eunix.env () in - List.fold_left - (fun vars (param, v) -> - let v = [ Ast.Fragment.make v ] in - Variables.add param (default_attribute, v) vars) - Variables.empty env - -let make ?(functions = []) ?(root = 0) ?(outermost = true) ?(home = "/root") - ?variables path cwd = - let exports = match variables with None -> seed_env () | Some v -> v in - { - cwd; - functions; - root; - outermost; - home; - exports; - variables = Variables.empty; - scopes = Stack.empty; - path; - } - -let pop t = - match Stack.pop t.scopes with - | None -> t - | Some (_, scopes) -> { t with scopes } - -let push t = - let scopes = Stack.push Variables.empty t.scopes in - { t with scopes } - -let cwd t = t.cwd -let set_cwd t cwd = { t with cwd } -let expand t = function `Tilde -> t.home -let path t = t.path -let path_as_variable t = [ Ast.Fragment.make (String.concat ":" t.path) ] - -let lookup_variables param t = - match param with - | "PATH" -> Some (path_as_variable t) - | _ -> ( - match Variables.find_opt param t.exports with - | None -> Variables.find_opt param t.variables |> Option.map snd - | Some (_, v) -> Some v) - -let lookup t ~param = - match Stack.peek t.scopes with - | None -> lookup_variables param t - | Some vs -> ( - let locals = Variables.find_opt param vs |> Option.map snd in - match locals with Some _ as v -> v | None -> lookup_variables param t) - -let remove ~param t = - let b, t = - match Variables.find_opt param t.variables with - | None -> (false, t) - | Some _ -> (true, { t with variables = Variables.remove param t.variables }) - in - match Variables.find_opt param t.exports with - | None -> (b || false, t) - | Some _ -> (true, { t with exports = Variables.remove param t.exports }) - -let remove_group ~id t = - let variables = - Variables.fold - (fun param ({ id = id'; _ }, _) vs -> - if String.equal id id' then begin - Variables.remove param vs - end - else begin - vs - end) - t.variables t.variables - in - let exports = - Variables.fold - (fun param ({ id = id'; _ }, _) vs -> - if String.equal id id' then begin - Variables.remove param vs - end - else begin - vs - end) - t.exports t.exports - in - { t with variables; exports } - -let exports t = - ("PATH", String.concat ":" (path t)) - :: (Variables.to_list t.exports - |> List.map (fun (k, (_, v)) -> - (k, Merry.Ast.Fragment.join_list ~sep:"" v))) - -let locals t = - match Stack.pop t.scopes with - | None -> [] - | Some (vars, _) -> - Variables.to_list vars |> List.map (fun (n, (_, v)) -> (n, v)) - -let all_variables t = - Variables.union (fun _ e _ -> Some e) t.exports t.variables - |> Variables.add "PATH" (default_attribute, path_as_variable t) - -let readonly t = - Variables.to_list (all_variables t) - |> List.filter_map (function - | p, ({ readonly = true; _ }, v) -> Some (p, v) - | _ -> None) - -let pp_readonly fmt t = - let rs = readonly t in - let rs = - List.map - (fun (p, cst) -> ("readonly " ^ p, Ast.Fragment.join_list ~sep:"" cst)) - rs - in - Fmt.(list ~sep:(Fmt.any "\n") (pair ~sep:(Fmt.any "=") string (quote string))) - fmt rs - -let pp_export fmt t = - let rs = exports t in - let rs = List.map (fun (p, cst) -> ("export " ^ p, cst)) rs in - Fmt.(list ~sep:(Fmt.any "\n") (pair ~sep:(Fmt.any "=") string (quote string))) - fmt rs - -let dump ppf s = - Fmt.pf ppf "Variables:[%a]\nLocals:%a" - Fmt.(list ~sep:Fmt.comma pp_variable) - (Variables.to_list (all_variables s)) - (Stack.pp pp_variables) s.scopes