Skip to content

Commit f37f37d

Browse files
authored
Merge pull request #603 from talex5/native-path
Add Eio.Path.native
2 parents e948fa7 + 31629a1 commit f37f37d

File tree

7 files changed

+114
-6
lines changed

7 files changed

+114
-6
lines changed

lib_eio/fs.ml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ type error =
99
| Not_found of Exn.Backend.t
1010
| Permission_denied of Exn.Backend.t
1111
| File_too_large
12+
| Not_native of string (** Raised by {!Path.native_exn}. *)
1213

1314
type Exn.err += E of error
1415

@@ -24,6 +25,7 @@ let () =
2425
| Not_found e -> Fmt.pf f "Not_found %a" Exn.Backend.pp e
2526
| Permission_denied e -> Fmt.pf f "Permission_denied %a" Exn.Backend.pp e
2627
| File_too_large -> Fmt.pf f "File_too_large"
28+
| Not_native m -> Fmt.pf f "Not_native %S" m
2729
end;
2830
true
2931
| _ -> false
@@ -62,6 +64,7 @@ module Pi = struct
6264
val rmdir : t -> path -> unit
6365
val rename : t -> path -> _ dir -> path -> unit
6466
val pp : t Fmt.t
67+
val native : t -> string -> string option
6568
end
6669

6770
type (_, _, _) Resource.pi +=

lib_eio/path.ml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,15 @@ let pp f (Resource.T (t, ops), p) =
1212
if p = "" then Fmt.pf f "<%a>" X.pp t
1313
else Fmt.pf f "<%a:%s>" X.pp t (String.escaped p)
1414

15+
let native (Resource.T (t, ops), p) =
16+
let module X = (val (Resource.get ops Fs.Pi.Dir)) in
17+
X.native t p
18+
19+
let native_exn t =
20+
match native t with
21+
| Some p -> p
22+
| None -> raise (Fs.err (Not_native (Fmt.str "%a" pp t)))
23+
1524
let open_in ~sw t =
1625
let (Resource.T (dir, ops), path) = t in
1726
let module X = (val (Resource.get ops Fs.Pi.Dir)) in

lib_eio/path.mli

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@
2323
{[
2424
Eio.Path.load (fs / "/etc/passwd")
2525
]}
26+
27+
In Eio, the directory separator is always "/", even on Windows.
28+
Use {!native} to convert to a native path.
2629
*)
2730

2831
open Std
@@ -41,6 +44,23 @@ val ( / ) : 'a t -> string -> 'a t
4144
val pp : _ t Fmt.t
4245
(** [pp] formats a [_ t] as "<label:path>", suitable for logging. *)
4346

47+
val native : _ t -> string option
48+
(** [native t] returns a path that can be used to refer to [t] with the host
49+
platform's native string-based file-system APIs, if available.
50+
This is intended for interoperability with non-Eio libraries.
51+
52+
This does not check for confinement (the resulting path might not be accessible
53+
via [t] itself). Also, if a directory was opened with {!open_dir} and later
54+
renamed, this might use the old name.
55+
56+
Using strings as paths is not secure if components in the path can be
57+
replaced by symlinks while the path is being used. For example, if you
58+
try to write to "/home/mal/output.txt" just as mal replaces "output.txt"
59+
with a symlink to "/etc/passwd". *)
60+
61+
val native_exn : _ t -> string
62+
(** Like {!native}, but raise a suitable exception if the path is not a native path. *)
63+
4464
(** {1 Reading files} *)
4565

4666
val load : _ t -> string

lib_eio_linux/eio_linux.ml

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -424,7 +424,7 @@ end
424424
module rec Dir : sig
425425
include Eio.Fs.Pi.DIR
426426

427-
val v : label:string -> Low_level.dir_fd -> t
427+
val v : label:string -> path:string -> Low_level.dir_fd -> t
428428

429429
val close : t -> unit
430430

@@ -433,9 +433,10 @@ end = struct
433433
type t = {
434434
fd : Low_level.dir_fd;
435435
label : string;
436+
path : string;
436437
}
437438

438-
let v ~label fd = { fd; label }
439+
let v ~label ~path fd = { fd; label; path }
439440

440441
let open_in t ~sw path =
441442
let fd = Low_level.openat ~sw t.fd path
@@ -461,14 +462,23 @@ end = struct
461462
in
462463
(flow fd :> Eio.File.rw_ty r)
463464

