1use smb_server_proto::types::Status;
5use smb_server_proto_smb1::consts;
6use smb_server_proto_smb1::create as create_codec;
7use smb_server_proto_smb1::header::RespBody;
8use smb_server_proto_smb1::misc;
9use smb_server_proto_smb1::rw::{self, ReadReq, WriteReq};
10use smb_server_vfs::SetOp;
11
12use crate::cmds::{IoCtx, share_vfs};
13use crate::dispatch::ReqView;
14use crate::state::next_fid;
15
16pub async fn query_disk(
18 io: &mut IoCtx<'_>,
19 req: &ReqView<'_>,
20 bodies: &mut Vec<RespBody>,
21) -> Result<Status, Status> {
22 let vfs = share_vfs(io, req.hdr.tid);
23 let (total, free, bps, spc) = vfs.query_disk().await.map_err(vfs_err)?;
24 let info = misc::QueryDiskInfo {
25 total_units: total,
26 free_units: free,
27 bps,
28 spc,
29 };
30 bodies.push(RespBody::new(
31 consts::COM_QUERY_INFORMATION_DISK,
32 info.encode(),
33 Vec::new(),
34 ));
35 Ok(Status::SUCCESS)
36}
37
38pub async fn nt_create(
40 io: &mut IoCtx<'_>,
41 req: &ReqView<'_>,
42 bodies: &mut Vec<RespBody>,
43) -> Result<Status, Status> {
44 let is_ipc = io
45 .conn
46 .trees
47 .get(&req.hdr.tid)
48 .map(|n| n == "ipc$")
49 .unwrap_or(false);
50 if is_ipc {
51 return Err(Status::OBJECT_NAME_NOT_FOUND);
52 }
53 let creq = create_codec::NtCreateReq::parse(
54 req.words,
55 req.data,
56 req.unicode(),
57 req.bc_off_abs + smb_server_proto_smb1::consts::WORD_LEN,
58 )
59 .map_err(|_| Status::INVALID_PARAMETER)?;
60
61 let base_rel = if creq.root_fid != 0 && creq.root_fid <= u16::MAX as u32 {
62 io.conn
63 .handles
64 .get(&(creq.root_fid as u16))
65 .map(|h| h.path.clone())
66 .ok_or(Status::INVALID_HANDLE)?
67 } else {
68 String::new()
69 };
70 let rel = join_rel(&base_rel, &creq.name);
71
72 let want_dir =
73 creq.create_options & smb_server_proto_smb1::consts::create_options::FILE_DIRECTORY_FILE != 0;
74 let no_dir =
75 creq.create_options & smb_server_proto_smb1::consts::create_options::FILE_NON_DIRECTORY_FILE != 0;
76 let delete_on_close =
77 creq.create_options & smb_server_proto_smb1::consts::create_options::FILE_DELETE_ON_CLOSE != 0;
78
79 let vfs = share_vfs(io, req.hdr.tid);
80 let (mut open, meta, action) = vfs
81 .create(
82 &rel,
83 want_dir,
84 creq.desired_access,
85 creq.disposition,
86 creq.create_options,
87 creq.ext_attrs,
88 )
89 .await
90 .map_err(vfs_err)?;
91
92 if no_dir && open.is_dir {
93 return Err(Status::FILE_IS_A_DIRECTORY);
94 }
95 open.delete_on_close |= delete_on_close;
96
97 let fid = next_fid();
98 io.conn.handles.insert(fid, open);
99
100 let body = create_codec::build_response(
101 fid,
102 action,
103 meta.times,
104 meta.attrs.0,
105 meta.alloc,
106 meta.eof,
107 meta.is_dir,
108 );
109 tracing::debug!(
110 fid = format!("{:#06x}", fid),
111 eof = meta.eof,
112 dir = meta.is_dir,
113 "nt_create"
114 );
115 metrics::counter!("smb_creates_total").increment(1);
116 bodies.push(RespBody::new(consts::COM_NT_CREATE_ANDX, body, Vec::new()));
117 Ok(Status::SUCCESS)
118}
119
120pub async fn read_andx(
122 io: &mut IoCtx<'_>,
123 req: &ReqView<'_>,
124 bodies: &mut Vec<RespBody>,
125) -> Result<Status, Status> {
126 let rr = ReadReq::parse(req.words).map_err(|_| Status::INVALID_PARAMETER)?;
127 tracing::trace!(
128 fid = format!("{:#06x}", rr.fid),
129 offset = rr.offset,
130 want = rr.max_count,
131 "read_andx"
132 );
133 let vfs = share_vfs(io, req.hdr.tid);
134
135 let Some(h) = io.conn.handles.get_mut(&rr.fid).map(|b| &mut **b) else {
136 return Err(Status::INVALID_HANDLE);
137 };
138 if h.is_dir || !h.can_read {
139 return Err(Status::ACCESS_DENIED);
140 }
141 let data = vfs
142 .read(h, rr.offset, rr.max_count)
143 .await
144 .map_err(vfs_err)?;
145 metrics::counter!("smb_bytes_read_total").increment(data.len() as u64);
146
147 let (params, bytes) = rw::read_response(&data);
148 bodies.push(RespBody::new(consts::COM_READ_ANDX, params, bytes));
149 Ok(Status::SUCCESS)
150}
151
152pub async fn write_andx(
154 io: &mut IoCtx<'_>,
155 req: &ReqView<'_>,
156 bodies: &mut Vec<RespBody>,
157) -> Result<Status, Status> {
158 let wr = WriteReq::parse(req.words, req.frame)?;
159 let vfs = share_vfs(io, req.hdr.tid);
160
161 let Some(h) = io.conn.handles.get_mut(&wr.fid).map(|b| &mut **b) else {
162 return Err(Status::INVALID_HANDLE);
163 };
164 if h.is_dir || !h.can_write {
165 return Err(Status::ACCESS_DENIED);
166 }
167 let written = vfs
168 .write(h, wr.offset, &wr.payload, wr.write_through)
169 .await
170 .map_err(vfs_err)?;
171
172 bodies.push(RespBody::new(
173 consts::COM_WRITE_ANDX,
174 rw::write_response(written),
175 Vec::new(),
176 ));
177 Ok(Status::SUCCESS)
178}
179
180pub async fn close(
182 io: &mut IoCtx<'_>,
183 req: &ReqView<'_>,
184 bodies: &mut Vec<RespBody>,
185) -> Result<Status, Status> {
186 let Some(cr) = misc::CloseReq::parse(req.words) else {
187 return Err(Status::INVALID_PARAMETER);
188 };
189 let vfs = share_vfs(io, req.hdr.tid);
190 let Some(mut h) = io.conn.handles.remove(&cr.fid) else {
191 return Err(Status::INVALID_HANDLE);
192 };
193 if cr.mtime != 0 && cr.mtime != u32::MAX {
194 let ft = smb_server_proto::types::FileTime::from_unix(cr.mtime as i64, 0);
196 let _ = vfs
197 .set_info_open(
198 &mut h,
199 &SetOp::Basic {
200 access: None,
201 write: Some(ft),
202 },
203 )
204 .await;
205 }
206 vfs.close(h).await.map_err(vfs_err)?;
207 bodies.push(RespBody::new(consts::COM_CLOSE, Vec::new(), Vec::new()));
208 Ok(Status::SUCCESS)
209}
210
211pub async fn flush(
213 io: &mut IoCtx<'_>,
214 req: &ReqView<'_>,
215 bodies: &mut Vec<RespBody>,
216) -> Result<Status, Status> {
217 let vfs = share_vfs(io, req.hdr.tid);
218 match misc::flush_fid(req.words) {
219 None => return Err(Status::INVALID_PARAMETER),
220 Some(0xFFFF) => vfs.flush_all().await.map_err(vfs_err)?,
221 Some(fid) => {
222 if let Some(h) = io.conn.handles.get_mut(&fid) {
223 vfs.flush(h).await.map_err(vfs_err)?;
224 }
225 }
226 }
227 bodies.push(RespBody::new(consts::COM_FLUSH, Vec::new(), Vec::new()));
228 Ok(Status::SUCCESS)
229}
230
231pub async fn seek(
233 io: &mut IoCtx<'_>,
234 req: &ReqView<'_>,
235 bodies: &mut Vec<RespBody>,
236) -> Result<Status, Status> {
237 let Some(sr) = misc::SeekReq::parse(req.words) else {
238 return Err(Status::INVALID_PARAMETER);
239 };
240 let vfs = share_vfs(io, req.hdr.tid);
241 let Some(h) = io.conn.handles.get_mut(&sr.fid) else {
242 return Err(Status::INVALID_HANDLE);
243 };
244 let pos = vfs.seek(h, sr.mode, sr.offset).await.map_err(vfs_err)?;
245 bodies.push(RespBody::new(
246 consts::COM_SEEK,
247 misc::SeekReq::build_response(pos),
248 Vec::new(),
249 ));
250 Ok(Status::SUCCESS)
251}
252
253pub async fn locking(
255 _io: &mut IoCtx<'_>,
256 req: &ReqView<'_>,
257 bodies: &mut Vec<RespBody>,
258) -> Result<Status, Status> {
259 let cmd = req.hdr.command;
260 bodies.push(RespBody::new(
261 cmd,
262 vec![smb_server_proto_smb1::consts::ANDX_NONE, 0, 0, 0],
263 Vec::new(),
264 ));
265 Ok(Status::SUCCESS)
266}
267
268pub(crate) fn vfs_err(e: smb_server_vfs::VfsError) -> Status {
271 use smb_server_vfs::VfsError as E;
272 match e {
273 E::NotFound => Status::OBJECT_PATH_NOT_FOUND,
274 E::AlreadyExists => Status::OBJECT_NAME_COLLISION,
275 E::AccessDenied => Status::ACCESS_DENIED,
276 E::DirectoryNotEmpty => Status::DIRECTORY_NOT_EMPTY,
277 E::InvalidArgument => Status::INVALID_PARAMETER,
278 E::NotSupported => Status::NOT_IMPLEMENTED,
279 E::StoppedOnSymlink { .. } => Status::STOPPED_ON_SYMLINK,
280 E::Io(_) => Status::UNSUCCESSFUL,
281 }
282}
283
284fn join_rel(base: &str, name: &str) -> String {
285 let name = name.trim_start_matches(['\\', '/']);
286 if base.is_empty() {
287 name.to_string()
288 } else {
289 format!("{}\\{}", base.trim_end_matches(['\\', '/']), name)
290 }
291}