From 9afc65a0ed76cd074450e91d0003ce74da114a53 Mon Sep 17 00:00:00 2001 From: Thomas Leonard Date: Mon, 26 Feb 2024 18:18:16 +0000 Subject: [PATCH] eio_linux: Expose more functions in Low_level module Add all the functions used by other parts of eio_linux (`openat`, `mkdir`, `read_link`, `unlink`, `rename` and `pipe`). Tidied the API up a bit too: - `mkdir_beneath` is now just `mkdir`. - `statx_confined` is now just `statx`. - `open_dir` is gone; the single user now calls `openat` directly. --- lib_eio_linux/eio_linux.ml | 13 +++++++--- lib_eio_linux/eio_linux.mli | 50 +++++++++++++++++++++++++++++++++---- lib_eio_linux/low_level.ml | 21 ++++++---------- lib_eio_linux/tests/test.ml | 10 ++++---- 4 files changed, 68 insertions(+), 26 deletions(-) diff --git a/lib_eio_linux/eio_linux.ml b/lib_eio_linux/eio_linux.ml index 26390b450..75aacd6d5 100644 --- a/lib_eio_linux/eio_linux.ml +++ b/lib_eio_linux/eio_linux.ml @@ -532,11 +532,18 @@ end = struct let d = v ~label ~path:(native_internal t path) (Low_level.FD fd) in Eio.Resource.T (d, Dir_handler.v) - let mkdir t ~perm path = Low_level.mkdir_beneath ~perm t.fd path + let mkdir t ~perm path = Low_level.mkdir ~perm t.fd path let read_dir t path = Switch.run ~name:"read_dir" @@ fun sw -> - let fd = Low_level.open_dir ~sw t.fd (if path = "" then "." else path) in + let path = if path = "" then "." else path in + let fd = + Low_level.openat ~sw t.fd path + ~seekable:false + ~access:`R + ~flags:Uring.Open_flags.(cloexec + directory) + ~perm:0 + in Low_level.read_dir fd let read_link t path = Low_level.read_link t.fd path @@ -562,7 +569,7 @@ end = struct if !Sched.statx_works then ( let module X = Uring.Statx in let x = X.create () in - Low_level.statx_confined ~follow ~mask:X.Mask.basic_stats t.fd path x; + Low_level.statx ~follow ~mask:X.Mask.basic_stats t.fd path x; { Eio.File.Stat. dev = X.dev x; ino = X.ino x; diff --git a/lib_eio_linux/eio_linux.mli b/lib_eio_linux/eio_linux.mli index aa8cfa3d1..ef5d53988 100644 --- a/lib_eio_linux/eio_linux.mli +++ b/lib_eio_linux/eio_linux.mli @@ -55,6 +55,11 @@ val run : (** Low-level API for using uring directly. *) module Low_level : sig + type dir_fd = + | FD of fd + | Cwd (** Confined to "." *) + | Fs (** Unconfined "."; also allows absolute paths *) + val noop : unit -> unit (** [noop ()] performs a uring noop. This is only useful for benchmarking. *) @@ -88,6 +93,16 @@ module Low_level : sig (** {1 File manipulation functions} *) + val openat : + sw:Switch.t -> + ?seekable:bool -> + access:[`R|`W|`RW] -> + flags:Uring.Open_flags.t -> + perm:Unix.file_perm -> + dir_fd -> string -> + fd + (** [openat ~sw ~access ~flags ~perm dir path] opens [dir/path]. *) + val openat2 : sw:Switch.t -> ?seekable:bool -> @@ -96,8 +111,9 @@ module Low_level : sig perm:Unix.file_perm -> resolve:Uring.Resolve.t -> ?dir:fd -> string -> fd - (** [openat2 ~sw ~flags ~perm ~resolve ~dir path] opens [dir/path]. + (** [openat2 ~sw ~access ~flags ~perm ~resolve ~dir path] opens [dir/path]. + It provides full access to the resolve flags. See {!Uring.openat2} for details. *) val read_upto : ?file_offset:Optint.Int63.t -> fd -> Uring.Region.chunk -> int -> int @@ -156,11 +172,35 @@ module Low_level : sig val fstat : fd -> Eio.File.Stat.t (** Like {!Unix.LargeFile.fstat}. *) - val statx : ?fd:fd -> mask:Uring.Statx.Mask.t -> string -> Uring.Statx.t -> Uring.Statx.Flags.t -> unit - (** [statx t ?fd ~mask path buf flags] stats [path], which is resolved relative to [fd] - (or the current directory if [fd] is not given). + val statx : + mask:Uring.Statx.Mask.t -> + follow:bool -> + dir_fd -> string -> + Uring.Statx.t -> + unit + (** [statx ~mask ~follow dir path buf] stats [dir / path]. + + The results are written to [buf]. + If [follow = true] and the item is a symlink, information is reported about the target of the link. + Otherwise, information about the symlink itself is returned. *) + + val mkdir : perm:int -> dir_fd -> string -> unit + (** [mkdir ~perm dir path] creates directory [dir / path]. *) + + val read_link : dir_fd -> string -> string + (** [read_link dir path] reads the target of symlink [dir / path]. *) + + val unlink : rmdir:bool -> dir_fd -> string -> unit + (** [unlink ~rmdir dir path] removes directory entry [dir / path]. + + If [rmdir = true] then the target must be a directory. + Otherwise, it must not be a directory. *) + + val rename : dir_fd -> string -> dir_fd -> string -> unit + (** [rename old_dir old_path new_dir new_path] renames [old_dir / old_path] as [new_dir / new_path]. *) - The results are written to [buf]. *) + val pipe : sw:Switch.t -> fd * fd + (** [pipe ~sw] returns a pair [r, w] with the readable and writeable ends of a new pipe. *) val read_dir : fd -> string list (** [read_dir dir] reads all directory entries from [dir]. diff --git a/lib_eio_linux/low_level.ml b/lib_eio_linux/low_level.ml index 9ba42b62f..16b36dad6 100644 --- a/lib_eio_linux/low_level.ml +++ b/lib_eio_linux/low_level.ml @@ -401,7 +401,7 @@ let with_parent_dir op dir path fn = Fd.use_exn op parent @@ fun parent -> fn parent leaf -let statx ?fd ~mask path buf flags = +let statx_raw ?fd ~mask path buf flags = let res = match fd with | None -> Sched.enter "statx" (enqueue_statx (None, path, buf, flags, mask)) @@ -411,14 +411,15 @@ let statx ?fd ~mask path buf flags = in if res <> 0 then raise @@ Err.wrap_fs (Uring.error_of_errno res) "statx" path -let statx_confined ~mask ~follow fd path buf = +let statx ~mask ~follow fd path buf = let module X = Uring.Statx in - let flags = if follow then X.Flags.empty else X.Flags.symlink_nofollow in + let flags = if follow then X.Flags.empty_path else X.Flags.(empty_path + symlink_nofollow) in match fd with - | Fs -> statx ~mask path buf flags + | Fs -> statx_raw ~mask path buf flags + | FD fd when path = "" -> statx_raw ~fd ~mask "" buf flags | Cwd | FD _ when not follow -> with_parent_dir_fd fd path @@ fun parent leaf -> - statx ~mask ~fd:parent leaf buf flags + statx_raw ~mask ~fd:parent leaf buf flags | Cwd | FD _ -> Switch.run ~name:"statx" @@ fun sw -> let fd = openat ~sw ~seekable:false fd (if path = "" then "." else path) @@ -426,9 +427,9 @@ let statx_confined ~mask ~follow fd path buf = ~flags:Uring.Open_flags.(cloexec + path) ~perm:0 in - statx ~fd ~mask "" buf Uring.Statx.Flags.(flags + empty_path) + statx_raw ~fd ~mask "" buf flags -let mkdir_beneath ~perm dir path = +let mkdir ~perm dir path = (* [mkdir] is really an operation on [path]'s parent. Get a reference to that first: *) with_parent_dir "mkdir" dir path @@ fun parent leaf -> try eio_mkdirat parent leaf perm @@ -470,12 +471,6 @@ let accept ~sw fd = client, client_addr ) -let open_dir ~sw dir path = - openat ~sw ~seekable:false dir path - ~access:`R - ~flags:Uring.Open_flags.(cloexec + directory) - ~perm:0 - let read_dir fd = Fd.use_exn "read_dir" fd @@ fun fd -> let rec read_all acc fd = diff --git a/lib_eio_linux/tests/test.ml b/lib_eio_linux/tests/test.ml index bc7fb6bd3..0a5f9c82d 100644 --- a/lib_eio_linux/tests/test.ml +++ b/lib_eio_linux/tests/test.ml @@ -165,13 +165,13 @@ let test_statx () = Eio.Path.with_open_out path ~create:(`Exclusive 0o600) @@ fun file -> Eio.Flow.copy_string "hello" file; let buf = Uring.Statx.create () in - let test expected_len ~flags ?fd path = - Eio_linux.Low_level.statx ~mask:X.Mask.(type' + size) ?fd path buf flags; + let test expected_len ~follow dir path = + Eio_linux.Low_level.statx ~follow ~mask:X.Mask.(type' + size) dir path buf; Alcotest.check kind_t "kind" `Regular_file (Uring.Statx.kind buf); Alcotest.(check int64) "size" expected_len (Uring.Statx.size buf) in (* Lookup via cwd *) - test 5L ~flags:X.Flags.empty ?fd:None "test2.data"; + test 5L ~follow:false Cwd "test2.data"; Eio.Flow.copy_string "+" file; (* Lookup via file FD *) Switch.run (fun sw -> @@ -182,7 +182,7 @@ let test_statx () = ~resolve:Uring.Resolve.empty "test2.data" in - test 6L ~flags:X.Flags.empty_path ~fd "" + test 6L ~follow:false (FD fd) "" ); (* Lookup via directory FD *) Eio.Flow.copy_string "+" file; @@ -194,7 +194,7 @@ let test_statx () = ~resolve:Uring.Resolve.empty "." in - test 7L ~flags:X.Flags.empty_path ~fd "test2.data" + test 7L ~follow:false (FD fd) "test2.data" ); ()