Compare commits

..

2 Commits

Author SHA1 Message Date
Vesa Karvonen
2b8cdede5e Skip tests by os_type 2023-07-27 18:11:41 +03:00
Vesa Karvonen
988e85a0b5 Get MDX tests working on Windows 2023-07-24 12:45:32 +03:00
71 changed files with 1463 additions and 2504 deletions

View File

@ -1534,26 +1534,19 @@ See Eio's own tests for examples, e.g., [tests/switch.md](tests/switch.md).
## Provider Interfaces ## Provider Interfaces
Eio applications use resources by calling functions (such as `Eio.Flow.write`). Eio applications use resources by calling functions (such as `Eio.Flow.write`).
These functions are actually wrappers that look up the implementing module and call These functions are actually wrappers that call methods on the resources.
the appropriate function on that.
This allows you to define your own resources. This allows you to define your own resources.
Here's a flow that produces an endless stream of zeros (like "/dev/zero"): Here's a flow that produces an endless stream of zeros (like "/dev/zero"):
```ocaml ```ocaml
module Zero = struct let zero = object
type t = unit inherit Eio.Flow.source
let single_read () buf = method read_into buf =
Cstruct.memset buf 0; Cstruct.memset buf 0;
Cstruct.length buf Cstruct.length buf
let read_methods = [] (* Optional optimisations *)
end end
let ops = Eio.Flow.Pi.source (module Zero)
let zero = Eio.Resource.T ((), ops)
``` ```
It can then be used like any other Eio flow: It can then be used like any other Eio flow:
@ -1566,6 +1559,34 @@ It can then be used like any other Eio flow:
- : unit = () - : unit = ()
``` ```
The `Flow.source` interface has some extra methods that can be used for optimisations
(for example, instead of filling a buffer with zeros it could be more efficient to share
a pre-allocated block of zeros).
Using `inherit` provides default implementations of these methods that say no optimisations are available.
It also protects you somewhat from API changes in future, as defaults can be provided for any new methods that get added.
Although it is possible to *use* an object by calling its methods directly,
it is recommended that you use the functions instead.
The functions provide type information to the compiler, leading to clearer error messages,
and may provide extra features or sanity checks.
For example `Eio.Flow.single_read` is defined as:
```ocaml
let single_read (t : #Eio.Flow.source) buf =
let got = t#read_into buf in
assert (got > 0 && got <= Cstruct.length buf);
got
```
As an exception to this rule, it is fine to use the methods of `env` directly
(e.g. using `main env#stdin` instead of `main (Eio.Stdenv.stdin env)`.
Here, the compiler already has the type from the `Eio_main.run` call immediately above it,
and `env` is acting as a simple record.
We avoid doing that in this guide only to avoid alarming OCaml users unfamiliar with object syntax.
See [Dynamic Dispatch](doc/rationale.md#dynamic-dispatch) for more discussion about the use of objects here.
## Example Applications ## Example Applications
- [gemini-eio][] is a simple Gemini browser. It shows how to integrate Eio with `ocaml-tls` and `notty`. - [gemini-eio][] is a simple Gemini browser. It shows how to integrate Eio with `ocaml-tls` and `notty`.
@ -1718,8 +1739,9 @@ Of course, you could use `with_open_in` in this case to simplify it further.
### Casting ### Casting
Unlike many languages, OCaml does not automatically cast to super-types as needed. Unlike many languages, OCaml does not automatically cast objects (polymorphic records) to super-types as needed.
Remember to keep the type polymorphic in your interface so users don't need to do this manually. Remember to keep the type polymorphic in your interface so users don't need to do this manually.
This is similar to the case with polymorphic variants (where APIs should use `[< ...]` or `[> ...]`).
For example, if you need an `Eio.Flow.source` then users should be able to use a `Flow.two_way` For example, if you need an `Eio.Flow.source` then users should be able to use a `Flow.two_way`
without having to cast it first: without having to cast it first:
@ -1729,13 +1751,13 @@ without having to cast it first:
(* BAD - user must cast to use function: *) (* BAD - user must cast to use function: *)
module Message : sig module Message : sig
type t type t
val read : Eio.Flow.source_ty r -> t val read : Eio.Flow.source -> t
end end
(* GOOD - a Flow.two_way can be used without casting: *) (* GOOD - a Flow.two_way can be used without casting: *)
module Message : sig module Message : sig
type t type t
val read : _ Eio.Flow.source -> t val read : #Eio.Flow.source -> t
end end
``` ```
@ -1744,18 +1766,20 @@ If you want to store the argument, this may require you to cast internally:
```ocaml ```ocaml
module Foo : sig module Foo : sig
type t type t
val of_source : _ Eio.Flow.source -> t val of_source : #Eio.Flow.source -> t
end = struct end = struct
type t = { type t = {
src : Eio.Flow.source_ty r; src : Eio.Flow.source;
} }
let of_source x = { let of_source x = {
src = (x :> Eio.Flow.source_ty r); src = (x :> Eio.Flow.source);
} }
end end
``` ```
Note: the `#type` syntax only works on types defined by classes, whereas the slightly more verbose `<type; ..>` works on all object types.
### Passing env ### Passing env
The `env` value you get from `Eio_main.run` is a powerful capability, The `env` value you get from `Eio_main.run` is a powerful capability,

View File

@ -43,4 +43,4 @@ module Eio_main = struct
end end
end end
let parse_config (flow : _ Eio.Flow.source) = ignore let parse_config (flow : #Eio.Flow.source) = ignore

View File

@ -125,7 +125,7 @@ For dynamic dispatch with subtyping, objects seem to be the best choice:
An object uses a single block to store the object's fields and a pointer to the shared method table. An object uses a single block to store the object's fields and a pointer to the shared method table.
- First-class modules and GADTs are an advanced feature of the language. - First-class modules and GADTs are an advanced feature of the language.
The new users we hope to attract to OCaml 5.0 are likely to be familiar with objects already. The new users we hope to attract to OCaml 5.00 are likely to be familiar with objects already.
- It is possible to provide base classes with default implementations of some methods. - It is possible to provide base classes with default implementations of some methods.
This can allow adding new operations to the API in future without breaking existing providers. This can allow adding new operations to the API in future without breaking existing providers.
@ -133,19 +133,24 @@ For dynamic dispatch with subtyping, objects seem to be the best choice:
In general, simulating objects using other features of the language leads to worse performance In general, simulating objects using other features of the language leads to worse performance
and worse ergonomics than using the language's built-in support. and worse ergonomics than using the language's built-in support.
However, in order for Eio to be widely accepted in the OCaml community, In Eio, we split the provider and consumer APIs:
we no longer use of objects and instead use a pair of a value and a function for looking up interfaces.
There is a problem here, because each interface has a different type,
so the function's return type depends on its input (the interface ID).
This requires using a GADT. However, GADT's don't support sub-typing.
To get around this, we use an extensible GADT to get the correct typing
(but which will raise an exception if the interface isn't supported),
and then wrap this with a polymorphic variant phantom type to help ensure
it is used correctly.
This system gives the same performance as using objects and without requiring allocation. - To *provide* a flow, you implement an object type.
However, care is needed when defining new interfaces, - To *use* a flow, you call a function (e.g. `Flow.close`).
since the compiler can't check that the resource really implements all the interfaces its phantom type suggests.
The functions mostly just call the corresponding method on the object.
If you call object methods directly in OCaml then you tend to get poor compiler error messages.
This is because OCaml can only refer to the object types by listing the methods you seem to want to use.
Using functions avoids this, because the function signature specifies the type of its argument,
allowing type inference to work as for non-object code.
In this way, users of Eio can be largely unaware that objects are being used at all.
The function wrappers can also provide extra checks that the API is being followed correctly,
such as asserting that a read does not return 0 bytes,
or add extra convenience functions without forcing every implementor to add them too.
Note that the use of objects in Eio is not motivated by the use of the "Object Capabilities" security model.
Despite the name, that is not specific to objects at all.
## Results vs Exceptions ## Results vs Exceptions

View File

@ -26,30 +26,26 @@ exception Buffer_limit_exceeded = Buf_read.Buffer_limit_exceeded
let initial_size = 10 let initial_size = 10
let max_size = 100 let max_size = 100
module Mock_flow = struct let mock_flow next = object (self)
type t = string list ref inherit Eio.Flow.source
let rec single_read t buf = val mutable next = next
match !t with
method read_into buf =
match next with
| [] -> | [] ->
raise End_of_file raise End_of_file
| "" :: xs -> | "" :: xs ->
t := xs; next <- xs;
single_read t buf self#read_into buf
| x :: xs -> | x :: xs ->
let len = min (Cstruct.length buf) (String.length x) in let len = min (Cstruct.length buf) (String.length x) in
Cstruct.blit_from_string x 0 buf 0 len; Cstruct.blit_from_string x 0 buf 0 len;
let x' = String.drop x len in let x' = String.drop x len in
t := (if x' = "" then xs else x' :: xs); next <- (if x' = "" then xs else x' :: xs);
len len
let read_methods = []
end end
let mock_flow =
let ops = Eio.Flow.Pi.source (module Mock_flow) in
fun chunks -> Eio.Resource.T (ref chunks, ops)
module Model = struct module Model = struct
type t = string ref type t = string ref

View File

@ -1,12 +1,10 @@
exception Buffer_limit_exceeded exception Buffer_limit_exceeded
open Std
type t = { type t = {
mutable buf : Cstruct.buffer; mutable buf : Cstruct.buffer;
mutable pos : int; mutable pos : int;
mutable len : int; mutable len : int;
mutable flow : Flow.source_ty r option; (* None if we've seen eof *) mutable flow : Flow.source option; (* None if we've seen eof *)
mutable consumed : int; (* Total bytes consumed so far *) mutable consumed : int; (* Total bytes consumed so far *)
max_size : int; max_size : int;
} }
@ -47,7 +45,7 @@ open Syntax
let capacity t = Bigarray.Array1.dim t.buf let capacity t = Bigarray.Array1.dim t.buf
let of_flow ?initial_size ~max_size flow = let of_flow ?initial_size ~max_size flow =
let flow = (flow :> Flow.source_ty r) in let flow = (flow :> Flow.source) in
if max_size <= 0 then Fmt.invalid_arg "Max size %d should be positive!" max_size; if max_size <= 0 then Fmt.invalid_arg "Max size %d should be positive!" max_size;
let initial_size = Option.value initial_size ~default:(min 4096 max_size) in let initial_size = Option.value initial_size ~default:(min 4096 max_size) in
let buf = Bigarray.(Array1.create char c_layout initial_size) in let buf = Bigarray.(Array1.create char c_layout initial_size) in
@ -130,23 +128,18 @@ let ensure_slow_path t n =
let ensure t n = let ensure t n =
if t.len < n then ensure_slow_path t n if t.len < n then ensure_slow_path t n
module F = struct let as_flow t =
type nonrec t = t object
inherit Flow.source
let single_read t dst = method read_into dst =
ensure t 1; ensure t 1;
let len = min (buffered_bytes t) (Cstruct.length dst) in let len = min (buffered_bytes t) (Cstruct.length dst) in
Cstruct.blit (peek t) 0 dst 0 len; Cstruct.blit (peek t) 0 dst 0 len;
consume t len; consume t len;
len len
let read_methods = []
end end
let as_flow =
let ops = Flow.Pi.source (module F) in
fun t -> Resource.T (t, ops)
let get t i = let get t i =
Bigarray.Array1.get t.buf (t.pos + i) Bigarray.Array1.get t.buf (t.pos + i)

View File

@ -9,8 +9,6 @@
]} ]}
*) *)
open Std
type t type t
(** An input buffer. *) (** An input buffer. *)
@ -23,7 +21,7 @@ type 'a parser = t -> 'a
@raise End_of_file The flow ended without enough data to parse an ['a]. @raise End_of_file The flow ended without enough data to parse an ['a].
@raise Buffer_limit_exceeded Parsing the value would exceed the configured size limit. *) @raise Buffer_limit_exceeded Parsing the value would exceed the configured size limit. *)
val parse : ?initial_size:int -> max_size:int -> 'a parser -> _ Flow.source -> ('a, [> `Msg of string]) result val parse : ?initial_size:int -> max_size:int -> 'a parser -> #Flow.source -> ('a, [> `Msg of string]) result
(** [parse p flow ~max_size] uses [p] to parse everything in [flow]. (** [parse p flow ~max_size] uses [p] to parse everything in [flow].
It is a convenience function that does It is a convenience function that does
@ -34,7 +32,7 @@ val parse : ?initial_size:int -> max_size:int -> 'a parser -> _ Flow.source -> (
@param initial_size see {!of_flow}. *) @param initial_size see {!of_flow}. *)
val parse_exn : ?initial_size:int -> max_size:int -> 'a parser -> _ Flow.source -> 'a val parse_exn : ?initial_size:int -> max_size:int -> 'a parser -> #Flow.source -> 'a
(** [parse_exn] wraps {!parse}, but raises [Failure msg] if that returns [Error (`Msg msg)]. (** [parse_exn] wraps {!parse}, but raises [Failure msg] if that returns [Error (`Msg msg)].
Catching exceptions with [parse] and then raising them might seem pointless, Catching exceptions with [parse] and then raising them might seem pointless,
@ -48,7 +46,7 @@ val parse_string : 'a parser -> string -> ('a, [> `Msg of string]) result
val parse_string_exn : 'a parser -> string -> 'a val parse_string_exn : 'a parser -> string -> 'a
(** [parse_string_exn] is like {!parse_string}, but handles errors like {!parse_exn}. *) (** [parse_string_exn] is like {!parse_string}, but handles errors like {!parse_exn}. *)
val of_flow : ?initial_size:int -> max_size:int -> _ Flow.source -> t val of_flow : ?initial_size:int -> max_size:int -> #Flow.source -> t
(** [of_flow ~max_size flow] is a buffered reader backed by [flow]. (** [of_flow ~max_size flow] is a buffered reader backed by [flow].
@param initial_size The initial amount of memory to allocate for the buffer. @param initial_size The initial amount of memory to allocate for the buffer.
@ -70,7 +68,7 @@ val of_buffer : Cstruct.buffer -> t
val of_string : string -> t val of_string : string -> t
(** [of_string s] is a reader that reads from [s]. *) (** [of_string s] is a reader that reads from [s]. *)
val as_flow : t -> Flow.source_ty r val as_flow : t -> Flow.source
(** [as_flow t] is a buffered flow. (** [as_flow t] is a buffered flow.
Reading from it will return data from the buffer, Reading from it will return data from the buffer,

View File

@ -85,7 +85,7 @@ exception Flush_aborted
(** {2 Running} *) (** {2 Running} *)
val with_flow : ?initial_size:int -> _ Flow.sink -> (t -> 'a) -> 'a val with_flow : ?initial_size:int -> #Flow.sink -> (t -> 'a) -> 'a
(** [with_flow flow fn] runs [fn writer], where [writer] is a buffer that flushes to [flow]. (** [with_flow flow fn] runs [fn writer], where [writer] is a buffer that flushes to [flow].
Concurrently with [fn], it also runs a fiber that copies from [writer] to [flow]. Concurrently with [fn], it also runs a fiber that copies from [writer] to [flow].

View File

@ -3,13 +3,19 @@ include Eio__core
module Debug = Private.Debug module Debug = Private.Debug
let traceln = Debug.traceln let traceln = Debug.traceln
module Std = Std module Std = struct
module Promise = Promise
module Fiber = Fiber
module Switch = Switch
let traceln = Debug.traceln
end
module Semaphore = Semaphore module Semaphore = Semaphore
module Mutex = Eio_mutex module Mutex = Eio_mutex
module Condition = Condition module Condition = Condition
module Stream = Stream module Stream = Stream
module Exn = Exn module Exn = Exn
module Resource = Resource module Generic = Generic
module Flow = Flow module Flow = Flow
module Buf_read = Buf_read module Buf_read = Buf_read
module Buf_write = Buf_write module Buf_write = Buf_write
@ -22,17 +28,17 @@ module Fs = Fs
module Path = Path module Path = Path
module Stdenv = struct module Stdenv = struct
let stdin (t : <stdin : _ Flow.source; ..>) = t#stdin let stdin (t : <stdin : #Flow.source; ..>) = t#stdin
let stdout (t : <stdout : _ Flow.sink; ..>) = t#stdout let stdout (t : <stdout : #Flow.sink; ..>) = t#stdout
let stderr (t : <stderr : _ Flow.sink; ..>) = t#stderr let stderr (t : <stderr : #Flow.sink; ..>) = t#stderr
let net (t : <net : _ Net.t; ..>) = t#net let net (t : <net : #Net.t; ..>) = t#net
let process_mgr (t : <process_mgr : #Process.mgr; ..>) = t#process_mgr let process_mgr (t : <process_mgr : #Process.mgr; ..>) = t#process_mgr
let domain_mgr (t : <domain_mgr : #Domain_manager.t; ..>) = t#domain_mgr let domain_mgr (t : <domain_mgr : #Domain_manager.t; ..>) = t#domain_mgr
let clock (t : <clock : #Time.clock; ..>) = t#clock let clock (t : <clock : #Time.clock; ..>) = t#clock
let mono_clock (t : <mono_clock : #Time.Mono.t; ..>) = t#mono_clock let mono_clock (t : <mono_clock : #Time.Mono.t; ..>) = t#mono_clock
let secure_random (t: <secure_random : _ Flow.source; ..>) = t#secure_random let secure_random (t: <secure_random : #Flow.source; ..>) = t#secure_random
let fs (t : <fs : _ Path.t; ..>) = t#fs let fs (t : <fs : #Fs.dir Path.t; ..>) = t#fs
let cwd (t : <cwd : _ Path.t; ..>) = t#cwd let cwd (t : <cwd : #Fs.dir Path.t; ..>) = t#cwd
let debug (t : <debug : 'a; ..>) = t#debug let debug (t : <debug : 'a; ..>) = t#debug
let backend_id (t: <backend_id : string; ..>) = t#backend_id let backend_id (t: <backend_id : string; ..>) = t#backend_id
end end

View File

@ -40,18 +40,30 @@ module Stream = Stream
module Cancel = Eio__core.Cancel module Cancel = Eio__core.Cancel
(** Commonly used standard features. This module is intended to be [open]ed. *) (** Commonly used standard features. This module is intended to be [open]ed. *)
module Std = Std module Std : sig
module Promise = Promise
module Fiber = Fiber
module Switch = Switch
val traceln :
?__POS__:string * int * int * int ->
('a, Format.formatter, unit, unit) format4 -> 'a
(** Same as {!Eio.traceln}. *)
end
(** {1 Cross-platform OS API} (** {1 Cross-platform OS API}
The general pattern here is that each type of resource has a set of functions for using it, The general pattern here is that each type of resource has a set of functions for using it,
plus a provider ([Pi]) module to allow defining your own implementations. plus an object type to allow defining your own implementations.
To use the resources, it is recommended that you use the functions rather than calling
methods directly. Using the functions results in better error messages from the compiler,
and may provide extra features or sanity checks.
The system resources are available from the environment argument provided by your event loop The system resources are available from the environment argument provided by your event loop
(e.g. {!Eio_main.run}). *) (e.g. {!Eio_main.run}). *)
(** Defines the base resource type. *) (** A base class for objects that can be queried at runtime for extra features. *)
module Resource = Resource module Generic = Generic
(** Byte streams. *) (** Byte streams. *)
module Flow = Flow module Flow = Flow
@ -163,9 +175,9 @@ module Stdenv : sig
To use these, see {!Flow}. *) To use these, see {!Flow}. *)
val stdin : <stdin : _ Flow.source as 'a; ..> -> 'a val stdin : <stdin : #Flow.source as 'a; ..> -> 'a
val stdout : <stdout : _ Flow.sink as 'a; ..> -> 'a val stdout : <stdout : #Flow.sink as 'a; ..> -> 'a
val stderr : <stderr : _ Flow.sink as 'a; ..> -> 'a val stderr : <stderr : #Flow.sink as 'a; ..> -> 'a
(** {1 File-system access} (** {1 File-system access}
@ -189,7 +201,7 @@ module Stdenv : sig
To use this, see {!Net}. To use this, see {!Net}.
*) *)
val net : <net : _ Net.t as 'a; ..> -> 'a val net : <net : #Net.t as 'a; ..> -> 'a
(** [net t] gives access to the process's network namespace. *) (** [net t] gives access to the process's network namespace. *)
(** {1 Processes } (** {1 Processes }
@ -221,7 +233,7 @@ module Stdenv : sig
(** {1 Randomness} *) (** {1 Randomness} *)
val secure_random : <secure_random : _ Flow.source as 'a; ..> -> 'a val secure_random : <secure_random : #Flow.source as 'a; ..> -> 'a
(** [secure_random t] is an infinite source of random bytes suitable for cryptographic purposes. *) (** [secure_random t] is an infinite source of random bytes suitable for cryptographic purposes. *)
(** {1 Debugging} *) (** {1 Debugging} *)

View File

@ -1,10 +1,13 @@
open Std (** Tranditional Unix permissions. *)
module Unix_perm = struct module Unix_perm = struct
type t = int type t = int
(** This is the same as {!Unix.file_perm}, but avoids a dependency on [Unix]. *)
end end
(** Portable file stats. *)
module Stat = struct module Stat = struct
(** Kind of file from st_mode. **)
type kind = [ type kind = [
| `Unknown | `Unknown
| `Fifo | `Fifo
@ -16,6 +19,7 @@ module Stat = struct
| `Socket | `Socket
] ]
(** Like stat(2). *)
type t = { type t = {
dev : Int64.t; dev : Int64.t;
ino : Int64.t; ino : Int64.t;
@ -32,85 +36,62 @@ module Stat = struct
} }
end end
type ro_ty = [`File | Flow.source_ty | Resource.close_ty] (** A file opened for reading. *)
class virtual ro = object (_ : <Generic.t; Flow.source; ..>)
type 'a ro = ([> ro_ty] as 'a) r method probe _ = None
method read_methods = []
type rw_ty = [ro_ty | Flow.sink_ty] method virtual pread : file_offset:Optint.Int63.t -> Cstruct.t list -> int
method virtual stat : Stat.t
type 'a rw = ([> rw_ty] as 'a) r
module Pi = struct
module type READ = sig
include Flow.Pi.SOURCE
val pread : t -> file_offset:Optint.Int63.t -> Cstruct.t list -> int
val stat : t -> Stat.t
val close : t -> unit
end end
module type WRITE = sig (** A file opened for reading and writing. *)
include Flow.Pi.SINK class virtual rw = object (_ : <Generic.t; Flow.source; Flow.sink; ..>)
include READ with type t := t inherit ro
method virtual pwrite : file_offset:Optint.Int63.t -> Cstruct.t list -> int
val pwrite : t -> file_offset:Optint.Int63.t -> Cstruct.t list -> int
end end
type (_, _, _) Resource.pi += (** [stat t] returns the {!Stat.t} record associated with [t]. *)
| Read : ('t, (module READ with type t = 't), [> ro_ty]) Resource.pi let stat (t : #ro) = t#stat
| Write : ('t, (module WRITE with type t = 't), [> rw_ty]) Resource.pi
let ro (type t) (module X : READ with type t = t) =
Resource.handler [
H (Flow.Pi.Source, (module X));
H (Read, (module X));
H (Resource.Close, X.close);
]
let rw (type t) (module X : WRITE with type t = t) =
Resource.handler (
H (Flow.Pi.Sink, (module X)) ::
H (Write, (module X)) ::
Resource.bindings (ro (module X))
)
end
let stat (Resource.T (t, ops)) =
let module X = (val (Resource.get ops Pi.Read)) in
X.stat t
(** [size t] returns the size of [t]. *)
let size t = (stat t).size let size t = (stat t).size
let pread (Resource.T (t, ops)) ~file_offset bufs = (** [pread t ~file_offset bufs] performs a single read of [t] at [file_offset] into [bufs].
let module X = (val (Resource.get ops Pi.Read)) in
let got = X.pread t ~file_offset bufs in It returns the number of bytes read, which may be less than the space in [bufs],
even if more bytes are available. Use {!pread_exact} instead if you require
the buffer to be filled.
To read at the current offset, use {!Flow.single_read} instead. *)
let pread (t : #ro) ~file_offset bufs =
let got = t#pread ~file_offset bufs in
assert (got > 0 && got <= Cstruct.lenv bufs); assert (got > 0 && got <= Cstruct.lenv bufs);
got got
let pread_exact (Resource.T (t, ops)) ~file_offset bufs = (** [pread_exact t ~file_offset bufs] reads from [t] into [bufs] until [bufs] is full.
let module X = (val (Resource.get ops Pi.Read)) in
let rec aux ~file_offset bufs =
if Cstruct.lenv bufs > 0 then (
let got = X.pread t ~file_offset bufs in
let file_offset = Optint.Int63.add file_offset (Optint.Int63.of_int got) in
aux ~file_offset (Cstruct.shiftv bufs got)
)
in
aux ~file_offset bufs
let pwrite_single (Resource.T (t, ops)) ~file_offset bufs = @raise End_of_file if the buffer could not be filled. *)
let module X = (val (Resource.get ops Pi.Write)) in let rec pread_exact (t : #ro) ~file_offset bufs =
let got = X.pwrite t ~file_offset bufs in if Cstruct.lenv bufs > 0 then (
let got = t#pread ~file_offset bufs in
let file_offset = Optint.Int63.add file_offset (Optint.Int63.of_int got) in
pread_exact t ~file_offset (Cstruct.shiftv bufs got)
)
(** [pwrite_single t ~file_offset bufs] performs a single write operation, writing
data from [bufs] to location [file_offset] in [t].
It returns the number of bytes written, which may be less than the length of [bufs].
In most cases, you will want to use {!pwrite_all} instead. *)
let pwrite_single (t : #rw) ~file_offset bufs =
let got = t#pwrite ~file_offset bufs in
assert (got > 0 && got <= Cstruct.lenv bufs); assert (got > 0 && got <= Cstruct.lenv bufs);
got got
let pwrite_all (Resource.T (t, ops)) ~file_offset bufs = (** [pwrite_all t ~file_offset bufs] writes all the data in [bufs] to location [file_offset] in [t]. *)
let module X = (val (Resource.get ops Pi.Write)) in let rec pwrite_all (t : #rw) ~file_offset bufs =
let rec aux ~file_offset bufs =
if Cstruct.lenv bufs > 0 then ( if Cstruct.lenv bufs > 0 then (
let got = X.pwrite t ~file_offset bufs in let got = t#pwrite ~file_offset bufs in
let file_offset = Optint.Int63.add file_offset (Optint.Int63.of_int got) in let file_offset = Optint.Int63.add file_offset (Optint.Int63.of_int got) in
aux ~file_offset (Cstruct.shiftv bufs got) pwrite_all t ~file_offset (Cstruct.shiftv bufs got)
) )
in
aux ~file_offset bufs

View File

@ -1,104 +0,0 @@
open Std
(** Tranditional Unix permissions. *)
module Unix_perm : sig
type t = int
(** This is the same as {!Unix.file_perm}, but avoids a dependency on [Unix]. *)
end
(** Portable file stats. *)
module Stat : sig
type kind = [
| `Unknown
| `Fifo
| `Character_special
| `Directory
| `Block_device
| `Regular_file
| `Symbolic_link
| `Socket
]
(** Kind of file from st_mode. **)
type t = {
dev : Int64.t;
ino : Int64.t;
kind : kind;
perm : Unix_perm.t;
nlink : Int64.t;
uid : Int64.t;
gid : Int64.t;
rdev : Int64.t;
size : Optint.Int63.t;
atime : float;
mtime : float;
ctime : float;
}
(** Like stat(2). *)
end
type ro_ty = [`File | Flow.source_ty | Resource.close_ty]
type 'a ro = ([> ro_ty] as 'a) r
(** A file opened for reading. *)
type rw_ty = [ro_ty | Flow.sink_ty]
type 'a rw = ([> rw_ty] as 'a) r
(** A file opened for reading and writing. *)
module Pi : sig
module type READ = sig
include Flow.Pi.SOURCE
val pread : t -> file_offset:Optint.Int63.t -> Cstruct.t list -> int
val stat : t -> Stat.t
val close : t -> unit
end
module type WRITE = sig
include Flow.Pi.SINK
include READ with type t := t
val pwrite : t -> file_offset:Optint.Int63.t -> Cstruct.t list -> int
end
type (_, _, _) Resource.pi +=
| Read : ('t, (module READ with type t = 't), [> ro_ty]) Resource.pi
| Write : ('t, (module WRITE with type t = 't), [> rw_ty]) Resource.pi
val ro : (module READ with type t = 't) -> ('t, ro_ty) Resource.handler
val rw : (module WRITE with type t = 't) -> ('t, rw_ty) Resource.handler
end
val stat : _ ro -> Stat.t
(** [stat t] returns the {!Stat.t} record associated with [t]. *)
val size : _ ro -> Optint.Int63.t
(** [size t] returns the size of [t]. *)
val pread : _ ro -> file_offset:Optint.Int63.t -> Cstruct.t list -> int
(** [pread t ~file_offset bufs] performs a single read of [t] at [file_offset] into [bufs].
It returns the number of bytes read, which may be less than the space in [bufs],
even if more bytes are available. Use {!pread_exact} instead if you require
the buffer to be filled.
To read at the current offset, use {!Flow.single_read} instead. *)
val pread_exact : _ ro -> file_offset:Optint.Int63.t -> Cstruct.t list -> unit
(** [pread_exact t ~file_offset bufs] reads from [t] into [bufs] until [bufs] is full.
@raise End_of_file if the buffer could not be filled. *)
val pwrite_single : _ rw -> file_offset:Optint.Int63.t -> Cstruct.t list -> int
(** [pwrite_single t ~file_offset bufs] performs a single write operation, writing
data from [bufs] to location [file_offset] in [t].
It returns the number of bytes written, which may be less than the length of [bufs].
In most cases, you will want to use {!pwrite_all} instead. *)
val pwrite_all : _ rw -> file_offset:Optint.Int63.t -> Cstruct.t list -> unit
(** [pwrite_all t ~file_offset bufs] writes all the data in [bufs] to location [file_offset] in [t]. *)

View File

@ -1,169 +1,106 @@
open Std
type shutdown_command = [ `Receive | `Send | `All ] type shutdown_command = [ `Receive | `Send | `All ]
type 't read_method = .. type read_method = ..
type 't read_method += Read_source_buffer of ('t -> (Cstruct.t list -> int) -> unit) type read_method += Read_source_buffer of ((Cstruct.t list -> int) -> unit)
type source_ty = [`R | `Flow] class type close = Generic.close
type 'a source = ([> source_ty] as 'a) r let close = Generic.close
type sink_ty = [`W | `Flow] class virtual source = object (_ : <Generic.t; ..>)
type 'a sink = ([> sink_ty] as 'a) r method probe _ = None
method read_methods : read_method list = []
type shutdown_ty = [`Shutdown] method virtual read_into : Cstruct.t -> int
type 'a shutdown = ([> shutdown_ty] as 'a) r
module Pi = struct
module type SOURCE = sig
type t
val read_methods : t read_method list
val single_read : t -> Cstruct.t -> int
end end
module type SINK = sig let single_read (t : #source) buf =
type t let got = t#read_into buf in
val copy : t -> src:_ source -> unit
val write : t -> Cstruct.t list -> unit
end
module type SHUTDOWN = sig
type t
val shutdown : t -> shutdown_command -> unit
end
type (_, _, _) Resource.pi +=
| Source : ('t, (module SOURCE with type t = 't), [> source_ty]) Resource.pi
| Sink : ('t, (module SINK with type t = 't), [> sink_ty]) Resource.pi
| Shutdown : ('t, (module SHUTDOWN with type t = 't), [> shutdown_ty]) Resource.pi
let source (type t) (module X : SOURCE with type t = t) =
Resource.handler [H (Source, (module X))]
let sink (type t) (module X : SINK with type t = t) =
Resource.handler [H (Sink, (module X))]
let shutdown (type t) (module X : SHUTDOWN with type t = t) =
Resource.handler [ H (Shutdown, (module X))]
module type TWO_WAY = sig
include SHUTDOWN
include SOURCE with type t := t
include SINK with type t := t
end
let two_way (type t) (module X : TWO_WAY with type t = t) =
Resource.handler [
H (Shutdown, (module X));
H (Source, (module X));
H (Sink, (module X));
]
end
open Pi
let close = Resource.close
let single_read (Resource.T (t, ops)) buf =
let module X = (val (Resource.get ops Source)) in
let got = X.single_read t buf in
assert (got > 0 && got <= Cstruct.length buf); assert (got > 0 && got <= Cstruct.length buf);
got got
let read_methods (t : #source) = t#read_methods
let rec read_exact t buf = let rec read_exact t buf =
if Cstruct.length buf > 0 then ( if Cstruct.length buf > 0 then (
let got = single_read t buf in let got = single_read t buf in
read_exact t (Cstruct.shift buf got) read_exact t (Cstruct.shift buf got)
) )
module Cstruct_source = struct let cstruct_source data : source =
type t = Cstruct.t list ref object (self)
val mutable data = data
let create data = ref data inherit source
let read_source_buffer t fn = method private read_source_buffer fn =
let rec aux () = let rec aux () =
match !t with match data with
| [] -> raise End_of_file | [] -> raise End_of_file
| x :: xs when Cstruct.length x = 0 -> t := xs; aux () | x :: xs when Cstruct.length x = 0 -> data <- xs; aux ()
| xs -> | xs ->
let n = fn xs in let n = fn xs in
t := Cstruct.shiftv xs n data <- Cstruct.shiftv xs n
in in
aux () aux ()
let read_methods = method! read_methods =
[ Read_source_buffer read_source_buffer ] [ Read_source_buffer self#read_source_buffer ]
let single_read t dst = method read_into dst =
let avail, src = Cstruct.fillv ~dst ~src:!t in let avail, src = Cstruct.fillv ~dst ~src:data in
if avail = 0 then raise End_of_file; if avail = 0 then raise End_of_file;
t := src; data <- src;
avail avail
end end
let cstruct_source = let string_source s : source =
let ops = Pi.source (module Cstruct_source) in object
fun data -> Resource.T (Cstruct_source.create data, ops) val mutable offset = 0
module String_source = struct inherit source
type t = {
s : string;
mutable offset : int;
}
let single_read t dst = method read_into dst =
if t.offset = String.length t.s then raise End_of_file; if offset = String.length s then raise End_of_file;
let len = min (Cstruct.length dst) (String.length t.s - t.offset) in
Cstruct.blit_from_string t.s t.offset dst 0 len; let len = min (Cstruct.length dst) (String.length s - offset) in
t.offset <- t.offset + len; Cstruct.blit_from_string s offset dst 0 len;
offset <- offset + len;
len len
let read_methods = []
let create s = { s; offset = 0 }
end end
let string_source = class virtual sink = object (self : <Generic.t; ..>)
let ops = Pi.source (module String_source) in method probe _ = None
fun s -> Resource.T (String_source.create s, ops) method virtual copy : 'a. (#source as 'a) -> unit
method write bufs = self#copy (cstruct_source bufs)
end
let write (Resource.T (t, ops)) bufs = let write (t : #sink) (bufs : Cstruct.t list) = t#write bufs
let module X = (val (Resource.get ops Sink)) in
X.write t bufs
let copy src (Resource.T (t, ops)) = let copy (src : #source) (dst : #sink) = dst#copy src
let module X = (val (Resource.get ops Sink)) in
X.copy t ~src
let copy_string s = copy (string_source s) let copy_string s = copy (string_source s)
module Buffer_sink = struct let buffer_sink b =
type t = Buffer.t object
inherit sink
let copy t ~src:(Resource.T (src, ops)) = method copy src =
let module Src = (val (Resource.get ops Source)) in
let buf = Cstruct.create 4096 in let buf = Cstruct.create 4096 in
try try
while true do while true do
let got = Src.single_read src buf in let got = src#read_into buf in
Buffer.add_string t (Cstruct.to_string ~len:got buf) Buffer.add_string b (Cstruct.to_string ~len:got buf)
done done
with End_of_file -> () with End_of_file -> ()
let write t bufs = method! write bufs =
List.iter (fun buf -> Buffer.add_bytes t (Cstruct.to_bytes buf)) bufs List.iter (fun buf -> Buffer.add_bytes b (Cstruct.to_bytes buf)) bufs
end end
let buffer_sink = class virtual two_way = object (_ : <source; sink; ..>)
let ops = Pi.sink (module Buffer_sink) in inherit sink
fun b -> Resource.T (b, ops) method read_methods = []
type two_way_ty = [source_ty | sink_ty | shutdown_ty] method virtual shutdown : shutdown_command -> unit
type 'a two_way = ([> two_way_ty] as 'a) r end
let shutdown (Resource.T (t, ops)) cmd = let shutdown (t : #two_way) = t#shutdown
let module X = (val (Resource.get ops Shutdown)) in
X.shutdown t cmd

View File

@ -4,37 +4,24 @@
To read structured data (e.g. a line at a time), wrap a source using {!Buf_read}. *) To read structured data (e.g. a line at a time), wrap a source using {!Buf_read}. *)
open Std
(** {2 Types} *)
type source_ty = [`R | `Flow]
type 'a source = ([> source_ty] as 'a) r
(** A readable flow provides a stream of bytes. *)
type sink_ty = [`W | `Flow]
type 'a sink = ([> sink_ty] as 'a) r
(** A writeable flow accepts a stream of bytes. *)
type shutdown_ty = [`Shutdown]
type 'a shutdown = ([> shutdown_ty] as 'a) r
type 'a read_method = ..
(** Sources can offer a list of ways to read them, in order of preference. *)
type shutdown_command = [
| `Receive (** Indicate that no more reads will be done *)
| `Send (** Indicate that no more writes will be done *)
| `All (** Indicate that no more reads or writes will be done *)
]
(** {2 Reading} *) (** {2 Reading} *)
val single_read : _ source -> Cstruct.t -> int type read_method = ..
(** Sources can offer a list of ways to read them, in order of preference. *)
class virtual source : object
inherit Generic.t
method read_methods : read_method list
method virtual read_into : Cstruct.t -> int
end
val single_read : #source -> Cstruct.t -> int
(** [single_read src buf] reads one or more bytes into [buf]. (** [single_read src buf] reads one or more bytes into [buf].
It returns the number of bytes read (which may be less than the It returns the number of bytes read (which may be less than the
buffer size even if there is more data to be read). buffer size even if there is more data to be read).
[single_read src] just makes a single call to [src#read_into]
(and asserts that the result is in range).
- Use {!read_exact} instead if you want to fill [buf] completely. - Use {!read_exact} instead if you want to fill [buf] completely.
- Use {!Buf_read.line} to read complete lines. - Use {!Buf_read.line} to read complete lines.
@ -44,18 +31,24 @@ val single_read : _ source -> Cstruct.t -> int
@raise End_of_file if there is no more data to read *) @raise End_of_file if there is no more data to read *)
val read_exact : _ source -> Cstruct.t -> unit val read_exact : #source -> Cstruct.t -> unit
(** [read_exact src dst] keeps reading into [dst] until it is full. (** [read_exact src dst] keeps reading into [dst] until it is full.
@raise End_of_file if the buffer could not be filled. *) @raise End_of_file if the buffer could not be filled. *)
val string_source : string -> source_ty r val read_methods : #source -> read_method list
(** [read_methods flow] is a list of extra ways of reading from [flow],
with the preferred (most efficient) methods first.
If no method is suitable, {!read} should be used as the fallback. *)
val string_source : string -> source
(** [string_source s] is a source that gives the bytes of [s]. *) (** [string_source s] is a source that gives the bytes of [s]. *)
val cstruct_source : Cstruct.t list -> source_ty r val cstruct_source : Cstruct.t list -> source
(** [cstruct_source cs] is a source that gives the bytes of [cs]. *) (** [cstruct_source cs] is a source that gives the bytes of [cs]. *)
type 't read_method += Read_source_buffer of ('t -> (Cstruct.t list -> int) -> unit) type read_method += Read_source_buffer of ((Cstruct.t list -> int) -> unit)
(** If a source offers [Read_source_buffer rsb] then the user can call [rsb t fn] (** If a source offers [Read_source_buffer rsb] then the user can call [rsb fn]
to borrow a view of the source's buffers. [fn] returns the number of bytes it consumed. to borrow a view of the source's buffers. [fn] returns the number of bytes it consumed.
[rsb] will raise [End_of_file] if no more data will be produced. [rsb] will raise [End_of_file] if no more data will be produced.
@ -65,7 +58,16 @@ type 't read_method += Read_source_buffer of ('t -> (Cstruct.t list -> int) -> u
(** {2 Writing} *) (** {2 Writing} *)
val write : _ sink -> Cstruct.t list -> unit (** Consumer base class. *)
class virtual sink : object
inherit Generic.t
method virtual copy : 'a. (#source as 'a) -> unit
method write : Cstruct.t list -> unit
(** The default implementation is [copy (cstruct_source ...)], but it can be overridden for speed. *)
end
val write : #sink -> Cstruct.t list -> unit
(** [write dst bufs] writes all bytes from [bufs]. (** [write dst bufs] writes all bytes from [bufs].
You should not perform multiple concurrent writes on the same flow You should not perform multiple concurrent writes on the same flow
@ -76,23 +78,33 @@ val write : _ sink -> Cstruct.t list -> unit
- {!Buf_write} to combine multiple small writes. - {!Buf_write} to combine multiple small writes.
- {!copy} for bulk transfers, as it allows some extra optimizations. *) - {!copy} for bulk transfers, as it allows some extra optimizations. *)
val copy : _ source -> _ sink -> unit val copy : #source -> #sink -> unit
(** [copy src dst] copies data from [src] to [dst] until end-of-file. *) (** [copy src dst] copies data from [src] to [dst] until end-of-file. *)
val copy_string : string -> _ sink -> unit val copy_string : string -> #sink -> unit
(** [copy_string s = copy (string_source s)] *) (** [copy_string s = copy (string_source s)] *)
val buffer_sink : Buffer.t -> sink_ty r val buffer_sink : Buffer.t -> sink
(** [buffer_sink b] is a sink that adds anything sent to it to [b]. (** [buffer_sink b] is a sink that adds anything sent to it to [b].
To collect data as a cstruct, use {!Buf_read} instead. *) To collect data as a cstruct, use {!Buf_read} instead. *)
(** {2 Bidirectional streams} *) (** {2 Bidirectional streams} *)
type two_way_ty = [source_ty | sink_ty | shutdown_ty] type shutdown_command = [
type 'a two_way = ([> two_way_ty] as 'a) r | `Receive (** Indicate that no more reads will be done *)
| `Send (** Indicate that no more writes will be done *)
| `All (** Indicate that no more reads or writes will be done *)
]
val shutdown : _ two_way -> shutdown_command -> unit class virtual two_way : object
inherit source
inherit sink
method virtual shutdown : shutdown_command -> unit
end
val shutdown : #two_way -> shutdown_command -> unit
(** [shutdown t cmd] indicates that the caller has finished reading or writing [t] (** [shutdown t cmd] indicates that the caller has finished reading or writing [t]
(depending on [cmd]). (depending on [cmd]).
@ -104,44 +116,7 @@ val shutdown : _ two_way -> shutdown_command -> unit
Flows are usually attached to switches and closed automatically when the switch Flows are usually attached to switches and closed automatically when the switch
finishes. However, it can be useful to close them sooner manually in some cases. *) finishes. However, it can be useful to close them sooner manually in some cases. *)
val close : [> `Close] r -> unit class type close = Generic.close
(** Alias of {!Resource.close}. *)
(** {2 Provider Interface} *)
module Pi : sig
module type SOURCE = sig
type t
val read_methods : t read_method list
val single_read : t -> Cstruct.t -> int
end
module type SINK = sig
type t
val copy : t -> src:_ source -> unit
val write : t -> Cstruct.t list -> unit
end
module type SHUTDOWN = sig
type t
val shutdown : t -> shutdown_command -> unit
end
val source : (module SOURCE with type t = 't) -> ('t, source_ty) Resource.handler
val sink : (module SINK with type t = 't) -> ('t, sink_ty) Resource.handler
val shutdown : (module SHUTDOWN with type t = 't) -> ('t, shutdown_ty) Resource.handler
module type TWO_WAY = sig
include SHUTDOWN
include SOURCE with type t := t
include SINK with type t := t
end
val two_way : (module TWO_WAY with type t = 't) -> ('t, two_way_ty) Resource.handler
type (_, _, _) Resource.pi +=
| Source : ('t, (module SOURCE with type t = 't), [> source_ty]) Resource.pi
| Sink : ('t, (module SINK with type t = 't), [> sink_ty]) Resource.pi
| Shutdown : ('t, (module SHUTDOWN with type t = 't), [> shutdown_ty]) Resource.pi
end
val close : #close -> unit
(** Alias of {!Generic.close}. *)

