Compare commits

...

4 Commits

Author SHA1 Message Date
Thomas Leonard
9256754fe8
Merge pull request #621 from talex5/stat-docs
Document File.Stat record fields
2023-09-26 15:56:25 +01:00
Thomas Leonard
9e445f19a1 Document File.Stat record fields 2023-09-26 11:29:47 +01:00
Thomas Leonard
afb83bf1fa
Merge pull request #620 from talex5/stat
Add Path.stat
2023-09-26 09:36:25 +01:00
Patrick Ferris
1938ce5ad0 Add Path.stat
Co-authored-by: Thomas Leonard <talex5@gmail.com>
Co-authored-by: Anil Madhavapeddy <anil@recoil.org>
2023-09-25 16:27:14 +01:00
12 changed files with 227 additions and 48 deletions

View File

@ -433,6 +433,9 @@ module Exn : sig
This is similar to {!Fmt.exn}, but can do a better job on {!Io} exceptions
because it can format them directly without having to convert to a string first. *)
val pp_err : err Fmt.t
(** [pp_err] formats an error code. *)
(** Extensible backend-specific exceptions. *)
module Backend : sig
type t = ..

View File

@ -16,6 +16,16 @@ module Stat = struct
| `Socket
]
let pp_kind ppf = function
| `Unknown -> Fmt.string ppf "unknown"
| `Fifo -> Fmt.string ppf "fifo"
| `Character_special -> Fmt.string ppf "character special file"
| `Directory -> Fmt.string ppf "directory"
| `Block_device -> Fmt.string ppf "block device"
| `Regular_file -> Fmt.string ppf "regular file"
| `Symbolic_link -> Fmt.string ppf "symbolic link"
| `Socket -> Fmt.string ppf "socket"
type t = {
dev : Int64.t;
ino : Int64.t;
@ -30,6 +40,22 @@ module Stat = struct
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
end
type ro_ty = [`File | Flow.source_ty | Resource.close_ty]

View File

@ -1,6 +1,6 @@
open Std
(** Tranditional Unix permissions. *)
(** Traditional Unix permissions. *)
module Unix_perm : sig
type t = int
(** This is the same as {!Unix.file_perm}, but avoids a dependency on [Unix]. *)
@ -21,21 +21,27 @@ module Stat : sig
]
(** Kind of file from st_mode. **)
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;
dev : Int64.t; (** Device containing the filesystem where the file resides. *)
ino : Int64.t; (** Inode number. *)
kind : kind; (** File type. *)
perm : Unix_perm.t; (** Permissions (mode). *)
nlink : Int64.t; (** Number of hard links. *)
uid : Int64.t; (** User ID of owner. *)
gid : Int64.t; (** Group ID of owner. *)
rdev : Int64.t; (** Device's ID (if this is a device). *)
size : Optint.Int63.t; (** Total size in bytes. *)
atime : float; (** Last access time. *)
mtime : float; (** Last modification time. *)
ctime : float; (** Creation time. *)
}
(** Like stat(2). *)
val pp : t Fmt.t
(** Pretty printer for {! t}. *)
end
type ro_ty = [`File | Flow.source_ty | Resource.close_ty]

View File

@ -42,8 +42,8 @@ type create = [
type dir_ty = [`Dir]
type 'a dir = ([> dir_ty] as 'a) r
(** Note: use the functions in {!Path} to access directories. *)
module Pi = struct
module type DIR = sig
type t
@ -60,6 +60,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 unlink : t -> path -> unit
val rmdir : t -> path -> unit
val rename : t -> path -> _ dir -> path -> unit

View File

@ -61,6 +61,14 @@ let read_dir t =
let bt = Printexc.get_raw_backtrace () in
Exn.reraise_with_context ex bt "reading directory %a" pp t
let stat ~follow t =
let (Resource.T (dir, ops), path) = t in
let module X = (val (Resource.get ops Fs.Pi.Dir)) in
try X.stat ~follow dir path
with Exn.Io _ as ex ->
let bt = Printexc.get_raw_backtrace () in
Exn.reraise_with_context ex bt "examining %a" pp t
let with_open_in path fn =
Switch.run @@ fun sw -> fn (open_in ~sw path)

View File

@ -128,6 +128,14 @@ val read_dir : _ t -> string list
Note: The special Unix entries "." and ".." are not included in the results. *)
(** {2 Metadata} *)
val stat : follow:bool -> _ t -> File.Stat.t
(** [stat ~follow t] returns metadata about the file [t].
If [t] is a symlink, the information returned is about the target if [follow = true],
otherwise it is about the link itself. *)
(** {1 Other} *)
val unlink : _ t -> unit

View File

