From 37497b6d4d2f25a0853b4f771e73282d38cc5805 Mon Sep 17 00:00:00 2001 From: Christian Schwarz Date: Mon, 20 Nov 2023 10:56:47 +0000 Subject: [PATCH] vendor (and add ext trait for) IoBuf/IoBufMut and OpenOptions from `tokio-uring` For [`open_at` support](https://github.com/neondatabase/tokio-epoll-uring/pull/21) we need an `OpenOptions` struct. Sadly we can't re-use the one from `tokio-uring` because it doesn't have a public API for the conversion of OpenOptions into the relevant libc flags. We created [a PR asking for such an API](https://github.com/neondatabase/tokio-uring/pull/1), but, in the meantime, let's unblock ourselves by vendoring the pieces of `tokio-uring` that we need, and cusotmize them as needed. The files that reproduce the `tokio-uring` LICENSE text at the top are copied from `tokio-uring.git:d5e90539bd6d1c518e848298564a098c300866bc`. Files without it were written by myself. --- Cargo.lock | 11 +- Cargo.toml | 1 + tokio-epoll-uring/Cargo.toml | 3 +- tokio-epoll-uring/src/lib.rs | 2 +- tokio-epoll-uring/src/ops/nop.rs | 2 + tokio-epoll-uring/src/ops/read.rs | 10 +- tokio-epoll-uring/src/system/completion.rs | 1 + tokio-epoll-uring/src/system/lifecycle.rs | 1 + .../src/system/lifecycle/handle.rs | 2 +- tokio-epoll-uring/src/system/slots.rs | 1 + tokio-epoll-uring/src/system/submission.rs | 1 + .../src/system/submission/op_fut.rs | 2 + .../system/test_util/shared_system_handle.rs | 3 +- uring-common/Cargo.toml | 9 + uring-common/src/buf.rs | 5 + uring-common/src/buf/io_buf.rs | 141 +++++++ uring-common/src/buf/io_buf_mut.rs | 89 +++++ uring-common/src/lib.rs | 5 + uring-common/src/open_options.rs | 362 ++++++++++++++++++ uring-common/src/open_options_io_uring_ext.rs | 37 ++ 20 files changed, 677 insertions(+), 11 deletions(-) create mode 100644 uring-common/Cargo.toml create mode 100644 uring-common/src/buf.rs create mode 100644 uring-common/src/buf/io_buf.rs create mode 100644 uring-common/src/buf/io_buf_mut.rs create mode 100644 uring-common/src/lib.rs create mode 100644 uring-common/src/open_options.rs create mode 100644 uring-common/src/open_options_io_uring_ext.rs diff --git a/Cargo.lock b/Cargo.lock index 5f78f5a..8e80523 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1611,7 +1611,6 @@ version = "0.1.0" dependencies = [ "assert-panic", "futures", - "io-uring 0.6.0", "nix 0.26.2", "once_cell", "os_pipe", @@ -1619,10 +1618,10 @@ dependencies = [ "tempfile", "thiserror", "tokio", - "tokio-uring", "tokio-util", "tracing", "tracing-subscriber", + "uring-common", ] [[package]] @@ -1731,6 +1730,14 @@ version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" +[[package]] +name = "uring-common" +version = "0.1.0" +dependencies = [ + "io-uring 0.6.0", + "libc", +] + [[package]] name = "utf8parse" version = "0.2.1" diff --git a/Cargo.toml b/Cargo.toml index 717df9d..e32cc4f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,4 +3,5 @@ members = [ "tokio-epoll-uring", "benchmark", + "uring-common", ] diff --git a/tokio-epoll-uring/Cargo.toml b/tokio-epoll-uring/Cargo.toml index 8e96817..e2f3418 100644 --- a/tokio-epoll-uring/Cargo.toml +++ b/tokio-epoll-uring/Cargo.toml @@ -9,14 +9,13 @@ license = "MIT OR Apache-2.0" [dependencies] futures = "0.3.28" -io-uring = "0.6.0" once_cell = "1.18.0" scopeguard = "1.1.0" thiserror = "1.0.44" tokio = { version = "1.29.1", features = ["io-std", "full"] } -tokio-uring = "0.4.0" tokio-util = "0.7.8" tracing = "0.1.37" +uring-common = { path = "../uring-common" } [dev-dependencies] tokio = { version = "1.29.1", features = ["full"] } diff --git a/tokio-epoll-uring/src/lib.rs b/tokio-epoll-uring/src/lib.rs index fcf5136..3154c0a 100644 --- a/tokio-epoll-uring/src/lib.rs +++ b/tokio-epoll-uring/src/lib.rs @@ -80,7 +80,7 @@ pub use system::lifecycle::thread_local::{thread_local_system, Handle}; pub use system::lifecycle::System; pub use system::submission::op_fut::Error as SystemError; -pub use tokio_uring::buf::{IoBuf, IoBufMut}; +pub use uring_common::buf::{IoBuf, IoBufMut}; pub(crate) mod util; diff --git a/tokio-epoll-uring/src/ops/nop.rs b/tokio-epoll-uring/src/ops/nop.rs index a3bae42..7794650 100644 --- a/tokio-epoll-uring/src/ops/nop.rs +++ b/tokio-epoll-uring/src/ops/nop.rs @@ -1,3 +1,5 @@ +use uring_common::io_uring; + use crate::system::submission::op_fut::Op; pub struct Nop {} diff --git a/tokio-epoll-uring/src/ops/read.rs b/tokio-epoll-uring/src/ops/read.rs index 3e86172..29e7515 100644 --- a/tokio-epoll-uring/src/ops/read.rs +++ b/tokio-epoll-uring/src/ops/read.rs @@ -1,21 +1,23 @@ use std::os::fd::{AsRawFd, OwnedFd}; +use uring_common::{buf::IoBufMut, io_uring}; + use crate::system::submission::op_fut::Op; pub struct ReadOp where - B: tokio_uring::buf::IoBufMut + Send, + B: IoBufMut + Send, { pub(crate) file: OwnedFd, pub(crate) offset: u64, pub(crate) buf: B, } -impl crate::sealed::Sealed for ReadOp where B: tokio_uring::buf::IoBufMut + Send {} +impl crate::sealed::Sealed for ReadOp where B: IoBufMut + Send {} impl Op for ReadOp where - B: tokio_uring::buf::IoBufMut + Send, + B: IoBufMut + Send, { type Resources = (OwnedFd, B); type Success = usize; @@ -43,7 +45,7 @@ where let res = if res < 0 { Err(std::io::Error::from_raw_os_error(-res)) } else { - unsafe { tokio_uring::buf::IoBufMut::set_init(&mut self.buf, res as usize) }; + unsafe { IoBufMut::set_init(&mut self.buf, res as usize) }; Ok(res as usize) }; ((self.file, self.buf), res) diff --git a/tokio-epoll-uring/src/system/completion.rs b/tokio-epoll-uring/src/system/completion.rs index a12c05b..a87c146 100644 --- a/tokio-epoll-uring/src/system/completion.rs +++ b/tokio-epoll-uring/src/system/completion.rs @@ -6,6 +6,7 @@ use std::{ use io_uring::CompletionQueue; use tokio::sync::{self, broadcast, mpsc, oneshot}; use tracing::{debug, info, info_span, trace, Instrument}; +use uring_common::io_uring; use crate::{system::submission::SubmitSideInner, util::oneshot_nonconsuming}; diff --git a/tokio-epoll-uring/src/system/lifecycle.rs b/tokio-epoll-uring/src/system/lifecycle.rs index a726ff4..82d6d22 100644 --- a/tokio-epoll-uring/src/system/lifecycle.rs +++ b/tokio-epoll-uring/src/system/lifecycle.rs @@ -7,6 +7,7 @@ pub mod handle; pub mod thread_local; use io_uring::{CompletionQueue, SubmissionQueue, Submitter}; +use uring_common::io_uring; use crate::{ system::{completion::ShutdownRequestImpl, RING_SIZE}, diff --git a/tokio-epoll-uring/src/system/lifecycle/handle.rs b/tokio-epoll-uring/src/system/lifecycle/handle.rs index 7de6d20..97b1f8b 100644 --- a/tokio-epoll-uring/src/system/lifecycle/handle.rs +++ b/tokio-epoll-uring/src/system/lifecycle/handle.rs @@ -2,7 +2,7 @@ use futures::FutureExt; use std::{os::fd::OwnedFd, task::ready}; -use tokio_uring::buf::IoBufMut; +use uring_common::buf::IoBufMut; use crate::{ ops::read::ReadOp, diff --git a/tokio-epoll-uring/src/system/slots.rs b/tokio-epoll-uring/src/system/slots.rs index 35df06c..e2ae935 100644 --- a/tokio-epoll-uring/src/system/slots.rs +++ b/tokio-epoll-uring/src/system/slots.rs @@ -32,6 +32,7 @@ use std::{ use tokio::sync::oneshot; use tracing::{debug, trace}; +use uring_common::io_uring; use crate::system::submission::op_fut::Error; diff --git a/tokio-epoll-uring/src/system/submission.rs b/tokio-epoll-uring/src/system/submission.rs index 85e93f4..f1f184e 100644 --- a/tokio-epoll-uring/src/system/submission.rs +++ b/tokio-epoll-uring/src/system/submission.rs @@ -6,6 +6,7 @@ use std::{ }; use io_uring::{SubmissionQueue, Submitter}; +use uring_common::io_uring; use super::{ completion::CompletionSide, diff --git a/tokio-epoll-uring/src/system/submission/op_fut.rs b/tokio-epoll-uring/src/system/submission/op_fut.rs index 9edaa25..1e8ba9d 100644 --- a/tokio-epoll-uring/src/system/submission/op_fut.rs +++ b/tokio-epoll-uring/src/system/submission/op_fut.rs @@ -12,6 +12,8 @@ pub trait Op: crate::sealed::Sealed + Sized + Send + 'static { fn make_sqe(&mut self) -> io_uring::squeue::Entry; } +use uring_common::io_uring; + use crate::system::{ completion::ProcessCompletionsCause, slots::{self, SlotHandle}, diff --git a/tokio-epoll-uring/src/system/test_util/shared_system_handle.rs b/tokio-epoll-uring/src/system/test_util/shared_system_handle.rs index 367177b..985adaa 100644 --- a/tokio-epoll-uring/src/system/test_util/shared_system_handle.rs +++ b/tokio-epoll-uring/src/system/test_util/shared_system_handle.rs @@ -1,6 +1,7 @@ use std::sync::{Arc, RwLock}; use futures::Future; +use uring_common::buf::IoBufMut; use crate::SystemError; use crate::{System, SystemHandle}; @@ -42,7 +43,7 @@ impl SharedSystemHandle { .initiate_shutdown() } - pub fn read( + pub fn read( &self, file: std::os::fd::OwnedFd, offset: u64, diff --git a/uring-common/Cargo.toml b/uring-common/Cargo.toml new file mode 100644 index 0000000..d3830b5 --- /dev/null +++ b/uring-common/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "uring-common" +version = "0.1.0" +edition = "2021" +description = "a subset of types from the `tokio-uring` crate" + +[dependencies] +libc = "0.2.80" +io-uring = "0.6.0" diff --git a/uring-common/src/buf.rs b/uring-common/src/buf.rs new file mode 100644 index 0000000..480fac6 --- /dev/null +++ b/uring-common/src/buf.rs @@ -0,0 +1,5 @@ +mod io_buf; +mod io_buf_mut; + +pub use io_buf::IoBuf; +pub use io_buf_mut::IoBufMut; diff --git a/uring-common/src/buf/io_buf.rs b/uring-common/src/buf/io_buf.rs new file mode 100644 index 0000000..1ca2c75 --- /dev/null +++ b/uring-common/src/buf/io_buf.rs @@ -0,0 +1,141 @@ +// Copyright (c) 2021 Carl Lerche +// +// Permission is hereby granted, free of charge, to any +// person obtaining a copy of this software and associated +// documentation files (the "Software"), to deal in the +// Software without restriction, including without +// limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of +// the Software, and to permit persons to whom the Software +// is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice +// shall be included in all copies or substantial portions +// of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +// ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +// TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +// SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. +// +// +// Based on tokio-uring.git:d5e90539bd6d1c518e848298564a098c300866bc + +/// An `io-uring` compatible buffer. +/// +/// The `IoBuf` trait is implemented by buffer types that can be used with +/// io-uring operations. Users will not need to use this trait directly. +/// The [`BoundedBuf`] trait provides some useful methods including `slice`. +/// +/// # Safety +/// +/// Buffers passed to `io-uring` operations must reference a stable memory +/// region. While the runtime holds ownership to a buffer, the pointer returned +/// by `stable_ptr` must remain valid even if the `IoBuf` value is moved. +/// +/// [`BoundedBuf`]: crate::buf::BoundedBuf +pub unsafe trait IoBuf: Unpin + 'static { + /// Returns a raw pointer to the vector’s buffer. + /// + /// This method is to be used by the `tokio-uring` runtime and it is not + /// expected for users to call it directly. + /// + /// The implementation must ensure that, while the `tokio-uring` runtime + /// owns the value, the pointer returned by `stable_ptr` **does not** + /// change. + fn stable_ptr(&self) -> *const u8; + + /// Number of initialized bytes. + /// + /// This method is to be used by the `tokio-uring` runtime and it is not + /// expected for users to call it directly. + /// + /// For `Vec`, this is identical to `len()`. + fn bytes_init(&self) -> usize; + + /// Total size of the buffer, including uninitialized memory, if any. + /// + /// This method is to be used by the `tokio-uring` runtime and it is not + /// expected for users to call it directly. + /// + /// For `Vec`, this is identical to `capacity()`. + fn bytes_total(&self) -> usize; +} + +unsafe impl IoBuf for Vec { + fn stable_ptr(&self) -> *const u8 { + self.as_ptr() + } + + fn bytes_init(&self) -> usize { + self.len() + } + + fn bytes_total(&self) -> usize { + self.capacity() + } +} + +unsafe impl IoBuf for &'static [u8] { + fn stable_ptr(&self) -> *const u8 { + self.as_ptr() + } + + fn bytes_init(&self) -> usize { + <[u8]>::len(self) + } + + fn bytes_total(&self) -> usize { + self.bytes_init() + } +} + +unsafe impl IoBuf for &'static str { + fn stable_ptr(&self) -> *const u8 { + self.as_ptr() + } + + fn bytes_init(&self) -> usize { + ::len(self) + } + + fn bytes_total(&self) -> usize { + self.bytes_init() + } +} + +#[cfg(feature = "bytes")] +unsafe impl IoBuf for bytes::Bytes { + fn stable_ptr(&self) -> *const u8 { + self.as_ptr() + } + + fn bytes_init(&self) -> usize { + self.len() + } + + fn bytes_total(&self) -> usize { + self.len() + } +} + +#[cfg(feature = "bytes")] +unsafe impl IoBuf for bytes::BytesMut { + fn stable_ptr(&self) -> *const u8 { + self.as_ptr() + } + + fn bytes_init(&self) -> usize { + self.len() + } + + fn bytes_total(&self) -> usize { + self.capacity() + } +} diff --git a/uring-common/src/buf/io_buf_mut.rs b/uring-common/src/buf/io_buf_mut.rs new file mode 100644 index 0000000..5e661f9 --- /dev/null +++ b/uring-common/src/buf/io_buf_mut.rs @@ -0,0 +1,89 @@ +// Copyright (c) 2021 Carl Lerche +// +// Permission is hereby granted, free of charge, to any +// person obtaining a copy of this software and associated +// documentation files (the "Software"), to deal in the +// Software without restriction, including without +// limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of +// the Software, and to permit persons to whom the Software +// is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice +// shall be included in all copies or substantial portions +// of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +// ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +// TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +// SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. +// +// +// Based on tokio-uring.git:d5e90539bd6d1c518e848298564a098c300866bc + +use crate::buf::IoBuf; + +/// A mutable`io-uring` compatible buffer. +/// +/// The `IoBufMut` trait is implemented by buffer types that can be used with +/// io-uring operations. Users will not need to use this trait directly. +/// +/// # Safety +/// +/// Buffers passed to `io-uring` operations must reference a stable memory +/// region. While the runtime holds ownership to a buffer, the pointer returned +/// by `stable_mut_ptr` must remain valid even if the `IoBufMut` value is moved. +pub unsafe trait IoBufMut: IoBuf { + /// Returns a raw mutable pointer to the vector’s buffer. + /// + /// This method is to be used by the `tokio-uring` runtime and it is not + /// expected for users to call it directly. + /// + /// The implementation must ensure that, while the `tokio-uring` runtime + /// owns the value, the pointer returned by `stable_mut_ptr` **does not** + /// change. + fn stable_mut_ptr(&mut self) -> *mut u8; + + /// Updates the number of initialized bytes. + /// + /// If the specified `pos` is greater than the value returned by + /// [`IoBuf::bytes_init`], it becomes the new water mark as returned by + /// `IoBuf::bytes_init`. + /// + /// # Safety + /// + /// The caller must ensure that all bytes starting at `stable_mut_ptr()` up + /// to `pos` are initialized and owned by the buffer. + unsafe fn set_init(&mut self, pos: usize); +} + +unsafe impl IoBufMut for Vec { + fn stable_mut_ptr(&mut self) -> *mut u8 { + self.as_mut_ptr() + } + + unsafe fn set_init(&mut self, init_len: usize) { + if self.len() < init_len { + self.set_len(init_len); + } + } +} + +#[cfg(feature = "bytes")] +unsafe impl IoBufMut for bytes::BytesMut { + fn stable_mut_ptr(&mut self) -> *mut u8 { + self.as_mut_ptr() + } + + unsafe fn set_init(&mut self, init_len: usize) { + if self.len() < init_len { + self.set_len(init_len); + } + } +} diff --git a/uring-common/src/lib.rs b/uring-common/src/lib.rs new file mode 100644 index 0000000..4925c24 --- /dev/null +++ b/uring-common/src/lib.rs @@ -0,0 +1,5 @@ +pub mod buf; +pub mod open_options; +pub mod open_options_io_uring_ext; + +pub use io_uring; diff --git a/uring-common/src/open_options.rs b/uring-common/src/open_options.rs new file mode 100644 index 0000000..2151e3e --- /dev/null +++ b/uring-common/src/open_options.rs @@ -0,0 +1,362 @@ +// Copyright (c) 2021 Carl Lerche +// +// Permission is hereby granted, free of charge, to any +// person obtaining a copy of this software and associated +// documentation files (the "Software"), to deal in the +// Software without restriction, including without +// limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of +// the Software, and to permit persons to whom the Software +// is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice +// shall be included in all copies or substantial portions +// of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +// ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +// TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +// SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. +// +// +// Based on tokio-uring.git:d5e90539bd6d1c518e848298564a098c300866bc + +use std::io; +use std::os::unix::fs::OpenOptionsExt; + +/// Options and flags which can be used to configure how a file is opened. +/// +/// This builder exposes the ability to configure how a [`File`] is opened and +/// what operations are permitted on the open file. The [`File::open`] and +/// [`File::create`] methods are aliases for commonly used options using this +/// builder. +/// +/// Generally speaking, when using `OpenOptions`, you'll first call +/// [`OpenOptions::new`], then chain calls to methods to set each option, then +/// call [`OpenOptions::open`], passing the path of the file you're trying to +/// open. This will give you a [`io::Result`] with a [`File`] inside that you +/// can further operate on. +/// +/// # Examples +/// +/// Opening a file to read: +/// +/// ```no_run +/// use tokio_uring::fs::OpenOptions; +/// +/// fn main() -> Result<(), Box> { +/// tokio_uring::start(async { +/// let file = OpenOptions::new() +/// .read(true) +/// .open("foo.txt") +/// .await?; +/// Ok(()) +/// }) +/// } +/// ``` +/// +/// Opening a file for both reading and writing, as well as creating it if it +/// doesn't exist: +/// +/// ```no_run +/// use tokio_uring::fs::OpenOptions; +/// +/// fn main() -> Result<(), Box> { +/// tokio_uring::start(async { +/// let file = OpenOptions::new() +/// .read(true) +/// .write(true) +/// .create(true) +/// .open("foo.txt") +/// .await?; +/// Ok(()) +/// }) +/// } +/// ``` +#[derive(Debug, Clone)] +pub struct OpenOptions { + read: bool, + write: bool, + append: bool, + truncate: bool, + create: bool, + create_new: bool, + pub(crate) mode: libc::mode_t, + pub(crate) custom_flags: libc::c_int, +} + +impl OpenOptions { + /// Creates a blank new set of options ready for configuration. + /// + /// All options are initially set to `false`. + /// + /// # Examples + /// + /// ```no_run + /// use tokio_uring::fs::OpenOptions; + /// + /// fn main() -> Result<(), Box> { + /// tokio_uring::start(async { + /// let file = OpenOptions::new() + /// .read(true) + /// .open("foo.txt") + /// .await?; + /// Ok(()) + /// }) + /// } + /// ``` + pub fn new() -> OpenOptions { + OpenOptions { + // generic + read: false, + write: false, + append: false, + truncate: false, + create: false, + create_new: false, + mode: 0o666, + custom_flags: 0, + } + } + + /// Sets the option for read access. + /// + /// This option, when true, will indicate that the file should be + /// `read`-able if opened. + /// + /// # Examples + /// + /// ```no_run + /// use tokio_uring::fs::OpenOptions; + /// + /// fn main() -> Result<(), Box> { + /// tokio_uring::start(async { + /// let file = OpenOptions::new() + /// .read(true) + /// .open("foo.txt") + /// .await?; + /// Ok(()) + /// }) + /// } + /// ``` + pub fn read(&mut self, read: bool) -> &mut OpenOptions { + self.read = read; + self + } + + /// Sets the option for write access. + /// + /// This option, when true, will indicate that the file should be + /// `write`-able if opened. + /// + /// If the file already exists, any write calls on it will overwrite its + /// contents, without truncating it. + /// + /// # Examples + /// + /// ```no_run + /// use tokio_uring::fs::OpenOptions; + /// + /// fn main() -> Result<(), Box> { + /// tokio_uring::start(async { + /// let file = OpenOptions::new() + /// .write(true) + /// .open("foo.txt") + /// .await?; + /// Ok(()) + /// }) + /// } + /// ``` + pub fn write(&mut self, write: bool) -> &mut OpenOptions { + self.write = write; + self + } + + /// Sets the option for the append mode. + /// + /// This option, when true, means that writes will append to a file instead + /// of overwriting previous contents. Note that setting + /// `.write(true).append(true)` has the same effect as setting only + /// `.append(true)`. + /// + /// For most filesystems, the operating system guarantees that all writes + /// are atomic: no writes get mangled because another process writes at the + /// same time. + /// + /// ## Note + /// + /// This function doesn't create the file if it doesn't exist. Use the + /// [`OpenOptions::create`] method to do so. + /// + /// # Examples + /// + /// ```no_run + /// use tokio_uring::fs::OpenOptions; + /// + /// fn main() -> Result<(), Box> { + /// tokio_uring::start(async { + /// let file = OpenOptions::new() + /// .append(true) + /// .open("foo.txt") + /// .await?; + /// Ok(()) + /// }) + /// } + /// ``` + pub fn append(&mut self, append: bool) -> &mut OpenOptions { + self.append = append; + self + } + + /// Sets the option for truncating a previous file. + /// + /// If a file is successfully opened with this option set it will truncate + /// the file to 0 length if it already exists. + /// + /// The file must be opened with write access for truncate to work. + /// + /// # Examples + /// + /// ```no_run + /// use tokio_uring::fs::OpenOptions; + /// + /// fn main() -> Result<(), Box> { + /// tokio_uring::start(async { + /// let file = OpenOptions::new() + /// .write(true) + /// .truncate(true) + /// .open("foo.txt") + /// .await?; + /// Ok(()) + /// }) + /// } + /// ``` + pub fn truncate(&mut self, truncate: bool) -> &mut OpenOptions { + self.truncate = truncate; + self + } + + /// Sets the option to create a new file, or open it if it already exists. + /// + /// In order for the file to be created, [`OpenOptions::write`] or + /// [`OpenOptions::append`] access must be used. + /// + /// # Examples + /// + /// ```no_run + /// use tokio_uring::fs::OpenOptions; + /// + /// fn main() -> Result<(), Box> { + /// tokio_uring::start(async { + /// let file = OpenOptions::new() + /// .write(true) + /// .create(true) + /// .open("foo.txt") + /// .await?; + /// Ok(()) + /// }) + /// } + /// ``` + pub fn create(&mut self, create: bool) -> &mut OpenOptions { + self.create = create; + self + } + + /// Sets the option to create a new file, failing if it already exists. + /// + /// No file is allowed to exist at the target location, also no (dangling) symlink. In this + /// way, if the call succeeds, the file returned is guaranteed to be new. + /// + /// This option is useful because it is atomic. Otherwise between checking + /// whether a file exists and creating a new one, the file may have been + /// created by another process (a TOCTOU race condition / attack). + /// + /// If `.create_new(true)` is set, [`.create()`] and [`.truncate()`] are + /// ignored. + /// + /// The file must be opened with write or append access in order to create + /// a new file. + /// + /// [`.create()`]: OpenOptions::create + /// [`.truncate()`]: OpenOptions::truncate + /// + /// # Examples + /// + /// ```no_run + /// use tokio_uring::fs::OpenOptions; + /// + /// fn main() -> Result<(), Box> { + /// tokio_uring::start(async { + /// let file = OpenOptions::new() + /// .write(true) + /// .create_new(true) + /// .open("foo.txt") + /// .await?; + /// Ok(()) + /// }) + /// } + /// ``` + pub fn create_new(&mut self, create_new: bool) -> &mut OpenOptions { + self.create_new = create_new; + self + } + + pub fn access_mode(&self) -> io::Result { + match (self.read, self.write, self.append) { + (true, false, false) => Ok(libc::O_RDONLY), + (false, true, false) => Ok(libc::O_WRONLY), + (true, true, false) => Ok(libc::O_RDWR), + (false, _, true) => Ok(libc::O_WRONLY | libc::O_APPEND), + (true, _, true) => Ok(libc::O_RDWR | libc::O_APPEND), + (false, false, false) => Err(io::Error::from_raw_os_error(libc::EINVAL)), + } + } + + pub fn creation_mode(&self) -> io::Result { + match (self.write, self.append) { + (true, false) => {} + (false, false) => { + if self.truncate || self.create || self.create_new { + return Err(io::Error::from_raw_os_error(libc::EINVAL)); + } + } + (_, true) => { + if self.truncate && !self.create_new { + return Err(io::Error::from_raw_os_error(libc::EINVAL)); + } + } + } + + Ok(match (self.create, self.truncate, self.create_new) { + (false, false, false) => 0, + (true, false, false) => libc::O_CREAT, + (false, true, false) => libc::O_TRUNC, + (true, true, false) => libc::O_CREAT | libc::O_TRUNC, + (_, _, true) => libc::O_CREAT | libc::O_EXCL, + }) + } +} + +impl Default for OpenOptions { + fn default() -> Self { + Self::new() + } +} + +impl OpenOptionsExt for OpenOptions { + fn mode(&mut self, mode: u32) -> &mut OpenOptions { + self.mode = mode; + self + } + + fn custom_flags(&mut self, flags: i32) -> &mut OpenOptions { + self.custom_flags = flags; + self + } +} diff --git a/uring-common/src/open_options_io_uring_ext.rs b/uring-common/src/open_options_io_uring_ext.rs new file mode 100644 index 0000000..edf07c9 --- /dev/null +++ b/uring-common/src/open_options_io_uring_ext.rs @@ -0,0 +1,37 @@ +// It's an ext trait to avoid changes in `open_options.rs`. +// +// See also: https://github.com/neondatabase/tokio-uring/pull/1 + +use std::io; + +use crate::open_options::OpenOptions; + +/// Extension trait to allow re-use of [`OpenOptions`] in other crates that +/// build on top of [`io_uring`]. +pub trait OpenOptionsIoUringExt { + /// Turn `self` into an [`::io_uring::opcode::OpenAt`] SQE for the given `path`. + /// + /// + /// # Safety + /// + /// The returned SQE stores the provided `path` pointer. + /// The caller must ensure that it remains valid until either the returned + /// SQE is dropped without being submitted, or if the SQE is submitted until + /// the corresponding completion is observed. + unsafe fn as_openat_sqe(&self, path: *const u8) -> io::Result; +} + +impl OpenOptionsIoUringExt for OpenOptions { + unsafe fn as_openat_sqe(&self, path: *const u8) -> io::Result { + use io_uring::{opcode, types}; + let flags = libc::O_CLOEXEC + | self.access_mode()? + | self.creation_mode()? + | (self.custom_flags & !libc::O_ACCMODE); + + Ok(opcode::OpenAt::new(types::Fd(libc::AT_FDCWD), path) + .flags(flags) + .mode(self.mode) + .build()) + } +}