View File

@ -1,7 +1,5 @@
(** Defines types used by file-systems. *) (** Defines types used by file-systems. *)
open Std
type path = string type path = string
type error = type error =
@ -38,32 +36,24 @@ type create = [
] ]
(** If a new file is created, the given permissions are used for it. *) (** If a new file is created, the given permissions are used for it. *)
type dir_ty = [`Dir]
type 'a dir = ([> dir_ty] as 'a) r
(** Note: use the functions in {!Path} to access directories. *) (** Note: use the functions in {!Path} to access directories. *)
module Pi = struct class virtual dir = object (_ : #Generic.t)
module type DIR = sig method probe _ = None
type t method virtual open_in : sw:Switch.t -> path -> <File.ro; Flow.close>
method virtual open_out :
val open_in : t -> sw:Switch.t -> path -> File.ro_ty r
val open_out :
t ->
sw:Switch.t -> sw:Switch.t ->
append:bool -> append:bool ->
create:create -> create:create ->
path -> File.rw_ty r path -> <File.rw; Flow.close>
method virtual mkdir : perm:File.Unix_perm.t -> path -> unit
val mkdir : t -> perm:File.Unix_perm.t -> path -> unit method virtual open_dir : sw:Switch.t -> path -> dir_with_close
val open_dir : t -> sw:Switch.t -> path -> [`Close | dir_ty] r method virtual read_dir : path -> string list
val read_dir : t -> path -> string list method virtual unlink : path -> unit
val unlink : t -> path -> unit method virtual rmdir : path -> unit
val rmdir : t -> path -> unit method virtual rename : path -> dir -> path -> unit
val rename : t -> path -> _ dir -> path -> unit method virtual pp : Format.formatter -> unit
val pp : t Fmt.t
end end
and virtual dir_with_close = object (_ : <Generic.close; ..>)
type (_, _, _) Resource.pi += (* This dummy class avoids an "Error: The type < .. > is not an object type" error from the compiler. *)
| Dir : ('t, (module DIR with type t = 't), [> dir_ty]) Resource.pi inherit dir
end end

13
lib_eio/generic.ml Normal file
View File

@ -0,0 +1,13 @@
type 'a ty = ..
class type t = object
method probe : 'a. 'a ty -> 'a option
end
let probe (t : #t) ty = t#probe ty
class type close = object
method close : unit
end
let close (t : #close) = t#close

30
lib_eio/generic.mli Normal file
View File

@ -0,0 +1,30 @@
type 'a ty = ..
(** An ['a ty] is a query for a feature of type ['a]. *)
class type t = object
method probe : 'a. 'a ty -> 'a option
end
val probe : #t -> 'a ty -> 'a option
(** [probe t feature] checks whether [t] supports [feature].
This is mostly for internal use.
For example, {!Eio_unix.FD.peek_opt} uses this to get the underlying Unix file descriptor from a flow. *)
(** {2 Closing}
Resources are usually attached to switches and closed automatically when the switch
finishes. However, it can be useful to close them sooner in some cases. *)
class type close = object
method close : unit
end
val close : #close -> unit
(** [close t] marks the resource as closed. It can no longer be used after this.
If [t] is already closed then this does nothing (it does not raise an exception).
Note: if an operation is currently in progress when this is called then it is not
necessarily cancelled, and any underlying OS resource (such as a file descriptor)
might not be closed immediately if other operations are using it. Closing a resource
only prevents new operations from starting. *)

View File

@ -34,8 +34,6 @@
]} ]}
*) *)
open Eio.Std
(** {2 Configuration} *) (** {2 Configuration} *)
(** Actions that can be performed by mock handlers. *) (** Actions that can be performed by mock handlers. *)
@ -91,8 +89,14 @@ module Flow : sig
| `Read_source_buffer (** Use the {!Eio.Flow.Read_source_buffer} optimisation. *) | `Read_source_buffer (** Use the {!Eio.Flow.Read_source_buffer} optimisation. *)
] ]
type ty = [`Generic | `Mock] Eio.Net.stream_socket_ty type t = <
type t = ty r Eio.Flow.two_way;
Eio.Flow.close;
on_read : string Handler.t;
on_copy_bytes : int Handler.t;
set_copy_method : copy_method -> unit;
attach_to_switch : Eio.Switch.t -> unit;
>
val make : ?pp:string Fmt.t -> string -> t val make : ?pp:string Fmt.t -> string -> t
(** [make label] is a mock Eio flow. (** [make label] is a mock Eio flow.
@ -112,20 +116,30 @@ end
(** Mock {!Eio.Net} networks and sockets. *) (** Mock {!Eio.Net} networks and sockets. *)
module Net : sig module Net : sig
type t = [`Generic | `Mock] Eio.Net.ty r type t = <
Eio.Net.t;
on_listen : Eio.Net.listening_socket Handler.t;
on_connect : Eio.Net.stream_socket Handler.t;
on_datagram_socket : Eio.Net.datagram_socket Handler.t;
on_getaddrinfo : Eio.Net.Sockaddr.t list Handler.t;
on_getnameinfo : (string * string) Handler.t;
>
type listening_socket = [`Generic | `Mock] Eio.Net.listening_socket_ty r type listening_socket = <
Eio.Net.listening_socket;
on_accept : (Flow.t * Eio.Net.Sockaddr.stream) Handler.t;
>
val make : string -> t val make : string -> t
(** [make label] is a new mock network. *) (** [make label] is a new mock network. *)
val on_connect : t -> _ Eio.Net.stream_socket Handler.actions -> unit val on_connect : t -> <Eio.Net.stream_socket; ..> Handler.actions -> unit
(** [on_connect t actions] configures what to do when a client tries to connect somewhere. *) (** [on_connect t actions] configures what to do when a client tries to connect somewhere. *)
val on_listen : t -> _ Eio.Net.listening_socket Handler.actions -> unit val on_listen : t -> #Eio.Net.listening_socket Handler.actions -> unit
(** [on_listen t actions] configures what to do when a server starts listening for incoming connections. *) (** [on_listen t actions] configures what to do when a server starts listening for incoming connections. *)
val on_datagram_socket : t -> _ Eio.Net.datagram_socket Handler.actions -> unit val on_datagram_socket : t -> <Eio.Net.datagram_socket; ..> Handler.actions -> unit
(** [on_datagram_socket t actions] configures how to create datagram sockets. *) (** [on_datagram_socket t actions] configures how to create datagram sockets. *)
val on_getaddrinfo : t -> Eio.Net.Sockaddr.t list Handler.actions -> unit val on_getaddrinfo : t -> Eio.Net.Sockaddr.t list Handler.actions -> unit

View File

@ -5,17 +5,14 @@ type copy_method = [
| `Read_source_buffer | `Read_source_buffer
] ]
module Mock_flow = struct type t = <
type tag = [`Generic | `Mock] Eio.Flow.two_way;
Eio.Flow.close;
type t = {
label : string;
pp : string Fmt.t;
on_close : (unit -> unit) Queue.t;
on_read : string Handler.t; on_read : string Handler.t;
on_copy_bytes : int Handler.t; on_copy_bytes : int Handler.t;
mutable copy_method : copy_method; set_copy_method : copy_method -> unit;
} attach_to_switch : Switch.t -> unit;
>
let pp_default f s = let pp_default f s =
let rec aux i = let rec aux i =
@ -37,100 +34,83 @@ module Mock_flow = struct
| x :: _ when Cstruct.length x >= len -> [Cstruct.sub x 0 len] | x :: _ when Cstruct.length x >= len -> [Cstruct.sub x 0 len]
| x :: xs -> x :: takev (len - Cstruct.length x) xs | x :: xs -> x :: takev (len - Cstruct.length x) xs
let make ?(pp=pp_default) label =
let on_read = Handler.make (`Raise End_of_file) in
let on_copy_bytes = Handler.make (`Return 4096) in
let copy_method = ref `Read_into in
(* Test optimised copying using Read_source_buffer *) (* Test optimised copying using Read_source_buffer *)
let copy_rsb_iovec t src = let copy_rsb_iovec src =
let size = Handler.run t.on_copy_bytes in let size = Handler.run on_copy_bytes in
let len = min (Cstruct.lenv src) size in let len = min (Cstruct.lenv src) size in
let bufs = takev len src in let bufs = takev len src in
traceln "%s: wrote (rsb) @[<v>%a@]" t.label (Fmt.Dump.list (Fmt.using Cstruct.to_string t.pp)) bufs; traceln "%s: wrote (rsb) @[<v>%a@]" label (Fmt.Dump.list (Fmt.using Cstruct.to_string pp)) bufs;
len len
in
let copy_rsb t rsb = let copy_rsb rsb =
try while true do rsb (copy_rsb_iovec t) done try while true do rsb copy_rsb_iovec done
with End_of_file -> () with End_of_file -> ()
in
(* Test fallback copy using buffer. *) (* Test fallback copy using buffer. *)
let copy_via_buffer t src = let copy_via_buffer src =
try try
while true do while true do
let size = Handler.run t.on_copy_bytes in let size = Handler.run on_copy_bytes in
let buf = Cstruct.create size in let buf = Cstruct.create size in
let n = Eio.Flow.single_read src buf in let n = Eio.Flow.single_read src buf in
traceln "%s: wrote @[<v>%a@]" t.label t.pp (Cstruct.to_string (Cstruct.sub buf 0 n)) traceln "%s: wrote @[<v>%a@]" label pp (Cstruct.to_string (Cstruct.sub buf 0 n))
done done
with End_of_file -> () with End_of_file -> ()
in
object (self)
inherit Eio.Flow.two_way
let read_methods = [] val on_close = Queue.create ()
let single_read t buf = method on_read = on_read
let data = Handler.run t.on_read in method on_copy_bytes = on_copy_bytes
method read_into buf =
let data = Handler.run on_read in
let len = String.length data in let len = String.length data in
if Cstruct.length buf < len then if Cstruct.length buf < len then
Fmt.failwith "%s: read buffer too short for %a!" t.label t.pp data; Fmt.failwith "%s: read buffer too short for %a!" label pp data;
Cstruct.blit_from_string data 0 buf 0 len; Cstruct.blit_from_string data 0 buf 0 len;
traceln "%s: read @[<v>%a@]" t.label t.pp data; traceln "%s: read @[<v>%a@]" label pp data;
len len
let copy t ~src = method copy src =
match t.copy_method with match !copy_method with
| `Read_into -> copy_via_buffer t src | `Read_into -> copy_via_buffer src
| `Read_source_buffer -> | `Read_source_buffer ->
let Eio.Resource.T (src, ops) = src in
let module Src = (val (Eio.Resource.get ops Eio.Flow.Pi.Source)) in
let try_rsb = function let try_rsb = function
| Eio.Flow.Read_source_buffer rsb -> copy_rsb t (rsb src); true | Eio.Flow.Read_source_buffer rsb -> copy_rsb rsb; true
| _ -> false | _ -> false
in in
if not (List.exists try_rsb Src.read_methods) then if not (List.exists try_rsb (Eio.Flow.read_methods src)) then
Fmt.failwith "Source does not offer Read_source_buffer optimisation" Fmt.failwith "Source does not offer Read_source_buffer optimisation"
let write t bufs = method set_copy_method m =
copy t ~src:(Eio.Flow.cstruct_source bufs) copy_method := m
let shutdown t cmd = method shutdown cmd =
traceln "%s: shutdown %s" t.label @@ traceln "%s: shutdown %s" label @@
match cmd with match cmd with
| `Receive -> "receive" | `Receive -> "receive"
| `Send -> "send" | `Send -> "send"
| `All -> "all" | `All -> "all"
let close t = method attach_to_switch sw =
while not (Queue.is_empty t.on_close) do let hook = Switch.on_release_cancellable sw (fun () -> Eio.Flow.close self) in
Queue.take t.on_close () Queue.add (fun () -> Eio.Switch.remove_hook hook) on_close
done;
traceln "%s: closed" t.label
let make ?(pp=pp_default) label = method close =
{ while not (Queue.is_empty on_close) do
pp; Queue.take on_close ()
label; done;
on_close = Queue.create (); traceln "%s: closed" label
on_read = Handler.make (`Raise End_of_file);
on_copy_bytes = Handler.make (`Return 4096);
copy_method = `Read_into;
}
end end
type ty = [`Generic | `Mock] Eio.Net.stream_socket_ty let on_read (t:t) = Handler.seq t#on_read
let on_copy_bytes (t:t) = Handler.seq t#on_copy_bytes
type t = ty r let set_copy_method (t:t) = t#set_copy_method
let attach_to_switch (t:t) = t#attach_to_switch
type (_, _, _) Eio.Resource.pi += Type : ('t, 't -> Mock_flow.t, ty) Eio.Resource.pi
let raw (Eio.Resource.T (t, ops)) = Eio.Resource.get ops Type t
let attach_to_switch t sw =
let t = raw t in
let hook = Switch.on_release_cancellable sw (fun () -> Mock_flow.close t) in
Queue.add (fun () -> Eio.Switch.remove_hook hook) t.on_close
let on_read t = Handler.seq (raw t).on_read
let on_copy_bytes t = Handler.seq (raw t).on_copy_bytes
let set_copy_method t v = (raw t).copy_method <- v
let handler = Eio.Resource.handler (
H (Type, Fun.id) ::
Eio.Resource.bindings (Eio.Net.Pi.stream_socket (module Mock_flow))
)
let make ?pp label : t =
Eio.Resource.T (Mock_flow.make ?pp label, handler)

View File

