Skip to content

Commit

Permalink
Implement dup and O_TMPFILE (#613)
Browse files Browse the repository at this point in the history
Make it possible to create an unnamed temporary file and to dup a
DmaFile.

Co-authored-by: Glauber Costa <[email protected]>
  • Loading branch information
vlovich and glommer authored Oct 15, 2023
1 parent ea6d236 commit b0eb0e2
Show file tree
Hide file tree
Showing 3 changed files with 160 additions and 4 deletions.
107 changes: 107 additions & 0 deletions glommio/src/io/dma_file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,66 @@ impl DmaFile {
OpenOptions::new().read(true).dma_open(path.as_ref()).await
}

/// Creates a duplicate instance pointing to the same file descriptor as self.
///
/// <p style="background:rgba(255,181,77,0.16);padding:0.75em;">
/// <strong>Warning:</strong> If the file has been opened with `append`,
/// then the position for writes will get ignored and the buffer will be written at
/// the current end of file. See the [man page] for `O_APPEND`. All dup'ed files
/// will share the same offset (i.e. writes to one will affect the other).
/// </p>
///
/// # Examples
/// ```no_run
/// use glommio::{
/// LocalExecutor,
/// io::{
/// OpenOptions,
/// DmaBuffer,
/// }
/// };
///
/// fn populate(buf: &mut DmaBuffer) {
/// buf.as_bytes_mut()[0..5].copy_from_slice(b"hello");
/// }
///
/// let ex = LocalExecutor::default();
/// ex.run(async {
/// // A new anonymous file is created within `some_directory/`.
/// let file = OpenOptions::new()
/// .create_new(true)
/// .read(true)
/// .write(true)
/// .tmpfile(true)
/// .dma_open("some_directory")
/// .await
/// .unwrap();
///
/// let file2 = file.dup().unwrap();
///
/// let mut buf = file.alloc_dma_buffer(4096);
/// // Write some data into the buffer.
/// populate(&mut buf);
///
/// let written = file.write_at(buf, 0).await.unwrap();
/// assert_eq!(written, 4096);
/// file.close().await.unwrap();
///
/// let read = file2.read_at_aligned(0, 4096).await.unwrap();
/// assert_eq!(read.len(), 4096);
/// assert_eq!(&read[0..6], b"hello\0");
/// });
/// ```
pub fn dup(&self) -> Result<Self> {
Ok(Self {
file: enhanced_try!(self.file.dup(), "Duplicating", self.file)?,
o_direct_alignment: self.o_direct_alignment,
max_sectors_size: self.max_sectors_size,
max_segment_size: self.max_segment_size,
pollable: self.pollable,
})
}

/// Write the buffer in `buf` to a specific position in the file.
///
/// It is expected that the buffer and the position be properly aligned
Expand Down Expand Up @@ -1264,4 +1324,51 @@ pub(crate) mod test {

assert_eq!(*buf1, *buf2);
});

dma_file_test!(dup, path, _k, {
fn populate(buf: &mut DmaBuffer) {
buf.as_bytes_mut()[0..5].copy_from_slice(b"hello");
buf.as_bytes_mut()[5..].fill(0);
}

// A new anonymous file is created within `some_directory/`.
let file = OpenOptions::new()
.create_new(true)
.read(true)
.write(true)
.tmpfile(true)
.dma_open(path)
.await
.unwrap();

let file2 = file.dup().unwrap();
let buffer_size = file.o_direct_alignment.try_into().unwrap();
let mut buf = file.alloc_dma_buffer(buffer_size);
// Write some data into the buffer.
populate(&mut buf);

let written = file.write_at(buf, 0).await.unwrap();
assert_eq!(written, buffer_size);
file.close().await.unwrap();

let read = file2.read_at_aligned(0, buffer_size).await.unwrap();
assert_eq!(read.len(), buffer_size);
assert_eq!(
&read[0..6],
b"hello\0",
"{}",
String::from_utf8_lossy(&read[0..6])
);
});

dma_file_test!(tmpfile_fails_if_not_writable, path, _k, {
OpenOptions::new()
.create_new(true)
.read(true)
.write(false)
.tmpfile(true)
.dma_open(path)
.await
.expect_err("O_TMPFILE requires opening with write permissions");
});
}
20 changes: 20 additions & 0 deletions glommio/src/io/glommio_file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,26 @@ impl GlommioFile {
Ok(file)
}

