Skip to content

Commit

Permalink
stat: switch to a GADT-based interface to support statx when available
Browse files Browse the repository at this point in the history
  • Loading branch information
avsm committed Aug 21, 2023
1 parent 755b05a commit ae063ae
Show file tree
Hide file tree
Showing 14 changed files with 199 additions and 164 deletions.
65 changes: 33 additions & 32 deletions lib_eio/file.ml
Original file line number Diff line number Diff line change
Expand Up @@ -26,36 +26,36 @@ module Stat = struct
| `Symbolic_link -> Fmt.string ppf "symbolic link"
| `Socket -> Fmt.string ppf "socket"

type t = {
dev : Int64.t;
ino : Int64.t;
kind : kind;
perm : Unix_perm.t;
nlink : Int64.t;
uid : Int64.t;
gid : Int64.t;
rdev : Int64.t;
size : Optint.Int63.t;
atime : float;
mtime : float;
ctime : float;
}

let pp ppf t =
Fmt.record [
Fmt.field "dev" (fun t -> t.dev) Fmt.int64;
Fmt.field "ino" (fun t -> t.ino) Fmt.int64;
Fmt.field "kind" (fun t -> t.kind) pp_kind;
Fmt.field "perm" (fun t -> t.perm) (fun ppf i -> Fmt.pf ppf "0o%o" i);
Fmt.field "nlink" (fun t -> t.nlink) Fmt.int64;
Fmt.field "uid" (fun t -> t.uid) Fmt.int64;
Fmt.field "gid" (fun t -> t.gid) Fmt.int64;
Fmt.field "rdev" (fun t -> t.rdev) Fmt.int64;
Fmt.field "size" (fun t -> t.size) Optint.Int63.pp;
Fmt.field "atime" (fun t -> t.atime) Fmt.float;
Fmt.field "mtime" (fun t -> t.mtime) Fmt.float;
Fmt.field "ctime" (fun t -> t.ctime) Fmt.float;
] ppf t
type 'a f =
| Dev : int64 f
| Ino : int64 f
| Kind : kind f
| Perm : int f
| Nlink : int64 f
| Uid : int64 f
| Gid : int64 f
| Rdev : int64 f
| Size : int64 f
| Atime : float f
| Ctime : float f
| Mtime : float f

type ('a, 'ty) t =
| [] : ('ty, 'ty) t
| (::) : 'a f * ('b, 'ty) t -> ('a -> 'b, 'ty) t

let dev = Dev
let ino = Ino
let kind = Kind
let perm = Perm
let nlink = Nlink
let uid = Uid
let gid = Gid
let rdev = Rdev
let size = Size
let atime = Atime
let ctime = Ctime
let mtime = Mtime
end

type ro_ty = [`File | Flow.source_ty | Resource.close_ty]
Expand All @@ -71,7 +71,7 @@ module Pi = struct
include Flow.Pi.SOURCE

val pread : t -> file_offset:Optint.Int63.t -> Cstruct.t list -> int
val stat : t -> Stat.t
val stat : 'a 'b . t -> ('a, 'b) Stat.t -> 'a -> 'b
val close : t -> unit
end

Expand Down Expand Up @@ -105,7 +105,8 @@ let stat (Resource.T (t, ops)) =
let module X = (val (Resource.get ops Pi.Read)) in
X.stat t

let size t = (stat t).size
let size t =
stat t [Stat.size] (fun s -> Optint.Int63.of_int64 s)

let pread (Resource.T (t, ops)) ~file_offset bufs =
let module X = (val (Resource.get ops Pi.Read)) in
Expand Down
58 changes: 36 additions & 22 deletions lib_eio/file.mli
Original file line number Diff line number Diff line change
Expand Up @@ -24,24 +24,36 @@ module Stat : sig
val pp_kind : kind Fmt.t
(** Pretty printer for {! kind}. *)

type t = {
dev : Int64.t;
ino : Int64.t;
kind : kind;
perm : Unix_perm.t;
nlink : Int64.t;
uid : Int64.t;
gid : Int64.t;
rdev : Int64.t;
size : Optint.Int63.t;
atime : float;
mtime : float;
ctime : float;
}
(** Like stat(2). *)

val pp : t Fmt.t
(** Pretty printer for {! t}. *)
type 'a f =
| Dev : int64 f
| Ino : int64 f
| Kind : kind f
| Perm : int f
| Nlink : int64 f
| Uid : int64 f
| Gid : int64 f
| Rdev : int64 f
| Size : int64 f
| Atime : float f
| Ctime : float f
| Mtime : float f

type ('a, 'ty) t =
| [] : ('ty, 'ty) t
| (::) : 'a f * ('b, 'ty) t -> ('a -> 'b, 'ty) t

val dev : int64 f
val ino : int64 f
val kind : kind f
val perm : int f
val nlink : int64 f
val uid : int64 f
val gid : int64 f
val rdev : int64 f
val size : int64 f
val atime : float f
val ctime : float f
val mtime : float f
end

type ro_ty = [`File | Flow.source_ty | Resource.close_ty]
Expand All @@ -59,7 +71,7 @@ module Pi : sig
include Flow.Pi.SOURCE

