Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Eio.Path.read_link #686

Merged
merged 1 commit into from
Feb 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions lib_eio/fs.ml
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ module Pi = struct
val unlink : t -> path -> unit
val rmdir : t -> path -> unit
val rename : t -> path -> _ dir -> path -> unit
val read_link : t -> path -> string
val pp : t Fmt.t
val native : t -> string -> string option
end
Expand Down
8 changes: 8 additions & 0 deletions lib_eio/path.ml
Original file line number Diff line number Diff line change
Expand Up @@ -211,3 +211,11 @@ let rec mkdirs ?(exists_ok=false) ~perm t =
);
try mkdir ~perm t
with Exn.Io (Fs.E Already_exists _, _) when exists_ok && is_directory t -> ()

let read_link t =
let (Resource.T (dir, ops), path) = t in
let module X = (val (Resource.get ops Fs.Pi.Dir)) in
try X.read_link dir path
with Exn.Io _ as ex ->
let bt = Printexc.get_raw_backtrace () in
Exn.reraise_with_context ex bt "reading target of symlink %a" pp t
3 changes: 3 additions & 0 deletions lib_eio/path.mli
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,9 @@ val is_directory : _ t -> bool

[is_directory t] is [kind ~follow:true t = `Directory]. *)

val read_link : _ t -> string
(** [read_link t] is the target of symlink [t]. *)

(** {1 Other} *)

val unlink : _ t -> unit
Expand Down
2 changes: 2 additions & 0 deletions lib_eio/unix/eio_unix.mli
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,8 @@ module Private : sig
module Rcfd = Rcfd

module Fork_action = Fork_action

val read_link : Fd.t option -> string -> string
end

module Pi = Pi
15 changes: 15 additions & 0 deletions lib_eio/unix/private.ml
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,18 @@ let run_in_systhread ?(label="systhread") fn =
Eio.Private.Suspend.enter label @@ fun _ctx enqueue ->
let _t : Thread.t = Thread.create (fun () -> enqueue (try Ok (fn ()) with exn -> Error exn)) () in
()

external eio_readlinkat : Unix.file_descr -> string -> Cstruct.t -> int = "eio_unix_readlinkat"

let read_link fd path =
match fd with
| None -> Unix.readlink path
| Some fd ->
Fd.use_exn "readlink" fd @@ fun fd ->
let rec aux size =
let buf = Cstruct.create_unsafe size in
let len = eio_readlinkat fd path buf in
if len < size then Cstruct.to_string ~len buf
else aux (size * 4)
in
aux 1024
37 changes: 35 additions & 2 deletions lib_eio/unix/stubs.c
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,53 @@

#include <unistd.h>
#include <fcntl.h>
#include <errno.h>

#include <caml/mlvalues.h>
#include <caml/unixsupport.h>
#include <caml/memory.h>
#include <caml/bigarray.h>

static void caml_stat_free_preserving_errno(void *ptr) {
int saved = errno;
caml_stat_free(ptr);
errno = saved;
}

CAMLprim value eio_unix_is_blocking(value v_fd) {
#ifdef _WIN32
// We should not call this function from Windows
uerror("Unsupported blocking check on Windows", Nothing);
caml_unix_error(EOPNOTSUPP, "Unsupported blocking check on Windows", Nothing);
#else
int fd = Int_val(v_fd);
int r = fcntl(fd, F_GETFL, 0);
if (r == -1)
uerror("fcntl", Nothing);
caml_uerror("fcntl", Nothing);

return Val_bool((r & O_NONBLOCK) == 0);
#endif
}

CAMLprim value eio_unix_readlinkat(value v_fd, value v_path, value v_cs) {
#ifdef _WIN32
caml_unix_error(EOPNOTSUPP, "readlinkat not supported on Windows", v_path);
#else
CAMLparam2(v_path, v_cs);
char *path;
value v_ba = Field(v_cs, 0);
value v_off = Field(v_cs, 1);
value v_len = Field(v_cs, 2);
char *buf = (char *)Caml_ba_data_val(v_ba) + Long_val(v_off);
size_t buf_size = Long_val(v_len);
int fd = Int_val(v_fd);
int ret;
caml_unix_check_path(v_path, "readlinkat");
path = caml_stat_strdup(String_val(v_path));
caml_enter_blocking_section();
ret = readlinkat(fd, path, buf, buf_size);
caml_leave_blocking_section();
caml_stat_free_preserving_errno(path);
if (ret == -1) caml_uerror("readlinkat", v_path);
CAMLreturn(Val_int(ret));
#endif
}
2 changes: 2 additions & 0 deletions lib_eio_linux/eio_linux.ml
Original file line number Diff line number Diff line change
Expand Up @@ -539,6 +539,8 @@ end = struct
let fd = Low_level.open_dir ~sw t.fd (if path = "" then "." else path) in
Low_level.read_dir fd

let read_link t path = Low_level.read_link t.fd path

let close t =
match t.fd with
| FD x -> Fd.close x
Expand Down
6 changes: 6 additions & 0 deletions lib_eio_linux/low_level.ml
Original file line number Diff line number Diff line change
Expand Up @@ -481,6 +481,12 @@ let read_dir fd =
in
Eio_unix.run_in_systhread ~label:"read_dir" (fun () -> read_all [] fd)

let read_link fd path =
try
with_parent_dir_fd fd path @@ fun parent leaf ->
Eio_unix.run_in_systhread ~label:"read_link" (fun () -> Eio_unix.Private.read_link (Some parent) leaf)
with Unix.Unix_error (code, name, arg) -> raise @@ Err.wrap_fs code name arg

(* https://www.iana.org/assignments/protocol-numbers/protocol-numbers.xhtml *)
let getaddrinfo ~service node =
let to_eio_sockaddr_t {Unix.ai_family; ai_addr; ai_socktype; ai_protocol; _ } =
Expand Down
4 changes: 4 additions & 0 deletions lib_eio_posix/fs.ml
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,10 @@ end = struct
Err.run Low_level.readdir path
|> Array.to_list

let read_link t path =
with_parent_dir t path @@ fun dirfd path ->
Err.run (Low_level.read_link ?dirfd) path

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!"
Expand Down
4 changes: 4 additions & 0 deletions lib_eio_posix/low_level.ml
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,10 @@ let readdir path =
let bt = Printexc.get_raw_backtrace () in
Unix.closedir h; Printexc.raise_with_backtrace ex bt

let read_link ?dirfd path =
in_worker_thread "read_link" @@ fun () ->
Eio_unix.Private.read_link dirfd path

external eio_readv : Unix.file_descr -> Cstruct.t array -> int = "caml_eio_posix_readv"
external eio_writev : Unix.file_descr -> Cstruct.t array -> int = "caml_eio_posix_writev"

Expand Down
1 change: 1 addition & 0 deletions lib_eio_posix/low_level.mli
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ external ctime_nsec : stat -> int = "ocaml_eio_posix_stat_ctime_nsec" [@@noalloc
external mtime_nsec : stat -> int = "ocaml_eio_posix_stat_mtime_nsec" [@@noalloc]

val realpath : string -> string
val read_link : ?dirfd:fd -> string -> string

val mkdir : ?dirfd:fd -> mode:int -> string -> unit
val unlink : ?dirfd:fd -> dir:bool -> string -> unit
Expand Down
4 changes: 4 additions & 0 deletions lib_eio_windows/fs.ml
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,10 @@ end = struct
Err.run Low_level.readdir path
|> Array.to_list

let read_link t path =
with_parent_dir t path @@ fun dirfd path ->
Err.run (Low_level.read_link ?dirfd) path

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_windows directory!"
Expand Down
4 changes: 4 additions & 0 deletions lib_eio_windows/low_level.ml
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,10 @@ let readdir path =
let bt = Printexc.get_raw_backtrace () in
Unix.closedir h; Printexc.raise_with_backtrace ex bt

let read_link ?dirfd path =
in_worker_thread @@ fun () ->
Eio_unix.Private.read_link dirfd path

external eio_readv : Unix.file_descr -> Cstruct.t array -> int = "caml_eio_windows_readv"

external eio_preadv : Unix.file_descr -> Cstruct.t array -> Optint.Int63.t -> int = "caml_eio_windows_preadv"
Expand Down
1 change: 1 addition & 0 deletions lib_eio_windows/low_level.mli
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ val fstat : fd -> Unix.LargeFile.stats
val lstat : string -> Unix.LargeFile.stats

val realpath : string -> string
val read_link : ?dirfd:fd -> string -> string

val mkdir : ?dirfd:fd -> ?nofollow:bool -> mode:int -> string -> unit
val unlink : ?dirfd:fd -> dir:bool -> string -> unit
Expand Down
26 changes: 26 additions & 0 deletions tests/fs.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,11 @@ let try_read_dir path =
| names -> traceln "read_dir %a -> %a" Path.pp path Fmt.Dump.(list string) names
| exception ex -> traceln "@[<h>%a@]" Eio.Exn.pp ex

let try_read_link path =
match Path.read_link path with
| target -> traceln "read_link %a -> %S" Path.pp path target
| exception ex -> traceln "@[<h>%a@]" Eio.Exn.pp ex

let try_unlink path =
match Path.unlink path with
| () -> traceln "unlink %a -> ok" Path.pp path
Expand Down Expand Up @@ -751,6 +756,27 @@ Unconfined:
- : unit = ()
```

# read_link

```ocaml
# run ~clear:["file"; "symlink"] @@ fun env ->
let fs = Eio.Stdenv.fs env in
let cwd = Eio.Stdenv.cwd env in
Switch.run @@ fun sw ->
Unix.symlink "file" "symlink";
try_read_link (cwd / "symlink");
try_read_link (fs / "symlink");
try_write_file (cwd / "file") "data" ~create:(`Exclusive 0o600);
try_read_link (cwd / "file");
try_read_link (cwd / "../unknown");
+read_link <cwd:symlink> -> "file"
+read_link <fs:symlink> -> "file"
+write <cwd:file> -> ok
+Eio.Io _, reading target of symlink <cwd:file>
+Eio.Io Fs Permission_denied _, reading target of symlink <cwd:../unknown>
- : unit = ()
```

# pread/pwrite

Check reading and writing vectors at arbitrary offsets:
Expand Down
Loading