Add C++ wrapper for liburing
Test: th
Bug: 250637970
Change-Id: I6303617ad02c6be2f2a6d3b28cd81f10406302bb
diff --git a/.gitignore b/.gitignore
index 84f29a3..f51c633 100644
--- a/.gitignore
+++ b/.gitignore
@@ -10,3 +10,7 @@
/update_engine_unittests
*.pyc
.vscode
+.git
+.cache
+.xmake
+
diff --git a/liburing_cpp/.gitignore b/liburing_cpp/.gitignore
new file mode 100644
index 0000000..e52e042
--- /dev/null
+++ b/liburing_cpp/.gitignore
@@ -0,0 +1,6 @@
+build
+.xmake
+.cache
+.vscode
+compile_commands.json
+
diff --git a/liburing_cpp/Android.bp b/liburing_cpp/Android.bp
new file mode 100644
index 0000000..2296f7e
--- /dev/null
+++ b/liburing_cpp/Android.bp
@@ -0,0 +1,31 @@
+
+cc_library {
+ name: "liburing_cpp",
+ host_supported: true,
+ recovery_available: true,
+ srcs: [
+ "src/IoUring.cpp",
+ "src/IoUringSQE.cpp",
+ ],
+ static_libs: [
+ "liburing",
+ ],
+ include_dirs: ["bionic/libc/kernel"],
+ export_include_dirs: [
+ "include",
+ ],
+}
+
+
+cc_test_host {
+ name: "liburing_cpp_tests",
+ srcs: [
+ "tests/BasicTests.cpp",
+ "tests/main.cpp",
+ ],
+ static_libs: [
+ "libgtest",
+ "liburing",
+ "liburing_cpp",
+ ],
+}
\ No newline at end of file
diff --git a/liburing_cpp/README.md b/liburing_cpp/README.md
new file mode 100644
index 0000000..1efabae
--- /dev/null
+++ b/liburing_cpp/README.md
@@ -0,0 +1,26 @@
+# liburing_cpp
+
+This project provides a idiomatic C++ wrapper for liburing.
+
+## Source Code Headers
+
+Every file containing source code must include copyright and license
+information. This includes any JS/CSS files that you might be serving out to
+browsers. (This is to help well-intentioned people avoid accidental copying that
+doesn't comply with the license.)
+
+Apache header:
+
+ Copyright 2022 Google LLC
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ https://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/liburing_cpp/include/liburing_cpp/BufferView.h b/liburing_cpp/include/liburing_cpp/BufferView.h
new file mode 100644
index 0000000..eff93df
--- /dev/null
+++ b/liburing_cpp/include/liburing_cpp/BufferView.h
@@ -0,0 +1,50 @@
+//
+// Copyright (C) 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#ifndef __BUFFER_VIEW_CPP_H
+#define __BUFFER_VIEW_CPP_H
+
+#include <stddef.h>
+#include <assert.h>
+
+// non-owning reference to a contiguous memory region. Similar to
+// std::string_view, but allows you to modify underlying memory region.
+template <typename T>
+struct BufferView {
+ constexpr BufferView() = default;
+ constexpr BufferView(T* data, size_t size) : ptr_(data), size_(size) {}
+
+ T* data() { return ptr_; }
+ size_t size() const { return size_; }
+ T* begin() { return ptr_; };
+ T* end() { return ptr_ + size_; }
+ bool empty() const { return size_ == 0; }
+
+ T& operator[](const size_t idx) {
+ assert(idx < size_);
+ return ptr_[idx];
+ }
+ const T& operator[](const size_t idx) const {
+ assert(idx < size_);
+ return ptr_[idx];
+ }
+
+ private:
+ T* ptr_{};
+ size_t size_{};
+};
+
+#endif
diff --git a/liburing_cpp/include/liburing_cpp/IoUring.h b/liburing_cpp/include/liburing_cpp/IoUring.h
new file mode 100644
index 0000000..324fb10
--- /dev/null
+++ b/liburing_cpp/include/liburing_cpp/IoUring.h
@@ -0,0 +1,79 @@
+//
+// Copyright (C) 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#ifndef __IO_URING_CPP_H
+#define __IO_URING_CPP_H
+
+#include <errno.h>
+#include <string.h>
+
+#include <sys/uio.h>
+
+#include <algorithm>
+#include <memory>
+#include <optional>
+#include <variant>
+
+#include "IoUringCQE.h"
+#include "IoUringSQE.h"
+
+namespace io_uring_cpp {
+
+template <typename Err, typename Res>
+struct [[nodiscard]] Result : public std::variant<Err, Res> {
+ constexpr bool IsOk() const { return std::holds_alternative<Res>(*this); }
+ constexpr bool IsErr() const { return std::holds_alternative<Err>(*this); }
+ constexpr Err GetError() const { return std::get<Err>(*this); }
+ constexpr const Res& GetResult() const& { return std::get<Res>(*this); }
+ constexpr Res&& GetResult() && { return std::get<Res>(*this); }
+};
+
+class IoUringInterface {
+ public:
+ virtual ~IoUringInterface() {}
+ // Registration helpers
+ // Register a fixed set of buffers to kernel.
+ virtual Errno RegisterBuffers(const struct iovec* iovecs,
+ size_t iovec_size) = 0;
+ virtual Errno UnregisterBuffers() = 0;
+
+ // Register a set of file descriptors to kernel.
+ virtual Errno RegisterFiles(const int* files, size_t files_size) = 0;
+ virtual Errno UnregisterFiles() = 0;
+ // Append a submission entry into this io_uring. This does not submit the
+ // operation to the kernel. For that, call |IoUringInterface::Submit()|
+ virtual IoUringSQE PrepRead(int fd, void *buf, unsigned nbytes,
+ uint64_t offset) = 0;
+ // Caller is responsible for making sure the input memory is available until
+ // this write operation completes.
+ virtual IoUringSQE PrepWrite(int fd, const void *buf, unsigned nbytes,
+ uint64_t offset) = 0;
+
+ // Ring operations
+ virtual IoUringSubmitResult Submit() = 0;
+ // Submit and block until |completions| number of CQEs are available
+ virtual IoUringSubmitResult SubmitAndWait(size_t completions) = 0;
+ virtual Result<Errno, IoUringCQE> PopCQE() = 0;
+ virtual Result<Errno, std::vector<IoUringCQE>> PopCQE(unsigned int count) = 0;
+ virtual Result<Errno, IoUringCQE> PeekCQE() = 0;
+
+ static std::unique_ptr<IoUringInterface> CreateLinuxIoUring(int queue_depth,
+ int flags);
+};
+
+} // namespace io_uring_cpp
+
+#endif
diff --git a/liburing_cpp/include/liburing_cpp/IoUringCQE.h b/liburing_cpp/include/liburing_cpp/IoUringCQE.h
new file mode 100644
index 0000000..e75731e
--- /dev/null
+++ b/liburing_cpp/include/liburing_cpp/IoUringCQE.h
@@ -0,0 +1,49 @@
+//
+// Copyright (C) 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#ifndef __IO_URING_CQE_CPP_H
+#define __IO_URING_CQE_CPP_H
+
+#include <stdint.h>
+
+#include <type_traits>
+
+namespace io_uring_cpp {
+
+struct IoUringCQE {
+ int32_t res;
+ uint32_t flags;
+ template <typename T>
+ T GetData() const {
+ static_assert(
+ std::is_trivially_copy_constructible_v<T>,
+ "Only trivially copiable types can be passed for io_uring data");
+ static_assert(sizeof(T) <= 8,
+ "io_uring SQE's data field has size of 8 bytes, can't pass "
+ "data larger than that.");
+ return *reinterpret_cast<const T*>(&userdata);
+ }
+
+ constexpr IoUringCQE(int32_t res, uint32_t flags, uint64_t userdata)
+ : res(res), flags(flags), userdata(userdata) {}
+
+ private:
+ uint64_t userdata;
+};
+
+} // namespace io_uring_cpp
+
+#endif
diff --git a/liburing_cpp/include/liburing_cpp/IoUringSQE.h b/liburing_cpp/include/liburing_cpp/IoUringSQE.h
new file mode 100644
index 0000000..ca2b365
--- /dev/null
+++ b/liburing_cpp/include/liburing_cpp/IoUringSQE.h
@@ -0,0 +1,79 @@
+//
+// Copyright (C) 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+#include <errno.h>
+#include <string.h>
+
+#include <algorithm>
+#include <iostream>
+#include <numeric>
+#include <type_traits>
+#include <vector>
+
+#ifndef __IO_URING_SQE_CPP_H
+#define __IO_URING_SQE_CPP_H
+
+namespace io_uring_cpp {
+
+struct [[nodiscard]] IoUringSubmitResult {
+ constexpr bool IsOk() const { return ret > 0; }
+ const char *ErrMsg() const {
+ if (IsOk()) {
+ return nullptr;
+ }
+ return strerror(-ret);
+ }
+ constexpr auto ErrCode() const { return std::min(ret, 0); }
+ constexpr auto EntriesSubmitted() const { return std::max(ret, 0); }
+
+ int ret;
+};
+
+struct [[nodiscard]] Errno {
+ constexpr Errno(int ret) : error_code(ret) {
+ error_code = std::abs(error_code);
+ }
+ int error_code;
+ constexpr int ErrCode() { return error_code; }
+ const char *ErrMsg();
+ constexpr bool IsOk() const { return error_code == 0; }
+};
+
+std::ostream &operator<<(std::ostream &, Errno err);
+
+struct [[nodiscard]] IoUringSQE {
+ constexpr IoUringSQE(void *p) : sqe(p) {}
+ IoUringSQE &SetFlags(unsigned int flags);
+ template <typename T>
+ IoUringSQE &SetData(const T &data) {
+ static_assert(
+ std::is_trivially_copy_constructible_v<T>,
+ "Only trivially copiable types can be passed for io_uring data");
+ static_assert(sizeof(T) <= 8,
+ "io_uring SQE's data field has size of 8 bytes, can't pass "
+ "data larger than that.");
+ return SetData(*reinterpret_cast<const uint64_t*>(&data));
+ }
+ IoUringSQE& SetData(uint64_t data);
+
+ constexpr bool IsOk() const { return sqe != nullptr; }
+
+ private:
+ void *sqe;
+};
+
+} // namespace io_uring_cpp
+
+#endif
diff --git a/liburing_cpp/src/IoUring.cpp b/liburing_cpp/src/IoUring.cpp
new file mode 100644
index 0000000..a5fe76f
--- /dev/null
+++ b/liburing_cpp/src/IoUring.cpp
@@ -0,0 +1,187 @@
+//
+// Copyright (C) 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#include <asm-generic/errno-base.h>
+#include <liburing_cpp/IoUring.h>
+#include <string.h>
+
+#include <algorithm>
+#include <iostream>
+#include <memory>
+
+#include "liburing.h"
+#include "liburing_cpp/IoUringCQE.h"
+
+namespace io_uring_cpp {
+
+template <typename T>
+bool IsZeroInitialized(const T& val) {
+ auto begin = reinterpret_cast<const char*>(&val);
+ auto end = begin + sizeof(val);
+ return std::all_of(begin, end, [](const auto& a) { return a == 0; });
+}
+
+class IoUring final : public IoUringInterface {
+ public:
+ ~IoUring() override {
+ if (!IsZeroInitialized(ring)) {
+ if (buffer_registered_) {
+ UnregisterBuffers();
+ }
+ if (files_registered_) {
+ UnregisterFiles();
+ }
+ io_uring_queue_exit(&ring);
+ }
+ }
+ IoUring(const IoUring&) = delete;
+ IoUring(IoUring&& rhs) {
+ ring = rhs.ring;
+ memset(&rhs.ring, 0, sizeof(rhs.ring));
+ }
+ IoUring& operator=(IoUring&& rhs) {
+ std::swap(ring, rhs.ring);
+ return *this;
+ }
+ Errno RegisterBuffers(const struct iovec* iovecs,
+ size_t iovec_size) override {
+ const auto ret =
+ Errno(io_uring_register_buffers(&ring, iovecs, iovec_size));
+ buffer_registered_ = ret.IsOk();
+ return ret;
+ }
+
+ Errno UnregisterBuffers() override {
+ const auto ret = Errno(io_uring_unregister_buffers(&ring));
+ buffer_registered_ = !ret.IsOk();
+ return ret;
+ }
+
+ Errno RegisterFiles(const int* files, size_t files_size) override {
+ const auto ret = Errno(io_uring_register_files(&ring, files, files_size));
+ files_registered_ = ret.IsOk();
+ return ret;
+ }
+
+ Errno UnregisterFiles() {
+ const auto ret = Errno(io_uring_unregister_files(&ring));
+ files_registered_ = !ret.IsOk();
+ return ret;
+ }
+
+ IoUringSQE PrepRead(int fd, void* buf, unsigned nbytes,
+ uint64_t offset) override {
+ auto sqe = io_uring_get_sqe(&ring);
+ if (sqe == nullptr) {
+ return IoUringSQE{nullptr};
+ }
+ io_uring_prep_read(sqe, fd, buf, nbytes, offset);
+ return IoUringSQE{static_cast<void*>(sqe)};
+ }
+ IoUringSQE PrepWrite(int fd, const void* buf, unsigned nbytes,
+ uint64_t offset) override {
+ auto sqe = io_uring_get_sqe(&ring);
+ if (sqe == nullptr) {
+ return IoUringSQE{nullptr};
+ }
+ io_uring_prep_write(sqe, fd, buf, nbytes, offset);
+ return IoUringSQE{static_cast<void*>(sqe)};
+ }
+ IoUringSubmitResult Submit() override {
+ return IoUringSubmitResult{io_uring_submit(&ring)};
+ }
+
+ IoUringSubmitResult SubmitAndWait(size_t completions) override {
+ return IoUringSubmitResult{io_uring_submit_and_wait(&ring, completions)};
+ }
+
+ Result<Errno, std::vector<IoUringCQE>> PopCQE(
+ const unsigned int count) override {
+ std::vector<io_uring_cqe*> cqe_ptrs;
+ cqe_ptrs.resize(count);
+ const auto ret = io_uring_wait_cqe_nr(&ring, cqe_ptrs.data(), count);
+ if (ret != 0) {
+ return {Errno(ret)};
+ }
+ const auto filled = io_uring_peek_batch_cqe(&ring, cqe_ptrs.data(), count);
+ if (filled != count) {
+ return {Errno(EAGAIN)};
+ }
+ std::vector<IoUringCQE> cqes;
+ cqes.reserve(count);
+ for (const auto& cqe : cqe_ptrs) {
+ if (cqe == nullptr) {
+ return {Errno(EAGAIN)};
+ }
+ cqes.push_back(IoUringCQE(cqe->res, cqe->flags, cqe->user_data));
+ io_uring_cqe_seen(&ring, cqe);
+ }
+ return {cqes};
+ }
+
+ Result<Errno, IoUringCQE> PopCQE() override {
+ struct io_uring_cqe* ptr{};
+ const auto ret = io_uring_wait_cqe(&ring, &ptr);
+ if (ret != 0) {
+ return {Errno(ret)};
+ }
+ const auto cqe = IoUringCQE(ptr->res, ptr->flags, ptr->user_data);
+ io_uring_cqe_seen(&ring, ptr);
+ return {cqe};
+ }
+
+ Result<Errno, IoUringCQE> PeekCQE() override {
+ struct io_uring_cqe* ptr{};
+ const auto ret = io_uring_peek_cqe(&ring, &ptr);
+ if (ret != 0) {
+ return {Errno(ret)};
+ }
+ return {IoUringCQE(ptr->res, ptr->flags, ptr->user_data)};
+ }
+
+ IoUring(struct io_uring r) : ring(r) {}
+
+ private:
+ struct io_uring ring {};
+ bool buffer_registered_ = false;
+ bool files_registered_ = false;
+ std::atomic<size_t> request_id_{};
+};
+
+const char* Errno::ErrMsg() {
+ if (error_code == 0) {
+ return nullptr;
+ }
+ return strerror(error_code);
+}
+
+std::ostream& operator<<(std::ostream& out, Errno err) {
+ out << err.ErrCode() << ", " << err.ErrMsg();
+ return out;
+}
+
+std::unique_ptr<IoUringInterface> IoUringInterface::CreateLinuxIoUring(
+ int queue_depth, int flags) {
+ struct io_uring ring {};
+ const auto err = io_uring_queue_init(queue_depth, &ring, flags);
+ if (err) {
+ errno = -err;
+ return {};
+ }
+ return std::unique_ptr<IoUringInterface>(new IoUring(ring));
+}
+
+} // namespace io_uring_cpp
diff --git a/liburing_cpp/src/IoUringSQE.cpp b/liburing_cpp/src/IoUringSQE.cpp
new file mode 100644
index 0000000..fc3d3ea
--- /dev/null
+++ b/liburing_cpp/src/IoUringSQE.cpp
@@ -0,0 +1,40 @@
+//
+// Copyright (C) 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#include <liburing_cpp/IoUringSQE.h>
+
+#include <cstdint>
+
+#include "liburing.h"
+
+namespace io_uring_cpp {
+
+IoUringSQE &IoUringSQE::SetFlags(unsigned int flags) {
+ if (IsOk()) {
+ ::io_uring_sqe_set_flags(static_cast<struct io_uring_sqe *>(sqe), flags);
+ }
+ return *this;
+}
+
+IoUringSQE &IoUringSQE::SetData(uint64_t data) {
+ if (IsOk()) {
+ ::io_uring_sqe_set_data(static_cast<struct io_uring_sqe *>(sqe),
+ reinterpret_cast<void *>(data));
+ }
+ return *this;
+}
+
+} // namespace io_uring_cpp
diff --git a/liburing_cpp/tests/BasicTests.cpp b/liburing_cpp/tests/BasicTests.cpp
new file mode 100644
index 0000000..81288f6
--- /dev/null
+++ b/liburing_cpp/tests/BasicTests.cpp
@@ -0,0 +1,194 @@
+//
+// Copyright (C) 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#include <gtest/gtest.h>
+#include <liburing_cpp/IoUring.h>
+
+#include <linux/fs.h>
+#include <stdio.h>
+
+#include <fcntl.h>
+#include <sys/mman.h>
+#include <sys/utsname.h>
+#include <unistd.h>
+
+#include <algorithm>
+#include <array>
+#include <cstring>
+#include <vector>
+
+using namespace io_uring_cpp;
+
+class IoUringTest : public ::testing::Test {
+ public:
+ IoUringTest() { fp = tmpfile(); }
+ ~IoUringTest() {
+ if (fp) {
+ fclose(fp);
+ }
+ }
+ void SetUp() override {
+ struct utsname buffer {};
+
+ ASSERT_EQ(uname(&buffer), 0)
+ << strerror(errno) << "Failed to get kernel version number";
+ int major = 0;
+ int minor = 0;
+ const auto matched = sscanf(buffer.release, "%d.%d", &major, &minor);
+ ASSERT_EQ(matched, 2) << "Unexpected kernel version format: "
+ << buffer.release;
+
+ if (major < 5 || (major == 5 && minor < 6)) {
+ GTEST_SKIP() << "Kernel version does not support io_uring "
+ << buffer.release;
+ return;
+ }
+
+ ring = IoUringInterface::CreateLinuxIoUring(4096, 0);
+ ASSERT_NE(ring, nullptr);
+ }
+ void Write(int fd, const void* data, const size_t len) {
+ const auto buf = static_cast<const char*>(data);
+ constexpr size_t IO_BATCH_SIZE = 4096;
+ size_t i = 0;
+ for (i = 0; i < len; i += IO_BATCH_SIZE) {
+ const auto sqe = ring->PrepWrite(fd, buf + i, IO_BATCH_SIZE, i);
+ ASSERT_TRUE(sqe.IsOk());
+ }
+ const auto bytes_remaining = len - i;
+ if (bytes_remaining) {
+ ASSERT_TRUE(ring->PrepWrite(fd, buf + i, bytes_remaining, i).IsOk());
+ }
+ const auto ret = ring->Submit();
+ ASSERT_TRUE(ret.IsOk()) << ret.ErrMsg();
+ for (size_t i = (len + IO_BATCH_SIZE - 1) / IO_BATCH_SIZE; i > 0; i--) {
+ const auto cqe = ring->PopCQE();
+ ASSERT_TRUE(cqe.IsOk());
+ ASSERT_GT(cqe.GetResult().res, 0);
+ }
+ }
+ std::unique_ptr<IoUringInterface> ring;
+ FILE* fp = nullptr;
+};
+
+TEST_F(IoUringTest, SmallRead) {
+ int fd = open("/proc/self/maps", O_RDONLY);
+ std::array<char, 1024> buf{};
+ const auto sqe = ring->PrepRead(fd, buf.data(), buf.size(), 0);
+ ASSERT_TRUE(sqe.IsOk()) << "Submission Queue is full!";
+ const auto ret = ring->Submit();
+ ASSERT_TRUE(ret.IsOk()) << ret.ErrMsg();
+ const auto cqe = ring->PopCQE();
+ ASSERT_TRUE(cqe.IsOk()) << cqe.GetError();
+ ASSERT_GT(cqe.GetResult().res, 0);
+}
+
+TEST_F(IoUringTest, SmallWrite) {
+ auto fp = tmpfile();
+ int fd = fileno(fp);
+ std::string buffer(256, 'A');
+ const auto sqe = ring->PrepWrite(fd, buffer.data(), buffer.size(), 0);
+ ASSERT_TRUE(sqe.IsOk()) << "Submission Queue is full!";
+ const auto ret = ring->Submit();
+ ASSERT_TRUE(ret.IsOk()) << ret.ErrMsg();
+ const auto cqe = ring->PopCQE();
+ ASSERT_TRUE(cqe.IsOk()) << cqe.GetError();
+
+ const auto bytes_read = pread(fd, buffer.data(), buffer.size(), 0);
+
+ ASSERT_EQ(bytes_read, buffer.size());
+
+ ASSERT_TRUE(std::all_of(buffer.begin(), buffer.end(), [](const auto& a) {
+ return a == 'A';
+ })) << buffer;
+ fclose(fp);
+}
+
+TEST_F(IoUringTest, ChunkedWrite) {
+ int fd = fileno(fp);
+ std::string buffer(16 * 1024 * 1024, 'A');
+ ASSERT_NO_FATAL_FAILURE(Write(fd, buffer.data(), buffer.size()));
+
+ const auto bytes_read = pread(fd, buffer.data(), buffer.size(), 0);
+
+ ASSERT_EQ(bytes_read, buffer.size());
+
+ ASSERT_TRUE(std::all_of(buffer.begin(), buffer.end(), [](const auto& a) {
+ return a == 'A';
+ })) << buffer;
+}
+
+// Page size doesn't really matter. We can replace 4096 with any value.
+static constexpr size_t kBlockSize = 4096;
+constexpr std::array<unsigned char, 4096> GetArbitraryPageData() {
+ std::array<unsigned char, kBlockSize> arr{};
+ int i = 0;
+ for (auto& a : arr) {
+ a = i++;
+ }
+ return arr;
+}
+
+void WriteTestData(int fd, const size_t offset, const size_t size) {
+ ASSERT_EQ(size % kBlockSize, 0);
+ static const auto data = GetArbitraryPageData();
+ size_t bytes_written = 0;
+ size_t cur_offset = offset;
+ while (bytes_written < size) {
+ const auto ret = pwrite(fd, data.data(), kBlockSize, cur_offset);
+ ASSERT_GT(ret, 0) << "Failed to pwrite " << strerror(errno);
+ bytes_written += ret;
+ cur_offset += ret;
+ }
+}
+
+TEST_F(IoUringTest, ExtentRead) {
+ const int fd = fileno(fp);
+ ASSERT_NO_FATAL_FAILURE(WriteTestData(fd, kBlockSize * 3, kBlockSize));
+ ASSERT_NO_FATAL_FAILURE(WriteTestData(fd, kBlockSize * 5, kBlockSize));
+ ASSERT_NO_FATAL_FAILURE(WriteTestData(fd, kBlockSize * 8, kBlockSize));
+ ASSERT_NO_FATAL_FAILURE(WriteTestData(fd, kBlockSize * 13, kBlockSize));
+ fsync(fd);
+
+ std::vector<unsigned char> data;
+ data.resize(kBlockSize * 4);
+
+ ASSERT_TRUE(
+ ring->PrepRead(fd, data.data(), kBlockSize, 3 * kBlockSize).IsOk());
+ ASSERT_TRUE(
+ ring->PrepRead(fd, data.data() + kBlockSize, kBlockSize, 5 * kBlockSize)
+ .IsOk());
+ ASSERT_TRUE(
+ ring->PrepRead(
+ fd, data.data() + kBlockSize * 2, kBlockSize, 8 * kBlockSize)
+ .IsOk());
+ ASSERT_TRUE(
+ ring->PrepRead(
+ fd, data.data() + kBlockSize * 3, kBlockSize, 13 * kBlockSize)
+ .IsOk());
+ ring->SubmitAndWait(4);
+ const auto cqes = ring->PopCQE(4);
+ if (cqes.IsErr()) {
+ FAIL() << cqes.GetError().ErrMsg();
+ return;
+ }
+ for (const auto& cqe : cqes.GetResult()) {
+ ASSERT_GT(cqe.res, 0);
+ }
+ for (int i = 0; i < data.size(); ++i) {
+ ASSERT_EQ(data[i], i % 256);
+ }
+}
\ No newline at end of file
diff --git a/liburing_cpp/tests/main.cpp b/liburing_cpp/tests/main.cpp
new file mode 100644
index 0000000..9874bf2
--- /dev/null
+++ b/liburing_cpp/tests/main.cpp
@@ -0,0 +1,22 @@
+//
+// Copyright (C) 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#include <gtest/gtest.h>
+
+int main(int argc, char *argv[]) {
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
\ No newline at end of file
diff --git a/liburing_cpp/xmake.lua b/liburing_cpp/xmake.lua
new file mode 100644
index 0000000..93e52ae
--- /dev/null
+++ b/liburing_cpp/xmake.lua
@@ -0,0 +1,19 @@
+add_requires("liburing", "gtest")
+
+target("liburing_cpp")
+ set_kind("static")
+ add_files("src/*.cpp")
+ add_packages("liburing")
+ set_languages("c++17")
+ add_includedirs("include", {public = true})
+ add_cxflags("-g")
+
+
+target("liburing_cpp_tests")
+ set_kind("binary")
+ add_files("tests/*.cpp")
+ set_languages("c++17")
+ add_deps("liburing_cpp")
+ add_packages("gtest", "liburing")
+ add_cxflags("-g")
+