| ... | ... | @@ -6326,295 +6326,6 @@ pub fn send( |
| 6326 | 6326 | }; |
| 6327 | 6327 | } |
| 6328 | 6328 | |
| 6329 | | pub const SendFileError = PReadError || WriteError || SendError; |
| 6330 | | |
| 6331 | | /// Transfer data between file descriptors, with optional headers and trailers. |
| 6332 | | /// |
| 6333 | | /// Returns the number of bytes written, which can be zero. |
| 6334 | | /// |
| 6335 | | /// The `sendfile` call copies `in_len` bytes from one file descriptor to another. When possible, |
| 6336 | | /// this is done within the operating system kernel, which can provide better performance |
| 6337 | | /// characteristics than transferring data from kernel to user space and back, such as with |
| 6338 | | /// `read` and `write` calls. When `in_len` is `0`, it means to copy until the end of the input file has been |
| 6339 | | /// reached. Note, however, that partial writes are still possible in this case. |
| 6340 | | /// |
| 6341 | | /// `in_fd` must be a file descriptor opened for reading, and `out_fd` must be a file descriptor |
| 6342 | | /// opened for writing. They may be any kind of file descriptor; however, if `in_fd` is not a regular |
| 6343 | | /// file system file, it may cause this function to fall back to calling `read` and `write`, in which case |
| 6344 | | /// atomicity guarantees no longer apply. |
| 6345 | | /// |
| 6346 | | /// Copying begins reading at `in_offset`. The input file descriptor seek position is ignored and not updated. |
| 6347 | | /// If the output file descriptor has a seek position, it is updated as bytes are written. When |
| 6348 | | /// `in_offset` is past the end of the input file, it successfully reads 0 bytes. |
| 6349 | | /// |
| 6350 | | /// `flags` has different meanings per operating system; refer to the respective man pages. |
| 6351 | | /// |
| 6352 | | /// These systems support atomically sending everything, including headers and trailers: |
| 6353 | | /// * macOS |
| 6354 | | /// * FreeBSD |
| 6355 | | /// |
| 6356 | | /// These systems support in-kernel data copying, but headers and trailers are not sent atomically: |
| 6357 | | /// * Linux |
| 6358 | | /// |
| 6359 | | /// Other systems fall back to calling `read` / `write`. |
| 6360 | | /// |
| 6361 | | /// Linux has a limit on how many bytes may be transferred in one `sendfile` call, which is `0x7ffff000` |
| 6362 | | /// on both 64-bit and 32-bit systems. This is due to using a signed C int as the return value, as |
| 6363 | | /// well as stuffing the errno codes into the last `4096` values. This is noted on the `sendfile` man page. |
| 6364 | | /// The limit on Darwin is `0x7fffffff`, trying to write more than that returns EINVAL. |
| 6365 | | /// The corresponding POSIX limit on this is `maxInt(isize)`. |
| 6366 | | pub fn sendfile( |
| 6367 | | out_fd: fd_t, |
| 6368 | | in_fd: fd_t, |
| 6369 | | in_offset: u64, |
| 6370 | | in_len: u64, |
| 6371 | | headers: []const iovec_const, |
| 6372 | | trailers: []const iovec_const, |
| 6373 | | flags: u32, |
| 6374 | | ) SendFileError!usize { |
| 6375 | | var header_done = false; |
| 6376 | | var total_written: usize = 0; |
| 6377 | | |
| 6378 | | // Prevents EOVERFLOW. |
| 6379 | | const size_t = std.meta.Int(.unsigned, @typeInfo(usize).int.bits - 1); |
| 6380 | | const max_count = switch (native_os) { |
| 6381 | | .linux => 0x7ffff000, |
| 6382 | | .macos, .ios, .watchos, .tvos, .visionos => maxInt(i32), |
| 6383 | | else => maxInt(size_t), |
| 6384 | | }; |
| 6385 | | |
| 6386 | | switch (native_os) { |
| 6387 | | .linux => sf: { |
| 6388 | | if (headers.len != 0) { |
| 6389 | | const amt = try writev(out_fd, headers); |
| 6390 | | total_written += amt; |
| 6391 | | if (amt < count_iovec_bytes(headers)) return total_written; |
| 6392 | | header_done = true; |
| 6393 | | } |
| 6394 | | |
| 6395 | | // Here we match BSD behavior, making a zero count value send as many bytes as possible. |
| 6396 | | const adjusted_count = if (in_len == 0) max_count else @min(in_len, max_count); |
| 6397 | | |
| 6398 | | const sendfile_sym = if (lfs64_abi) system.sendfile64 else system.sendfile; |
| 6399 | | while (true) { |
| 6400 | | var offset: off_t = @bitCast(in_offset); |
| 6401 | | const rc = sendfile_sym(out_fd, in_fd, &offset, adjusted_count); |
| 6402 | | switch (errno(rc)) { |
| 6403 | | .SUCCESS => { |
| 6404 | | const amt: usize = @bitCast(rc); |
| 6405 | | total_written += amt; |
| 6406 | | if (in_len == 0 and amt == 0) { |
| 6407 | | // We have detected EOF from `in_fd`. |
| 6408 | | break; |
| 6409 | | } else if (amt < in_len) { |
| 6410 | | return total_written; |
| 6411 | | } else { |
| 6412 | | break; |
| 6413 | | } |
| 6414 | | }, |
| 6415 | | |
| 6416 | | .BADF => unreachable, // Always a race condition. |
| 6417 | | .FAULT => unreachable, // Segmentation fault. |
| 6418 | | .OVERFLOW => unreachable, // We avoid passing too large of a `count`. |
| 6419 | | .NOTCONN => return error.BrokenPipe, // `out_fd` is an unconnected socket |
| 6420 | | |
| 6421 | | .INVAL => { |
| 6422 | | // EINVAL could be any of the following situations: |
| 6423 | | // * Descriptor is not valid or locked |
| 6424 | | // * an mmap(2)-like operation is not available for in_fd |
| 6425 | | // * count is negative |
| 6426 | | // * out_fd has the APPEND flag set |
| 6427 | | // Because of the "mmap(2)-like operation" possibility, we fall back to doing read/write |
| 6428 | | // manually. |
| 6429 | | break :sf; |
| 6430 | | }, |
| 6431 | | .AGAIN => return error.WouldBlock, |
| 6432 | | .IO => return error.InputOutput, |
| 6433 | | .PIPE => return error.BrokenPipe, |
| 6434 | | .NOMEM => return error.SystemResources, |
| 6435 | | .NXIO => return error.Unseekable, |
| 6436 | | .SPIPE => return error.Unseekable, |
| 6437 | | else => |err| { |
| 6438 | | unexpectedErrno(err) catch {}; |
| 6439 | | break :sf; |
| 6440 | | }, |
| 6441 | | } |
| 6442 | | } |
| 6443 | | |
| 6444 | | if (trailers.len != 0) { |
| 6445 | | total_written += try writev(out_fd, trailers); |
| 6446 | | } |
| 6447 | | |
| 6448 | | return total_written; |
| 6449 | | }, |
| 6450 | | .freebsd => sf: { |
| 6451 | | var hdtr_data: std.c.sf_hdtr = undefined; |
| 6452 | | var hdtr: ?*std.c.sf_hdtr = null; |
| 6453 | | if (headers.len != 0 or trailers.len != 0) { |
| 6454 | | // Here we carefully avoid `@intCast` by returning partial writes when |
| 6455 | | // too many io vectors are provided. |
| 6456 | | const hdr_cnt = cast(u31, headers.len) orelse maxInt(u31); |
| 6457 | | if (headers.len > hdr_cnt) return writev(out_fd, headers); |
| 6458 | | |
| 6459 | | const trl_cnt = cast(u31, trailers.len) orelse maxInt(u31); |
| 6460 | | |
| 6461 | | hdtr_data = std.c.sf_hdtr{ |
| 6462 | | .headers = headers.ptr, |
| 6463 | | .hdr_cnt = hdr_cnt, |
| 6464 | | .trailers = trailers.ptr, |
| 6465 | | .trl_cnt = trl_cnt, |
| 6466 | | }; |
| 6467 | | hdtr = &hdtr_data; |
| 6468 | | } |
| 6469 | | |
| 6470 | | while (true) { |
| 6471 | | var sbytes: off_t = undefined; |
| 6472 | | const err = errno(system.sendfile(in_fd, out_fd, @bitCast(in_offset), @min(in_len, max_count), hdtr, &sbytes, flags)); |
| 6473 | | const amt: usize = @bitCast(sbytes); |
| 6474 | | switch (err) { |
| 6475 | | .SUCCESS => return amt, |
| 6476 | | |
| 6477 | | .BADF => unreachable, // Always a race condition. |
| 6478 | | .FAULT => unreachable, // Segmentation fault. |
| 6479 | | .NOTCONN => return error.BrokenPipe, // `out_fd` is an unconnected socket |
| 6480 | | |
| 6481 | | .INVAL, .OPNOTSUPP, .NOTSOCK, .NOSYS => { |
| 6482 | | // EINVAL could be any of the following situations: |
| 6483 | | // * The fd argument is not a regular file. |
| 6484 | | // * The s argument is not a SOCK.STREAM type socket. |
| 6485 | | // * The offset argument is negative. |
| 6486 | | // Because of some of these possibilities, we fall back to doing read/write |
| 6487 | | // manually, the same as ENOSYS. |
| 6488 | | break :sf; |
| 6489 | | }, |
| 6490 | | |
| 6491 | | .INTR => if (amt != 0) return amt else continue, |
| 6492 | | |
| 6493 | | .AGAIN => if (amt != 0) { |
| 6494 | | return amt; |
| 6495 | | } else { |
| 6496 | | return error.WouldBlock; |
| 6497 | | }, |
| 6498 | | |
| 6499 | | .BUSY => if (amt != 0) { |
| 6500 | | return amt; |
| 6501 | | } else { |
| 6502 | | return error.WouldBlock; |
| 6503 | | }, |
| 6504 | | |
| 6505 | | .IO => return error.InputOutput, |
| 6506 | | .NOBUFS => return error.SystemResources, |
| 6507 | | .PIPE => return error.BrokenPipe, |
| 6508 | | |
| 6509 | | else => { |
| 6510 | | unexpectedErrno(err) catch {}; |
| 6511 | | if (amt != 0) { |
| 6512 | | return amt; |
| 6513 | | } else { |
| 6514 | | break :sf; |
| 6515 | | } |
| 6516 | | }, |
| 6517 | | } |
| 6518 | | } |
| 6519 | | }, |
| 6520 | | .macos, .ios, .tvos, .watchos, .visionos => sf: { |
| 6521 | | var hdtr_data: std.c.sf_hdtr = undefined; |
| 6522 | | var hdtr: ?*std.c.sf_hdtr = null; |
| 6523 | | if (headers.len != 0 or trailers.len != 0) { |
| 6524 | | // Here we carefully avoid `@intCast` by returning partial writes when |
| 6525 | | // too many io vectors are provided. |
| 6526 | | const hdr_cnt = cast(u31, headers.len) orelse maxInt(u31); |
| 6527 | | if (headers.len > hdr_cnt) return writev(out_fd, headers); |
| 6528 | | |
| 6529 | | const trl_cnt = cast(u31, trailers.len) orelse maxInt(u31); |
| 6530 | | |
| 6531 | | hdtr_data = std.c.sf_hdtr{ |
| 6532 | | .headers = headers.ptr, |
| 6533 | | .hdr_cnt = hdr_cnt, |
| 6534 | | .trailers = trailers.ptr, |
| 6535 | | .trl_cnt = trl_cnt, |
| 6536 | | }; |
| 6537 | | hdtr = &hdtr_data; |
| 6538 | | } |
| 6539 | | |
| 6540 | | while (true) { |
| 6541 | | var sbytes: off_t = @min(in_len, max_count); |
| 6542 | | const err = errno(system.sendfile(in_fd, out_fd, @bitCast(in_offset), &sbytes, hdtr, flags)); |
| 6543 | | const amt: usize = @bitCast(sbytes); |
| 6544 | | switch (err) { |
| 6545 | | .SUCCESS => return amt, |
| 6546 | | |
| 6547 | | .BADF => unreachable, // Always a race condition. |
| 6548 | | .FAULT => unreachable, // Segmentation fault. |
| 6549 | | .INVAL => unreachable, |
| 6550 | | .NOTCONN => return error.BrokenPipe, // `out_fd` is an unconnected socket |
| 6551 | | |
| 6552 | | .OPNOTSUPP, .NOTSOCK, .NOSYS => break :sf, |
| 6553 | | |
| 6554 | | .INTR => if (amt != 0) return amt else continue, |
| 6555 | | |
| 6556 | | .AGAIN => if (amt != 0) { |
| 6557 | | return amt; |
| 6558 | | } else { |
| 6559 | | return error.WouldBlock; |
| 6560 | | }, |
| 6561 | | |
| 6562 | | .IO => return error.InputOutput, |
| 6563 | | .PIPE => return error.BrokenPipe, |
| 6564 | | |
| 6565 | | else => { |
| 6566 | | unexpectedErrno(err) catch {}; |
| 6567 | | if (amt != 0) { |
| 6568 | | return amt; |
| 6569 | | } else { |
| 6570 | | break :sf; |
| 6571 | | } |
| 6572 | | }, |
| 6573 | | } |
| 6574 | | } |
| 6575 | | }, |
| 6576 | | else => {}, // fall back to read/write |
| 6577 | | } |
| 6578 | | |
| 6579 | | if (headers.len != 0 and !header_done) { |
| 6580 | | const amt = try writev(out_fd, headers); |
| 6581 | | total_written += amt; |
| 6582 | | if (amt < count_iovec_bytes(headers)) return total_written; |
| 6583 | | } |
| 6584 | | |
| 6585 | | rw: { |
| 6586 | | var buf: [8 * 4096]u8 = undefined; |
| 6587 | | // Here we match BSD behavior, making a zero count value send as many bytes as possible. |
| 6588 | | const adjusted_count = if (in_len == 0) buf.len else @min(buf.len, in_len); |
| 6589 | | const amt_read = try pread(in_fd, buf[0..adjusted_count], in_offset); |
| 6590 | | if (amt_read == 0) { |
| 6591 | | if (in_len == 0) { |
| 6592 | | // We have detected EOF from `in_fd`. |
| 6593 | | break :rw; |
| 6594 | | } else { |
| 6595 | | return total_written; |
| 6596 | | } |
| 6597 | | } |
| 6598 | | const amt_written = try write(out_fd, buf[0..amt_read]); |
| 6599 | | total_written += amt_written; |
| 6600 | | if (amt_written < in_len or in_len == 0) return total_written; |
| 6601 | | } |
| 6602 | | |
| 6603 | | if (trailers.len != 0) { |
| 6604 | | total_written += try writev(out_fd, trailers); |
| 6605 | | } |
| 6606 | | |
| 6607 | | return total_written; |
| 6608 | | } |
| 6609 | | |
| 6610 | | fn count_iovec_bytes(iovs: []const iovec_const) usize { |
| 6611 | | var count: usize = 0; |
| 6612 | | for (iovs) |iov| { |
| 6613 | | count += iov.len; |
| 6614 | | } |
| 6615 | | return count; |
| 6616 | | } |
| 6617 | | |
| 6618 | 6329 | pub const CopyFileRangeError = error{ |
| 6619 | 6330 | FileTooBig, |
| 6620 | 6331 | InputOutput, |