mirror of
https://github.com/ocaml-multicore/eio.git
synced 2025-07-20 00:01:05 -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
|
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. *)
|
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. *)
|
(** Extensible backend-specific exceptions. *)
|
||||||
module Backend : sig
|
module Backend : sig
|
||||||
type t = ..
|
type t = ..
|
||||||
|
@ -16,6 +16,16 @@ module Stat = struct
|
|||||||
| `Socket
|
| `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 = {
|
type t = {
|
||||||
dev : Int64.t;
|
dev : Int64.t;
|
||||||
ino : Int64.t;
|
ino : Int64.t;
|
||||||
@ -30,6 +40,22 @@ module Stat = struct
|
|||||||
mtime : float;
|
mtime : float;
|
||||||
ctime : 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
|
end
|
||||||
|
|
||||||
type ro_ty = [`File | Flow.source_ty | Resource.close_ty]
|
type ro_ty = [`File | Flow.source_ty | Resource.close_ty]
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
open Std
|
open Std
|
||||||
|
|
||||||
(** Tranditional Unix permissions. *)
|
(** Traditional Unix permissions. *)
|
||||||
module Unix_perm : sig
|
module Unix_perm : sig
|
||||||
type t = int
|
type t = int
|
||||||
(** This is the same as {!Unix.file_perm}, but avoids a dependency on [Unix]. *)
|
(** 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. **)
|
(** Kind of file from st_mode. **)
|
||||||
|
|
||||||
|
val pp_kind : kind Fmt.t
|
||||||
|
(** Pretty printer for {! kind}. *)
|
||||||
|
|
||||||
type t = {
|
type t = {
|
||||||
dev : Int64.t;
|
dev : Int64.t; (** Device containing the filesystem where the file resides. *)
|
||||||
ino : Int64.t;
|
ino : Int64.t; (** Inode number. *)
|
||||||
kind : kind;
|
kind : kind; (** File type. *)
|
||||||
perm : Unix_perm.t;
|
perm : Unix_perm.t; (** Permissions (mode). *)
|
||||||
nlink : Int64.t;
|
nlink : Int64.t; (** Number of hard links. *)
|
||||||
uid : Int64.t;
|
uid : Int64.t; (** User ID of owner. *)
|
||||||
gid : Int64.t;
|
gid : Int64.t; (** Group ID of owner. *)
|
||||||
rdev : Int64.t;
|
rdev : Int64.t; (** Device's ID (if this is a device). *)
|
||||||
size : Optint.Int63.t;
|
size : Optint.Int63.t; (** Total size in bytes. *)
|
||||||
atime : float;
|
atime : float; (** Last access time. *)
|
||||||
mtime : float;
|
mtime : float; (** Last modification time. *)
|
||||||
ctime : float;
|
ctime : float; (** Creation time. *)
|
||||||
}
|
}
|
||||||
(** Like stat(2). *)
|
(** Like stat(2). *)
|
||||||
|
|
||||||
|
val pp : t Fmt.t
|
||||||
|
(** Pretty printer for {! t}. *)
|
||||||
end
|
end
|
||||||
|
|
||||||
type ro_ty = [`File | Flow.source_ty | Resource.close_ty]
|
type ro_ty = [`File | Flow.source_ty | Resource.close_ty]
|
||||||
|
@ -42,8 +42,8 @@ type create = [
|
|||||||
|
|
||||||
type dir_ty = [`Dir]
|
type dir_ty = [`Dir]
|
||||||
type 'a dir = ([> dir_ty] as 'a) r
|
type 'a dir = ([> dir_ty] as 'a) r
|
||||||
|
|
||||||
(** Note: use the functions in {!Path} to access directories. *)
|
(** Note: use the functions in {!Path} to access directories. *)
|
||||||
|
|
||||||
module Pi = struct
|
module Pi = struct
|
||||||
module type DIR = sig
|
module type DIR = sig
|
||||||
type t
|
type t
|
||||||
@ -60,6 +60,7 @@ module Pi = struct
|
|||||||
val mkdir : t -> perm:File.Unix_perm.t -> path -> unit
|
val mkdir : t -> perm:File.Unix_perm.t -> path -> unit
|
||||||
val open_dir : t -> sw:Switch.t -> path -> [`Close | dir_ty] r
|
val open_dir : t -> sw:Switch.t -> path -> [`Close | dir_ty] r
|
||||||
val read_dir : t -> path -> string list
|
val read_dir : t -> path -> string list
|
||||||
|
val stat : t -> follow:bool -> string -> File.Stat.t
|
||||||
val unlink : t -> path -> unit
|
val unlink : t -> path -> unit
|
||||||
val rmdir : t -> path -> unit
|
val rmdir : t -> path -> unit
|
||||||
val rename : t -> path -> _ dir -> path -> unit
|
val rename : t -> path -> _ dir -> path -> unit
|
||||||
|
@ -61,6 +61,14 @@ let read_dir t =
|
|||||||
let bt = Printexc.get_raw_backtrace () in
|
let bt = Printexc.get_raw_backtrace () in
|
||||||
Exn.reraise_with_context ex bt "reading directory %a" pp t
|
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 =
|
let with_open_in path fn =
|
||||||
Switch.run @@ fun sw -> fn (open_in ~sw path)
|
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. *)
|
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} *)
|
(** {1 Other} *)
|
||||||
|
|
||||||
val unlink : _ t -> unit
|
val unlink : _ t -> unit
|
||||||
|
@ -526,6 +526,34 @@ end = struct
|
|||||||
let unlink t path = Low_level.unlink ~rmdir:false t.fd path
|
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 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 =
|
let rename t old_path t2 new_path =
|
||||||
match get_dir_fd_opt t2 with
|
match get_dir_fd_opt t2 with
|
||||||
| Some fd2 -> Low_level.rename t.fd old_path fd2 new_path
|
| Some fd2 -> Low_level.rename t.fd old_path fd2 new_path
|
||||||
|
@ -350,25 +350,43 @@ let getrandom { Cstruct.buffer; off; len } =
|
|||||||
in
|
in
|
||||||
loop 0
|
loop 0
|
||||||
|
|
||||||
(* [with_parent_dir dir path fn] runs [fn parent (basename path)],
|
(* [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]. *)
|
where [parent] is a path FD for [path]'s parent, resolved using [Resolve.beneath].
|
||||||
let with_parent_dir op dir path fn =
|
|
||||||
|
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 dir_path = Filename.dirname path in
|
||||||
let leaf = Filename.basename path in
|
let leaf = Filename.basename path in
|
||||||
Switch.run (fun sw ->
|
Switch.run (fun sw ->
|
||||||
let parent =
|
|
||||||
match dir with
|
match dir with
|
||||||
| FD d when dir_path = "." -> d
|
| _ 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
|
openat ~sw ~seekable:false dir dir_path
|
||||||
~access:`R
|
~access:`R
|
||||||
~flags:Uring.Open_flags.(cloexec + path + directory)
|
~flags:Uring.Open_flags.(cloexec + path + directory)
|
||||||
~perm:0
|
~perm:0
|
||||||
in
|
in
|
||||||
Fd.use_exn op parent @@ fun parent ->
|
|
||||||
fn parent leaf
|
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 statx ?fd ~mask path buf flags =
|
||||||
let res =
|
let res =
|
||||||
match fd with
|
match fd with
|
||||||
@ -379,6 +397,23 @@ let statx ?fd ~mask path buf flags =
|
|||||||
in
|
in
|
||||||
if res <> 0 then raise @@ Err.wrap_fs (Uring.error_of_errno res) "statx" path
|
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 =
|
let mkdir_beneath ~perm dir path =
|
||||||
(* [mkdir] is really an operation on [path]'s parent. Get a reference to that first: *)
|
(* [mkdir] is really an operation on [path]'s parent. Get a reference to that first: *)
|
||||||
with_parent_dir "mkdir" dir path @@ fun parent leaf ->
|
with_parent_dir "mkdir" dir path @@ fun parent leaf ->
|
||||||
|
@ -2,12 +2,7 @@ open Eio.Std
|
|||||||
|
|
||||||
module Fd = Eio_unix.Fd
|
module Fd = Eio_unix.Fd
|
||||||
|
|
||||||
module Impl = struct
|
let float_of_time s ns =
|
||||||
type tag = [`Generic | `Unix]
|
|
||||||
|
|
||||||
type t = Eio_unix.Fd.t
|
|
||||||
|
|
||||||
let float_of_time s ns =
|
|
||||||
let s = Int64.to_float s in
|
let s = Int64.to_float s in
|
||||||
let f = s +. (float ns /. 1e9) in
|
let f = s +. (float ns /. 1e9) in
|
||||||
(* It's possible that we might round up to the next second.
|
(* It's possible that we might round up to the next second.
|
||||||
@ -16,10 +11,7 @@ module Impl = struct
|
|||||||
if floor f = s then f
|
if floor f = s then f
|
||||||
else Float.pred f
|
else Float.pred f
|
||||||
|
|
||||||
let stat t =
|
let eio_of_stat x =
|
||||||
try
|
|
||||||
let x = Low_level.create_stat () in
|
|
||||||
Low_level.fstat ~buf:x t;
|
|
||||||
{ Eio.File.Stat.
|
{ Eio.File.Stat.
|
||||||
dev = Low_level.dev x;
|
dev = Low_level.dev x;
|
||||||
ino = Low_level.ino x;
|
ino = Low_level.ino x;
|
||||||
@ -34,6 +26,17 @@ module Impl = struct
|
|||||||
mtime = float_of_time (Low_level.mtime_sec x) (Low_level.mtime_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);
|
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 stat t =
|
||||||
|
try
|
||||||
|
let x = Low_level.create_stat () in
|
||||||
|
Low_level.fstat ~buf:x t;
|
||||||
|
eio_of_stat x
|
||||||
with Unix.Unix_error (code, name, arg) -> raise @@ Err.wrap code name arg
|
with Unix.Unix_error (code, name, arg) -> raise @@ Err.wrap code name arg
|
||||||
|
|
||||||
let single_write t bufs =
|
let single_write t bufs =
|
||||||
|
@ -137,6 +137,16 @@ end = struct
|
|||||||
with_parent_dir t path @@ fun dirfd path ->
|
with_parent_dir t path @@ fun dirfd path ->
|
||||||
Err.run (Low_level.unlink ?dirfd ~dir:true) 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 =
|
let read_dir t path =
|
||||||
(* todo: need fdopendir here to avoid races *)
|
(* todo: need fdopendir here to avoid races *)
|
||||||
let path = resolve t path in
|
let path = resolve t path in
|
||||||
|
@ -145,6 +145,15 @@ end = struct
|
|||||||
with_parent_dir t path @@ fun dirfd path ->
|
with_parent_dir t path @@ fun dirfd path ->
|
||||||
Err.run (Low_level.unlink ?dirfd ~dir:true) 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 =
|
let read_dir t path =
|
||||||
(* todo: need fdopendir here to avoid races *)
|
(* todo: need fdopendir here to avoid races *)
|
||||||
let path = resolve t path in
|
let path = resolve t path in
|
||||||
@ -153,7 +162,7 @@ end = struct
|
|||||||
|
|
||||||
let rename t old_path new_dir new_path =
|
let rename t old_path new_dir new_path =
|
||||||
match Handler.as_posix_dir new_dir with
|
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 ->
|
| Some new_dir ->
|
||||||
with_parent_dir t old_path @@ fun old_dir old_path ->
|
with_parent_dir t old_path @@ fun old_dir old_path ->
|
||||||
with_parent_dir new_dir new_path @@ fun new_dir new_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
|
# 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
|
```ocaml
|
||||||
# run @@ fun env ->
|
# run @@ fun env ->
|
||||||
let cwd = Eio.Stdenv.cwd env in
|
let cwd = Eio.Stdenv.cwd env in
|
||||||
@ -533,6 +549,32 @@ Unconfined:
|
|||||||
- : unit = ()
|
- : 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
|
# pread/pwrite
|
||||||
|
|
||||||
Check reading and writing vectors at arbitrary offsets:
|
Check reading and writing vectors at arbitrary offsets:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user