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; 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 = (); 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}