diff --git a/lib_eio/file.ml b/lib_eio/file.ml index 3353a714d..1b8e34179 100644 --- a/lib_eio/file.ml +++ b/lib_eio/file.ml @@ -72,6 +72,7 @@ module Pi = struct val pread : t -> file_offset:Optint.Int63.t -> Cstruct.t list -> int val stat : t -> Stat.t + val seek : t -> Optint.Int63.t -> [`Set | `Cur | `End] -> Optint.Int63.t val close : t -> unit end @@ -80,6 +81,8 @@ module Pi = struct include READ with type t := t val pwrite : t -> file_offset:Optint.Int63.t -> Cstruct.t list -> int + val sync : t -> unit + val truncate : t -> Optint.Int63.t -> unit end type (_, _, _) Resource.pi += @@ -140,3 +143,15 @@ let pwrite_all (Resource.T (t, ops)) ~file_offset bufs = ) in aux ~file_offset bufs + +let seek (Resource.T (t, ops)) off cmd = + let module X = (val (Resource.get ops Pi.Read)) in + X.seek t off cmd + +let sync (Resource.T (t, ops)) = + let module X = (val (Resource.get ops Pi.Write)) in + X.sync t + +let truncate (Resource.T (t, ops)) len = + let module X = (val (Resource.get ops Pi.Write)) in + X.truncate t len diff --git a/lib_eio/file.mli b/lib_eio/file.mli index 532c79f07..01bce61ae 100644 --- a/lib_eio/file.mli +++ b/lib_eio/file.mli @@ -1,5 +1,7 @@ open Std +(** {2 Types} *) + (** Traditional Unix permissions. *) module Unix_perm : sig type t = int @@ -54,30 +56,7 @@ 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 +(** {2 Metadata} *) val stat : _ ro -> Stat.t (** [stat t] returns the {!Stat.t} record associated with [t]. *) @@ -85,6 +64,8 @@ val stat : _ ro -> Stat.t val size : _ ro -> Optint.Int63.t (** [size t] returns the size of [t]. *) +(** {2 Reading and writing} *) + 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]. @@ -108,3 +89,48 @@ val pwrite_single : _ rw -> file_offset:Optint.Int63.t -> Cstruct.t list -> int 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]. *) + +val seek : _ ro -> Optint.Int63.t -> [`Set | `Cur | `End] -> Optint.Int63.t +(** Set and/or get the current file position. + + Like {!Unix.lseek}. *) + +val sync : _ rw -> unit +(** Flush file buffers to disk. + + Like {!Unix.fsync}. *) + +val truncate : _ rw -> Optint.Int63.t -> unit +(** Set the length of a file. + + Like {!Unix.ftruncate}. *) + +(** {2 Provider Interface} *) + +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 seek : t -> Optint.Int63.t -> [`Set | `Cur | `End] -> Optint.Int63.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 + val sync : t -> unit + val truncate : t -> Optint.Int63.t -> unit + 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 diff --git a/lib_eio_linux/eio_linux.ml b/lib_eio_linux/eio_linux.ml index aeb936bd9..93abbf218 100644 --- a/lib_eio_linux/eio_linux.ml +++ b/lib_eio_linux/eio_linux.ml @@ -192,6 +192,10 @@ module Flow = struct 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 + + let seek = Low_level.lseek + let sync = Low_level.fsync + let truncate = Low_level.ftruncate end let flow_handler = Eio_unix.Pi.flow_handler (module Flow) diff --git a/lib_eio_linux/eio_linux.mli b/lib_eio_linux/eio_linux.mli index 5c5202642..aa8cfa3d1 100644 --- a/lib_eio_linux/eio_linux.mli +++ b/lib_eio_linux/eio_linux.mli @@ -167,6 +167,21 @@ module Low_level : sig The entries are not returned in any particular order (not even necessarily the order in which Linux returns them). *) + val lseek : fd -> Optint.Int63.t -> [`Set | `Cur | `End] -> Optint.Int63.t + (** Set and/or get the current file position. + + Like {!Unix.lseek}. *) + + val fsync : fd -> unit + (** Flush file buffers to disk. + + Like {!Unix.fsync}. *) + + val ftruncate : fd -> Optint.Int63.t -> unit + (** Set the length of a file. + + Like {!Unix.ftruncate}. *) + (** {1 Sockets} *) val accept : sw:Switch.t -> fd -> (fd * Unix.sockaddr) diff --git a/lib_eio_linux/low_level.ml b/lib_eio_linux/low_level.ml index 613f23618..c1ed4f873 100644 --- a/lib_eio_linux/low_level.ml +++ b/lib_eio_linux/low_level.ml @@ -341,6 +341,27 @@ external eio_getrandom : Cstruct.buffer -> int -> int -> int = "caml_eio_getrand external eio_getdents : Unix.file_descr -> string list = "caml_eio_getdents" +let lseek fd off cmd = + Fd.use_exn "lseek" fd @@ fun fd -> + let cmd = + match cmd with + | `Set -> Unix.SEEK_SET + | `Cur -> Unix.SEEK_CUR + | `End -> Unix.SEEK_END + in + Unix.LargeFile.lseek fd (Optint.Int63.to_int64 off) cmd + |> Optint.Int63.of_int64 + +let fsync fd = + (* todo: https://github.com/ocaml-multicore/ocaml-uring/pull/103 *) + Eio_unix.run_in_systhread @@ fun () -> + Fd.use_exn "fsync" fd Unix.fsync + +let ftruncate fd len = + Eio_unix.run_in_systhread @@ fun () -> + Fd.use_exn "ftruncate" fd @@ fun fd -> + Unix.LargeFile.ftruncate fd (Optint.Int63.to_int64 len) + let getrandom { Cstruct.buffer; off; len } = let rec loop n = if n = len then diff --git a/lib_eio_posix/flow.ml b/lib_eio_posix/flow.ml index d84c8796c..2588bf113 100644 --- a/lib_eio_posix/flow.ml +++ b/lib_eio_posix/flow.ml @@ -79,6 +79,10 @@ module Impl = struct let _addr, n, fds = Low_level.recv_msg_with_fds t ~sw ~max_fds (Array.of_list data) in n, fds + let seek = Low_level.lseek + let sync = Low_level.fsync + let truncate = Low_level.ftruncate + let fd t = t let close = Eio_unix.Fd.close diff --git a/lib_eio_posix/low_level.ml b/lib_eio_posix/low_level.ml index 4138cabb9..fe490a0eb 100644 --- a/lib_eio_posix/low_level.ml +++ b/lib_eio_posix/low_level.ml @@ -256,6 +256,26 @@ external atime_nsec : stat -> int = "ocaml_eio_posix_stat_atime_nsec" [@@noalloc external ctime_nsec : stat -> int = "ocaml_eio_posix_stat_ctime_nsec" [@@noalloc] external mtime_nsec : stat -> int = "ocaml_eio_posix_stat_mtime_nsec" [@@noalloc] +let lseek fd off cmd = + Fd.use_exn "lseek" fd @@ fun fd -> + let cmd = + match cmd with + | `Set -> Unix.SEEK_SET + | `Cur -> Unix.SEEK_CUR + | `End -> Unix.SEEK_END + in + Unix.LargeFile.lseek fd (Optint.Int63.to_int64 off) cmd + |> Optint.Int63.of_int64 + +let fsync fd = + Eio_unix.run_in_systhread @@ fun () -> + Fd.use_exn "fsync" fd Unix.fsync + +let ftruncate fd len = + Eio_unix.run_in_systhread @@ fun () -> + Fd.use_exn "ftruncate" fd @@ fun fd -> + Unix.LargeFile.ftruncate fd (Optint.Int63.to_int64 len) + let pipe ~sw = let unix_r, unix_w = Unix.pipe ~cloexec:true () in let r = Fd.of_unix ~sw ~blocking:false ~close_unix:true unix_r in diff --git a/lib_eio_posix/low_level.mli b/lib_eio_posix/low_level.mli index b93acf425..aea817966 100644 --- a/lib_eio_posix/low_level.mli +++ b/lib_eio_posix/low_level.mli @@ -35,6 +35,10 @@ val send_msg : fd -> ?fds:fd list -> ?dst:Unix.sockaddr -> Cstruct.t array -> in val getrandom : Cstruct.t -> unit +val lseek : fd -> Optint.Int63.t -> [`Set | `Cur | `End] -> Optint.Int63.t +val fsync : fd -> unit +val ftruncate : fd -> Optint.Int63.t -> unit + type stat val create_stat : unit -> stat diff --git a/lib_eio_windows/flow.ml b/lib_eio_windows/flow.ml index b56580342..5eacd68da 100755 --- a/lib_eio_windows/flow.ml +++ b/lib_eio_windows/flow.ml @@ -76,6 +76,10 @@ module Impl = struct let recv_msg_with_fds _t ~sw:_ ~max_fds:_ _data = failwith "Not implemented on Windows" + let seek = Low_level.lseek + let sync = Low_level.fsync + let truncate = Low_level.ftruncate + let fd t = t let close = Eio_unix.Fd.close diff --git a/lib_eio_windows/low_level.ml b/lib_eio_windows/low_level.ml index d9fd69ffa..df68f9aad 100755 --- a/lib_eio_windows/low_level.ml +++ b/lib_eio_windows/low_level.ml @@ -231,6 +231,26 @@ let rename ?old_dir old_path ?new_dir new_path = in_worker_thread @@ fun () -> eio_renameat old_dir old_path new_dir new_path +let lseek fd off cmd = + Fd.use_exn "lseek" fd @@ fun fd -> + let cmd = + match cmd with + | `Set -> Unix.SEEK_SET + | `Cur -> Unix.SEEK_CUR + | `End -> Unix.SEEK_END + in + Unix.LargeFile.lseek fd (Optint.Int63.to_int64 off) cmd + |> Optint.Int63.of_int64 + +let fsync fd = + Eio_unix.run_in_systhread @@ fun () -> + Fd.use_exn "fsync" fd Unix.fsync + +let ftruncate fd len = + Eio_unix.run_in_systhread @@ fun () -> + Fd.use_exn "ftruncate" fd @@ fun fd -> + Unix.LargeFile.ftruncate fd (Optint.Int63.to_int64 len) + let pipe ~sw = let unix_r, unix_w = Unix.pipe ~cloexec:true () in let r = Fd.of_unix ~sw ~blocking:false ~close_unix:true unix_r in diff --git a/lib_eio_windows/low_level.mli b/lib_eio_windows/low_level.mli index 189aed7bf..22c843bb8 100755 --- a/lib_eio_windows/low_level.mli +++ b/lib_eio_windows/low_level.mli @@ -34,6 +34,10 @@ val send_msg : fd -> ?dst:Unix.sockaddr -> bytes -> int val getrandom : Cstruct.t -> unit +val lseek : fd -> Optint.Int63.t -> [`Set | `Cur | `End] -> Optint.Int63.t +val fsync : fd -> unit +val ftruncate : fd -> Optint.Int63.t -> unit + val fstat : fd -> Unix.LargeFile.stats val lstat : string -> Unix.LargeFile.stats diff --git a/tests/fs.md b/tests/fs.md index d56dc74d0..6b46e985a 100644 --- a/tests/fs.md +++ b/tests/fs.md @@ -675,3 +675,23 @@ Exception: Failure "Simulated error". + -> Some /etc/passwd - : unit = () ``` + +# Seek, truncate and sync + +```ocaml +# Eio_main.run @@ fun env -> + Eio.Path.with_open_out (env#cwd / "seek-test") ~create:(`If_missing 0o700) @@ fun file -> + Eio.File.truncate file (Int63.of_int 10); + assert ((Eio.File.stat file).size = (Int63.of_int 10)); + let pos = Eio.File.seek file (Int63.of_int 3) `Set in + traceln "seek from start: %a" Int63.pp pos; + let pos = Eio.File.seek file (Int63.of_int 2) `Cur in + traceln "relative seek: %a" Int63.pp pos; + let pos = Eio.File.seek file (Int63.of_int (-1)) `End in + traceln "seek from end: %a" Int63.pp pos; + Eio.File.sync file; (* (no way to check if this actually worked, but ensure it runs) *) ++seek from start: 3 ++relative seek: 5 ++seek from end: 9 +- : unit = () +```