val pread : t -> file_offset:Optint.Int63.t -> Cstruct.t list -> int
val stat : t -> Stat.t
val stat : 'a 'b . t -> ('a, 'b) Stat.t -> 'a -> 'b
val close : t -> unit
end

Expand All @@ -79,11 +91,13 @@ module Pi : sig
val rw : (module WRITE with type t = 't) -> ('t, rw_ty) Resource.handler
end

val stat : _ ro -> Stat.t
(** [stat t] returns the {!Stat.t} record associated with [t]. *)
val stat : _ ro -> ('a, 'b) Stat.t -> 'a -> 'b
(** [stat t fields fn] will retrieve the file statistics for the specified
[fields] and apply them as arguments to [fn]. *)

val size : _ ro -> Optint.Int63.t
(** [size t] returns the size of [t]. *)
(** [size t] returns the size of [t].
Equivalent to [stat t [File.size] Fun.id]. *)

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].
Expand Down
2 changes: 1 addition & 1 deletion lib_eio/fs.ml
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ module Pi = struct
val mkdir : t -> perm:File.Unix_perm.t -> path -> unit
val open_dir : t -> sw:Switch.t -> path -> [`Close | dir_ty] r
val read_dir : t -> path -> string list
val stat : t -> follow:bool -> string -> File.Stat.t
val stat : t -> follow:bool -> string -> ('a,'b) File.Stat.t -> 'a -> 'b
val unlink : t -> path -> unit
val rmdir : t -> path -> unit
val rename : t -> path -> _ dir -> path -> unit
Expand Down
2 changes: 1 addition & 1 deletion lib_eio/path.mli
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ val read_dir : _ t -> string list

(** {2 Metadata} *)

val stat : follow:bool -> _ t -> File.Stat.t
val stat : follow:bool -> _ t -> ('a, 'b) File.Stat.t -> 'a -> 'b
(** [stat ~follow t] returns metadata about the file [t].
If [t] is a symlink, the information returned is about the target if [follow = true],
Expand Down
22 changes: 4 additions & 18 deletions lib_eio_linux/eio_linux.ml
Original file line number Diff line number Diff line change
Expand Up @@ -145,9 +145,9 @@ module Flow = struct

let close = Eio_unix.Fd.close

let is_tty t = Fd.use_exn "isatty" t Unix.isatty
let stat t f k = Low_level.statx ~fd:t ~flags:Uring.Statx.Flags.empty_path "" f k

let stat = Low_level.fstat
let is_tty t = Fd.use_exn "isatty" t Unix.isatty

let single_read t buf =
if is_tty t then (
Expand Down Expand Up @@ -496,25 +496,11 @@ end = struct
let unlink t path = Low_level.unlink ~rmdir:false t.fd path
let rmdir t path = Low_level.unlink ~rmdir:true t.fd path

let stat t ~follow path =
let stat t ~follow path f k =
let flags =
if follow then Uring.Statx.Flags.empty else Uring.Statx.Flags.(empty + symlink_nofollow)
in
let statx = Low_level.statx ~mask:Uring.Statx.Mask.basic_stats t.fd path flags in
Eio.File.Stat.{
dev = statx.dev;
rdev = statx.rdev;
ino = statx.ino;
kind = statx.kind;
perm = statx.perm;
nlink = statx.nlink;
uid = statx.uid;
gid = statx.gid;
size = statx.size;
atime = statx.atime;
mtime = statx.mtime;
ctime = statx.ctime;
}
Low_level.stat t.fd path ~flags f k

let rename t old_path t2 new_path =
match get_dir_fd_opt t2 with
Expand Down
7 changes: 5 additions & 2 deletions lib_eio_linux/eio_linux.mli
Original file line number Diff line number Diff line change
Expand Up @@ -153,8 +153,11 @@ module Low_level : sig
val await_writable : fd -> unit
(** [await_writable fd] blocks until [fd] is writable (or has an error). *)

val fstat : fd -> Eio.File.Stat.t
(** Like {!Unix.LargeFile.fstat}. *)
val statx : ?buf:Uring.Statx.t -> ?fd:fd -> ?flags:Uring.Statx.Flags.t -> string -> ('a, 'b) Eio.File.Stat.t -> 'a -> 'b
(** [statx ?buf ?fd ?flags path fields fn] runs {!Uring.statx} on the optional [fd]
with [flags] and [path]. The query mask is calculated from the [fields], and
the selected results are applied as arguments to [fn]. If [buf] is not passed
in then a fresh {!Uring.Statx.t} is allocated, otherwise [buf] will be used. *)

val read_dir : fd -> string list
(** [read_dir dir] reads all directory entries from [dir].
Expand Down
109 changes: 53 additions & 56 deletions lib_eio_linux/low_level.ml
Original file line number Diff line number Diff line change
Expand Up @@ -303,35 +303,59 @@ let openat ~sw ?seekable ~access ~flags ~perm dir path =
| Cwd -> openat2 ~sw ?seekable ~access ~flags ~perm ~resolve:Uring.Resolve.beneath path
| Fs -> openat2 ~sw ?seekable ~access ~flags ~perm ~resolve:Uring.Resolve.empty path

let fstat t =
(* todo: use uring *)
try
let ust = Fd.use_exn "fstat" t Unix.LargeFile.fstat in
let st_kind : Eio.File.Stat.kind =
match ust.st_kind with
| Unix.S_REG -> `Regular_file
| Unix.S_DIR -> `Directory
| Unix.S_CHR -> `Character_special
| Unix.S_BLK -> `Block_device
| Unix.S_LNK -> `Symbolic_link
| Unix.S_FIFO -> `Fifo
| Unix.S_SOCK -> `Socket
in
Eio.File.Stat.{
dev = ust.st_dev |> Int64.of_int;
ino = ust.st_ino |> Int64.of_int;
kind = st_kind;
perm = ust.st_perm;
nlink = ust.st_nlink |> Int64.of_int;
uid = ust.st_uid |> Int64.of_int;
gid = ust.st_gid |> Int64.of_int;
rdev = ust.st_rdev |> Int64.of_int;
size = ust.st_size |> Optint.Int63.of_int64;
atime = ust.st_atime;
mtime = ust.st_mtime;
ctime = ust.st_ctime;
}
with Unix.Unix_error (code, name, arg) -> raise @@ Err.wrap_fs code name arg
let float_of_time s ns = Int64.to_float s +. (float ns /. 1e9)

let statx ?buf ?fd ?(flags=Uring.Statx.Flags.empty) path f k =
let module X = Uring.Statx in
let module M = Uring.Statx.Mask in
let module U = Eio.File.Stat in
let rec mask : type a b . (a,b) U.t -> M.t -> M.t = fun v acc ->
match v with
| U.Dev :: tl -> mask tl acc
| U.Ino :: tl -> mask tl M.(acc + M.ino)
| U.Kind :: tl -> mask tl M.(acc + M.type')
| U.Perm :: tl -> mask tl M.(acc + M.mode)
| U.Nlink :: tl -> mask tl M.(acc + M.nlink)
| U.Uid :: tl -> mask tl M.(acc + M.uid)
| U.Gid :: tl -> mask tl M.(acc + M.gid)
| U.Rdev :: tl -> mask tl acc
| U.Size :: tl -> mask tl M.(acc + M.size)
| U.Atime :: tl -> mask tl M.(acc + M.atime)
| U.Ctime :: tl -> mask tl M.(acc + M.ctime)
| U.Mtime :: tl -> mask tl M.(acc + M.mtime)
| [] -> acc
in
let mask = mask f M.type' in (* TODO this could be empty as well, but needs uring#100 *)
let statx = match buf with None -> Uring.Statx.create () | Some t -> t in
let res = match fd with
| Some fd ->
Fd.use_exn "fstat" fd @@ fun fd ->
Sched.enter (enqueue_statx (Some fd, path, statx, flags, mask))
| None-> Sched.enter (enqueue_statx (None, path, statx, flags, mask))
in
if res <> 0 then raise @@ Err.wrap_fs (Uring.error_of_errno res) "statx" "";
let rec fn : type a b. (a, b) U.t -> a -> b = fun v acc ->
match v with
| U.Dev :: tl -> fn tl @@ acc (X.dev statx)
| U.Ino :: tl -> fn tl @@ acc (X.ino statx)
| U.Kind :: tl -> fn tl @@ acc (X.kind statx)
| U.Perm :: tl -> fn tl @@ acc (X.perm statx)
| U.Nlink :: tl -> fn tl @@ acc (X.nlink statx)
| U.Uid :: tl -> fn tl @@ acc (X.uid statx)
| U.Gid :: tl -> fn tl @@ acc (X.gid statx)
| U.Rdev :: tl -> fn tl @@ acc (X.rdev statx)
| U.Size :: tl -> fn tl @@ acc (X.size statx)
| U.Atime :: tl -> fn tl @@ acc (float_of_time (X.atime_sec statx) (X.atime_nsec statx))
| U.Mtime :: tl -> fn tl @@ acc (float_of_time (X.mtime_sec statx) (X.mtime_nsec statx))
| U.Ctime :: tl -> fn tl @@ acc (float_of_time (X.ctime_sec statx) (X.ctime_nsec statx))
| [] -> acc
in
fn f k

let stat dir_fd ?flags path f k =
match dir_fd with
| FD fd -> statx ~fd ?flags path f k
| Cwd | Fs -> statx ?flags path f k

external eio_mkdirat : Unix.file_descr -> string -> Unix.file_perm -> unit = "caml_eio_mkdirat"

Expand Down Expand Up @@ -375,33 +399,6 @@ let mkdir_beneath ~perm dir path =
try eio_mkdirat parent leaf perm
with Unix.Unix_error (code, name, arg) -> raise @@ Err.wrap_fs code name arg

let float_of_time (s, ns) = Int64.to_float s +. (float ns /. 1e9)

let statx ~mask fd path flags =
let x = Uring.Statx.create () in
let res = match fd with
| FD fd ->
Fd.use_exn "statx" fd @@ fun fd ->
Sched.enter (enqueue_statx (Some fd, path, x, flags, mask))
| Cwd | Fs -> Sched.enter (enqueue_statx (None, path, x, flags, mask))
in
if res <> 0 then raise @@ Err.wrap_fs (Uring.error_of_errno res) "statx" "";
let module S = Uring.Statx in
{ Eio.File.Stat.
dev = S.dev x;
ino = S.ino x;
kind = S.kind x;
perm = S.perm x;
nlink = S.nlink x;
uid = S.uid x;
gid = S.gid x;
rdev = S.rdev x;
size = Optint.Int63.of_int64 (S.size x);
atime = float_of_time (S.atime_sec x, S.atime_nsec x);
mtime = float_of_time (S.mtime_sec x, S.mtime_nsec x);
ctime = float_of_time (S.ctime_sec x, S.ctime_nsec x);
}

let unlink ~rmdir dir path =
(* [unlink] is really an operation on [path]'s parent. Get a reference to that first: *)
with_parent_dir "unlink" dir path @@ fun parent leaf ->
Expand Down
2 changes: 1 addition & 1 deletion lib_eio_linux/tests/eurcp_lib.ml
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ let run_cp block_size queue_depth infile outfile () =
~resolve:Uring.Resolve.empty
~perm:0o644
in
let insize = (U.fstat infd).size in
let insize = U.statx ~fd:infd "" [Eio.File.Stat.Size] (fun size -> Int63.of_int64 size) in
Logs.debug (fun l -> l "eurcp: %s -> %s size %a queue %d bs %d"
infile
outfile
Expand Down
Loading

0 comments on commit ae063ae

Please sign in to comment.