diff --git a/src/fs/mod.rs b/src/fs/mod.rs index 89b392d2..2fc5ac15 100644 --- a/src/fs/mod.rs +++ b/src/fs/mod.rs @@ -20,3 +20,6 @@ mod statx; pub use statx::is_dir_regfile; pub use statx::statx; pub use statx::StatxBuilder; + +mod symlink; +pub use symlink::symlink; diff --git a/src/fs/symlink.rs b/src/fs/symlink.rs new file mode 100644 index 00000000..6e0f4e2b --- /dev/null +++ b/src/fs/symlink.rs @@ -0,0 +1,10 @@ +use crate::runtime::driver::op::Op; +use std::io; +use std::path::Path; + +/// Creates a new symbolic link on the filesystem. +/// The dst path will be a symbolic link pointing to the src path. +/// This is an async version of std::os::unix::fs::symlink. +pub async fn symlink, Q: AsRef>(src: P, dst: Q) -> io::Result<()> { + Op::symlink(src, dst)?.await +} diff --git a/src/io/mod.rs b/src/io/mod.rs index 6985bdd3..1afcef22 100644 --- a/src/io/mod.rs +++ b/src/io/mod.rs @@ -43,6 +43,8 @@ pub(crate) use socket::Socket; mod statx; +mod symlink; + mod unlink_at; mod util; diff --git a/src/io/symlink.rs b/src/io/symlink.rs new file mode 100644 index 00000000..8509dddc --- /dev/null +++ b/src/io/symlink.rs @@ -0,0 +1,45 @@ +use crate::runtime::driver::op::{Completable, CqeResult, Op}; +use crate::runtime::CONTEXT; + +use super::util::cstr; + +use std::ffi::CString; +use std::io; +use std::path::Path; + +pub(crate) struct Symlink { + pub(crate) _from: CString, + pub(crate) _to: CString, +} + +impl Op { + pub(crate) fn symlink, Q: AsRef>( + from: P, + to: Q, + ) -> io::Result> { + use io_uring::{opcode, types}; + + let _from = cstr(from.as_ref())?; + let _to = cstr(to.as_ref())?; + + CONTEXT.with(|x| { + x.handle().expect("Not in a runtime context").submit_op( + Symlink { _from, _to }, + |symlink| { + let from_ref = symlink._from.as_c_str().as_ptr(); + let to_ref = symlink._to.as_c_str().as_ptr(); + + opcode::SymlinkAt::new(types::Fd(libc::AT_FDCWD), from_ref, to_ref).build() + }, + ) + }) + } +} + +impl Completable for Symlink { + type Output = io::Result<()>; + + fn complete(self, cqe: CqeResult) -> Self::Output { + cqe.result.map(|_| ()) + } +} diff --git a/tests/fs_symlink.rs b/tests/fs_symlink.rs new file mode 100644 index 00000000..4e115afc --- /dev/null +++ b/tests/fs_symlink.rs @@ -0,0 +1,28 @@ +#[path = "../src/future.rs"] +#[allow(warnings)] +mod future; + +use std::io::Write; +use tokio_test::assert_ok; +use tokio_uring::fs; + +use tempfile::tempdir; +use tempfile::NamedTempFile; + +const TEST_PAYLOAD: &[u8] = b"I am data in the source file"; + +#[test] +fn test_create_symlink() { + tokio_uring::start(async { + let mut src_file = NamedTempFile::new().unwrap(); + src_file.write_all(TEST_PAYLOAD).unwrap(); + + let dst_enclosing_dir = tempdir().unwrap(); + + assert_ok!(fs::symlink(src_file.path(), dst_enclosing_dir.path().join("abc")).await); + + let content = std::fs::read(dst_enclosing_dir.path().join("abc")).unwrap(); + + assert_eq!(content, TEST_PAYLOAD); + }); +}