Skip to content

Commit afb83bf

Browse files
authored
Merge pull request #620 from talex5/stat
Add Path.stat
2 parents f1be218 + 1938ce5 commit afb83bf

File tree

12 files changed

+214
-35
lines changed

12 files changed

+214
-35
lines changed

lib_eio/core/eio__core.mli

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -433,6 +433,9 @@ module Exn : sig
433433
This is similar to {!Fmt.exn}, but can do a better job on {!Io} exceptions
434434
because it can format them directly without having to convert to a string first. *)
435435

436+
val pp_err : err Fmt.t
437+
(** [pp_err] formats an error code. *)
438+
436439
(** Extensible backend-specific exceptions. *)
437440
module Backend : sig
438441
type t = ..

lib_eio/file.ml

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,16 @@ module Stat = struct
1616
| `Socket
1717
]
1818

19+
let pp_kind ppf = function
20+
| `Unknown -> Fmt.string ppf "unknown"
21+
| `Fifo -> Fmt.string ppf "fifo"
22+
| `Character_special -> Fmt.string ppf "character special file"
23+
| `Directory -> Fmt.string ppf "directory"
24+
| `Block_device -> Fmt.string ppf "block device"
25+
| `Regular_file -> Fmt.string ppf "regular file"
26+
| `Symbolic_link -> Fmt.string ppf "symbolic link"
27+
| `Socket -> Fmt.string ppf "socket"
28+
1929
type t = {
2030
dev : Int64.t;
2131
ino : Int64.t;
@@ -30,6 +40,22 @@ module Stat = struct
3040
mtime : float;
3141
ctime : float;
3242
}
43+
44+
let pp ppf t =
45+
Fmt.record [
46+
Fmt.field "dev" (fun t -> t.dev) Fmt.int64;
47+
Fmt.field "ino" (fun t -> t.ino) Fmt.int64;
48+
Fmt.field "kind" (fun t -> t.kind) pp_kind;
49+
Fmt.field "perm" (fun t -> t.perm) (fun ppf i -> Fmt.pf ppf "0o%o" i);
50+
Fmt.field "nlink" (fun t -> t.nlink) Fmt.int64;
51+
Fmt.field "uid" (fun t -> t.uid) Fmt.int64;
52+
Fmt.field "gid" (fun t -> t.gid) Fmt.int64;
53+
Fmt.field "rdev" (fun t -> t.rdev) Fmt.int64;
54+
Fmt.field "size" (fun t -> t.size) Optint.Int63.pp;
55+
Fmt.field "atime" (fun t -> t.atime) Fmt.float;
56+
Fmt.field "mtime" (fun t -> t.mtime) Fmt.float;
57+
Fmt.field "ctime" (fun t -> t.ctime) Fmt.float;
58+
] ppf t
3359
end
3460

3561
type ro_ty = [`File | Flow.source_ty | Resource.close_ty]

lib_eio/file.mli

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ module Stat : sig
2121
]
2222
(** Kind of file from st_mode. **)
2323

24+
val pp_kind : kind Fmt.t
25+
(** Pretty printer for {! kind}. *)
26+
2427
type t = {
2528
dev : Int64.t;
2629
ino : Int64.t;
@@ -36,6 +39,9 @@ module Stat : sig
3639
ctime : float;
3740
}
3841
(** Like stat(2). *)
42+
43+
val pp : t Fmt.t
44+
(** Pretty printer for {! t}. *)
3945
end
4046

