mirror of
https://github.com/ocaml-multicore/eio.git
synced 2025-07-19 00:01:28 -04:00
Compare commits
4 Commits
f1be218b26
...
9256754fe8
Author | SHA1 | Date | |
---|---|---|---|
|
9256754fe8 | ||
|
9e445f19a1 | ||
|
afb83bf1fa | ||
|
1938ce5ad0 |
@ -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 = ..
|
||||
|
@ -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]
|
||||
|
@ -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]
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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 ->
|
||||
|
@ -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 =
|
||||
|
@ -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
|
||||
|
@ -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 ->
|
||||
|
42
tests/fs.md
42
tests/fs.md
@ -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:
|
||||
|
Loading…
x
Reference in New Issue
Block a user