@ -526,6 +526,34 @@ 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 float_of_time s ns =
let s = Int64.to_float s in
let f = s +. (float ns /. 1e9) in
(* It's possible that we might round up to the next second.
Since some algorithms only care about the seconds part,
make sure the integer part is always [s]: *)
if floor f = s then f
else Float.pred f
let stat t ~follow path =
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;
{ Eio.File.Stat.
dev = X.dev x;
ino = X.ino x;
kind = X.kind x;
perm = X.perm x;
nlink = X.nlink x;
uid = X.uid x;
gid = X.gid x;
rdev = X.rdev x;
size = X.size x |> Optint.Int63.of_int64;
atime = float_of_time (X.atime_sec x) (X.atime_nsec x);
mtime = float_of_time (X.mtime_sec x) (X.mtime_nsec x);
ctime = float_of_time (X.ctime_sec x) (X.ctime_nsec x);
}
let rename t old_path t2 new_path =
match get_dir_fd_opt t2 with
| Some fd2 -> Low_level.rename t.fd old_path fd2 new_path

View File

@ -350,25 +350,43 @@ let getrandom { Cstruct.buffer; off; len } =
in
loop 0
(* [with_parent_dir dir path fn] runs [fn parent (basename path)],
where [parent] is a path FD for [path]'s parent, resolved using [Resolve.beneath]. *)
let with_parent_dir op dir path fn =
(* [with_parent_dir_fd dir path fn] runs [fn parent (basename path)],
where [parent] is a path FD for [path]'s parent, resolved using [Resolve.beneath].
If [basename path] is ".." then we treat it as if path had "/." on the end,
to avoid the special case.
todo: Optimise this by doing [fn AT_FDCWD path] if [dir = Fs].
*)
let with_parent_dir_fd dir path fn =
let dir_path = Filename.dirname path in
let leaf = Filename.basename path in
Switch.run (fun sw ->
let parent =
match dir with
| FD d when dir_path = "." -> d
| _ ->
match dir with
| _ when leaf = ".." ->
let fd =
openat ~sw ~seekable:false dir path (* Open the full path *)
~access:`R
~flags:Uring.Open_flags.(cloexec + path + directory)
~perm:0
in
fn fd "."
| FD d when dir_path = "." -> fn d leaf
| _ ->
let parent =
openat ~sw ~seekable:false dir dir_path
~access:`R
~flags:Uring.Open_flags.(cloexec + path + directory)
~perm:0
in
Fd.use_exn op parent @@ fun parent ->
fn parent leaf
in
fn parent leaf
)
let with_parent_dir op dir path fn =
with_parent_dir_fd dir path @@ fun parent leaf ->
Fd.use_exn op parent @@ fun parent ->
fn parent leaf
let statx ?fd ~mask path buf flags =
let res =
match fd with
@ -379,6 +397,23 @@ 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 module X = Uring.Statx in
let flags = if follow then X.Flags.empty else X.Flags.symlink_nofollow in
match fd with
| Fs -> statx ~mask path buf flags
| Cwd | FD _ when not follow ->
with_parent_dir_fd fd path @@ fun parent leaf ->
statx ~mask ~fd:parent leaf buf flags
| Cwd | FD _ ->
Switch.run @@ fun sw ->
let fd = openat ~sw ~seekable:false fd (if path = "" then "." else path)
~access:`R
~flags:Uring.Open_flags.(cloexec + path)
~perm:0
in
statx ~fd ~mask "" buf Uring.Statx.Flags.(flags + empty_path)
let mkdir_beneath ~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 ->

View File

@ -2,38 +2,41 @@ open Eio.Std
module Fd = Eio_unix.Fd
let float_of_time s ns =
let s = Int64.to_float s in
let f = s +. (float ns /. 1e9) in
(* It's possible that we might round up to the next second.
Since some algorithms only care about the seconds part,
make sure the integer part is always [s]: *)
if floor f = s then f
else Float.pred f
let eio_of_stat x =
{ Eio.File.Stat.
dev = Low_level.dev x;
ino = Low_level.ino x;
kind = Low_level.kind x;
perm = Low_level.perm x;
nlink = Low_level.nlink x;
uid = Low_level.uid x;
gid = Low_level.gid x;
rdev = Low_level.rdev x;
size = Low_level.size x |> Optint.Int63.of_int64;
atime = float_of_time (Low_level.atime_sec x) (Low_level.atime_nsec x);
mtime = float_of_time (Low_level.mtime_sec x) (Low_level.mtime_nsec x);
ctime = float_of_time (Low_level.ctime_sec x) (Low_level.ctime_nsec x);
}
module Impl = struct
type tag = [`Generic | `Unix]
type t = Eio_unix.Fd.t
let float_of_time s ns =
let s = Int64.to_float s in
let f = s +. (float ns /. 1e9) in
(* It's possible that we might round up to the next second.
Since some algorithms only care about the seconds part,
make sure the integer part is always [s]: *)
if floor f = s then f
else Float.pred f
let stat t =
try
let x = Low_level.create_stat () in
Low_level.fstat ~buf:x t;
{ Eio.File.Stat.
dev = Low_level.dev x;
ino = Low_level.ino x;
kind = Low_level.kind x;
perm = Low_level.perm x;
nlink = Low_level.nlink x;
uid = Low_level.uid x;
gid = Low_level.gid x;
rdev = Low_level.rdev x;
size = Low_level.size x |> Optint.Int63.of_int64;
atime = float_of_time (Low_level.atime_sec x) (Low_level.atime_nsec x);
mtime = float_of_time (Low_level.mtime_sec x) (Low_level.mtime_nsec x);
ctime = float_of_time (Low_level.ctime_sec x) (Low_level.ctime_nsec x);
}
eio_of_stat x
with Unix.Unix_error (code, name, arg) -> raise @@ Err.wrap code name arg
let single_write t bufs =

View File

@ -137,6 +137,16 @@ end = struct
with_parent_dir t path @@ fun dirfd path ->
Err.run (Low_level.unlink ?dirfd ~dir:true) path
let stat t ~follow path =
let buf = Low_level.create_stat () in
if follow then (
Err.run (Low_level.fstatat ~buf ~follow:true) (resolve t path);
) else (
with_parent_dir t path @@ fun dirfd path ->
Err.run (Low_level.fstatat ~buf ?dirfd ~follow:false) path;
);
Flow.eio_of_stat buf
let read_dir t path =
(* todo: need fdopendir here to avoid races *)
let path = resolve t path in

View File

@ -145,6 +145,15 @@ end = struct
with_parent_dir t path @@ fun dirfd path ->
Err.run (Low_level.unlink ?dirfd ~dir:true) path
let stat t ~follow path =
Switch.run @@ fun sw ->
let open Low_level in
let flags = Low_level.Flags.Open.(generic_read + synchronise) in
let dis = Flags.Disposition.open_if in
let create = Flags.Create.non_directory in
let fd = Err.run (openat ~sw ~nofollow:(not follow) (resolve t path) flags dis) create in
Flow.Impl.stat fd
let read_dir t path =
(* todo: need fdopendir here to avoid races *)
let path = resolve t path in
@ -153,7 +162,7 @@ end = struct
let rename t old_path new_dir new_path =
match Handler.as_posix_dir new_dir with
| None -> invalid_arg "Target is not an eio_posix directory!"
| None -> invalid_arg "Target is not an eio_windows directory!"
| Some new_dir ->
with_parent_dir t old_path @@ fun old_dir old_path ->
with_parent_dir new_dir new_path @@ fun new_dir new_path ->

View File

@ -520,6 +520,22 @@ Unconfined:
```
# Stat
```ocaml
let try_stat path =
let stat ~follow =
match Eio.Path.stat ~follow path with
| info -> Fmt.str "@[<h>%a@]" Eio.File.Stat.pp_kind info.kind
| exception Eio.Io (e, _) -> Fmt.str "@[<h>%a@]" Eio.Exn.pp_err e
in
let a = stat ~follow:false in
let b = stat ~follow:true in
if a = b then
traceln "%a -> %s" Eio.Path.pp path a
else
traceln "%a -> %s / %s" Eio.Path.pp path a b
```
```ocaml
# run @@ fun env ->
let cwd = Eio.Stdenv.cwd env in
@ -533,6 +549,32 @@ Unconfined:
- : unit = ()
```
Fstatat:
```ocaml
# run @@ fun env ->
let cwd = Eio.Stdenv.cwd env in
Switch.run @@ fun sw ->
try_mkdir (cwd / "stat_subdir2");
Unix.symlink "stat_subdir2" "symlink";
Unix.symlink "missing" "broken-symlink";
try_stat (cwd / "stat_subdir2");
try_stat (cwd / "symlink");
try_stat (cwd / "broken-symlink");
try_stat cwd;
try_stat (cwd / "..");
Unix.symlink ".." "parent-symlink";
try_stat (cwd / "parent-symlink");
+mkdir <cwd:stat_subdir2> -> ok
+<cwd:stat_subdir2> -> directory
+<cwd:symlink> -> symbolic link / directory
+<cwd:broken-symlink> -> symbolic link / Fs Not_found _
+<cwd> -> directory
+<cwd:..> -> Fs Permission_denied _
+<cwd:parent-symlink> -> symbolic link / Fs Permission_denied _
- : unit = ()
```
# pread/pwrite
Check reading and writing vectors at arbitrary offsets: