| author | |
| committer | |
| log | c81345c8aec56a108f6f98001666a1552d65ce85 |
| tree | 6a18842256a157896d175d4e254811b9bf478090 |
| parent | bd287dd1942f0a72e6bd9dc8475bd4e7d34fa5f8 |
| signature |
* 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 aarch6420 files changed, 816 insertions(+), 352 deletions(-)
lib/std/c.zig+1-1| ... | ... | @@ -142,7 +142,7 @@ pub extern "c" fn sendto( |
| 142 | 142 | buf: *const c_void, |
| 143 | 143 | len: usize, |
| 144 | 144 | flags: u32, |
| 145 | dest_addr: *const sockaddr, | |
| 145 | dest_addr: ?*const sockaddr, | |
| 146 | 146 | addrlen: socklen_t, |
| 147 | 147 | ) isize; |
| 148 | 148 |
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 | 55 | pub extern "c" fn host_get_clock_service(host: host_t, clock_id: clock_id_t, clock_serv: ?[*]clock_serv_t) kern_return_t; |
| 56 | 56 | pub extern "c" fn mach_port_deallocate(task: ipc_space_t, name: mach_port_name_t) kern_return_t; |
| 57 | 57 | |
| 58 | pub const sf_hdtr = extern struct { | |
| 59 | headers: [*]iovec_const, | |
| 60 | hdr_cnt: c_int, | |
| 61 | trailers: [*]iovec_const, | |
| 62 | trl_cnt: c_int, | |
| 63 | }; | |
| 64 | ||
| 65 | pub 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 | ||
| 58 | 74 | pub fn sigaddset(set: *sigset_t, signo: u5) void { |
| 59 | 75 | set.* |= @as(u32, 1) << (signo - 1); |
| 60 | 76 | } |
lib/std/c/freebsd.zig+9-1| ... | ... | @@ -14,7 +14,15 @@ pub const sf_hdtr = extern struct { |
| 14 | 14 | trailers: [*]iovec_const, |
| 15 | 15 | trl_cnt: c_int, |
| 16 | 16 | }; |
| 17 | pub extern "c" fn sendfile(fd: c_int, s: c_int, offset: u64, nbytes: usize, sf_hdtr: ?*sf_hdtr, sbytes: ?*u64, flags: c_int) c_int; | |
| 17 | pub 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; | |
| 18 | 26 | |
| 19 | 27 | pub const dl_iterate_phdr_callback = extern fn (info: *dl_phdr_info, size: usize, data: ?*c_void) c_int; |
| 20 | 28 | pub extern "c" fn dl_iterate_phdr(callback: dl_iterate_phdr_callback, data: ?*c_void) c_int; |
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 | 82 | |
| 83 | 83 | pub extern "c" fn memfd_create(name: [*:0]const u8, flags: c_uint) c_int; |
| 84 | 84 | |
| 85 | pub extern "c" fn sendfile( | |
| 86 | out_fd: fd_t, | |
| 87 | in_fd: fd_t, | |
| 88 | offset: ?*off_t, | |
| 89 | count: usize, | |
| 90 | ) isize; | |
| 91 | ||
| 85 | 92 | pub const pthread_attr_t = extern struct { |
| 86 | 93 | __size: [56]u8, |
| 87 | 94 | __align: c_long, |
lib/std/event/loop.zig+12-10| ... | ... | @@ -236,7 +236,8 @@ pub const Loop = struct { |
| 236 | 236 | var extra_thread_index: usize = 0; |
| 237 | 237 | errdefer { |
| 238 | 238 | // 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 | 241 | while (extra_thread_index != 0) { |
| 241 | 242 | extra_thread_index -= 1; |
| 242 | 243 | self.extra_threads[extra_thread_index].wait(); |
| ... | ... | @@ -682,7 +683,8 @@ pub const Loop = struct { |
| 682 | 683 | .linux => { |
| 683 | 684 | self.posixFsRequest(&self.os_data.fs_end_request); |
| 684 | 685 | // 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 | 688 | return; |
| 687 | 689 | }, |
| 688 | 690 | .macosx, .freebsd, .netbsd, .dragonfly => { |
| ... | ... | @@ -831,7 +833,7 @@ pub const Loop = struct { |
| 831 | 833 | |
| 832 | 834 | /// Performs an async `os.write` using a separate thread. |
| 833 | 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 | 837 | var req_node = Request.Node{ |
| 836 | 838 | .data = .{ |
| 837 | 839 | .msg = .{ |
| ... | ... | @@ -852,7 +854,7 @@ pub const Loop = struct { |
| 852 | 854 | |
| 853 | 855 | /// Performs an async `os.writev` using a separate thread. |
| 854 | 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 | 858 | var req_node = Request.Node{ |
| 857 | 859 | .data = .{ |
| 858 | 860 | .msg = .{ |
| ... | ... | @@ -873,7 +875,7 @@ pub const Loop = struct { |
| 873 | 875 | |
| 874 | 876 | /// Performs an async `os.pwritev` using a separate thread. |
| 875 | 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 | 879 | var req_node = Request.Node{ |
| 878 | 880 | .data = .{ |
| 879 | 881 | .msg = .{ |
| ... | ... | @@ -1137,7 +1139,7 @@ pub const Loop = struct { |
| 1137 | 1139 | pub const Write = struct { |
| 1138 | 1140 | fd: os.fd_t, |
| 1139 | 1141 | bytes: []const u8, |
| 1140 | result: Error!void, | |
| 1142 | result: Error!usize, | |
| 1141 | 1143 | |
| 1142 | 1144 | pub const Error = os.WriteError; |
| 1143 | 1145 | }; |
| ... | ... | @@ -1145,7 +1147,7 @@ pub const Loop = struct { |
| 1145 | 1147 | pub const WriteV = struct { |
| 1146 | 1148 | fd: os.fd_t, |
| 1147 | 1149 | iov: []const os.iovec_const, |
| 1148 | result: Error!void, | |
| 1150 | result: Error!usize, | |
| 1149 | 1151 | |
| 1150 | 1152 | pub const Error = os.WriteError; |
| 1151 | 1153 | }; |
| ... | ... | @@ -1154,9 +1156,9 @@ pub const Loop = struct { |
| 1154 | 1156 | fd: os.fd_t, |
| 1155 | 1157 | iov: []const os.iovec_const, |
| 1156 | 1158 | offset: usize, |
| 1157 | result: Error!void, | |
| 1159 | result: Error!usize, | |
| 1158 | 1160 | |
| 1159 | pub const Error = os.WriteError; | |
| 1161 | pub const Error = os.PWriteError; | |
| 1160 | 1162 | }; |
| 1161 | 1163 | |
| 1162 | 1164 | pub const PReadV = struct { |
| ... | ... | @@ -1165,7 +1167,7 @@ pub const Loop = struct { |
| 1165 | 1167 | offset: usize, |
| 1166 | 1168 | result: Error!usize, |
| 1167 | 1169 | |
| 1168 | pub const Error = os.ReadError; | |
| 1170 | pub const Error = os.PReadError; | |
| 1169 | 1171 | }; |
| 1170 | 1172 | |
| 1171 | 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 | 153 | var buf: [mem.page_size * 6]u8 = undefined; |
| 154 | 154 | while (true) { |
| 155 | 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 | 157 | if (amt != buf.len) { |
| 158 | 158 | try atomic_file.file.updateTimes(src_stat.atime, src_stat.mtime); |
| 159 | 159 | try atomic_file.finish(); |
| ... | ... | @@ -1329,7 +1329,7 @@ pub const Dir = struct { |
| 1329 | 1329 | pub fn writeFile(self: Dir, sub_path: []const u8, data: []const u8) !void { |
| 1330 | 1330 | var file = try self.createFile(sub_path, .{}); |
| 1331 | 1331 | defer file.close(); |
| 1332 | try file.write(data); | |
| 1332 | try file.writeAll(data); | |
| 1333 | 1333 | } |
| 1334 | 1334 | |
| 1335 | 1335 | pub const AccessError = os.AccessError; |
lib/std/fs/file.zig+123-17| ... | ... | @@ -228,63 +228,169 @@ pub const File = struct { |
| 228 | 228 | } |
| 229 | 229 | |
| 230 | 230 | pub const ReadError = os.ReadError; |
| 231 | pub const PReadError = os.PReadError; | |
| 231 | 232 | |
| 232 | 233 | pub fn read(self: File, buffer: []u8) ReadError!usize { |
| 233 | 234 | if (need_async_thread and self.io_mode == .blocking and !self.async_block_allowed) { |
| 234 | 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 | } |
| 238 | 240 | |
| 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 | 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 | } |
| 245 | 262 | |
| 246 | 263 | pub fn readv(self: File, iovecs: []const os.iovec) ReadError!usize { |
| 247 | 264 | if (need_async_thread and self.io_mode == .blocking and !self.async_block_allowed) { |
| 248 | 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 | } |
| 252 | 270 | |
| 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 | 288 | if (need_async_thread and self.io_mode == .blocking and !self.async_block_allowed) { |
| 255 | 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 | } |
| 259 | 312 | |
| 260 | 313 | pub const WriteError = os.WriteError; |
| 314 | pub const PWriteError = os.PWriteError; | |
| 261 | 315 | |
| 262 | pub fn write(self: File, bytes: []const u8) WriteError!void { | |
| 316 | pub fn write(self: File, bytes: []const u8) WriteError!usize { | |
| 263 | 317 | if (need_async_thread and self.io_mode == .blocking and !self.async_block_allowed) { |
| 264 | 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 | } |
| 268 | 323 | |
| 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 | 332 | if (need_async_thread and self.io_mode == .blocking and !self.async_block_allowed) { |
| 271 | 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 | } |
| 275 | 338 | |
| 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 | 347 | if (need_async_thread and self.io_mode == .blocking and !self.async_block_allowed) { |
| 278 | 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 | } |
| 282 | 353 | |
| 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 | 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 | } |
| 289 | 395 | |
| 290 | 396 | pub fn inStream(file: File) InStream { |
| ... | ... | @@ -335,7 +441,7 @@ pub const File = struct { |
| 335 | 441 | pub const Error = WriteError; |
| 336 | 442 | pub const Stream = io.OutStream(Error); |
| 337 | 443 | |
| 338 | fn writeFn(out_stream: *Stream, bytes: []const u8) Error!void { | |
| 444 | fn writeFn(out_stream: *Stream, bytes: []const u8) Error!usize { | |
| 339 | 445 | const self = @fieldParentPtr(OutStream, "stream", out_stream); |
| 340 | 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 | 437 | }; |
| 438 | 438 | } |
| 439 | 439 | |
| 440 | /// This is a simple OutStream that writes to a fixed buffer, and returns an error | |
| 441 | /// when it runs out of space. | |
| 440 | /// This is a simple OutStream that writes to a fixed buffer. If the returned number | |
| 441 | /// of bytes written is less than requested, the buffer is full. | |
| 442 | /// Returns error.OutOfMemory when no bytes would be written. | |
| 442 | 443 | pub const SliceOutStream = struct { |
| 443 | pub const Error = error{OutOfSpace}; | |
| 444 | pub const Error = error{OutOfMemory}; | |
| 444 | 445 | pub const Stream = OutStream(Error); |
| 445 | 446 | |
| 446 | 447 | stream: Stream, |
| ... | ... | @@ -464,9 +465,11 @@ pub const SliceOutStream = struct { |
| 464 | 465 | self.pos = 0; |
| 465 | 466 | } |
| 466 | 467 | |
| 467 | fn writeFn(out_stream: *Stream, bytes: []const u8) Error!void { | |
| 468 | fn writeFn(out_stream: *Stream, bytes: []const u8) Error!usize { | |
| 468 | 469 | const self = @fieldParentPtr(SliceOutStream, "stream", out_stream); |
| 469 | 470 | |
| 471 | if (bytes.len == 0) return 0; | |
| 472 | ||
| 470 | 473 | assert(self.pos <= self.slice.len); |
| 471 | 474 | |
| 472 | 475 | const n = if (self.pos + bytes.len <= self.slice.len) |
| ... | ... | @@ -477,9 +480,9 @@ pub const SliceOutStream = struct { |
| 477 | 480 | std.mem.copy(u8, self.slice[self.pos .. self.pos + n], bytes[0..n]); |
| 478 | 481 | self.pos += n; |
| 479 | 482 | |
| 480 | if (n < bytes.len) { | |
| 481 | return Error.OutOfSpace; | |
| 482 | } | |
| 483 | if (n == 0) return error.OutOfMemory; | |
| 484 | ||
| 485 | return n; | |
| 483 | 486 | } |
| 484 | 487 | }; |
| 485 | 488 | |
| ... | ... | @@ -508,7 +511,9 @@ pub const NullOutStream = struct { |
| 508 | 511 | }; |
| 509 | 512 | } |
| 510 | 513 | |
| 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 | }; |
| 513 | 518 | |
| 514 | 519 | test "io.NullOutStream" { |
| ... | ... | @@ -536,10 +541,11 @@ pub fn CountingOutStream(comptime OutStreamError: type) type { |
| 536 | 541 | }; |
| 537 | 542 | } |
| 538 | 543 | |
| 539 | fn writeFn(out_stream: *Stream, bytes: []const u8) OutStreamError!void { | |
| 544 | fn writeFn(out_stream: *Stream, bytes: []const u8) OutStreamError!usize { | |
| 540 | 545 | const self = @fieldParentPtr(Self, "stream", out_stream); |
| 541 | 546 | try self.child_stream.write(bytes); |
| 542 | 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 | 594 | } |
| 589 | 595 | } |
| 590 | 596 | |
| 591 | fn writeFn(out_stream: *Stream, bytes: []const u8) Error!void { | |
| 597 | fn writeFn(out_stream: *Stream, bytes: []const u8) Error!usize { | |
| 592 | 598 | const self = @fieldParentPtr(Self, "stream", out_stream); |
| 593 | 599 | if (bytes.len >= self.fifo.writableLength()) { |
| 594 | 600 | try self.flush(); |
| 595 | return self.unbuffered_out_stream.write(bytes); | |
| 601 | return self.unbuffered_out_stream.writeOnce(bytes); | |
| 596 | 602 | } |
| 597 | 603 | self.fifo.writeAssumeCapacity(bytes); |
| 604 | return bytes.len; | |
| 598 | 605 | } |
| 599 | 606 | }; |
| 600 | 607 | } |
| ... | ... | @@ -614,9 +621,10 @@ pub const BufferOutStream = struct { |
| 614 | 621 | }; |
| 615 | 622 | } |
| 616 | 623 | |
| 617 | fn writeFn(out_stream: *Stream, bytes: []const u8) !void { | |
| 624 | fn writeFn(out_stream: *Stream, bytes: []const u8) !usize { | |
| 618 | 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 | }; |
| 622 | 630 | |
| ... | ... | @@ -734,17 +742,17 @@ pub fn BitOutStream(endian: builtin.Endian, comptime Error: type) type { |
| 734 | 742 | self.bit_count = 0; |
| 735 | 743 | } |
| 736 | 744 | |
| 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 | 746 | var self = @fieldParentPtr(Self, "stream", self_stream); |
| 739 | 747 | |
| 740 | //@NOTE: I'm not sure this is a good idea, maybe flushBits should be forced | |
| 748 | // TODO: I'm not sure this is a good idea, maybe flushBits should be forced | |
| 741 | 749 | if (self.bit_count > 0) { |
| 742 | 750 | for (buffer) |b, i| |
| 743 | 751 | try self.writeBits(b, u8_bit_count); |
| 744 | return; | |
| 752 | return buffer.len; | |
| 745 | 753 | } |
| 746 | 754 | |
| 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 | 20 | }; |
| 21 | 21 | } |
| 22 | 22 | |
| 23 | fn writeFn(out_stream: *Stream, bytes: []const u8) Error!void { | |
| 23 | fn writeFn(out_stream: *Stream, bytes: []const u8) Error!usize { | |
| 24 | 24 | const self = @fieldParentPtr(COutStream, "stream", out_stream); |
| 25 | 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 | 27 | switch (std.c._errno().*) { |
| 28 | 28 | 0 => unreachable, |
| 29 | 29 | os.EINVAL => unreachable, |
lib/std/io/out_stream.zig+10-3| ... | ... | @@ -14,13 +14,13 @@ pub fn OutStream(comptime WriteError: type) type { |
| 14 | 14 | const Self = @This(); |
| 15 | 15 | pub const Error = WriteError; |
| 16 | 16 | pub const WriteFn = if (std.io.is_async) |
| 17 | async fn (self: *Self, bytes: []const u8) Error!void | |
| 17 | async fn (self: *Self, bytes: []const u8) Error!usize | |
| 18 | 18 | else |
| 19 | fn (self: *Self, bytes: []const u8) Error!void; | |
| 19 | fn (self: *Self, bytes: []const u8) Error!usize; | |
| 20 | 20 | |
| 21 | 21 | writeFn: WriteFn, |
| 22 | 22 | |
| 23 | pub fn write(self: *Self, bytes: []const u8) Error!void { | |
| 23 | pub fn writeOnce(self: *Self, bytes: []const u8) Error!usize { | |
| 24 | 24 | if (std.io.is_async) { |
| 25 | 25 | // Let's not be writing 0xaa in safe modes for upwards of 4 MiB for every stream write. |
| 26 | 26 | @setRuntimeSafety(false); |
| ... | ... | @@ -31,6 +31,13 @@ pub fn OutStream(comptime WriteError: type) type { |
| 31 | 31 | } |
| 32 | 32 | } |
| 33 | 33 | |
| 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 | 41 | pub fn print(self: *Self, comptime format: []const u8, args: var) Error!void { |
| 35 | 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 | 134 | try ss.stream.write("world"); |
| 135 | 135 | expect(mem.eql(u8, ss.getWritten(), "Helloworld")); |
| 136 | 136 | |
| 137 | expectError(error.OutOfSpace, ss.stream.write("!")); | |
| 137 | expectError(error.OutOfMemory, ss.stream.write("!")); | |
| 138 | 138 | expect(mem.eql(u8, ss.getWritten(), "Helloworld")); |
| 139 | 139 | |
| 140 | 140 | ss.reset(); |
| 141 | 141 | expect(ss.getWritten().len == 0); |
| 142 | 142 | |
| 143 | expectError(error.OutOfSpace, ss.stream.write("Hello world!")); | |
| 143 | expectError(error.OutOfMemory, ss.stream.write("Hello world!")); | |
| 144 | 144 | expect(mem.eql(u8, ss.getWritten(), "Hello worl")); |
| 145 | 145 | } |
| 146 | 146 | |
| ... | ... | @@ -617,7 +617,7 @@ test "File seek ops" { |
| 617 | 617 | fs.cwd().deleteFile(tmp_file_name) catch {}; |
| 618 | 618 | } |
| 619 | 619 | |
| 620 | try file.write(&([_]u8{0x55} ** 8192)); | |
| 620 | try file.writeAll(&([_]u8{0x55} ** 8192)); | |
| 621 | 621 | |
| 622 | 622 | // Seek to the end |
| 623 | 623 | try file.seekFromEnd(0); |
lib/std/os.zig+449-273| ... | ... | @@ -298,6 +298,11 @@ pub const ReadError = error{ |
| 298 | 298 | /// buf.len. If 0 bytes were read, that means EOF. |
| 299 | 299 | /// If the application has a global event loop enabled, EAGAIN is handled |
| 300 | 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)`. | |
| 301 | 306 | pub fn read(fd: fd_t, buf: []u8) ReadError!usize { |
| 302 | 307 | if (builtin.os.tag == .windows) { |
| 303 | 308 | return windows.ReadFile(fd, buf, null); |
| ... | ... | @@ -316,8 +321,15 @@ pub fn read(fd: fd_t, buf: []u8) ReadError!usize { |
| 316 | 321 | } |
| 317 | 322 | } |
| 318 | 323 | |
| 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 | 331 | while (true) { |
| 320 | const rc = system.read(fd, buf.ptr, buf.len); | |
| 332 | const rc = system.read(fd, buf.ptr, adjusted_len); | |
| 321 | 333 | switch (errno(rc)) { |
| 322 | 334 | 0 => return @intCast(usize, rc), |
| 323 | 335 | EINTR => continue, |
| ... | ... | @@ -352,32 +364,18 @@ pub fn read(fd: fd_t, buf: []u8) ReadError!usize { |
| 352 | 364 | /// * Windows |
| 353 | 365 | /// On these systems, the read races with concurrent writes to the same file descriptor. |
| 354 | 366 | pub fn readv(fd: fd_t, iov: []const iovec) ReadError!usize { |
| 355 | if (builtin.os.tag == .windows) { | |
| 356 | // TODO batch these into parallel requests | |
| 357 | var off: usize = 0; | |
| 358 | var iov_i: usize = 0; | |
| 359 | var inner_off: usize = 0; | |
| 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 | |
| 367 | if (std.Target.current.os.tag == .windows) { | |
| 368 | // TODO does Windows have a way to read an io vector? | |
| 369 | if (iov.len == 0) return @as(usize, 0); | |
| 370 | const first = iov[0]; | |
| 371 | return read(fd, first.iov_base[0..first.iov_len]); | |
| 374 | 372 | } |
| 375 | 373 | |
| 376 | 374 | while (true) { |
| 377 | 375 | // 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 | 377 | switch (errno(rc)) { |
| 380 | 0 => return @bitCast(usize, rc), | |
| 378 | 0 => return @intCast(usize, rc), | |
| 381 | 379 | EINTR => continue, |
| 382 | 380 | EINVAL => unreachable, |
| 383 | 381 | EFAULT => unreachable, |
| ... | ... | @@ -397,6 +395,8 @@ pub fn readv(fd: fd_t, iov: []const iovec) ReadError!usize { |
| 397 | 395 | } |
| 398 | 396 | } |
| 399 | 397 | |
| 398 | pub const PReadError = ReadError || error{Unseekable}; | |
| 399 | ||
| 400 | 400 | /// Number of bytes read is returned. Upon reading end-of-file, zero is returned. |
| 401 | 401 | /// |
| 402 | 402 | /// Retries when interrupted by a signal. |
| ... | ... | @@ -405,7 +405,7 @@ pub fn readv(fd: fd_t, iov: []const iovec) ReadError!usize { |
| 405 | 405 | /// via the event loop. Otherwise EAGAIN results in `error.WouldBlock`. |
| 406 | 406 | /// On Windows, if the application has a global event loop enabled, I/O Completion Ports are |
| 407 | 407 | /// used to perform the I/O. `error.WouldBlock` is not possible on Windows. |
| 408 | pub fn pread(fd: fd_t, buf: []u8, offset: u64) ReadError!usize { | |
| 408 | pub fn pread(fd: fd_t, buf: []u8, offset: u64) PReadError!usize { | |
| 409 | 409 | if (builtin.os.tag == .windows) { |
| 410 | 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 | 429 | ENOBUFS => return error.SystemResources, |
| 430 | 430 | ENOMEM => return error.SystemResources, |
| 431 | 431 | ECONNRESET => return error.ConnectionResetByPeer, |
| 432 | ENXIO => return error.Unseekable, | |
| 433 | ESPIPE => return error.Unseekable, | |
| 434 | EOVERFLOW => return error.Unseekable, | |
| 432 | 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 | 451 | /// * Darwin |
| 449 | 452 | /// * Windows |
| 450 | 453 | /// On these systems, the read races with concurrent writes to the same file descriptor. |
| 451 | pub fn preadv(fd: fd_t, iov: []const iovec, offset: u64) ReadError!usize { | |
| 452 | if (comptime std.Target.current.isDarwin()) { | |
| 453 | // Darwin does not have preadv but it does have pread. | |
| 454 | var off: usize = 0; | |
| 455 | var iov_i: usize = 0; | |
| 456 | var inner_off: usize = 0; | |
| 457 | while (true) { | |
| 458 | const v = iov[iov_i]; | |
| 459 | const rc = darwin.pread(fd, v.iov_base + inner_off, v.iov_len - inner_off, offset + off); | |
| 460 | const err = darwin.getErrno(rc); | |
| 461 | switch (err) { | |
| 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 | } | |
| 454 | pub fn preadv(fd: fd_t, iov: []const iovec, offset: u64) PReadError!usize { | |
| 455 | const have_pread_but_not_preadv = switch (std.Target.current.os.tag) { | |
| 456 | .windows, .macosx, .ios, .watchos, .tvos => true, | |
| 457 | else => false, | |
| 458 | }; | |
| 459 | if (have_pread_but_not_preadv) { | |
| 460 | // We could loop here; but proper usage of `preadv` must handle partial reads anyway. | |
| 461 | // So we simply read into the first vector only. | |
| 462 | if (iov.len == 0) return @as(usize, 0); | |
| 463 | const first = iov[0]; | |
| 464 | return pread(fd, first.iov_base[0..first.iov_len], offset); | |
| 494 | 465 | } |
| 495 | 466 | |
| 496 | if (builtin.os.tag == .windows) { | |
| 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 | } | |
| 467 | const iov_count = math.cast(u31, iov.len) catch math.maxInt(u31); | |
| 516 | 468 | |
| 517 | 469 | while (true) { |
| 518 | // TODO handle the case when iov_len is too large and get rid of this @intCast | |
| 519 | const rc = system.preadv(fd, iov.ptr, @intCast(u32, iov.len), offset); | |
| 470 | const rc = system.preadv(fd, iov.ptr, iov_count, offset); | |
| 520 | 471 | switch (errno(rc)) { |
| 521 | 472 | 0 => return @bitCast(usize, rc), |
| 522 | 473 | EINTR => continue, |
| ... | ... | @@ -533,6 +484,9 @@ pub fn preadv(fd: fd_t, iov: []const iovec, offset: u64) ReadError!usize { |
| 533 | 484 | EISDIR => return error.IsDir, |
| 534 | 485 | ENOBUFS => return error.SystemResources, |
| 535 | 486 | ENOMEM => return error.SystemResources, |
| 487 | ENXIO => return error.Unseekable, | |
| 488 | ESPIPE => return error.Unseekable, | |
| 489 | EOVERFLOW => return error.Unseekable, | |
| 536 | 490 | else => |err| return unexpectedErrno(err), |
| 537 | 491 | } |
| 538 | 492 | } |
| ... | ... | @@ -553,10 +507,28 @@ pub const WriteError = error{ |
| 553 | 507 | WouldBlock, |
| 554 | 508 | } || UnexpectedError; |
| 555 | 509 | |
| 556 | /// Write to a file descriptor. Keeps trying if it gets interrupted. | |
| 557 | /// If the application has a global event loop enabled, EAGAIN is handled | |
| 558 | /// via the event loop. Otherwise EAGAIN results in error.WouldBlock. | |
| 559 | pub fn write(fd: fd_t, bytes: []const u8) WriteError!void { | |
| 510 | /// Write to a file descriptor. | |
| 511 | /// Retries when interrupted by a signal. | |
| 512 | /// Returns the number of bytes written. If nonzero bytes were supplied, this will be nonzero. | |
| 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)`. | |
| 531 | pub fn write(fd: fd_t, bytes: []const u8) WriteError!usize { | |
| 560 | 532 | if (builtin.os.tag == .windows) { |
| 561 | 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 | 540 | }}; |
| 569 | 541 | var nwritten: usize = undefined; |
| 570 | 542 | switch (wasi.fd_write(fd, &ciovs, ciovs.len, &nwritten)) { |
| 571 | 0 => return, | |
| 543 | 0 => return nwritten, | |
| 572 | 544 | else => |err| return unexpectedErrno(err), |
| 573 | 545 | } |
| 574 | 546 | } |
| 575 | 547 | |
| 576 | // Linux can return EINVAL when write amount is > 0x7ffff000 | |
| 577 | // See https://github.com/ziglang/zig/pull/743#issuecomment-363165856 | |
| 578 | // TODO audit this. Shawn Landden says that this is not actually true. | |
| 579 | // if this logic should stay, move it to std.os.linux | |
| 580 | const max_bytes_len = 0x7ffff000; | |
| 548 | const max_count = switch (std.Target.current.os.tag) { | |
| 549 | .linux => 0x7ffff000, | |
| 550 | else => math.maxInt(isize), | |
| 551 | }; | |
| 552 | const adjusted_len = math.min(max_count, bytes.len); | |
| 581 | 553 | |
| 582 | var index: usize = 0; | |
| 583 | while (index < bytes.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); | |
| 554 | while (true) { | |
| 555 | const rc = system.write(fd, bytes.ptr, adjusted_len); | |
| 586 | 556 | switch (errno(rc)) { |
| 587 | 0 => { | |
| 588 | index += @intCast(usize, rc); | |
| 589 | continue; | |
| 590 | }, | |
| 557 | 0 => return @intCast(usize, rc), | |
| 591 | 558 | EINTR => continue, |
| 592 | 559 | EINVAL => unreachable, |
| 593 | 560 | EFAULT => unreachable, |
| ... | ... | @@ -611,14 +578,36 @@ pub fn write(fd: fd_t, bytes: []const u8) WriteError!void { |
| 611 | 578 | } |
| 612 | 579 | |
| 613 | 580 | /// Write multiple buffers to a file descriptor. |
| 614 | /// If the application has a global event loop enabled, EAGAIN is handled | |
| 615 | /// via the event loop. Otherwise EAGAIN results in error.WouldBlock. | |
| 616 | pub fn writev(fd: fd_t, iov: []const iovec_const) WriteError!void { | |
| 581 | /// Retries when interrupted by a signal. | |
| 582 | /// Returns the number of bytes written. If nonzero bytes were supplied, this will be nonzero. | |
| 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. | |
| 598 | pub 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 | 607 | while (true) { |
| 618 | // TODO handle the case when iov_len is too large and get rid of this @intCast | |
| 619 | const rc = system.writev(fd, iov.ptr, @intCast(u32, iov.len)); | |
| 608 | const rc = system.writev(fd, iov.ptr, iov_count); | |
| 620 | 609 | switch (errno(rc)) { |
| 621 | 0 => return, | |
| 610 | 0 => return @intCast(usize, rc), | |
| 622 | 611 | EINTR => continue, |
| 623 | 612 | EINVAL => unreachable, |
| 624 | 613 | EFAULT => unreachable, |
| ... | ... | @@ -641,23 +630,45 @@ pub fn writev(fd: fd_t, iov: []const iovec_const) WriteError!void { |
| 641 | 630 | } |
| 642 | 631 | } |
| 643 | 632 | |
| 633 | pub const PWriteError = WriteError || error{Unseekable}; | |
| 634 | ||
| 644 | 635 | /// Write to a file descriptor, with a position offset. |
| 645 | /// | |
| 646 | 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 | 647 | /// For POSIX systems, if the application has a global event loop enabled, EAGAIN is handled |
| 649 | 648 | /// via the event loop. Otherwise EAGAIN results in `error.WouldBlock`. |
| 650 | 649 | /// On Windows, if the application has a global event loop enabled, I/O Completion Ports are |
| 651 | 650 | /// used to perform the I/O. `error.WouldBlock` is not possible on Windows. |
| 652 | pub 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)`. | |
| 656 | pub fn pwrite(fd: fd_t, bytes: []const u8, offset: u64) PWriteError!usize { | |
| 653 | 657 | if (std.Target.current.os.tag == .windows) { |
| 654 | 658 | return windows.WriteFile(fd, bytes, offset); |
| 655 | 659 | } |
| 656 | 660 | |
| 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 | 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 | 670 | switch (errno(rc)) { |
| 660 | 0 => return, | |
| 671 | 0 => return @intCast(usize, rc), | |
| 661 | 672 | EINTR => continue, |
| 662 | 673 | EINVAL => unreachable, |
| 663 | 674 | EFAULT => unreachable, |
| ... | ... | @@ -675,84 +686,54 @@ pub fn pwrite(fd: fd_t, bytes: []const u8, offset: u64) WriteError!void { |
| 675 | 686 | ENOSPC => return error.NoSpaceLeft, |
| 676 | 687 | EPERM => return error.AccessDenied, |
| 677 | 688 | EPIPE => return error.BrokenPipe, |
| 689 | ENXIO => return error.Unseekable, | |
| 690 | ESPIPE => return error.Unseekable, | |
| 691 | EOVERFLOW => return error.Unseekable, | |
| 678 | 692 | else => |err| return unexpectedErrno(err), |
| 679 | 693 | } |
| 680 | 694 | } |
| 681 | 695 | } |
| 682 | 696 | |
| 683 | 697 | /// Write multiple buffers to a file descriptor, with a position offset. |
| 684 | /// | |
| 685 | 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 | 709 | /// If the application has a global event loop enabled, EAGAIN is handled |
| 688 | 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 | 714 | /// * Darwin |
| 692 | 715 | /// * Windows |
| 693 | /// On these systems, the write races with concurrent writes to the same file descriptor, and | |
| 694 | /// the file can be in a partially written state when an error occurs. | |
| 695 | pub fn pwritev(fd: fd_t, iov: []const iovec_const, offset: u64) WriteError!void { | |
| 696 | if (comptime std.Target.current.isDarwin()) { | |
| 697 | // Darwin does not have pwritev but it does have pwrite. | |
| 698 | var off: usize = 0; | |
| 699 | var iov_i: usize = 0; | |
| 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 | } | |
| 716 | /// | |
| 717 | /// If `iov.len` is larger than will fit in a `u31`, a partial write will occur. | |
| 718 | pub fn pwritev(fd: fd_t, iov: []const iovec_const, offset: u64) PWriteError!usize { | |
| 719 | const have_pwrite_but_not_pwritev = switch (std.Target.current.os.tag) { | |
| 720 | .windows, .macosx, .ios, .watchos, .tvos => true, | |
| 721 | else => false, | |
| 722 | }; | |
| 741 | 723 | |
| 742 | if (std.Target.current.os.tag == .windows) { | |
| 743 | var off = offset; | |
| 744 | for (iov) |item| { | |
| 745 | try pwrite(fd, item.iov_base[0..item.iov_len], off); | |
| 746 | off += buf.len; | |
| 747 | } | |
| 748 | return; | |
| 724 | if (have_pwrite_but_not_pwritev) { | |
| 725 | // We could loop here; but proper usage of `pwritev` must handle partial writes anyway. | |
| 726 | // So we simply write the first vector only. | |
| 727 | if (iov.len == 0) return @as(usize, 0); | |
| 728 | const first = iov[0]; | |
| 729 | return pwrite(fd, first.iov_base[0..first.iov_len], offset); | |
| 749 | 730 | } |
| 750 | 731 | |
| 732 | const iov_count = math.cast(u31, iov.len) catch math.maxInt(u31); | |
| 751 | 733 | while (true) { |
| 752 | // TODO handle the case when iov_len is too large and get rid of this @intCast | |
| 753 | const rc = system.pwritev(fd, iov.ptr, @intCast(u32, iov.len), offset); | |
| 734 | const rc = system.pwritev(fd, iov.ptr, iov_count, offset); | |
| 754 | 735 | switch (errno(rc)) { |
| 755 | 0 => return, | |
| 736 | 0 => return @intCast(usize, rc), | |
| 756 | 737 | EINTR => continue, |
| 757 | 738 | EINVAL => unreachable, |
| 758 | 739 | EFAULT => unreachable, |
| ... | ... | @@ -770,6 +751,9 @@ pub fn pwritev(fd: fd_t, iov: []const iovec_const, offset: u64) WriteError!void |
| 770 | 751 | ENOSPC => return error.NoSpaceLeft, |
| 771 | 752 | EPERM => return error.AccessDenied, |
| 772 | 753 | EPIPE => return error.BrokenPipe, |
| 754 | ENXIO => return error.Unseekable, | |
| 755 | ESPIPE => return error.Unseekable, | |
| 756 | EOVERFLOW => return error.Unseekable, | |
| 773 | 757 | else => |err| return unexpectedErrno(err), |
| 774 | 758 | } |
| 775 | 759 | } |
| ... | ... | @@ -3389,7 +3373,6 @@ pub const SendError = error{ |
| 3389 | 3373 | |
| 3390 | 3374 | /// The socket type requires that message be sent atomically, and the size of the message |
| 3391 | 3375 | /// to be sent made this impossible. The message is not transmitted. |
| 3392 | /// | |
| 3393 | 3376 | MessageTooBig, |
| 3394 | 3377 | |
| 3395 | 3378 | /// The output queue for a network interface was full. This generally indicates that the |
| ... | ... | @@ -3498,119 +3481,312 @@ pub fn send( |
| 3498 | 3481 | return sendto(sockfd, buf, flags, null, 0); |
| 3499 | 3482 | } |
| 3500 | 3483 | |
| 3501 | pub const SendFileError = error{ | |
| 3502 | /// There was an unspecified error while reading from infd. | |
| 3503 | InputOutput, | |
| 3504 | ||
| 3505 | /// There was insufficient resources for processing. | |
| 3506 | SystemResources, | |
| 3507 | ||
| 3508 | /// The value provided for count overflows the maximum size of either | |
| 3509 | /// infd or outfd. | |
| 3510 | Overflow, | |
| 3511 | ||
| 3512 | /// Offset was provided, but infd is not seekable. | |
| 3513 | Unseekable, | |
| 3484 | pub const SendFileError = PReadError || WriteError || SendError; | |
| 3514 | 3485 | |
| 3515 | /// The outfd is marked nonblocking and the requested operation would block, and | |
| 3516 | /// there is no global event loop configured. | |
| 3517 | WouldBlock, | |
| 3518 | } || WriteError || UnexpectedError; | |
| 3519 | ||
| 3520 | pub const sf_hdtr = struct { | |
| 3521 | headers: []iovec_const, | |
| 3522 | trailers: []iovec_const, | |
| 3523 | }; | |
| 3486 | fn 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 | } | |
| 3524 | 3493 | |
| 3525 | /// Transfer data between file descriptors. | |
| 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. | |
| 3526 | 3510 | /// |
| 3527 | /// The `sendfile` call copies `count` bytes from one file descriptor to another within the kernel. This can | |
| 3528 | /// be more performant than transferring data from the kernel to user space and back, such as with | |
| 3529 | /// `read` and `write` calls. | |
| 3511 | /// `flags` has different meanings per operating system; refer to the respective man pages. | |
| 3530 | 3512 | /// |
| 3531 | /// The `infd` should be a file descriptor opened for reading, and `outfd` should be a file descriptor | |
| 3532 | /// opened for writing. Copying will begin at `offset`, if not null, which will be updated to reflect | |
| 3533 | /// the number of bytes read. If `offset` is null, the copying will begin at the current seek position, | |
| 3534 | /// and the file position will be updated. | |
| 3535 | pub fn sendfile(infd: fd_t, outfd: fd_t, offset: u64, count: usize, optional_hdtr: ?*const sf_hdtr, flags: u32) SendFileError!usize { | |
| 3536 | // XXX: check if offset is > length of file, return 0 bytes written | |
| 3537 | // XXX: document systems where headers are sent atomically. | |
| 3538 | // XXX: compute new offset on EINTR/EAGAIN | |
| 3539 | var rc: usize = undefined; | |
| 3540 | var err: usize = undefined; | |
| 3541 | if (builtin.os == .linux) { | |
| 3542 | while (true) { | |
| 3543 | try lseek_SET(infd, offset); | |
| 3544 | ||
| 3545 | if (optional_hdtr) |hdtr| { | |
| 3546 | try writev(outfd, hdtr.headers); | |
| 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)`. | |
| 3526 | pub 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; | |
| 3547 | 3558 | } |
| 3548 | 3559 | |
| 3549 | rc = system.sendfile(outfd, infd, null, count); | |
| 3550 | err = errno(rc); | |
| 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 | } | |
| 3551 | 3612 | |
| 3552 | if (optional_hdtr) |hdtr| { | |
| 3553 | try writev(outfd, hdtr.trailers); | |
| 3613 | if (trailers.len != 0) { | |
| 3614 | total_written += try writev(out_fd, trailers); | |
| 3554 | 3615 | } |
| 3555 | 3616 | |
| 3556 | switch (err) { | |
| 3557 | 0 => return @intCast(usize, rc), | |
| 3558 | else => return unexpectedErrno(err), | |
| 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 | } | |
| 3559 | 3638 | |
| 3560 | EBADF => unreachable, | |
| 3561 | EINVAL => unreachable, | |
| 3562 | EFAULT => unreachable, | |
| 3563 | EAGAIN => if (std.event.Loop.instance) |loop| { | |
| 3564 | loop.waitUntilFdWritable(outfd); | |
| 3565 | continue; | |
| 3566 | } else { | |
| 3567 | return error.WouldBlock; | |
| 3568 | }, | |
| 3569 | EIO => return error.InputOutput, | |
| 3570 | ENOMEM => return error.SystemResources, | |
| 3571 | EOVERFLOW => return error.Overflow, | |
| 3572 | ESPIPE => return error.Unseekable, | |
| 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 | } | |
| 3573 | 3695 | } |
| 3574 | } | |
| 3575 | } else if (builtin.os == .freebsd) { | |
| 3576 | while (true) { | |
| 3577 | var rcount: u64 = 0; | |
| 3578 | var hdtr: std.c.sf_hdtr = undefined; | |
| 3579 | if (optional_hdtr) |h| { | |
| 3580 | hdtr = std.c.sf_hdtr{ | |
| 3581 | .headers = h.headers.ptr, | |
| 3582 | .hdr_cnt = @intCast(c_int, h.headers.len), | |
| 3583 | .trailers = h.trailers.ptr, | |
| 3584 | .trl_cnt = @intCast(c_int, h.trailers.len), | |
| 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, | |
| 3585 | 3713 | }; |
| 3714 | hdtr = &hdtr_data; | |
| 3586 | 3715 | } |
| 3587 | err = errno(system.sendfile(infd, outfd, offset, count, &hdtr, &rcount, @intCast(c_int, flags))); | |
| 3588 | switch (err) { | |
| 3589 | 0 => return @intCast(usize, rcount), | |
| 3590 | else => return unexpectedErrno(err), | |
| 3591 | 3716 | |
| 3592 | EBADF => unreachable, | |
| 3593 | EFAULT => unreachable, | |
| 3594 | EINVAL => unreachable, | |
| 3595 | ENOTCAPABLE => unreachable, | |
| 3596 | ENOTCONN => unreachable, | |
| 3597 | ENOTSOCK => unreachable, | |
| 3598 | EAGAIN => if (std.event.Loop.instance) |loop| { | |
| 3599 | loop.waitUntilFdWritable(outfd); | |
| 3600 | continue; | |
| 3601 | } else { | |
| 3602 | return error.WouldBlock; | |
| 3603 | }, | |
| 3604 | EBUSY => return error.DeviceBusy, | |
| 3605 | EINTR => continue, | |
| 3606 | EIO => return error.InputOutput, | |
| 3607 | ENOBUFS => return error.SystemResources, | |
| 3608 | EPIPE => return error.BrokenPipe, | |
| 3717 | const adjusted_count = math.min(count, max_count); | |
| 3718 | ||
| 3719 | while (true) { | |
| 3720 | var sbytes: off_t = adjusted_count; | |
| 3721 | const err = errno(system.sendfile(out_fd, in_fd, in_offset, &sbytes, hdtr, flags)); | |
| 3722 | const amt = @bitCast(usize, sbytes); | |
| 3723 | switch (err) { | |
| 3724 | 0 => return amt, | |
| 3725 | ||
| 3726 | EBADF => unreachable, // Always a race condition. | |
| 3727 | EFAULT => unreachable, // Segmentation fault. | |
| 3728 | EINVAL => unreachable, | |
| 3729 | ENOTCONN => unreachable, // `out_fd` is an unconnected socket. | |
| 3730 | ||
| 3731 | ENOTSUP, ENOTSOCK, ENOSYS => break :sf, | |
| 3732 | ||
| 3733 | EINTR => if (amt != 0) return amt else continue, | |
| 3734 | ||
| 3735 | EAGAIN => if (amt != 0) { | |
| 3736 | return amt; | |
| 3737 | } else if (std.event.Loop.instance) |loop| { | |
| 3738 | loop.waitUntilFdWritable(out_fd); | |
| 3739 | continue; | |
| 3740 | } else { | |
| 3741 | return error.WouldBlock; | |
| 3742 | }, | |
| 3743 | ||
| 3744 | EIO => return error.InputOutput, | |
| 3745 | EPIPE => return error.BrokenPipe, | |
| 3746 | ||
| 3747 | else => { | |
| 3748 | _ = unexpectedErrno(err); | |
| 3749 | if (amt != 0) { | |
| 3750 | return amt; | |
| 3751 | } else { | |
| 3752 | break :sf; | |
| 3753 | } | |
| 3754 | }, | |
| 3755 | } | |
| 3756 | } | |
| 3757 | }, | |
| 3758 | else => {}, // fall back to read/write | |
| 3759 | } | |
| 3760 | ||
| 3761 | if (headers.len != 0 and !header_done) { | |
| 3762 | const amt = try writev(out_fd, headers); | |
| 3763 | total_written += amt; | |
| 3764 | if (amt < count_iovec_bytes(headers)) return total_written; | |
| 3765 | } | |
| 3766 | ||
| 3767 | rw: { | |
| 3768 | var buf: [8 * 4096]u8 = undefined; | |
| 3769 | // Here we match BSD behavior, making a zero count value send as many bytes as possible. | |
| 3770 | const adjusted_count = if (count == 0) buf.len else math.min(buf.len, count); | |
| 3771 | const amt_read = try pread(in_fd, buf[0..adjusted_count], in_offset); | |
| 3772 | if (amt_read == 0) { | |
| 3773 | if (count == 0) { | |
| 3774 | // We have detected EOF from `in_fd`. | |
| 3775 | break :rw; | |
| 3776 | } else { | |
| 3777 | return total_written; | |
| 3609 | 3778 | } |
| 3610 | 3779 | } |
| 3611 | } else { | |
| 3612 | @compileError("sendfile unimplemented for this target"); | |
| 3780 | const amt_written = try write(out_fd, buf[0..amt_read]); | |
| 3781 | total_written += amt_written; | |
| 3782 | if (amt_written < count or count == 0) return total_written; | |
| 3783 | } | |
| 3784 | ||
| 3785 | if (trailers.len != 0) { | |
| 3786 | total_written += try writev(out_fd, trailers); | |
| 3613 | 3787 | } |
| 3788 | ||
| 3789 | return total_written; | |
| 3614 | 3790 | } |
| 3615 | 3791 | |
| 3616 | 3792 | pub const PollError = error{ |
lib/std/os/bits/linux/arm64.zig+1| ... | ... | @@ -82,6 +82,7 @@ pub const SYS_pread64 = 67; |
| 82 | 82 | pub const SYS_pwrite64 = 68; |
| 83 | 83 | pub const SYS_preadv = 69; |
| 84 | 84 | pub const SYS_pwritev = 70; |
| 85 | pub const SYS_sendfile = 71; | |
| 85 | 86 | pub const SYS_pselect6 = 72; |
| 86 | 87 | pub const SYS_ppoll = 73; |
| 87 | 88 | pub const SYS_signalfd4 = 74; |
lib/std/os/linux.zig+28-5| ... | ... | @@ -316,8 +316,19 @@ pub fn symlinkat(existing: [*:0]const u8, newfd: i32, newpath: [*:0]const u8) us |
| 316 | 316 | return syscall3(SYS_symlinkat, @ptrToInt(existing), @bitCast(usize, @as(isize, newfd)), @ptrToInt(newpath)); |
| 317 | 317 | } |
| 318 | 318 | |
| 319 | pub fn pread(fd: i32, buf: [*]u8, count: usize, offset: usize) usize { | |
| 320 | return syscall4(SYS_pread, @bitCast(usize, @as(isize, fd)), @ptrToInt(buf), count, offset); | |
| 319 | pub fn pread(fd: i32, buf: [*]u8, count: usize, offset: u64) usize { | |
| 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 | } |
| 322 | 333 | |
| 323 | 334 | pub fn access(path: [*:0]const u8, mode: u32) usize { |
| ... | ... | @@ -846,11 +857,23 @@ pub fn sendto(fd: i32, buf: [*]const u8, len: usize, flags: u32, addr: ?*const s |
| 846 | 857 | return syscall6(SYS_sendto, @bitCast(usize, @as(isize, fd)), @ptrToInt(buf), len, flags, @ptrToInt(addr), @intCast(usize, alen)); |
| 847 | 858 | } |
| 848 | 859 | |
| 849 | pub fn sendfile(outfd: i32, infd: i32, offset: ?*u64, count: usize) usize { | |
| 860 | pub fn sendfile(outfd: i32, infd: i32, offset: ?*i64, count: usize) usize { | |
| 850 | 861 | if (@hasDecl(@This(), "SYS_sendfile64")) { |
| 851 | return syscall4(SYS_sendfile64, @bitCast(usize, @as(isize, outfd)), @bitCast(usize, @as(isize, infd)), @ptrToInt(offset), count); | |
| 862 | return syscall4( | |
| 863 | SYS_sendfile64, | |
| 864 | @bitCast(usize, @as(isize, outfd)), | |
| 865 | @bitCast(usize, @as(isize, infd)), | |
| 866 | @ptrToInt(offset), | |
| 867 | count, | |
| 868 | ); | |
| 852 | 869 | } else { |
| 853 | return syscall4(SYS_sendfile, @bitCast(usize, @as(isize, outfd)), @bitCast(usize, @as(isize, infd)), @ptrToInt(offset), count); | |
| 870 | return syscall4( | |
| 871 | SYS_sendfile, | |
| 872 | @bitCast(usize, @as(isize, outfd)), | |
| 873 | @bitCast(usize, @as(isize, infd)), | |
| 874 | @ptrToInt(offset), | |
| 875 | count, | |
| 876 | ); | |
| 854 | 877 | } |
| 855 | 878 | } |
| 856 | 879 |
lib/std/os/test.zig+111-3| ... | ... | @@ -44,6 +44,114 @@ fn testThreadIdFn(thread_id: *Thread.Id) void { |
| 44 | 44 | thread_id.* = Thread.getCurrentId(); |
| 45 | 45 | } |
| 46 | 46 | |
| 47 | test "sendfile" { | |
| 48 | try fs.makePath(a, "os_test_tmp"); | |
| 49 | defer fs.deleteTree("os_test_tmp") catch {}; | |
| 50 | ||
| 51 | var dir = try fs.cwd().openDirList("os_test_tmp"); | |
| 52 | defer dir.close(); | |
| 53 | ||
| 54 | const line1 = "line1\n"; | |
| 55 | const line2 = "second line\n"; | |
| 56 | var vecs = [_]os.iovec_const{ | |
| 57 | .{ | |
| 58 | .iov_base = line1, | |
| 59 | .iov_len = line1.len, | |
| 60 | }, | |
| 61 | .{ | |
| 62 | .iov_base = line2, | |
| 63 | .iov_len = line2.len, | |
| 64 | }, | |
| 65 | }; | |
| 66 | ||
| 67 | var src_file = try dir.createFileC("sendfile1.txt", .{ .read = true }); | |
| 68 | defer src_file.close(); | |
| 69 | ||
| 70 | try src_file.writevAll(&vecs); | |
| 71 | ||
| 72 | var dest_file = try dir.createFileC("sendfile2.txt", .{ .read = true }); | |
| 73 | defer dest_file.close(); | |
| 74 | ||
| 75 | const header1 = "header1\n"; | |
| 76 | const header2 = "second header\n"; | |
| 77 | var headers = [_]os.iovec_const{ | |
| 78 | .{ | |
| 79 | .iov_base = header1, | |
| 80 | .iov_len = header1.len, | |
| 81 | }, | |
| 82 | .{ | |
| 83 | .iov_base = header2, | |
| 84 | .iov_len = header2.len, | |
| 85 | }, | |
| 86 | }; | |
| 87 | ||
| 88 | const trailer1 = "trailer1\n"; | |
| 89 | const trailer2 = "second trailer\n"; | |
| 90 | var trailers = [_]os.iovec_const{ | |
| 91 | .{ | |
| 92 | .iov_base = trailer1, | |
| 93 | .iov_len = trailer1.len, | |
| 94 | }, | |
| 95 | .{ | |
| 96 | .iov_base = trailer2, | |
| 97 | .iov_len = trailer2.len, | |
| 98 | }, | |
| 99 | }; | |
| 100 | ||
| 101 | var written_buf: [header1.len + header2.len + 10 + trailer1.len + trailer2.len]u8 = undefined; | |
| 102 | try sendfileAll(dest_file.handle, src_file.handle, 1, 10, &headers, &trailers, 0); | |
| 103 | ||
| 104 | try dest_file.preadAll(&written_buf, 0); | |
| 105 | expect(mem.eql(u8, &written_buf, "header1\nsecond header\nine1\nsecontrailer1\nsecond trailer\n")); | |
| 106 | } | |
| 107 | ||
| 108 | fn sendfileAll( | |
| 109 | out_fd: os.fd_t, | |
| 110 | in_fd: os.fd_t, | |
| 111 | offset: u64, | |
| 112 | count: usize, | |
| 113 | headers: []os.iovec_const, | |
| 114 | trailers: []os.iovec_const, | |
| 115 | flags: u32, | |
| 116 | ) os.SendFileError!void { | |
| 117 | var amt: usize = undefined; | |
| 118 | hdrs: { | |
| 119 | var i: usize = 0; | |
| 120 | while (i < headers.len) { | |
| 121 | amt = try os.sendfile(out_fd, in_fd, offset, count, headers[i..], trailers, flags); | |
| 122 | while (amt >= headers[i].iov_len) { | |
| 123 | amt -= headers[i].iov_len; | |
| 124 | i += 1; | |
| 125 | if (i >= headers.len) break :hdrs; | |
| 126 | } | |
| 127 | headers[i].iov_base += amt; | |
| 128 | headers[i].iov_len -= amt; | |
| 129 | } | |
| 130 | } | |
| 131 | var off = amt; | |
| 132 | while (off < count) { | |
| 133 | amt = try os.sendfile(out_fd, in_fd, offset + off, count - off, &[0]os.iovec_const{}, trailers, flags); | |
| 134 | off += amt; | |
| 135 | } | |
| 136 | amt = off - count; | |
| 137 | var i: usize = 0; | |
| 138 | while (i < trailers.len) { | |
| 139 | while (amt >= headers[i].iov_len) { | |
| 140 | amt -= trailers[i].iov_len; | |
| 141 | i += 1; | |
| 142 | if (i >= trailers.len) return; | |
| 143 | } | |
| 144 | trailers[i].iov_base += amt; | |
| 145 | trailers[i].iov_len -= amt; | |
| 146 | if (std.Target.current.os.tag == .windows) { | |
| 147 | amt = try os.writev(out_fd, trailers[i..]); | |
| 148 | } else { | |
| 149 | // Here we must use send because it's the only way to give the flags. | |
| 150 | amt = try os.send(out_fd, trailers[i].iov_base[0..trailers[i].iov_len], flags); | |
| 151 | } | |
| 152 | } | |
| 153 | } | |
| 154 | ||
| 47 | 155 | test "std.Thread.getCurrentId" { |
| 48 | 156 | if (builtin.single_threaded) return error.SkipZigTest; |
| 49 | 157 | |
| ... | ... | @@ -103,7 +211,7 @@ test "AtomicFile" { |
| 103 | 211 | { |
| 104 | 212 | var af = try fs.AtomicFile.init(test_out_file, File.default_mode); |
| 105 | 213 | defer af.deinit(); |
| 106 | try af.file.write(test_content); | |
| 214 | try af.file.writeAll(test_content); | |
| 107 | 215 | try af.finish(); |
| 108 | 216 | } |
| 109 | 217 | const content = try io.readFileAlloc(testing.allocator, test_out_file); |
| ... | ... | @@ -226,7 +334,7 @@ test "pipe" { |
| 226 | 334 | return error.SkipZigTest; |
| 227 | 335 | |
| 228 | 336 | var fds = try os.pipe(); |
| 229 | try os.write(fds[1], "hello"); | |
| 337 | expect((try os.write(fds[1], "hello")) == 5); | |
| 230 | 338 | var buf: [16]u8 = undefined; |
| 231 | 339 | expect((try os.read(fds[0], buf[0..])) == 5); |
| 232 | 340 | testing.expectEqualSlices(u8, buf[0..5], "hello"); |
| ... | ... | @@ -248,7 +356,7 @@ test "memfd_create" { |
| 248 | 356 | else => |e| return e, |
| 249 | 357 | }; |
| 250 | 358 | defer std.os.close(fd); |
| 251 | try std.os.write(fd, "test"); | |
| 359 | expect((try std.os.write(fd, "test")) == 4); | |
| 252 | 360 | try std.os.lseek_SET(fd, 0); |
| 253 | 361 | |
| 254 | 362 | var buf: [10]u8 = undefined; |
lib/std/os/windows.zig+8-6| ... | ... | @@ -424,7 +424,7 @@ pub const WriteFileError = error{ |
| 424 | 424 | Unexpected, |
| 425 | 425 | }; |
| 426 | 426 | |
| 427 | pub fn WriteFile(handle: HANDLE, bytes: []const u8, offset: ?u64) WriteFileError!void { | |
| 427 | pub fn WriteFile(handle: HANDLE, bytes: []const u8, offset: ?u64) WriteFileError!usize { | |
| 428 | 428 | if (std.event.Loop.instance) |loop| { |
| 429 | 429 | // TODO support async WriteFile with no offset |
| 430 | 430 | const off = offset.?; |
| ... | ... | @@ -445,8 +445,8 @@ pub fn WriteFile(handle: HANDLE, bytes: []const u8, offset: ?u64) WriteFileError |
| 445 | 445 | _ = CreateIoCompletionPort(fd, loop.os_data.io_port, undefined, undefined); |
| 446 | 446 | loop.beginOneEvent(); |
| 447 | 447 | suspend { |
| 448 | // TODO replace this @intCast with a loop that writes all the bytes | |
| 449 | _ = kernel32.WriteFile(fd, bytes.ptr, @intCast(windows.DWORD, bytes.len), null, &resume_node.base.overlapped); | |
| 448 | const adjusted_len = math.cast(windows.DWORD, bytes.len) catch maxInt(windows.DWORD); | |
| 449 | _ = kernel32.WriteFile(fd, bytes.ptr, adjusted_len, null, &resume_node.base.overlapped); | |
| 450 | 450 | } |
| 451 | 451 | var bytes_transferred: windows.DWORD = undefined; |
| 452 | 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 | 460 | else => |err| return windows.unexpectedError(err), |
| 461 | 461 | } |
| 462 | 462 | } |
| 463 | return bytes_transferred; | |
| 463 | 464 | } else { |
| 464 | 465 | var bytes_written: DWORD = undefined; |
| 465 | 466 | var overlapped_data: OVERLAPPED = undefined; |
| ... | ... | @@ -473,18 +474,19 @@ pub fn WriteFile(handle: HANDLE, bytes: []const u8, offset: ?u64) WriteFileError |
| 473 | 474 | }; |
| 474 | 475 | break :blk &overlapped_data; |
| 475 | 476 | } else null; |
| 476 | // TODO replace this @intCast with a loop that writes all the bytes | |
| 477 | if (kernel32.WriteFile(handle, bytes.ptr, @intCast(u32, bytes.len), &bytes_written, overlapped) == 0) { | |
| 477 | const adjusted_len = math.cast(u32, bytes.len) catch maxInt(u32); | |
| 478 | if (kernel32.WriteFile(handle, bytes.ptr, adjusted_len, &bytes_written, overlapped) == 0) { | |
| 478 | 479 | switch (kernel32.GetLastError()) { |
| 479 | 480 | .INVALID_USER_BUFFER => return error.SystemResources, |
| 480 | 481 | .NOT_ENOUGH_MEMORY => return error.SystemResources, |
| 481 | 482 | .OPERATION_ABORTED => return error.OperationAborted, |
| 482 | 483 | .NOT_ENOUGH_QUOTA => return error.SystemResources, |
| 483 | .IO_PENDING => unreachable, // this function is for blocking files only | |
| 484 | .IO_PENDING => unreachable, | |
| 484 | 485 | .BROKEN_PIPE => return error.BrokenPipe, |
| 485 | 486 | else => |err| return unexpectedError(err), |
| 486 | 487 | } |
| 487 | 488 | } |
| 489 | return bytes_written; | |
| 488 | 490 | } |
| 489 | 491 | } |
| 490 | 492 |
lib/std/zig/render.zig+5-4| ... | ... | @@ -29,7 +29,7 @@ pub fn render(allocator: *mem.Allocator, stream: var, tree: *ast.Tree) (@TypeOf( |
| 29 | 29 | source_index: usize, |
| 30 | 30 | source: []const u8, |
| 31 | 31 | |
| 32 | fn write(iface_stream: *Stream, bytes: []const u8) StreamError!void { | |
| 32 | fn write(iface_stream: *Stream, bytes: []const u8) StreamError!usize { | |
| 33 | 33 | const self = @fieldParentPtr(MyStream, "stream", iface_stream); |
| 34 | 34 | |
| 35 | 35 | if (!self.anything_changed_ptr.*) { |
| ... | ... | @@ -45,7 +45,7 @@ pub fn render(allocator: *mem.Allocator, stream: var, tree: *ast.Tree) (@TypeOf( |
| 45 | 45 | } |
| 46 | 46 | } |
| 47 | 47 | |
| 48 | try self.child_stream.write(bytes); | |
| 48 | return self.child_stream.writeOnce(bytes); | |
| 49 | 49 | } |
| 50 | 50 | }; |
| 51 | 51 | var my_stream = MyStream{ |
| ... | ... | @@ -2443,14 +2443,15 @@ const FindByteOutStream = struct { |
| 2443 | 2443 | }; |
| 2444 | 2444 | } |
| 2445 | 2445 | |
| 2446 | fn writeFn(out_stream: *Stream, bytes: []const u8) Error!void { | |
| 2446 | fn writeFn(out_stream: *Stream, bytes: []const u8) Error!usize { | |
| 2447 | 2447 | const self = @fieldParentPtr(Self, "stream", out_stream); |
| 2448 | if (self.byte_found) return; | |
| 2448 | if (self.byte_found) return bytes.len; | |
| 2449 | 2449 | self.byte_found = blk: { |
| 2450 | 2450 | for (bytes) |b| |
| 2451 | 2451 | if (b == self.byte) break :blk true; |
| 2452 | 2452 | break :blk false; |
| 2453 | 2453 | }; |
| 2454 | return bytes.len; | |
| 2454 | 2455 | } |
| 2455 | 2456 | }; |
| 2456 | 2457 |
lib/std/zig/system.zig+1| ... | ... | @@ -796,6 +796,7 @@ pub const NativeTargetInfo = struct { |
| 796 | 796 | error.SystemResources => return error.SystemResources, |
| 797 | 797 | error.IsDir => return error.UnableToReadElfFile, |
| 798 | 798 | error.BrokenPipe => return error.UnableToReadElfFile, |
| 799 | error.Unseekable => return error.UnableToReadElfFile, | |
| 799 | 800 | error.ConnectionResetByPeer => return error.UnableToReadElfFile, |
| 800 | 801 | error.Unexpected => return error.Unexpected, |
| 801 | 802 | error.InputOutput => return error.FileSystem, |
test/standalone/cat/main.zig+2-1| ... | ... | @@ -42,6 +42,7 @@ fn usage(exe: []const u8) !void { |
| 42 | 42 | return error.Invalid; |
| 43 | 43 | } |
| 44 | 44 | |
| 45 | // TODO use copy_file_range | |
| 45 | 46 | fn cat_file(stdout: fs.File, file: fs.File) !void { |
| 46 | 47 | var buf: [1024 * 4]u8 = undefined; |
| 47 | 48 | |
| ... | ... | @@ -55,7 +56,7 @@ fn cat_file(stdout: fs.File, file: fs.File) !void { |
| 55 | 56 | break; |
| 56 | 57 | } |
| 57 | 58 | |
| 58 | stdout.write(buf[0..bytes_read]) catch |err| { | |
| 59 | stdout.writeAll(buf[0..bytes_read]) catch |err| { | |
| 59 | 60 | warn("Unable to write to stdout: {}\n", .{@errorName(err)}); |
| 60 | 61 | return err; |
| 61 | 62 | }; |
test/standalone/hello_world/hello.zig+1-4| ... | ... | @@ -1,8 +1,5 @@ |
| 1 | 1 | const std = @import("std"); |
| 2 | 2 | |
| 3 | 3 | pub fn main() !void { |
| 4 | const stdout_file = std.io.getStdOut(); | |
| 5 | // If this program encounters pipe failure when printing to stdout, exit | |
| 6 | // with an error. | |
| 7 | try stdout_file.write("Hello, world!\n"); | |
| 4 | try std.io.getStdOut().writeAll("Hello, World!\n"); | |
| 8 | 5 | } |