Skip to content

Commit 16ec240

Browse files
BerrysoftCopilot
andauthored
feat(fs): dirfd support (#703)
* feat(fs,unix): dirfd support * test(fs): test dir * feat(fs,win): dirfd support * fix(fs,win): metadata types * fix(fs,win): nightly features * feat(fs,win): rename & hard_link * feat(fs,win): dirfd symlinks * fix(fs): apply suggestions Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * fix(fs): don't remove the dir itself * fix(fs,unix): check absolute path * fix(fs,win): custom flags * fix(fs): use correct absolute path * test(fs): unwrap_err instead of should_panic * feat(fs): remove relative path checks * feat(fs): make "dir" optional * feat(fs,win): reduce syscalls for `set_permissions` * test(fs): remove `test_absolute` --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent 8cf1b4d commit 16ec240

23 files changed

Lines changed: 1218 additions & 65 deletions

File tree

compio-driver/src/op.rs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,38 @@ impl<S, F, D> IntoInner for AsyncifyFd<S, F, D> {
234234
}
235235
}
236236

237+
pin_project! {
238+
/// Spawn a blocking function with two file descriptors in the thread pool.
239+
pub struct AsyncifyFd2<S1, S2, F, D> {
240+
pub(crate) fd1: SharedFd<S1>,
241+
pub(crate) fd2: SharedFd<S2>,
242+
pub(crate) f: Option<F>,
243+
pub(crate) data: Option<D>,
244+
_p: PhantomPinned,
245+
}
246+
}
247+
248+
impl<S1, S2, F, D> AsyncifyFd2<S1, S2, F, D> {
249+
/// Create [`AsyncifyFd2`].
250+
pub fn new(fd1: SharedFd<S1>, fd2: SharedFd<S2>, f: F) -> Self {
251+
Self {
252+
fd1,
253+
fd2,
254+
f: Some(f),
255+
data: None,
256+
_p: PhantomPinned,
257+
}
258+
}
259+
}
260+
261+
impl<S1, S2, F, D> IntoInner for AsyncifyFd2<S1, S2, F, D> {
262+
type Inner = D;
263+
264+
fn into_inner(mut self) -> Self::Inner {
265+
self.data.take().expect("the data should not be None")
266+
}
267+
}
268+
237269
/// Close the file fd.
238270
pub struct CloseFile {
239271
pub(crate) fd: ManuallyDrop<OwnedFd>,

compio-driver/src/sys/iocp/op.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,30 @@ unsafe impl<
154154
}
155155
}
156156

157+
unsafe impl<
158+
S1,
159+
S2,
160+
D: std::marker::Send + 'static,
161+
F: (FnOnce(&S1, &S2) -> BufResult<usize, D>) + std::marker::Send + 'static,
162+
> OpCode for AsyncifyFd2<S1, S2, F, D>
163+
{
164+
fn op_type(&self) -> OpType {
165+
OpType::Blocking
166+
}
167+
168+
unsafe fn operate(self: Pin<&mut Self>, _optr: *mut OVERLAPPED) -> Poll<io::Result<usize>> {
169+
// SAFETY: self won't be moved
170+
let this = self.project();
171+
let f = this
172+
.f
173+
.take()
174+
.expect("the operate method could only be called once");
175+
let BufResult(res, data) = f(this.fd1, this.fd2);
176+
*this.data = Some(data);
177+
Poll::Ready(res)
178+
}
179+
}
180+
157181
unsafe impl OpCode for CloseFile {
158182
fn op_type(&self) -> OpType {
159183
OpType::Blocking

compio-driver/src/sys/iour/op.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,29 @@ unsafe impl<
6161
}
6262
}
6363

