blown_fuse/ops/
dir.rs

1use std::{
2    convert::Infallible,
3    ffi::{CStr, OsStr},
4    marker::PhantomData,
5    os::unix::ffi::OsStrExt,
6};
7
8use crate::{
9    io::{Entry, EntryType, Ino, Interruptible, Known, Stat, Ttl},
10    proto,
11    sealed::Sealed,
12    Done, Errno, Operation, Reply, Request,
13};
14
15use super::{
16    c_to_os, make_entry,
17    traits::{
18        ReplyBuffered, ReplyKnown, ReplyNotFound, RequestHandle, RequestName, RequestOffset,
19        RequestSize,
20    },
21    FromRequest,
22};
23
24use bytemuck::{bytes_of, Zeroable};
25use bytes::BufMut;
26use nix::sys::stat::SFlag;
27
28pub enum Lookup {}
29pub enum Readdir {}
30pub struct BufferedReaddir<B>(Infallible, PhantomData<B>);
31
32pub trait ReplyFound<'o>: ReplyKnown<'o> {
33    fn not_found_for(reply: Reply<'o, Self>, ttl: Ttl) -> Done<'o>;
34}
35
36pub trait ReplyEntries<'o>: Operation<'o> {
37    fn entry(reply: Reply<'o, Self>, entry: Entry<impl Known>) -> Interruptible<'o, Self, ()>;
38    fn end(reply: Reply<'o, Self>) -> Done<'o>;
39}
40
41pub struct ReaddirState<B> {
42    max_read: usize,
43    is_plus: bool,
44    buffer: B,
45}
46
47impl Sealed for Lookup {}
48impl Sealed for Readdir {}
49impl<B> Sealed for BufferedReaddir<B> {}
50
51impl<'o> Operation<'o> for Lookup {
52    type RequestBody = &'o CStr; // name()
53    type ReplyState = ();
54}
55
56impl<'o> Operation<'o> for Readdir {
57    type RequestBody = proto::OpcodeSelect<
58        &'o proto::ReaddirPlusIn,
59        &'o proto::ReaddirIn,
60        { proto::Opcode::ReaddirPlus as u32 },
61    >;
62
63    type ReplyState = ReaddirState<()>;
64}
65
66impl<'o, B> Operation<'o> for BufferedReaddir<B> {
67    type RequestBody = (); // Never actually created
68    type ReplyState = ReaddirState<B>;
69}
70
71impl<'o> RequestName<'o> for Lookup {
72    fn name<'a>(request: &'a Request<'o, Self>) -> &'a OsStr {
73        c_to_os(request.body)
74    }
75}
76
77impl<'o> ReplyNotFound<'o> for Lookup {
78    fn not_found(reply: Reply<'o, Self>) -> Done<'o> {
79        reply.fail(Errno::ENOENT)
80    }
81}
82
83impl<'o> ReplyKnown<'o> for Lookup {}
84
85impl<'o> ReplyFound<'o> for Lookup {
86    fn not_found_for(reply: Reply<'o, Self>, ttl: Ttl) -> Done<'o> {
87        reply.single(&make_entry(
88            (Ino::NULL, ttl),
89            (Zeroable::zeroed(), Ttl::NULL),
90        ))
91    }
92}
93
94impl<'o> RequestHandle<'o> for Readdir {
95    fn handle(request: &Request<'o, Self>) -> u64 {
96        readdir_read_in(request).fh
97    }
98}
99
100impl<'o> RequestOffset<'o> for Readdir {
101    fn offset(request: &Request<'o, Self>) -> u64 {
102        readdir_read_in(request).offset
103    }
104}
105
106impl<'o> RequestSize<'o> for Readdir {
107    fn size(request: &Request<'o, Self>) -> u32 {
108        readdir_read_in(request).size
109    }
110}
111
112impl<'o, B> ReplyBuffered<'o, B> for Readdir
113where
114    B: BufMut + AsRef<[u8]>,
115{
116    type Buffered = BufferedReaddir<B>;
117
118    fn buffered(reply: Reply<'o, Self>, buffer: B) -> Reply<'o, Self::Buffered> {
119        assert!(buffer.as_ref().is_empty());
120
121        let ReaddirState {
122            max_read,
123            is_plus,
124            buffer: (),
125        } = reply.state;
126
127        Reply {
128            session: reply.session,
129            unique: reply.unique,
130            state: ReaddirState {
131                max_read,
132                is_plus,
133                buffer,
134            },
135        }
136    }
137}
138
139impl<'o, B: BufMut + AsRef<[u8]>> ReplyEntries<'o> for BufferedReaddir<B> {
140    fn entry(mut reply: Reply<'o, Self>, entry: Entry<impl Known>) -> Interruptible<'o, Self, ()> {
141        let entry_header_len = if reply.state.is_plus {
142            std::mem::size_of::<proto::DirentPlus>()
143        } else {
144            std::mem::size_of::<proto::Dirent>()
145        };
146
147        let name = entry.name.as_bytes();
148        let padding_len = dirent_pad_bytes(entry_header_len + name.len());
149
150        let buffer = &mut reply.state.buffer;
151        let remaining = buffer
152            .remaining_mut()
153            .min(reply.state.max_read - buffer.as_ref().len());
154
155        let record_len = entry_header_len + name.len() + padding_len;
156        if remaining < record_len {
157            if buffer.as_ref().is_empty() {
158                log::error!("Buffer for readdir req #{} is too small", reply.unique);
159                return Interruptible::Interrupted(reply.fail(Errno::ENOBUFS));
160            }
161
162            return Interruptible::Interrupted(reply.end());
163        }
164
165        let inode = entry.inode.inode();
166        let entry_type = match inode.inode_type() {
167            EntryType::Fifo => SFlag::S_IFIFO,
168            EntryType::CharacterDevice => SFlag::S_IFCHR,
169            EntryType::Directory => SFlag::S_IFDIR,
170            EntryType::BlockDevice => SFlag::S_IFBLK,
171            EntryType::File => SFlag::S_IFREG,
172            EntryType::Symlink => SFlag::S_IFLNK,
173            EntryType::Socket => SFlag::S_IFSOCK,
174        };
175
176        let ino = inode.ino();
177        let dirent = proto::Dirent {
178            ino: ino.as_raw(),
179            off: entry.offset,
180            namelen: name.len().try_into().unwrap(),
181            entry_type: entry_type.bits() >> 12,
182        };
183
184        enum Ent {
185            Dirent(proto::Dirent),
186            DirentPlus(proto::DirentPlus),
187        }
188
189        let ent = if reply.state.is_plus {
190            let (attrs, attrs_ttl) = inode.attrs();
191            let attrs = attrs.finish(inode);
192            let entry_out = make_entry((ino, entry.ttl), (attrs, attrs_ttl));
193
194            if name != ".".as_bytes() && name != "..".as_bytes() {
195                entry.inode.unveil();
196            }
197
198            Ent::DirentPlus(proto::DirentPlus { entry_out, dirent })
199        } else {
200            Ent::Dirent(dirent)
201        };
202
203        let entry_header = match &ent {
204            Ent::Dirent(dirent) => bytes_of(dirent),
205            Ent::DirentPlus(dirent_plus) => bytes_of(dirent_plus),
206        };
207
208        buffer.put_slice(entry_header);
209        buffer.put_slice(name);
210        buffer.put_slice(&[0; 7][..padding_len]);
211
212        if remaining - record_len >= entry_header.len() + (1 << proto::DIRENT_ALIGNMENT_BITS) {
213            Interruptible::Completed(reply, ())
214        } else {
215            Interruptible::Interrupted(reply.end())
216        }
217    }
218
219    fn end(reply: Reply<'o, Self>) -> Done<'o> {
220        reply.inner(|reply| reply.state.buffer.as_ref())
221    }
222}
223
224impl<'o> FromRequest<'o, Readdir> for ReaddirState<()> {
225    fn from_request(request: &Request<'o, Readdir>) -> Self {
226        ReaddirState {
227            max_read: request.size() as usize,
228            is_plus: matches!(request.body, proto::OpcodeSelect::Match(_)),
229            buffer: (),
230        }
231    }
232}
233
234fn dirent_pad_bytes(entry_len: usize) -> usize {
235    const ALIGN_MASK: usize = (1 << proto::DIRENT_ALIGNMENT_BITS) - 1;
236    ((entry_len + ALIGN_MASK) & !ALIGN_MASK) - entry_len
237}
238
239fn readdir_read_in<'a>(request: &'a Request<'_, Readdir>) -> &'a proto::ReadIn {
240    use proto::OpcodeSelect::*;
241
242    match &request.body {
243        Match(readdir_plus) => &readdir_plus.read_in,
244        Alt(readdir) => &readdir.read_in,
245    }
246}