diff --git a/example/main.ml b/example/main.ml index a2e60e5..23675be 100644 --- a/example/main.ml +++ b/example/main.ml @@ -15,6 +15,12 @@ let try_read_file path = | s -> traceln "read %a -> %S" Path.pp path s | exception ex -> traceln "@[%a@]" Eio.Exn.pp ex +let try_read_dir path = + match Path.read_dir path with + | names -> + traceln "read_dir %a -> %a" Path.pp path Fmt.Dump.(list string) names + | exception ex -> traceln "@[%a@]" Eio.Exn.pp ex + let try_write_file ~create ?append path content = match Path.save ~create ?append path content with | () -> traceln "write %a -> ok" Path.pp path @@ -79,14 +85,19 @@ let try_symlink ~link_to path = | exception ex -> traceln "@[%a@]" Eio.Exn.pp ex let () = - run ~clear:[ "to-subdir"; "to-root"; "dangle" ] @@ fun env -> + run ~clear:[ "d1"; "subdir/d2"; "subdir/d3" ] @@ fun env -> + Switch.run @@ fun sw -> let cwd = Eio.Stdenv.cwd env in - Path.symlink ~link_to:"/" (cwd / "to-root"); Path.symlink ~link_to:"subdir" (cwd / "to-subdir"); - Path.symlink ~link_to:"foo" (cwd / "dangle"); + try_mkdir (cwd / "d1"); try_mkdir (cwd / "subdir"); - try_mkdir (cwd / "to-subdir/nested"); - try_mkdir (cwd / "to-root/tmp/foo"); - try_mkdir (cwd / "../foo"); - try_mkdir (cwd / "to-subdir"); - try_mkdir (cwd / "dangle/foo") + try_mkdir (cwd / "subdir/d2"); + try_read_dir (cwd / "d1"); + try_read_dir (cwd / "subdir/d2"); + try_rmdir (cwd / "d1"); + try_rmdir (cwd / "subdir/d2"); + try_read_dir (cwd / "d1"); + try_read_dir (cwd / "subdir/d2"); + try_mkdir (cwd / "subdir/d3"); + try_rmdir (cwd / "to-subdir/d3"); + try_read_dir (cwd / "subdir/d3") diff --git a/src/eio_mem.ml b/src/eio_mem.ml index de6506f..6232313 100644 --- a/src/eio_mem.ml +++ b/src/eio_mem.ml @@ -69,12 +69,14 @@ module Fs = struct let mkdir t ~perm path = Low_level.mkdirat ~perm t.fd path let unlink t path = Low_level.unlink t.fd path - let rmdir _ _ = Fmt.failwith "TODO: rmdir" + let rmdir t path = Low_level.rmdir t.fd path let stat t ~follow path = Low_level.statat ~follow t.fd path let read_dir t path = Low_level.read_dir t.fd path - let with_dir_entries _ _ _ = Fmt.failwith "TODO: with_dir_entries" let read_link t path = Low_level.read_link t.fd path - let rename _ _ _ _ = Fmt.failwith "TODO: rename" + + let rename t old_path new_dir new_path = + Low_level.rename t.fd old_path new_dir new_path + let symlink ~link_to t path = Low_level.symlinkat ~link_to t.fd path let open_dir t ~sw path = diff --git a/src/low_level.ml b/src/low_level.ml index 6e10f09..202cd08 100644 --- a/src/low_level.ml +++ b/src/low_level.ml @@ -82,6 +82,8 @@ module Inode = struct type t = int64 let compare = Int64.compare + let equal = Int64.equal + let pp = Fmt.int64 let fresh = let i = ref 0L in @@ -97,6 +99,8 @@ module Dir = struct let make ~parent name stat = { name; stat; entries = [ (stat.ino, "."); (parent, "..") ] } + let is_empty dir = List.length dir.entries = 2 + let parent dir = List.find (function _, ".." -> true | _ -> false) dir.entries end @@ -156,9 +160,9 @@ end type resource = Resource.t -let get_dir = function +let get_dir ~label = function | Resource.Dir dir -> dir - | r -> Fmt.invalid_arg "get_dir: %a" Resource.pp r + | r -> Fmt.invalid_arg "get_dir(%s): %a" label Resource.pp r let reset_file ~truncate ~append r = match !r with @@ -261,9 +265,10 @@ module Filesystem = struct if Int64.equal dir.stat.ino 0L then "/" ^ acc else let parent = + (* let ino_p, fname = Dir.parent dir in *) read_filesystem @@ Entries.find (Dir.parent dir |> fst) in - loop (get_dir !parent) (dir.name ^ "/" ^ acc) + loop (get_dir ~label:"absolute" !parent) (dir.name ^ "/" ^ acc) in loop dir "" @@ -277,10 +282,10 @@ module Filesystem = struct if is_confined ~root p then () else Error.permission_denied p let rec find_entry_relative_to_dir ~check ~follow fs dir (path : string) = - let confinement_dir = get_dir !dir in + let confinement_dir = get_dir ~label:"confinement-dir" !dir in let confinement_path = absolute_path confinement_dir in let check_confinement parent resource_name = - let parent_path = absolute_path (get_dir !parent) in + let parent_path = absolute_path (get_dir ~label:"confinement" !parent) in let path_of_next_item = if resource_name = "/" then resource_name else begin @@ -297,7 +302,9 @@ module Filesystem = struct if check then check_confinement dir s.link_to; find_entry_relative_to_dir ~check ~follow fs parent s.link_to | _ -> - let parent_path = absolute_path (get_dir !parent) in + let parent_path = + absolute_path (get_dir ~label:"find-loop" !parent) + in let resource_name = Resource.name !r in if resource_name = "/" then (resource_name, r) else begin @@ -306,12 +313,13 @@ module Filesystem = struct | "" :: segs -> loop ~parent r segs | seg :: segs -> ( if check then check_confinement dir seg; + let entries, resolved_dir = + entries_exn + ~check:(if check then Some (check_confinement dir) else None) + ~follow ~parent fs r + in match - List.find_opt - (fun (ino, name) -> String.equal seg name) - (entries_exn - ~check:(if check then Some (check_confinement dir) else None) - ~follow ~parent fs !r) + List.find_opt (fun (ino, name) -> String.equal seg name) entries with | None -> Error.not_found (String.concat "/" segs) | Some (ino, name) -> ( @@ -319,7 +327,7 @@ module Filesystem = struct | None -> Error.not_found (String.concat "/" segs) | Some n -> if check then check_confinement dir (Resource.name !n); - loop ~parent:r n segs)) + loop ~parent:resolved_dir n segs)) in let parent = match !dir with @@ -332,15 +340,17 @@ module Filesystem = struct loop ~parent dir (Path.segs path) and entries_exn ~check ~follow ~parent fs : - resource -> (Inode.t * string) list = function - | Resource.Dir dir -> dir.entries + resource ref -> (Inode.t * string) list * resource ref = + fun r -> + match !r with + | Resource.Dir dir -> (dir.entries, r) | Symlink sym -> Option.iter (fun f -> f sym.link_to) check; let _, r = find_entry_relative_to_dir ~check:(Option.is_some check) ~follow fs parent sym.link_to in - entries_exn ~check ~follow ~parent fs !r + entries_exn ~check ~follow ~parent fs r | r -> Fmt.failwith "Resource not a directory: %a" Resource.pp r let lookup ~follow dir_fd path = @@ -378,8 +388,11 @@ module Filesystem = struct find_entry_relative_to_dir ~check:true ~follow fs dir path in (* Check for confinement *) - if is_confined ~root:(absolute_path (get_dir !dir)) resource_path then - r + if + is_confined + ~root:(absolute_path (get_dir ~label:"lookup" !dir)) + resource_path + then r else Error.permission_denied resource_path end @@ -468,7 +481,7 @@ let unlink dir_fd path = let path = check_path path in let dir, base = dir_base path in let r = Filesystem.lookup ~follow:false dir_fd dir in - let d = get_dir !r in + let d = get_dir ~label:"unlink" !r in let entries = List.filter (fun (_, name) -> not (String.equal base name)) d.entries in @@ -478,7 +491,7 @@ let mkdirat dir_fd ~perm path = let path = check_path path in let mkdir r name = let new_stat = make_new_stat ~kind:`Directory ~perm () in - let d = get_dir !r in + let d = get_dir ~label:"mkdirat" !r in let dir = Dir.make ~parent:d.stat.ino name new_stat in let ino = new_stat.ino in Filesystem.add_file ino (ref (Resource.Dir dir)); @@ -497,7 +510,7 @@ let symlinkat ~link_to dir_fd path = let path = check_path path in let symlink r name = let new_stat = make_new_stat ~kind:`Symbolic_link ~perm:0o644 () in - let d = get_dir !r in + let d = get_dir ~label:"symlink" !r in let dir = Symlink.make ~link_to name new_stat in let ino = new_stat.ino in Filesystem.add_file ino (ref (Resource.Symlink dir)); @@ -524,6 +537,30 @@ let read_dir dir_fd path = |> List.filter (function "." | ".." -> false | _ -> true) | _ -> Error.err "Not a directory" +let rmdir dir_fd path = + let path = check_path path in + let r = Filesystem.lookup ~follow:true dir_fd path in + match !r with + | Resource.Dir d -> + if not (Dir.is_empty d) then Error.err "Directory is not empty" + else begin + (* TODO: need to delete from FD table? *) + let dir, base = dir_base path in + let parent = Filesystem.lookup ~follow:true dir_fd dir in + let r = get_dir ~label:"rmdir" !parent in + let entries = + List.filter + (fun (ino, _) -> not (Inode.equal d.stat.ino ino)) + r.entries + in + parent := Dir { r with entries } + end + | _ -> Error.err "Not a directory" + +let rename dir_fd old_path new_dir new_path = + ignore (dir_fd, old_path, new_dir, new_path); + failwith "TODO" + let stat fd = read_fd_table @@ fun fds -> match Fd_table.find_opt fd fds |> Option.map ( ! ) with diff --git a/test/fs.md b/test/fs.md index 1cb0683..97d3da3 100644 --- a/test/fs.md +++ b/test/fs.md @@ -4,7 +4,7 @@ module Int63 = Optint.Int63 module Path = Eio.Path -let () = Eio.Exn.Backend.show := false +let () = Eio.Exn.Backend.show := false open Eio.Std @@ -28,6 +28,11 @@ let try_write_file ~create ?append path content = | () -> traceln "write %a -> ok" Path.pp path | exception ex -> traceln "@[%a@]" Eio.Exn.pp ex +let try_read_dir path = + match Path.read_dir path with + | names -> traceln "read_dir %a -> %a" Path.pp path Fmt.Dump.(list string) names + | exception ex -> traceln "@[%a@]" Eio.Exn.pp ex + let try_mkdir path = match Path.mkdir path ~perm:0o700 with | () -> traceln "mkdir %a -> ok" Path.pp path @@ -200,3 +205,57 @@ Creating directories with nesting, symlinks, etc: +Eio.Io Fs Not_found _, creating directory - : unit = () ``` + +# Rmdir + +Similar to `unlink`, but works on directories: + +```ocaml +# run ~clear:["d1"; "subdir/d2"; "subdir/d3"] @@ fun env -> + Switch.run @@ fun sw -> + let cwd = Eio.Stdenv.cwd env in + Path.symlink ~link_to:"subdir" (cwd / "to-subdir"); + try_mkdir (cwd / "d1"); + try_mkdir (cwd / "subdir"); + try_mkdir (cwd / "subdir/d2"); + try_read_dir (cwd / "d1"); + try_read_dir (cwd / "subdir/d2"); + try_rmdir (cwd / "d1"); + try_rmdir (cwd / "subdir/d2"); + try_read_dir (cwd / "d1"); + try_read_dir (cwd / "subdir/d2"); + try_mkdir (cwd / "subdir/d3"); + try_rmdir (cwd / "to-subdir/d3"); + try_read_dir (cwd / "subdir/d3");; ++mkdir -> ok ++mkdir -> ok ++mkdir -> ok ++read_dir -> [] ++read_dir -> [] ++rmdir -> ok ++rmdir -> ok ++Eio.Io Fs Not_found _, reading directory ++Eio.Io Fs Not_found _, reading directory ++mkdir -> ok ++rmdir -> ok ++Eio.Io Fs Not_found _, reading directory +- : unit = () +``` + +Removing something that doesn't exist or is out of scope: + +```ocaml +# run @@ fun env -> + Switch.run @@ fun sw -> + let cwd = Eio.Stdenv.cwd env in + Path.symlink ~link_to:"/" (cwd / "to-root"); + try_rmdir (cwd / "missing"); + try_rmdir (cwd / "../foo"); + try_rmdir (cwd / "to-subdir/foo"); + try_rmdir (cwd / "to-root/foo");; ++Eio.Io Fs Not_found _, removing directory ++Eio.Io Fs Permission_denied _, removing directory ++Eio.Io Fs Not_found _, removing directory ++Eio.Io Fs Permission_denied _, removing directory +- : unit = () +```