| ... | @@ -413,6 +413,27 @@ pub const IO_Uring = struct { | ... | @@ -413,6 +413,27 @@ pub const IO_Uring = struct { |
| 413 | return sqe; | 413 | return sqe; |
| 414 | } | 414 | } |
| 415 | | 415 | |
| | 416 | /// Queues (but does not submit) an SQE to perform a `splice(2)` |
| | 417 | /// Either `fd_in` or `fd_out` must be a pipe. |
| | 418 | /// If `fd_in` refers to a pipe, `off_in` is ignored and must be set to -1. |
| | 419 | /// If `fd_in` does not refer to a pipe and `off_in` is -1, then `len` are read |
| | 420 | /// from `fd_in` starting from the file offset, which is incremented by the number of bytes read. |
| | 421 | /// If `fd_in` does not refer to a pipe and `off_in` is not -1, then the starting offset of `fd_in` will be `off_in`. |
| | 422 | /// This splice operation can be used to implement sendfile by splicing to an intermediate pipe first, |
| | 423 | /// then splice to the final destination. In fact, the implementation of sendfile in kernel uses splice internally. |
| | 424 | /// |
| | 425 | /// NOTE that even if fd_in or fd_out refers to a pipe, the splice operation can still fail with EINVAL if one of the |
| | 426 | /// fd doesn't explicitly support splice peration, e.g. reading from terminal is unsupported from kernel 5.7 to 5.11. |
| | 427 | /// See https://github.com/axboe/liburing/issues/291 |
| | 428 | /// |
| | 429 | /// Returns a pointer to the SQE so that you can further modify the SQE for advanced use cases. |
| | 430 | pub fn splice(self: *IO_Uring, user_data: u64, fd_in: os.fd_t, off_in: i64, fd_out: os.fd_t, off_out: i64, len: usize) !*linux.io_uring_sqe { |
| | 431 | const sqe = try self.get_sqe(); |
| | 432 | io_uring_prep_splice(sqe, fd_in, off_in, fd_out, off_out, len); |
| | 433 | sqe.user_data = user_data; |
| | 434 | return sqe; |
| | 435 | } |
| | 436 | |
| 416 | /// Queues (but does not submit) an SQE to perform a IORING_OP_READ_FIXED. | 437 | /// Queues (but does not submit) an SQE to perform a IORING_OP_READ_FIXED. |
| 417 | /// The `buffer` provided must be registered with the kernel by calling `register_buffers` first. | 438 | /// The `buffer` provided must be registered with the kernel by calling `register_buffers` first. |
| 418 | /// The `buffer_index` must be the same as its index in the array provided to `register_buffers`. | 439 | /// The `buffer_index` must be the same as its index in the array provided to `register_buffers`. |
| ... | @@ -1244,6 +1265,12 @@ pub fn io_uring_prep_write(sqe: *linux.io_uring_sqe, fd: os.fd_t, buffer: []cons | ... | @@ -1244,6 +1265,12 @@ pub fn io_uring_prep_write(sqe: *linux.io_uring_sqe, fd: os.fd_t, buffer: []cons |
| 1244 | io_uring_prep_rw(.WRITE, sqe, fd, @intFromPtr(buffer.ptr), buffer.len, offset); | 1265 | io_uring_prep_rw(.WRITE, sqe, fd, @intFromPtr(buffer.ptr), buffer.len, offset); |
| 1245 | } | 1266 | } |
| 1246 | | 1267 | |
| | 1268 | pub fn io_uring_prep_splice(sqe: *linux.io_uring_sqe, fd_in: os.fd_t, off_in: i64, fd_out: os.fd_t, off_out: i64, len: usize) void { |
| | 1269 | io_uring_prep_rw(.SPLICE, sqe, fd_out, undefined, len, @bitCast(off_out)); |
| | 1270 | sqe.addr = @bitCast(off_in); |
| | 1271 | sqe.splice_fd_in = fd_in; |
| | 1272 | } |
| | 1273 | |
| 1247 | pub fn io_uring_prep_readv( | 1274 | pub fn io_uring_prep_readv( |
| 1248 | sqe: *linux.io_uring_sqe, | 1275 | sqe: *linux.io_uring_sqe, |
| 1249 | fd: os.fd_t, | 1276 | fd: os.fd_t, |
| ... | @@ -1828,6 +1855,77 @@ test "write/read" { | ... | @@ -1828,6 +1855,77 @@ test "write/read" { |
| 1828 | try testing.expectEqualSlices(u8, buffer_write[0..], buffer_read[0..]); | 1855 | try testing.expectEqualSlices(u8, buffer_write[0..], buffer_read[0..]); |
| 1829 | } | 1856 | } |
| 1830 | | 1857 | |
| | 1858 | test "splice/read" { |
| | 1859 | if (builtin.os.tag != .linux) return error.SkipZigTest; |
| | 1860 | |
| | 1861 | var ring = IO_Uring.init(4, 0) catch |err| switch (err) { |
| | 1862 | error.SystemOutdated => return error.SkipZigTest, |
| | 1863 | error.PermissionDenied => return error.SkipZigTest, |
| | 1864 | else => return err, |
| | 1865 | }; |
| | 1866 | defer ring.deinit(); |
| | 1867 | |
| | 1868 | var tmp = std.testing.tmpDir(.{}); |
| | 1869 | const path_src = "test_io_uring_splice_src"; |
| | 1870 | const file_src = try tmp.dir.createFile(path_src, .{ .read = true, .truncate = true }); |
| | 1871 | defer file_src.close(); |
| | 1872 | const fd_src = file_src.handle; |
| | 1873 | |
| | 1874 | const path_dst = "test_io_uring_splice_dst"; |
| | 1875 | const file_dst = try tmp.dir.createFile(path_dst, .{ .read = true, .truncate = true }); |
| | 1876 | defer file_dst.close(); |
| | 1877 | const fd_dst = file_dst.handle; |
| | 1878 | |
| | 1879 | const buffer_write = [_]u8{97} ** 20; |
| | 1880 | var buffer_read = [_]u8{98} ** 20; |
| | 1881 | _ = try file_src.write(&buffer_write); |
| | 1882 | |
| | 1883 | var fds = try os.pipe(); |
| | 1884 | const pipe_offset: i64 = -1; |
| | 1885 | |
| | 1886 | const sqe_splice_to_pipe = try ring.splice(0x11111111, fd_src, 0, fds[1], pipe_offset, buffer_write.len); |
| | 1887 | try testing.expectEqual(linux.IORING_OP.SPLICE, sqe_splice_to_pipe.opcode); |
| | 1888 | try testing.expectEqual(@as(u64, 0), sqe_splice_to_pipe.addr); |
| | 1889 | try testing.expectEqual(@as(u64, @bitCast((pipe_offset))), sqe_splice_to_pipe.off); |
| | 1890 | sqe_splice_to_pipe.flags |= linux.IOSQE_IO_LINK; |
| | 1891 | |
| | 1892 | const sqe_splice_from_pipe = try ring.splice(0x22222222, fds[0], pipe_offset, fd_dst, 10, buffer_write.len); |
| | 1893 | try testing.expectEqual(linux.IORING_OP.SPLICE, sqe_splice_from_pipe.opcode); |
| | 1894 | try testing.expectEqual(@as(u64, @bitCast(pipe_offset)), sqe_splice_from_pipe.addr); |
| | 1895 | try testing.expectEqual(@as(u64, 10), sqe_splice_from_pipe.off); |
| | 1896 | sqe_splice_from_pipe.flags |= linux.IOSQE_IO_LINK; |
| | 1897 | |
| | 1898 | const sqe_read = try ring.read(0x33333333, fd_dst, .{ .buffer = buffer_read[0..] }, 10); |
| | 1899 | try testing.expectEqual(linux.IORING_OP.READ, sqe_read.opcode); |
| | 1900 | try testing.expectEqual(@as(u64, 10), sqe_read.off); |
| | 1901 | try testing.expectEqual(@as(u32, 3), try ring.submit()); |
| | 1902 | |
| | 1903 | const cqe_splice_to_pipe = try ring.copy_cqe(); |
| | 1904 | const cqe_splice_from_pipe = try ring.copy_cqe(); |
| | 1905 | const cqe_read = try ring.copy_cqe(); |
| | 1906 | // Prior to Linux Kernel 5.6 this is the only way to test for splice/read support: |
| | 1907 | // https://lwn.net/Articles/809820/ |
| | 1908 | if (cqe_splice_to_pipe.err() == .INVAL) return error.SkipZigTest; |
| | 1909 | if (cqe_splice_from_pipe.err() == .INVAL) return error.SkipZigTest; |
| | 1910 | if (cqe_read.err() == .INVAL) return error.SkipZigTest; |
| | 1911 | try testing.expectEqual(linux.io_uring_cqe{ |
| | 1912 | .user_data = 0x11111111, |
| | 1913 | .res = buffer_write.len, |
| | 1914 | .flags = 0, |
| | 1915 | }, cqe_splice_to_pipe); |
| | 1916 | try testing.expectEqual(linux.io_uring_cqe{ |
| | 1917 | .user_data = 0x22222222, |
| | 1918 | .res = buffer_write.len, |
| | 1919 | .flags = 0, |
| | 1920 | }, cqe_splice_from_pipe); |
| | 1921 | try testing.expectEqual(linux.io_uring_cqe{ |
| | 1922 | .user_data = 0x33333333, |
| | 1923 | .res = buffer_read.len, |
| | 1924 | .flags = 0, |
| | 1925 | }, cqe_read); |
| | 1926 | try testing.expectEqualSlices(u8, buffer_write[0..], buffer_read[0..]); |
| | 1927 | } |
| | 1928 | |
| 1831 | test "write_fixed/read_fixed" { | 1929 | test "write_fixed/read_fixed" { |
| 1832 | if (builtin.os.tag != .linux) return error.SkipZigTest; | 1930 | if (builtin.os.tag != .linux) return error.SkipZigTest; |
| 1833 | | 1931 | |