@ -1,138 +1,98 @@
open Eio.Std open Eio.Std
type ty = [`Generic | `Mock] Eio.Net.ty type t = <
type t = ty r Eio.Net.t;
on_listen : Eio.Net.listening_socket Handler.t;
module Impl = struct on_connect : Eio.Net.stream_socket Handler.t;
type tag = [`Generic] on_datagram_socket : Eio.Net.datagram_socket Handler.t;
type t = {
label : string;
on_listen : tag Eio.Net.listening_socket_ty r Handler.t;
on_connect : tag Eio.Net.stream_socket_ty r Handler.t;
on_datagram_socket : tag Eio.Net.datagram_socket_ty r Handler.t;
on_getaddrinfo : Eio.Net.Sockaddr.t list Handler.t; on_getaddrinfo : Eio.Net.Sockaddr.t list Handler.t;
on_getnameinfo : (string * string) Handler.t; on_getnameinfo : (string * string) Handler.t;
} >
let make label = {
label;
on_listen = Handler.make (`Raise (Failure "Mock listen handler not configured"));
on_connect = Handler.make (`Raise (Failure "Mock connect handler not configured"));
on_datagram_socket = Handler.make (`Raise (Failure "Mock datagram_socket handler not configured"));
on_getaddrinfo = Handler.make (`Raise (Failure "Mock getaddrinfo handler not configured"));
on_getnameinfo = Handler.make (`Raise (Failure "Mock getnameinfo handler not configured"));
}
let on_listen t = t.on_listen
let on_connect t = t.on_connect
let on_datagram_socket t = t.on_datagram_socket
let on_getaddrinfo t = t.on_getaddrinfo
let on_getnameinfo t = t.on_getnameinfo
let listen t ~reuse_addr:_ ~reuse_port:_ ~backlog:_ ~sw addr =
traceln "%s: listen on %a" t.label Eio.Net.Sockaddr.pp addr;
let socket = Handler.run t.on_listen in
Switch.on_release sw (fun () -> Eio.Resource.close socket);
socket
let connect t ~sw addr =
traceln "%s: connect to %a" t.label Eio.Net.Sockaddr.pp addr;
let socket = Handler.run t.on_connect in
Switch.on_release sw (fun () -> Eio.Flow.close socket);
socket
let datagram_socket t ~reuse_addr:_ ~reuse_port:_ ~sw addr =
(match addr with
| #Eio.Net.Sockaddr.datagram as saddr -> traceln "%s: datagram_socket %a" t.label Eio.Net.Sockaddr.pp saddr
| `UdpV4 -> traceln "%s: datagram_socket UDPv4" t.label
| `UdpV6 -> traceln "%s: datagram_socket UDPv6" t.label
);
let socket = Handler.run t.on_datagram_socket in
Switch.on_release sw (fun () -> Eio.Flow.close socket);
socket
let getaddrinfo t ~service node =
traceln "%s: getaddrinfo ~service:%s %s" t.label service node;
Handler.run t.on_getaddrinfo
let getnameinfo t sockaddr =
traceln "%s: getnameinfo %a" t.label Eio.Net.Sockaddr.pp sockaddr;
Handler.run t.on_getnameinfo
type (_, _, _) Eio.Resource.pi += Raw : ('t, 't -> t, ty) Eio.Resource.pi
let raw (Eio.Resource.T (t, ops)) = Eio.Resource.get ops Raw t
end
let make : string -> t =
let super = Eio.Net.Pi.network (module Impl) in
let handler = Eio.Resource.handler (
H (Impl.Raw, Fun.id) ::
Eio.Resource.bindings super
) in
fun label -> Eio.Resource.T (Impl.make label, handler)
let on_connect (t:t) actions =
let t = Impl.raw t in
let as_socket x = (x :> [`Generic] Eio.Net.stream_socket_ty r) in
Handler.seq t.on_connect (List.map (Action.map as_socket) actions)
let on_listen (t:t) actions =
let t = Impl.raw t in
let as_socket x = (x :> [`Generic] Eio.Net.listening_socket_ty r) in
Handler.seq t.on_listen (List.map (Action.map as_socket) actions)
let on_datagram_socket (t:t) (actions : _ r Handler.actions) =
let t = Impl.raw t in
let as_socket x = (x :> [`Generic] Eio.Net.datagram_socket_ty r) in
Handler.seq t.on_datagram_socket (List.map (Action.map as_socket) actions)
let on_getaddrinfo (t:t) actions = Handler.seq (Impl.raw t).on_getaddrinfo actions
let on_getnameinfo (t:t) actions = Handler.seq (Impl.raw t).on_getnameinfo actions
type listening_socket_ty = [`Generic | `Mock] Eio.Net.listening_socket_ty
type listening_socket = listening_socket_ty r
module Listening_socket = struct
type t = {
label : string;
on_accept : (Flow.t * Eio.Net.Sockaddr.stream) Handler.t;
}
type tag = [`Generic]
let make label = let make label =
{ let on_listen = Handler.make (`Raise (Failure "Mock listen handler not configured")) in
label; let on_connect = Handler.make (`Raise (Failure "Mock connect handler not configured")) in
on_accept = Handler.make (`Raise (Failure "Mock accept handler not configured")) let on_datagram_socket = Handler.make (`Raise (Failure "Mock datagram_socket handler not configured")) in
} let on_getaddrinfo = Handler.make (`Raise (Failure "Mock getaddrinfo handler not configured")) in
let on_getnameinfo = Handler.make (`Raise (Failure "Mock getnameinfo handler not configured")) in
object
inherit Eio.Net.t
let on_accept t = t.on_accept method on_listen = on_listen
method on_connect = on_connect
method on_datagram_socket = on_datagram_socket
method on_getaddrinfo = on_getaddrinfo
method on_getnameinfo = on_getnameinfo
let accept t ~sw = method listen ~reuse_addr:_ ~reuse_port:_ ~backlog:_ ~sw addr =
let socket, addr = Handler.run t.on_accept in traceln "%s: listen on %a" label Eio.Net.Sockaddr.pp addr;
Flow.attach_to_switch (socket : Flow.t) sw; let socket = Handler.run on_listen in
traceln "%s: accepted connection from %a" t.label Eio.Net.Sockaddr.pp addr; Switch.on_release sw (fun () -> Eio.Flow.close socket);
(socket :> tag Eio.Net.stream_socket_ty r), addr socket
let close t = method connect ~sw addr =
traceln "%s: closed" t.label traceln "%s: connect to %a" label Eio.Net.Sockaddr.pp addr;
let socket = Handler.run on_connect in
Switch.on_release sw (fun () -> Eio.Flow.close socket);
socket
type (_, _, _) Eio.Resource.pi += Type : ('t, 't -> t, listening_socket_ty) Eio.Resource.pi method datagram_socket ~reuse_addr:_ ~reuse_port:_ ~sw addr =
let raw (Eio.Resource.T (t, ops)) = Eio.Resource.get ops Type t (match addr with
| #Eio.Net.Sockaddr.datagram as saddr -> traceln "%s: datagram_socket %a" label Eio.Net.Sockaddr.pp saddr
| `UdpV4 -> traceln "%s: datagram_socket UDPv4" label
| `UdpV6 -> traceln "%s: datagram_socket UDPv6" label
);
let socket = Handler.run on_datagram_socket in
Switch.on_release sw (fun () -> Eio.Flow.close socket);
socket
method getaddrinfo ~service node =
traceln "%s: getaddrinfo ~service:%s %s" label service node;
Handler.run on_getaddrinfo
method getnameinfo sockaddr =
traceln "%s: getnameinfo %a" label Eio.Net.Sockaddr.pp sockaddr;
Handler.run on_getnameinfo
end end
let listening_socket_handler = let on_connect (t:t) actions =
Eio.Resource.handler @@ let as_socket x = (x :> Eio.Net.stream_socket) in
Eio.Resource.bindings (Eio.Net.Pi.listening_socket (module Listening_socket)) @ [ Handler.seq t#on_connect (List.map (Action.map as_socket) actions)
H (Listening_socket.Type, Fun.id);
]
let listening_socket label : listening_socket = let on_listen (t:t) actions =
Eio.Resource.T (Listening_socket.make label, listening_socket_handler) let as_socket x = (x :> Eio.Net.listening_socket) in
Handler.seq t#on_listen (List.map (Action.map as_socket) actions)
let on_accept l actions = let on_datagram_socket (t:t) actions =
let r = Listening_socket.raw l in let as_socket x = (x :> Eio.Net.datagram_socket) in
Handler.seq t#on_datagram_socket (List.map (Action.map as_socket) actions)
let on_getaddrinfo (t:t) actions = Handler.seq t#on_getaddrinfo actions
let on_getnameinfo (t:t) actions = Handler.seq t#on_getnameinfo actions
type listening_socket = <
Eio.Net.listening_socket;
on_accept : (Flow.t * Eio.Net.Sockaddr.stream) Handler.t;
>
let listening_socket label =
let on_accept = Handler.make (`Raise (Failure "Mock accept handler not configured")) in
object
inherit Eio.Net.listening_socket
method on_accept = on_accept
method accept ~sw =
let socket, addr = Handler.run on_accept in
Flow.attach_to_switch socket sw;
traceln "%s: accepted connection from %a" label Eio.Net.Sockaddr.pp addr;
(socket :> Eio.Net.stream_socket), addr
method close =
traceln "%s: closed" label
end
let on_accept (l:listening_socket) actions =
let as_accept_pair x = (x :> Flow.t * Eio.Net.Sockaddr.stream) in let as_accept_pair x = (x :> Flow.t * Eio.Net.Sockaddr.stream) in
Handler.seq r.on_accept (List.map (Action.map as_accept_pair) actions) Handler.seq l#on_accept (List.map (Action.map as_accept_pair) actions)

View File

@ -1,5 +1,3 @@
open Std
type connection_failure = type connection_failure =
| Refused of Exn.Backend.t | Refused of Exn.Backend.t
| No_matching_addresses | No_matching_addresses
@ -159,114 +157,30 @@ module Sockaddr = struct
Format.fprintf f "udp:%a:%d" Ipaddr.pp_for_uri addr port Format.fprintf f "udp:%a:%d" Ipaddr.pp_for_uri addr port
end end
type socket_ty = [`Socket | `Close] class virtual socket = object (_ : <Generic.t; Generic.close; ..>)
type 'a socket = ([> socket_ty] as 'a) r method probe _ = None
type 'tag stream_socket_ty = [`Stream | `Platform of 'tag | `Shutdown | socket_ty | Flow.source_ty | Flow.sink_ty]
type 'a stream_socket = 'a r
constraint 'a = [> [> `Generic] stream_socket_ty]
type 'tag listening_socket_ty = [ `Accept | `Platform of 'tag | socket_ty]
type 'a listening_socket = 'a r
constraint 'a = [> [> `Generic] listening_socket_ty]
type 'a connection_handler = 'a stream_socket -> Sockaddr.stream -> unit
type 'tag datagram_socket_ty = [`Datagram | `Platform of 'tag | `Shutdown | socket_ty]
type 'a datagram_socket = 'a r
constraint 'a = [> [> `Generic] datagram_socket_ty]
type 'tag ty = [`Network | `Platform of 'tag]
type 'a t = 'a r
constraint 'a = [> [> `Generic] ty]
module Pi = struct
module type STREAM_SOCKET = sig
type tag
include Flow.Pi.SHUTDOWN
include Flow.Pi.SOURCE with type t := t
include Flow.Pi.SINK with type t := t
val close : t -> unit
end end
let stream_socket (type t tag) (module X : STREAM_SOCKET with type t = t and type tag = tag) = class virtual stream_socket = object (_ : #socket)
Resource.handler @@ inherit Flow.two_way
H (Resource.Close, X.close) ::
Resource.bindings (Flow.Pi.two_way (module X))
module type DATAGRAM_SOCKET = sig
type tag
include Flow.Pi.SHUTDOWN
val send : t -> ?dst:Sockaddr.datagram -> Cstruct.t list -> unit
val recv : t -> Cstruct.t -> Sockaddr.datagram * int
val close : t -> unit
end end
type (_, _, _) Resource.pi += class virtual listening_socket = object (_ : <Generic.close; ..>)
| Datagram_socket : ('t, (module DATAGRAM_SOCKET with type t = 't), [> _ datagram_socket_ty]) Resource.pi inherit socket
method virtual accept : sw:Switch.t -> stream_socket * Sockaddr.stream
let datagram_socket (type t tag) (module X : DATAGRAM_SOCKET with type t = t and type tag = tag) =
Resource.handler @@
Resource.bindings (Flow.Pi.shutdown (module X)) @ [
H (Datagram_socket, (module X));
H (Resource.Close, X.close)
]
module type LISTENING_SOCKET = sig
type t
type tag
val accept : t -> sw:Switch.t -> tag stream_socket_ty r * Sockaddr.stream
val close : t -> unit
end end
type (_, _, _) Resource.pi += type connection_handler = stream_socket -> Sockaddr.stream -> unit
| Listening_socket : ('t, (module LISTENING_SOCKET with type t = 't and type tag = 'tag), [> 'tag listening_socket_ty]) Resource.pi
let listening_socket (type t tag) (module X : LISTENING_SOCKET with type t = t and type tag = tag) = let accept ~sw (t : #listening_socket) = t#accept ~sw
Resource.handler [
H (Resource.Close, X.close);
H (Listening_socket, (module X))
]
module type NETWORK = sig let accept_fork ~sw (t : #listening_socket) ~on_error handle =
type t
type tag
val listen : t -> reuse_addr:bool -> reuse_port:bool -> backlog:int -> sw:Switch.t -> Sockaddr.stream -> tag listening_socket_ty r
val connect : t -> sw:Switch.t -> Sockaddr.stream -> tag stream_socket_ty r
val datagram_socket :
t
-> reuse_addr:bool
-> reuse_port:bool
-> sw:Switch.t
-> [Sockaddr.datagram | `UdpV4 | `UdpV6]
-> tag datagram_socket_ty r
val getaddrinfo : t -> service:string -> string -> Sockaddr.t list
val getnameinfo : t -> Sockaddr.t -> (string * string)
end
type (_, _, _) Resource.pi +=
| Network : ('t, (module NETWORK with type t = 't and type tag = 'tag), [> 'tag ty]) Resource.pi
let network (type t tag) (module X : NETWORK with type t = t and type tag = tag) =
Resource.handler [
H (Network, (module X));
]
end
let accept ~sw (type tag) (Resource.T (t, ops) : [> tag listening_socket_ty] r) =
let module X = (val (Resource.get ops Pi.Listening_socket)) in
X.accept t ~sw
let accept_fork ~sw (t : [> 'a listening_socket_ty] r) ~on_error handle =
let child_started = ref false in let child_started = ref false in
let flow, addr = accept ~sw t in let flow, addr = accept ~sw t in
Fun.protect ~finally:(fun () -> if !child_started = false then Flow.close flow) Fun.protect ~finally:(fun () -> if !child_started = false then Flow.close flow)
(fun () -> (fun () ->
Fiber.fork ~sw (fun () -> Fiber.fork ~sw (fun () ->
match child_started := true; handle (flow :> 'a stream_socket_ty r) addr with match child_started := true; handle (flow :> stream_socket) addr with
| x -> Flow.close flow; x | x -> Flow.close flow; x
| exception (Cancel.Cancelled _ as ex) -> | exception (Cancel.Cancelled _ as ex) ->
Flow.close flow; Flow.close flow;
@ -277,37 +191,42 @@ let accept_fork ~sw (t : [> 'a listening_socket_ty] r) ~on_error handle =
) )
) )
let send (Resource.T (t, ops)) ?dst bufs = class virtual datagram_socket = object
let module X = (val (Resource.get ops Pi.Datagram_socket)) in inherit socket
X.send t ?dst bufs method virtual send : ?dst:Sockaddr.datagram -> Cstruct.t list -> unit
method virtual recv : Cstruct.t -> Sockaddr.datagram * int
end
let recv (Resource.T (t, ops)) buf = let send (t:#datagram_socket) = t#send
let module X = (val (Resource.get ops Pi.Datagram_socket)) in let recv (t:#datagram_socket) = t#recv
X.recv t buf
let listen (type tag) ?(reuse_addr=false) ?(reuse_port=false) ~backlog ~sw (t:[> tag ty] r) = class virtual t = object
let (Resource.T (t, ops)) = t in method virtual listen : reuse_addr:bool -> reuse_port:bool -> backlog:int -> sw:Switch.t -> Sockaddr.stream -> listening_socket
let module X = (val (Resource.get ops Pi.Network)) in method virtual connect : sw:Switch.t -> Sockaddr.stream -> stream_socket
X.listen t ~reuse_addr ~reuse_port ~backlog ~sw method virtual datagram_socket :
reuse_addr:bool
-> reuse_port:bool
-> sw:Switch.t
-> [Sockaddr.datagram | `UdpV4 | `UdpV6]
-> datagram_socket
let connect (type tag) ~sw (t:[> tag ty] r) addr = method virtual getaddrinfo : service:string -> string -> Sockaddr.t list
let (Resource.T (t, ops)) = t in method virtual getnameinfo : Sockaddr.t -> (string * string)
let module X = (val (Resource.get ops Pi.Network)) in end
try X.connect t ~sw addr
let listen ?(reuse_addr=false) ?(reuse_port=false) ~backlog ~sw (t:#t) = t#listen ~reuse_addr ~reuse_port ~backlog ~sw
let connect ~sw (t:#t) addr =
try t#connect ~sw addr
with Exn.Io _ as ex -> with Exn.Io _ as ex ->
let bt = Printexc.get_raw_backtrace () in let bt = Printexc.get_raw_backtrace () in
Exn.reraise_with_context ex bt "connecting to %a" Sockaddr.pp addr Exn.reraise_with_context ex bt "connecting to %a" Sockaddr.pp addr
let datagram_socket (type tag) ?(reuse_addr=false) ?(reuse_port=false) ~sw (t:[> tag ty] r) addr = let datagram_socket ?(reuse_addr=false) ?(reuse_port=false) ~sw (t:#t) addr =
let (Resource.T (t, ops)) = t in
let module X = (val (Resource.get ops Pi.Network)) in
let addr = (addr :> [Sockaddr.datagram | `UdpV4 | `UdpV6]) in let addr = (addr :> [Sockaddr.datagram | `UdpV4 | `UdpV6]) in
X.datagram_socket t ~reuse_addr ~reuse_port ~sw addr t#datagram_socket ~reuse_addr ~reuse_port ~sw addr
let getaddrinfo (type tag) ?(service="") (t:[> tag ty] r) hostname = let getaddrinfo ?(service="") (t:#t) hostname = t#getaddrinfo ~service hostname
let (Resource.T (t, ops)) = t in
let module X = (val (Resource.get ops Pi.Network)) in
X.getaddrinfo t ~service hostname
let getaddrinfo_stream ?service t hostname = let getaddrinfo_stream ?service t hostname =
getaddrinfo ?service t hostname getaddrinfo ?service t hostname
@ -323,12 +242,9 @@ let getaddrinfo_datagram ?service t hostname =
| _ -> None | _ -> None
) )
let getnameinfo (type tag) (t:[> tag ty] r) sockaddr = let getnameinfo (t:#t) sockaddr = t#getnameinfo sockaddr
let (Resource.T (t, ops)) = t in
let module X = (val (Resource.get ops Pi.Network)) in
X.getnameinfo t sockaddr
let close = Resource.close let close = Generic.close
let with_tcp_connect ?(timeout=Time.Timeout.none) ~host ~service t f = let with_tcp_connect ?(timeout=Time.Timeout.none) ~host ~service t f =
Switch.run @@ fun sw -> Switch.run @@ fun sw ->

View File

@ -11,8 +11,6 @@
]} ]}
*) *)
open Std
type connection_failure = type connection_failure =
| Refused of Exn.Backend.t | Refused of Exn.Backend.t
| No_matching_addresses | No_matching_addresses
@ -102,34 +100,45 @@ module Sockaddr : sig
val pp : Format.formatter -> [< t] -> unit val pp : Format.formatter -> [< t] -> unit
end end
(** {2 Types} *) (** {2 Provider Interfaces} *)
type socket_ty = [`Socket | `Close] class virtual socket : object (<Generic.close; ..>)
type 'a socket = ([> socket_ty] as 'a) r inherit Generic.t
end
type 'tag stream_socket_ty = [`Stream | `Platform of 'tag | `Shutdown | socket_ty | Flow.source_ty | Flow.sink_ty] class virtual stream_socket : object
type 'a stream_socket = 'a r inherit socket
constraint 'a = [> [> `Generic] stream_socket_ty] inherit Flow.two_way
end
type 'tag listening_socket_ty = [ `Accept | `Platform of 'tag | socket_ty] class virtual datagram_socket : object
type 'a listening_socket = 'a r inherit socket
constraint 'a = [> [> `Generic] listening_socket_ty] method virtual send : ?dst:Sockaddr.datagram -> Cstruct.t list -> unit
method virtual recv : Cstruct.t -> Sockaddr.datagram * int
end
type 'a connection_handler = 'a stream_socket -> Sockaddr.stream -> unit class virtual listening_socket : object (<Generic.close; ..>)
(** A [_ connection_handler] handles incoming connections from a listening socket. *) inherit socket
method virtual accept : sw:Switch.t -> stream_socket * Sockaddr.stream
end
type 'tag datagram_socket_ty = [`Datagram | `Platform of 'tag | `Shutdown | socket_ty] class virtual t : object
type 'a datagram_socket = 'a r method virtual listen : reuse_addr:bool -> reuse_port:bool -> backlog:int -> sw:Switch.t -> Sockaddr.stream -> listening_socket
constraint 'a = [> [> `Generic] datagram_socket_ty] method virtual connect : sw:Switch.t -> Sockaddr.stream -> stream_socket
method virtual datagram_socket :
reuse_addr:bool
-> reuse_port:bool
-> sw:Switch.t
-> [Sockaddr.datagram | `UdpV4 | `UdpV6]
-> datagram_socket
type 'tag ty = [`Network | `Platform of 'tag] method virtual getaddrinfo : service:string -> string -> Sockaddr.t list
method virtual getnameinfo : Sockaddr.t -> (string * string)
type 'a t = 'a r end
constraint 'a = [> [> `Generic] ty]
(** {2 Out-bound Connections} *) (** {2 Out-bound Connections} *)
val connect : sw:Switch.t -> [> 'tag ty] t -> Sockaddr.stream -> 'tag stream_socket_ty r val connect : sw:Switch.t -> #t -> Sockaddr.stream -> stream_socket
(** [connect ~sw t addr] is a new socket connected to remote address [addr]. (** [connect ~sw t addr] is a new socket connected to remote address [addr].
The new socket will be closed when [sw] finishes, unless closed manually first. *) The new socket will be closed when [sw] finishes, unless closed manually first. *)
@ -138,8 +147,8 @@ val with_tcp_connect :
?timeout:Time.Timeout.t -> ?timeout:Time.Timeout.t ->
host:string -> host:string ->
service:string -> service:string ->
[> 'tag ty] r -> #t ->
('tag stream_socket_ty r -> 'b) -> (stream_socket -> 'b) ->
'b 'b
(** [with_tcp_connect ~host ~service t f] creates a tcp connection [conn] to [host] and [service] and executes (** [with_tcp_connect ~host ~service t f] creates a tcp connection [conn] to [host] and [service] and executes
[f conn]. [f conn].
@ -160,9 +169,7 @@ val with_tcp_connect :
(** {2 Incoming Connections} *) (** {2 Incoming Connections} *)
val listen : val listen : ?reuse_addr:bool -> ?reuse_port:bool -> backlog:int -> sw:Switch.t -> #t -> Sockaddr.stream -> listening_socket
?reuse_addr:bool -> ?reuse_port:bool -> backlog:int -> sw:Switch.t ->
[> 'tag ty] r -> Sockaddr.stream -> 'tag listening_socket_ty r
(** [listen ~sw ~backlog t addr] is a new listening socket bound to local address [addr]. (** [listen ~sw ~backlog t addr] is a new listening socket bound to local address [addr].
The new socket will be closed when [sw] finishes, unless closed manually first. The new socket will be closed when [sw] finishes, unless closed manually first.
@ -176,18 +183,21 @@ val listen :
val accept : val accept :
sw:Switch.t -> sw:Switch.t ->
[> 'tag listening_socket_ty] r -> #listening_socket ->
'tag stream_socket_ty r * Sockaddr.stream stream_socket * Sockaddr.stream
(** [accept ~sw socket] waits until a new connection is ready on [socket] and returns it. (** [accept ~sw socket] waits until a new connection is ready on [socket] and returns it.
The new socket will be closed automatically when [sw] finishes, if not closed earlier. The new socket will be closed automatically when [sw] finishes, if not closed earlier.
If you want to handle multiple connections, consider using {!accept_fork} instead. *) If you want to handle multiple connections, consider using {!accept_fork} instead. *)
type connection_handler = stream_socket -> Sockaddr.stream -> unit
(** [connection_handler] handles incoming connections from a listening socket. *)
val accept_fork : val accept_fork :
sw:Switch.t -> sw:Switch.t ->
[> 'tag listening_socket_ty] r -> #listening_socket ->
on_error:(exn -> unit) -> on_error:(exn -> unit) ->
[< 'tag stream_socket_ty] connection_handler -> connection_handler ->
unit unit
(** [accept_fork ~sw ~on_error socket fn] accepts a connection and handles it in a new fiber. (** [accept_fork ~sw ~on_error socket fn] accepts a connection and handles it in a new fiber.
@ -212,8 +222,8 @@ val run_server :
?additional_domains:(#Domain_manager.t * int) -> ?additional_domains:(#Domain_manager.t * int) ->
?stop:'a Promise.t -> ?stop:'a Promise.t ->
on_error:(exn -> unit) -> on_error:(exn -> unit) ->
[> 'tag listening_socket_ty ] r -> #listening_socket ->
[< 'tag stream_socket_ty] connection_handler -> connection_handler ->
'a 'a
(** [run_server ~on_error sock connection_handler] establishes a concurrent socket server [s]. (** [run_server ~on_error sock connection_handler] establishes a concurrent socket server [s].
@ -243,9 +253,9 @@ val datagram_socket :
?reuse_addr:bool ?reuse_addr:bool
-> ?reuse_port:bool -> ?reuse_port:bool
-> sw:Switch.t -> sw:Switch.t
-> [> 'tag ty] r -> #t
-> [< Sockaddr.datagram | `UdpV4 | `UdpV6] -> [< Sockaddr.datagram | `UdpV4 | `UdpV6]
-> 'tag datagram_socket_ty r -> datagram_socket
(** [datagram_socket ~sw t addr] creates a new datagram socket bound to [addr]. The new (** [datagram_socket ~sw t addr] creates a new datagram socket bound to [addr]. The new
socket will be closed when [sw] finishes. socket will be closed when [sw] finishes.
@ -257,19 +267,19 @@ val datagram_socket :
@param reuse_addr Set the {!Unix.SO_REUSEADDR} socket option. @param reuse_addr Set the {!Unix.SO_REUSEADDR} socket option.
@param reuse_port Set the {!Unix.SO_REUSEPORT} socket option. *) @param reuse_port Set the {!Unix.SO_REUSEPORT} socket option. *)
val send : _ datagram_socket -> ?dst:Sockaddr.datagram -> Cstruct.t list -> unit val send : #datagram_socket -> ?dst:Sockaddr.datagram -> Cstruct.t list -> unit
(** [send sock buf] sends the data in [buf] using the the datagram socket [sock]. (** [send sock buf] sends the data in [buf] using the the datagram socket [sock].
@param dst If [sock] isn't connected, this provides the destination. *) @param dst If [sock] isn't connected, this provides the destination. *)
val recv : _ datagram_socket -> Cstruct.t -> Sockaddr.datagram * int val recv : #datagram_socket -> Cstruct.t -> Sockaddr.datagram * int
(** [recv sock buf] receives data from the socket [sock] putting it in [buf]. The number of bytes received is (** [recv sock buf] receives data from the socket [sock] putting it in [buf]. The number of bytes received is
returned along with the sender address and port. If the [buf] is too small then excess bytes may be discarded returned along with the sender address and port. If the [buf] is too small then excess bytes may be discarded
depending on the type of the socket the message is received from. *) depending on the type of the socket the message is received from. *)
(** {2 DNS queries} *) (** {2 DNS queries} *)
val getaddrinfo: ?service:string -> _ t -> string -> Sockaddr.t list val getaddrinfo: ?service:string -> #t -> string -> Sockaddr.t list
(** [getaddrinfo ?service t node] returns a list of IP addresses for [node]. [node] is either a domain name or (** [getaddrinfo ?service t node] returns a list of IP addresses for [node]. [node] is either a domain name or
an IP address. an IP address.
@ -278,84 +288,18 @@ val getaddrinfo: ?service:string -> _ t -> string -> Sockaddr.t list
For a more thorough treatment, see {{:https://man7.org/linux/man-pages/man3/getaddrinfo.3.html} getaddrinfo}. *) For a more thorough treatment, see {{:https://man7.org/linux/man-pages/man3/getaddrinfo.3.html} getaddrinfo}. *)
val getaddrinfo_stream: ?service:string -> _ t -> string -> Sockaddr.stream list val getaddrinfo_stream: ?service:string -> #t -> string -> Sockaddr.stream list
(** [getaddrinfo_stream] is like {!getaddrinfo}, but filters out non-stream protocols. *) (** [getaddrinfo_stream] is like {!getaddrinfo}, but filters out non-stream protocols. *)
val getaddrinfo_datagram: ?service:string -> _ t -> string -> Sockaddr.datagram list val getaddrinfo_datagram: ?service:string -> #t -> string -> Sockaddr.datagram list
(** [getaddrinfo_datagram] is like {!getaddrinfo}, but filters out non-datagram protocols. *) (** [getaddrinfo_datagram] is like {!getaddrinfo}, but filters out non-datagram protocols. *)
val getnameinfo : _ t -> Sockaddr.t -> (string * string) val getnameinfo : #t -> Sockaddr.t -> (string * string)
(** [getnameinfo t sockaddr] is [(hostname, service)] corresponding to [sockaddr]. [hostname] is the (** [getnameinfo t sockaddr] is [(hostname, service)] corresponding to [sockaddr]. [hostname] is the
registered domain name represented by [sockaddr]. [service] is the IANA specified textual name of the registered domain name represented by [sockaddr]. [service] is the IANA specified textual name of the
port specified in [sockaddr], e.g. 'ftp', 'http', 'https', etc. *) port specified in [sockaddr], e.g. 'ftp', 'http', 'https', etc. *)
(** {2 Closing} *) (** {2 Closing} *)
val close : [> `Close] r -> unit val close : #Generic.close -> unit
(** Alias of {!Resource.close}. *) (** Alias of {!Generic.close}. *)
(** {2 Provider Interface} *)
module Pi : sig
module type STREAM_SOCKET = sig
type tag
include Flow.Pi.SHUTDOWN
include Flow.Pi.SOURCE with type t := t
include Flow.Pi.SINK with type t := t
val close : t -> unit
end
val stream_socket :
(module STREAM_SOCKET with type t = 't and type tag = 'tag) ->
('t, 'tag stream_socket_ty) Resource.handler
module type DATAGRAM_SOCKET = sig
type tag
include Flow.Pi.SHUTDOWN
val send : t -> ?dst:Sockaddr.datagram -> Cstruct.t list -> unit
val recv : t -> Cstruct.t -> Sockaddr.datagram * int
val close : t -> unit
end
val datagram_socket :
(module DATAGRAM_SOCKET with type t = 't and type tag = 'tag) ->
('t, 'tag datagram_socket_ty) Resource.handler
module type LISTENING_SOCKET = sig
type t
type tag
val accept : t -> sw:Switch.t -> tag stream_socket_ty r * Sockaddr.stream
val close : t -> unit
end
val listening_socket :
(module LISTENING_SOCKET with type t = 't and type tag = 'tag) ->
('t, 'tag listening_socket_ty) Resource.handler
module type NETWORK = sig
type t
type tag
val listen :
t -> reuse_addr:bool -> reuse_port:bool -> backlog:int -> sw:Switch.t ->
Sockaddr.stream -> tag listening_socket_ty r
val connect : t -> sw:Switch.t -> Sockaddr.stream -> tag stream_socket_ty r
val datagram_socket :
t
-> reuse_addr:bool
-> reuse_port:bool
-> sw:Switch.t
-> [Sockaddr.datagram | `UdpV4 | `UdpV6]
-> tag datagram_socket_ty r
val getaddrinfo : t -> service:string -> string -> Sockaddr.t list
val getnameinfo : t -> Sockaddr.t -> (string * string)
end
val network :
(module NETWORK with type t = 't and type tag = 'tag) ->
('t, 'tag ty) Resource.handler
end

View File

@ -1,4 +1,4 @@
type 'a t = 'a Fs.dir * Fs.path type 'a t = (#Fs.dir as 'a) * Fs.path
let ( / ) (dir, p1) p2 = let ( / ) (dir, p1) p2 =
match p1, p2 with match p1, p2 with
@ -7,50 +7,39 @@ let ( / ) (dir, p1) p2 =
| ".", p2 -> (dir, p2) | ".", p2 -> (dir, p2)
| p1, p2 -> (dir, Filename.concat p1 p2) | p1, p2 -> (dir, Filename.concat p1 p2)
let pp f (Resource.T (t, ops), p) = let pp f ((t:#Fs.dir), p) =
let module X = (val (Resource.get ops Fs.Pi.Dir)) in if p = "" then Fmt.pf f "<%t>" t#pp
if p = "" then Fmt.pf f "<%a>" X.pp t else Fmt.pf f "<%t:%s>" t#pp (String.escaped p)
else Fmt.pf f "<%a:%s>" X.pp t (String.escaped p)
let open_in ~sw t = let open_in ~sw ((t:#Fs.dir), path) =
let (Resource.T (dir, ops), path) = t in try t#open_in ~sw path
let module X = (val (Resource.get ops Fs.Pi.Dir)) in
try X.open_in dir ~sw path
with Exn.Io _ as ex -> with Exn.Io _ as ex ->
let bt = Printexc.get_raw_backtrace () in let bt = Printexc.get_raw_backtrace () in
Exn.reraise_with_context ex bt "opening %a" pp t Exn.reraise_with_context ex bt "opening %a" pp (t, path)
let open_out ~sw ?(append=false) ~create t = let open_out ~sw ?(append=false) ~create ((t:#Fs.dir), path) =
let (Resource.T (dir, ops), path) = t in try t#open_out ~sw ~append ~create path
let module X = (val (Resource.get ops Fs.Pi.Dir)) in
try X.open_out dir ~sw ~append ~create path
with Exn.Io _ as ex -> with Exn.Io _ as ex ->
let bt = Printexc.get_raw_backtrace () in let bt = Printexc.get_raw_backtrace () in
Exn.reraise_with_context ex bt "opening %a" pp t Exn.reraise_with_context ex bt "opening %a" pp (t, path)
let open_dir ~sw t = let open_dir ~sw ((t:#Fs.dir), path) =
let (Resource.T (dir, ops), path) = t in try (t#open_dir ~sw path, "")
let module X = (val (Resource.get ops Fs.Pi.Dir)) in
try X.open_dir dir ~sw path, ""
with Exn.Io _ as ex -> with Exn.Io _ as ex ->
let bt = Printexc.get_raw_backtrace () in let bt = Printexc.get_raw_backtrace () in
Exn.reraise_with_context ex bt "opening directory %a" pp t Exn.reraise_with_context ex bt "opening directory %a" pp (t, path)
let mkdir ~perm t = let mkdir ~perm ((t:#Fs.dir), path) =
let (Resource.T (dir, ops), path) = t in try t#mkdir ~perm path
let module X = (val (Resource.get ops Fs.Pi.Dir)) in
try X.mkdir dir ~perm path
with Exn.Io _ as ex -> with Exn.Io _ as ex ->
let bt = Printexc.get_raw_backtrace () in let bt = Printexc.get_raw_backtrace () in
Exn.reraise_with_context ex bt "creating directory %a" pp t Exn.reraise_with_context ex bt "creating directory %a" pp (t, path)
let read_dir t = let read_dir ((t:#Fs.dir), path) =
let (Resource.T (dir, ops), path) = t in try List.sort String.compare (t#read_dir path)
let module X = (val (Resource.get ops Fs.Pi.Dir)) in
try List.sort String.compare (X.read_dir dir path)
with Exn.Io _ as ex -> with Exn.Io _ as ex ->
let bt = Printexc.get_raw_backtrace () in let bt = Printexc.get_raw_backtrace () in
Exn.reraise_with_context ex bt "reading directory %a" pp t Exn.reraise_with_context ex bt "reading directory %a" pp (t, path)
let with_open_in path fn = let with_open_in path fn =
Switch.run @@ fun sw -> fn (open_in ~sw path) Switch.run @@ fun sw -> fn (open_in ~sw path)
@ -88,27 +77,20 @@ let save ?append ~create path data =
with_open_out ?append ~create path @@ fun flow -> with_open_out ?append ~create path @@ fun flow ->
Flow.copy_string data flow Flow.copy_string data flow
let unlink t = let unlink ((t:#Fs.dir), path) =
let (Resource.T (dir, ops), path) = t in try t#unlink path
let module X = (val (Resource.get ops Fs.Pi.Dir)) in
try X.unlink dir path
with Exn.Io _ as ex -> with Exn.Io _ as ex ->
let bt = Printexc.get_raw_backtrace () in let bt = Printexc.get_raw_backtrace () in
Exn.reraise_with_context ex bt "removing file %a" pp t Exn.reraise_with_context ex bt "removing file %a" pp (t, path)
let rmdir t = let rmdir ((t:#Fs.dir), path) =
let (Resource.T (dir, ops), path) = t in try t#rmdir path
let module X = (val (Resource.get ops Fs.Pi.Dir)) in
try X.rmdir dir path
with Exn.Io _ as ex -> with Exn.Io _ as ex ->
let bt = Printexc.get_raw_backtrace () in let bt = Printexc.get_raw_backtrace () in
Exn.reraise_with_context ex bt "removing directory %a" pp t Exn.reraise_with_context ex bt "removing directory %a" pp (t, path)
let rename t1 t2 = let rename ((t1:#Fs.dir), old_path) (t2, new_path) =
let (dir2, new_path) = t2 in try t1#rename old_path (t2 :> Fs.dir) new_path
let (Resource.T (dir, ops), old_path) = t1 in
let module X = (val (Resource.get ops Fs.Pi.Dir)) in
try X.rename dir old_path (dir2 :> _ Fs.dir) new_path
with Exn.Io _ as ex -> with Exn.Io _ as ex ->
let bt = Printexc.get_raw_backtrace () in let bt = Printexc.get_raw_backtrace () in
Exn.reraise_with_context ex bt "renaming %a to %a" pp t1 pp t2 Exn.reraise_with_context ex bt "renaming %a to %a" pp (t1, old_path) pp (t2, new_path)

View File

@ -25,10 +25,9 @@
]} ]}
*) *)
open Std
open Fs open Fs
type 'a t = 'a Fs.dir * path type 'a t = (#Fs.dir as 'a) * path
(** An OS directory FD and a path relative to it, for use with e.g. [openat(2)]. *) (** An OS directory FD and a path relative to it, for use with e.g. [openat(2)]. *)
val ( / ) : 'a t -> string -> 'a t val ( / ) : 'a t -> string -> 'a t
@ -48,12 +47,12 @@ val load : _ t -> string
This is a convenience wrapper around {!with_open_in}. *) This is a convenience wrapper around {!with_open_in}. *)
val open_in : sw:Switch.t -> _ t -> File.ro_ty r val open_in : sw:Switch.t -> _ t -> <File.ro; Flow.close>
(** [open_in ~sw t] opens [t] for reading. (** [open_in ~sw t] opens [t] for reading.
Note: files are always opened in binary mode. *) Note: files are always opened in binary mode. *)
val with_open_in : _ t -> (File.ro_ty r -> 'a) -> 'a val with_open_in : _ t -> (<File.ro; Flow.close> -> 'a) -> 'a
(** [with_open_in] is like [open_in], but calls [fn flow] with the new flow and closes (** [with_open_in] is like [open_in], but calls [fn flow] with the new flow and closes
it automatically when [fn] returns (if it hasn't already been closed by then). *) it automatically when [fn] returns (if it hasn't already been closed by then). *)
@ -73,7 +72,7 @@ val open_out :
sw:Switch.t -> sw:Switch.t ->
?append:bool -> ?append:bool ->
create:create -> create:create ->
_ t -> File.rw_ty Resource.t _ t -> <File.rw; Flow.close>
(** [open_out ~sw t] opens [t] for reading and writing. (** [open_out ~sw t] opens [t] for reading and writing.
Note: files are always opened in binary mode. Note: files are always opened in binary mode.
@ -83,7 +82,7 @@ val open_out :
val with_open_out : val with_open_out :
?append:bool -> ?append:bool ->
create:create -> create:create ->
_ t -> (File.rw_ty r -> 'a) -> 'a _ t -> (<File.rw; Flow.close> -> 'a) -> 'a
(** [with_open_out] is like [open_out], but calls [fn flow] with the new flow and closes (** [with_open_out] is like [open_out], but calls [fn flow] with the new flow and closes
it automatically when [fn] returns (if it hasn't already been closed by then). *) it automatically when [fn] returns (if it hasn't already been closed by then). *)
@ -92,12 +91,12 @@ val with_open_out :
val mkdir : perm:File.Unix_perm.t -> _ t -> unit val mkdir : perm:File.Unix_perm.t -> _ t -> unit
(** [mkdir ~perm t] creates a new directory [t] with permissions [perm]. *) (** [mkdir ~perm t] creates a new directory [t] with permissions [perm]. *)
val open_dir : sw:Switch.t -> _ t -> [`Close | dir_ty] t val open_dir : sw:Switch.t -> _ t -> <dir; Flow.close> t
(** [open_dir ~sw t] opens [t]. (** [open_dir ~sw t] opens [t].
This can be passed to functions to grant access only to the subtree [t]. *) This can be passed to functions to grant access only to the subtree [t]. *)
val with_open_dir : _ t -> ([`Close | dir_ty] t -> 'a) -> 'a val with_open_dir : _ t -> (<dir; Flow.close> t -> 'a) -> 'a
(** [with_open_dir] is like [open_dir], but calls [fn dir] with the new directory and closes (** [with_open_dir] is like [open_dir], but calls [fn dir] with the new directory and closes
it automatically when [fn] returns (if it hasn't already been closed by then). *) it automatically when [fn] returns (if it hasn't already been closed by then). *)

View File

@ -1,5 +1,3 @@
open Std
type exit_status = [ type exit_status = [
| `Exited of int | `Exited of int
| `Signaled of int | `Signaled of int
@ -51,14 +49,14 @@ let signal proc = proc#signal
class virtual mgr = object class virtual mgr = object
method virtual pipe : method virtual pipe :
sw:Switch.t -> sw:Switch.t ->
[Flow.source_ty | Resource.close_ty] r * [Flow.sink_ty | Resource.close_ty] r <Flow.source; Flow.close> * <Flow.sink; Flow.close>
method virtual spawn : method virtual spawn :
sw:Switch.t -> sw:Switch.t ->
?cwd:Fs.dir_ty Path.t -> ?cwd:Fs.dir Path.t ->
?stdin:Flow.source_ty r -> ?stdin:Flow.source ->
?stdout:Flow.sink_ty r -> ?stdout:Flow.sink ->
?stderr:Flow.sink_ty r -> ?stderr:Flow.sink ->
?env:string array -> ?env:string array ->
?executable:string -> ?executable:string ->
string list -> string list ->
@ -79,12 +77,12 @@ let pp_args = Fmt.hbox (Fmt.list ~sep:Fmt.sp pp_arg)
let spawn ~sw (t:#mgr) ?cwd ?stdin ?stdout ?stderr ?env ?executable args = let spawn ~sw (t:#mgr) ?cwd ?stdin ?stdout ?stderr ?env ?executable args =
t#spawn ~sw t#spawn ~sw
?cwd:(cwd :> Fs.dir_ty Path.t option) ?cwd:(cwd :> Fs.dir Path.t option)
?env ?env
?executable args ?executable args
?stdin:(stdin :> Flow.source_ty r option) ?stdin:(stdin :> Flow.source option)
?stdout:(stdout :> Flow.sink_ty r option) ?stdout:(stdout :> Flow.sink option)
?stderr:(stderr :> Flow.sink_ty r option) ?stderr:(stderr :> Flow.sink option)
let run (t:#mgr) ?cwd ?stdin ?stdout ?stderr ?(is_success = Int.equal 0) ?env ?executable args = let run (t:#mgr) ?cwd ?stdin ?stdout ?stderr ?(is_success = Int.equal 0) ?env ?executable args =
Switch.run @@ fun sw -> Switch.run @@ fun sw ->

View File

@ -6,8 +6,6 @@
]} ]}
*) *)
open Std
(** {2 Status and error types} *) (** {2 Status and error types} *)
type exit_status = [ type exit_status = [
@ -71,14 +69,14 @@ val signal : #t -> int -> unit
class virtual mgr : object class virtual mgr : object
method virtual pipe : method virtual pipe :
sw:Switch.t -> sw:Switch.t ->
[Flow.source_ty | Resource.close_ty] r * [Flow.sink_ty | Resource.close_ty] r <Flow.source; Flow.close> * <Flow.sink; Flow.close>
method virtual spawn : method virtual spawn :
sw:Switch.t -> sw:Switch.t ->
?cwd:Fs.dir_ty Path.t -> ?cwd:Fs.dir Path.t ->
?stdin:Flow.source_ty r -> ?stdin:Flow.source ->
?stdout:Flow.sink_ty r -> ?stdout:Flow.sink ->
?stderr:Flow.sink_ty r -> ?stderr:Flow.sink ->
?env:string array -> ?env:string array ->
?executable:string -> ?executable:string ->
string list -> string list ->
@ -89,10 +87,10 @@ end
val spawn : val spawn :
sw:Switch.t -> sw:Switch.t ->
#mgr -> #mgr ->
?cwd:Fs.dir_ty Path.t -> ?cwd:#Fs.dir Path.t ->
?stdin:_ Flow.source -> ?stdin:#Flow.source ->
?stdout:_ Flow.sink -> ?stdout:#Flow.sink ->
?stderr:_ Flow.sink -> ?stderr:#Flow.sink ->
?env:string array -> ?env:string array ->
?executable:string -> ?executable:string ->
string list -> t string list -> t
@ -115,10 +113,10 @@ val spawn :
val run : val run :
#mgr -> #mgr ->
?cwd:_ Path.t -> ?cwd:#Fs.dir Path.t ->
?stdin:_ Flow.source -> ?stdin:#Flow.source ->
?stdout:_ Flow.sink -> ?stdout:#Flow.sink ->
?stderr:_ Flow.sink -> ?stderr:#Flow.sink ->
?is_success:(int -> bool) -> ?is_success:(int -> bool) ->
?env:string array -> ?env:string array ->
?executable:string -> ?executable:string ->
@ -134,9 +132,9 @@ val run :
val parse_out : val parse_out :
#mgr -> #mgr ->
'a Buf_read.parser -> 'a Buf_read.parser ->
?cwd:_ Path.t -> ?cwd:#Fs.dir Path.t ->
?stdin:_ Flow.source -> ?stdin:#Flow.source ->
?stderr:_ Flow.sink -> ?stderr:#Flow.sink ->
?is_success:(int -> bool) -> ?is_success:(int -> bool) ->
?env:string array -> ?env:string array ->
?executable:string -> ?executable:string ->
@ -154,7 +152,7 @@ val parse_out :
(** {2 Pipes} *) (** {2 Pipes} *)
val pipe : sw:Switch.t -> #mgr -> [Flow.source_ty | Resource.close_ty] r * [Flow.sink_ty | Resource.close_ty] r val pipe : sw:Switch.t -> #mgr -> <Flow.source; Flow.close> * <Flow.sink; Flow.close>
(** [pipe ~sw mgr] creates a pipe backed by the OS. (** [pipe ~sw mgr] creates a pipe backed by the OS.
The flows can be used by {!spawn} without the need for extra fibers to copy the data. The flows can be used by {!spawn} without the need for extra fibers to copy the data.

View File

@ -1,35 +0,0 @@
type ('t, 'impl, 'tags) pi = ..
type _ binding = H : ('t, 'impl, 'tags) pi * 'impl -> 't binding
type 't ops = 't binding array
type ('t, 'tags) handler = 't ops
type -'a t = T : ('t * 't ops) -> 'a t
let not_supported () = failwith "Operation not supported!"
let handler = Array.of_list
let bindings = Array.to_list
let get : 't ops -> ('t, 'impl, 'tags) pi -> 'impl = fun ops op ->
let rec aux i =
if i = Array.length ops then not_supported ();
let H (k, v) = ops.(i) in
if Obj.repr k == Obj.repr op then Obj.magic v
else aux (i + 1)
in
aux 0
let get_opt : 't ops -> ('t, 'impl, 'tags) pi -> 'impl option = fun ops op ->
let rec aux i =
if i = Array.length ops then None
else (
let H (k, v) = ops.(i) in
if Obj.repr k == Obj.repr op then Some (Obj.magic v)
else aux (i + 1)
)
in
aux 0
type close_ty = [`Close]
type (_, _, _) pi += Close : ('t, 't -> unit, [> close_ty]) pi
let close (T (t, ops)) = get ops Close t

View File

@ -1,114 +0,0 @@
(** Resources are typically operating-system provided resources such as open files
and network sockets. However, they can also be pure OCaml resources (such as mocks)
or wrappers (such as an encrypted flow that wraps an unencrypted OS flow).
A resource's type shows which interfaces it supports. For example, a
[[source | sink] t] is a resource that can be used as a source or a sink.
If you are familiar with object types, this is roughly equivalent to the
type [<source; sink>]. We avoid using object types here as some OCaml
programmers find them confusing. *)
(** {2 Types} *)
type ('t, -'tags) handler
(** A [('t, 'tags) handler] can be used to look up the implementation for a type ['t].
['tags] is a phantom type to record which interfaces are supported.
Internally, a handler is a set of {!type-binding}s. *)
type -'tags t = T : ('t * ('t, 'tags) handler) -> 'tags t (** *)
(** A resource is a pair of a value and a handler for it.
Normally there will be convenience functions provided for using resources
and you will not need to match on [T] yourself except when defining a new interface. *)
(** {2 Defining new interfaces}
These types and functions can be used to define new interfaces that others
can implement.
When defining a new interface, you will typically provide:
- The tags that indicate that the interface is supported (e.g. {!Flow.source_ty}).
- A convenience type to match all sub-types easily (e.g. {!Flow.source}).
- Functions allowing users to call the interface (e.g. {!Flow.single_read}).
- A module to let providers implement the interface (e.g. {!Flow.Pi}).
*)
type ('t, 'iface, 'tag) pi = ..
(** A provider interface describes an interface that a resource can implement.
- ['t] is the type of the resource itself.
- ['iface] is the API that can be requested.
- ['tag] is the tag (or tags) indicating that the interface is supported.
For example, the value {!Close} (of type [(fd, fd -> unit, [> `Close]) pi]) can be
used with a resource backed by an [fd], and which offers at least the
[`Close] tag, to request its close function.
Often, the API requested will be a module type, but it can be a single function
as in this example.
*)
type _ binding = H : ('t, 'impl, 'tags) pi * 'impl -> 't binding (** *)
(** A binding [H (pi, impl)] says to use [impl] to implement [pi].
For example: [H (Close, M.close)]. *)
val handler : 't binding list -> ('t, _) handler
(** [handler ops] is a handler that looks up interfaces using the assoc list [ops].
For example [shutdown (module Foo)] is a handler that handles the [Close] and [Shutdown]
interfaces for resources of type [Foo.t] by using the [Foo] module:
{[
let shutdown (type t) (module X : SHUTDOWN with type t = t) : (t, shutdown_ty) handler =
handler [
H (Close, X.close);
H (Shutdown, (module X));
]
]}
Be sure to give the return type explicitly, as this cannot be inferred.
*)
val bindings : ('t, _) handler -> 't binding list
(** [bindings (handler ops) = ops].
This is useful if you want to extend an interface
and you already have a handler for that interface. *)
val get : ('t, 'tags) handler -> ('t, 'impl, 'tags) pi -> 'impl
(** [get handler iface] uses [handler] to get the implementation of [iface].
For example:
{[
let write (Resource.T (t, ops)) bufs =
let module X = (val (Resource.get ops Sink)) in
X.write t bufs
]}
*)
val get_opt : ('t, _) handler -> ('t, 'impl, _) pi -> 'impl option
(** [get_opt] is like {!get}, but the handler need not have a compatible type.
Instead, this performs a check at runtime and returns [None] if the interface
is not supported. *)
(** {2 Closing}
Resources are usually attached to switches and closed automatically when the switch
finishes. However, it can be useful to close them sooner in some cases. *)
type close_ty = [`Close]
type (_, _, _) pi += Close : ('t, 't -> unit, [> close_ty]) pi
val close : [> close_ty] t -> unit
(** [close t] marks the resource as closed. It can no longer be used after this.
If [t] is already closed then this does nothing (it does not raise an exception).
Note: if an operation is currently in progress when this is called then it is not
necessarily cancelled, and any underlying OS resource (such as a file descriptor)
might not be closed immediately if other operations are using it. Closing a resource
only prevents new operations from starting. *)

View File

@ -1,5 +0,0 @@
module Promise = Eio__core.Promise
module Fiber = Eio__core.Fiber
module Switch = Eio__core.Switch
type 'a r = 'a Resource.t
let traceln = Debug.traceln

View File

@ -1,10 +0,0 @@
module Promise = Eio__core.Promise
module Fiber = Eio__core.Fiber
module Switch = Eio__core.Switch
type 'a r = 'a Resource.t
val traceln :
?__POS__:string * int * int * int ->
('a, Format.formatter, unit, unit) format4 -> 'a
(** Same as {!Eio.traceln}. *)

View File

@ -1,12 +1,11 @@
[@@@alert "-unstable"] [@@@alert "-unstable"]
open Eio.Std
module Fd = Fd module Fd = Fd
module Resource = Resource module Resource = Resource
module Private = Private module Private = Private
include Types include Types
type socket = Net.stream_socket
let await_readable = Private.await_readable let await_readable = Private.await_readable
let await_writable = Private.await_writable let await_writable = Private.await_writable
@ -30,21 +29,20 @@ module Ctf = Ctf_unix
module Process = Process module Process = Process
module Net = Net module Net = Net
module Pi = Pi
module Stdenv = struct module Stdenv = struct
type base = < type base = <
stdin : source_ty r; stdin : source;
stdout : sink_ty r; stdout : sink;
stderr : sink_ty r; stderr : sink;
net : [`Unix | `Generic] Eio.Net.ty r; net : Eio.Net.t;
domain_mgr : Eio.Domain_manager.t; domain_mgr : Eio.Domain_manager.t;
process_mgr : Process.mgr; process_mgr : Process.mgr;
clock : Eio.Time.clock; clock : Eio.Time.clock;
mono_clock : Eio.Time.Mono.t; mono_clock : Eio.Time.Mono.t;
fs : Eio.Fs.dir_ty Eio.Path.t; fs : Eio.Fs.dir Eio.Path.t;
cwd : Eio.Fs.dir_ty Eio.Path.t; cwd : Eio.Fs.dir Eio.Path.t;
secure_random : Eio.Flow.source_ty r; secure_random : Eio.Flow.source;
debug : Eio.Debug.t; debug : Eio.Debug.t;
backend_id: string; backend_id: string;
> >

View File

@ -16,15 +16,16 @@ module Fd = Fd
(** Eio resources backed by an OS file descriptor. *) (** Eio resources backed by an OS file descriptor. *)
module Resource : sig module Resource : sig
type 'a t = ([> `Unix_fd] as 'a) Eio.Resource.t type t = < fd : Fd.t >
(** Resources that have FDs are tagged with [`Unix_fd]. *) (** Resources that have FDs are sub-types of [t]. *)
type ('t, _, _) Eio.Resource.pi += T : ('t, 't -> Fd.t, [> `Unix_fd]) Eio.Resource.pi val fd : <t;..> -> Fd.t
val fd : _ t -> Fd.t
(** [fd t] returns the FD being wrapped by a resource. *) (** [fd t] returns the FD being wrapped by a resource. *)
val fd_opt : _ Eio.Resource.t -> Fd.t option type _ Eio.Generic.ty += FD : Fd.t Eio.Generic.ty
(** Resources that wrap FDs can handle this in their [probe] method to expose the FD. *)
val fd_opt : #Eio.Generic.t -> Fd.t option
(** [fd_opt t] returns the FD being wrapped by a generic resource, if any. (** [fd_opt t] returns the FD being wrapped by a generic resource, if any.
This just probes [t] using {!extension-FD}. *) This just probes [t] using {!extension-FD}. *)
@ -33,10 +34,9 @@ end
module Net = Net module Net = Net
(** Extended network API with support for file descriptors. *) (** Extended network API with support for file descriptors. *)
type source_ty = [`Unix_fd | Eio.Resource.close_ty | Eio.Flow.source_ty] type source = < Eio.Flow.source; Resource.t; Eio.Flow.close >
type sink_ty = [`Unix_fd | Eio.Resource.close_ty | Eio.Flow.sink_ty] type sink = < Eio.Flow.sink; Resource.t; Eio.Flow.close >
type 'a source = ([> source_ty] as 'a) r type socket = Net.stream_socket
type 'a sink = ([> sink_ty] as 'a) r
val await_readable : Unix.file_descr -> unit val await_readable : Unix.file_descr -> unit
(** [await_readable fd] blocks until [fd] is readable (or has an error). *) (** [await_readable fd] blocks until [fd] is readable (or has an error). *)
@ -54,7 +54,7 @@ val run_in_systhread : (unit -> 'a) -> 'a
(** [run_in_systhread fn] runs the function [fn] in a newly created system thread (a {! Thread.t}). (** [run_in_systhread fn] runs the function [fn] in a newly created system thread (a {! Thread.t}).
This allows blocking calls to be made non-blocking. *) This allows blocking calls to be made non-blocking. *)
val pipe : Switch.t -> source_ty r * sink_ty r val pipe : Switch.t -> source * sink
(** [pipe sw] returns a connected pair of flows [src] and [sink]. Data written to [sink] (** [pipe sw] returns a connected pair of flows [src] and [sink]. Data written to [sink]
can be read from [src]. can be read from [src].
Note that, like all FDs created by Eio, they are both marked as close-on-exec by default. *) Note that, like all FDs created by Eio, they are both marked as close-on-exec by default. *)
@ -65,17 +65,17 @@ module Process = Process
(** The set of resources provided to a process on a Unix-compatible system. *) (** The set of resources provided to a process on a Unix-compatible system. *)
module Stdenv : sig module Stdenv : sig
type base = < type base = <
stdin : source_ty r; stdin : source;
stdout : sink_ty r; stdout : sink;
stderr : sink_ty r; stderr : sink;
net : [`Unix | `Generic] Eio.Net.ty r; net : Eio.Net.t;
domain_mgr : Eio.Domain_manager.t; domain_mgr : Eio.Domain_manager.t;
process_mgr : Process.mgr; process_mgr : Process.mgr;
clock : Eio.Time.clock; clock : Eio.Time.clock;
mono_clock : Eio.Time.Mono.t; mono_clock : Eio.Time.Mono.t;
fs : Eio.Fs.dir_ty Eio.Path.t; fs : Eio.Fs.dir Eio.Path.t;
cwd : Eio.Fs.dir_ty Eio.Path.t; cwd : Eio.Fs.dir Eio.Path.t;
secure_random : Eio.Flow.source_ty r; secure_random : Eio.Flow.source;
debug : Eio.Debug.t; debug : Eio.Debug.t;
backend_id : string; backend_id : string;
> >
@ -90,7 +90,7 @@ module Private : sig
| Await_readable : Unix.file_descr -> unit Effect.t (** See {!await_readable} *) | Await_readable : Unix.file_descr -> unit Effect.t (** See {!await_readable} *)
| Await_writable : Unix.file_descr -> unit Effect.t (** See {!await_writable} *) | Await_writable : Unix.file_descr -> unit Effect.t (** See {!await_writable} *)
| Get_monotonic_clock : Eio.Time.Mono.t Effect.t | Get_monotonic_clock : Eio.Time.Mono.t Effect.t
| Pipe : Eio.Switch.t -> (source_ty r * sink_ty r) Effect.t (** See {!pipe} *) | Pipe : Eio.Switch.t -> (source * sink) Effect.t (** See {!pipe} *)
module Rcfd = Rcfd module Rcfd = Rcfd
@ -98,5 +98,3 @@ module Private : sig
end end
module Ctf = Ctf_unix module Ctf = Ctf_unix
module Pi = Pi

View File

@ -46,11 +46,6 @@ let of_unix ~sw ?blocking ?seekable ~close_unix fd =
t.release_hook <- Switch.on_release_cancellable sw (fun () -> close t); t.release_hook <- Switch.on_release_cancellable sw (fun () -> close t);
t t
let of_unix_list ~sw fds =
match Switch.get_error sw with
| Some e -> List.iter Unix.close fds; raise e
| None -> List.map (of_unix ~sw ~close_unix:true) fds
external eio_is_blocking : Unix.file_descr -> bool = "eio_unix_is_blocking" external eio_is_blocking : Unix.file_descr -> bool = "eio_unix_is_blocking"
let is_blocking t = let is_blocking t =

View File

@ -14,10 +14,6 @@ val of_unix : sw:Switch.t -> ?blocking:bool -> ?seekable:bool -> close_unix:bool
@param seekable The value to be returned by {!is_seekable}. Defaults to probing if needed. @param seekable The value to be returned by {!is_seekable}. Defaults to probing if needed.
@param close_unix Whether {!close} also closes [fd] (this should normally be [true]). *) @param close_unix Whether {!close} also closes [fd] (this should normally be [true]). *)
val of_unix_list : sw:Switch.t -> Unix.file_descr list -> t list
(** [of_unix_list ~sw fds] is like [List.map (of_unix ~sw ~close_unix:true) fds],
except that if [sw] is off then it closes all the FDs. *)
(** {2 Using FDs} *) (** {2 Using FDs} *)
val use : t -> (Unix.file_descr -> 'a) -> if_closed:(unit -> 'a) -> 'a val use : t -> (Unix.file_descr -> 'a) -> if_closed:(unit -> 'a) -> 'a

View File

@ -1,9 +1,3 @@
/* Note: fork actions MUST NOT allocate (either on the OCaml heap or with C malloc).
* This is because e.g. we might have forked while another thread in the parent had a lock.
* In the child, we inherit a copy of the locked mutex, but no corresponding thread to
* release it.
*/
#include <stdlib.h> #include <stdlib.h>
#include <unistd.h> #include <unistd.h>
#include <fcntl.h> #include <fcntl.h>
@ -12,9 +6,6 @@
#include <caml/mlvalues.h> #include <caml/mlvalues.h>
#include <caml/unixsupport.h> #include <caml/unixsupport.h>
#include <caml/memory.h>
#include <caml/custom.h>
#include <caml/fail.h>
#include "fork_action.h" #include "fork_action.h"
@ -51,61 +42,24 @@ void eio_unix_fork_error(int fd, char *fn, char *buf) {
try_write_all(fd, buf); try_write_all(fd, buf);
} }
#define String_array_val(v) *((char ***)Data_custom_val(v)) static char **make_string_array(int errors, value v_array) {
int n = Wosize_val(v_array);
static void finalize_string_array(value v) { char **c = calloc(sizeof(char *), (n + 1));
free(String_array_val(v)); if (!c) {
String_array_val(v) = NULL; eio_unix_fork_error(errors, "make_string_array", "out of memory");
_exit(1);
} }
static struct custom_operations string_array_ops = {
"string.array",
finalize_string_array,
custom_compare_default,
custom_hash_default,
custom_serialize_default,
custom_deserialize_default,
custom_compare_ext_default,
custom_fixed_length_default
};
CAMLprim value eio_unix_make_string_array(value v_len) {
CAMLparam0();
CAMLlocal1(v_str_array);
int n = Int_val(v_len);
uintnat total;
if (caml_umul_overflow(sizeof(char *), n + 1, &total))
caml_raise_out_of_memory();
v_str_array = caml_alloc_custom_mem(&string_array_ops, sizeof(char ***), total);
char **c = calloc(sizeof(char *), n + 1);
String_array_val(v_str_array) = c;
if (!c)
caml_raise_out_of_memory();
CAMLreturn(v_str_array);
}
static void fill_string_array(char **c, value v_ocaml_array) {
int n = Wosize_val(v_ocaml_array);
for (int i = 0; i < n; i++) { for (int i = 0; i < n; i++) {
c[i] = (char *) String_val(Field(v_ocaml_array, i)); c[i] = (char *) String_val(Field(v_array, i));
} }
c[n] = NULL; c[n] = NULL;
return c;
} }
static void action_execve(int errors, value v_config) { static void action_execve(int errors, value v_config) {
value v_exe = Field(v_config, 1); value v_exe = Field(v_config, 1);
char **argv = String_array_val(Field(v_config, 2)); char **argv = make_string_array(errors, Field(v_config, 2));
char **envp = String_array_val(Field(v_config, 4)); char **envp = make_string_array(errors, Field(v_config, 3));
fill_string_array(argv, Field(v_config, 3));
fill_string_array(envp, Field(v_config, 5));
execve(String_val(v_exe), argv, envp); execve(String_val(v_exe), argv, envp);
eio_unix_fork_error(errors, "execve", strerror(errno)); eio_unix_fork_error(errors, "execve", strerror(errno));
_exit(1); _exit(1);

View File

@ -17,14 +17,9 @@ let rec with_actions actions fn =
with_actions xs @@ fun c_actions -> with_actions xs @@ fun c_actions ->
fn (c_action :: c_actions) fn (c_action :: c_actions)
type c_array
external make_string_array : int -> c_array = "eio_unix_make_string_array"
external action_execve : unit -> fork_fn = "eio_unix_fork_execve" external action_execve : unit -> fork_fn = "eio_unix_fork_execve"
let action_execve = action_execve () let action_execve = action_execve ()
let execve path ~argv ~env = let execve path ~argv ~env = { run = fun k -> k (Obj.repr (action_execve, path, argv, env)) }
let argv_c_array = make_string_array (Array.length argv) in
let env_c_array = make_string_array (Array.length env) in
{ run = fun k -> k (Obj.repr (action_execve, path, argv_c_array, argv, env_c_array, env)) }
external action_chdir : unit -> fork_fn = "eio_unix_fork_chdir" external action_chdir : unit -> fork_fn = "eio_unix_fork_chdir"
let action_chdir = action_chdir () let action_chdir = action_chdir ()

View File

@ -1,8 +1,7 @@
#include <caml/mlvalues.h> #include <caml/mlvalues.h>
#include <caml/alloc.h> #include <caml/alloc.h>
/* A function that runs in the forked child process. /* A function that runs in the forked child process. It must not run any OCaml code or invoke the GC.
* It must not run any OCaml code, invoke the GC, or even call [malloc].
* If the action fails then it writes an error message to the FD [errors] and calls [_exit]. * If the action fails then it writes an error message to the FD [errors] and calls [_exit].
* v_args is the c_action tuple (where field 0 is the function itself). * v_args is the c_action tuple (where field 0 is the function itself).
*/ */

View File

@ -1,12 +1,5 @@
open Eio.Std open Eio.Std
type stream_socket_ty = [`Generic | `Unix] Eio.Net.stream_socket_ty
type datagram_socket_ty = [`Generic | `Unix] Eio.Net.datagram_socket_ty
type listening_socket_ty = [`Generic | `Unix] Eio.Net.listening_socket_ty
type 'a stream_socket = ([> stream_socket_ty] as 'a) r
type 'a datagram_socket = ([> datagram_socket_ty] as 'a) r
type 'a listening_socket = ([> listening_socket_ty] as 'a) r
module Ipaddr = struct module Ipaddr = struct
let to_unix : _ Eio.Net.Ipaddr.t -> Unix.inet_addr = Obj.magic let to_unix : _ Eio.Net.Ipaddr.t -> Unix.inet_addr = Obj.magic
let of_unix : Unix.inet_addr -> _ Eio.Net.Ipaddr.t = Obj.magic let of_unix : Unix.inet_addr -> _ Eio.Net.Ipaddr.t = Obj.magic
@ -30,19 +23,13 @@ let sockaddr_of_unix_datagram = function
let host = Ipaddr.of_unix host in let host = Ipaddr.of_unix host in
`Udp (host, port) `Udp (host, port)
let send_msg (Eio.Resource.T (t, ops)) ?(fds=[]) bufs = class virtual stream_socket = object (_ : <Resource.t; ..>)
let module X = (val (Eio.Resource.get ops Pi.Stream_socket)) in inherit Eio.Net.stream_socket
let rec aux ~fds bufs = end
let sent = X.send_msg t ~fds bufs in
match Cstruct.shiftv bufs sent with
| [] -> ()
| bufs -> aux bufs ~fds:[]
in
aux ~fds bufs
let recv_msg_with_fds (Eio.Resource.T (t, ops)) ~sw ~max_fds bufs = class virtual datagram_socket = object (_ : <Resource.t; ..>)
let module X = (val (Eio.Resource.get ops Pi.Stream_socket)) in inherit Eio.Net.datagram_socket
X.recv_msg_with_fds t ~sw ~max_fds bufs end
let getnameinfo (sockaddr : Eio.Net.Sockaddr.t) = let getnameinfo (sockaddr : Eio.Net.Sockaddr.t) =
let options = let options =
@ -55,33 +42,28 @@ let getnameinfo (sockaddr : Eio.Net.Sockaddr.t) =
let Unix.{ni_hostname; ni_service} = Unix.getnameinfo sockaddr options in let Unix.{ni_hostname; ni_service} = Unix.getnameinfo sockaddr options in
(ni_hostname, ni_service)) (ni_hostname, ni_service))
type t = [`Generic | `Unix] Eio.Net.ty r class virtual t = object
inherit Eio.Net.t
method getnameinfo = getnameinfo
end
[@@@alert "-unstable"] [@@@alert "-unstable"]
type _ Effect.t += type _ Effect.t +=
| Import_socket_stream : Switch.t * bool * Unix.file_descr -> [`Unix_fd | stream_socket_ty] r Effect.t | Import_socket_stream : Switch.t * bool * Unix.file_descr -> stream_socket Effect.t
| Import_socket_datagram : Switch.t * bool * Unix.file_descr -> [`Unix_fd | datagram_socket_ty] r Effect.t | Import_socket_datagram : Switch.t * bool * Unix.file_descr -> datagram_socket Effect.t
| Socketpair_stream : Switch.t * Unix.socket_domain * int -> | Socketpair_stream : Switch.t * Unix.socket_domain * int ->
([`Unix_fd | stream_socket_ty] r * [`Unix_fd | stream_socket_ty] r) Effect.t (stream_socket * stream_socket) Effect.t
| Socketpair_datagram : Switch.t * Unix.socket_domain * int -> | Socketpair_datagram : Switch.t * Unix.socket_domain * int ->
([`Unix_fd | datagram_socket_ty] r * [`Unix_fd | datagram_socket_ty] r) Effect.t (datagram_socket * datagram_socket) Effect.t
let open_stream s = (s : _ stream_socket :> [< `Unix_fd | stream_socket_ty] r) let import_socket_stream ~sw ~close_unix fd = Effect.perform (Import_socket_stream (sw, close_unix, fd))
let open_datagram s = (s : _ datagram_socket :> [< `Unix_fd | datagram_socket_ty] r)
let import_socket_stream ~sw ~close_unix fd = let import_socket_datagram ~sw ~close_unix fd = Effect.perform (Import_socket_datagram (sw, close_unix, fd))
open_stream @@ Effect.perform (Import_socket_stream (sw, close_unix, fd))
let import_socket_datagram ~sw ~close_unix fd =
open_datagram @@ Effect.perform (Import_socket_datagram (sw, close_unix, fd))
let socketpair_stream ~sw ?(domain=Unix.PF_UNIX) ?(protocol=0) () = let socketpair_stream ~sw ?(domain=Unix.PF_UNIX) ?(protocol=0) () =
let a, b = Effect.perform (Socketpair_stream (sw, domain, protocol)) in Effect.perform (Socketpair_stream (sw, domain, protocol))
(open_stream a, open_stream b)
let socketpair_datagram ~sw ?(domain=Unix.PF_UNIX) ?(protocol=0) () = let socketpair_datagram ~sw ?(domain=Unix.PF_UNIX) ?(protocol=0) () =
Effect.perform (Socketpair_datagram (sw, domain, protocol)) Effect.perform (Socketpair_datagram (sw, domain, protocol))
let fd socket =
Option.get (Resource.fd_opt socket)

View File

@ -4,35 +4,19 @@ open Eio.Std
These extend the types in {!Eio.Net} with support for file descriptors. *) These extend the types in {!Eio.Net} with support for file descriptors. *)
type stream_socket_ty = [`Generic | `Unix] Eio.Net.stream_socket_ty class virtual stream_socket : object (<Resource.t; ..>)
type datagram_socket_ty = [`Generic | `Unix] Eio.Net.datagram_socket_ty inherit Eio.Net.stream_socket
type listening_socket_ty = [`Generic | `Unix] Eio.Net.listening_socket_ty end
type 'a stream_socket = ([> stream_socket_ty] as 'a) r
type 'a datagram_socket = ([> datagram_socket_ty] as 'a) r
type 'a listening_socket = ([> listening_socket_ty] as 'a) r
type t = [`Generic | `Unix] Eio.Net.ty r class virtual datagram_socket : object (<Resource.t; ..>)
inherit Eio.Net.datagram_socket
end
(** {2 Passing file descriptors} *) class virtual t : object
inherit Eio.Net.t
val send_msg : method getnameinfo : Eio.Net.Sockaddr.t -> (string * string)
[> `Platform of [>`Unix] | `Socket | `Stream] r -> end
?fds:Fd.t list ->
Cstruct.t list -> unit
(** Like {!Eio.Flow.write}, but allows passing file descriptors (for Unix-domain sockets). *)
val recv_msg_with_fds :
[> `Platform of [>`Unix] | `Socket | `Stream] r ->
sw:Switch.t ->
max_fds:int ->
Cstruct.t list ->
int * Fd.t list
(** Like {!Eio.Flow.single_read}, but also allows receiving file descriptors (for Unix-domain sockets).
@param max_fds The maximum number of file descriptors to accept (additional ones will be closed). *)
val fd : [> `Platform of [> `Unix] | `Socket] r -> Fd.t
(** [fd socket] is the underlying FD of [socket]. *)
(** {2 Unix address conversions} (** {2 Unix address conversions}
@ -55,7 +39,7 @@ end
(** {2 Creating or importing sockets} *) (** {2 Creating or importing sockets} *)
val import_socket_stream : sw:Switch.t -> close_unix:bool -> Unix.file_descr -> [`Unix_fd | stream_socket_ty] r val import_socket_stream : sw:Switch.t -> close_unix:bool -> Unix.file_descr -> stream_socket
(** [import_socket_stream ~sw ~close_unix:true fd] is an Eio flow that uses [fd]. (** [import_socket_stream ~sw ~close_unix:true fd] is an Eio flow that uses [fd].
It can be cast to e.g. {!source} for a one-way flow. It can be cast to e.g. {!source} for a one-way flow.
@ -63,7 +47,7 @@ val import_socket_stream : sw:Switch.t -> close_unix:bool -> Unix.file_descr ->
The [close_unix] and [sw] arguments are passed to {!Fd.of_unix}. *) The [close_unix] and [sw] arguments are passed to {!Fd.of_unix}. *)
val import_socket_datagram : sw:Switch.t -> close_unix:bool -> Unix.file_descr -> [`Unix_fd | datagram_socket_ty] r val import_socket_datagram : sw:Switch.t -> close_unix:bool -> Unix.file_descr -> datagram_socket
(** [import_socket_datagram ~sw ~close_unix:true fd] is an Eio datagram socket that uses [fd]. (** [import_socket_datagram ~sw ~close_unix:true fd] is an Eio datagram socket that uses [fd].
The socket object will be closed when [sw] finishes. The socket object will be closed when [sw] finishes.
@ -75,7 +59,7 @@ val socketpair_stream :
?domain:Unix.socket_domain -> ?domain:Unix.socket_domain ->
?protocol:int -> ?protocol:int ->
unit -> unit ->
[`Unix_fd | stream_socket_ty] r * [`Unix_fd | stream_socket_ty] r stream_socket * stream_socket
(** [socketpair_stream ~sw ()] returns a connected pair of flows, such that writes to one can be read by the other. (** [socketpair_stream ~sw ()] returns a connected pair of flows, such that writes to one can be read by the other.
This creates OS-level resources using [socketpair(2)]. This creates OS-level resources using [socketpair(2)].
@ -86,7 +70,7 @@ val socketpair_datagram :
?domain:Unix.socket_domain -> ?domain:Unix.socket_domain ->
?protocol:int -> ?protocol:int ->
unit -> unit ->
[`Unix_fd | datagram_socket_ty] r * [`Unix_fd | datagram_socket_ty] r datagram_socket * datagram_socket
(** [socketpair_datagram ~sw ()] returns a connected pair of flows, such that writes to one can be read by the other. (** [socketpair_datagram ~sw ()] returns a connected pair of flows, such that writes to one can be read by the other.
This creates OS-level resources using [socketpair(2)]. This creates OS-level resources using [socketpair(2)].
@ -99,11 +83,11 @@ val getnameinfo : Eio.Net.Sockaddr.t -> (string * string)
type _ Effect.t += type _ Effect.t +=
| Import_socket_stream : | Import_socket_stream :
Switch.t * bool * Unix.file_descr -> [`Unix_fd | stream_socket_ty] r Effect.t (** See {!import_socket_stream} *) Switch.t * bool * Unix.file_descr -> stream_socket Effect.t (** See {!import_socket_stream} *)
| Import_socket_datagram : | Import_socket_datagram :
Switch.t * bool * Unix.file_descr -> [`Unix_fd | datagram_socket_ty] r Effect.t (** See {!import_socket_datagram} *) Switch.t * bool * Unix.file_descr -> datagram_socket Effect.t (** See {!import_socket_datagram} *)
| Socketpair_stream : Eio.Switch.t * Unix.socket_domain * int -> | Socketpair_stream : Eio.Switch.t * Unix.socket_domain * int ->
([`Unix_fd | stream_socket_ty] r * [`Unix_fd | stream_socket_ty] r) Effect.t (** See {!socketpair_stream} *) (stream_socket * stream_socket) Effect.t (** See {!socketpair_stream} *)
| Socketpair_datagram : Eio.Switch.t * Unix.socket_domain * int -> | Socketpair_datagram : Eio.Switch.t * Unix.socket_domain * int ->
([`Unix_fd | datagram_socket_ty] r * [`Unix_fd | datagram_socket_ty] r) Effect.t (** See {!socketpair_datagram} *) (datagram_socket * datagram_socket) Effect.t (** See {!socketpair_datagram} *)
[@@alert "-unstable"] [@@alert "-unstable"]

View File

@ -1,51 +0,0 @@
open Eio.Std
module type STREAM_SOCKET = sig
include Eio.Net.Pi.STREAM_SOCKET
val send_msg : t -> fds:Fd.t list -> Cstruct.t list -> int
val recv_msg_with_fds : t -> sw:Switch.t -> max_fds:int -> Cstruct.t list -> int * Fd.t list
val fd : t -> Fd.t
end
type (_, _, _) Eio.Resource.pi +=
| Stream_socket : ('t, (module STREAM_SOCKET with type t = 't), [> `Platform of [> `Unix] | `Socket | `Stream]) Eio.Resource.pi
module type FLOW = sig
include Eio.File.Pi.WRITE
include STREAM_SOCKET with type t := t
end
let flow_handler (type t tag) (module X : FLOW with type t = t and type tag = tag) : (t, _) Eio.Resource.handler =
Eio.Resource.handler @@
Eio.Resource.bindings (Eio.Net.Pi.stream_socket (module X)) @
Eio.Resource.bindings (Eio.File.Pi.rw (module X)) @ [
H (Resource.T, X.fd);
H (Stream_socket, (module X));
]
module type DATAGRAM_SOCKET = sig
include Eio.Net.Pi.DATAGRAM_SOCKET
val fd : t -> Fd.t
end
let datagram_handler (type t tag) (module X : DATAGRAM_SOCKET with type t = t and type tag = tag) : (t, _) Eio.Resource.handler =
Eio.Resource.handler @@
Eio.Resource.bindings (Eio.Net.Pi.datagram_socket (module X)) @ [
H (Resource.T, X.fd);
]
module type LISTENING_SOCKET = sig
include Eio.Net.Pi.LISTENING_SOCKET
val fd : t -> Fd.t
end
let listening_socket_handler (type t tag) (module X : LISTENING_SOCKET with type t = t and type tag = tag)
: (t, _) Eio.Resource.handler =
Eio.Resource.handler @@
Eio.Resource.bindings (Eio.Net.Pi.listening_socket (module X)) @ [
H (Resource.T, X.fd);
]

View File

@ -1,42 +0,0 @@
open Eio.Std
module type STREAM_SOCKET = sig
include Eio.Net.Pi.STREAM_SOCKET
val send_msg : t -> fds:Fd.t list -> Cstruct.t list -> int
val recv_msg_with_fds : t -> sw:Switch.t -> max_fds:int -> Cstruct.t list -> int * Fd.t list
val fd : t -> Fd.t
end
type (_, _, _) Eio.Resource.pi +=
| Stream_socket : ('t, (module STREAM_SOCKET with type t = 't), [> `Platform of [> `Unix] | `Socket | `Stream]) Eio.Resource.pi
module type FLOW = sig
include Eio.File.Pi.WRITE
include STREAM_SOCKET with type t := t
end
val flow_handler :
(module FLOW with type t = 't and type tag = 'tag) ->
('t, [`Unix_fd | 'tag Eio.Net.stream_socket_ty | Eio.File.rw_ty]) Eio.Resource.handler
module type DATAGRAM_SOCKET = sig
include Eio.Net.Pi.DATAGRAM_SOCKET
val fd : t -> Fd.t
end
val datagram_handler :
(module DATAGRAM_SOCKET with type t = 't and type tag = 'tag) ->
('t, [`Unix_fd | 'tag Eio.Net.datagram_socket_ty]) Eio.Resource.handler
module type LISTENING_SOCKET = sig
include Eio.Net.Pi.LISTENING_SOCKET
val fd : t -> Fd.t
end
val listening_socket_handler :
(module LISTENING_SOCKET with type t = 't and type tag = 'tag) ->
('t, [`Unix_fd | 'tag Eio.Net.listening_socket_ty]) Eio.Resource.handler

View File

@ -7,7 +7,7 @@ type _ Effect.t +=
| Await_readable : Unix.file_descr -> unit Effect.t | Await_readable : Unix.file_descr -> unit Effect.t
| Await_writable : Unix.file_descr -> unit Effect.t | Await_writable : Unix.file_descr -> unit Effect.t
| Get_monotonic_clock : Eio.Time.Mono.t Effect.t | Get_monotonic_clock : Eio.Time.Mono.t Effect.t
| Pipe : Switch.t -> (source_ty r * sink_ty r) Effect.t | Pipe : Switch.t -> (source * sink) Effect.t
let await_readable fd = Effect.perform (Await_readable fd) let await_readable fd = Effect.perform (Await_readable fd)
let await_writable fd = Effect.perform (Await_writable fd) let await_writable fd = Effect.perform (Await_writable fd)

View File

@ -72,13 +72,11 @@ let get_env = function
class virtual mgr = object (self) class virtual mgr = object (self)
inherit Eio.Process.mgr inherit Eio.Process.mgr
method pipe ~sw = method pipe ~sw = (Private.pipe sw :> <Eio.Flow.source; Eio.Flow.close> * <Eio.Flow.sink; Eio.Flow.close>)
(Private.pipe sw :> ([Eio.Resource.close_ty | Eio.Flow.source_ty] r *
[Eio.Resource.close_ty | Eio.Flow.sink_ty] r))
method virtual spawn_unix : method virtual spawn_unix :
sw:Switch.t -> sw:Switch.t ->
?cwd:Eio.Fs.dir_ty Eio.Path.t -> ?cwd:Eio.Fs.dir Eio.Path.t ->
env:string array -> env:string array ->
fds:(int * Fd.t * Fork_action.blocking) list -> fds:(int * Fd.t * Fork_action.blocking) list ->
executable:string -> executable:string ->

View File

@ -7,11 +7,11 @@ class virtual mgr : object
method pipe : method pipe :
sw:Switch.t -> sw:Switch.t ->
[Eio.Flow.source_ty | Eio.Resource.close_ty] r * [Eio.Flow.sink_ty | Eio.Resource.close_ty] r <Eio.Flow.source; Eio.Flow.close> * <Eio.Flow.sink; Eio.Flow.close>
method virtual spawn_unix : method virtual spawn_unix :
sw:Switch.t -> sw:Switch.t ->
?cwd:Eio.Fs.dir_ty Eio.Path.t -> ?cwd:Eio.Fs.dir Eio.Path.t ->
env:string array -> env:string array ->
fds:(int * Fd.t * Fork_action.blocking) list -> fds:(int * Fd.t * Fork_action.blocking) list ->
executable:string -> executable:string ->
@ -20,10 +20,10 @@ class virtual mgr : object
method spawn : method spawn :
sw:Switch.t -> sw:Switch.t ->
?cwd:Eio.Fs.dir_ty Eio.Path.t -> ?cwd:Eio.Fs.dir Eio.Path.t ->
?stdin:Eio.Flow.source_ty r -> ?stdin:Eio.Flow.source ->
?stdout:Eio.Flow.sink_ty r -> ?stdout:Eio.Flow.sink ->
?stderr:Eio.Flow.sink_ty r -> ?stderr:Eio.Flow.sink ->
?env:string array -> ?env:string array ->
?executable:string -> ?executable:string ->
string list -> string list ->
@ -34,7 +34,7 @@ end
val spawn_unix : val spawn_unix :
sw:Switch.t -> sw:Switch.t ->
#mgr -> #mgr ->
?cwd:Eio.Fs.dir_ty Eio.Path.t -> ?cwd:Eio.Fs.dir Eio.Path.t ->
fds:(int * Fd.t * Fork_action.blocking) list -> fds:(int * Fd.t * Fork_action.blocking) list ->
?env:string array -> ?env:string array ->
?executable:string -> ?executable:string ->

View File

@ -1,9 +1,6 @@
type 'a t = ([> `Unix_fd] as 'a) Eio.Resource.t type t = < fd : Fd.t >
type ('t, _, _) Eio.Resource.pi += T : ('t, 't -> Fd.t, [> `Unix_fd]) Eio.Resource.pi type _ Eio.Generic.ty += FD : Fd.t Eio.Generic.ty
let fd (Eio.Resource.T (t, ops)) = Eio.Resource.get ops T t
let fd_opt (Eio.Resource.T (t, ops)) = let fd t = t#fd
match Eio.Resource.get_opt ops T with let fd_opt t = Eio.Generic.probe t FD
| Some f -> Some (f t)
| None -> None

View File

@ -1,4 +1,2 @@
type source_ty = [`Unix_fd | Eio.Resource.close_ty | Eio.Flow.source_ty] type source = < Eio.Flow.source; Resource.t; Eio.Flow.close >
type sink_ty = [`Unix_fd | Eio.Resource.close_ty | Eio.Flow.sink_ty] type sink = < Eio.Flow.sink; Resource.t; Eio.Flow.close >
type 'a source = ([> source_ty] as 'a) Eio.Resource.t
type 'a sink = ([> sink_ty] as 'a) Eio.Resource.t

View File

@ -29,14 +29,11 @@ module Lf_queue = Eio_utils.Lf_queue
module Low_level = Low_level module Low_level = Low_level
(* When renaming, we get a plain [Eio.Fs.dir]. We need extra access to check type _ Eio.Generic.ty += Dir_fd : Low_level.dir_fd Eio.Generic.ty
that the new location is within its sandbox. *) let get_dir_fd_opt t = Eio.Generic.probe t Dir_fd
type ('t, _, _) Eio.Resource.pi += Dir_fd : ('t, 't -> Low_level.dir_fd, [> `Dir_fd]) Eio.Resource.pi
let get_dir_fd_opt (Eio.Resource.T (t, ops)) = type source = Eio_unix.source
match Eio.Resource.get_opt ops Dir_fd with type sink = Eio_unix.sink
| Some f -> Some (f t)
| None -> None
(* When copying between a source with an FD and a sink with an FD, we can share the chunk (* When copying between a source with an FD and a sink with an FD, we can share the chunk
and avoid copying. *) and avoid copying. *)
@ -86,13 +83,13 @@ let copy_with_rsb rsb dst =
(* Copy by allocating a chunk from the pre-shared buffer and asking (* Copy by allocating a chunk from the pre-shared buffer and asking
the source to write into it. This used when the other methods the source to write into it. This used when the other methods
aren't available. *) aren't available. *)
let fallback_copy (type src) (module Src : Eio.Flow.Pi.SOURCE with type t = src) src dst = let fallback_copy src dst =
let fallback () = let fallback () =
(* No chunks available. Use regular memory instead. *) (* No chunks available. Use regular memory instead. *)
let buf = Cstruct.create 4096 in let buf = Cstruct.create 4096 in
try try
while true do while true do
let got = Src.single_read src buf in let got = Eio.Flow.single_read src buf in
Low_level.writev dst [Cstruct.sub buf 0 got] Low_level.writev dst [Cstruct.sub buf 0 got]
done done
with End_of_file -> () with End_of_file -> ()
@ -101,134 +98,99 @@ let fallback_copy (type src) (module Src : Eio.Flow.Pi.SOURCE with type t = src)
let chunk_cs = Uring.Region.to_cstruct chunk in let chunk_cs = Uring.Region.to_cstruct chunk in
try try
while true do while true do
let got = Src.single_read src chunk_cs in let got = Eio.Flow.single_read src chunk_cs in
Low_level.write dst chunk got Low_level.write dst chunk got
done done
with End_of_file -> () with End_of_file -> ()
module Datagram_socket = struct let datagram_socket sock = object
type tag = [`Generic | `Unix] inherit Eio.Net.datagram_socket
type t = Eio_unix.Fd.t method fd = sock
let fd t = t method close = Fd.close sock
let close = Eio_unix.Fd.close method send ?dst buf =
let send t ?dst buf =
let dst = Option.map Eio_unix.Net.sockaddr_to_unix dst in let dst = Option.map Eio_unix.Net.sockaddr_to_unix dst in
let sent = Low_level.send_msg t ?dst buf in let sent = Low_level.send_msg sock ?dst buf in
assert (sent = Cstruct.lenv buf) assert (sent = Cstruct.lenv buf)
let recv t buf = method recv buf =
let addr, recv = Low_level.recv_msg t [buf] in let addr, recv = Low_level.recv_msg sock [buf] in
Eio_unix.Net.sockaddr_of_unix_datagram (Uring.Sockaddr.get addr), recv Eio_unix.Net.sockaddr_of_unix_datagram (Uring.Sockaddr.get addr), recv
let shutdown t cmd =
Low_level.shutdown t @@ match cmd with
| `Receive -> Unix.SHUTDOWN_RECEIVE
| `Send -> Unix.SHUTDOWN_SEND
| `All -> Unix.SHUTDOWN_ALL
end end
let datagram_handler = Eio_unix.Pi.datagram_handler (module Datagram_socket)
let datagram_socket fd =
Eio.Resource.T (fd, datagram_handler)
module Flow = struct
type tag = [`Generic | `Unix]
type t = Eio_unix.Fd.t
let fd t = t
let close = Eio_unix.Fd.close
let is_tty t = Fd.use_exn "isatty" t Unix.isatty
let stat = Low_level.fstat
let single_read t buf =
if is_tty t then (
(* Work-around for https://github.com/axboe/liburing/issues/354
(should be fixed in Linux 5.14) *)
Low_level.await_readable t
);
Low_level.readv t [buf]
let pread t ~file_offset bufs =
Low_level.readv ~file_offset t bufs
let pwrite t ~file_offset bufs =
Low_level.writev_single ~file_offset t bufs
let read_methods = []
let write t bufs = Low_level.writev t bufs
let copy t ~src =
match Eio_unix.Resource.fd_opt src with
| Some src -> fast_copy_try_splice src t
| None ->
let Eio.Resource.T (src, ops) = src in
let module Src = (val (Eio.Resource.get ops Eio.Flow.Pi.Source)) in
let rec aux = function
| Eio.Flow.Read_source_buffer rsb :: _ -> copy_with_rsb (rsb src) t
| _ :: xs -> aux xs
| [] -> fallback_copy (module Src) src t
in
aux Src.read_methods
let shutdown t cmd =
Low_level.shutdown t @@ match cmd with
| `Receive -> Unix.SHUTDOWN_RECEIVE
| `Send -> Unix.SHUTDOWN_SEND
| `All -> Unix.SHUTDOWN_ALL
let send_msg t ~fds data =
Low_level.send_msg t ~fds data
let recv_msg_with_fds t ~sw ~max_fds data =
let _addr, n, fds = Low_level.recv_msg_with_fds t ~sw ~max_fds data in
n, fds
end
let flow_handler = Eio_unix.Pi.flow_handler (module Flow)
let flow fd = let flow fd =
let r = Eio.Resource.T (fd, flow_handler) in let is_tty = Fd.use_exn "isatty" fd Unix.isatty in
(r : [`Unix_fd | Eio_unix.Net.stream_socket_ty | Eio.File.rw_ty] r :> object (_ : <source; sink; ..>)
[< `Unix_fd | Eio_unix.Net.stream_socket_ty | Eio.File.rw_ty] r) method fd = fd
method close = Fd.close fd
let source fd = (flow fd :> _ Eio_unix.source) method stat = Low_level.fstat fd
let sink fd = (flow fd :> _ Eio_unix.sink)
module Listening_socket = struct method probe : type a. a Eio.Generic.ty -> a option = function
type t = Fd.t | Eio_unix.Resource.FD -> Some fd
| _ -> None
type tag = [`Generic | `Unix] method read_into buf =
if is_tty then (
(* Work-around for https://github.com/axboe/liburing/issues/354
(should be fixed in Linux 5.14) *)
Low_level.await_readable fd
);
Low_level.readv fd [buf]
let fd t = t method pread ~file_offset bufs =
Low_level.readv ~file_offset fd bufs
let close = Fd.close method pwrite ~file_offset bufs =
Low_level.writev_single ~file_offset fd bufs
let accept t ~sw = method read_methods = []
method write bufs = Low_level.writev fd bufs
method copy src =
match Eio_unix.Resource.fd_opt src with
| Some src -> fast_copy_try_splice src fd
| None ->
let rec aux = function
| Eio.Flow.Read_source_buffer rsb :: _ -> copy_with_rsb rsb fd
| _ :: xs -> aux xs
| [] -> fallback_copy src fd
in
aux (Eio.Flow.read_methods src)
method shutdown cmd =
Low_level.shutdown fd @@ match cmd with
| `Receive -> Unix.SHUTDOWN_RECEIVE
| `Send -> Unix.SHUTDOWN_SEND
| `All -> Unix.SHUTDOWN_ALL
end
let source fd = (flow fd :> source)
let sink fd = (flow fd :> sink)
let listening_socket fd = object
inherit Eio.Net.listening_socket
method close = Fd.close fd
method accept ~sw =
Switch.check sw; Switch.check sw;
let client, client_addr = Low_level.accept ~sw t in let client, client_addr = Low_level.accept ~sw fd in
let client_addr = match client_addr with let client_addr = match client_addr with
| Unix.ADDR_UNIX path -> `Unix path | Unix.ADDR_UNIX path -> `Unix path
| Unix.ADDR_INET (host, port) -> `Tcp (Eio_unix.Net.Ipaddr.of_unix host, port) | Unix.ADDR_INET (host, port) -> `Tcp (Eio_unix.Net.Ipaddr.of_unix host, port)
in in
let flow = (flow client :> _ Eio.Net.stream_socket) in let flow = (flow client :> Eio.Net.stream_socket) in
flow, client_addr flow, client_addr
method! probe : type a. a Eio.Generic.ty -> a option = function
| Eio_unix.Resource.FD -> Some fd
| _ -> None
end end
let listening_handler = Eio_unix.Pi.listening_socket_handler (module Listening_socket)
let listening_socket fd =
Eio.Resource.T (fd, listening_handler)
let socket_domain_of = function let socket_domain_of = function
| `Unix _ -> Unix.PF_UNIX | `Unix _ -> Unix.PF_UNIX
| `UdpV4 -> Unix.PF_INET | `UdpV4 -> Unix.PF_INET
@ -244,13 +206,12 @@ let connect ~sw connect_addr =
let sock_unix = Unix.socket ~cloexec:true (socket_domain_of connect_addr) Unix.SOCK_STREAM 0 in let sock_unix = Unix.socket ~cloexec:true (socket_domain_of connect_addr) Unix.SOCK_STREAM 0 in
let sock = Fd.of_unix ~sw ~seekable:false ~close_unix:true sock_unix in let sock = Fd.of_unix ~sw ~seekable:false ~close_unix:true sock_unix in
Low_level.connect sock addr; Low_level.connect sock addr;
(flow sock :> _ Eio_unix.Net.stream_socket) (flow sock :> Eio.Net.stream_socket)
module Impl = struct let net = object
type t = unit inherit Eio_unix.Net.t
type tag = [`Unix | `Generic]
let listen () ~reuse_addr ~reuse_port ~backlog ~sw listen_addr = method listen ~reuse_addr ~reuse_port ~backlog ~sw listen_addr =
if reuse_addr then ( if reuse_addr then (
match listen_addr with match listen_addr with
| `Tcp _ -> () | `Tcp _ -> ()
@ -277,11 +238,11 @@ module Impl = struct
Unix.setsockopt sock_unix Unix.SO_REUSEPORT true; Unix.setsockopt sock_unix Unix.SO_REUSEPORT true;
Unix.bind sock_unix addr; Unix.bind sock_unix addr;
Unix.listen sock_unix backlog; Unix.listen sock_unix backlog;
(listening_socket sock :> _ Eio.Net.listening_socket_ty r) listening_socket sock
let connect () ~sw addr = (connect ~sw addr :> [`Generic | `Unix] Eio.Net.stream_socket_ty r) method connect = connect
let datagram_socket () ~reuse_addr ~reuse_port ~sw saddr = method datagram_socket ~reuse_addr ~reuse_port ~sw saddr =
if reuse_addr then ( if reuse_addr then (
match saddr with match saddr with
| `Udp _ | `UdpV4 | `UdpV6 -> () | `Udp _ | `UdpV4 | `UdpV6 -> ()
@ -304,16 +265,11 @@ module Impl = struct
Unix.bind sock_unix addr Unix.bind sock_unix addr
| `UdpV4 | `UdpV6 -> () | `UdpV4 | `UdpV6 -> ()
end; end;
(datagram_socket sock :> [`Generic | `Unix] Eio.Net.datagram_socket_ty r) (datagram_socket sock :> Eio.Net.datagram_socket)
let getaddrinfo () = Low_level.getaddrinfo method getaddrinfo = Low_level.getaddrinfo
let getnameinfo () = Eio_unix.Net.getnameinfo
end end
let net =
let handler = Eio.Net.Pi.network (module Impl) in
Eio.Resource.T ((), handler)
type stdenv = Eio_unix.Stdenv.base type stdenv = Eio_unix.Stdenv.base
module Process = Low_level.Process module Process = Low_level.Process
@ -421,31 +377,22 @@ let clock = object
Eio.Time.Mono.sleep mono_clock d Eio.Time.Mono.sleep mono_clock d
end end
module rec Dir : sig class dir ~label (fd : Low_level.dir_fd) = object
include Eio.Fs.Pi.DIR inherit Eio.Fs.dir
val v : label:string -> Low_level.dir_fd -> t method! probe : type a. a Eio.Generic.ty -> a option = function
| Dir_fd -> Some fd
| _ -> None
val close : t -> unit method open_in ~sw path =
let fd = Low_level.openat ~sw fd path
val fd : t -> Low_level.dir_fd
end = struct
type t = {
fd : Low_level.dir_fd;
label : string;
}
let v ~label fd = { fd; label }
let open_in t ~sw path =
let fd = Low_level.openat ~sw t.fd path
~access:`R ~access:`R
~flags:Uring.Open_flags.cloexec ~flags:Uring.Open_flags.cloexec
~perm:0 ~perm:0
in in
(flow fd :> Eio.File.ro_ty r) (flow fd :> <Eio.File.ro; Eio.Flow.close>)
let open_out t ~sw ~append ~create path = method open_out ~sw ~append ~create path =
let perm, flags = let perm, flags =
match create with match create with
| `Never -> 0, Uring.Open_flags.empty | `Never -> 0, Uring.Open_flags.empty
@ -454,75 +401,56 @@ end = struct
| `Exclusive perm -> perm, Uring.Open_flags.(creat + excl) | `Exclusive perm -> perm, Uring.Open_flags.(creat + excl)
in in
let flags = if append then Uring.Open_flags.(flags + append) else flags in let flags = if append then Uring.Open_flags.(flags + append) else flags in
let fd = Low_level.openat ~sw t.fd path let fd = Low_level.openat ~sw fd path
~access:`RW ~access:`RW
~flags:Uring.Open_flags.(cloexec + flags) ~flags:Uring.Open_flags.(cloexec + flags)
~perm ~perm
in in
(flow fd :> Eio.File.rw_ty r) (flow fd :> <Eio.File.rw; Eio.Flow.close>)
let open_dir t ~sw path = method open_dir ~sw path =
let fd = Low_level.openat ~sw ~seekable:false t.fd (if path = "" then "." else path) let fd = Low_level.openat ~sw ~seekable:false fd (if path = "" then "." else path)
~access:`R ~access:`R
~flags:Uring.Open_flags.(cloexec + path + directory) ~flags:Uring.Open_flags.(cloexec + path + directory)
~perm:0 ~perm:0
in in
let label = Filename.basename path in let label = Filename.basename path in
let d = v ~label (Low_level.FD fd) in (new dir ~label (Low_level.FD fd) :> <Eio.Fs.dir; Eio.Flow.close>)
Eio.Resource.T (d, Dir_handler.v)
let mkdir t ~perm path = Low_level.mkdir_beneath ~perm t.fd path method mkdir ~perm path = Low_level.mkdir_beneath ~perm fd path
let read_dir t path = method read_dir path =
Switch.run @@ fun sw -> Switch.run @@ fun sw ->
let fd = Low_level.open_dir ~sw t.fd (if path = "" then "." else path) in let fd = Low_level.open_dir ~sw fd (if path = "" then "." else path) in
Low_level.read_dir fd Low_level.read_dir fd
let close t = method close =
match t.fd with match fd with
| FD x -> Fd.close x | FD x -> Fd.close x
| Cwd | Fs -> failwith "Can't close non-FD directory!" | Cwd | Fs -> failwith "Can't close non-FD directory!"
let unlink t path = Low_level.unlink ~rmdir:false t.fd path method unlink path = Low_level.unlink ~rmdir:false fd path
let rmdir t path = Low_level.unlink ~rmdir:true t.fd path method rmdir path = Low_level.unlink ~rmdir:true fd path
let rename t old_path t2 new_path = method rename old_path t2 new_path =
match get_dir_fd_opt t2 with match get_dir_fd_opt t2 with
| Some fd2 -> Low_level.rename t.fd old_path fd2 new_path | Some fd2 -> Low_level.rename fd old_path fd2 new_path
| None -> raise (Unix.Unix_error (Unix.EXDEV, "rename-dst", new_path)) | None -> raise (Unix.Unix_error (Unix.EXDEV, "rename-dst", new_path))
let pp f t = Fmt.string f (String.escaped t.label) method pp f = Fmt.string f (String.escaped label)
let fd t = t.fd
end
and Dir_handler : sig
val v : (Dir.t, [`Dir | `Close]) Eio.Resource.handler
end = struct
let v = Eio.Resource.handler [
H (Eio.Fs.Pi.Dir, (module Dir));
H (Eio.Resource.Close, Dir.close);
H (Dir_fd, Dir.fd);
]
end end
let dir ~label fd = Eio.Resource.T (Dir.v ~label fd, Dir_handler.v) let secure_random = object
inherit Eio.Flow.source
module Secure_random = struct method read_into buf = Low_level.getrandom buf; Cstruct.length buf
type t = unit
let single_read () buf = Low_level.getrandom buf; Cstruct.length buf
let read_methods = []
end end
let secure_random =
let ops = Eio.Flow.Pi.source (module Secure_random) in
Eio.Resource.T ((), ops)
let stdenv ~run_event_loop = let stdenv ~run_event_loop =
let stdin = source Eio_unix.Fd.stdin in let stdin = source Eio_unix.Fd.stdin in
let stdout = sink Eio_unix.Fd.stdout in let stdout = sink Eio_unix.Fd.stdout in
let stderr = sink Eio_unix.Fd.stderr in let stderr = sink Eio_unix.Fd.stderr in
let fs = (dir ~label:"fs" Fs, "") in let fs = (new dir ~label:"fs" Fs, "") in
let cwd = (dir ~label:"cwd" Cwd, "") in let cwd = (new dir ~label:"cwd" Cwd, "") in
object (_ : stdenv) object (_ : stdenv)
method stdin = stdin method stdin = stdin
method stdout = stdout method stdout = stdout
@ -532,8 +460,8 @@ let stdenv ~run_event_loop =
method domain_mgr = domain_mgr ~run_event_loop method domain_mgr = domain_mgr ~run_event_loop
method clock = clock method clock = clock
method mono_clock = mono_clock method mono_clock = mono_clock
method fs = (fs :> Eio.Fs.dir_ty Eio.Path.t) method fs = (fs :> Eio.Fs.dir Eio.Path.t)
method cwd = (cwd :> Eio.Fs.dir_ty Eio.Path.t) method cwd = (cwd :> Eio.Fs.dir Eio.Path.t)
method secure_random = secure_random method secure_random = secure_random
method debug = Eio.Private.Debug.v method debug = Eio.Private.Debug.v
method backend_id = "linux" method backend_id = "linux"
@ -548,7 +476,7 @@ let run_event_loop (type a) ?fallback config (main : _ -> a) arg : a =
| Eio_unix.Private.Get_monotonic_clock -> Some (fun k -> continue k mono_clock) | Eio_unix.Private.Get_monotonic_clock -> Some (fun k -> continue k mono_clock)
| Eio_unix.Net.Import_socket_stream (sw, close_unix, fd) -> Some (fun k -> | Eio_unix.Net.Import_socket_stream (sw, close_unix, fd) -> Some (fun k ->
let fd = Fd.of_unix ~sw ~seekable:false ~close_unix fd in let fd = Fd.of_unix ~sw ~seekable:false ~close_unix fd in
continue k (flow fd :> _ Eio_unix.Net.stream_socket) continue k (flow fd :> Eio_unix.Net.stream_socket)
) )
| Eio_unix.Net.Import_socket_datagram (sw, close_unix, fd) -> Some (fun k -> | Eio_unix.Net.Import_socket_datagram (sw, close_unix, fd) -> Some (fun k ->
let fd = Fd.of_unix ~sw ~seekable:false ~close_unix fd in let fd = Fd.of_unix ~sw ~seekable:false ~close_unix fd in
@ -559,7 +487,7 @@ let run_event_loop (type a) ?fallback config (main : _ -> a) arg : a =
let a, b = Unix.socketpair ~cloexec:true domain Unix.SOCK_STREAM protocol in let a, b = Unix.socketpair ~cloexec:true domain Unix.SOCK_STREAM protocol in
let a = Fd.of_unix ~sw ~seekable:false ~close_unix:true a |> flow in let a = Fd.of_unix ~sw ~seekable:false ~close_unix:true a |> flow in
let b = Fd.of_unix ~sw ~seekable:false ~close_unix:true b |> flow in let b = Fd.of_unix ~sw ~seekable:false ~close_unix:true b |> flow in
((a :> _ Eio_unix.Net.stream_socket), (b :> _ Eio_unix.Net.stream_socket)) ((a :> Eio_unix.Net.stream_socket), (b :> Eio_unix.Net.stream_socket))
with with
| r -> continue k r | r -> continue k r
| exception Unix.Unix_error (code, name, arg) -> | exception Unix.Unix_error (code, name, arg) ->
@ -570,7 +498,7 @@ let run_event_loop (type a) ?fallback config (main : _ -> a) arg : a =
let a, b = Unix.socketpair ~cloexec:true domain Unix.SOCK_DGRAM protocol in let a, b = Unix.socketpair ~cloexec:true domain Unix.SOCK_DGRAM protocol in
let a = Fd.of_unix ~sw ~seekable:false ~close_unix:true a |> datagram_socket in let a = Fd.of_unix ~sw ~seekable:false ~close_unix:true a |> datagram_socket in
let b = Fd.of_unix ~sw ~seekable:false ~close_unix:true b |> datagram_socket in let b = Fd.of_unix ~sw ~seekable:false ~close_unix:true b |> datagram_socket in
((a :> _ Eio_unix.Net.datagram_socket), (b :> _ Eio_unix.Net.datagram_socket)) ((a :> Eio_unix.Net.datagram_socket), (b :> Eio_unix.Net.datagram_socket))
with with
| r -> continue k r | r -> continue k r
| exception Unix.Unix_error (code, name, arg) -> | exception Unix.Unix_error (code, name, arg) ->
@ -579,8 +507,8 @@ let run_event_loop (type a) ?fallback config (main : _ -> a) arg : a =
| Eio_unix.Private.Pipe sw -> Some (fun k -> | Eio_unix.Private.Pipe sw -> Some (fun k ->
match match
let r, w = Low_level.pipe ~sw in let r, w = Low_level.pipe ~sw in
let r = (flow r :> _ Eio_unix.source) in let r = (flow r :> Eio_unix.source) in
let w = (flow w :> _ Eio_unix.sink) in let w = (flow w :> Eio_unix.sink) in
(r, w) (r, w)
with with
| r -> continue k r | r -> continue k r

View File

@ -25,10 +25,15 @@ open Eio.Std
type fd := Eio_unix.Fd.t type fd := Eio_unix.Fd.t
(** {1 Main Loop} *) (** {1 Eio API} *)
type source = Eio_unix.source
type sink = Eio_unix.sink
type stdenv = Eio_unix.Stdenv.base type stdenv = Eio_unix.Stdenv.base
(** {1 Main Loop} *)
val run : val run :
?queue_depth:int -> ?queue_depth:int ->
?n_blocks:int -> ?n_blocks:int ->

View File

@ -101,7 +101,7 @@ CAMLprim value caml_eio_getrandom(value v_ba, value v_off, value v_len) {
ssize_t off = (ssize_t)Long_val(v_off); ssize_t off = (ssize_t)Long_val(v_off);
ssize_t len = (ssize_t)Long_val(v_len); ssize_t len = (ssize_t)Long_val(v_len);
do { do {
void *buf = (char *)Caml_ba_data_val(v_ba) + off; void *buf = Caml_ba_data_val(v_ba) + off;
caml_enter_blocking_section(); caml_enter_blocking_section();
#if __GLIBC__ > 2 || __GLIBC_MINOR__ > 24 #if __GLIBC__ > 2 || __GLIBC_MINOR__ > 24
ret = getrandom(buf, len, 0); ret = getrandom(buf, len, 0);

View File

@ -263,7 +263,10 @@ let recv_msg_with_fds ~sw ~max_fds fd buf =
if res < 0 then ( if res < 0 then (
raise @@ Err.wrap (Uring.error_of_errno res) "recv_msg" "" raise @@ Err.wrap (Uring.error_of_errno res) "recv_msg" ""
); );
let fds = Uring.Msghdr.get_fds msghdr |> Fd.of_unix_list ~sw in let fds =
Uring.Msghdr.get_fds msghdr
|> List.map (fun fd -> Fd.of_unix ~sw ~close_unix:true fd)
in
addr, res, fds addr, res, fds
let with_chunk ~fallback fn = let with_chunk ~fallback fn =

View File

@ -0,0 +1,42 @@
# Setting up the environment
```ocaml
# #require "eio_linux";;
```
```ocaml
open Eio.Std
let ( / ) = Eio.Path.( / )
```
Sending a file descriptor over a Unix domain socket:
```ocaml
# Eio_linux.run @@ fun env ->
Switch.run @@ fun sw ->
let fd = Eio.Path.open_out ~sw (env#cwd / "tmp.txt") ~create:(`Exclusive 0o600) in
Eio.Flow.copy_string "Test data" fd;
let r, w = Unix.(socketpair PF_UNIX SOCK_STREAM 0) in
let r = Eio_unix.Fd.of_unix ~sw ~seekable:false ~close_unix:true r in
let w = Eio_unix.Fd.of_unix ~sw ~seekable:false ~close_unix:true w in
Fiber.both
(fun () ->
let sent = Eio_linux.Low_level.send_msg w [Cstruct.of_string "x"] ~fds:[Eio_unix.Resource.fd_opt fd |> Option.get] in
assert (sent = 1)
)
(fun () ->
let buf = Cstruct.of_string "?" in
let addr, got, fds = Eio_linux.Low_level.recv_msg_with_fds ~sw r ~max_fds:10 [buf] in
traceln "Got: %S plus %d FD" (Cstruct.to_string buf) (List.length fds);
match fds with
| [fd] ->
Eio_unix.Fd.use_exn "read" fd @@ fun fd ->
ignore (Unix.lseek fd 0 Unix.SEEK_SET : int);
traceln "Read: %S" (really_input_string (Unix.in_channel_of_descr fd) 9);
| _ -> assert false
);;
+Got: "x" plus 1 FD
+Read: "Test data"
- : unit = ()
```

View File

@ -20,7 +20,7 @@ open Eio.Std
module Fd = Eio_unix.Fd module Fd = Eio_unix.Fd
let socketpair k ~sw ~domain ~ty ~protocol wrap_a wrap_b = let socketpair k ~sw ~domain ~ty ~protocol ~wrap =
let open Effect.Deep in let open Effect.Deep in
match match
let unix_a, unix_b = Unix.socketpair ~cloexec:true domain ty protocol in let unix_a, unix_b = Unix.socketpair ~cloexec:true domain ty protocol in
@ -28,7 +28,7 @@ let socketpair k ~sw ~domain ~ty ~protocol wrap_a wrap_b =
let b = Fd.of_unix ~sw ~blocking:false ~close_unix:true unix_b in let b = Fd.of_unix ~sw ~blocking:false ~close_unix:true unix_b in
Unix.set_nonblock unix_a; Unix.set_nonblock unix_a;
Unix.set_nonblock unix_b; Unix.set_nonblock unix_b;
(wrap_a a, wrap_b b) (wrap a, wrap b)
with with
| r -> continue k r | r -> continue k r
| exception Unix.Unix_error (code, name, arg) -> | exception Unix.Unix_error (code, name, arg) ->
@ -45,7 +45,7 @@ let run_event_loop fn x =
| Eio_unix.Net.Import_socket_stream (sw, close_unix, unix_fd) -> Some (fun k -> | Eio_unix.Net.Import_socket_stream (sw, close_unix, unix_fd) -> Some (fun k ->
let fd = Fd.of_unix ~sw ~blocking:false ~close_unix unix_fd in let fd = Fd.of_unix ~sw ~blocking:false ~close_unix unix_fd in
Unix.set_nonblock unix_fd; Unix.set_nonblock unix_fd;
continue k (Flow.of_fd fd :> _ Eio_unix.Net.stream_socket) continue k (Flow.of_fd fd :> Eio_unix.Net.stream_socket)
) )
| Eio_unix.Net.Import_socket_datagram (sw, close_unix, unix_fd) -> Some (fun k -> | Eio_unix.Net.Import_socket_datagram (sw, close_unix, unix_fd) -> Some (fun k ->
let fd = Fd.of_unix ~sw ~blocking:false ~close_unix unix_fd in let fd = Fd.of_unix ~sw ~blocking:false ~close_unix unix_fd in
@ -53,18 +53,18 @@ let run_event_loop fn x =
continue k (Net.datagram_socket fd) continue k (Net.datagram_socket fd)
) )
| Eio_unix.Net.Socketpair_stream (sw, domain, protocol) -> Some (fun k -> | Eio_unix.Net.Socketpair_stream (sw, domain, protocol) -> Some (fun k ->
let wrap fd = (Flow.of_fd fd :> _ Eio_unix.Net.stream_socket) in socketpair k ~sw ~domain ~protocol ~ty:Unix.SOCK_STREAM
socketpair k ~sw ~domain ~protocol ~ty:Unix.SOCK_STREAM wrap wrap ~wrap:(fun fd -> (Flow.of_fd fd :> Eio_unix.Net.stream_socket))
) )
| Eio_unix.Net.Socketpair_datagram (sw, domain, protocol) -> Some (fun k -> | Eio_unix.Net.Socketpair_datagram (sw, domain, protocol) -> Some (fun k ->
let wrap fd = Net.datagram_socket fd in socketpair k ~sw ~domain ~protocol ~ty:Unix.SOCK_DGRAM
socketpair k ~sw ~domain ~protocol ~ty:Unix.SOCK_DGRAM wrap wrap ~wrap:(fun fd -> Net.datagram_socket fd)
) )
| Eio_unix.Private.Pipe sw -> Some (fun k -> | Eio_unix.Private.Pipe sw -> Some (fun k ->
match match
let r, w = Low_level.pipe ~sw in let r, w = Low_level.pipe ~sw in
let source = Flow.of_fd r in let source = (Flow.of_fd r :> Eio_unix.source) in
let sink = Flow.of_fd w in let sink = (Flow.of_fd w :> Eio_unix.sink) in
(source, sink) (source, sink)
with with
| r -> continue k r | r -> continue k r

View File

@ -22,9 +22,9 @@ let run main =
(* SIGPIPE makes no sense in a modern application. *) (* SIGPIPE makes no sense in a modern application. *)
Sys.(set_signal sigpipe Signal_ignore); Sys.(set_signal sigpipe Signal_ignore);
Eio_unix.Process.install_sigchld_handler (); Eio_unix.Process.install_sigchld_handler ();
let stdin = (Flow.of_fd Eio_unix.Fd.stdin :> _ Eio_unix.source) in let stdin = (Flow.of_fd Eio_unix.Fd.stdin :> Eio_unix.source) in
let stdout = (Flow.of_fd Eio_unix.Fd.stdout :> _ Eio_unix.sink) in let stdout = (Flow.of_fd Eio_unix.Fd.stdout :> Eio_unix.sink) in
let stderr = (Flow.of_fd Eio_unix.Fd.stderr :> _ Eio_unix.sink) in let stderr = (Flow.of_fd Eio_unix.Fd.stderr :> Eio_unix.sink) in
Domain_mgr.run_event_loop main @@ object (_ : stdenv) Domain_mgr.run_event_loop main @@ object (_ : stdenv)
method stdin = stdin method stdin = stdin
method stdout = stdout method stdout = stdout
@ -35,8 +35,8 @@ let run main =
method net = Net.v method net = Net.v
method process_mgr = Process.v method process_mgr = Process.v
method domain_mgr = Domain_mgr.v method domain_mgr = Domain_mgr.v
method cwd = ((Fs.cwd, "") :> Eio.Fs.dir_ty Eio.Path.t) method cwd = ((Fs.cwd, "") :> Eio.Fs.dir Eio.Path.t)
method fs = ((Fs.fs, "") :> Eio.Fs.dir_ty Eio.Path.t) method fs = ((Fs.fs, "") :> Eio.Fs.dir Eio.Path.t)
method secure_random = Flow.secure_random method secure_random = Flow.secure_random
method backend_id = "posix" method backend_id = "posix"
end end

View File

@ -212,51 +212,23 @@ CAMLprim value caml_eio_posix_spawn(value v_errors, value v_actions) {
CAMLreturn(Val_long(child_pid)); CAMLreturn(Val_long(child_pid));
} }
/* Copy [n_fds] from [v_fds] to [msg]. */ CAMLprim value caml_eio_posix_send_msg(value v_fd, value v_dst_opt, value v_bufs) {
static void fill_fds(struct msghdr *msg, int n_fds, value v_fds) { CAMLparam2(v_dst_opt, v_bufs);
if (n_fds > 0) {
int i;
struct cmsghdr *cm;
cm = CMSG_FIRSTHDR(msg);
cm->cmsg_level = SOL_SOCKET;
cm->cmsg_type = SCM_RIGHTS;
cm->cmsg_len = CMSG_LEN(n_fds * sizeof(int));
for (i = 0; i < n_fds; i++) {
int fd = -1;
if (Is_block(v_fds)) {
fd = Int_val(Field(v_fds, 0));
v_fds = Field(v_fds, 1);
}
((int *)CMSG_DATA(cm))[i] = fd;
}
}
}
CAMLprim value caml_eio_posix_send_msg(value v_fd, value v_n_fds, value v_fds, value v_dst_opt, value v_bufs) {
CAMLparam3(v_fds, v_dst_opt, v_bufs);
int n_bufs = Wosize_val(v_bufs); int n_bufs = Wosize_val(v_bufs);
int n_fds = Int_val(v_n_fds);
struct iovec iov[n_bufs]; struct iovec iov[n_bufs];
union sock_addr_union dst_addr; union sock_addr_union dst_addr;
int controllen = n_fds > 0 ? CMSG_SPACE(sizeof(int) * n_fds) : 0;
char cmsg[controllen];
struct msghdr msg = { struct msghdr msg = {
.msg_iov = iov, .msg_iov = iov,
.msg_iovlen = n_bufs, .msg_iovlen = n_bufs,
.msg_control = n_fds > 0 ? cmsg : NULL,
.msg_controllen = controllen,
}; };
ssize_t r; ssize_t r;
memset(cmsg, 0, controllen);
if (Is_some(v_dst_opt)) { if (Is_some(v_dst_opt)) {
caml_unix_get_sockaddr(Some_val(v_dst_opt), &dst_addr, &msg.msg_namelen); caml_unix_get_sockaddr(Some_val(v_dst_opt), &dst_addr, &msg.msg_namelen);
msg.msg_name = &dst_addr; msg.msg_name = &dst_addr;
} }
fill_iov(iov, v_bufs); fill_iov(iov, v_bufs);
fill_fds(&msg, n_fds, v_fds);
caml_enter_blocking_section(); caml_enter_blocking_section();
r = sendmsg(Int_val(v_fd), &msg, 0); r = sendmsg(Int_val(v_fd), &msg, 0);
@ -266,49 +238,20 @@ CAMLprim value caml_eio_posix_send_msg(value v_fd, value v_n_fds, value v_fds, v
CAMLreturn(Val_long(r)); CAMLreturn(Val_long(r));
} }
static value get_msghdr_fds(struct msghdr *msg) { CAMLprim value caml_eio_posix_recv_msg(value v_fd, value v_bufs) {
CAMLparam0();
CAMLlocal2(v_list, v_cons);
struct cmsghdr *cm;
v_list = Val_int(0);
for (cm = CMSG_FIRSTHDR(msg); cm; cm = CMSG_NXTHDR(msg, cm)) {
if (cm->cmsg_level == SOL_SOCKET && cm->cmsg_type == SCM_RIGHTS) {
int *fds = (int *) CMSG_DATA(cm);
int n_fds = (cm->cmsg_len - CMSG_LEN(0)) / sizeof(int);
int i;
for (i = n_fds - 1; i >= 0; i--) {
value fd = Val_int(fds[i]);
v_cons = caml_alloc_tuple(2);
Store_field(v_cons, 0, fd);
Store_field(v_cons, 1, v_list);
v_list = v_cons;
}
}
}
CAMLreturn(v_list);
}
CAMLprim value caml_eio_posix_recv_msg(value v_fd, value v_max_fds, value v_bufs) {
CAMLparam1(v_bufs); CAMLparam1(v_bufs);
CAMLlocal2(v_result, v_addr); CAMLlocal2(v_result, v_addr);
int max_fds = Int_val(v_max_fds);
int n_bufs = Wosize_val(v_bufs); int n_bufs = Wosize_val(v_bufs);
struct iovec iov[n_bufs]; struct iovec iov[n_bufs];
union sock_addr_union source_addr; union sock_addr_union source_addr;
int controllen = max_fds > 0 ? CMSG_SPACE(sizeof(int) * max_fds) : 0;
char cmsg[controllen];
struct msghdr msg = { struct msghdr msg = {
.msg_name = &source_addr, .msg_name = &source_addr,
.msg_namelen = sizeof(source_addr), .msg_namelen = sizeof(source_addr),
.msg_iov = iov, .msg_iov = iov,
.msg_iovlen = n_bufs, .msg_iovlen = n_bufs,
.msg_control = max_fds > 0 ? cmsg : NULL,
.msg_controllen = controllen,
}; };
ssize_t r; ssize_t r;
memset(cmsg, 0, controllen);
fill_iov(iov, v_bufs); fill_iov(iov, v_bufs);
caml_enter_blocking_section(); caml_enter_blocking_section();
@ -318,10 +261,9 @@ CAMLprim value caml_eio_posix_recv_msg(value v_fd, value v_max_fds, value v_bufs
v_addr = caml_unix_alloc_sockaddr(&source_addr, msg.msg_namelen, -1); v_addr = caml_unix_alloc_sockaddr(&source_addr, msg.msg_namelen, -1);
v_result = caml_alloc_tuple(3); v_result = caml_alloc_tuple(2);
Store_field(v_result, 0, v_addr); Store_field(v_result, 0, v_addr);
Store_field(v_result, 1, Val_long(r)); Store_field(v_result, 1, Val_long(r));
Store_field(v_result, 2, get_msghdr_fds(&msg));
CAMLreturn(v_result); CAMLreturn(v_result);
} }

View File

@ -1,15 +1,8 @@
open Eio.Std
module Fd = Eio_unix.Fd module Fd = Eio_unix.Fd
module Impl = struct let fstat fd =
type tag = [`Generic | `Unix]
type t = Eio_unix.Fd.t
let stat t =
try try
let ust = Low_level.fstat t in let ust = Low_level.fstat fd in
let st_kind : Eio.File.Stat.kind = let st_kind : Eio.File.Stat.kind =
match ust.st_kind with match ust.st_kind with
| Unix.S_REG -> `Regular_file | Unix.S_REG -> `Regular_file
@ -36,35 +29,35 @@ module Impl = struct
} }
with Unix.Unix_error (code, name, arg) -> raise @@ Err.wrap code name arg with Unix.Unix_error (code, name, arg) -> raise @@ Err.wrap code name arg
let write t bufs = let write_bufs fd bufs =
try try
let rec loop = function let rec loop = function
| [] -> () | [] -> ()
| bufs -> | bufs ->
let wrote = Low_level.writev t (Array.of_list bufs) in let wrote = Low_level.writev fd (Array.of_list bufs) in
loop (Cstruct.shiftv bufs wrote) loop (Cstruct.shiftv bufs wrote)
in in
loop bufs loop bufs
with Unix.Unix_error (code, name, arg) -> raise (Err.wrap code name arg) with Unix.Unix_error (code, name, arg) -> raise (Err.wrap code name arg)
let copy dst ~src = let copy src dst =
let buf = Cstruct.create 4096 in let buf = Cstruct.create 4096 in
try try
while true do while true do
let got = Eio.Flow.single_read src buf in let got = Eio.Flow.single_read src buf in
write dst [Cstruct.sub buf 0 got] write_bufs dst [Cstruct.sub buf 0 got]
done done
with End_of_file -> () with End_of_file -> ()
let single_read t buf = let read fd buf =
match Low_level.readv t [| buf |] with match Low_level.readv fd [| buf |] with
| 0 -> raise End_of_file | 0 -> raise End_of_file
| got -> got | got -> got
| exception (Unix.Unix_error (code, name, arg)) -> raise (Err.wrap code name arg) | exception (Unix.Unix_error (code, name, arg)) -> raise (Err.wrap code name arg)
let shutdown t cmd = let shutdown fd cmd =
try try
Low_level.shutdown t @@ match cmd with Low_level.shutdown fd @@ match cmd with
| `Receive -> Unix.SHUTDOWN_RECEIVE | `Receive -> Unix.SHUTDOWN_RECEIVE
| `Send -> Unix.SHUTDOWN_SEND | `Send -> Unix.SHUTDOWN_SEND
| `All -> Unix.SHUTDOWN_ALL | `All -> Unix.SHUTDOWN_ALL
@ -72,44 +65,34 @@ module Impl = struct
| Unix.Unix_error (Unix.ENOTCONN, _, _) -> () | Unix.Unix_error (Unix.ENOTCONN, _, _) -> ()
| Unix.Unix_error (code, name, arg) -> raise (Err.wrap code name arg) | Unix.Unix_error (code, name, arg) -> raise (Err.wrap code name arg)
let read_methods = [] let of_fd fd = object (_ : <Eio_unix.Net.stream_socket; Eio.File.rw>)
method fd = fd
let pread t ~file_offset bufs = method read_methods = []
let got = Low_level.preadv ~file_offset t (Array.of_list bufs) in method copy src = copy src fd
method pread ~file_offset bufs =
let got = Low_level.preadv ~file_offset fd (Array.of_list bufs) in
if got = 0 then raise End_of_file if got = 0 then raise End_of_file
else got else got
let pwrite t ~file_offset bufs = Low_level.pwritev ~file_offset t (Array.of_list bufs) method pwrite ~file_offset bufs = Low_level.pwritev ~file_offset fd (Array.of_list bufs)
let send_msg t ~fds data = method stat = fstat fd
Low_level.send_msg ~fds t (Array.of_list data) method read_into buf = read fd buf
method write bufs = write_bufs fd bufs
method shutdown cmd = shutdown fd cmd
method close = Fd.close fd
let recv_msg_with_fds t ~sw ~max_fds data = method probe : type a. a Eio.Generic.ty -> a option = function
let _addr, n, fds = Low_level.recv_msg_with_fds t ~sw ~max_fds (Array.of_list data) in | Eio_unix.Resource.FD -> Some fd
n, fds | _ -> None
let fd t = t
let close = Eio_unix.Fd.close
end end
let handler = Eio_unix.Pi.flow_handler (module Impl) let secure_random = object
inherit Eio.Flow.source
let of_fd fd = method read_into buf =
let r = Eio.Resource.T (fd, handler) in
(r : [`Unix_fd | Eio_unix.Net.stream_socket_ty | Eio.File.rw_ty] r :>
[< `Unix_fd | Eio_unix.Net.stream_socket_ty | Eio.File.rw_ty] r)
module Secure_random = struct
type t = unit
let single_read () buf =
Low_level.getrandom buf; Low_level.getrandom buf;
Cstruct.length buf Cstruct.length buf
let read_methods = []
end end
let secure_random =
let ops = Eio.Flow.Pi.source (module Secure_random) in
Eio.Resource.T ((), ops)

View File

@ -26,35 +26,120 @@ open Eio.Std
module Fd = Eio_unix.Fd module Fd = Eio_unix.Fd
module rec Dir : sig class virtual posix_dir = object
include Eio.Fs.Pi.DIR inherit Eio.Fs.dir
val v : label:string -> sandbox:bool -> string -> t val virtual opt_nofollow : Low_level.Open_flags.t
(** Extra flags for open operations. Sandboxes will add [O_NOFOLLOW] here. *)
val resolve : t -> string -> string method virtual private resolve : string -> string
(** [resolve t path] returns the real path that should be used to access [path]. (** [resolve path] returns the real path that should be used to access [path].
For sandboxes, this is [realpath path] (and it checks that it is within the sandbox). For sandboxes, this is [realpath path] (and it checks that it is within the sandbox).
For unrestricted access, this returns [path] unchanged. For unrestricted access, this is the identity function. *)
@raise Eio.Fs.Permission_denied if sandboxed and [path] is outside of [dir_path]. *)
val with_parent_dir : t -> string -> (Fd.t option -> string -> 'a) -> 'a method virtual with_parent_dir : 'a. (string -> (Fd.t option -> string -> 'a) -> 'a)
(** [with_parent_dir t path fn] runs [fn dir_fd rel_path], (** [with_parent_dir path fn] runs [fn dir_fd rel_path],
where [rel_path] accessed relative to [dir_fd] gives access to [path]. where [rel_path] accessed relative to [dir_fd] gives access to [path].
For unrestricted access, this just runs [fn None path]. For unrestricted access, this just runs [fn None path].
For sandboxes, it opens the parent of [path] as [dir_fd] and runs [fn (Some dir_fd) (basename path)]. *) For sandboxes, it opens the parent of [path] as [dir_fd] and runs [fn (Some dir_fd) (basename path)]. *)
end = struct end
type t = {
dir_path : string;
sandbox : bool;
label : string;
mutable closed : bool;
}
let resolve t path = (* When renaming, we get a plain [Eio.Fs.dir]. We need extra access to check
if t.sandbox then ( that the new location is within its sandbox. *)
if t.closed then Fmt.invalid_arg "Attempt to use closed directory %S" t.dir_path; type _ Eio.Generic.ty += Posix_dir : posix_dir Eio.Generic.ty
let as_posix_dir x = Eio.Generic.probe x Posix_dir
class virtual dir ~label = object (self)
inherit posix_dir
val mutable closed = false
method! probe : type a. a Eio.Generic.ty -> a option = function
| Posix_dir -> Some (self :> posix_dir)
| _ -> None
method open_in ~sw path =
let fd = Err.run (Low_level.openat ~mode:0 ~sw (self#resolve path)) Low_level.Open_flags.(opt_nofollow + rdonly) in
(Flow.of_fd fd :> <Eio.File.ro; Eio.Flow.close>)
method open_out ~sw ~append ~create path =
let mode, flags =
match create with
| `Never -> 0, Low_level.Open_flags.empty
| `If_missing perm -> perm, Low_level.Open_flags.creat
| `Or_truncate perm -> perm, Low_level.Open_flags.(creat + trunc)
| `Exclusive perm -> perm, Low_level.Open_flags.(creat + excl)
in
let flags = if append then Low_level.Open_flags.(flags + append) else flags in
let flags = Low_level.Open_flags.(flags + rdwr + opt_nofollow) in
match
self#with_parent_dir path @@ fun dirfd path ->
Low_level.openat ?dirfd ~sw ~mode path flags
with
| fd -> (Flow.of_fd fd :> <Eio.File.rw; Eio.Flow.close>)
| exception Unix.Unix_error (ELOOP, _, _) ->
(* The leaf was a symlink (or we're unconfined and the main path changed, but ignore that).
A leaf symlink might be OK, but we need to check it's still in the sandbox.
todo: possibly we should limit the number of redirections here, like the kernel does. *)
let target = Unix.readlink path in
let full_target =
if Filename.is_relative target then
Filename.concat (Filename.dirname path) target
else target
in
self#open_out ~sw ~append ~create full_target
| exception Unix.Unix_error (code, name, arg) ->
raise (Err.wrap code name arg)
method mkdir ~perm path =
self#with_parent_dir path @@ fun dirfd path ->
Err.run (Low_level.mkdir ?dirfd ~mode:perm) path
method unlink path =
self#with_parent_dir path @@ fun dirfd path ->
Err.run (Low_level.unlink ?dirfd ~dir:false) path
method rmdir path =
self#with_parent_dir path @@ fun dirfd path ->
Err.run (Low_level.unlink ?dirfd ~dir:true) path
method read_dir path =
(* todo: need fdopendir here to avoid races *)
let path = self#resolve path in
Err.run Low_level.readdir path
|> Array.to_list
method rename old_path new_dir new_path =
match as_posix_dir new_dir with
| None -> invalid_arg "Target is not an eio_posix directory!"
| Some new_dir ->
self#with_parent_dir old_path @@ fun old_dir old_path ->
new_dir#with_parent_dir new_path @@ fun new_dir new_path ->
Err.run (Low_level.rename ?old_dir old_path ?new_dir) new_path
method open_dir ~sw path =
Switch.check sw;
let label = Filename.basename path in
let d = new sandbox ~label (self#resolve path) in
Switch.on_release sw (fun () -> d#close);
(d :> Eio.Fs.dir_with_close)
method close = closed <- true
method pp f = Fmt.string f (String.escaped label)
end
and sandbox ~label dir_path = object (self)
inherit dir ~label
val opt_nofollow = Low_level.Open_flags.nofollow
(* Resolve a relative path to an absolute one, with no symlinks.
@raise Eio.Fs.Permission_denied if it's outside of [dir_path]. *)
method private resolve path =
if closed then Fmt.invalid_arg "Attempt to use closed directory %S" dir_path;
if Filename.is_relative path then ( if Filename.is_relative path then (
let dir_path = Err.run Low_level.realpath t.dir_path in let dir_path = Err.run Low_level.realpath dir_path in
let full = Err.run Low_level.realpath (Filename.concat dir_path path) in let full = Err.run Low_level.realpath (Filename.concat dir_path path) in
let prefix_len = String.length dir_path + 1 in let prefix_len = String.length dir_path + 1 in
if String.length full >= prefix_len && String.sub full 0 prefix_len = dir_path ^ Filename.dir_sep then if String.length full >= prefix_len && String.sub full 0 prefix_len = dir_path ^ Filename.dir_sep then
@ -66,11 +151,9 @@ end = struct
) else ( ) else (
raise @@ Eio.Fs.err (Permission_denied Err.Absolute_path) raise @@ Eio.Fs.err (Permission_denied Err.Absolute_path)
) )
) else path
let with_parent_dir t path fn = method with_parent_dir path fn =
if t.sandbox then ( if closed then Fmt.invalid_arg "Attempt to use closed directory %S" dir_path;
if t.closed then Fmt.invalid_arg "Attempt to use closed directory %S" t.dir_path;
let dir, leaf = Filename.dirname path, Filename.basename path in let dir, leaf = Filename.dirname path, Filename.basename path in
if leaf = ".." then ( if leaf = ".." then (
(* We could be smarter here and normalise the path first, but '..' (* We could be smarter here and normalise the path first, but '..'
@ -78,110 +161,22 @@ end = struct
anyway. *) anyway. *)
raise (Eio.Fs.err (Permission_denied (Err.Invalid_leaf leaf))) raise (Eio.Fs.err (Permission_denied (Err.Invalid_leaf leaf)))
) else ( ) else (
let dir = resolve t dir in let dir = self#resolve dir in
Switch.run @@ fun sw -> Switch.run @@ fun sw ->
let dirfd = Low_level.openat ~sw ~mode:0 dir Low_level.Open_flags.(directory + rdonly + nofollow) in let dirfd = Low_level.openat ~sw ~mode:0 dir Low_level.Open_flags.(directory + rdonly + nofollow) in
fn (Some dirfd) leaf fn (Some dirfd) leaf
) )
) else fn None path
let v ~label ~sandbox dir_path = { dir_path; sandbox; label; closed = false }
(* Sandboxes use [O_NOFOLLOW] when opening files ([resolve] already removed any symlinks).
This avoids a race where symlink might be added after [realpath] returns. *)
let opt_nofollow t =
if t.sandbox then Low_level.Open_flags.nofollow else Low_level.Open_flags.empty
let open_in t ~sw path =
let fd = Err.run (Low_level.openat ~mode:0 ~sw (resolve t path)) Low_level.Open_flags.(opt_nofollow t + rdonly) in
(Flow.of_fd fd :> Eio.File.ro_ty Eio.Resource.t)
let rec open_out t ~sw ~append ~create path =
let mode, flags =
match create with
| `Never -> 0, Low_level.Open_flags.empty
| `If_missing perm -> perm, Low_level.Open_flags.creat
| `Or_truncate perm -> perm, Low_level.Open_flags.(creat + trunc)
| `Exclusive perm -> perm, Low_level.Open_flags.(creat + excl)
in
let flags = if append then Low_level.Open_flags.(flags + append) else flags in
let flags = Low_level.Open_flags.(flags + rdwr + opt_nofollow t) in
match
with_parent_dir t path @@ fun dirfd path ->
Low_level.openat ?dirfd ~sw ~mode path flags
with
| fd -> (Flow.of_fd fd :> Eio.File.rw_ty r)
| exception Unix.Unix_error (ELOOP, _, _) ->
(* The leaf was a symlink (or we're unconfined and the main path changed, but ignore that).
A leaf symlink might be OK, but we need to check it's still in the sandbox.
todo: possibly we should limit the number of redirections here, like the kernel does. *)
let target = Unix.readlink path in
let full_target =
if Filename.is_relative target then
Filename.concat (Filename.dirname path) target
else target
in
open_out t ~sw ~append ~create full_target
| exception Unix.Unix_error (code, name, arg) ->
raise (Err.wrap code name arg)
let mkdir t ~perm path =
with_parent_dir t path @@ fun dirfd path ->
Err.run (Low_level.mkdir ?dirfd ~mode:perm) path
let unlink t path =
with_parent_dir t path @@ fun dirfd path ->
Err.run (Low_level.unlink ?dirfd ~dir:false) path
let rmdir t path =
with_parent_dir t path @@ fun dirfd path ->
Err.run (Low_level.unlink ?dirfd ~dir:true) path
let read_dir t path =
(* todo: need fdopendir here to avoid races *)
let path = resolve t path in
Err.run Low_level.readdir path
|> Array.to_list
let rename t old_path new_dir new_path =
match Handler.as_posix_dir new_dir with
| None -> invalid_arg "Target is not an eio_posix directory!"
| Some new_dir ->
with_parent_dir t old_path @@ fun old_dir old_path ->
with_parent_dir new_dir new_path @@ fun new_dir new_path ->
Err.run (Low_level.rename ?old_dir old_path ?new_dir) new_path
let close t = t.closed <- true
let open_dir t ~sw path =
Switch.check sw;
let label = Filename.basename path in
let d = v ~label (resolve t path) ~sandbox:true in
Switch.on_release sw (fun () -> close d);
Eio.Resource.T (d, Handler.v)
let pp f t = Fmt.string f (String.escaped t.label)
end
and Handler : sig
val v : (Dir.t, [`Dir | `Close]) Eio.Resource.handler
val as_posix_dir : [> `Dir] r -> Dir.t option
end = struct
(* When renaming, we get a plain [Eio.Fs.dir]. We need extra access to check
that the new location is within its sandbox. *)
type (_, _, _) Eio.Resource.pi += Posix_dir : ('t, 't -> Dir.t, [> `Posix_dir]) Eio.Resource.pi
let as_posix_dir (Eio.Resource.T (t, ops)) =
match Eio.Resource.get_opt ops Posix_dir with
| None -> None
| Some fn -> Some (fn t)
let v = Eio.Resource.handler [
H (Eio.Fs.Pi.Dir, (module Dir));
H (Posix_dir, Fun.id);
]
end end
(* Full access to the filesystem. *) (* Full access to the filesystem. *)
let fs = Eio.Resource.T (Dir.v ~label:"fs" ~sandbox:false ".", Handler.v) let fs = object
let cwd = Eio.Resource.T (Dir.v ~label:"cwd" ~sandbox:true ".", Handler.v) inherit dir ~label:"fs"
val opt_nofollow = Low_level.Open_flags.empty
(* No checks *)
method private resolve path = path
method private with_parent_dir path fn = fn None path
end
let cwd = new sandbox ~label:"cwd" "."

View File

@ -79,27 +79,16 @@ let accept ~sw sock =
let shutdown sock cmd = let shutdown sock cmd =
Fd.use_exn "shutdown" sock (fun fd -> Unix.shutdown fd cmd) Fd.use_exn "shutdown" sock (fun fd -> Unix.shutdown fd cmd)
external eio_send_msg : Unix.file_descr -> int -> Unix.file_descr list -> Unix.sockaddr option -> Cstruct.t array -> int = "caml_eio_posix_send_msg" external eio_send_msg : Unix.file_descr -> Unix.sockaddr option -> Cstruct.t array -> int = "caml_eio_posix_send_msg"
external eio_recv_msg : Unix.file_descr -> int -> Cstruct.t array -> Unix.sockaddr * int * Unix.file_descr list = "caml_eio_posix_recv_msg" external eio_recv_msg : Unix.file_descr -> Cstruct.t array -> Unix.sockaddr * int = "caml_eio_posix_recv_msg"
let send_msg fd ?(fds = []) ?dst buf = let send_msg fd ?dst buf =
Fd.use_exn "send_msg" fd @@ fun fd -> Fd.use_exn "send_msg" fd @@ fun fd ->
Fd.use_exn_list "send_msg" fds @@ fun fds -> do_nonblocking Write (fun fd -> eio_send_msg fd dst buf) fd
do_nonblocking Write (fun fd -> eio_send_msg fd (List.length fds) fds dst buf) fd
let recv_msg fd buf = let recv_msg fd buf =
let addr, got, _ =
Fd.use_exn "recv_msg" fd @@ fun fd -> Fd.use_exn "recv_msg" fd @@ fun fd ->
do_nonblocking Read (fun fd -> eio_recv_msg fd 0 buf) fd do_nonblocking Read (fun fd -> eio_recv_msg fd buf) fd
in
(addr, got)
let recv_msg_with_fds ~sw ~max_fds fd buf =
let addr, got, fds =
Fd.use_exn "recv_msg" fd @@ fun fd ->
do_nonblocking Read (fun fd -> eio_recv_msg fd max_fds buf) fd
in
(addr, got, Eio_unix.Fd.of_unix_list ~sw fds)
external eio_getrandom : Cstruct.buffer -> int -> int -> int = "caml_eio_posix_getrandom" external eio_getrandom : Cstruct.buffer -> int -> int -> int = "caml_eio_posix_getrandom"

View File

@ -29,9 +29,7 @@ val accept : sw:Switch.t -> fd -> fd * Unix.sockaddr
val shutdown : fd -> Unix.shutdown_command -> unit val shutdown : fd -> Unix.shutdown_command -> unit
val recv_msg : fd -> Cstruct.t array -> Unix.sockaddr * int val recv_msg : fd -> Cstruct.t array -> Unix.sockaddr * int
val recv_msg_with_fds : sw:Switch.t -> max_fds:int -> fd -> Cstruct.t array -> Unix.sockaddr * int * fd list val send_msg : fd -> ?dst:Unix.sockaddr -> Cstruct.t array -> int
val send_msg : fd -> ?fds:fd list -> ?dst:Unix.sockaddr -> Cstruct.t array -> int
val getrandom : Cstruct.t -> unit val getrandom : Cstruct.t -> unit

View File

@ -12,71 +12,44 @@ let socket_domain_of = function
~v4:(fun _ -> Unix.PF_INET) ~v4:(fun _ -> Unix.PF_INET)
~v6:(fun _ -> Unix.PF_INET6) ~v6:(fun _ -> Unix.PF_INET6)
module Listening_socket = struct let listening_socket ~hook fd = object
type t = { inherit Eio.Net.listening_socket
hook : Switch.hook;
fd : Fd.t;
}
type tag = [`Generic | `Unix] method close =
Switch.remove_hook hook;
Fd.close fd
let make ~hook fd = { hook; fd } method accept ~sw =
let client, client_addr = Err.run (Low_level.accept ~sw) fd in
let fd t = t.fd
let close t =
Switch.remove_hook t.hook;
Fd.close t.fd
let accept t ~sw =
let client, client_addr = Err.run (Low_level.accept ~sw) t.fd in
let client_addr = match client_addr with let client_addr = match client_addr with
| Unix.ADDR_UNIX path -> `Unix path | Unix.ADDR_UNIX path -> `Unix path
| Unix.ADDR_INET (host, port) -> `Tcp (Eio_unix.Net.Ipaddr.of_unix host, port) | Unix.ADDR_INET (host, port) -> `Tcp (Eio_unix.Net.Ipaddr.of_unix host, port)
in in
let flow = (Flow.of_fd client :> _ Eio.Net.stream_socket) in let flow = (Flow.of_fd client :> Eio.Net.stream_socket) in
flow, client_addr flow, client_addr
method! probe : type a. a Eio.Generic.ty -> a option = function
| Eio_unix.Resource.FD -> Some fd
| _ -> None
end end
let listening_handler = Eio_unix.Pi.listening_socket_handler (module Listening_socket) let datagram_socket sock = object
inherit Eio_unix.Net.datagram_socket
let listening_socket ~hook fd = method close = Fd.close sock
Eio.Resource.T (Listening_socket.make ~hook fd, listening_handler)
module Datagram_socket = struct method fd = sock
type tag = [`Generic | `Unix]
type t = Eio_unix.Fd.t method send ?dst buf =
let close = Fd.close
let fd t = t
let send t ?dst buf =
let dst = Option.map Eio_unix.Net.sockaddr_to_unix dst in let dst = Option.map Eio_unix.Net.sockaddr_to_unix dst in
let sent = Err.run (Low_level.send_msg t ?dst) (Array.of_list buf) in let sent = Err.run (Low_level.send_msg sock ?dst) (Array.of_list buf) in
assert (sent = Cstruct.lenv buf) assert (sent = Cstruct.lenv buf)
let recv t buf = method recv buf =
let addr, recv = Err.run (Low_level.recv_msg t) [| buf |] in let addr, recv = Err.run (Low_level.recv_msg sock) [| buf |] in
Eio_unix.Net.sockaddr_of_unix_datagram addr, recv Eio_unix.Net.sockaddr_of_unix_datagram addr, recv
let shutdown t cmd =
try
Low_level.shutdown t @@ match cmd with
| `Receive -> Unix.SHUTDOWN_RECEIVE
| `Send -> Unix.SHUTDOWN_SEND
| `All -> Unix.SHUTDOWN_ALL
with
| Unix.Unix_error (Unix.ENOTCONN, _, _) -> ()
| Unix.Unix_error (code, name, arg) -> raise (Err.wrap code name arg)
end end
let datagram_handler = Eio_unix.Pi.datagram_handler (module Datagram_socket)
let datagram_socket fd =
Eio.Resource.T (fd, datagram_handler)
(* https://www.iana.org/assignments/protocol-numbers/protocol-numbers.xhtml *) (* https://www.iana.org/assignments/protocol-numbers/protocol-numbers.xhtml *)
let getaddrinfo ~service node = let getaddrinfo ~service node =
let to_eio_sockaddr_t {Unix.ai_family; ai_addr; ai_socktype; ai_protocol; _ } = let to_eio_sockaddr_t {Unix.ai_family; ai_addr; ai_socktype; ai_protocol; _ } =
@ -132,7 +105,7 @@ let listen ~reuse_addr ~reuse_port ~backlog ~sw (listen_addr : Eio.Net.Sockaddr.
Unix.bind fd addr; Unix.bind fd addr;
Unix.listen fd backlog; Unix.listen fd backlog;
); );
(listening_socket ~hook sock :> _ Eio.Net.listening_socket_ty r) listening_socket ~hook sock
let connect ~sw connect_addr = let connect ~sw connect_addr =
let socket_type, addr = let socket_type, addr =
@ -145,7 +118,7 @@ let connect ~sw connect_addr =
let sock = Low_level.socket ~sw (socket_domain_of connect_addr) socket_type 0 in let sock = Low_level.socket ~sw (socket_domain_of connect_addr) socket_type 0 in
try try
Low_level.connect sock addr; Low_level.connect sock addr;
(Flow.of_fd sock :> _ Eio_unix.Net.stream_socket) (Flow.of_fd sock :> Eio.Net.stream_socket)
with Unix.Unix_error (code, name, arg) -> raise (Err.wrap code name arg) with Unix.Unix_error (code, name, arg) -> raise (Err.wrap code name arg)
let create_datagram_socket ~reuse_addr ~reuse_port ~sw saddr = let create_datagram_socket ~reuse_addr ~reuse_port ~sw saddr =
@ -162,26 +135,13 @@ let create_datagram_socket ~reuse_addr ~reuse_port ~sw saddr =
) )
| `UdpV4 | `UdpV6 -> () | `UdpV4 | `UdpV6 -> ()
end; end;
datagram_socket sock (datagram_socket sock :> Eio.Net.datagram_socket)
module Impl = struct let v = object
type t = unit inherit Eio_unix.Net.t
type tag = [`Generic | `Unix]
let listen () = listen method listen = listen
method connect = connect
let connect () ~sw addr = method datagram_socket = create_datagram_socket
let socket = connect ~sw addr in method getaddrinfo = getaddrinfo
(socket :> [`Generic | `Unix] Eio.Net.stream_socket_ty r)
let datagram_socket () ~reuse_addr ~reuse_port ~sw saddr =
let socket = create_datagram_socket ~reuse_addr ~reuse_port ~sw saddr in
(socket :> [`Generic | `Unix] Eio.Net.datagram_socket_ty r)
let getaddrinfo () = getaddrinfo
let getnameinfo () = Eio_unix.Net.getnameinfo
end end
let v : Impl.tag Eio.Net.ty r =
let handler = Eio.Net.Pi.network (module Impl) in
Eio.Resource.T ((), handler)

View File

@ -24,11 +24,11 @@ let v = object
] in ] in
let with_actions cwd fn = match cwd with let with_actions cwd fn = match cwd with
| None -> fn actions | None -> fn actions
| Some ((dir, path) : Eio.Fs.dir_ty Eio.Path.t) -> | Some ((dir, path) : Eio.Fs.dir Eio.Path.t) ->
match Fs.Handler.as_posix_dir dir with match Eio.Generic.probe dir Fs.Posix_dir with
| None -> Fmt.invalid_arg "cwd is not an OS directory!" | None -> Fmt.invalid_arg "cwd is not an OS directory!"
| Some posix -> | Some posix ->
Fs.Dir.with_parent_dir posix path @@ fun dirfd s -> posix#with_parent_dir path @@ fun dirfd s ->
Switch.run @@ fun launch_sw -> Switch.run @@ fun launch_sw ->
let cwd = Low_level.openat ?dirfd ~sw:launch_sw ~mode:0 s Low_level.Open_flags.(rdonly + directory) in let cwd = Low_level.openat ?dirfd ~sw:launch_sw ~mode:0 s Low_level.Open_flags.(rdonly + directory) in
fn (Process.Fork_action.fchdir cwd :: actions) fn (Process.Fork_action.fchdir cwd :: actions)

View File

@ -20,7 +20,7 @@ open Eio.Std
module Fd = Eio_unix.Fd module Fd = Eio_unix.Fd
let socketpair k ~sw ~domain ~ty ~protocol wrap_a wrap_b = let socketpair k ~sw ~domain ~ty ~protocol ~wrap =
let open Effect.Deep in let open Effect.Deep in
match match
let unix_a, unix_b = Unix.socketpair ~cloexec:true domain ty protocol in let unix_a, unix_b = Unix.socketpair ~cloexec:true domain ty protocol in
@ -28,7 +28,7 @@ let socketpair k ~sw ~domain ~ty ~protocol wrap_a wrap_b =
let b = Fd.of_unix ~sw ~blocking:false ~close_unix:true unix_b in let b = Fd.of_unix ~sw ~blocking:false ~close_unix:true unix_b in
Unix.set_nonblock unix_a; Unix.set_nonblock unix_a;
Unix.set_nonblock unix_b; Unix.set_nonblock unix_b;
(wrap_a a, wrap_b b) (wrap a, wrap b)
with with
| r -> continue k r | r -> continue k r
| exception Unix.Unix_error (code, name, arg) -> | exception Unix.Unix_error (code, name, arg) ->
@ -46,7 +46,7 @@ let run_event_loop fn x =
let fd = Fd.of_unix ~sw ~blocking:false ~close_unix unix_fd in let fd = Fd.of_unix ~sw ~blocking:false ~close_unix unix_fd in
(* TODO: On Windows, if the FD from Unix.pipe () is passed this will fail *) (* TODO: On Windows, if the FD from Unix.pipe () is passed this will fail *)
(try Unix.set_nonblock unix_fd with Unix.Unix_error (Unix.ENOTSOCK, _, _) -> ()); (try Unix.set_nonblock unix_fd with Unix.Unix_error (Unix.ENOTSOCK, _, _) -> ());
continue k (Flow.of_fd fd :> _ Eio_unix.Net.stream_socket) continue k (Flow.of_fd fd :> Eio_unix.Net.stream_socket)
) )
| Eio_unix.Net.Import_socket_datagram (sw, close_unix, unix_fd) -> Some (fun k -> | Eio_unix.Net.Import_socket_datagram (sw, close_unix, unix_fd) -> Some (fun k ->
let fd = Fd.of_unix ~sw ~blocking:false ~close_unix unix_fd in let fd = Fd.of_unix ~sw ~blocking:false ~close_unix unix_fd in
@ -54,18 +54,18 @@ let run_event_loop fn x =
continue k (Net.datagram_socket fd) continue k (Net.datagram_socket fd)
) )
| Eio_unix.Net.Socketpair_stream (sw, domain, protocol) -> Some (fun k -> | Eio_unix.Net.Socketpair_stream (sw, domain, protocol) -> Some (fun k ->
let wrap fd = (Flow.of_fd fd :> _ Eio_unix.Net.stream_socket) in socketpair k ~sw ~domain ~protocol ~ty:Unix.SOCK_STREAM
socketpair k ~sw ~domain ~protocol ~ty:Unix.SOCK_STREAM wrap wrap ~wrap:(fun fd -> (Flow.of_fd fd :> Eio_unix.Net.stream_socket))
) )
| Eio_unix.Net.Socketpair_datagram (sw, domain, protocol) -> Some (fun k -> | Eio_unix.Net.Socketpair_datagram (sw, domain, protocol) -> Some (fun k ->
let wrap fd = Net.datagram_socket fd in socketpair k ~sw ~domain ~protocol ~ty:Unix.SOCK_DGRAM
socketpair k ~sw ~domain ~protocol ~ty:Unix.SOCK_DGRAM wrap wrap ~wrap:(fun fd -> Net.datagram_socket fd)
) )
| Eio_unix.Private.Pipe sw -> Some (fun k -> | Eio_unix.Private.Pipe sw -> Some (fun k ->
match match
let r, w = Low_level.pipe ~sw in let r, w = Low_level.pipe ~sw in
let source = Flow.of_fd r in let source = (Flow.of_fd r :> Eio_unix.source) in
let sink = Flow.of_fd w in let sink = (Flow.of_fd w :> Eio_unix.sink) in
(source, sink) (source, sink)
with with
| r -> continue k r | r -> continue k r

View File

@ -19,9 +19,9 @@ module Low_level = Low_level
type stdenv = Eio_unix.Stdenv.base type stdenv = Eio_unix.Stdenv.base
let run main = let run main =
let stdin = (Flow.of_fd Eio_unix.Fd.stdin :> _ Eio_unix.source) in let stdin = (Flow.of_fd Eio_unix.Fd.stdin :> Eio_unix.source) in
let stdout = (Flow.of_fd Eio_unix.Fd.stdout :> _ Eio_unix.sink) in let stdout = (Flow.of_fd Eio_unix.Fd.stdout :> Eio_unix.sink) in
let stderr = (Flow.of_fd Eio_unix.Fd.stderr :> _ Eio_unix.sink) in let stderr = (Flow.of_fd Eio_unix.Fd.stderr :> Eio_unix.sink) in
Domain_mgr.run_event_loop main @@ object (_ : stdenv) Domain_mgr.run_event_loop main @@ object (_ : stdenv)
method stdin = stdin method stdin = stdin
method stdout = stdout method stdout = stdout
@ -31,8 +31,8 @@ let run main =
method mono_clock = Time.mono_clock method mono_clock = Time.mono_clock
method net = Net.v method net = Net.v
method domain_mgr = Domain_mgr.v method domain_mgr = Domain_mgr.v
method cwd = ((Fs.cwd, "") :> Eio.Fs.dir_ty Eio.Path.t) method cwd = ((Fs.cwd, "") :> Eio.Fs.dir Eio.Path.t)
method fs = ((Fs.fs, "") :> Eio.Fs.dir_ty Eio.Path.t) method fs = ((Fs.fs, "") :> Eio.Fs.dir Eio.Path.t)
method process_mgr = failwith "process operations not supported on Windows yet" method process_mgr = failwith "process operations not supported on Windows yet"
method secure_random = Flow.secure_random method secure_random = Flow.secure_random
method backend_id = "windows" method backend_id = "windows"

View File

@ -1,15 +1,8 @@
open Eio.Std
module Fd = Eio_unix.Fd module Fd = Eio_unix.Fd
module Impl = struct let fstat fd =
type tag = [`Generic | `Unix]
type t = Eio_unix.Fd.t
let stat t =
try try
let ust = Low_level.fstat t in let ust = Low_level.fstat fd in
let st_kind : Eio.File.Stat.kind = let st_kind : Eio.File.Stat.kind =
match ust.st_kind with match ust.st_kind with
| Unix.S_REG -> `Regular_file | Unix.S_REG -> `Regular_file
@ -36,28 +29,29 @@ module Impl = struct
} }
with Unix.Unix_error (code, name, arg) -> raise @@ Err.wrap code name arg with Unix.Unix_error (code, name, arg) -> raise @@ Err.wrap code name arg
let write t bufs = let write_bufs fd bufs =
try Low_level.writev t bufs try
Low_level.writev fd bufs
with Unix.Unix_error (code, name, arg) -> raise (Err.wrap code name arg) with Unix.Unix_error (code, name, arg) -> raise (Err.wrap code name arg)
let copy dst ~src = let copy src dst =
let buf = Cstruct.create 4096 in let buf = Cstruct.create 4096 in
try try
while true do while true do
let got = Eio.Flow.single_read src buf in let got = Eio.Flow.single_read src buf in
write dst [Cstruct.sub buf 0 got] write_bufs dst [Cstruct.sub buf 0 got]
done done
with End_of_file -> () with End_of_file -> ()
let single_read t buf = let read fd buf =
match Low_level.read_cstruct t buf with match Low_level.read_cstruct fd buf with
| 0 -> raise End_of_file | 0 -> raise End_of_file
| got -> got | got -> got
| exception (Unix.Unix_error (code, name, arg)) -> raise (Err.wrap code name arg) | exception (Unix.Unix_error (code, name, arg)) -> raise (Err.wrap code name arg)
let shutdown t cmd = let shutdown fd cmd =
try try
Low_level.shutdown t @@ match cmd with Low_level.shutdown fd @@ match cmd with
| `Receive -> Unix.SHUTDOWN_RECEIVE | `Receive -> Unix.SHUTDOWN_RECEIVE
| `Send -> Unix.SHUTDOWN_SEND | `Send -> Unix.SHUTDOWN_SEND
| `All -> Unix.SHUTDOWN_ALL | `All -> Unix.SHUTDOWN_ALL
@ -65,41 +59,34 @@ module Impl = struct
| Unix.Unix_error (Unix.ENOTCONN, _, _) -> () | Unix.Unix_error (Unix.ENOTCONN, _, _) -> ()
| Unix.Unix_error (code, name, arg) -> raise (Err.wrap code name arg) | Unix.Unix_error (code, name, arg) -> raise (Err.wrap code name arg)
let read_methods = [] let of_fd fd = object (_ : <Eio_unix.Net.stream_socket; Eio.File.rw>)
method fd = fd
let pread t ~file_offset bufs = method read_methods = []
let got = Low_level.preadv ~file_offset t (Array.of_list bufs) in method copy src = copy src fd
method pread ~file_offset bufs =
let got = Low_level.preadv ~file_offset fd (Array.of_list bufs) in
if got = 0 then raise End_of_file if got = 0 then raise End_of_file
else got else got
let pwrite t ~file_offset bufs = Low_level.pwritev ~file_offset t (Array.of_list bufs) method pwrite ~file_offset bufs = Low_level.pwritev ~file_offset fd (Array.of_list bufs)
let send_msg _t ~fds:_ _data = failwith "Not implemented on Windows" method stat = fstat fd
method read_into buf = read fd buf
method write bufs = write_bufs fd bufs
method shutdown cmd = shutdown fd cmd
method close = Fd.close fd
let recv_msg_with_fds _t ~sw:_ ~max_fds:_ _data = failwith "Not implemented on Windows" method probe : type a. a Eio.Generic.ty -> a option = function
| Eio_unix.Resource.FD -> Some fd
let fd t = t | _ -> None
let close = Eio_unix.Fd.close
end end
let handler = Eio_unix.Pi.flow_handler (module Impl) let secure_random = object
inherit Eio.Flow.source
let of_fd fd = method read_into buf =
let r = Eio.Resource.T (fd, handler) in
(r : [`Unix_fd | Eio_unix.Net.stream_socket_ty | Eio.File.rw_ty] r :>
[< `Unix_fd | Eio_unix.Net.stream_socket_ty | Eio.File.rw_ty] r)
module Secure_random = struct
type t = unit
let single_read () buf =
Low_level.getrandom buf; Low_level.getrandom buf;
Cstruct.length buf Cstruct.length buf
let read_methods = []
end end
let secure_random =
let ops = Eio.Flow.Pi.source (module Secure_random) in
Eio.Resource.T ((), ops)

View File

@ -26,80 +26,44 @@ open Eio.Std
module Fd = Eio_unix.Fd module Fd = Eio_unix.Fd
module rec Dir : sig class virtual posix_dir = object
include Eio.Fs.Pi.DIR inherit Eio.Fs.dir
val v : label:string -> sandbox:bool -> string -> t val virtual opt_nofollow : bool
(** Emulate [O_NOFOLLOW] here. *)
val resolve : t -> string -> string method virtual private resolve : string -> string
(** [resolve t path] returns the real path that should be used to access [path]. (** [resolve path] returns the real path that should be used to access [path].
For sandboxes, this is [realpath path] (and it checks that it is within the sandbox). For sandboxes, this is [realpath path] (and it checks that it is within the sandbox).
For unrestricted access, this returns [path] unchanged. For unrestricted access, this is the identity function. *)
@raise Eio.Fs.Permission_denied if sandboxed and [path] is outside of [dir_path]. *)
val with_parent_dir : t -> string -> (Fd.t option -> string -> 'a) -> 'a method virtual with_parent_dir : 'a. (string -> (Fd.t option -> string -> 'a) -> 'a)
(** [with_parent_dir t path fn] runs [fn dir_fd rel_path], (** [with_parent_dir path fn] runs [fn dir_fd rel_path],
where [rel_path] accessed relative to [dir_fd] gives access to [path]. where [rel_path] accessed relative to [dir_fd] gives access to [path].
For unrestricted access, this just runs [fn None path]. For unrestricted access, this just runs [fn None path].
For sandboxes, it opens the parent of [path] as [dir_fd] and runs [fn (Some dir_fd) (basename path)]. *) For sandboxes, it opens the parent of [path] as [dir_fd] and runs [fn (Some dir_fd) (basename path)]. *)
end = struct end
type t = {
dir_path : string;
sandbox : bool;
label : string;
mutable closed : bool;
}
let resolve t path = (* When renaming, we get a plain [Eio.Fs.dir]. We need extra access to check
if t.sandbox then ( that the new location is within its sandbox. *)
if t.closed then Fmt.invalid_arg "Attempt to use closed directory %S" t.dir_path; type _ Eio.Generic.ty += Posix_dir : posix_dir Eio.Generic.ty
if Filename.is_relative path then ( let as_posix_dir x = Eio.Generic.probe x Posix_dir
let dir_path = Err.run Low_level.realpath t.dir_path in
let full = Err.run Low_level.realpath (Filename.concat dir_path path) in
let prefix_len = String.length dir_path + 1 in
(* \\??\\ Is necessary with NtCreateFile. *)
if String.length full >= prefix_len && String.sub full 0 prefix_len = dir_path ^ Filename.dir_sep then begin
"\\??\\" ^ full
end else if full = dir_path then
"\\??\\" ^ full
else
raise @@ Eio.Fs.err (Permission_denied (Err.Outside_sandbox (full, dir_path)))
) else (
raise @@ Eio.Fs.err (Permission_denied Err.Absolute_path)
)
) else path
let with_parent_dir t path fn = class virtual dir ~label = object (self)
if t.sandbox then ( inherit posix_dir
if t.closed then Fmt.invalid_arg "Attempt to use closed directory %S" t.dir_path;
let dir, leaf = Filename.dirname path, Filename.basename path in val mutable closed = false
if leaf = ".." then (
(* We could be smarter here and normalise the path first, but '..' method! probe : type a. a Eio.Generic.ty -> a option = function
doesn't make sense for any of the current uses of [with_parent_dir] | Posix_dir -> Some (self :> posix_dir)
anyway. *) | _ -> None
raise (Eio.Fs.err (Permission_denied (Err.Invalid_leaf leaf)))
) else ( method open_in ~sw path =
let dir = resolve t dir in
Switch.run @@ fun sw ->
let open Low_level in let open Low_level in
let dirfd = Low_level.openat ~sw ~nofollow:true dir Flags.Open.(generic_read + synchronise) Flags.Disposition.(open_if) Flags.Create.(directory) in let fd = Err.run (Low_level.openat ~sw ~nofollow:opt_nofollow (self#resolve path)) Low_level.Flags.Open.(generic_read + synchronise) Flags.Disposition.(open_if) Flags.Create.(non_directory) in
fn (Some dirfd) leaf (Flow.of_fd fd :> <Eio.File.ro; Eio.Flow.close>)
)
) else fn None path
let v ~label ~sandbox dir_path = { dir_path; sandbox; label; closed = false } method open_out ~sw ~append ~create path =
(* Sandboxes use [O_NOFOLLOW] when opening files ([resolve] already removed any symlinks).
This avoids a race where symlink might be added after [realpath] returns.
TODO: Emulate [O_NOFOLLOW] here. *)
let opt_nofollow t = t.sandbox
let open_in t ~sw path =
let open Low_level in
let fd = Err.run (Low_level.openat ~sw ~nofollow:(opt_nofollow t) (resolve t path)) Low_level.Flags.Open.(generic_read + synchronise) Flags.Disposition.(open_if) Flags.Create.(non_directory) in
(Flow.of_fd fd :> Eio.File.ro_ty Eio.Resource.t)
let rec open_out t ~sw ~append ~create path =
let open Low_level in let open Low_level in
let _mode, disp = let _mode, disp =
match create with match create with
@ -108,15 +72,12 @@ end = struct
| `Or_truncate perm -> perm, Low_level.Flags.Disposition.overwrite_if | `Or_truncate perm -> perm, Low_level.Flags.Disposition.overwrite_if
| `Exclusive perm -> perm, Low_level.Flags.Disposition.create | `Exclusive perm -> perm, Low_level.Flags.Disposition.create
in in
let flags = let flags = if append then Low_level.Flags.Open.(synchronise + append) else Low_level.Flags.Open.(generic_write + synchronise) in
if append then Low_level.Flags.Open.(synchronise + append)
else Low_level.Flags.Open.(generic_write + synchronise)
in
match match
with_parent_dir t path @@ fun dirfd path -> self#with_parent_dir path @@ fun dirfd path ->
Low_level.openat ?dirfd ~nofollow:(opt_nofollow t) ~sw path flags disp Flags.Create.(non_directory) Low_level.openat ?dirfd ~nofollow:opt_nofollow ~sw path flags disp Flags.Create.(non_directory)
with with
| fd -> (Flow.of_fd fd :> Eio.File.rw_ty r) | fd -> (Flow.of_fd fd :> <Eio.File.rw; Eio.Flow.close>)
(* This is the result of raising [caml_unix_error(ELOOP,...)] *) (* This is the result of raising [caml_unix_error(ELOOP,...)] *)
| exception Unix.Unix_error (EUNKNOWNERR 114, _, _) -> | exception Unix.Unix_error (EUNKNOWNERR 114, _, _) ->
print_endline "UNKNOWN"; print_endline "UNKNOWN";
@ -129,67 +90,98 @@ end = struct
Filename.concat (Filename.dirname path) target Filename.concat (Filename.dirname path) target
else target else target
in in
open_out t ~sw ~append ~create full_target self#open_out ~sw ~append ~create full_target
| exception Unix.Unix_error (code, name, arg) -> | exception Unix.Unix_error (code, name, arg) ->
raise (Err.wrap code name arg) raise (Err.wrap code name arg)
let mkdir t ~perm path = method mkdir ~perm path =
with_parent_dir t path @@ fun dirfd path -> self#with_parent_dir path @@ fun dirfd path ->
Err.run (Low_level.mkdir ?dirfd ~mode:perm) path Err.run (Low_level.mkdir ?dirfd ~mode:perm) path
let unlink t path = method unlink path =
with_parent_dir t path @@ fun dirfd path -> self#with_parent_dir path @@ fun dirfd path ->
Err.run (Low_level.unlink ?dirfd ~dir:false) path Err.run (Low_level.unlink ?dirfd ~dir:false) path
let rmdir t path = method rmdir path =
with_parent_dir t path @@ fun dirfd path -> self#with_parent_dir path @@ fun dirfd path ->
Err.run (Low_level.unlink ?dirfd ~dir:true) path Err.run (Low_level.unlink ?dirfd ~dir:true) path
let read_dir t path = method read_dir path =
(* todo: need fdopendir here to avoid races *) (* todo: need fdopendir here to avoid races *)
let path = resolve t path in let path = self#resolve path in
Err.run Low_level.readdir path Err.run Low_level.readdir path
|> Array.to_list |> Array.to_list
let rename t old_path new_dir new_path = method rename old_path new_dir new_path =
match Handler.as_posix_dir new_dir with match as_posix_dir new_dir with
| None -> invalid_arg "Target is not an eio_posix directory!" | None -> invalid_arg "Target is not an eio_posix directory!"
| Some new_dir -> | Some new_dir ->
with_parent_dir t old_path @@ fun old_dir old_path -> self#with_parent_dir old_path @@ fun old_dir old_path ->
with_parent_dir new_dir new_path @@ fun new_dir new_path -> new_dir#with_parent_dir new_path @@ fun new_dir new_path ->
Err.run (Low_level.rename ?old_dir old_path ?new_dir) new_path Err.run (Low_level.rename ?old_dir old_path ?new_dir) new_path
let close t = t.closed <- true method open_dir ~sw path =
let open_dir t ~sw path =
Switch.check sw; Switch.check sw;
let label = Filename.basename path in let label = Filename.basename path in
let d = v ~label (resolve t path) ~sandbox:true in let d = new sandbox ~label (self#resolve path) in
Switch.on_release sw (fun () -> close d); Switch.on_release sw (fun () -> d#close);
Eio.Resource.T (d, Handler.v) (d :> Eio.Fs.dir_with_close)
let pp f t = Fmt.string f (String.escaped t.label) method close = closed <- true
method pp f = Fmt.string f (String.escaped label)
end end
and Handler : sig
val v : (Dir.t, [`Dir | `Close]) Eio.Resource.handler
val as_posix_dir : [> `Dir] r -> Dir.t option and sandbox ~label dir_path = object (self)
end = struct inherit dir ~label
(* When renaming, we get a plain [Eio.Fs.dir]. We need extra access to check
that the new location is within its sandbox. *)
type (_, _, _) Eio.Resource.pi += Posix_dir : ('t, 't -> Dir.t, [> `Posix_dir]) Eio.Resource.pi
let as_posix_dir (Eio.Resource.T (t, ops)) = val opt_nofollow = true
match Eio.Resource.get_opt ops Posix_dir with
| None -> None
| Some fn -> Some (fn t)
let v = Eio.Resource.handler [ (* Resolve a relative path to an absolute one, with no symlinks.
H (Eio.Fs.Pi.Dir, (module Dir)); @raise Eio.Fs.Permission_denied if it's outside of [dir_path]. *)
H (Posix_dir, Fun.id); method private resolve path =
] if closed then Fmt.invalid_arg "Attempt to use closed directory %S" dir_path;
if Filename.is_relative path then (
let dir_path = Err.run Low_level.realpath dir_path in
let full = Err.run Low_level.realpath (Filename.concat dir_path path) in
let prefix_len = String.length dir_path + 1 in
(* \\??\\ Is necessary with NtCreateFile. *)
if String.length full >= prefix_len && String.sub full 0 prefix_len = dir_path ^ Filename.dir_sep then begin
"\\??\\" ^ full
end else if full = dir_path then
"\\??\\" ^ full
else
raise @@ Eio.Fs.err (Permission_denied (Err.Outside_sandbox (full, dir_path)))
) else (
raise @@ Eio.Fs.err (Permission_denied Err.Absolute_path)
)
method with_parent_dir path fn =
if closed then Fmt.invalid_arg "Attempt to use closed directory %S" dir_path;
let dir, leaf = Filename.dirname path, Filename.basename path in
if leaf = ".." then (
(* We could be smarter here and normalise the path first, but '..'
doesn't make sense for any of the current uses of [with_parent_dir]
anyway. *)
raise (Eio.Fs.err (Permission_denied (Err.Invalid_leaf leaf)))
) else (
let dir = self#resolve dir in
Switch.run @@ fun sw ->
let open Low_level in
let dirfd = Low_level.openat ~sw ~nofollow:true dir Flags.Open.(generic_read + synchronise) Flags.Disposition.(open_if) Flags.Create.(directory) in
fn (Some dirfd) leaf
)
end end
(* Full access to the filesystem. *) (* Full access to the filesystem. *)
let fs = Eio.Resource.T (Dir.v ~label:"fs" ~sandbox:false ".", Handler.v) let fs = object
let cwd = Eio.Resource.T (Dir.v ~label:"cwd" ~sandbox:true ".", Handler.v) inherit dir ~label:"fs"
val opt_nofollow = false
(* No checks *)
method private resolve path = path
method private with_parent_dir path fn = fn None path
end
let cwd = new sandbox ~label:"cwd" "."

View File

@ -12,73 +12,47 @@ let socket_domain_of = function
~v4:(fun _ -> Unix.PF_INET) ~v4:(fun _ -> Unix.PF_INET)
~v6:(fun _ -> Unix.PF_INET6) ~v6:(fun _ -> Unix.PF_INET6)
module Listening_socket = struct let listening_socket ~hook fd = object
type t = { inherit Eio.Net.listening_socket
hook : Switch.hook;
fd : Fd.t;
}
type tag = [`Generic | `Unix] method close =
Switch.remove_hook hook;
Fd.close fd
let make ~hook fd = { hook; fd } method accept ~sw =
let client, client_addr = Err.run (Low_level.accept ~sw) fd in
let fd t = t.fd
let close t =
Switch.remove_hook t.hook;
Fd.close t.fd
let accept t ~sw =
let client, client_addr = Err.run (Low_level.accept ~sw) t.fd in
let client_addr = match client_addr with let client_addr = match client_addr with
| Unix.ADDR_UNIX path -> `Unix path | Unix.ADDR_UNIX path -> `Unix path
| Unix.ADDR_INET (host, port) -> `Tcp (Eio_unix.Net.Ipaddr.of_unix host, port) | Unix.ADDR_INET (host, port) -> `Tcp (Eio_unix.Net.Ipaddr.of_unix host, port)
in in
let flow = (Flow.of_fd client :> _ Eio.Net.stream_socket) in let flow = (Flow.of_fd client :> Eio.Net.stream_socket) in
flow, client_addr flow, client_addr
method! probe : type a. a Eio.Generic.ty -> a option = function
| Eio_unix.Resource.FD -> Some fd
| _ -> None
end end
let listening_handler = Eio_unix.Pi.listening_socket_handler (module Listening_socket) (* todo: would be nice to avoid copying between bytes and cstructs here *)
let datagram_socket sock = object
inherit Eio_unix.Net.datagram_socket
let listening_socket ~hook fd = method close = Fd.close sock
Eio.Resource.T (Listening_socket.make ~hook fd, listening_handler)
module Datagram_socket = struct method fd = sock
type tag = [`Generic | `Unix]
type t = Eio_unix.Fd.t method send ?dst buf =
let close = Fd.close
let fd t = t
let send t ?dst buf =
let dst = Option.map Eio_unix.Net.sockaddr_to_unix dst in let dst = Option.map Eio_unix.Net.sockaddr_to_unix dst in
let sent = Err.run (Low_level.send_msg t ?dst) (Bytes.unsafe_of_string (Cstruct.copyv buf)) in let sent = Err.run (Low_level.send_msg sock ?dst) (Bytes.unsafe_of_string (Cstruct.copyv buf)) in
assert (sent = Cstruct.lenv buf) assert (sent = Cstruct.lenv buf)
let recv t buf = method recv buf =
let b = Bytes.create (Cstruct.length buf) in let b = Bytes.create (Cstruct.length buf) in
let recv, addr = Err.run (Low_level.recv_msg t) b in let recv, addr = Err.run (Low_level.recv_msg sock) b in
Cstruct.blit_from_bytes b 0 buf 0 recv; Cstruct.blit_from_bytes b 0 buf 0 recv;
Eio_unix.Net.sockaddr_of_unix_datagram addr, recv Eio_unix.Net.sockaddr_of_unix_datagram addr, recv
let shutdown t cmd =
try
Low_level.shutdown t @@ match cmd with
| `Receive -> Unix.SHUTDOWN_RECEIVE
| `Send -> Unix.SHUTDOWN_SEND
| `All -> Unix.SHUTDOWN_ALL
with
| Unix.Unix_error (Unix.ENOTCONN, _, _) -> ()
| Unix.Unix_error (code, name, arg) -> raise (Err.wrap code name arg)
end end
let datagram_handler = Eio_unix.Pi.datagram_handler (module Datagram_socket)
let datagram_socket fd =
Eio.Resource.T (fd, datagram_handler)
(* https://www.iana.org/assignments/protocol-numbers/protocol-numbers.xhtml *) (* https://www.iana.org/assignments/protocol-numbers/protocol-numbers.xhtml *)
let getaddrinfo ~service node = let getaddrinfo ~service node =
let to_eio_sockaddr_t {Unix.ai_family; ai_addr; ai_socktype; ai_protocol; _ } = let to_eio_sockaddr_t {Unix.ai_family; ai_addr; ai_socktype; ai_protocol; _ } =
@ -136,7 +110,7 @@ let listen ~reuse_addr ~reuse_port ~backlog ~sw (listen_addr : Eio.Net.Sockaddr.
Unix.bind fd addr; Unix.bind fd addr;
Unix.listen fd backlog Unix.listen fd backlog
); );
(listening_socket ~hook sock :> _ Eio.Net.listening_socket_ty r) listening_socket ~hook sock
let connect ~sw connect_addr = let connect ~sw connect_addr =
let socket_type, addr = let socket_type, addr =
@ -149,7 +123,7 @@ let connect ~sw connect_addr =
let sock = Low_level.socket ~sw (socket_domain_of connect_addr) socket_type 0 in let sock = Low_level.socket ~sw (socket_domain_of connect_addr) socket_type 0 in
try try
Low_level.connect sock addr; Low_level.connect sock addr;
(Flow.of_fd sock :> _ Eio_unix.Net.stream_socket) (Flow.of_fd sock :> Eio.Net.stream_socket)
with Unix.Unix_error (code, name, arg) -> raise (Err.wrap code name arg) with Unix.Unix_error (code, name, arg) -> raise (Err.wrap code name arg)
let create_datagram_socket ~reuse_addr ~reuse_port ~sw saddr = let create_datagram_socket ~reuse_addr ~reuse_port ~sw saddr =
@ -166,26 +140,13 @@ let create_datagram_socket ~reuse_addr ~reuse_port ~sw saddr =
) )
| `UdpV4 | `UdpV6 -> () | `UdpV4 | `UdpV6 -> ()
end; end;
datagram_socket sock (datagram_socket sock :> Eio.Net.datagram_socket)
module Impl = struct let v = object
type t = unit inherit Eio_unix.Net.t
type tag = [`Generic | `Unix]
let listen () = listen method listen = listen
method connect = connect
let connect () ~sw addr = method datagram_socket = create_datagram_socket
let socket = connect ~sw addr in method getaddrinfo = getaddrinfo
(socket :> [`Generic | `Unix] Eio.Net.stream_socket_ty r)
let datagram_socket () ~reuse_addr ~reuse_port ~sw saddr =
let socket = create_datagram_socket ~reuse_addr ~reuse_port ~sw saddr in
(socket :> [`Generic | `Unix] Eio.Net.datagram_socket_ty r)
let getaddrinfo () = getaddrinfo
let getnameinfo () = Eio_unix.Net.getnameinfo
end end
let v : Impl.tag Eio.Net.ty r =
let handler = Eio.Net.Pi.network (module Impl) in
Eio.Resource.T ((), handler)

View File

@ -85,8 +85,8 @@ let test_wrap_socket pipe_or_socketpair () =
| `Pipe -> Unix.pipe () | `Pipe -> Unix.pipe ()
| `Socketpair -> Unix.socketpair Unix.PF_UNIX Unix.SOCK_STREAM 0 | `Socketpair -> Unix.socketpair Unix.PF_UNIX Unix.SOCK_STREAM 0
in in
let source = (Eio_unix.Net.import_socket_stream ~sw ~close_unix:true r :> Eio.Flow.source_ty r) in let source = (Eio_unix.Net.import_socket_stream ~sw ~close_unix:true r :> Eio.Flow.source) in
let sink = (Eio_unix.Net.import_socket_stream ~sw ~close_unix:true w :> Eio.Flow.sink_ty r) in let sink = (Eio_unix.Net.import_socket_stream ~sw ~close_unix:true w :> Eio.Flow.sink) in
let msg = "Hello" in let msg = "Hello" in
Fiber.both Fiber.both
(fun () -> Eio.Flow.copy_string (msg ^ "\n") sink) (fun () -> Eio.Flow.copy_string (msg ^ "\n") sink)
@ -98,8 +98,8 @@ let test_wrap_socket pipe_or_socketpair () =
let test_eio_socketpair () = let test_eio_socketpair () =
Switch.run @@ fun sw -> Switch.run @@ fun sw ->
let a, b = Eio_unix.Net.socketpair_stream ~sw () in let a, b = Eio_unix.Net.socketpair_stream ~sw () in
ignore (Eio_unix.Net.fd a : Eio_unix.Fd.t); ignore (Eio_unix.Resource.fd a : Eio_unix.Fd.t);
ignore (Eio_unix.Net.fd b : Eio_unix.Fd.t); ignore (Eio_unix.Resource.fd b : Eio_unix.Fd.t);
Eio.Flow.copy_string "foo" a; Eio.Flow.copy_string "foo" a;
Eio.Flow.close a; Eio.Flow.close a;
let msg = Eio.Buf_read.of_flow b ~max_size:10 |> Eio.Buf_read.take_all in let msg = Eio.Buf_read.of_flow b ~max_size:10 |> Eio.Buf_read.take_all in

View File

@ -1,35 +1,26 @@
open Eio.Std open Eio.Std
let n_domains = 4
let n_rounds = 100 let n_rounds = 100
let n_procs_per_round_per_domain = 100 / n_domains let n_procs_per_round = 100
let run_in_domain mgr = let main mgr =
let echo n = Eio.Process.parse_out mgr Eio.Buf_read.line ["sh"; "-c"; "echo " ^ string_of_int n] in let echo n = Eio.Process.parse_out mgr Eio.Buf_read.line ["sh"; "-c"; "echo " ^ string_of_int n] in
let t0 = Unix.gettimeofday () in
for i = 1 to n_rounds do
Switch.run @@ fun sw -> Switch.run @@ fun sw ->
for j = 1 to n_procs_per_round_per_domain do for j = 1 to n_procs_per_round do
Fiber.fork ~sw (fun () -> Fiber.fork ~sw (fun () ->
let result = echo j in let result = echo j in
assert (int_of_string result = j); assert (int_of_string result = j);
(* traceln "OK: %d" j *) (* traceln "OK: %d" j *)
) )
done
let main ~dm mgr =
let t0 = Unix.gettimeofday () in
for i = 1 to n_rounds do
Switch.run (fun sw ->
for _ = 1 to n_domains - 1 do
Fiber.fork ~sw (fun () -> Eio.Domain_manager.run dm (fun () -> run_in_domain mgr))
done; done;
Fiber.fork ~sw (fun () -> run_in_domain mgr); if false then traceln "Finished round %d/%d" i n_rounds
);
if true then traceln "Finished round %d/%d" i n_rounds
done; done;
let t1 = Unix.gettimeofday () in let t1 = Unix.gettimeofday () in
let n_procs = n_rounds * n_procs_per_round_per_domain * n_domains in let n_procs = n_rounds * n_procs_per_round in
traceln "Finished process stress test: ran %d processes in %.2fs (using %d domains)" n_procs (t1 -. t0) n_domains traceln "Finished process stress test: ran %d processes in %.2fs" n_procs (t1 -. t0)
let () = let () =
Eio_main.run @@ fun env -> Eio_main.run @@ fun env ->
main ~dm:env#domain_mgr env#process_mgr main env#process_mgr

View File

@ -19,13 +19,12 @@ let ensure t n =
(* The next data to be returned by `mock_flow`. `[]` to raise `End_of_file`: *) (* The next data to be returned by `mock_flow`. `[]` to raise `End_of_file`: *)
let next = ref [] let next = ref []
let mock_flow = let mock_flow = object
let module X = struct inherit Eio.Flow.source
type t = unit
let read_methods = [] method read_methods = []
let single_read () buf = method read_into buf =
match !next with match !next with
| [] -> | [] ->
traceln "mock_flow returning Eof"; traceln "mock_flow returning Eof";
@ -37,9 +36,7 @@ let mock_flow =
let x' = String.sub x len (String.length x - len) in let x' = String.sub x len (String.length x - len) in
next := (if x' = "" then xs else x' :: xs); next := (if x' = "" then xs else x' :: xs);
len len
end in end
let ops = Eio.Flow.Pi.source (module X) in
Eio.Resource.T ((), ops)
let read flow n = let read flow n =
let buf = Cstruct.create n in let buf = Cstruct.create n in
@ -241,7 +238,7 @@ Exception: End_of_file.
```ocaml ```ocaml
# let bflow = R.of_flow mock_flow ~max_size:100 |> R.as_flow;; # let bflow = R.of_flow mock_flow ~max_size:100 |> R.as_flow;;
val bflow : Eio.Flow.source_ty Eio.Std.r = Eio__.Resource.T (<poly>, <abstr>) val bflow : Eio.Flow.source = <obj>
# next := ["foo"; "bar"]; read bflow 2;; # next := ["foo"; "bar"]; read bflow 2;;
+mock_flow returning 3 bytes +mock_flow returning 3 bytes
+Read "fo" +Read "fo"

View File

@ -216,10 +216,9 @@ the whole batch to be flushed.
Check flush waits for the write to succeed: Check flush waits for the write to succeed:
```ocaml ```ocaml
module Slow_writer = struct let slow_writer = object
type t = unit inherit Eio.Flow.sink
method copy src =
let copy t ~src =
let buf = Cstruct.create 10 in let buf = Cstruct.create 10 in
try try
while true do while true do
@ -228,12 +227,7 @@ module Slow_writer = struct
traceln "Write %S" (Cstruct.to_string buf ~len) traceln "Write %S" (Cstruct.to_string buf ~len)
done done
with End_of_file -> () with End_of_file -> ()
let write t bufs = copy t ~src:(Eio.Flow.cstruct_source bufs)
end end
let slow_writer =
let ops = Eio.Flow.Pi.sink (module Slow_writer) in
Eio.Resource.T ((), ops)
``` ```
```ocaml ```ocaml

View File

@ -1,77 +0,0 @@
# Setting up the environment
```ocaml
# #require "eio_main";;
```
```ocaml
open Eio.Std
let ( / ) = Eio.Path.( / )
```
```ocaml
(* Send [to_send] to [w] and get it from [r], then read it. *)
let test ~to_send r w =
Switch.run @@ fun sw ->
Fiber.both
(fun () -> Eio_unix.Net.send_msg w [Cstruct.of_string "x"] ~fds:to_send)
(fun () ->
let buf = Cstruct.of_string "?" in
let got, fds = Eio_unix.Net.recv_msg_with_fds ~sw r ~max_fds:2 [buf] in
let msg = Cstruct.to_string buf ~len:got in
traceln "Got: %S plus %d FDs" msg (List.length fds);
fds |> List.iter (fun fd ->
Eio_unix.Fd.use_exn "read" fd @@ fun fd ->
let len = Unix.lseek fd 0 Unix.SEEK_CUR in
ignore (Unix.lseek fd 0 Unix.SEEK_SET : int);
traceln "Read: %S" (really_input_string (Unix.in_channel_of_descr fd) len);
)
)
let with_tmp_file dir id fn =
let path = (dir / (Printf.sprintf "tmp-%s.txt" id)) in
Eio.Path.with_open_out path ~create:(`Exclusive 0o600) @@ fun file ->
Fun.protect
(fun () ->
Eio.Flow.copy_string id file;
fn (Option.get (Eio_unix.Resource.fd_opt file))
)
~finally:(fun () -> Eio.Path.unlink path)
```
## Tests
Using a socket-pair:
```ocaml
# Eio_main.run @@ fun env ->
with_tmp_file env#cwd "foo" @@ fun fd1 ->
with_tmp_file env#cwd "bar" @@ fun fd2 ->
Switch.run @@ fun sw ->
let r, w = Eio_unix.Net.socketpair_stream ~sw ~domain:PF_UNIX ~protocol:0 () in
test ~to_send:[fd1; fd2] r w;;
+Got: "x" plus 2 FDs
+Read: "foo"
+Read: "bar"
- : unit = ()
```
Using named sockets:
```ocaml
# Eio_main.run @@ fun env ->
let net = env#net in
with_tmp_file env#cwd "foo" @@ fun fd ->
Switch.run @@ fun sw ->
let addr = `Unix "test.socket" in
let server = Eio.Net.listen ~sw net ~reuse_addr:true ~backlog:1 addr in
let r, w = Fiber.pair
(fun () -> Eio.Net.connect ~sw net addr)
(fun () -> fst (Eio.Net.accept ~sw server))
in
test ~to_send:[fd] r w;;
+Got: "x" plus 1 FDs
+Read: "foo"
- : unit = ()
```

View File

@ -12,23 +12,23 @@ let run fn =
Eio_main.run @@ fun _ -> Eio_main.run @@ fun _ ->
fn () fn ()
let mock_source = let mock_source items =
let module X = struct object
type t = Cstruct.t list ref inherit Eio.Flow.source
let read_methods = [] val mutable items = items
let single_read t buf = method read_methods = []
match !t with
method read_into buf =
match items with
| [] -> raise End_of_file | [] -> raise End_of_file
| x :: xs -> | x :: xs ->
let len = min (Cstruct.length buf) (Cstruct.length x) in let len = min (Cstruct.length buf) (Cstruct.length x) in
Cstruct.blit x 0 buf 0 len; Cstruct.blit x 0 buf 0 len;
t := Cstruct.shiftv (x :: xs) len; items <- Cstruct.shiftv (x :: xs) len;
len len
end in end
let ops = Eio.Flow.Pi.source (module X) in
fun items -> Eio.Resource.T (ref items, ops)
``` ```
## read_exact ## read_exact

View File

@ -8,7 +8,7 @@
```ocaml ```ocaml
open Eio.Std open Eio.Std
let run (fn : net:_ Eio.Net.t -> Switch.t -> unit) = let run (fn : net:#Eio.Net.t -> Switch.t -> unit) =
Eio_main.run @@ fun env -> Eio_main.run @@ fun env ->
let net = Eio.Stdenv.net env in let net = Eio.Stdenv.net env in
Switch.run (fn ~net) Switch.run (fn ~net)
@ -361,8 +361,8 @@ Wrapping a Unix FD as an Eio stream socket:
# Eio_main.run @@ fun _ -> # Eio_main.run @@ fun _ ->
Switch.run @@ fun sw -> Switch.run @@ fun sw ->
let r, w = Unix.pipe () in let r, w = Unix.pipe () in
let source = (Eio_unix.Net.import_socket_stream ~sw ~close_unix:true r :> _ Eio.Flow.source) in let source = (Eio_unix.Net.import_socket_stream ~sw ~close_unix:true r :> Eio.Flow.source) in
let sink = (Eio_unix.Net.import_socket_stream ~sw ~close_unix:true w :> _ Eio.Flow.sink) in let sink = (Eio_unix.Net.import_socket_stream ~sw ~close_unix:true w :> Eio.Flow.sink) in
Fiber.both Fiber.both
(fun () -> Eio.Flow.copy_string "Hello\n!" sink) (fun () -> Eio.Flow.copy_string "Hello\n!" sink)
(fun () -> (fun () ->
@ -470,8 +470,8 @@ Exception: Failure "Simulated error".
# Eio_main.run @@ fun _ -> # Eio_main.run @@ fun _ ->
Switch.run @@ fun sw -> Switch.run @@ fun sw ->
let a, b = Eio_unix.Net.socketpair_stream ~sw () in let a, b = Eio_unix.Net.socketpair_stream ~sw () in
ignore (Eio_unix.Net.fd a : Eio_unix.Fd.t); ignore (Eio_unix.Resource.fd a : Eio_unix.Fd.t);
ignore (Eio_unix.Net.fd b : Eio_unix.Fd.t); ignore (Eio_unix.Resource.fd b : Eio_unix.Fd.t);
Eio.Flow.copy_string "foo" a; Eio.Flow.copy_string "foo" a;
Eio.Flow.close a; Eio.Flow.close a;
let msg = Eio.Buf_read.of_flow b ~max_size:10 |> Eio.Buf_read.take_all in let msg = Eio.Buf_read.of_flow b ~max_size:10 |> Eio.Buf_read.take_all in
@ -746,8 +746,8 @@ Eio.Io Net Connection_failure Timeout,
# Eio_main.run @@ fun _ -> # Eio_main.run @@ fun _ ->
Switch.run @@ fun sw -> Switch.run @@ fun sw ->
let a, b = Eio_unix.Net.socketpair_datagram ~sw ~domain:Unix.PF_UNIX () in let a, b = Eio_unix.Net.socketpair_datagram ~sw ~domain:Unix.PF_UNIX () in
ignore (Eio_unix.Net.fd a : Eio_unix.Fd.t); ignore (Eio_unix.Resource.fd a : Eio_unix.Fd.t);
ignore (Eio_unix.Net.fd b : Eio_unix.Fd.t); ignore (Eio_unix.Resource.fd b : Eio_unix.Fd.t);
let l = [ "foo"; "bar"; "foobar"; "cellar door"; "" ] in let l = [ "foo"; "bar"; "foobar"; "cellar door"; "" ] in
let buf = Cstruct.create 32 in let buf = Cstruct.create 32 in
let write bufs = Eio.Net.send a (List.map Cstruct.of_string bufs) in let write bufs = Eio.Net.send a (List.map Cstruct.of_string bufs) in
@ -998,18 +998,3 @@ Limiting to 2 concurrent connections:
+flow3: closed +flow3: closed
- : unit = () - : unit = ()
``` ```
We keep the polymorphism when using a Unix network:
```ocaml
let _check_types ~(net:Eio_unix.Net.t) =
Switch.run @@ fun sw ->
let addr = `Unix "/socket" in
let server : [`Generic | `Unix] Eio.Net.listening_socket_ty r =
Eio.Net.listen ~sw net addr ~backlog:5
in
Eio.Net.accept_fork ~sw ~on_error:raise server
(fun (_flow : [`Generic | `Unix] Eio.Net.stream_socket_ty r) _addr -> assert false);
let _client : [`Generic | `Unix] Eio.Net.stream_socket_ty r = Eio.Net.connect ~sw net addr in
();;
```