-
-
Notifications
You must be signed in to change notification settings - Fork 2.9k
std.os read/write functions + sendfile #4611
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
Conversation
This changset adds a `sendfile(2)` syscall bindings to the linux bits component. Where available, the `sendfile64(2)` syscall will be transparently called. A wrapping function has also been added to the std.os to transform errno returns to Zig errors. Change-Id: I86769fc4382c0771e3656e7b21137bafd99a4411
* rework os.sendfile and add macosx support, and a fallback implementation for any OS. * fix sendto compile error * std.os write functions support partial writes. closes #3443. * std.os pread / pwrite functions can now return `error.Unseekable`. * std.fs.File read/write functions now have readAll/writeAll variants which loop to complete operations even when partial reads/writes happen. * Audit std.os read/write functions with respect to Linux returning EINVAL for lengths greater than 0x7fff0000. * std.os read/write shim functions do not unnecessarily loop. Since partial reads/writes are part of the API, the caller will be forced to loop anyway, and so that would just be code bloat. * Improve doc comments * Add a non-trivial test for std.os.sendfile * Fix std.os.pread on 32 bit Linux * Add missing SYS_sendfile bit on aarch64
if (amt_read == 0) return off; // EOF | ||
} else unreachable; // TODO https://github.com/ziglang/zig/issues/707 | ||
if (std.Target.current.os.tag == .windows) { | ||
// TODO does Windows have a way to read an io vector? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes. ReadFileScatter
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks- I will open an issue
} | ||
pub fn preadv(fd: fd_t, iov: []const iovec, offset: u64) PReadError!usize { | ||
const have_pread_but_not_preadv = switch (std.Target.current.os.tag) { | ||
.windows, .macosx, .ios, .watchos, .tvos => true, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
windows has NtReadFileScatter
/// If `iov.len` is larger than will fit in a `u31`, a partial write will occur. | ||
pub fn writev(fd: fd_t, iov: []const iovec_const) WriteError!usize { | ||
if (std.Target.current.os.tag == .windows) { | ||
// TODO does Windows have a way to write an io vector? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
} | ||
|
||
rw: { | ||
var buf: [8 * 4096]u8 = undefined; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should use a LinearFifo
here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It lets you gracefully handle partial reads and partial writes (which helps in both speed, number of syscalls and correctness)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This code is intentionally doing exactly one read/write and returning a partial write. The burden of proof is on you to show why LinearFifo would be better than this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ah sorry, I thought there was a loop here.
That'll come in the fallback path for copy_file_range
later :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Even copy_file_range
can return a partial write, so I'm not sure that's true- the loopy code is in std.fs.File
readAll()
and writeAll()
related functions. And std.io.OutStream.write
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wait... we do need a loop here. A partial read (non 0) should be followed by another read right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nope, any looping is redundant since the function can return partial writes, and so the callsite must do looping or itself return partials. In fact, it's actually providing more power and information to return on the first partial read or partial write, since that exposes how the underlying mechanism works. As an example, this is exploited by command line input reading a line from the terminal.
@@ -82,6 +82,7 @@ pub const SYS_pread64 = 67; | |||
pub const SYS_pwrite64 = 68; | |||
pub const SYS_preadv = 69; | |||
pub const SYS_pwritev = 70; | |||
pub const SYS_sendfile = 71; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
o.o how odd that this was missed!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah... i made some confused noises out loud when I saw that, lol
const amt_read = try pread(in_fd, buf[0..adjusted_count], in_offset); | ||
if (amt_read == 0) { | ||
if (count == 0) { | ||
// We have detected EOF from `in_fd`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should still write 0 bytes here.
This would have an effect if e.g. the previous "write" was a sendto
without MSG_EOR
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think there's value in considering this. Can you make that a separate proposal? I think all the read/write functions would need to be audited if this were the policy.
🤦♂️ CI is failing because |
Will also fail on freebsd due to upstream bug (see my other pr) |
=> #4612 |
This merges #4131 and then does the following:
implementation for any OS.
error.Unseekable
.which loop to complete operations even when partial reads/writes
happen.
EINVAL for lengths greater than 0x7fff0000.
partial reads/writes are part of the API, the caller will be forced
to loop anyway, and so that would just be code bloat.