64+
unsafe impl<
65+
S1,
66+
S2,
67+
D: std::marker::Send + 'static,
68+
F: (FnOnce(&S1, &S2) -> BufResult<usize, D>) + std::marker::Send + 'static,
69+
> OpCode for AsyncifyFd2<S1, S2, F, D>
70+
{
71+
fn create_entry(self: Pin<&mut Self>) -> OpEntry {
72+
OpEntry::Blocking
73+
}
74+
75+
fn call_blocking(self: Pin<&mut Self>) -> std::io::Result<usize> {
76+
let this = self.project();
77+
let f = this
78+
.f
79+
.take()
80+
.expect("the operate method could only be called once");
81+
let BufResult(res, data) = f(this.fd1, this.fd2);
82+
*this.data = Some(data);
83+
res
84+
}
85+
}
86+
6487
unsafe impl<S: AsFd> OpCode for OpenFile<S> {
6588
fn create_entry(self: Pin<&mut Self>) -> OpEntry {
6689
opcode::OpenAt::new(Fd(self.dirfd.as_fd().as_raw_fd()), self.path.as_ptr())

compio-driver/src/sys/poll/op.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,29 @@ unsafe impl<
6464
}
6565
}
6666

67+
unsafe impl<
68+
S1,
69+
S2,
70+
D: std::marker::Send + 'static,
71+
F: (FnOnce(&S1, &S2) -> BufResult<usize, D>) + std::marker::Send + 'static,
72+
> OpCode for AsyncifyFd2<S1, S2, F, D>
73+
{
74+
fn pre_submit(self: Pin<&mut Self>) -> io::Result<Decision> {
75+
Ok(Decision::Blocking)
76+
}
77+
78+
fn operate(self: Pin<&mut Self>) -> Poll<io::Result<usize>> {
79+
let this = self.project();
80+
let f = this
81+
.f
82+
.take()
83+
.expect("the operate method could only be called once");
84+
let BufResult(res, data) = f(this.fd1, this.fd2);
85+
*this.data = Some(data);
86+
Poll::Ready(res)
87+
}
88+
}
89+
6790
unsafe impl<S: AsFd> OpCode for OpenFile<S> {
6891
fn pre_submit(self: Pin<&mut Self>) -> io::Result<Decision> {
6992
Ok(Decision::Blocking)

compio-driver/src/sys/stub/op.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,15 @@ impl<
2424
{
2525
}
2626

27+
impl<
28+
S1,
29+
S2,
30+
D: std::marker::Send + 'static,
31+
F: (FnOnce(&S1, &S2) -> BufResult<usize, D>) + std::marker::Send + 'static,
32+
> OpCode for AsyncifyFd2<S1, S2, F, D>
33+
{
34+
}
35+
2736
impl<S: AsFd> OpCode for OpenFile<S> {}
2837

2938
impl OpCode for CloseFile {}

compio-fs/Cargo.toml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ windows-sys = { workspace = true, features = [
3535
"Win32_System_SystemServices",
3636
] }
3737

38+
cap-primitives = { version = "4.0.0", optional = true }
39+
3840
# Windows specific dev dependencies
3941
[target.'cfg(windows)'.dev-dependencies]
4042
windows-sys = { workspace = true, features = ["Win32_Security_Authorization"] }
@@ -64,6 +66,12 @@ nix = { workspace = true, features = ["fs"] }
6466
compio-net = { workspace = true }
6567

6668
[features]
69+
dir = ["dep:cap-primitives"]
70+
6771
read_buf = ["compio-buf/read_buf", "compio-io/read_buf"]
6872
windows_by_handle = []
6973
nightly = ["read_buf", "windows_by_handle"]
74+
75+
[[test]]
76+
name = "dir"
77+
required-features = ["dir"]

compio-fs/build.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,6 @@ fn main() {
1010
solarish: { any(target_os = "illumos", target_os = "solaris") },
1111
gnulinux: { all(target_os = "linux", target_env = "gnu") },
1212
linux_all: { any(target_os = "linux", target_os = "android") },
13+
dirfd: { feature = "dir" },
1314
}
1415
}

compio-fs/src/dirfd/mod.rs

Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
use std::{io, path::Path};
2+
3+
use compio_buf::{BufResult, IoBuf, buf_try};
4+
use compio_io::{AsyncReadAtExt, AsyncWriteAtExt};
5+
6+
use crate::{DirBuilder, File, Metadata, OpenOptions};
7+
8+
#[cfg(unix)]
9+
#[path = "unix.rs"]
10+
mod sys;
11+
12+
#[cfg(windows)]
13+
#[path = "windows.rs"]
14+
mod sys;
15+
16+
/// A reference to an open directory on a filesystem.
17+
///
18+
/// ## Platform specific
19+
/// * Windows: the operations are forwarded to `cap-primitives`. They will treat
20+
/// self as the root of the filesystem.
21+
/// * Unix: the operatiosn are forwarded to syscalls directly. They don't limit
22+
/// the path to be under self. If the path is absolute, the directory
23+
/// represented by self will be ignored.
24+
#[derive(Debug, Clone)]
25+
pub struct Dir {
26+
inner: sys::Dir,
27+
}
28+
29+
impl Dir {
30+
/// Opens a directory at the specified path and returns a reference to it.
31+
pub async fn open(path: impl AsRef<Path>) -> io::Result<Self> {
32+
Ok(Dir {
33+
inner: sys::Dir::open(path).await?,
34+
})
35+
}
36+
37+
/// Opens a file at `path` with the options specified by `options`.
38+
pub async fn open_file_with(
39+
&self,
40+
path: impl AsRef<Path>,
41+
options: &OpenOptions,
42+
) -> io::Result<File> {
43+
self.inner.open_file_with(path, options).await
44+
}
45+
46+
/// Attempts to open a file in read-only mode.
47+
pub async fn open_file(&self, path: impl AsRef<Path>) -> io::Result<File> {
48+
self.open_file_with(path, OpenOptions::new().read(true))
49+
.await
50+
}
51+
52+
/// Opens a file in write-only mode.
53+
pub async fn create_file(&self, path: impl AsRef<Path>) -> io::Result<File> {
54+
self.open_file_with(
55+
path,
56+
OpenOptions::new().write(true).create(true).truncate(true),
57+
)
58+
.await
59+
}
60+
61+
/// Attempts to open a directory.
62+
pub async fn open_dir(&self, path: impl AsRef<Path>) -> io::Result<Self> {
63+
Ok(Self {
64+
inner: self.inner.open_dir(path).await?,
65+
})
66+
}
67+
68+
/// Creates the specified directory with the options configured in this
69+
/// builder.
70+
pub async fn create_dir_with(
71+
&self,
72+
path: impl AsRef<Path>,
73+
builder: &DirBuilder,
74+
) -> io::Result<()> {
75+
self.inner.create_dir_with(path, builder).await
76+
}
77+
78+
/// Creates a new, empty directory at the provided path.
79+
pub async fn create_dir(&self, path: impl AsRef<Path>) -> io::Result<()> {
80+
self.create_dir_with(path, &DirBuilder::new()).await
81+
}
82+
83+
/// Recursively create a directory and all of its parent components if they
84+
/// are missing.
85+
pub async fn create_dir_all(&self, path: impl AsRef<Path>) -> io::Result<()> {
86+
self.create_dir_with(path, DirBuilder::new().recursive(true))
87+
.await
88+
}
89+
90+
/// Queries metadata about the underlying directory.
91+
pub async fn dir_metadata(&self) -> io::Result<Metadata> {
92+
self.inner.dir_metadata().await
93+
}
94+
95+
/// Given a path, query the file system to get information about a file,
96+
/// directory, etc.
97+
pub async fn metadata(&self, path: impl AsRef<Path>) -> io::Result<Metadata> {
98+
self.inner.metadata(path).await
99+
}
100+
101+
/// Query the metadata about a file without following symlinks.
102+
pub async fn symlink_metadata(&self, path: impl AsRef<Path>) -> io::Result<Metadata> {
103+
self.inner.symlink_metadata(path).await
104+
}
105+
106+
/// Creates a new hard link on a filesystem.
107+
pub async fn hard_link(
108+
&self,
109+
source: impl AsRef<Path>,
110+
target_dir: &Self,
111+
target: impl AsRef<Path>,
112+
) -> io::Result<()> {
113+
self.inner
114+
.hard_link(source, &target_dir.inner, target)
115+
.await
116+
}
117+
118+
/// Creates a new symbolic link on a filesystem.
119+
///
120+
/// The `original` argument provides the target of the symlink. The `link`
121+
/// argument provides the name of the created symlink.
122+
///
123+
/// Despite the argument ordering, `original` is not resolved relative to
124+
/// self here. `link` is resolved relative to self, and `original` is
125+
/// not resolved within this function.
126+
#[cfg(unix)]
127+
pub async fn symlink(
128+
&self,
129+
original: impl AsRef<Path>,
130+
link: impl AsRef<Path>,
131+
) -> io::Result<()> {
132+
self.inner.symlink(original, link).await
133+
}
134+
135+
/// Creates a new file symbolic link on a filesystem.
136+
///
137+
/// The `original` argument provides the target of the symlink. The `link`
138+
/// argument provides the name of the created symlink.
139+
///
140+
/// Despite the argument ordering, `original` is not resolved relative to
141+
/// self here. `link` is resolved relative to self, and `original` is
142+
/// not resolved within this function.
143+
#[cfg(windows)]
144+
pub async fn symlink_file(
145+
&self,
146+
original: impl AsRef<Path>,
147+
link: impl AsRef<Path>,
148+
) -> io::Result<()> {
149+
self.inner.symlink_file(original, link).await
150+
}
151+
152+
/// Creates a new directory symlink on a filesystem.
153+
///
154+
/// The `original` argument provides the target of the symlink. The `link`
155+
/// argument provides the name of the created symlink.
156+
///
157+
/// Despite the argument ordering, `original` is not resolved relative to
158+
/// self here. `link` is resolved relative to self, and `original` is
159+
/// not resolved within this function.
160+
#[cfg(windows)]
161+
pub async fn symlink_dir(
162+
&self,
163+
original: impl AsRef<Path>,
164+
link: impl AsRef<Path>,
165+
) -> io::Result<()> {
166+
self.inner.symlink_dir(original, link).await
167+
}
168+
169+
/// Rename a file or directory to a new name, replacing the original file if
170+
/// it already exists.
171+
pub async fn rename(
172+
&self,
173+
from: impl AsRef<Path>,
174+
to_dir: &Self,
175+
to: impl AsRef<Path>,
176+
) -> io::Result<()> {
177+
self.inner.rename(from, &to_dir.inner, to).await
178+
}
179+
180+
/// Removes a file from a filesystem.
181+
pub async fn remove_file(&self, path: impl AsRef<Path>) -> io::Result<()> {
182+
self.inner.remove_file(path).await
183+
}
184+
185+
/// Removes an empty directory.
186+
pub async fn remove_dir(&self, path: impl AsRef<Path>) -> io::Result<()> {
187+
self.inner.remove_dir(path).await
188+
}
189+
190+
/// Read the entire contents of a file into a bytes vector.
191+
pub async fn read(&self, path: impl AsRef<Path>) -> io::Result<Vec<u8>> {
192+
let file = self.open_file(path).await?;
193+
let BufResult(res, buf) = file.read_to_end_at(Vec::new(), 0).await;
194+
res?;
195+
Ok(buf)
196+
}
197+
198+
/// Write a buffer as the entire contents of a file.
199+
pub async fn write<B: IoBuf>(&self, path: impl AsRef<Path>, buf: B) -> BufResult<(), B> {
200+
let (mut file, buf) = buf_try!(self.create_file(path).await, buf);
201+
file.write_all_at(buf, 0).await
202+
}
203+
}
204+
205+
compio_driver::impl_raw_fd!(Dir, std::fs::File, inner);

0 commit comments

Comments
 (0)