authorgravatar for joad.nacer@gmail.comjoadnacer <joad.nacer@gmail.com> 2023-10-20 01:35:12+01:00
committergravatar for git@vexu.euVeikka Tuominen <git@vexu.eu> 2023-10-21 05:02:17+03:00
log89aad1b89c0800bf54ffda905d5c3cbbe1c3bd71
tree0bce1e5be84f3af6c8ed409747b54fa05b6173a7
parent0d4d8dfc1583138a59147f1aab5eec803a23adb1

linux.io_uring: Implement splice functions


1 files changed, 98 insertions(+), 0 deletions(-)

lib/std/os/linux/io_uring.zig+98
...@@ -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 }
415415
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}
12461267
1268pub 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
1247pub fn io_uring_prep_readv(1274pub 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}
18301857
1858test "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
1831test "write_fixed/read_fixed" {1929test "write_fixed/read_fixed" {
1832 if (builtin.os.tag != .linux) return error.SkipZigTest;1930 if (builtin.os.tag != .linux) return error.SkipZigTest;
18331931