| author | |
| committer | |
| log | 226b801830857a9075fdd8180739105bb73eed0e |
| tree | 48c684f434c6a44d2e0684adc69b31862dcb90ab |
| parent | 387418277a4964714ddaec3336a602ec87dde0f9 |
| parent | 9d6cc75ce3be9ab291614fc0d4361877e9200126 |
| signature | Signed by PGP key 4AEE18F83AFDEB23 |
std.os read/write functions + sendfile21 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; |
| 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,6 +55,22 @@ pub extern "c" fn clock_get_time(clock_serv: clock_serv_t, cur_time: *mach_times |
| 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; | 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 | pub extern "c" fn mach_port_deallocate(task: ipc_space_t, name: mach_port_name_t) kern_return_t; | 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: [*]const iovec_const, | ||
| 60 | hdr_cnt: c_int, | ||
| 61 | trailers: [*]const 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 | pub fn sigaddset(set: *sigset_t, signo: u5) void { | 74 | pub 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; |
| 8 | pub extern "c" fn sigaltstack(ss: ?*stack_t, old_ss: ?*stack_t) c_int; | 8 | pub extern "c" fn sigaltstack(ss: ?*stack_t, old_ss: ?*stack_t) c_int; |
| 9 | pub extern "c" fn getrandom(buf_ptr: [*]u8, buf_len: usize, flags: c_uint) isize; | 9 | pub extern "c" fn getrandom(buf_ptr: [*]u8, buf_len: usize, flags: c_uint) isize; |
| 10 | 10 | ||
| 11 | pub 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 | }; | ||
| 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; | ||
| 26 | |||
| 11 | pub const dl_iterate_phdr_callback = extern fn (info: *dl_phdr_info, size: usize, data: ?*c_void) c_int; | 27 | pub const dl_iterate_phdr_callback = extern fn (info: *dl_phdr_info, size: usize, data: ?*c_void) c_int; |
| 12 | pub extern "c" fn dl_iterate_phdr(callback: dl_iterate_phdr_callback, data: ?*c_void) c_int; | 28 | pub extern "c" fn dl_iterate_phdr(callback: dl_iterate_phdr_callback, data: ?*c_void) c_int; |
| 13 | 29 |
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; |
| 82 | 82 | ||
| 83 | pub extern "c" fn memfd_create(name: [*:0]const u8, flags: c_uint) c_int; | 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 | pub const pthread_attr_t = extern struct { | 92 | pub 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 fail | 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 | 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 fail | 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 | 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 { |
| 831 | 833 | ||
| 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 { |
| 852 | 854 | ||
| 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 { |
| 873 | 875 | ||
| 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, |
| 1141 | 1143 | ||
| 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, |
| 1149 | 1151 | ||
| 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, |
| 1158 | 1160 | ||
| 1159 | pub const Error = os.WriteError; | 1161 | pub const Error = os.PWriteError; |
| 1160 | }; | 1162 | }; |
| 1161 | 1163 | ||
| 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, |
| 1167 | 1169 | ||
| 1168 | pub const Error = os.ReadError; | 1170 | pub const Error = os.PReadError; |
| 1169 | }; | 1171 | }; |
| 1170 | 1172 | ||
| 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 | } |
| 1334 | 1334 | ||
| 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 | } |
| 229 | 229 | ||
| 230 | pub const ReadError = os.ReadError; | 230 | pub const ReadError = os.ReadError; |
| 231 | pub const PReadError = os.PReadError; | ||
| 231 | 232 | ||
| 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 | } |
| 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 | 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 | } |
| 245 | 262 | ||
| 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 | } |
| 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 | 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 | } |
| 259 | 312 | ||
| 260 | pub const WriteError = os.WriteError; | 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 | 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 | } |
| 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 | 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 | } |
| 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 | 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 | } |
| 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 | 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 | } |
| 289 | 395 | ||
| 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); |
| 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 | 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 | } |
| 439 | 439 | ||
| 440 | /// This is a simple OutStream that writes to a fixed buffer, and returns an error | 440 | /// 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. | ||
| 442 | pub const SliceOutStream = struct { | 443 | pub 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); |
| 445 | 446 | ||
| 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 | } |
| 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 | const self = @fieldParentPtr(SliceOutStream, "stream", out_stream); | 469 | const self = @fieldParentPtr(SliceOutStream, "stream", out_stream); |
| 469 | 470 | ||
| 471 | if (bytes.len == 0) return 0; | ||
| 472 | |||
| 470 | assert(self.pos <= self.slice.len); | 473 | assert(self.pos <= self.slice.len); |
| 471 | 474 | ||
| 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; |
| 479 | 482 | ||
| 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 | }; |
| 485 | 488 | ||
| ... | @@ -508,7 +511,9 @@ pub const NullOutStream = struct { | ... | @@ -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 | test "io.NullOutStream" { | 519 | test "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 | } |
| 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 | 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 | } |
| 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 | 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 | } |
| 616 | 623 | ||
| 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 | }; |
| 622 | 630 | ||
| ... | @@ -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 | } |
| 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 | var self = @fieldParentPtr(Self, "stream", self_stream); | 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 | 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 | } |
| 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,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 | 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!void | 17 | async fn (self: *Self, bytes: []const u8) Error!usize |
| 18 | else | 18 | else |
| 19 | fn (self: *Self, bytes: []const u8) Error!void; | 19 | fn (self: *Self, bytes: []const u8) Error!usize; |
| 20 | 20 | ||
| 21 | writeFn: WriteFn, | 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 | 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 | } |
| 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 | 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")); |
| 136 | 136 | ||
| 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")); |
| 139 | 139 | ||
| 140 | ss.reset(); | 140 | ss.reset(); |
| 141 | expect(ss.getWritten().len == 0); | 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 | expect(mem.eql(u8, ss.getWritten(), "Hello worl")); | 144 | expect(mem.eql(u8, ss.getWritten(), "Hello worl")); |
| 145 | } | 145 | } |
| 146 | 146 | ||
| ... | @@ -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 | } |
| 619 | 619 | ||
| 620 | try file.write(&([_]u8{0x55} ** 8192)); | 620 | try file.writeAll(&([_]u8{0x55} ** 8192)); |
| 621 | 621 | ||
| 622 | // Seek to the end | 622 | // 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 handled | 299 | /// 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)`. | ||
| 301 | pub fn read(fd: fd_t, buf: []u8) ReadError!usize { | 306 | pub 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 | } |
| 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 | 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 | /// * Windows | 364 | /// * 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. |
| 354 | pub fn readv(fd: fd_t, iov: []const iovec) ReadError!usize { | 366 | pub 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 requests | 368 | // 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 | } |
| 375 | 373 | ||
| 376 | while (true) { | 374 | while (true) { |
| 377 | // TODO handle the case when iov_len is too large and get rid of this @intCast | 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 | 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 | } |
| 399 | 397 | ||
| 398 | pub 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 are | 406 | /// 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. |
| 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 | 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 | /// * Darwin | 451 | /// * Darwin |
| 449 | /// * Windows | 452 | /// * 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. |
| 451 | pub fn preadv(fd: fd_t, iov: []const iovec, offset: u64) ReadError!usize { | 454 | pub 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 | } |
| 495 | 466 | ||
| 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 | } | ||
| 516 | 468 | ||
| 517 | while (true) { | 469 | while (true) { |
| 518 | // TODO handle the case when iov_len is too large and get rid of this @intCast | 470 | 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; |
| 555 | 509 | ||
| 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 handled | 511 | /// 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. |
| 559 | pub 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)`. | ||
| 531 | pub 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 | } |
| 575 | 547 | ||
| 576 | // Linux can return EINVAL when write amount is > 0x7ffff000 | 548 | const max_count = switch (std.Target.current.os.tag) { |
| 577 | // See https://github.com/ziglang/zig/pull/743#issuecomment-363165856 | 549 | .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.linux | 551 | }; |
| 580 | const max_bytes_len = 0x7ffff000; | 552 | const adjusted_len = math.min(max_count, bytes.len); |
| 581 | 553 | ||
| 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 | } |
| 612 | 579 | ||
| 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 handled | 581 | /// 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. |
| 616 | pub 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. | ||
| 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 | while (true) { | 607 | while (true) { |
| 618 | // TODO handle the case when iov_len is too large and get rid of this @intCast | 608 | 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 | } |
| 643 | 632 | ||
| 633 | pub 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 handled | 647 | /// 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 are | 649 | /// 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. |
| 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 | 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 | } |
| 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 | 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 | } |
| 682 | 696 | ||
| 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 handled | 709 | /// 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 | /// * Darwin | 714 | /// * Darwin |
| 692 | /// * Windows | 715 | /// * Windows |
| 693 | /// On these systems, the write races with concurrent writes to the same file descriptor, and | 716 | /// |
| 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. |
| 695 | pub fn pwritev(fd: fd_t, iov: []const iovec_const, offset: u64) WriteError!void { | 718 | pub 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 | } | ||
| 741 | 723 | ||
| 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 | } |
| 750 | 731 | ||
| 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 @intCast | 734 | 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{ |
| 3389 | 3373 | ||
| 3390 | /// The socket type requires that message be sent atomically, and the size of the message | 3374 | /// 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, |
| 3394 | 3377 | ||
| 3395 | /// The output queue for a network interface was full. This generally indicates that the | 3378 | /// 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 | } |
| 3500 | 3483 | ||
| 3484 | pub const SendFileError = PReadError || WriteError || SendError; | ||
| 3485 | |||
| 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 | } | ||
| 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)`. | ||
| 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; | ||
| 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 | |||
| 3501 | pub const PollError = error{ | 3793 | pub 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 supported | 926 | /// Operation not supported |
| 927 | pub const ENOTSUP = 45; | 927 | pub const ENOTSUP = 45; |
| 928 | 928 | ||
| 929 | /// Operation not supported. Alias of `ENOTSUP`. | ||
| 930 | pub const EOPNOTSUPP = ENOTSUP; | ||
| 931 | |||
| 929 | /// Protocol family not supported | 932 | /// Protocol family not supported |
| 930 | pub const EPFNOSUPPORT = 46; | 933 | pub const EPFNOSUPPORT = 46; |
| 931 | 934 |
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; |
| 82 | pub const SYS_pwrite64 = 68; | 82 | pub const SYS_pwrite64 = 68; |
| 83 | pub const SYS_preadv = 69; | 83 | pub const SYS_preadv = 69; |
| 84 | pub const SYS_pwritev = 70; | 84 | pub const SYS_pwritev = 70; |
| 85 | pub const SYS_sendfile = 71; | ||
| 85 | pub const SYS_pselect6 = 72; | 86 | pub const SYS_pselect6 = 72; |
| 86 | pub const SYS_ppoll = 73; | 87 | pub const SYS_ppoll = 73; |
| 87 | pub const SYS_signalfd4 = 74; | 88 | pub 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 | } |
| 318 | 318 | ||
| 319 | pub fn pread(fd: i32, buf: [*]u8, count: usize, offset: usize) usize { | 319 | pub 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 | } |
| 322 | 333 | ||
| 323 | pub fn access(path: [*:0]const u8, mode: u32) usize { | 334 | pub 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 | } |
| 848 | 859 | ||
| 860 | pub 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 | |||
| 849 | pub fn socketpair(domain: i32, socket_type: i32, protocol: i32, fd: [2]i32) usize { | 880 | pub 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 | } |
| 46 | 46 | ||
| 47 | test "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 | |||
| 112 | fn 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 | |||
| 47 | test "std.Thread.getCurrentId" { | 159 | test "std.Thread.getCurrentId" { |
| 48 | if (builtin.single_threaded) return error.SkipZigTest; | 160 | if (builtin.single_threaded) return error.SkipZigTest; |
| 49 | 161 | ||
| ... | @@ -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; |
| 227 | 339 | ||
| 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); |
| 253 | 365 | ||
| 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 | }; |
| 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 | if (std.event.Loop.instance) |loop| { | 428 | if (std.event.Loop.instance) |loop| { |
| 429 | // TODO support async WriteFile with no offset | 429 | // 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 bytes | 448 | 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 bytes | 477 | 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 only | 484 | .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 | } |
| 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,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, |
| 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 | const self = @fieldParentPtr(MyStream, "stream", iface_stream); | 33 | const self = @fieldParentPtr(MyStream, "stream", iface_stream); |
| 34 | 34 | ||
| 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 | } |
| 47 | 47 | ||
| 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 | } |
| 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 | 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 | }; |
| 2456 | 2457 |
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 | } |
| 44 | 44 | ||
| 45 | // TODO use copy_file_range | ||
| 45 | fn cat_file(stdout: fs.File, file: fs.File) !void { | 46 | fn cat_file(stdout: fs.File, file: fs.File) !void { |
| 46 | var buf: [1024 * 4]u8 = undefined; | 47 | var buf: [1024 * 4]u8 = undefined; |
| 47 | 48 | ||
| ... | @@ -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 | } |
| 57 | 58 | ||
| 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 @@ |
| 1 | const std = @import("std"); | 1 | const std = @import("std"); |
| 2 | 2 | ||
| 3 | pub fn main() !void { | 3 | pub 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 | } |