authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2020-03-03 10:15:37-05:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2020-03-03 10:15:37-05:00
log226b801830857a9075fdd8180739105bb73eed0e
tree48c684f434c6a44d2e0684adc69b31862dcb90ab
parent387418277a4964714ddaec3336a602ec87dde0f9
parent9d6cc75ce3be9ab291614fc0d4361877e9200126
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

Merge pull request #4612 from ziglang/os-read-write-sendfile

std.os read/write functions + sendfile

21 files changed, 855 insertions(+), 252 deletions(-)

lib/std/c.zig+1-1
...@@ -142,7 +142,7 @@ pub extern "c" fn sendto(...@@ -142,7 +142,7 @@ pub extern "c" fn sendto(
142 buf: *const c_void,142 buf: *const c_void,
143 len: usize,143 len: usize,
144 flags: u32,144 flags: u32,
145 dest_addr: *const sockaddr,145 dest_addr: ?*const sockaddr,
146 addrlen: socklen_t,146 addrlen: socklen_t,
147) isize;147) isize;
148148
lib/std/c/darwin.zig+16
...@@ -55,6 +55,22 @@ pub extern "c" fn clock_get_time(clock_serv: clock_serv_t, cur_time: *mach_times...@@ -55,6 +55,22 @@ pub extern "c" fn clock_get_time(clock_serv: clock_serv_t, cur_time: *mach_times
55pub extern "c" fn host_get_clock_service(host: host_t, clock_id: clock_id_t, clock_serv: ?[*]clock_serv_t) kern_return_t;55pub extern "c" fn host_get_clock_service(host: host_t, clock_id: clock_id_t, clock_serv: ?[*]clock_serv_t) kern_return_t;
56pub extern "c" fn mach_port_deallocate(task: ipc_space_t, name: mach_port_name_t) kern_return_t;56pub extern "c" fn mach_port_deallocate(task: ipc_space_t, name: mach_port_name_t) kern_return_t;
5757
58pub const sf_hdtr = extern struct {
59 headers: [*]const iovec_const,
60 hdr_cnt: c_int,
61 trailers: [*]const iovec_const,
62 trl_cnt: c_int,
63};
64
65pub extern "c" fn sendfile(
66 out_fd: fd_t,
67 in_fd: fd_t,
68 offset: off_t,
69 len: *off_t,
70 sf_hdtr: ?*sf_hdtr,
71 flags: u32,
72) c_int;
73
58pub fn sigaddset(set: *sigset_t, signo: u5) void {74pub fn sigaddset(set: *sigset_t, signo: u5) void {
59 set.* |= @as(u32, 1) << (signo - 1);75 set.* |= @as(u32, 1) << (signo - 1);
60}76}
lib/std/c/freebsd.zig+16
...@@ -8,6 +8,22 @@ pub extern "c" fn getdents(fd: c_int, buf_ptr: [*]u8, nbytes: usize) usize;...@@ -8,6 +8,22 @@ pub extern "c" fn getdents(fd: c_int, buf_ptr: [*]u8, nbytes: usize) usize;
8pub extern "c" fn sigaltstack(ss: ?*stack_t, old_ss: ?*stack_t) c_int;8pub extern "c" fn sigaltstack(ss: ?*stack_t, old_ss: ?*stack_t) c_int;
9pub extern "c" fn getrandom(buf_ptr: [*]u8, buf_len: usize, flags: c_uint) isize;9pub extern "c" fn getrandom(buf_ptr: [*]u8, buf_len: usize, flags: c_uint) isize;
1010
11pub const sf_hdtr = extern struct {
12 headers: [*]const iovec_const,
13 hdr_cnt: c_int,
14 trailers: [*]const iovec_const,
15 trl_cnt: c_int,
16};
17pub extern "c" fn sendfile(
18 out_fd: fd_t,
19 in_fd: fd_t,
20 offset: ?*off_t,
21 nbytes: usize,
22 sf_hdtr: ?*sf_hdtr,
23 sbytes: ?*off_t,
24 flags: u32,
25) c_int;
26
11pub const dl_iterate_phdr_callback = extern fn (info: *dl_phdr_info, size: usize, data: ?*c_void) c_int;27pub const dl_iterate_phdr_callback = extern fn (info: *dl_phdr_info, size: usize, data: ?*c_void) c_int;
12pub extern "c" fn dl_iterate_phdr(callback: dl_iterate_phdr_callback, data: ?*c_void) c_int;28pub extern "c" fn dl_iterate_phdr(callback: dl_iterate_phdr_callback, data: ?*c_void) c_int;
1329
lib/std/c/linux.zig+7
...@@ -82,6 +82,13 @@ pub extern "c" fn sigaltstack(ss: ?*stack_t, old_ss: ?*stack_t) c_int;...@@ -82,6 +82,13 @@ pub extern "c" fn sigaltstack(ss: ?*stack_t, old_ss: ?*stack_t) c_int;
8282
83pub extern "c" fn memfd_create(name: [*:0]const u8, flags: c_uint) c_int;83pub extern "c" fn memfd_create(name: [*:0]const u8, flags: c_uint) c_int;
8484
85pub extern "c" fn sendfile(
86 out_fd: fd_t,
87 in_fd: fd_t,
88 offset: ?*off_t,
89 count: usize,
90) isize;
91
85pub const pthread_attr_t = extern struct {92pub const pthread_attr_t = extern struct {
86 __size: [56]u8,93 __size: [56]u8,
87 __align: c_long,94 __align: c_long,
lib/std/event/loop.zig+12-10
...@@ -236,7 +236,8 @@ pub const Loop = struct {...@@ -236,7 +236,8 @@ pub const Loop = struct {
236 var extra_thread_index: usize = 0;236 var extra_thread_index: usize = 0;
237 errdefer {237 errdefer {
238 // writing 8 bytes to an eventfd cannot fail238 // writing 8 bytes to an eventfd cannot fail
239 noasync os.write(self.os_data.final_eventfd, &wakeup_bytes) catch unreachable;239 const amt = noasync os.write(self.os_data.final_eventfd, &wakeup_bytes) catch unreachable;
240 assert(amt == wakeup_bytes.len);
240 while (extra_thread_index != 0) {241 while (extra_thread_index != 0) {
241 extra_thread_index -= 1;242 extra_thread_index -= 1;
242 self.extra_threads[extra_thread_index].wait();243 self.extra_threads[extra_thread_index].wait();
...@@ -682,7 +683,8 @@ pub const Loop = struct {...@@ -682,7 +683,8 @@ pub const Loop = struct {
682 .linux => {683 .linux => {
683 self.posixFsRequest(&self.os_data.fs_end_request);684 self.posixFsRequest(&self.os_data.fs_end_request);
684 // writing 8 bytes to an eventfd cannot fail685 // writing 8 bytes to an eventfd cannot fail
685 noasync os.write(self.os_data.final_eventfd, &wakeup_bytes) catch unreachable;686 const amt = noasync os.write(self.os_data.final_eventfd, &wakeup_bytes) catch unreachable;
687 assert(amt == wakeup_bytes.len);
686 return;688 return;
687 },689 },
688 .macosx, .freebsd, .netbsd, .dragonfly => {690 .macosx, .freebsd, .netbsd, .dragonfly => {
...@@ -831,7 +833,7 @@ pub const Loop = struct {...@@ -831,7 +833,7 @@ pub const Loop = struct {
831833
832 /// Performs an async `os.write` using a separate thread.834 /// Performs an async `os.write` using a separate thread.
833 /// `fd` must block and not return EAGAIN.835 /// `fd` must block and not return EAGAIN.
834 pub fn write(self: *Loop, fd: os.fd_t, bytes: []const u8) os.WriteError!void {836 pub fn write(self: *Loop, fd: os.fd_t, bytes: []const u8) os.WriteError!usize {
835 var req_node = Request.Node{837 var req_node = Request.Node{
836 .data = .{838 .data = .{
837 .msg = .{839 .msg = .{
...@@ -852,7 +854,7 @@ pub const Loop = struct {...@@ -852,7 +854,7 @@ pub const Loop = struct {
852854
853 /// Performs an async `os.writev` using a separate thread.855 /// Performs an async `os.writev` using a separate thread.
854 /// `fd` must block and not return EAGAIN.856 /// `fd` must block and not return EAGAIN.
855 pub fn writev(self: *Loop, fd: os.fd_t, iov: []const os.iovec_const) os.WriteError!void {857 pub fn writev(self: *Loop, fd: os.fd_t, iov: []const os.iovec_const) os.WriteError!usize {
856 var req_node = Request.Node{858 var req_node = Request.Node{
857 .data = .{859 .data = .{
858 .msg = .{860 .msg = .{
...@@ -873,7 +875,7 @@ pub const Loop = struct {...@@ -873,7 +875,7 @@ pub const Loop = struct {
873875
874 /// Performs an async `os.pwritev` using a separate thread.876 /// Performs an async `os.pwritev` using a separate thread.
875 /// `fd` must block and not return EAGAIN.877 /// `fd` must block and not return EAGAIN.
876 pub fn pwritev(self: *Loop, fd: os.fd_t, iov: []const os.iovec_const, offset: u64) os.WriteError!void {878 pub fn pwritev(self: *Loop, fd: os.fd_t, iov: []const os.iovec_const, offset: u64) os.WriteError!usize {
877 var req_node = Request.Node{879 var req_node = Request.Node{
878 .data = .{880 .data = .{
879 .msg = .{881 .msg = .{
...@@ -1137,7 +1139,7 @@ pub const Loop = struct {...@@ -1137,7 +1139,7 @@ pub const Loop = struct {
1137 pub const Write = struct {1139 pub const Write = struct {
1138 fd: os.fd_t,1140 fd: os.fd_t,
1139 bytes: []const u8,1141 bytes: []const u8,
1140 result: Error!void,1142 result: Error!usize,
11411143
1142 pub const Error = os.WriteError;1144 pub const Error = os.WriteError;
1143 };1145 };
...@@ -1145,7 +1147,7 @@ pub const Loop = struct {...@@ -1145,7 +1147,7 @@ pub const Loop = struct {
1145 pub const WriteV = struct {1147 pub const WriteV = struct {
1146 fd: os.fd_t,1148 fd: os.fd_t,
1147 iov: []const os.iovec_const,1149 iov: []const os.iovec_const,
1148 result: Error!void,1150 result: Error!usize,
11491151
1150 pub const Error = os.WriteError;1152 pub const Error = os.WriteError;
1151 };1153 };
...@@ -1154,9 +1156,9 @@ pub const Loop = struct {...@@ -1154,9 +1156,9 @@ pub const Loop = struct {
1154 fd: os.fd_t,1156 fd: os.fd_t,
1155 iov: []const os.iovec_const,1157 iov: []const os.iovec_const,
1156 offset: usize,1158 offset: usize,
1157 result: Error!void,1159 result: Error!usize,
11581160
1159 pub const Error = os.WriteError;1161 pub const Error = os.PWriteError;
1160 };1162 };
11611163
1162 pub const PReadV = struct {1164 pub const PReadV = struct {
...@@ -1165,7 +1167,7 @@ pub const Loop = struct {...@@ -1165,7 +1167,7 @@ pub const Loop = struct {
1165 offset: usize,1167 offset: usize,
1166 result: Error!usize,1168 result: Error!usize,
11671169
1168 pub const Error = os.ReadError;1170 pub const Error = os.PReadError;
1169 };1171 };
11701172
1171 pub const Open = struct {1173 pub const Open = struct {
lib/std/fs.zig+2-2
...@@ -153,7 +153,7 @@ pub fn updateFileMode(source_path: []const u8, dest_path: []const u8, mode: ?Fil...@@ -153,7 +153,7 @@ pub fn updateFileMode(source_path: []const u8, dest_path: []const u8, mode: ?Fil
153 var buf: [mem.page_size * 6]u8 = undefined;153 var buf: [mem.page_size * 6]u8 = undefined;
154 while (true) {154 while (true) {
155 const amt = try in_stream.readFull(buf[0..]);155 const amt = try in_stream.readFull(buf[0..]);
156 try atomic_file.file.write(buf[0..amt]);156 try atomic_file.file.writeAll(buf[0..amt]);
157 if (amt != buf.len) {157 if (amt != buf.len) {
158 try atomic_file.file.updateTimes(src_stat.atime, src_stat.mtime);158 try atomic_file.file.updateTimes(src_stat.atime, src_stat.mtime);
159 try atomic_file.finish();159 try atomic_file.finish();
...@@ -1329,7 +1329,7 @@ pub const Dir = struct {...@@ -1329,7 +1329,7 @@ pub const Dir = struct {
1329 pub fn writeFile(self: Dir, sub_path: []const u8, data: []const u8) !void {1329 pub fn writeFile(self: Dir, sub_path: []const u8, data: []const u8) !void {
1330 var file = try self.createFile(sub_path, .{});1330 var file = try self.createFile(sub_path, .{});
1331 defer file.close();1331 defer file.close();
1332 try file.write(data);1332 try file.writeAll(data);
1333 }1333 }
13341334
1335 pub const AccessError = os.AccessError;1335 pub const AccessError = os.AccessError;
lib/std/fs/file.zig+123-17
...@@ -228,63 +228,169 @@ pub const File = struct {...@@ -228,63 +228,169 @@ pub const File = struct {
228 }228 }
229229
230 pub const ReadError = os.ReadError;230 pub const ReadError = os.ReadError;
231 pub const PReadError = os.PReadError;
231232
232 pub fn read(self: File, buffer: []u8) ReadError!usize {233 pub fn read(self: File, buffer: []u8) ReadError!usize {
233 if (need_async_thread and self.io_mode == .blocking and !self.async_block_allowed) {234 if (need_async_thread and self.io_mode == .blocking and !self.async_block_allowed) {
234 return std.event.Loop.instance.?.read(self.handle, buffer);235 return std.event.Loop.instance.?.read(self.handle, buffer);
236 } else {
237 return os.read(self.handle, buffer);
235 }238 }
236 return os.read(self.handle, buffer);
237 }239 }
238240
239 pub fn pread(self: File, buffer: []u8, offset: u64) ReadError!usize {241 pub fn readAll(self: File, buffer: []u8) ReadError!void {
242 var index: usize = 0;
243 while (index < buffer.len) {
244 index += try self.read(buffer[index..]);
245 }
246 }
247
248 pub fn pread(self: File, buffer: []u8, offset: u64) PReadError!usize {
240 if (need_async_thread and self.io_mode == .blocking and !self.async_block_allowed) {249 if (need_async_thread and self.io_mode == .blocking and !self.async_block_allowed) {
241 return std.event.Loop.instance.?.pread(self.handle, buffer);250 return std.event.Loop.instance.?.pread(self.handle, buffer, offset);
251 } else {
252 return os.pread(self.handle, buffer, offset);
253 }
254 }
255
256 pub fn preadAll(self: File, buffer: []u8, offset: u64) PReadError!void {
257 var index: usize = 0;
258 while (index < buffer.len) {
259 index += try self.pread(buffer[index..], offset + index);
242 }260 }
243 return os.pread(self.handle, buffer, offset);
244 }261 }
245262
246 pub fn readv(self: File, iovecs: []const os.iovec) ReadError!usize {263 pub fn readv(self: File, iovecs: []const os.iovec) ReadError!usize {
247 if (need_async_thread and self.io_mode == .blocking and !self.async_block_allowed) {264 if (need_async_thread and self.io_mode == .blocking and !self.async_block_allowed) {
248 return std.event.Loop.instance.?.readv(self.handle, iovecs);265 return std.event.Loop.instance.?.readv(self.handle, iovecs);
266 } else {
267 return os.readv(self.handle, iovecs);
249 }268 }
250 return os.readv(self.handle, iovecs);
251 }269 }
252270
253 pub fn preadv(self: File, iovecs: []const os.iovec, offset: u64) ReadError!usize {271 /// The `iovecs` parameter is mutable because this function needs to mutate the fields in
272 /// order to handle partial reads from the underlying OS layer.
273 pub fn readvAll(self: File, iovecs: []os.iovec) ReadError!void {
274 var i: usize = 0;
275 while (true) {
276 var amt = try self.readv(iovecs[i..]);
277 while (amt >= iovecs[i].iov_len) {
278 amt -= iovecs[i].iov_len;
279 i += 1;
280 if (i >= iovecs.len) return;
281 }
282 iovecs[i].iov_base += amt;
283 iovecs[i].iov_len -= amt;
284 }
285 }
286
287 pub fn preadv(self: File, iovecs: []const os.iovec, offset: u64) PReadError!usize {
254 if (need_async_thread and self.io_mode == .blocking and !self.async_block_allowed) {288 if (need_async_thread and self.io_mode == .blocking and !self.async_block_allowed) {
255 return std.event.Loop.instance.?.preadv(self.handle, iovecs, offset);289 return std.event.Loop.instance.?.preadv(self.handle, iovecs, offset);
290 } else {
291 return os.preadv(self.handle, iovecs, offset);
292 }
293 }
294
295 /// The `iovecs` parameter is mutable because this function needs to mutate the fields in
296 /// order to handle partial reads from the underlying OS layer.
297 pub fn preadvAll(self: File, iovecs: []const os.iovec, offset: u64) PReadError!void {
298 var i: usize = 0;
299 var off: usize = 0;
300 while (true) {
301 var amt = try self.preadv(iovecs[i..], offset + off);
302 off += amt;
303 while (amt >= iovecs[i].iov_len) {
304 amt -= iovecs[i].iov_len;
305 i += 1;
306 if (i >= iovecs.len) return;
307 }
308 iovecs[i].iov_base += amt;
309 iovecs[i].iov_len -= amt;
256 }310 }
257 return os.preadv(self.handle, iovecs, offset);
258 }311 }
259312
260 pub const WriteError = os.WriteError;313 pub const WriteError = os.WriteError;
314 pub const PWriteError = os.PWriteError;
261315
262 pub fn write(self: File, bytes: []const u8) WriteError!void {316 pub fn write(self: File, bytes: []const u8) WriteError!usize {
263 if (need_async_thread and self.io_mode == .blocking and !self.async_block_allowed) {317 if (need_async_thread and self.io_mode == .blocking and !self.async_block_allowed) {
264 return std.event.Loop.instance.?.write(self.handle, bytes);318 return std.event.Loop.instance.?.write(self.handle, bytes);
319 } else {
320 return os.write(self.handle, bytes);
265 }321 }
266 return os.write(self.handle, bytes);
267 }322 }
268323
269 pub fn pwrite(self: File, bytes: []const u8, offset: u64) WriteError!void {324 pub fn writeAll(self: File, bytes: []const u8) WriteError!void {
325 var index: usize = 0;
326 while (index < bytes.len) {
327 index += try self.write(bytes[index..]);
328 }
329 }
330
331 pub fn pwrite(self: File, bytes: []const u8, offset: u64) PWriteError!usize {
270 if (need_async_thread and self.io_mode == .blocking and !self.async_block_allowed) {332 if (need_async_thread and self.io_mode == .blocking and !self.async_block_allowed) {
271 return std.event.Loop.instance.?.pwrite(self.handle, bytes, offset);333 return std.event.Loop.instance.?.pwrite(self.handle, bytes, offset);
334 } else {
335 return os.pwrite(self.handle, bytes, offset);
272 }336 }
273 return os.pwrite(self.handle, bytes, offset);
274 }337 }
275338
276 pub fn writev(self: File, iovecs: []const os.iovec_const) WriteError!void {339 pub fn pwriteAll(self: File, bytes: []const u8, offset: u64) PWriteError!void {
340 var index: usize = 0;
341 while (index < bytes.len) {
342 index += try self.pwrite(bytes[index..], offset + index);
343 }
344 }
345
346 pub fn writev(self: File, iovecs: []const os.iovec_const) WriteError!usize {
277 if (need_async_thread and self.io_mode == .blocking and !self.async_block_allowed) {347 if (need_async_thread and self.io_mode == .blocking and !self.async_block_allowed) {
278 return std.event.Loop.instance.?.writev(self.handle, iovecs);348 return std.event.Loop.instance.?.writev(self.handle, iovecs);
349 } else {
350 return os.writev(self.handle, iovecs);
279 }351 }
280 return os.writev(self.handle, iovecs);
281 }352 }
282353
283 pub fn pwritev(self: File, iovecs: []const os.iovec_const, offset: usize) WriteError!void {354 /// The `iovecs` parameter is mutable because this function needs to mutate the fields in
355 /// order to handle partial writes from the underlying OS layer.
356 pub fn writevAll(self: File, iovecs: []os.iovec_const) WriteError!void {
357 var i: usize = 0;
358 while (true) {
359 var amt = try self.writev(iovecs[i..]);
360 while (amt >= iovecs[i].iov_len) {
361 amt -= iovecs[i].iov_len;
362 i += 1;
363 if (i >= iovecs.len) return;
364 }
365 iovecs[i].iov_base += amt;
366 iovecs[i].iov_len -= amt;
367 }
368 }
369
370 pub fn pwritev(self: File, iovecs: []os.iovec_const, offset: usize) PWriteError!usize {
284 if (need_async_thread and self.io_mode == .blocking and !self.async_block_allowed) {371 if (need_async_thread and self.io_mode == .blocking and !self.async_block_allowed) {
285 return std.event.Loop.instance.?.pwritev(self.handle, iovecs);372 return std.event.Loop.instance.?.pwritev(self.handle, iovecs, offset);
373 } else {
374 return os.pwritev(self.handle, iovecs, offset);
375 }
376 }
377
378 /// The `iovecs` parameter is mutable because this function needs to mutate the fields in
379 /// order to handle partial writes from the underlying OS layer.
380 pub fn pwritevAll(self: File, iovecs: []os.iovec_const, offset: usize) PWriteError!void {
381 var i: usize = 0;
382 var off: usize = 0;
383 while (true) {
384 var amt = try self.pwritev(iovecs[i..], offset + off);
385 off += amt;
386 while (amt >= iovecs[i].iov_len) {
387 amt -= iovecs[i].iov_len;
388 i += 1;
389 if (i >= iovecs.len) return;
390 }
391 iovecs[i].iov_base += amt;
392 iovecs[i].iov_len -= amt;
286 }393 }
287 return os.pwritev(self.handle, iovecs);
288 }394 }
289395
290 pub fn inStream(file: File) InStream {396 pub fn inStream(file: File) InStream {
...@@ -335,7 +441,7 @@ pub const File = struct {...@@ -335,7 +441,7 @@ pub const File = struct {
335 pub const Error = WriteError;441 pub const Error = WriteError;
336 pub const Stream = io.OutStream(Error);442 pub const Stream = io.OutStream(Error);
337443
338 fn writeFn(out_stream: *Stream, bytes: []const u8) Error!void {444 fn writeFn(out_stream: *Stream, bytes: []const u8) Error!usize {
339 const self = @fieldParentPtr(OutStream, "stream", out_stream);445 const self = @fieldParentPtr(OutStream, "stream", out_stream);
340 return self.file.write(bytes);446 return self.file.write(bytes);
341 }447 }
lib/std/io.zig+25-17
...@@ -437,10 +437,11 @@ pub fn BitInStream(endian: builtin.Endian, comptime Error: type) type {...@@ -437,10 +437,11 @@ pub fn BitInStream(endian: builtin.Endian, comptime Error: type) type {
437 };437 };
438}438}
439439
440/// This is a simple OutStream that writes to a fixed buffer, and returns an error440/// This is a simple OutStream that writes to a fixed buffer. If the returned number
441/// when it runs out of space.441/// of bytes written is less than requested, the buffer is full.
442/// Returns error.OutOfMemory when no bytes would be written.
442pub const SliceOutStream = struct {443pub const SliceOutStream = struct {
443 pub const Error = error{OutOfSpace};444 pub const Error = error{OutOfMemory};
444 pub const Stream = OutStream(Error);445 pub const Stream = OutStream(Error);
445446
446 stream: Stream,447 stream: Stream,
...@@ -464,9 +465,11 @@ pub const SliceOutStream = struct {...@@ -464,9 +465,11 @@ pub const SliceOutStream = struct {
464 self.pos = 0;465 self.pos = 0;
465 }466 }
466467
467 fn writeFn(out_stream: *Stream, bytes: []const u8) Error!void {468 fn writeFn(out_stream: *Stream, bytes: []const u8) Error!usize {
468 const self = @fieldParentPtr(SliceOutStream, "stream", out_stream);469 const self = @fieldParentPtr(SliceOutStream, "stream", out_stream);
469470
471 if (bytes.len == 0) return 0;
472
470 assert(self.pos <= self.slice.len);473 assert(self.pos <= self.slice.len);
471474
472 const n = if (self.pos + bytes.len <= self.slice.len)475 const n = if (self.pos + bytes.len <= self.slice.len)
...@@ -477,9 +480,9 @@ pub const SliceOutStream = struct {...@@ -477,9 +480,9 @@ pub const SliceOutStream = struct {
477 std.mem.copy(u8, self.slice[self.pos .. self.pos + n], bytes[0..n]);480 std.mem.copy(u8, self.slice[self.pos .. self.pos + n], bytes[0..n]);
478 self.pos += n;481 self.pos += n;
479482
480 if (n < bytes.len) {483 if (n == 0) return error.OutOfMemory;
481 return Error.OutOfSpace;484
482 }485 return n;
483 }486 }
484};487};
485488
...@@ -508,7 +511,9 @@ pub const NullOutStream = struct {...@@ -508,7 +511,9 @@ pub const NullOutStream = struct {
508 };511 };
509 }512 }
510513
511 fn writeFn(out_stream: *Stream, bytes: []const u8) Error!void {}514 fn writeFn(out_stream: *Stream, bytes: []const u8) Error!usize {
515 return bytes.len;
516 }
512};517};
513518
514test "io.NullOutStream" {519test "io.NullOutStream" {
...@@ -536,10 +541,11 @@ pub fn CountingOutStream(comptime OutStreamError: type) type {...@@ -536,10 +541,11 @@ pub fn CountingOutStream(comptime OutStreamError: type) type {
536 };541 };
537 }542 }
538543
539 fn writeFn(out_stream: *Stream, bytes: []const u8) OutStreamError!void {544 fn writeFn(out_stream: *Stream, bytes: []const u8) OutStreamError!usize {
540 const self = @fieldParentPtr(Self, "stream", out_stream);545 const self = @fieldParentPtr(Self, "stream", out_stream);
541 try self.child_stream.write(bytes);546 try self.child_stream.write(bytes);
542 self.bytes_written += bytes.len;547 self.bytes_written += bytes.len;
548 return bytes.len;
543 }549 }
544 };550 };
545}551}
...@@ -588,13 +594,14 @@ pub fn BufferedOutStreamCustom(comptime buffer_size: usize, comptime OutStreamEr...@@ -588,13 +594,14 @@ pub fn BufferedOutStreamCustom(comptime buffer_size: usize, comptime OutStreamEr
588 }594 }
589 }595 }
590596
591 fn writeFn(out_stream: *Stream, bytes: []const u8) Error!void {597 fn writeFn(out_stream: *Stream, bytes: []const u8) Error!usize {
592 const self = @fieldParentPtr(Self, "stream", out_stream);598 const self = @fieldParentPtr(Self, "stream", out_stream);
593 if (bytes.len >= self.fifo.writableLength()) {599 if (bytes.len >= self.fifo.writableLength()) {
594 try self.flush();600 try self.flush();
595 return self.unbuffered_out_stream.write(bytes);601 return self.unbuffered_out_stream.writeOnce(bytes);
596 }602 }
597 self.fifo.writeAssumeCapacity(bytes);603 self.fifo.writeAssumeCapacity(bytes);
604 return bytes.len;
598 }605 }
599 };606 };
600}607}
...@@ -614,9 +621,10 @@ pub const BufferOutStream = struct {...@@ -614,9 +621,10 @@ pub const BufferOutStream = struct {
614 };621 };
615 }622 }
616623
617 fn writeFn(out_stream: *Stream, bytes: []const u8) !void {624 fn writeFn(out_stream: *Stream, bytes: []const u8) !usize {
618 const self = @fieldParentPtr(BufferOutStream, "stream", out_stream);625 const self = @fieldParentPtr(BufferOutStream, "stream", out_stream);
619 return self.buffer.append(bytes);626 try self.buffer.append(bytes);
627 return bytes.len;
620 }628 }
621};629};
622630
...@@ -734,17 +742,17 @@ pub fn BitOutStream(endian: builtin.Endian, comptime Error: type) type {...@@ -734,17 +742,17 @@ pub fn BitOutStream(endian: builtin.Endian, comptime Error: type) type {
734 self.bit_count = 0;742 self.bit_count = 0;
735 }743 }
736744
737 pub fn write(self_stream: *Stream, buffer: []const u8) Error!void {745 pub fn write(self_stream: *Stream, buffer: []const u8) Error!usize {
738 var self = @fieldParentPtr(Self, "stream", self_stream);746 var self = @fieldParentPtr(Self, "stream", self_stream);
739747
740 //@NOTE: I'm not sure this is a good idea, maybe flushBits should be forced748 // TODO: I'm not sure this is a good idea, maybe flushBits should be forced
741 if (self.bit_count > 0) {749 if (self.bit_count > 0) {
742 for (buffer) |b, i|750 for (buffer) |b, i|
743 try self.writeBits(b, u8_bit_count);751 try self.writeBits(b, u8_bit_count);
744 return;752 return buffer.len;
745 }753 }
746754
747 return self.out_stream.write(buffer);755 return self.out_stream.writeOnce(buffer);
748 }756 }
749 };757 };
750}758}
lib/std/io/c_out_stream.zig+2-2
...@@ -20,10 +20,10 @@ pub const COutStream = struct {...@@ -20,10 +20,10 @@ pub const COutStream = struct {
20 };20 };
21 }21 }
2222
23 fn writeFn(out_stream: *Stream, bytes: []const u8) Error!void {23 fn writeFn(out_stream: *Stream, bytes: []const u8) Error!usize {
24 const self = @fieldParentPtr(COutStream, "stream", out_stream);24 const self = @fieldParentPtr(COutStream, "stream", out_stream);
25 const amt_written = std.c.fwrite(bytes.ptr, 1, bytes.len, self.c_file);25 const amt_written = std.c.fwrite(bytes.ptr, 1, bytes.len, self.c_file);
26 if (amt_written == bytes.len) return;26 if (amt_written >= 0) return amt_written;
27 switch (std.c._errno().*) {27 switch (std.c._errno().*) {
28 0 => unreachable,28 0 => unreachable,
29 os.EINVAL => unreachable,29 os.EINVAL => unreachable,
lib/std/io/out_stream.zig+10-3
...@@ -14,13 +14,13 @@ pub fn OutStream(comptime WriteError: type) type {...@@ -14,13 +14,13 @@ pub fn OutStream(comptime WriteError: type) type {
14 const Self = @This();14 const Self = @This();
15 pub const Error = WriteError;15 pub const Error = WriteError;
16 pub const WriteFn = if (std.io.is_async)16 pub const WriteFn = if (std.io.is_async)
17 async fn (self: *Self, bytes: []const u8) Error!void17 async fn (self: *Self, bytes: []const u8) Error!usize
18 else18 else
19 fn (self: *Self, bytes: []const u8) Error!void;19 fn (self: *Self, bytes: []const u8) Error!usize;
2020
21 writeFn: WriteFn,21 writeFn: WriteFn,
2222
23 pub fn write(self: *Self, bytes: []const u8) Error!void {23 pub fn writeOnce(self: *Self, bytes: []const u8) Error!usize {
24 if (std.io.is_async) {24 if (std.io.is_async) {
25 // Let's not be writing 0xaa in safe modes for upwards of 4 MiB for every stream write.25 // Let's not be writing 0xaa in safe modes for upwards of 4 MiB for every stream write.
26 @setRuntimeSafety(false);26 @setRuntimeSafety(false);
...@@ -31,6 +31,13 @@ pub fn OutStream(comptime WriteError: type) type {...@@ -31,6 +31,13 @@ pub fn OutStream(comptime WriteError: type) type {
31 }31 }
32 }32 }
3333
34 pub fn write(self: *Self, bytes: []const u8) Error!void {
35 var index: usize = 0;
36 while (index != bytes.len) {
37 index += try self.writeOnce(bytes[index..]);
38 }
39 }
40
34 pub fn print(self: *Self, comptime format: []const u8, args: var) Error!void {41 pub fn print(self: *Self, comptime format: []const u8, args: var) Error!void {
35 return std.fmt.format(self, Error, write, format, args);42 return std.fmt.format(self, Error, write, format, args);
36 }43 }
lib/std/io/test.zig+3-3
...@@ -134,13 +134,13 @@ test "SliceOutStream" {...@@ -134,13 +134,13 @@ test "SliceOutStream" {
134 try ss.stream.write("world");134 try ss.stream.write("world");
135 expect(mem.eql(u8, ss.getWritten(), "Helloworld"));135 expect(mem.eql(u8, ss.getWritten(), "Helloworld"));
136136
137 expectError(error.OutOfSpace, ss.stream.write("!"));137 expectError(error.OutOfMemory, ss.stream.write("!"));
138 expect(mem.eql(u8, ss.getWritten(), "Helloworld"));138 expect(mem.eql(u8, ss.getWritten(), "Helloworld"));
139139
140 ss.reset();140 ss.reset();
141 expect(ss.getWritten().len == 0);141 expect(ss.getWritten().len == 0);
142142
143 expectError(error.OutOfSpace, ss.stream.write("Hello world!"));143 expectError(error.OutOfMemory, ss.stream.write("Hello world!"));
144 expect(mem.eql(u8, ss.getWritten(), "Hello worl"));144 expect(mem.eql(u8, ss.getWritten(), "Hello worl"));
145}145}
146146
...@@ -617,7 +617,7 @@ test "File seek ops" {...@@ -617,7 +617,7 @@ test "File seek ops" {
617 fs.cwd().deleteFile(tmp_file_name) catch {};617 fs.cwd().deleteFile(tmp_file_name) catch {};
618 }618 }
619619
620 try file.write(&([_]u8{0x55} ** 8192));620 try file.writeAll(&([_]u8{0x55} ** 8192));
621621
622 // Seek to the end622 // Seek to the end
623 try file.seekFromEnd(0);623 try file.seekFromEnd(0);
lib/std/os.zig+469-177
...@@ -298,6 +298,11 @@ pub const ReadError = error{...@@ -298,6 +298,11 @@ pub const ReadError = error{
298/// buf.len. If 0 bytes were read, that means EOF.298/// buf.len. If 0 bytes were read, that means EOF.
299/// If the application has a global event loop enabled, EAGAIN is handled299/// If the application has a global event loop enabled, EAGAIN is handled
300/// via the event loop. Otherwise EAGAIN results in error.WouldBlock.300/// via the event loop. Otherwise EAGAIN results in error.WouldBlock.
301///
302/// Linux has a limit on how many bytes may be transferred in one `read` call, which is `0x7ffff000`
303/// on both 64-bit and 32-bit systems. This is due to using a signed C int as the return value, as
304/// well as stuffing the errno codes into the last `4096` values. This is noted on the `read` man page.
305/// For POSIX the limit is `math.maxInt(isize)`.
301pub fn read(fd: fd_t, buf: []u8) ReadError!usize {306pub fn read(fd: fd_t, buf: []u8) ReadError!usize {
302 if (builtin.os.tag == .windows) {307 if (builtin.os.tag == .windows) {
303 return windows.ReadFile(fd, buf, null);308 return windows.ReadFile(fd, buf, null);
...@@ -316,8 +321,15 @@ pub fn read(fd: fd_t, buf: []u8) ReadError!usize {...@@ -316,8 +321,15 @@ pub fn read(fd: fd_t, buf: []u8) ReadError!usize {
316 }321 }
317 }322 }
318323
324 // Prevents EINVAL.
325 const max_count = switch (std.Target.current.os.tag) {
326 .linux => 0x7ffff000,
327 else => math.maxInt(isize),
328 };
329 const adjusted_len = math.min(max_count, buf.len);
330
319 while (true) {331 while (true) {
320 const rc = system.read(fd, buf.ptr, buf.len);332 const rc = system.read(fd, buf.ptr, adjusted_len);
321 switch (errno(rc)) {333 switch (errno(rc)) {
322 0 => return @intCast(usize, rc),334 0 => return @intCast(usize, rc),
323 EINTR => continue,335 EINTR => continue,
...@@ -352,32 +364,18 @@ pub fn read(fd: fd_t, buf: []u8) ReadError!usize {...@@ -352,32 +364,18 @@ pub fn read(fd: fd_t, buf: []u8) ReadError!usize {
352/// * Windows364/// * Windows
353/// On these systems, the read races with concurrent writes to the same file descriptor.365/// On these systems, the read races with concurrent writes to the same file descriptor.
354pub fn readv(fd: fd_t, iov: []const iovec) ReadError!usize {366pub fn readv(fd: fd_t, iov: []const iovec) ReadError!usize {
355 if (builtin.os.tag == .windows) {367 if (std.Target.current.os.tag == .windows) {
356 // TODO batch these into parallel requests368 // TODO does Windows have a way to read an io vector?
357 var off: usize = 0;369 if (iov.len == 0) return @as(usize, 0);
358 var iov_i: usize = 0;370 const first = iov[0];
359 var inner_off: usize = 0;371 return read(fd, first.iov_base[0..first.iov_len]);
360 while (true) {
361 const v = iov[iov_i];
362 const amt_read = try read(fd, v.iov_base[inner_off .. v.iov_len - inner_off]);
363 off += amt_read;
364 inner_off += amt_read;
365 if (inner_off == v.len) {
366 iov_i += 1;
367 inner_off = 0;
368 if (iov_i == iov.len) {
369 return off;
370 }
371 }
372 if (amt_read == 0) return off; // EOF
373 } else unreachable; // TODO https://github.com/ziglang/zig/issues/707
374 }372 }
375373
376 while (true) {374 while (true) {
377 // TODO handle the case when iov_len is too large and get rid of this @intCast375 // TODO handle the case when iov_len is too large and get rid of this @intCast
378 const rc = system.readv(fd, iov.ptr, @intCast(u32, iov.len));376 const rc = system.readv(fd, iov.ptr, iov_count);
379 switch (errno(rc)) {377 switch (errno(rc)) {
380 0 => return @bitCast(usize, rc),378 0 => return @intCast(usize, rc),
381 EINTR => continue,379 EINTR => continue,
382 EINVAL => unreachable,380 EINVAL => unreachable,
383 EFAULT => unreachable,381 EFAULT => unreachable,
...@@ -397,6 +395,8 @@ pub fn readv(fd: fd_t, iov: []const iovec) ReadError!usize {...@@ -397,6 +395,8 @@ pub fn readv(fd: fd_t, iov: []const iovec) ReadError!usize {
397 }395 }
398}396}
399397
398pub const PReadError = ReadError || error{Unseekable};
399
400/// Number of bytes read is returned. Upon reading end-of-file, zero is returned.400/// Number of bytes read is returned. Upon reading end-of-file, zero is returned.
401///401///
402/// Retries when interrupted by a signal.402/// Retries when interrupted by a signal.
...@@ -405,7 +405,7 @@ pub fn readv(fd: fd_t, iov: []const iovec) ReadError!usize {...@@ -405,7 +405,7 @@ pub fn readv(fd: fd_t, iov: []const iovec) ReadError!usize {
405/// via the event loop. Otherwise EAGAIN results in `error.WouldBlock`.405/// via the event loop. Otherwise EAGAIN results in `error.WouldBlock`.
406/// On Windows, if the application has a global event loop enabled, I/O Completion Ports are406/// On Windows, if the application has a global event loop enabled, I/O Completion Ports are
407/// used to perform the I/O. `error.WouldBlock` is not possible on Windows.407/// used to perform the I/O. `error.WouldBlock` is not possible on Windows.
408pub fn pread(fd: fd_t, buf: []u8, offset: u64) ReadError!usize {408pub fn pread(fd: fd_t, buf: []u8, offset: u64) PReadError!usize {
409 if (builtin.os.tag == .windows) {409 if (builtin.os.tag == .windows) {
410 return windows.ReadFile(fd, buf, offset);410 return windows.ReadFile(fd, buf, offset);
411 }411 }
...@@ -429,6 +429,9 @@ pub fn pread(fd: fd_t, buf: []u8, offset: u64) ReadError!usize {...@@ -429,6 +429,9 @@ pub fn pread(fd: fd_t, buf: []u8, offset: u64) ReadError!usize {
429 ENOBUFS => return error.SystemResources,429 ENOBUFS => return error.SystemResources,
430 ENOMEM => return error.SystemResources,430 ENOMEM => return error.SystemResources,
431 ECONNRESET => return error.ConnectionResetByPeer,431 ECONNRESET => return error.ConnectionResetByPeer,
432 ENXIO => return error.Unseekable,
433 ESPIPE => return error.Unseekable,
434 EOVERFLOW => return error.Unseekable,
432 else => |err| return unexpectedErrno(err),435 else => |err| return unexpectedErrno(err),
433 }436 }
434 }437 }
...@@ -448,75 +451,23 @@ pub fn pread(fd: fd_t, buf: []u8, offset: u64) ReadError!usize {...@@ -448,75 +451,23 @@ pub fn pread(fd: fd_t, buf: []u8, offset: u64) ReadError!usize {
448/// * Darwin451/// * Darwin
449/// * Windows452/// * Windows
450/// On these systems, the read races with concurrent writes to the same file descriptor.453/// On these systems, the read races with concurrent writes to the same file descriptor.
451pub fn preadv(fd: fd_t, iov: []const iovec, offset: u64) ReadError!usize {454pub fn preadv(fd: fd_t, iov: []const iovec, offset: u64) PReadError!usize {
452 if (comptime std.Target.current.isDarwin()) {455 const have_pread_but_not_preadv = switch (std.Target.current.os.tag) {
453 // Darwin does not have preadv but it does have pread.456 .windows, .macosx, .ios, .watchos, .tvos => true,
454 var off: usize = 0;457 else => false,
455 var iov_i: usize = 0;458 };
456 var inner_off: usize = 0;459 if (have_pread_but_not_preadv) {
457 while (true) {460 // We could loop here; but proper usage of `preadv` must handle partial reads anyway.
458 const v = iov[iov_i];461 // So we simply read into the first vector only.
459 const rc = darwin.pread(fd, v.iov_base + inner_off, v.iov_len - inner_off, offset + off);462 if (iov.len == 0) return @as(usize, 0);
460 const err = darwin.getErrno(rc);463 const first = iov[0];
461 switch (err) {464 return pread(fd, first.iov_base[0..first.iov_len], offset);
462 0 => {
463 const amt_read = @bitCast(usize, rc);
464 off += amt_read;
465 inner_off += amt_read;
466 if (inner_off == v.iov_len) {
467 iov_i += 1;
468 inner_off = 0;
469 if (iov_i == iov.len) {
470 return off;
471 }
472 }
473 if (rc == 0) return off; // EOF
474 continue;
475 },
476 EINTR => continue,
477 EINVAL => unreachable,
478 EFAULT => unreachable,
479 ESPIPE => unreachable, // fd is not seekable
480 EAGAIN => if (std.event.Loop.instance) |loop| {
481 loop.waitUntilFdReadable(fd);
482 continue;
483 } else {
484 return error.WouldBlock;
485 },
486 EBADF => unreachable, // always a race condition
487 EIO => return error.InputOutput,
488 EISDIR => return error.IsDir,
489 ENOBUFS => return error.SystemResources,
490 ENOMEM => return error.SystemResources,
491 else => return unexpectedErrno(err),
492 }
493 }
494 }465 }
495466
496 if (builtin.os.tag == .windows) {467 const iov_count = math.cast(u31, iov.len) catch math.maxInt(u31);
497 // TODO batch these into parallel requests
498 var off: usize = 0;
499 var iov_i: usize = 0;
500 var inner_off: usize = 0;
501 while (true) {
502 const v = iov[iov_i];
503 const amt_read = try pread(fd, v.iov_base[inner_off .. v.iov_len - inner_off], offset + off);
504 off += amt_read;
505 inner_off += amt_read;
506 if (inner_off == v.len) {
507 iov_i += 1;
508 inner_off = 0;
509 if (iov_i == iov.len) {
510 return off;
511 }
512 }
513 if (amt_read == 0) return off; // EOF
514 } else unreachable; // TODO https://github.com/ziglang/zig/issues/707
515 }
516468
517 while (true) {469 while (true) {
518 // TODO handle the case when iov_len is too large and get rid of this @intCast470 const rc = system.preadv(fd, iov.ptr, iov_count, offset);
519 const rc = system.preadv(fd, iov.ptr, @intCast(u32, iov.len), offset);
520 switch (errno(rc)) {471 switch (errno(rc)) {
521 0 => return @bitCast(usize, rc),472 0 => return @bitCast(usize, rc),
522 EINTR => continue,473 EINTR => continue,
...@@ -533,6 +484,9 @@ pub fn preadv(fd: fd_t, iov: []const iovec, offset: u64) ReadError!usize {...@@ -533,6 +484,9 @@ pub fn preadv(fd: fd_t, iov: []const iovec, offset: u64) ReadError!usize {
533 EISDIR => return error.IsDir,484 EISDIR => return error.IsDir,
534 ENOBUFS => return error.SystemResources,485 ENOBUFS => return error.SystemResources,
535 ENOMEM => return error.SystemResources,486 ENOMEM => return error.SystemResources,
487 ENXIO => return error.Unseekable,
488 ESPIPE => return error.Unseekable,
489 EOVERFLOW => return error.Unseekable,
536 else => |err| return unexpectedErrno(err),490 else => |err| return unexpectedErrno(err),
537 }491 }
538 }492 }
...@@ -553,10 +507,28 @@ pub const WriteError = error{...@@ -553,10 +507,28 @@ pub const WriteError = error{
553 WouldBlock,507 WouldBlock,
554} || UnexpectedError;508} || UnexpectedError;
555509
556/// Write to a file descriptor. Keeps trying if it gets interrupted.510/// Write to a file descriptor.
557/// If the application has a global event loop enabled, EAGAIN is handled511/// Retries when interrupted by a signal.
558/// via the event loop. Otherwise EAGAIN results in error.WouldBlock.512/// Returns the number of bytes written. If nonzero bytes were supplied, this will be nonzero.
559pub fn write(fd: fd_t, bytes: []const u8) WriteError!void {513///
514/// Note that a successful write() may transfer fewer than count bytes. Such partial writes can
515/// occur for various reasons; for example, because there was insufficient space on the disk
516/// device to write all of the requested bytes, or because a blocked write() to a socket, pipe, or
517/// similar was interrupted by a signal handler after it had transferred some, but before it had
518/// transferred all of the requested bytes. In the event of a partial write, the caller can make
519/// another write() call to transfer the remaining bytes. The subsequent call will either
520/// transfer further bytes or may result in an error (e.g., if the disk is now full).
521///
522/// For POSIX systems, if the application has a global event loop enabled, EAGAIN is handled
523/// via the event loop. Otherwise EAGAIN results in `error.WouldBlock`.
524/// On Windows, if the application has a global event loop enabled, I/O Completion Ports are
525/// used to perform the I/O. `error.WouldBlock` is not possible on Windows.
526///
527/// Linux has a limit on how many bytes may be transferred in one `write` call, which is `0x7ffff000`
528/// on both 64-bit and 32-bit systems. This is due to using a signed C int as the return value, as
529/// well as stuffing the errno codes into the last `4096` values. This is noted on the `write` man page.
530/// The corresponding POSIX limit is `math.maxInt(isize)`.
531pub fn write(fd: fd_t, bytes: []const u8) WriteError!usize {
560 if (builtin.os.tag == .windows) {532 if (builtin.os.tag == .windows) {
561 return windows.WriteFile(fd, bytes, null);533 return windows.WriteFile(fd, bytes, null);
562 }534 }
...@@ -568,26 +540,21 @@ pub fn write(fd: fd_t, bytes: []const u8) WriteError!void {...@@ -568,26 +540,21 @@ pub fn write(fd: fd_t, bytes: []const u8) WriteError!void {
568 }};540 }};
569 var nwritten: usize = undefined;541 var nwritten: usize = undefined;
570 switch (wasi.fd_write(fd, &ciovs, ciovs.len, &nwritten)) {542 switch (wasi.fd_write(fd, &ciovs, ciovs.len, &nwritten)) {
571 0 => return,543 0 => return nwritten,
572 else => |err| return unexpectedErrno(err),544 else => |err| return unexpectedErrno(err),
573 }545 }
574 }546 }
575547
576 // Linux can return EINVAL when write amount is > 0x7ffff000548 const max_count = switch (std.Target.current.os.tag) {
577 // See https://github.com/ziglang/zig/pull/743#issuecomment-363165856549 .linux => 0x7ffff000,
578 // TODO audit this. Shawn Landden says that this is not actually true.550 else => math.maxInt(isize),
579 // if this logic should stay, move it to std.os.linux551 };
580 const max_bytes_len = 0x7ffff000;552 const adjusted_len = math.min(max_count, bytes.len);
581553
582 var index: usize = 0;554 while (true) {
583 while (index < bytes.len) {555 const rc = system.write(fd, bytes.ptr, adjusted_len);
584 const amt_to_write = math.min(bytes.len - index, @as(usize, max_bytes_len));
585 const rc = system.write(fd, bytes.ptr + index, amt_to_write);
586 switch (errno(rc)) {556 switch (errno(rc)) {
587 0 => {557 0 => return @intCast(usize, rc),
588 index += @intCast(usize, rc);
589 continue;
590 },
591 EINTR => continue,558 EINTR => continue,
592 EINVAL => unreachable,559 EINVAL => unreachable,
593 EFAULT => unreachable,560 EFAULT => unreachable,
...@@ -611,14 +578,36 @@ pub fn write(fd: fd_t, bytes: []const u8) WriteError!void {...@@ -611,14 +578,36 @@ pub fn write(fd: fd_t, bytes: []const u8) WriteError!void {
611}578}
612579
613/// Write multiple buffers to a file descriptor.580/// Write multiple buffers to a file descriptor.
614/// If the application has a global event loop enabled, EAGAIN is handled581/// Retries when interrupted by a signal.
615/// via the event loop. Otherwise EAGAIN results in error.WouldBlock.582/// Returns the number of bytes written. If nonzero bytes were supplied, this will be nonzero.
616pub fn writev(fd: fd_t, iov: []const iovec_const) WriteError!void {583///
584/// Note that a successful write() may transfer fewer bytes than supplied. Such partial writes can
585/// occur for various reasons; for example, because there was insufficient space on the disk
586/// device to write all of the requested bytes, or because a blocked write() to a socket, pipe, or
587/// similar was interrupted by a signal handler after it had transferred some, but before it had
588/// transferred all of the requested bytes. In the event of a partial write, the caller can make
589/// another write() call to transfer the remaining bytes. The subsequent call will either
590/// transfer further bytes or may result in an error (e.g., if the disk is now full).
591///
592/// For POSIX systems, if the application has a global event loop enabled, EAGAIN is handled
593/// via the event loop. Otherwise EAGAIN results in `error.WouldBlock`.
594/// On Windows, if the application has a global event loop enabled, I/O Completion Ports are
595/// used to perform the I/O. `error.WouldBlock` is not possible on Windows.
596///
597/// If `iov.len` is larger than will fit in a `u31`, a partial write will occur.
598pub fn writev(fd: fd_t, iov: []const iovec_const) WriteError!usize {
599 if (std.Target.current.os.tag == .windows) {
600 // TODO does Windows have a way to write an io vector?
601 if (iov.len == 0) return @as(usize, 0);
602 const first = iov[0];
603 return write(fd, first.iov_base[0..first.iov_len]);
604 }
605
606 const iov_count = math.cast(u31, iov.len) catch math.maxInt(u31);
617 while (true) {607 while (true) {
618 // TODO handle the case when iov_len is too large and get rid of this @intCast608 const rc = system.writev(fd, iov.ptr, iov_count);
619 const rc = system.writev(fd, iov.ptr, @intCast(u32, iov.len));
620 switch (errno(rc)) {609 switch (errno(rc)) {
621 0 => return,610 0 => return @intCast(usize, rc),
622 EINTR => continue,611 EINTR => continue,
623 EINVAL => unreachable,612 EINVAL => unreachable,
624 EFAULT => unreachable,613 EFAULT => unreachable,
...@@ -641,23 +630,45 @@ pub fn writev(fd: fd_t, iov: []const iovec_const) WriteError!void {...@@ -641,23 +630,45 @@ pub fn writev(fd: fd_t, iov: []const iovec_const) WriteError!void {
641 }630 }
642}631}
643632
633pub const PWriteError = WriteError || error{Unseekable};
634
644/// Write to a file descriptor, with a position offset.635/// Write to a file descriptor, with a position offset.
645///
646/// Retries when interrupted by a signal.636/// Retries when interrupted by a signal.
637/// Returns the number of bytes written. If nonzero bytes were supplied, this will be nonzero.
638///
639/// Note that a successful write() may transfer fewer bytes than supplied. Such partial writes can
640/// occur for various reasons; for example, because there was insufficient space on the disk
641/// device to write all of the requested bytes, or because a blocked write() to a socket, pipe, or
642/// similar was interrupted by a signal handler after it had transferred some, but before it had
643/// transferred all of the requested bytes. In the event of a partial write, the caller can make
644/// another write() call to transfer the remaining bytes. The subsequent call will either
645/// transfer further bytes or may result in an error (e.g., if the disk is now full).
647///646///
648/// For POSIX systems, if the application has a global event loop enabled, EAGAIN is handled647/// For POSIX systems, if the application has a global event loop enabled, EAGAIN is handled
649/// via the event loop. Otherwise EAGAIN results in `error.WouldBlock`.648/// via the event loop. Otherwise EAGAIN results in `error.WouldBlock`.
650/// On Windows, if the application has a global event loop enabled, I/O Completion Ports are649/// On Windows, if the application has a global event loop enabled, I/O Completion Ports are
651/// used to perform the I/O. `error.WouldBlock` is not possible on Windows.650/// used to perform the I/O. `error.WouldBlock` is not possible on Windows.
652pub fn pwrite(fd: fd_t, bytes: []const u8, offset: u64) WriteError!void {651///
652/// Linux has a limit on how many bytes may be transferred in one `pwrite` call, which is `0x7ffff000`
653/// on both 64-bit and 32-bit systems. This is due to using a signed C int as the return value, as
654/// well as stuffing the errno codes into the last `4096` values. This is noted on the `write` man page.
655/// The corresponding POSIX limit is `math.maxInt(isize)`.
656pub fn pwrite(fd: fd_t, bytes: []const u8, offset: u64) PWriteError!usize {
653 if (std.Target.current.os.tag == .windows) {657 if (std.Target.current.os.tag == .windows) {
654 return windows.WriteFile(fd, bytes, offset);658 return windows.WriteFile(fd, bytes, offset);
655 }659 }
656660
661 // Prevent EINVAL.
662 const max_count = switch (std.Target.current.os.tag) {
663 .linux => 0x7ffff000,
664 else => math.maxInt(isize),
665 };
666 const adjusted_len = math.min(max_count, bytes.len);
667
657 while (true) {668 while (true) {
658 const rc = system.pwrite(fd, bytes.ptr, bytes.len, offset);669 const rc = system.pwrite(fd, bytes.ptr, adjusted_len, offset);
659 switch (errno(rc)) {670 switch (errno(rc)) {
660 0 => return,671 0 => return @intCast(usize, rc),
661 EINTR => continue,672 EINTR => continue,
662 EINVAL => unreachable,673 EINVAL => unreachable,
663 EFAULT => unreachable,674 EFAULT => unreachable,
...@@ -675,84 +686,54 @@ pub fn pwrite(fd: fd_t, bytes: []const u8, offset: u64) WriteError!void {...@@ -675,84 +686,54 @@ pub fn pwrite(fd: fd_t, bytes: []const u8, offset: u64) WriteError!void {
675 ENOSPC => return error.NoSpaceLeft,686 ENOSPC => return error.NoSpaceLeft,
676 EPERM => return error.AccessDenied,687 EPERM => return error.AccessDenied,
677 EPIPE => return error.BrokenPipe,688 EPIPE => return error.BrokenPipe,
689 ENXIO => return error.Unseekable,
690 ESPIPE => return error.Unseekable,
691 EOVERFLOW => return error.Unseekable,
678 else => |err| return unexpectedErrno(err),692 else => |err| return unexpectedErrno(err),
679 }693 }
680 }694 }
681}695}
682696
683/// Write multiple buffers to a file descriptor, with a position offset.697/// Write multiple buffers to a file descriptor, with a position offset.
684///
685/// Retries when interrupted by a signal.698/// Retries when interrupted by a signal.
699/// Returns the number of bytes written. If nonzero bytes were supplied, this will be nonzero.
700///
701/// Note that a successful write() may transfer fewer than count bytes. Such partial writes can
702/// occur for various reasons; for example, because there was insufficient space on the disk
703/// device to write all of the requested bytes, or because a blocked write() to a socket, pipe, or
704/// similar was interrupted by a signal handler after it had transferred some, but before it had
705/// transferred all of the requested bytes. In the event of a partial write, the caller can make
706/// another write() call to transfer the remaining bytes. The subsequent call will either
707/// transfer further bytes or may result in an error (e.g., if the disk is now full).
686///708///
687/// If the application has a global event loop enabled, EAGAIN is handled709/// If the application has a global event loop enabled, EAGAIN is handled
688/// via the event loop. Otherwise EAGAIN results in `error.WouldBlock`.710/// via the event loop. Otherwise EAGAIN results in `error.WouldBlock`.
689///711///
690/// This operation is non-atomic on the following systems:712/// The following systems do not have this syscall, and will return partial writes if more than one
713/// vector is provided:
691/// * Darwin714/// * Darwin
692/// * Windows715/// * Windows
693/// On these systems, the write races with concurrent writes to the same file descriptor, and716///
694/// the file can be in a partially written state when an error occurs.717/// If `iov.len` is larger than will fit in a `u31`, a partial write will occur.
695pub fn pwritev(fd: fd_t, iov: []const iovec_const, offset: u64) WriteError!void {718pub fn pwritev(fd: fd_t, iov: []const iovec_const, offset: u64) PWriteError!usize {
696 if (comptime std.Target.current.isDarwin()) {719 const have_pwrite_but_not_pwritev = switch (std.Target.current.os.tag) {
697 // Darwin does not have pwritev but it does have pwrite.720 .windows, .macosx, .ios, .watchos, .tvos => true,
698 var off: usize = 0;721 else => false,
699 var iov_i: usize = 0;722 };
700 var inner_off: usize = 0;
701 while (true) {
702 const v = iov[iov_i];
703 const rc = darwin.pwrite(fd, v.iov_base + inner_off, v.iov_len - inner_off, offset + off);
704 const err = darwin.getErrno(rc);
705 switch (err) {
706 0 => {
707 const amt_written = @bitCast(usize, rc);
708 off += amt_written;
709 inner_off += amt_written;
710 if (inner_off == v.iov_len) {
711 iov_i += 1;
712 inner_off = 0;
713 if (iov_i == iov.len) {
714 return;
715 }
716 }
717 continue;
718 },
719 EINTR => continue,
720 ESPIPE => unreachable, // `fd` is not seekable.
721 EINVAL => unreachable,
722 EFAULT => unreachable,
723 EAGAIN => if (std.event.Loop.instance) |loop| {
724 loop.waitUntilFdWritable(fd);
725 continue;
726 } else {
727 return error.WouldBlock;
728 },
729 EBADF => unreachable, // Always a race condition.
730 EDESTADDRREQ => unreachable, // `connect` was never called.
731 EDQUOT => return error.DiskQuota,
732 EFBIG => return error.FileTooBig,
733 EIO => return error.InputOutput,
734 ENOSPC => return error.NoSpaceLeft,
735 EPERM => return error.AccessDenied,
736 EPIPE => return error.BrokenPipe,
737 else => return unexpectedErrno(err),
738 }
739 }
740 }
741723
742 if (std.Target.current.os.tag == .windows) {724 if (have_pwrite_but_not_pwritev) {
743 var off = offset;725 // We could loop here; but proper usage of `pwritev` must handle partial writes anyway.
744 for (iov) |item| {726 // So we simply write the first vector only.
745 try pwrite(fd, item.iov_base[0..item.iov_len], off);727 if (iov.len == 0) return @as(usize, 0);
746 off += buf.len;728 const first = iov[0];
747 }729 return pwrite(fd, first.iov_base[0..first.iov_len], offset);
748 return;
749 }730 }
750731
732 const iov_count = math.cast(u31, iov.len) catch math.maxInt(u31);
751 while (true) {733 while (true) {
752 // TODO handle the case when iov_len is too large and get rid of this @intCast734 const rc = system.pwritev(fd, iov.ptr, iov_count, offset);
753 const rc = system.pwritev(fd, iov.ptr, @intCast(u32, iov.len), offset);
754 switch (errno(rc)) {735 switch (errno(rc)) {
755 0 => return,736 0 => return @intCast(usize, rc),
756 EINTR => continue,737 EINTR => continue,
757 EINVAL => unreachable,738 EINVAL => unreachable,
758 EFAULT => unreachable,739 EFAULT => unreachable,
...@@ -770,6 +751,9 @@ pub fn pwritev(fd: fd_t, iov: []const iovec_const, offset: u64) WriteError!void...@@ -770,6 +751,9 @@ pub fn pwritev(fd: fd_t, iov: []const iovec_const, offset: u64) WriteError!void
770 ENOSPC => return error.NoSpaceLeft,751 ENOSPC => return error.NoSpaceLeft,
771 EPERM => return error.AccessDenied,752 EPERM => return error.AccessDenied,
772 EPIPE => return error.BrokenPipe,753 EPIPE => return error.BrokenPipe,
754 ENXIO => return error.Unseekable,
755 ESPIPE => return error.Unseekable,
756 EOVERFLOW => return error.Unseekable,
773 else => |err| return unexpectedErrno(err),757 else => |err| return unexpectedErrno(err),
774 }758 }
775 }759 }
...@@ -3389,7 +3373,6 @@ pub const SendError = error{...@@ -3389,7 +3373,6 @@ pub const SendError = error{
33893373
3390 /// The socket type requires that message be sent atomically, and the size of the message3374 /// The socket type requires that message be sent atomically, and the size of the message
3391 /// to be sent made this impossible. The message is not transmitted.3375 /// to be sent made this impossible. The message is not transmitted.
3392 ///
3393 MessageTooBig,3376 MessageTooBig,
33943377
3395 /// The output queue for a network interface was full. This generally indicates that the3378 /// The output queue for a network interface was full. This generally indicates that the
...@@ -3498,6 +3481,315 @@ pub fn send(...@@ -3498,6 +3481,315 @@ pub fn send(
3498 return sendto(sockfd, buf, flags, null, 0);3481 return sendto(sockfd, buf, flags, null, 0);
3499}3482}
35003483
3484pub const SendFileError = PReadError || WriteError || SendError;
3485
3486fn count_iovec_bytes(iovs: []const iovec_const) usize {
3487 var count: usize = 0;
3488 for (iovs) |iov| {
3489 count += iov.iov_len;
3490 }
3491 return count;
3492}
3493
3494/// Transfer data between file descriptors, with optional headers and trailers.
3495/// Returns the number of bytes written. This will be zero if `in_offset` falls beyond the end of the file.
3496///
3497/// The `sendfile` call copies `count` bytes from one file descriptor to another. When possible,
3498/// this is done within the operating system kernel, which can provide better performance
3499/// characteristics than transferring data from kernel to user space and back, such as with
3500/// `read` and `write` calls. When `count` is `0`, it means to copy until the end of the input file has been
3501/// reached. Note, however, that partial writes are still possible in this case.
3502///
3503/// `in_fd` must be a file descriptor opened for reading, and `out_fd` must be a file descriptor
3504/// opened for writing. They may be any kind of file descriptor; however, if `in_fd` is not a regular
3505/// file system file, it may cause this function to fall back to calling `read` and `write`, in which case
3506/// atomicity guarantees no longer apply.
3507///
3508/// Copying begins reading at `in_offset`. The input file descriptor seek position is ignored and not updated.
3509/// If the output file descriptor has a seek position, it is updated as bytes are written.
3510///
3511/// `flags` has different meanings per operating system; refer to the respective man pages.
3512///
3513/// These systems support atomically sending everything, including headers and trailers:
3514/// * macOS
3515/// * FreeBSD
3516///
3517/// These systems support in-kernel data copying, but headers and trailers are not sent atomically:
3518/// * Linux
3519///
3520/// Other systems fall back to calling `read` / `write`.
3521///
3522/// Linux has a limit on how many bytes may be transferred in one `sendfile` call, which is `0x7ffff000`
3523/// on both 64-bit and 32-bit systems. This is due to using a signed C int as the return value, as
3524/// well as stuffing the errno codes into the last `4096` values. This is cited on the `sendfile` man page.
3525/// The corresponding POSIX limit on this is `math.maxInt(isize)`.
3526pub fn sendfile(
3527 out_fd: fd_t,
3528 in_fd: fd_t,
3529 in_offset: u64,
3530 count: usize,
3531 headers: []const iovec_const,
3532 trailers: []const iovec_const,
3533 flags: u32,
3534) SendFileError!usize {
3535 var header_done = false;
3536 var total_written: usize = 0;
3537
3538 // Prevents EOVERFLOW.
3539 const max_count = switch (std.Target.current.os.tag) {
3540 .linux => 0x7ffff000,
3541 else => math.maxInt(isize),
3542 };
3543
3544 switch (std.Target.current.os.tag) {
3545 .linux => sf: {
3546 // sendfile() first appeared in Linux 2.2, glibc 2.1.
3547 const call_sf = comptime if (builtin.link_libc)
3548 std.c.versionCheck(.{ .major = 2, .minor = 1 }).ok
3549 else
3550 std.Target.current.os.version_range.linux.range.max.order(.{ .major = 2, .minor = 2 }) != .lt;
3551 if (!call_sf) break :sf;
3552
3553 if (headers.len != 0) {
3554 const amt = try writev(out_fd, headers);
3555 total_written += amt;
3556 if (amt < count_iovec_bytes(headers)) return total_written;
3557 header_done = true;
3558 }
3559
3560 // Here we match BSD behavior, making a zero count value send as many bytes as possible.
3561 const adjusted_count = if (count == 0) max_count else math.min(count, max_count);
3562
3563 while (true) {
3564 var offset: off_t = @bitCast(off_t, in_offset);
3565 const rc = system.sendfile(out_fd, in_fd, &offset, adjusted_count);
3566 switch (errno(rc)) {
3567 0 => {
3568 const amt = @bitCast(usize, rc);
3569 total_written += amt;
3570 if (count == 0 and amt == 0) {
3571 // We have detected EOF from `in_fd`.
3572 break;
3573 } else if (amt < count) {
3574 return total_written;
3575 } else {
3576 break;
3577 }
3578 },
3579
3580 EBADF => unreachable, // Always a race condition.
3581 EFAULT => unreachable, // Segmentation fault.
3582 EOVERFLOW => unreachable, // We avoid passing too large of a `count`.
3583 ENOTCONN => unreachable, // `out_fd` is an unconnected socket.
3584
3585 EINVAL, ENOSYS => {
3586 // EINVAL could be any of the following situations:
3587 // * Descriptor is not valid or locked
3588 // * an mmap(2)-like operation is not available for in_fd
3589 // * count is negative
3590 // * out_fd has the O_APPEND flag set
3591 // Because of the "mmap(2)-like operation" possibility, we fall back to doing read/write
3592 // manually, the same as ENOSYS.
3593 break :sf;
3594 },
3595 EAGAIN => if (std.event.Loop.instance) |loop| {
3596 loop.waitUntilFdWritable(out_fd);
3597 continue;
3598 } else {
3599 return error.WouldBlock;
3600 },
3601 EIO => return error.InputOutput,
3602 EPIPE => return error.BrokenPipe,
3603 ENOMEM => return error.SystemResources,
3604 ENXIO => return error.Unseekable,
3605 ESPIPE => return error.Unseekable,
3606 else => |err| {
3607 const discard = unexpectedErrno(err);
3608 break :sf;
3609 },
3610 }
3611 }
3612
3613 if (trailers.len != 0) {
3614 total_written += try writev(out_fd, trailers);
3615 }
3616
3617 return total_written;
3618 },
3619 .freebsd => sf: {
3620 var hdtr_data: std.c.sf_hdtr = undefined;
3621 var hdtr: ?*std.c.sf_hdtr = null;
3622 if (headers.len != 0 or trailers.len != 0) {
3623 // Here we carefully avoid `@intCast` by returning partial writes when
3624 // too many io vectors are provided.
3625 const hdr_cnt = math.cast(u31, headers.len) catch math.maxInt(u31);
3626 if (headers.len > hdr_cnt) return writev(out_fd, headers);
3627
3628 const trl_cnt = math.cast(u31, trailers.len) catch math.maxInt(u31);
3629
3630 hdtr_data = std.c.sf_hdtr{
3631 .headers = headers.ptr,
3632 .hdr_cnt = hdr_cnt,
3633 .trailers = trailers.ptr,
3634 .trl_cnt = trl_cnt,
3635 };
3636 hdtr = &hdtr_data;
3637 }
3638
3639 const adjusted_count = math.min(count, max_count);
3640
3641 while (true) {
3642 var sbytes: off_t = undefined;
3643 const err = errno(system.sendfile(out_fd, in_fd, in_offset, adjusted_count, hdtr, &sbytes, flags));
3644 const amt = @bitCast(usize, sbytes);
3645 switch (err) {
3646 0 => return amt,
3647
3648 EBADF => unreachable, // Always a race condition.
3649 EFAULT => unreachable, // Segmentation fault.
3650 ENOTCONN => unreachable, // `out_fd` is an unconnected socket.
3651
3652 EINVAL, EOPNOTSUPP, ENOTSOCK, ENOSYS => {
3653 // EINVAL could be any of the following situations:
3654 // * The fd argument is not a regular file.
3655 // * The s argument is not a SOCK_STREAM type socket.
3656 // * The offset argument is negative.
3657 // Because of some of these possibilities, we fall back to doing read/write
3658 // manually, the same as ENOSYS.
3659 break :sf;
3660 },
3661
3662 EINTR => if (amt != 0) return amt else continue,
3663
3664 EAGAIN => if (amt != 0) {
3665 return amt;
3666 } else if (std.event.Loop.instance) |loop| {
3667 loop.waitUntilFdWritable(out_fd);
3668 continue;
3669 } else {
3670 return error.WouldBlock;
3671 },
3672
3673 EBUSY => if (amt != 0) {
3674 return amt;
3675 } else if (std.event.Loop.instance) |loop| {
3676 loop.waitUntilFdReadable(in_fd);
3677 continue;
3678 } else {
3679 return error.WouldBlock;
3680 },
3681
3682 EIO => return error.InputOutput,
3683 ENOBUFS => return error.SystemResources,
3684 EPIPE => return error.BrokenPipe,
3685
3686 else => {
3687 const discard = unexpectedErrno(err);
3688 if (amt != 0) {
3689 return amt;
3690 } else {
3691 break :sf;
3692 }
3693 },
3694 }
3695 }
3696 },
3697 .macosx, .ios, .tvos, .watchos => sf: {
3698 var hdtr_data: std.c.sf_hdtr = undefined;
3699 var hdtr: ?*std.c.sf_hdtr = null;
3700 if (headers.len != 0 or trailers.len != 0) {
3701 // Here we carefully avoid `@intCast` by returning partial writes when
3702 // too many io vectors are provided.
3703 const hdr_cnt = math.cast(u31, headers.len) catch math.maxInt(u31);
3704 if (headers.len > hdr_cnt) return writev(out_fd, headers);
3705
3706 const trl_cnt = math.cast(u31, trailers.len) catch math.maxInt(u31);
3707
3708 hdtr_data = std.c.sf_hdtr{
3709 .headers = headers.ptr,
3710 .hdr_cnt = hdr_cnt,
3711 .trailers = trailers.ptr,
3712 .trl_cnt = trl_cnt,
3713 };
3714 hdtr = &hdtr_data;
3715 }
3716
3717 const adjusted_count = math.min(count, @as(u63, max_count));
3718
3719 while (true) {
3720 var sbytes: off_t = adjusted_count;
3721 const signed_offset = @bitCast(i64, in_offset);
3722 const err = errno(system.sendfile(out_fd, in_fd, signed_offset, &sbytes, hdtr, flags));
3723 const amt = @bitCast(usize, sbytes);
3724 switch (err) {
3725 0 => return amt,
3726
3727 EBADF => unreachable, // Always a race condition.
3728 EFAULT => unreachable, // Segmentation fault.
3729 EINVAL => unreachable,
3730 ENOTCONN => unreachable, // `out_fd` is an unconnected socket.
3731
3732 ENOTSUP, ENOTSOCK, ENOSYS => break :sf,
3733
3734 EINTR => if (amt != 0) return amt else continue,
3735
3736 EAGAIN => if (amt != 0) {
3737 return amt;
3738 } else if (std.event.Loop.instance) |loop| {
3739 loop.waitUntilFdWritable(out_fd);
3740 continue;
3741 } else {
3742 return error.WouldBlock;
3743 },
3744
3745 EIO => return error.InputOutput,
3746 EPIPE => return error.BrokenPipe,
3747
3748 else => {
3749 const discard = unexpectedErrno(err);
3750 if (amt != 0) {
3751 return amt;
3752 } else {
3753 break :sf;
3754 }
3755 },
3756 }
3757 }
3758 },
3759 else => {}, // fall back to read/write
3760 }
3761
3762 if (headers.len != 0 and !header_done) {
3763 const amt = try writev(out_fd, headers);
3764 total_written += amt;
3765 if (amt < count_iovec_bytes(headers)) return total_written;
3766 }
3767
3768 rw: {
3769 var buf: [8 * 4096]u8 = undefined;
3770 // Here we match BSD behavior, making a zero count value send as many bytes as possible.
3771 const adjusted_count = if (count == 0) buf.len else math.min(buf.len, count);
3772 const amt_read = try pread(in_fd, buf[0..adjusted_count], in_offset);
3773 if (amt_read == 0) {
3774 if (count == 0) {
3775 // We have detected EOF from `in_fd`.
3776 break :rw;
3777 } else {
3778 return total_written;
3779 }
3780 }
3781 const amt_written = try write(out_fd, buf[0..amt_read]);
3782 total_written += amt_written;
3783 if (amt_written < count or count == 0) return total_written;
3784 }
3785
3786 if (trailers.len != 0) {
3787 total_written += try writev(out_fd, trailers);
3788 }
3789
3790 return total_written;
3791}
3792
3501pub const PollError = error{3793pub const PollError = error{
3502 /// The kernel had no space to allocate file descriptor tables.3794 /// The kernel had no space to allocate file descriptor tables.
3503 SystemResources,3795 SystemResources,
lib/std/os/bits/darwin.zig+3
...@@ -926,6 +926,9 @@ pub const ESOCKTNOSUPPORT = 44;...@@ -926,6 +926,9 @@ pub const ESOCKTNOSUPPORT = 44;
926/// Operation not supported926/// Operation not supported
927pub const ENOTSUP = 45;927pub const ENOTSUP = 45;
928928
929/// Operation not supported. Alias of `ENOTSUP`.
930pub const EOPNOTSUPP = ENOTSUP;
931
929/// Protocol family not supported932/// Protocol family not supported
930pub const EPFNOSUPPORT = 46;933pub const EPFNOSUPPORT = 46;
931934
lib/std/os/bits/linux/arm64.zig+1
...@@ -82,6 +82,7 @@ pub const SYS_pread64 = 67;...@@ -82,6 +82,7 @@ pub const SYS_pread64 = 67;
82pub const SYS_pwrite64 = 68;82pub const SYS_pwrite64 = 68;
83pub const SYS_preadv = 69;83pub const SYS_preadv = 69;
84pub const SYS_pwritev = 70;84pub const SYS_pwritev = 70;
85pub const SYS_sendfile = 71;
85pub const SYS_pselect6 = 72;86pub const SYS_pselect6 = 72;
86pub const SYS_ppoll = 73;87pub const SYS_ppoll = 73;
87pub const SYS_signalfd4 = 74;88pub const SYS_signalfd4 = 74;
lib/std/os/linux.zig+33-2
...@@ -316,8 +316,19 @@ pub fn symlinkat(existing: [*:0]const u8, newfd: i32, newpath: [*:0]const u8) us...@@ -316,8 +316,19 @@ pub fn symlinkat(existing: [*:0]const u8, newfd: i32, newpath: [*:0]const u8) us
316 return syscall3(SYS_symlinkat, @ptrToInt(existing), @bitCast(usize, @as(isize, newfd)), @ptrToInt(newpath));316 return syscall3(SYS_symlinkat, @ptrToInt(existing), @bitCast(usize, @as(isize, newfd)), @ptrToInt(newpath));
317}317}
318318
319pub fn pread(fd: i32, buf: [*]u8, count: usize, offset: usize) usize {319pub fn pread(fd: i32, buf: [*]u8, count: usize, offset: u64) usize {
320 return syscall4(SYS_pread, @bitCast(usize, @as(isize, fd)), @ptrToInt(buf), count, offset);320 if (@hasDecl(@This(), "SYS_pread64")) {
321 return syscall5(
322 SYS_pread64,
323 @bitCast(usize, @as(isize, fd)),
324 @ptrToInt(buf),
325 count,
326 @truncate(usize, offset),
327 @truncate(usize, offset >> 32),
328 );
329 } else {
330 return syscall4(SYS_pread, @bitCast(usize, @as(isize, fd)), @ptrToInt(buf), count, offset);
331 }
321}332}
322333
323pub fn access(path: [*:0]const u8, mode: u32) usize {334pub fn access(path: [*:0]const u8, mode: u32) usize {
...@@ -846,6 +857,26 @@ pub fn sendto(fd: i32, buf: [*]const u8, len: usize, flags: u32, addr: ?*const s...@@ -846,6 +857,26 @@ pub fn sendto(fd: i32, buf: [*]const u8, len: usize, flags: u32, addr: ?*const s
846 return syscall6(SYS_sendto, @bitCast(usize, @as(isize, fd)), @ptrToInt(buf), len, flags, @ptrToInt(addr), @intCast(usize, alen));857 return syscall6(SYS_sendto, @bitCast(usize, @as(isize, fd)), @ptrToInt(buf), len, flags, @ptrToInt(addr), @intCast(usize, alen));
847}858}
848859
860pub fn sendfile(outfd: i32, infd: i32, offset: ?*i64, count: usize) usize {
861 if (@hasDecl(@This(), "SYS_sendfile64")) {
862 return syscall4(
863 SYS_sendfile64,
864 @bitCast(usize, @as(isize, outfd)),
865 @bitCast(usize, @as(isize, infd)),
866 @ptrToInt(offset),
867 count,
868 );
869 } else {
870 return syscall4(
871 SYS_sendfile,
872 @bitCast(usize, @as(isize, outfd)),
873 @bitCast(usize, @as(isize, infd)),
874 @ptrToInt(offset),
875 count,
876 );
877 }
878}
879
849pub fn socketpair(domain: i32, socket_type: i32, protocol: i32, fd: [2]i32) usize {880pub fn socketpair(domain: i32, socket_type: i32, protocol: i32, fd: [2]i32) usize {
850 if (builtin.arch == .i386) {881 if (builtin.arch == .i386) {
851 return socketcall(SC_socketpair, &[4]usize{ @intCast(usize, domain), @intCast(usize, socket_type), @intCast(usize, protocol), @ptrToInt(&fd[0]) });882 return socketcall(SC_socketpair, &[4]usize{ @intCast(usize, domain), @intCast(usize, socket_type), @intCast(usize, protocol), @ptrToInt(&fd[0]) });
lib/std/os/test.zig+115-3
...@@ -44,6 +44,118 @@ fn testThreadIdFn(thread_id: *Thread.Id) void {...@@ -44,6 +44,118 @@ fn testThreadIdFn(thread_id: *Thread.Id) void {
44 thread_id.* = Thread.getCurrentId();44 thread_id.* = Thread.getCurrentId();
45}45}
4646
47test "sendfile" {
48 if (std.Target.current.cpu.arch == .mipsel) {
49 // https://github.com/ziglang/zig/issues/4615
50 return error.SkipZigTest;
51 }
52 try fs.makePath(a, "os_test_tmp");
53 defer fs.deleteTree("os_test_tmp") catch {};
54
55 var dir = try fs.cwd().openDirList("os_test_tmp");
56 defer dir.close();
57
58 const line1 = "line1\n";
59 const line2 = "second line\n";
60 var vecs = [_]os.iovec_const{
61 .{
62 .iov_base = line1,
63 .iov_len = line1.len,
64 },
65 .{
66 .iov_base = line2,
67 .iov_len = line2.len,
68 },
69 };
70
71 var src_file = try dir.createFileC("sendfile1.txt", .{ .read = true });
72 defer src_file.close();
73
74 try src_file.writevAll(&vecs);
75
76 var dest_file = try dir.createFileC("sendfile2.txt", .{ .read = true });
77 defer dest_file.close();
78
79 const header1 = "header1\n";
80 const header2 = "second header\n";
81 var headers = [_]os.iovec_const{
82 .{
83 .iov_base = header1,
84 .iov_len = header1.len,
85 },
86 .{
87 .iov_base = header2,
88 .iov_len = header2.len,
89 },
90 };
91
92 const trailer1 = "trailer1\n";
93 const trailer2 = "second trailer\n";
94 var trailers = [_]os.iovec_const{
95 .{
96 .iov_base = trailer1,
97 .iov_len = trailer1.len,
98 },
99 .{
100 .iov_base = trailer2,
101 .iov_len = trailer2.len,
102 },
103 };
104
105 var written_buf: [header1.len + header2.len + 10 + trailer1.len + trailer2.len]u8 = undefined;
106 try sendfileAll(dest_file.handle, src_file.handle, 1, 10, &headers, &trailers, 0);
107
108 try dest_file.preadAll(&written_buf, 0);
109 expect(mem.eql(u8, &written_buf, "header1\nsecond header\nine1\nsecontrailer1\nsecond trailer\n"));
110}
111
112fn sendfileAll(
113 out_fd: os.fd_t,
114 in_fd: os.fd_t,
115 offset: u64,
116 count: usize,
117 headers: []os.iovec_const,
118 trailers: []os.iovec_const,
119 flags: u32,
120) os.SendFileError!void {
121 var amt: usize = undefined;
122 hdrs: {
123 var i: usize = 0;
124 while (i < headers.len) {
125 amt = try os.sendfile(out_fd, in_fd, offset, count, headers[i..], trailers, flags);
126 while (amt >= headers[i].iov_len) {
127 amt -= headers[i].iov_len;
128 i += 1;
129 if (i >= headers.len) break :hdrs;
130 }
131 headers[i].iov_base += amt;
132 headers[i].iov_len -= amt;
133 }
134 }
135 var off = amt;
136 while (off < count) {
137 amt = try os.sendfile(out_fd, in_fd, offset + off, count - off, &[0]os.iovec_const{}, trailers, flags);
138 off += amt;
139 }
140 amt = off - count;
141 var i: usize = 0;
142 while (i < trailers.len) {
143 while (amt >= headers[i].iov_len) {
144 amt -= trailers[i].iov_len;
145 i += 1;
146 if (i >= trailers.len) return;
147 }
148 trailers[i].iov_base += amt;
149 trailers[i].iov_len -= amt;
150 if (std.Target.current.os.tag == .windows) {
151 amt = try os.writev(out_fd, trailers[i..]);
152 } else {
153 // Here we must use send because it's the only way to give the flags.
154 amt = try os.send(out_fd, trailers[i].iov_base[0..trailers[i].iov_len], flags);
155 }
156 }
157}
158
47test "std.Thread.getCurrentId" {159test "std.Thread.getCurrentId" {
48 if (builtin.single_threaded) return error.SkipZigTest;160 if (builtin.single_threaded) return error.SkipZigTest;
49161
...@@ -103,7 +215,7 @@ test "AtomicFile" {...@@ -103,7 +215,7 @@ test "AtomicFile" {
103 {215 {
104 var af = try fs.AtomicFile.init(test_out_file, File.default_mode);216 var af = try fs.AtomicFile.init(test_out_file, File.default_mode);
105 defer af.deinit();217 defer af.deinit();
106 try af.file.write(test_content);218 try af.file.writeAll(test_content);
107 try af.finish();219 try af.finish();
108 }220 }
109 const content = try io.readFileAlloc(testing.allocator, test_out_file);221 const content = try io.readFileAlloc(testing.allocator, test_out_file);
...@@ -226,7 +338,7 @@ test "pipe" {...@@ -226,7 +338,7 @@ test "pipe" {
226 return error.SkipZigTest;338 return error.SkipZigTest;
227339
228 var fds = try os.pipe();340 var fds = try os.pipe();
229 try os.write(fds[1], "hello");341 expect((try os.write(fds[1], "hello")) == 5);
230 var buf: [16]u8 = undefined;342 var buf: [16]u8 = undefined;
231 expect((try os.read(fds[0], buf[0..])) == 5);343 expect((try os.read(fds[0], buf[0..])) == 5);
232 testing.expectEqualSlices(u8, buf[0..5], "hello");344 testing.expectEqualSlices(u8, buf[0..5], "hello");
...@@ -248,7 +360,7 @@ test "memfd_create" {...@@ -248,7 +360,7 @@ test "memfd_create" {
248 else => |e| return e,360 else => |e| return e,
249 };361 };
250 defer std.os.close(fd);362 defer std.os.close(fd);
251 try std.os.write(fd, "test");363 expect((try std.os.write(fd, "test")) == 4);
252 try std.os.lseek_SET(fd, 0);364 try std.os.lseek_SET(fd, 0);
253365
254 var buf: [10]u8 = undefined;366 var buf: [10]u8 = undefined;
lib/std/os/windows.zig+8-6
...@@ -424,7 +424,7 @@ pub const WriteFileError = error{...@@ -424,7 +424,7 @@ pub const WriteFileError = error{
424 Unexpected,424 Unexpected,
425};425};
426426
427pub fn WriteFile(handle: HANDLE, bytes: []const u8, offset: ?u64) WriteFileError!void {427pub fn WriteFile(handle: HANDLE, bytes: []const u8, offset: ?u64) WriteFileError!usize {
428 if (std.event.Loop.instance) |loop| {428 if (std.event.Loop.instance) |loop| {
429 // TODO support async WriteFile with no offset429 // TODO support async WriteFile with no offset
430 const off = offset.?;430 const off = offset.?;
...@@ -445,8 +445,8 @@ pub fn WriteFile(handle: HANDLE, bytes: []const u8, offset: ?u64) WriteFileError...@@ -445,8 +445,8 @@ pub fn WriteFile(handle: HANDLE, bytes: []const u8, offset: ?u64) WriteFileError
445 _ = CreateIoCompletionPort(fd, loop.os_data.io_port, undefined, undefined);445 _ = CreateIoCompletionPort(fd, loop.os_data.io_port, undefined, undefined);
446 loop.beginOneEvent();446 loop.beginOneEvent();
447 suspend {447 suspend {
448 // TODO replace this @intCast with a loop that writes all the bytes448 const adjusted_len = math.cast(windows.DWORD, bytes.len) catch maxInt(windows.DWORD);
449 _ = kernel32.WriteFile(fd, bytes.ptr, @intCast(windows.DWORD, bytes.len), null, &resume_node.base.overlapped);449 _ = kernel32.WriteFile(fd, bytes.ptr, adjusted_len, null, &resume_node.base.overlapped);
450 }450 }
451 var bytes_transferred: windows.DWORD = undefined;451 var bytes_transferred: windows.DWORD = undefined;
452 if (kernel32.GetOverlappedResult(fd, &resume_node.base.overlapped, &bytes_transferred, FALSE) == 0) {452 if (kernel32.GetOverlappedResult(fd, &resume_node.base.overlapped, &bytes_transferred, FALSE) == 0) {
...@@ -460,6 +460,7 @@ pub fn WriteFile(handle: HANDLE, bytes: []const u8, offset: ?u64) WriteFileError...@@ -460,6 +460,7 @@ pub fn WriteFile(handle: HANDLE, bytes: []const u8, offset: ?u64) WriteFileError
460 else => |err| return windows.unexpectedError(err),460 else => |err| return windows.unexpectedError(err),
461 }461 }
462 }462 }
463 return bytes_transferred;
463 } else {464 } else {
464 var bytes_written: DWORD = undefined;465 var bytes_written: DWORD = undefined;
465 var overlapped_data: OVERLAPPED = undefined;466 var overlapped_data: OVERLAPPED = undefined;
...@@ -473,18 +474,19 @@ pub fn WriteFile(handle: HANDLE, bytes: []const u8, offset: ?u64) WriteFileError...@@ -473,18 +474,19 @@ pub fn WriteFile(handle: HANDLE, bytes: []const u8, offset: ?u64) WriteFileError
473 };474 };
474 break :blk &overlapped_data;475 break :blk &overlapped_data;
475 } else null;476 } else null;
476 // TODO replace this @intCast with a loop that writes all the bytes477 const adjusted_len = math.cast(u32, bytes.len) catch maxInt(u32);
477 if (kernel32.WriteFile(handle, bytes.ptr, @intCast(u32, bytes.len), &bytes_written, overlapped) == 0) {478 if (kernel32.WriteFile(handle, bytes.ptr, adjusted_len, &bytes_written, overlapped) == 0) {
478 switch (kernel32.GetLastError()) {479 switch (kernel32.GetLastError()) {
479 .INVALID_USER_BUFFER => return error.SystemResources,480 .INVALID_USER_BUFFER => return error.SystemResources,
480 .NOT_ENOUGH_MEMORY => return error.SystemResources,481 .NOT_ENOUGH_MEMORY => return error.SystemResources,
481 .OPERATION_ABORTED => return error.OperationAborted,482 .OPERATION_ABORTED => return error.OperationAborted,
482 .NOT_ENOUGH_QUOTA => return error.SystemResources,483 .NOT_ENOUGH_QUOTA => return error.SystemResources,
483 .IO_PENDING => unreachable, // this function is for blocking files only484 .IO_PENDING => unreachable,
484 .BROKEN_PIPE => return error.BrokenPipe,485 .BROKEN_PIPE => return error.BrokenPipe,
485 else => |err| return unexpectedError(err),486 else => |err| return unexpectedError(err),
486 }487 }
487 }488 }
489 return bytes_written;
488 }490 }
489}491}
490492
lib/std/zig/render.zig+5-4
...@@ -29,7 +29,7 @@ pub fn render(allocator: *mem.Allocator, stream: var, tree: *ast.Tree) (@TypeOf(...@@ -29,7 +29,7 @@ pub fn render(allocator: *mem.Allocator, stream: var, tree: *ast.Tree) (@TypeOf(
29 source_index: usize,29 source_index: usize,
30 source: []const u8,30 source: []const u8,
3131
32 fn write(iface_stream: *Stream, bytes: []const u8) StreamError!void {32 fn write(iface_stream: *Stream, bytes: []const u8) StreamError!usize {
33 const self = @fieldParentPtr(MyStream, "stream", iface_stream);33 const self = @fieldParentPtr(MyStream, "stream", iface_stream);
3434
35 if (!self.anything_changed_ptr.*) {35 if (!self.anything_changed_ptr.*) {
...@@ -45,7 +45,7 @@ pub fn render(allocator: *mem.Allocator, stream: var, tree: *ast.Tree) (@TypeOf(...@@ -45,7 +45,7 @@ pub fn render(allocator: *mem.Allocator, stream: var, tree: *ast.Tree) (@TypeOf(
45 }45 }
46 }46 }
4747
48 try self.child_stream.write(bytes);48 return self.child_stream.writeOnce(bytes);
49 }49 }
50 };50 };
51 var my_stream = MyStream{51 var my_stream = MyStream{
...@@ -2443,14 +2443,15 @@ const FindByteOutStream = struct {...@@ -2443,14 +2443,15 @@ const FindByteOutStream = struct {
2443 };2443 };
2444 }2444 }
24452445
2446 fn writeFn(out_stream: *Stream, bytes: []const u8) Error!void {2446 fn writeFn(out_stream: *Stream, bytes: []const u8) Error!usize {
2447 const self = @fieldParentPtr(Self, "stream", out_stream);2447 const self = @fieldParentPtr(Self, "stream", out_stream);
2448 if (self.byte_found) return;2448 if (self.byte_found) return bytes.len;
2449 self.byte_found = blk: {2449 self.byte_found = blk: {
2450 for (bytes) |b|2450 for (bytes) |b|
2451 if (b == self.byte) break :blk true;2451 if (b == self.byte) break :blk true;
2452 break :blk false;2452 break :blk false;
2453 };2453 };
2454 return bytes.len;
2454 }2455 }
2455};2456};
24562457
lib/std/zig/system.zig+1
...@@ -796,6 +796,7 @@ pub const NativeTargetInfo = struct {...@@ -796,6 +796,7 @@ pub const NativeTargetInfo = struct {
796 error.SystemResources => return error.SystemResources,796 error.SystemResources => return error.SystemResources,
797 error.IsDir => return error.UnableToReadElfFile,797 error.IsDir => return error.UnableToReadElfFile,
798 error.BrokenPipe => return error.UnableToReadElfFile,798 error.BrokenPipe => return error.UnableToReadElfFile,
799 error.Unseekable => return error.UnableToReadElfFile,
799 error.ConnectionResetByPeer => return error.UnableToReadElfFile,800 error.ConnectionResetByPeer => return error.UnableToReadElfFile,
800 error.Unexpected => return error.Unexpected,801 error.Unexpected => return error.Unexpected,
801 error.InputOutput => return error.FileSystem,802 error.InputOutput => return error.FileSystem,
test/standalone/cat/main.zig+2-1
...@@ -42,6 +42,7 @@ fn usage(exe: []const u8) !void {...@@ -42,6 +42,7 @@ fn usage(exe: []const u8) !void {
42 return error.Invalid;42 return error.Invalid;
43}43}
4444
45// TODO use copy_file_range
45fn cat_file(stdout: fs.File, file: fs.File) !void {46fn cat_file(stdout: fs.File, file: fs.File) !void {
46 var buf: [1024 * 4]u8 = undefined;47 var buf: [1024 * 4]u8 = undefined;
4748
...@@ -55,7 +56,7 @@ fn cat_file(stdout: fs.File, file: fs.File) !void {...@@ -55,7 +56,7 @@ fn cat_file(stdout: fs.File, file: fs.File) !void {
55 break;56 break;
56 }57 }
5758
58 stdout.write(buf[0..bytes_read]) catch |err| {59 stdout.writeAll(buf[0..bytes_read]) catch |err| {
59 warn("Unable to write to stdout: {}\n", .{@errorName(err)});60 warn("Unable to write to stdout: {}\n", .{@errorName(err)});
60 return err;61 return err;
61 };62 };
test/standalone/hello_world/hello.zig+1-4
...@@ -1,8 +1,5 @@...@@ -1,8 +1,5 @@
1const std = @import("std");1const std = @import("std");
22
3pub fn main() !void {3pub fn main() !void {
4 const stdout_file = std.io.getStdOut();4 try std.io.getStdOut().writeAll("Hello, World!\n");
5 // If this program encounters pipe failure when printing to stdout, exit
6 // with an error.
7 try stdout_file.write("Hello, world!\n");
8}5}