Skip to content

Commit 42d4d9e

Browse files
committed
Add tests and fix creation modes
1 parent 8fa06b2 commit 42d4d9e

File tree

7 files changed

+219
-20
lines changed

7 files changed

+219
-20
lines changed

lib_eio_windows/eio_windows_stubs.c

Lines changed: 71 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ CAMLprim value caml_eio_windows_pwritev(value v_fd, value v_bufs, value v_offset
8484
// File-system operations
8585

8686
// We recreate an openat like function using NtCreateFile
87-
CAMLprim value caml_eio_windows_openat(value v_dirfd, value v_pathname, value v_open_flags, value v_create_disposition, value v_create_options)
87+
CAMLprim value caml_eio_windows_openat(value v_dirfd, value v_pathname, value v_desired_access, value v_create_disposition, value v_create_options)
8888
{
8989
CAMLparam2(v_dirfd, v_pathname);
9090
HANDLE h, dir;
@@ -121,7 +121,7 @@ CAMLprim value caml_eio_windows_openat(value v_dirfd, value v_pathname, value v_
121121
// Create the file
122122
r = NtCreatefile(
123123
&h,
124-
(GENERIC_READ | GENERIC_WRITE | SYNCHRONIZE),
124+
Int_val(v_desired_access),
125125
&obj_attr,
126126
&io_status,
127127
0, // Allocation size
@@ -135,14 +135,14 @@ CAMLprim value caml_eio_windows_openat(value v_dirfd, value v_pathname, value v_
135135

136136
// Free the allocated pathname
137137
caml_stat_free(pathname);
138-
138+
139139
if (h == INVALID_HANDLE_VALUE) {
140-
caml_win32_maperr(GetLastError());
140+
caml_win32_maperr(RtlNtStatusToDosError(r));
141141
uerror("openat", v_pathname);
142142
}
143143

144144
if (!NT_SUCCESS(r)) {
145-
caml_win32_maperr(GetLastError());
145+
caml_win32_maperr(RtlNtStatusToDosError(r));
146146
uerror("openat", Nothing);
147147
}
148148

@@ -154,9 +154,73 @@ CAMLprim value caml_eio_windows_mkdirat(value v_fd, value v_path, value v_perm)
154154
uerror("mkdirat is not supported on windows yet", Nothing);
155155
}
156156

157-
CAMLprim value caml_eio_windows_unlinkat(value v_fd, value v_path, value v_dir)
157+
CAMLprim value caml_eio_windows_unlinkat(value v_dirfd, value v_pathname, value v_dir)
158158
{
159-
uerror("unlinkat is not supported on windows yet", Nothing);
159+
CAMLparam2(v_dirfd, v_pathname);
160+
HANDLE h, dir;
161+
OBJECT_ATTRIBUTES obj_attr;
162+
IO_STATUS_BLOCK io_status;
163+
wchar_t *pathname;
164+
UNICODE_STRING relative;
165+
NTSTATUS r;
166+
167+
// Not sure what the overhead of this is, but it allows us to have low-level control
168+
// over file creation. In particular, we can specify the HANDLE to the parent directory
169+
// of a relative path a la openat.
170+
pNtCreateFile NtCreatefile = (pNtCreateFile)GetProcAddress(GetModuleHandle("ntdll.dll"), "NtCreateFile");
171+
caml_unix_check_path(v_pathname, "openat");
172+
pathname = caml_stat_strdup_to_utf16(String_val(v_pathname));
173+
RtlInitUnicodeString(&relative, pathname);
174+
175+
// If NULL the filepath has to be absolute
176+
if (Is_some(v_dirfd)) {
177+
dir = Handle_val(Field(v_dirfd, 0));
178+
} else {
179+
dir = NULL;
180+
}
181+
182+
// Initialise object attributes, passing in the root directory FD
183+
InitializeObjectAttributes(
184+
&obj_attr,
185+
&relative,
186+
OBJ_CASE_INSENSITIVE, // TODO: Double-check what flags need to be passed at this point.
187+
dir,
188+
NULL
189+
);
190+
191+
// Create the file
192+
r = NtCreatefile(
193+
&h,
194+
(SYNCHRONIZE | DELETE),
195+
&obj_attr,
196+
&io_status,
197+
0, // Allocation size
198+
FILE_ATTRIBUTE_NORMAL, // TODO: Could check flags to see if we can do READONLY here a la OCaml
199+
(FILE_SHARE_DELETE),
200+
FILE_OPEN,
201+
((Bool_val(v_dir) ? FILE_DIRECTORY_FILE : FILE_NON_DIRECTORY_FILE) | FILE_SYNCHRONOUS_IO_NONALERT | FILE_DELETE_ON_CLOSE),
202+
NULL, // Extended attribute buffer
203+
0 // Extended attribute buffer length
204+
);
205+
206+
// Free the allocated pathname
207+
caml_stat_free(pathname);
208+
209+
if (h == INVALID_HANDLE_VALUE) {
210+
caml_win32_maperr(RtlNtStatusToDosError(r));
211+
uerror("openat", v_pathname);
212+
}
213+
214+
if (!NT_SUCCESS(r)) {
215+
caml_win32_maperr(RtlNtStatusToDosError(r));
216+
uerror("openat", Nothing);
217+
}
218+
219+
// Now close the file to delete it
220+
BOOL closed;
221+
closed = CloseHandle(h);
222+
223+
CAMLreturn(Val_unit);
160224
}
161225

162226
CAMLprim value caml_eio_windows_renameat(value v_old_fd, value v_old_path, value v_new_fd, value v_new_path)

lib_eio_windows/fs.ml

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -60,23 +60,22 @@ class virtual dir ~label = object (self)
6060

6161
method open_in ~sw path =
6262
let open Low_level in
63-
let fd = Err.run (Low_level.openat ~sw (self#resolve path)) Low_level.Flags.Open.(rdonly) Flags.Disposition.(open_if) Flags.Create.(non_directory) in
63+
let fd = Err.run (Low_level.openat ~sw (self#resolve path)) Low_level.Flags.Open.(generic_read + synchronise) Flags.Disposition.(open_if) Flags.Create.(non_directory) in
6464
(Flow.of_fd fd :> <Eio.File.ro; Eio.Flow.close>)
6565

6666
method open_out ~sw ~append ~create path =
6767
let open Low_level in
68-
let _mode, flags =
68+
let _mode, disp =
6969
match create with
70-
| `Never -> 0, Low_level.Flags.Open.empty
71-
| `If_missing perm -> perm, Low_level.Flags.Open.creat
72-
| `Or_truncate perm -> perm, Low_level.Flags.Open.(creat + trunc)
73-
| `Exclusive perm -> perm, Low_level.Flags.Open.(creat + excl)
70+
| `Never -> 0, Low_level.Flags.Disposition.open_
71+
| `If_missing perm -> perm, Low_level.Flags.Disposition.open_if
72+
| `Or_truncate perm -> perm, Low_level.Flags.Disposition.overwrite_if
73+
| `Exclusive perm -> perm, Low_level.Flags.Disposition.create
7474
in
75-
let flags = if append then Low_level.Flags.Open.(flags + append) else flags in
76-
let flags = Low_level.Flags.Open.(flags + rdwr + opt_nofollow) in
75+
let flags = if append then Low_level.Flags.Open.(synchronise + append) else Low_level.Flags.Open.(generic_write + synchronise) in
7776
match
7877
self#with_parent_dir path @@ fun dirfd path ->
79-
Low_level.openat ?dirfd ~sw path flags Flags.Disposition.(open_if) Flags.Create.(non_directory)
78+
Low_level.openat ?dirfd ~sw path flags disp Flags.Create.(non_directory)
8079
with
8180
| fd -> (Flow.of_fd fd :> <Eio.File.rw; Eio.Flow.close>)
8281
| exception Unix.Unix_error (ELOOP, _, _) ->
@@ -168,7 +167,7 @@ and sandbox ~label dir_path = object (self)
168167
let dir = self#resolve dir in
169168
Switch.run @@ fun sw ->
170169
let open Low_level in
171-
let dirfd = Low_level.openat ~sw dir Flags.Open.(rdonly) Flags.Disposition.(open_if) Flags.Create.(directory) in
170+
let dirfd = Low_level.openat ~sw dir Flags.Open.(generic_read + synchronise) Flags.Disposition.(open_if) Flags.Create.(directory) in
172171
fn (Some dirfd) leaf
173172
)
174173
end

lib_eio_windows/include/discover.ml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ let () =
44
C.main ~name:"discover" (fun c ->
55
let defs =
66
C.C_define.import c ~c_flags:["-D_LARGEFILE64_SOURCE"]
7-
~includes:["sys/types.h"; "sys/stat.h"; "fcntl.h"; "ntdef.h"]
7+
~includes:["sys/types.h"; "sys/stat.h"; "fcntl.h"; "winternl.h"; "ntdef.h"]
88
C.C_define.Type.[
99
"_O_RDONLY", Int;
1010
"_O_RDWR", Int;
@@ -15,6 +15,12 @@ let () =
1515
"_O_TRUNC", Int;
1616
"_O_EXCL", Int;
1717

18+
(* Desired Access *)
19+
"GENERIC_READ", Int;
20+
"GENERIC_WRITE", Int;
21+
"SYNCHRONIZE", Int;
22+
"FILE_APPEND_DATA", Int;
23+
1824
(* Create Disposition *)
1925
"FILE_SUPERSEDE", Int;
2026
"FILE_CREATE", Int;

lib_eio_windows/low_level.ml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,12 +158,16 @@ module Flags = struct
158158
let rdonly = Config.o_rdonly
159159
let rdwr = Config.o_rdwr
160160
let wronly = Config.o_wronly
161-
let append = Config.o_append
162161
let cloexec = Config.o_noinherit
163162
let creat = Config.o_creat
164163
let excl = Config.o_excl
165164
let trunc = Config.o_trunc
166165

166+
let generic_read = Config.generic_read
167+
let generic_write = Config.generic_write
168+
let synchronise = Config.synchronize
169+
let append = Config.file_append_data
170+
167171
let empty = 0
168172
let ( + ) = ( lor )
169173
end

lib_eio_windows/low_level.mli

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,11 +60,15 @@ module Flags : sig
6060
val rdonly : t
6161
val rdwr : t
6262
val wronly : t
63-
val append : t
6463
val creat : t
6564
val excl : t
6665
val trunc : t
6766

67+
val generic_read : t
68+
val generic_write : t
69+
val synchronise : t
70+
val append : t
71+
6872
val empty : t
6973
val ( + ) : t -> t -> t
7074
end

lib_eio_windows/test/test.ml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ let () =
5353
Eio_windows.run @@ fun env ->
5454
Alcotest.run "eio_windows" [
5555
"net", Test_net.tests env;
56+
"fs", Test_fs.tests env;
5657
"timeout", Timeout.tests env;
5758
"random", Random.tests env;
5859
"dla", Dla.tests

lib_eio_windows/test/test_fs.ml

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
module Int63 = Optint.Int63
2+
module Path = Eio.Path
3+
4+
let () = Eio.Exn.Backend.show := false
5+
6+
open Eio.Std
7+
8+
let ( / ) = Path.( / )
9+
10+
let try_read_file path =
11+
match Path.load path with
12+
| s -> traceln "read %a -> %S" Path.pp path s
13+
| exception ex -> traceln "@[<h>%a@]" Eio.Exn.pp ex
14+
15+
let try_write_file ~create ?append path content =
16+
match Path.save ~create ?append path content with
17+
| () -> traceln "write %a -> ok" Path.pp path
18+
| exception ex -> traceln "@[<h>%a@]" Eio.Exn.pp ex
19+
20+
let try_mkdir path =
21+
match Path.mkdir path ~perm:0o700 with
22+
| () -> traceln "mkdir %a -> ok" Path.pp path
23+
| exception ex -> traceln "@[<h>%a@]" Eio.Exn.pp ex
24+
25+
let try_rename p1 p2 =
26+
match Path.rename p1 p2 with
27+
| () -> traceln "rename %a to %a -> ok" Path.pp p1 Path.pp p2
28+
| exception ex -> traceln "@[<h>%a@]" Eio.Exn.pp ex
29+
30+
let try_read_dir path =
31+
match Path.read_dir path with
32+
| names -> traceln "read_dir %a -> %a" Path.pp path Fmt.Dump.(list string) names
33+
| exception ex -> traceln "@[<h>%a@]" Eio.Exn.pp ex
34+
35+
let try_unlink path =
36+
match Path.unlink path with
37+
| () -> traceln "unlink %a -> ok" Path.pp path
38+
| exception ex -> traceln "@[<h>%a@]" Eio.Exn.pp ex
39+
40+
let try_rmdir path =
41+
match Path.rmdir path with
42+
| () -> traceln "rmdir %a -> ok" Path.pp path
43+
| exception ex -> traceln "@[<h>%a@]" Eio.Exn.pp ex
44+
45+
let with_temp_file path fn =
46+
Fun.protect (fun () -> fn path) ~finally:(fun () -> Eio.Path.unlink path)
47+
48+
let chdir path =
49+
traceln "chdir %S" path;
50+
Unix.chdir path
51+
52+
let assert_kind path kind =
53+
Path.with_open_in path @@ fun file ->
54+
assert ((Eio.File.stat file).kind = kind)
55+
56+
let test_create_and_read env () =
57+
let cwd = Eio.Stdenv.cwd env in
58+
let data = "my-data" in
59+
with_temp_file (cwd / "test-file") @@ fun path ->
60+
Path.save ~create:(`Exclusive 0o666) path data;
61+
Alcotest.(check string) "same data" data (Path.load path)
62+
63+
let test_cwd_no_access_abs env () =
64+
let cwd = Eio.Stdenv.cwd env in
65+
let temp = Filename.temp_file "eio" "win" in
66+
try
67+
Path.save ~create:(`Exclusive 0o666) (cwd / temp) "my-data";
68+
failwith "Should have failed"
69+
with Eio.Io (Eio.Fs.E (Permission_denied _), _) -> ()
70+
71+
let test_exclusive env () =
72+
let cwd = Eio.Stdenv.cwd env in
73+
with_temp_file (cwd / "test-file") @@ fun path ->
74+
Path.save ~create:(`Exclusive 0o666) path "first-write";
75+
try
76+
Path.save ~create:(`Exclusive 0o666) path "first-write";
77+
failwith "Should have failed"
78+
with Eio.Io (Eio.Fs.E (Already_exists _), _) -> ()
79+
80+
let test_if_missing env () =
81+
let cwd = Eio.Stdenv.cwd env in
82+
let test_file = (cwd / "test-file") in
83+
with_temp_file test_file @@ fun test_file ->
84+
Path.save ~create:(`If_missing 0o666) test_file "1st-write-original";
85+
Path.save ~create:(`If_missing 0o666) test_file "2nd-write";
86+
Alcotest.(check string) "same contents" "2nd-write-original" (Path.load test_file)
87+
88+
let test_trunc env () =
89+
let cwd = Eio.Stdenv.cwd env in
90+
let test_file = (cwd / "test-file") in
91+
with_temp_file test_file @@ fun test_file ->
92+
Path.save ~create:(`Or_truncate 0o666) test_file "1st-write-original";
93+
Path.save ~create:(`Or_truncate 0o666) test_file "2nd-write";
94+
Alcotest.(check string) "same contents" "2nd-write" (Path.load test_file)
95+
96+
let test_empty env () =
97+
let cwd = Eio.Stdenv.cwd env in
98+
let test_file = (cwd / "test-file") in
99+
try
100+
Path.save ~create:`Never test_file "1st-write-original";
101+
traceln "Got %S" @@ Path.load test_file;
102+
failwith "Should have failed"
103+
with Eio.Io (Eio.Fs.E (Not_found _), _) -> ()
104+
105+
let test_append env () =
106+
let cwd = Eio.Stdenv.cwd env in
107+
let test_file = (cwd / "test-file") in
108+
with_temp_file test_file @@ fun test_file ->
109+
Path.save ~create:(`Or_truncate 0o666) test_file "1st-write-original";
110+
Path.save ~create:`Never ~append:true test_file "2nd-write";
111+
Alcotest.(check string) "append" "1st-write-original2nd-write" (Path.load test_file)
112+
113+
let tests env = [
114+
"create-write-read", `Quick, test_create_and_read env;
115+
"cwd-abs-path", `Quick, test_cwd_no_access_abs env;
116+
"create-exclusive", `Quick, test_exclusive env;
117+
"create-if_missing", `Quick, test_if_missing env;
118+
"create-trunc", `Quick, test_trunc env;
119+
"create-empty", `Quick, test_empty env;
120+
"append", `Quick, test_append env;
121+
]

0 commit comments

Comments
 (0)