-
Notifications
You must be signed in to change notification settings - Fork 71
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
10 changed files
with
410 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
(tests | ||
(package eio) | ||
(libraries cstruct crowbar fmt eio eio.mock) | ||
(names fuzz_buf_read fuzz_buf_write)) | ||
(libraries cstruct crowbar fmt eio eio.mock eio.unix) | ||
(names fuzz_buf_read fuzz_buf_write fuzz_inherit_fds)) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
module I = Eio_unix__Inherit_fds | ||
|
||
module S = Set.Make(Int) | ||
|
||
let pp f = function | ||
| `Cloexec x -> Fmt.pf f "close %d" x | ||
| `Keep x -> Fmt.pf f "keep %d" x | ||
|
||
let rec has_duplicates ~seen = function | ||
| [] -> false | ||
| (dst, _) :: _ when S.mem dst seen -> true | ||
| (dst, _) :: xs -> has_duplicates xs ~seen:(S.add dst seen) | ||
|
||
let inherit_fds mapping = | ||
let has_duplicates = has_duplicates ~seen:S.empty mapping in | ||
let fds = Hashtbl.create 10 in | ||
mapping |> List.iter (fun (_dst, src) -> | ||
Hashtbl.add fds src (`Cloexec src); | ||
); | ||
match I.plan mapping with | ||
| exception (Invalid_argument _) -> assert has_duplicates | ||
| plan -> | ||
assert (not has_duplicates); | ||
plan |> List.iter (fun {I.src; dst} -> | ||
(* Fmt.pr "%d -> %d@." src dst; *) | ||
let v = | ||
match Hashtbl.find fds src with | ||
| `Cloexec x | `Keep x -> | ||
if dst = -1 then `Cloexec x else `Keep x | ||
in | ||
Hashtbl.add fds dst v | ||
); | ||
mapping |> List.iter (fun (dst, src) -> | ||
let v = Hashtbl.find fds dst in | ||
Crowbar.check_eq ~pp v (`Keep src); | ||
Hashtbl.remove fds dst; | ||
); | ||
fds |> Hashtbl.iter (fun x -> function | ||
| `Cloexec _ -> () | ||
| `Keep _ -> Fmt.failwith "%d should be close-on-exec!" x | ||
) | ||
|
||
let fd = Crowbar.range 10 (* Restrict range to make cycles more likely *) | ||
|
||
let () = | ||
Crowbar.(add_test ~name:"inherit_fds" [list (pair fd fd)] inherit_fds) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
(* | ||
* Copyright (C) 2023 Thomas Leonard | ||
* | ||
* Permission to use, copy, modify, and distribute this software for any | ||
* purpose with or without fee is hereby granted, provided that the above | ||
* copyright notice and this permission notice appear in all copies. | ||
* | ||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES | ||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF | ||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR | ||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES | ||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN | ||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF | ||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. | ||
*) | ||
|
||
module M = Map.Make(Int) | ||
|
||
module Count = struct | ||
let create () = ref M.empty | ||
|
||
let get t fd = | ||
M.find_opt fd !t | ||
|> Option.value ~default:0 | ||
|
||
let incr t fd = | ||
let inc x = Some (1 + Option.value x ~default:0) in | ||
t := M.update fd inc !t | ||
|
||
let decr t fd = | ||
match get t fd with | ||
| i when i <= 0 -> assert false | ||
| 1 -> t := M.remove fd !t; `Unused | ||
| i -> t := M.add fd (pred i) !t; `Still_needed | ||
end | ||
|
||
type action = { src : int; dst : int } | ||
|
||
let plan mapping = | ||
let mapping = | ||
List.fold_left (fun acc (dst, src) -> | ||
if M.mem dst acc then Fmt.invalid_arg "FD %d assigned twice!" dst; | ||
M.add dst src acc | ||
) M.empty mapping | ||
in | ||
let plan = ref [] in | ||
let dup2 src dst = plan := {src; dst} :: !plan in | ||
let users_of = Count.create () in | ||
(* First, for any FDs that map to themselves we emit (fd, fd) and then forget about it, | ||
as this doesn't interfere with anything else. | ||
We also set [users_of] to track how many times each FD is needed. *) | ||
let mapping = mapping |> M.filter (fun dst src -> | ||
if src = dst then (dup2 src src; false) (* Just clear the close-on-exec flag. *) | ||
else (Count.incr users_of src; true) | ||
) in | ||
let tmp = ref (-1) in (* The FD we dup'd to the temporary FD when breaking cycles. *) | ||
let rec no_users dst = | ||
(* Nothing requires the old value of [dst] now, | ||
so if we wanted to put something there, do it. *) | ||
M.find_opt dst mapping |> Option.iter (fun src -> dup src dst) | ||
and dup src dst = | ||
(* Duplicate [src] as [dst]. *) | ||
if src = !tmp then ( | ||
(* We moved [src] to [tmp] to break a cycle, so use [tmp] instead. | ||
Also, there's nothing to do after this as the cycle is broken. *) | ||
dup2 (-1) dst; | ||
) else ( | ||
dup2 src dst; | ||
(* Record that [dst] no longer depends on [src]. *) | ||
match Count.decr users_of src with | ||
| `Still_needed -> () | ||
| `Unused -> no_users src | ||
) | ||
in | ||
(* Find any loose ends and work backwards. | ||
Note: we need to do this in two steps because [dup] modifies [users_of]. *) | ||
mapping | ||
|> M.filter (fun dst _src -> Count.get users_of dst = 0) (* FDs with no dependants *) | ||
|> M.iter (fun dst src -> dup src dst); | ||
(* At this point there are no loose ends; we have nothing but cycles left. *) | ||
(* M.iter (fun _ v -> assert (v = 1)) !users_of; *) | ||
(* For each cycle, break it at one point using the temporary FD. | ||
It's safe to allocate the temporary FD now because every FD we plan to use is already allocated. *) | ||
let rec break_cycles () = | ||
match M.min_binding_opt !users_of with (* Pick any remaining FD. *) | ||
| None -> () | ||
| Some (src, _) -> | ||
dup2 src (-1); (* Duplicate [src] somewhere. *) | ||
tmp := src; (* Remember that when we try to use it later. *) | ||
(* The FD that needed [src] can now use [tmp] instead: *) | ||
let state = Count.decr users_of src in | ||
assert (state = `Unused); | ||
no_users src; (* Free this cycle. *) | ||
break_cycles () (* Free any other cycles. *) | ||
in | ||
break_cycles (); | ||
List.rev !plan |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
(** Plan how to renumber FDs in a child process. *) | ||
|
||
type action = { src : int; dst : int } | ||
(** { src; dst} is (roughly) a request to [dup2(src, dst)]. | ||
[dst] should not be marked as close-on-exec. | ||
If [src = dst] then simply clear the close-on-exec flag for the FD. | ||
An FD of -1 means to use a temporary FD (e.g. use [dup] the first time, | ||
with close-on-exec true). This is needed if there are cycles (e.g. we want | ||
to switch FDs 1 and 2). Only one temporary FD is needed at a time, so it | ||
can be reused as necessary. *) | ||
|
||
val plan : (int * int) list -> action list | ||
(** [plan mapping] calculates a sequence of operations to renumber file descriptors so that | ||
FD x afterwards refers to the object that [List.assoc mapping x] referred to at the start. | ||
It returns a list of actions to be performed in sequence. | ||
Example: [plan [1, 2]] is just [[(2, 1)]]. *) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.