465+
let native_internal t path =
466+
if Filename.is_relative path then (
467+
let p = Filename.concat t.path path in
468+
if p = "" then "."
469+
else if p = "." then p
470+
else if Filename.is_implicit p then "./" ^ p
471+
else p
472+
) else path
473+
464474
let open_dir t ~sw path =
465475
let fd = Low_level.openat ~sw ~seekable:false t.fd (if path = "" then "." else path)
466476
~access:`R
467477
~flags:Uring.Open_flags.(cloexec + path + directory)
468478
~perm:0
469479
in
470480
let label = Filename.basename path in
471-
let d = v ~label (Low_level.FD fd) in
481+
let d = v ~label ~path:(native_internal t path) (Low_level.FD fd) in
472482
Eio.Resource.T (d, Dir_handler.v)
473483

474484
let mkdir t ~perm path = Low_level.mkdir_beneath ~perm t.fd path
@@ -494,6 +504,9 @@ end = struct
494504
let pp f t = Fmt.string f (String.escaped t.label)
495505

496506
let fd t = t.fd
507+
508+
let native t path =
509+
Some (native_internal t path)
497510
end
498511
and Dir_handler : sig
499512
val v : (Dir.t, [`Dir | `Close]) Eio.Resource.handler
@@ -505,7 +518,7 @@ end = struct
505518
]
506519
end
507520

508-
let dir ~label fd = Eio.Resource.T (Dir.v ~label fd, Dir_handler.v)
521+
let dir ~label ~path fd = Eio.Resource.T (Dir.v ~label ~path fd, Dir_handler.v)
509522

510523
module Secure_random = struct
511524
type t = unit
@@ -521,8 +534,8 @@ let stdenv ~run_event_loop =
521534
let stdin = source Eio_unix.Fd.stdin in
522535
let stdout = sink Eio_unix.Fd.stdout in
523536
let stderr = sink Eio_unix.Fd.stderr in
524-
let fs = (dir ~label:"fs" Fs, "") in
525-
let cwd = (dir ~label:"cwd" Cwd, "") in
537+
let fs = (dir ~label:"fs" ~path:"" Fs, "") in
538+
let cwd = (dir ~label:"cwd" ~path:"" Cwd, "") in
526539
object (_ : stdenv)
527540
method stdin = stdin
528541
method stdout = stdout

lib_eio_posix/fs.ml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,21 @@ end = struct
161161
Eio.Resource.T (d, Handler.v)
162162

163163
let pp f t = Fmt.string f (String.escaped t.label)
164+
165+
let native_internal t path =
166+
if Filename.is_relative path then (
167+
let p =
168+
if t.dir_path = "." then path
169+
else Filename.concat t.dir_path path
170+
in
171+
if p = "" then "."
172+
else if p = "." then p
173+
else if Filename.is_implicit p then "./" ^ p
174+
else p
175+
) else path
176+
177+
let native t path =
178+
Some (native_internal t path)
164179
end
165180
and Handler : sig
166181
val v : (Dir.t, [`Dir | `Close]) Eio.Resource.handler

lib_eio_windows/fs.ml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,9 @@ end = struct
169169
Eio.Resource.T (d, Handler.v)
170170

171171
let pp f t = Fmt.string f (String.escaped t.label)
172+
173+
let native _t _path =
174+
failwith "TODO: Windows native"
172175
end
173176
and Handler : sig
174177
val v : (Dir.t, [`Dir | `Close]) Eio.Resource.handler

tests/fs.md

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -588,3 +588,48 @@ Ensure reads can be cancelled promptly, even if there is no need to wait:
588588
(fun () -> failwith "Simulated error");;
589589
Exception: Failure "Simulated error".
590590
```
591+
592+
# Native paths
593+
594+
```ocaml
595+
# Eio_main.run @@ fun env ->
596+
let cwd = Sys.getcwd () ^ "/" in
597+
let test x =
598+
let native = Eio.Path.native x in
599+
let result =
600+
native |> Option.map @@ fun native ->
601+
if String.starts_with ~prefix:cwd native then
602+
"./" ^ String.sub native (String.length cwd) (String.length native - String.length cwd)
603+
else native
604+
in
605+
traceln "%a -> %a" Eio.Path.pp x Fmt.(Dump.option string) result
606+
in
607+
test env#fs;
608+
test (env#fs / "/");
609+
test (env#fs / "/etc/hosts");
610+
test (env#fs / ".");
611+
test (env#fs / "foo/bar");
612+
test env#cwd;
613+
test (env#cwd / "..");
614+
let sub = env#cwd / "native-sub" in
615+
Eio.Path.mkdir sub ~perm:0o700;
616+
Eio.Path.with_open_dir sub @@ fun sub ->
617+
test sub;
618+
test (sub / "foo.txt");
619+
test (sub / ".");
620+
test (sub / "..");
621+
test (sub / "/etc/passwd");
622+
+<fs> -> Some .
623+
+<fs:/> -> Some /
624+
+<fs:/etc/hosts> -> Some /etc/hosts
625+
+<fs:.> -> Some .
626+
+<fs:foo/bar> -> Some ./foo/bar
627+
+<cwd> -> Some .
628+
+<cwd:..> -> Some ./..
629+
+<native-sub> -> Some ./native-sub/
630+
+<native-sub:foo.txt> -> Some ./native-sub/foo.txt
631+
+<native-sub:.> -> Some ./native-sub/.
632+
+<native-sub:..> -> Some ./native-sub/..
633+
+<native-sub:/etc/passwd> -> Some /etc/passwd
634+
- : unit = ()
635+
```

0 commit comments

Comments
 (0)