pub(crate) fn dup(&self) -> io::Result<Self> {
let reactor = crate::executor().reactor();

let duped = Self {
file: Some(nix::unistd::dup(self.file.unwrap())?),
path: self.path.clone(),
inode: self.inode,
dev_major: self.dev_major,
dev_minor: self.dev_minor,
reactor: Rc::downgrade(&reactor),
scheduler: RefCell::new(None),
};

if self.scheduler.borrow().is_some() {
duped.attach_scheduler();
}

Ok(duped)
}

pub(super) fn attach_scheduler(&self) {
if self.scheduler.borrow().is_none() {
self.scheduler.replace(Some(
Expand Down
37 changes: 33 additions & 4 deletions glommio/src/io/open_options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ pub struct OpenOptions {
truncate: bool,
create: bool,
create_new: bool,
tmpfile: bool,
tmpfile_linkable: bool,
// system-specific
pub(super) custom_flags: libc::c_int,
pub(super) mode: libc::mode_t,
Expand All @@ -46,6 +48,8 @@ impl OpenOptions {
truncate: false,
create: false,
create_new: false,
tmpfile: false,
tmpfile_linkable: false,
// system-specific
custom_flags: 0,
// previously, we defaulted to 0o644, but 0o666 is used by libstd
Expand Down Expand Up @@ -134,6 +138,25 @@ impl OpenOptions {
self
}

/// Sets the option to create a new unnamed temporary file if the operating
/// system supports it. The path provided to [`dma_open`](#method.dma_open) is the
/// path of the directory to parent the file. This also requires [`write`](#method.write)
/// to have been set. See [`tmpfile_linkable`](#method.tmpfile_linkable) if you want to
/// be able to later create a name for this file using `linkat` (not yet implemented).
pub fn tmpfile(&mut self, tmpfile: bool) -> &mut Self {
self.tmpfile = tmpfile;
self
}

/// When [`tmpfile`](#method.tmpfile) is set to true, this controls whether the temporary file
/// may be put into the filesystem as a named file at a later date using `linkat`.
/// For now, since `linkat` is not implemented within glommio, you'll need to link
/// it by hand.
pub fn tmpfile_linkable(&mut self, linkable: bool) -> &mut Self {
self.tmpfile_linkable = linkable;
self
}

/// Pass custom flags to the flags' argument of `open_at`.
pub fn custom_flags(&mut self, flags: i32) -> &mut Self {
self.custom_flags = flags;
Expand Down Expand Up @@ -161,17 +184,23 @@ impl OpenOptions {
match (self.write, self.append) {
(true, false) => {}
(false, false) => {
if self.truncate || self.create || self.create_new {
if self.truncate || self.create || self.create_new || self.tmpfile {
return Err(io::Error::from_raw_os_error(libc::EINVAL).into());
}
}
(_, true) => {
if self.truncate && !self.create_new {
if self.truncate && !(self.create_new || self.tmpfile) {
return Err(io::Error::from_raw_os_error(libc::EINVAL).into());
}
}
}

match (self.tmpfile, self.tmpfile_linkable) {
(false, _) => (),
(true, false) => return Ok(libc::O_EXCL | libc::O_TMPFILE),
(true, true) => return Ok(libc::O_TMPFILE),
}

Ok(match (self.create, self.truncate, self.create_new) {
(false, false, false) => 0,
(true, false, false) => libc::O_CREAT,
Expand All @@ -187,7 +216,7 @@ impl OpenOptions {
DmaFile::open_with_options(
-1_i32,
path.as_ref(),
if self.create || self.create_new {
if self.create || self.create_new || self.tmpfile {
"Creating"
} else {
"Opening"
Expand All @@ -203,7 +232,7 @@ impl OpenOptions {
BufferedFile::open_with_options(
-1_i32,
path.as_ref(),
if self.create || self.create_new {
if self.create || self.create_new || self.tmpfile {
"Creating"
} else {
"Opening"
Expand Down

0 comments on commit b0eb0e2

Please sign in to comment.