4147
type ro_ty = [`File | Flow.source_ty | Resource.close_ty]

lib_eio/fs.ml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,8 @@ type create = [
4242

4343
type dir_ty = [`Dir]
4444
type 'a dir = ([> dir_ty] as 'a) r
45-
4645
(** Note: use the functions in {!Path} to access directories. *)
46+
4747
module Pi = struct
4848
module type DIR = sig
4949
type t
@@ -60,6 +60,7 @@ module Pi = struct
6060
val mkdir : t -> perm:File.Unix_perm.t -> path -> unit
6161
val open_dir : t -> sw:Switch.t -> path -> [`Close | dir_ty] r
6262
val read_dir : t -> path -> string list
63+
val stat : t -> follow:bool -> string -> File.Stat.t
6364
val unlink : t -> path -> unit
6465
val rmdir : t -> path -> unit
6566
val rename : t -> path -> _ dir -> path -> unit

lib_eio/path.ml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,14 @@ let read_dir t =
6161
let bt = Printexc.get_raw_backtrace () in
6262
Exn.reraise_with_context ex bt "reading directory %a" pp t
6363

64+
let stat ~follow t =
65+
let (Resource.T (dir, ops), path) = t in
66+
let module X = (val (Resource.get ops Fs.Pi.Dir)) in
67+
try X.stat ~follow dir path
68+
with Exn.Io _ as ex ->
69+
let bt = Printexc.get_raw_backtrace () in
70+
Exn.reraise_with_context ex bt "examining %a" pp t
71+
6472
let with_open_in path fn =
6573
Switch.run @@ fun sw -> fn (open_in ~sw path)
6674

lib_eio/path.mli

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,14 @@ val read_dir : _ t -> string list
128128
129129
Note: The special Unix entries "." and ".." are not included in the results. *)
130130

131+
(** {2 Metadata} *)
132+
133+
val stat : follow:bool -> _ t -> File.Stat.t
134+
(** [stat ~follow t] returns metadata about the file [t].
135+
136+
If [t] is a symlink, the information returned is about the target if [follow = true],
137+
otherwise it is about the link itself. *)
138+
131139
(** {1 Other} *)
132140

133141
val unlink : _ t -> unit

lib_eio_linux/eio_linux.ml

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -526,6 +526,34 @@ end = struct
526526
let unlink t path = Low_level.unlink ~rmdir:false t.fd path
527527
let rmdir t path = Low_level.unlink ~rmdir:true t.fd path
528528

529+
let float_of_time s ns =
530+
let s = Int64.to_float s in
531+
let f = s +. (float ns /. 1e9) in
532+
(* It's possible that we might round up to the next second.
533+
Since some algorithms only care about the seconds part,
534+
make sure the integer part is always [s]: *)
535+
if floor f = s then f
536+
else Float.pred f
537+
538+
let stat t ~follow path =
539+
let module X = Uring.Statx in
540+
let x = X.create () in
541+
Low_level.statx_confined ~follow ~mask:X.Mask.basic_stats t.fd path x;
542+
{ Eio.File.Stat.
543+
dev = X.dev x;
544+
ino = X.ino x;
545+
kind = X.kind x;
546+
perm = X.perm x;
547+
nlink = X.nlink x;
548+
uid = X.uid x;
549+
gid = X.gid x;
550+
rdev = X.rdev x;
551+
size = X.size x |> Optint.Int63.of_int64;
552+
atime = float_of_time (X.atime_sec x) (X.atime_nsec x);
553+
mtime = float_of_time (X.mtime_sec x) (X.mtime_nsec x);
554+
ctime = float_of_time (X.ctime_sec x) (X.ctime_nsec x);
555+
}
556+
529557
let rename t old_path t2 new_path =
530558
match get_dir_fd_opt t2 with
531559
| Some fd2 -> Low_level.rename t.fd old_path fd2 new_path

lib_eio_linux/low_level.ml

Lines changed: 45 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -350,25 +350,43 @@ let getrandom { Cstruct.buffer; off; len } =
350350
in
351351
loop 0
352352

353-
(* [with_parent_dir dir path fn] runs [fn parent (basename path)],
354-
where [parent] is a path FD for [path]'s parent, resolved using [Resolve.beneath]. *)
355-
let with_parent_dir op dir path fn =
353+
(* [with_parent_dir_fd dir path fn] runs [fn parent (basename path)],
354+
where [parent] is a path FD for [path]'s parent, resolved using [Resolve.beneath].
355+
356+
If [basename path] is ".." then we treat it as if path had "/." on the end,
357+
to avoid the special case.
358+
359+
todo: Optimise this by doing [fn AT_FDCWD path] if [dir = Fs].
360+
*)
361+
let with_parent_dir_fd dir path fn =
356362
let dir_path = Filename.dirname path in
357363
let leaf = Filename.basename path in
358364
Switch.run (fun sw ->
359-
let parent =
360-
match dir with
361-
| FD d when dir_path = "." -> d
362-
| _ ->
365+
match dir with
366+
| _ when leaf = ".." ->
367+
let fd =
368+
openat ~sw ~seekable:false dir path (* Open the full path *)
369+
~access:`R
370+
~flags:Uring.Open_flags.(cloexec + path + directory)
371+
~perm:0
372+
in
373+
fn fd "."
374+
| FD d when dir_path = "." -> fn d leaf
375+
| _ ->
376+
let parent =
363377
openat ~sw ~seekable:false dir dir_path
364378
~access:`R
365379
~flags:Uring.Open_flags.(cloexec + path + directory)
366380
~perm:0
367-
in
368-
Fd.use_exn op parent @@ fun parent ->
369-
fn parent leaf
381+
in
382+
fn parent leaf
370383
)
371384

385+
let with_parent_dir op dir path fn =
386+
with_parent_dir_fd dir path @@ fun parent leaf ->
387+
Fd.use_exn op parent @@ fun parent ->
388+
fn parent leaf
389+
372390
let statx ?fd ~mask path buf flags =
373391
let res =
374392
match fd with
@@ -379,6 +397,23 @@ let statx ?fd ~mask path buf flags =
379397
in
380398
if res <> 0 then raise @@ Err.wrap_fs (Uring.error_of_errno res) "statx" path
381399

400+
let statx_confined ~mask ~follow fd path buf =
401+
let module X = Uring.Statx in
402+
let flags = if follow then X.Flags.empty else X.Flags.symlink_nofollow in
403+
match fd with
404+
| Fs -> statx ~mask path buf flags
405+
| Cwd | FD _ when not follow ->
406+
with_parent_dir_fd fd path @@ fun parent leaf ->
407+
statx ~mask ~fd:parent leaf buf flags
408+
| Cwd | FD _ ->
409+
Switch.run @@ fun sw ->
410+
let fd = openat ~sw ~seekable:false fd (if path = "" then "." else path)
411+
~access:`R
412+
~flags:Uring.Open_flags.(cloexec + path)
413+
~perm:0
414+
in
415+
statx ~fd ~mask "" buf Uring.Statx.Flags.(flags + empty_path)
416+
382417
let mkdir_beneath ~perm dir path =
383418
(* [mkdir] is really an operation on [path]'s parent. Get a reference to that first: *)
384419
with_parent_dir "mkdir" dir path @@ fun parent leaf ->

lib_eio_posix/flow.ml

Lines changed: 26 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2,38 +2,41 @@ open Eio.Std
22

33
module Fd = Eio_unix.Fd
44

5+
let float_of_time s ns =
6+
let s = Int64.to_float s in
7+
let f = s +. (float ns /. 1e9) in
8+
(* It's possible that we might round up to the next second.
9+
Since some algorithms only care about the seconds part,
10+
make sure the integer part is always [s]: *)
11+
if floor f = s then f
12+
else Float.pred f
13+
14+
let eio_of_stat x =
15+
{ Eio.File.Stat.
16+
dev = Low_level.dev x;
17+
ino = Low_level.ino x;
18+
kind = Low_level.kind x;
19+
perm = Low_level.perm x;
20+
nlink = Low_level.nlink x;
21+
uid = Low_level.uid x;
22+
gid = Low_level.gid x;
23+
rdev = Low_level.rdev x;
24+
size = Low_level.size x |> Optint.Int63.of_int64;
25+
atime = float_of_time (Low_level.atime_sec x) (Low_level.atime_nsec x);
26+
mtime = float_of_time (Low_level.mtime_sec x) (Low_level.mtime_nsec x);
27+
ctime = float_of_time (Low_level.ctime_sec x) (Low_level.ctime_nsec x);
28+
}
29+
530
module Impl = struct
631
type tag = [`Generic | `Unix]
732

833
type t = Eio_unix.Fd.t
934

10-
let float_of_time s ns =
11-
let s = Int64.to_float s in
12-
let f = s +. (float ns /. 1e9) in
13-
(* It's possible that we might round up to the next second.
14-
Since some algorithms only care about the seconds part,
15-
make sure the integer part is always [s]: *)
16-
if floor f = s then f
17-
else Float.pred f
18-
1935
let stat t =
2036
try
2137
let x = Low_level.create_stat () in
2238
Low_level.fstat ~buf:x t;
23-
{ Eio.File.Stat.
24-
dev = Low_level.dev x;
25-
ino = Low_level.ino x;
26-
kind = Low_level.kind x;
27-
perm = Low_level.perm x;
28-
nlink = Low_level.nlink x;
29-
uid = Low_level.uid x;
30-
gid = Low_level.gid x;
31-
rdev = Low_level.rdev x;
32-
size = Low_level.size x |> Optint.Int63.of_int64;
33-
atime = float_of_time (Low_level.atime_sec x) (Low_level.atime_nsec x);
34-
mtime = float_of_time (Low_level.mtime_sec x) (Low_level.mtime_nsec x);
35-
ctime = float_of_time (Low_level.ctime_sec x) (Low_level.ctime_nsec x);
36-
}
39+
eio_of_stat x
3740
with Unix.Unix_error (code, name, arg) -> raise @@ Err.wrap code name arg
3841

3942
let single_write t bufs =

lib_eio_posix/fs.ml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,16 @@ end = struct
137137
with_parent_dir t path @@ fun dirfd path ->
138138
Err.run (Low_level.unlink ?dirfd ~dir:true) path
139139

140+
let stat t ~follow path =
141+
let buf = Low_level.create_stat () in
142+
if follow then (
143+
Err.run (Low_level.fstatat ~buf ~follow:true) (resolve t path);
144+
) else (
145+
with_parent_dir t path @@ fun dirfd path ->
146+
Err.run (Low_level.fstatat ~buf ?dirfd ~follow:false) path;
147+
);
148+
Flow.eio_of_stat buf
149+
140150
let read_dir t path =
141151
(* todo: need fdopendir here to avoid races *)
142152
let path = resolve t path in

0 commit comments

Comments
 (0)