diff --git a/example/main.ml b/example/main.ml index 6999ece..e5645f3 100644 --- a/example/main.ml +++ b/example/main.ml @@ -1,8 +1,16 @@ let ( / ) = Eio.Path.( / ) +module L = Eio_mem.Low_level + let () = - Eio_main.run @@ fun _ -> + Eio_main.run @@ fun e -> Eio_mem.run @@ fun env -> - let cwd = Eio.Stdenv.cwd env in - Eio.Path.save ~create:(`Exclusive 0o666) (cwd / "test-file") "my-data"; - Eio.traceln "Got %S" @@ Eio.Path.load (cwd / "test-file") + let () = + Eio.Path.with_open_out ~create:(`If_missing 0o644) (env#fs / "hello.txt") + @@ fun flow -> + let fd = Eio_mem.Resource.fd_opt flow |> Option.get in + let nfd = L.dup fd in + let _ : int = L.writev nfd [ Cstruct.of_string "Hello, World" ] in + () + in + Eio.traceln "Got: %s" (Eio.Path.load (env#fs / "hello.txt")) diff --git a/src/dune b/src/dune index a2b57f8..a984522 100644 --- a/src/dune +++ b/src/dune @@ -1,4 +1,4 @@ (library (name eio_mem) (public_name eio_mem) - (libraries eio fpath)) + (libraries eio eio.unix fpath)) diff --git a/src/eio_mem.ml b/src/eio_mem.ml index 06f5c8a..cbc83f6 100644 --- a/src/eio_mem.ml +++ b/src/eio_mem.ml @@ -15,6 +15,8 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. *) open Eio.Std +module Resource = Resource +module Low_level = Low_level module Fs = struct module Config = struct diff --git a/src/eio_mem.mli b/src/eio_mem.mli index 162ba3f..1c512b1 100644 --- a/src/eio_mem.mli +++ b/src/eio_mem.mli @@ -22,6 +22,24 @@ module Fs : sig (** A [tree]-like pretty printer. *) end +module Low_level = Low_level + +module Resource : sig + type 'a t = ([> `Mem_fd ] as 'a) Eio.Resource.t + (** Resources that have FDs are tagged with [`Mem_fd]. *) + + type ('t, _, _) Eio.Resource.pi += + | T : ('t, 't -> Low_level.Fd.t, [> `Mem_fd ]) Eio.Resource.pi + + val fd : _ t -> Low_level.Fd.t + (** [fd t] returns the FD being wrapped by a resource. *) + + val fd_opt : _ Eio.Resource.t -> Low_level.Fd.t option + (** [fd_opt t] returns the FD being wrapped by a generic resource, if any. + + This just probes [t] using {!extension-T}. *) +end + type stdenv = < fs : Eio.Fs.dir_ty Eio.Path.t ; cwd : Eio.Fs.dir_ty Eio.Path.t > val run : ?cwd:string -> (stdenv -> 'a) -> 'a diff --git a/src/flow.ml b/src/flow.ml index 47aa4ce..7524ce8 100644 --- a/src/flow.ml +++ b/src/flow.ml @@ -48,6 +48,8 @@ end module type FLOW = sig include Eio.File.Pi.WRITE include Eio.Net.Pi.STREAM_SOCKET with type t := t + + val fd : t -> Low_level.Fd.t end let flow_handler (type t tag) @@ -57,6 +59,7 @@ let flow_handler (type t tag) Eio.Resource.handler @@ Eio.Resource.bindings (Eio.Net.Pi.stream_socket (module X)) @ Eio.Resource.bindings (Eio.File.Pi.rw (module X)) + @ [ Eio.Resource.H (Resource.T, X.fd) ] let handler = flow_handler (module Impl) let of_fd fd = Eio.Resource.T (fd, handler) diff --git a/src/low_level.ml b/src/low_level.ml index 8efe655..55cc050 100644 --- a/src/low_level.ml +++ b/src/low_level.ml @@ -53,8 +53,13 @@ module Error = struct let exn = Eio.Exn.create Eio.Fs.(E (Permission_denied (Eio_mem path))) in raise exn - let badf () = - let exn = Eio.Exn.create (Eio.Exn.X (Eio_mem "Bad file descriptor")) in + let badf ?msg () = + let msg = + match msg with + | None -> "Bad file descriptor" + | Some m -> Fmt.str "Bad file descriptor: %s" m + in + let exn = Eio.Exn.create (Eio.Exn.X (Eio_mem msg)) in raise exn let err ctx = @@ -206,6 +211,33 @@ let next_fd_from_fds fds = let next_fd () = read_fd_table next_fd_from_fds +let dup fd = + let r = read_fd_table (Fd_table.find_opt fd) in + match r with + | None -> Error.badf () + | Some r -> + let new_fd = next_fd () in + let () = update_fd_table @@ fun tbl -> Fd_table.add new_fd r tbl in + new_fd + +let close fd = + update_fd_table @@ fun fds -> + match Fd_table.find_opt fd fds with + | None -> Error.badf ~msg:"close" () + | Some _ -> Fd_table.remove fd fds + +let dup2 ~src ~tgt = + let src_r = read_fd_table (Fd_table.find_opt src) in + let tgt_r = read_fd_table (Fd_table.find_opt tgt) in + match src_r with + | None -> Error.badf () + | Some r -> ( + match tgt_r with + | None -> update_fd_table @@ fun tbl -> Fd_table.add tgt r tbl + | Some _ -> + close tgt; + update_fd_table @@ fun tbl -> Fd_table.add tgt r tbl) + let default_stat = Eio.File.Stat. { @@ -411,12 +443,6 @@ let entries = function | Resource.Dir { Dir.entries; _ } -> entries | r -> Fmt.invalid_arg "entries: %a" Resource.pp r -let close fd = - update_fd_table @@ fun fds -> - match Fd_table.find_opt fd fds with - | None -> Error.badf () - | Some _ -> Fd_table.remove fd fds - let openat ~mode:_ ~sw dir_fd path flags = let path = check_path path in let is_create = Open_flags.(mem o_creat flags) in diff --git a/test/dune b/test/dune index a8b1c93..aa4c223 100644 --- a/test/dune +++ b/test/dune @@ -1,3 +1,3 @@ (mdx (files fs.md) - (libraries eio eio.core eio.mock optint eio_mem fmt)) + (libraries eio eio.core cstruct eio.mock optint eio_mem fmt)) diff --git a/test/fs.md b/test/fs.md index 92403fc..cf78672 100644 --- a/test/fs.md +++ b/test/fs.md @@ -10,6 +10,8 @@ open Eio.Std let ( / ) = Path.( / ) +module L = Eio_mem.Low_level + let run ?clear:(paths = []) ?(cwd="/home/bactrian") fn = Eio_mock.Backend.run @@ fun () -> Eio_mem.run ~cwd @@ fun env -> @@ -379,3 +381,18 @@ Confined: - : unit = () ``` +# Low level API + +```ocaml +# run ~clear:[ "hello.txt"; "world.txt" ] @@ fun env -> + let () = + Eio.Path.with_open_out ~create:(`If_missing 0o644) (env#fs / "hello.txt") @@ fun flow -> + let fd = Eio_mem.Resource.fd_opt flow |> Option.get in + let nfd = L.dup fd in + let _ : int = L.writev nfd [ Cstruct.of_string "Hello, World" ] in + () + in + Eio.traceln "Got: %s" (Eio.Path.load (env#fs / "hello.txt")) ++Got: Hello, World +- : unit = () +```