unix: add Cstruct_unix.{read,write,writev,send,recv}#302
unix: add Cstruct_unix.{read,write,writev,send,recv}#302djs55 wants to merge 18 commits intomirage:mainfrom
Conversation
This is useful for direct-style code to avoid copying Cstructs into temporary byte buffers. Signed-off-by: David Scott <dave@recoil.org>
|
Me gusta ! I've been carrying this around for ages: https://github.com/haesbaert/rawlink/blob/520207190814ebfc0db9fd4f6d9c6e0f1297732d/lib/rawlink_stubs.c#L466 |
unix/unix_cstruct.ml
Outdated
| | remaining -> | ||
| (* write at most iov_max at a time *) | ||
| let to_send = first iov_max [] remaining in | ||
| let n = stub_writev fd (List.map (fun x -> x.Cstruct.buffer, x.Cstruct.off, x.Cstruct.len) to_send) in |
There was a problem hiding this comment.
do you expand to a tuple here because the Cstruct.t record type might change? Given this is in the same repository, it's probably safe enough to just pass the Cstruct record in directly to the FFI and save some allocations
There was a problem hiding this comment.
Yes that was it -- I was using these stubs in another repo. I'll drop the tuple since we're here.
unix/recv_stubs.c
Outdated
| int fd = Int_val(val_fd); | ||
|
|
||
| caml_release_runtime_system(); | ||
| n = recv(fd, buf, len, 0); |
There was a problem hiding this comment.
I'm not sure a recv with null flags is very useful, it becomes just read(2), (don't know about windows though).
There was a problem hiding this comment.
Good point, I’ve copied the flags code from your stubs :)
unix/recv_stubs.c
Outdated
| val_ofs = Field(val_c, 1); | ||
| val_len = Field(val_c, 2); | ||
|
|
||
| void *buf = (void *)Caml_ba_data_val(val_buf) + Long_val(val_ofs); |
There was a problem hiding this comment.
minor bikeshedding, would move buf to the beginning of the block, declarations after statement is not valid C89 (not sure if anyone cares these days)
Also void * arithmetic is technically undefined behaviour (also not sure if anyone cares).
There was a problem hiding this comment.
I think void * is used here to be in-sync with what recv expects:
ssize_t recv(int sockfd, void *buf, size_t len, int flags);There was a problem hiding this comment.
Ack but passing pointer of any type to something that expects a void pointer is ok, my only bit was that we're doing (void *)++, which is undefined behavior (but every compiler implicitly assumes char *) so it's ok.
There was a problem hiding this comment.
https://stackoverflow.com/questions/3922958/void-arithmetic says it's a GCC extension, and attempting this should produce warnings.
unix/send_stubs.c
Outdated
|
|
||
| SOCKET s = Socket_val(val_fd); | ||
| caml_release_runtime_system(); | ||
| n = send(s, buf, len, 0); |
unix/write_stubs.c
Outdated
| val_len = Field(val_c, 2); | ||
| void *buf = (char *)Caml_ba_data_val(val_buf) + Long_val(val_ofs); | ||
| size_t len = Long_val(val_len); | ||
| int n = 0; |
| #include <limits.h> | ||
| #endif | ||
|
|
||
| CAMLprim |
There was a problem hiding this comment.
bikeshedding, the other stubs don't break line here
There was a problem hiding this comment.
Still breaking line here in stub_cstruct_iov_max
|
I can provide Cstruct_unix.recvfrom and Cstruct_unix.sendto if it's desirable, I just can't do the windows bits. |
Linux doesn't export IOV_MAX unless you define _XOPEN_SOURCE or _GNU_SOURCE (from @haesbaert) Co-authored-by: Christiano Haesbaert <haesbaert@haesbaert.org>
makes it easier to grep for the source of the error if we just name it after the function. (from @avsm) Co-authored-by: Anil Madhavapeddy <anil@recoil.org>
The recv/send tests looked a bit complex so I've changed mine, don't see why we would need threads since it's just one message.
unix/unix_cstruct.mli
Outdated
| If only a partial receive is possible, the return argument is now many bytes were received. *) | ||
|
|
||
| val recvfrom: Unix.file_descr -> Cstruct.t -> Unix.msg_flag list -> int * Unix.sockaddr | ||
| (** [recvfrom fd c] Like Unix.recvfrom, but for Cstruct. *) |
There was a problem hiding this comment.
| (** [recvfrom fd c] Like Unix.recvfrom, but for Cstruct. *) | |
| (** [recvfrom fd c] Like {! Unix.recvfrom}, but for Cstruct. *) |
will link to it on the ocaml.org docs then.
unix/recvfrom_stubs.c
Outdated
| caml_acquire_runtime_system(); | ||
|
|
||
| if (n == -1) | ||
| caml_uerror("recvfrom", Nothing); |
There was a problem hiding this comment.
| caml_uerror("recvfrom", Nothing); | |
| caml_uerror("cstruct_recvfrom", Nothing); |
I do find these Unix_errors are marginally more useful if you can grep for the function they came from
Signed-off-by: David Scott <dave@recoil.org>
- `caml_uerror` is defined in ocaml/ocaml#67a4d75cfafa040099cdd322e23464362359af29 and is in 5.0+ - for backwards compat ocaml 5.0 has #define uerror caml_uerror Signed-off-by: David Scott <dave@recoil.org>
This allows compat with OCaml 4.13 Signed-off-by: David Scott <dave@recoil.org>
Windows socket handles and error codes are managed differently. Signed-off-by: David Scott <dave@recoil.org>
Signed-off-by: David Scott <dave@recoil.org>
Signed-off-by: David Scott <dave@recoil.org>
There's no need to repack it as a tuple for the C bindings because the definition is in this repository. Signed-off-by: David Scott <dave@recoil.org>
Signed-off-by: David Scott <dave@recoil.org>
Signed-off-by: David Scott <dave@recoil.org>
Back to vi... Signed-off-by: David Scott <dave@recoil.org>
This became redundant when the re-packing of a Cstruct.t as a tuple was removed. Signed-off-by: David Scott <dave@recoil.org>
Signed-off-by: David Scott <dave@recoil.org>
Signed-off-by: David Scott <dave@recoil.org>
Signed-off-by: David Scott <dave@recoil.org>
| #endif | ||
|
|
||
| static int msg_flag_table[] = { | ||
| MSG_OOB, MSG_DONTROUTE, MSG_PEEK /* XXX */ |
There was a problem hiding this comment.
what's the XXX for here? Incomplete flags?
There was a problem hiding this comment.
Cause we assume how stdlib ordered things and also because we redefine them, would be nice if stdlib exposed them as not static so we could just use caml_msg_flag_table or something
There was a problem hiding this comment.
The stdlib does expose them -- as OCaml values. Just add a type definition in unix_cstruct.mli instead and get rid of the XXX:
type msg_flag = Unix.msg_flag = MSG_OOB | MSG_DONTROUTE | MSG_PEEK
This will create an alias to Unix.msg_flag that also checks that it's exactly the same as what is specified there.
Try for example, swapping two of the flags:
type t = Unix.msg_flag = MSG_OOB | MSG_PEEK | MSG_DONTROUTE;;
Error: This variant or record definition does not match that of type
Unix.msg_flag
Constructors MSG_DONTROUTE and MSG_PEEK have been swapped.
There was a problem hiding this comment.
Ah that's smart ! yes we should do that.
| let iov_max = stub_iov_max () | ||
|
|
||
| (* return the first n fragments, suitable for writev *) | ||
| let rec first n rev_acc = function |
There was a problem hiding this comment.
I mildly dislike having to do these potentially O(n) list operations for a writev, and mandate that in the external interface. In your vpnkit usecase, would you be able to pass in a Cstruct.t array instead for writev, and then just call writev with simple O(1) time slices instead?
It affects the interface we expose in the mli, hence my asking now (as opposed to optimising later)
| val_buf = Field(val_c, 0); | ||
| val_ofs = Field(val_c, 1); | ||
| val_len = Field(val_c, 2); | ||
| void *buf = (char *)Caml_ba_data_val(val_buf) + Long_val(val_ofs); |
There was a problem hiding this comment.
| void *buf = (char *)Caml_ba_data_val(val_buf) + Long_val(val_ofs); | |
| uint8_t *buf = (uint8_t *)Caml_ba_data_val(val_buf) + Long_val(val_ofs); |
There was a problem hiding this comment.
I approve this change, the FFI about bigarray says that it's an uint8_t array. We should keep this assumption on the C side too.
| #include <limits.h> | ||
| #endif | ||
|
|
||
| CAMLprim |
There was a problem hiding this comment.
Still breaking line here in stub_cstruct_iov_max
| length++; | ||
| /* Only copy up to the iovec array size */ | ||
| if (length > IOV_MAX) | ||
| length = IOV_MAX; |
There was a problem hiding this comment.
Silently truncating it might be worse than just letting the syscall failing later on in uerror with EINVAL.
You exposed the maximum, if the user goes above he should be punished with an exception imho.
It's a bit harmless for a STREAM that has to handle shortcounts, but a DATAGRAM will send half a packet.
(even if we're all a bit insane because IOV_MAX is huge)
There was a problem hiding this comment.
We should mostly mirror what writev(2) itself does, and that returns an EINVAL if > IOV_MAX. So I agree that a uerror should be raised if it's too large, and not a silent truncation.
| val_buf = Field(head, 0); | ||
| val_ofs = Field(head, 1); | ||
| val_len = Field(head, 2); | ||
| iovec[i].iov_base = (char *)Caml_ba_data_val(val_buf) + Long_val(val_ofs); |
There was a problem hiding this comment.
| iovec[i].iov_base = (char *)Caml_ba_data_val(val_buf) + Long_val(val_ofs); | |
| iovec[i].iov_base = (uint8_t *)Caml_ba_data_val(val_buf) + Long_val(val_ofs); |
|
A release should be done soon but this PR has some remaining issues. As far as I can say, it's mostly about style. @djs55 can you do a review of them? |
|
Just thought I would mention that I will try to get a |
|
I haven't worked out a nice story for supporting |
I think it's ok when the module explicitely mention |
This is useful for direct-style code to avoid copying Cstructs into temporary byte buffers to use the
Unix.API.Signed-off-by: David Scott dave@recoil.org