1use super::{SessionRequest, SessionSender};
2use crate::sftp::dir::{Dir, DirRequest};
3use crate::sftp::file::{File, FileRequest};
4use crate::sftp::types::{Metadata, OpenFileType, OpenOptions, RenameOptions, WriteMode};
5use camino::Utf8PathBuf;
6use error::SftpError;
7use smol::channel::{bounded, RecvError, Sender};
8use std::convert::TryInto;
9use std::io;
10use thiserror::Error;
11
12pub(crate) mod dir;
13pub(crate) mod error;
14pub(crate) mod file;
15pub(crate) mod types;
16
17fn into_invalid_data<E>(err: E) -> io::Error
18where
19 E: Into<Box<dyn std::error::Error + Send + Sync>>,
20{
21 io::Error::new(io::ErrorKind::InvalidData, err)
22}
23
24pub type SftpChannelResult<T> = Result<T, SftpChannelError>;
26
27#[derive(Debug, Error)]
29pub enum SftpChannelError {
30 #[error(transparent)]
31 Sftp(#[from] SftpError),
32
33 #[error("File IO failed: {}", .0)]
34 FileIo(#[from] std::io::Error),
35
36 #[error("Failed to send request: {}", .0)]
37 SendFailed(#[from] anyhow::Error),
38
39 #[error("Failed to receive response: {}", .0)]
40 RecvFailed(#[from] RecvError),
41
42 #[cfg(feature = "ssh2")]
43 #[error("Library-specific error: {}", .0)]
44 Ssh2(#[source] ssh2::Error),
45
46 #[cfg(feature = "libssh-rs")]
47 #[error("Library-specific error: {}", .0)]
48 LibSsh(#[source] libssh_rs::Error),
49
50 #[error("Not Implemented")]
51 NotImplemented,
52}
53
54#[derive(Clone, Debug)]
56pub struct Sftp {
57 pub(crate) tx: SessionSender,
58}
59
60impl Sftp {
61 pub async fn open_with_mode<T, E>(
63 &self,
64 filename: T,
65 opts: OpenOptions,
66 ) -> SftpChannelResult<File>
67 where
68 T: TryInto<Utf8PathBuf, Error = E>,
69 E: Into<Box<dyn std::error::Error + Send + Sync>>,
70 {
71 let (reply, rx) = bounded(1);
72
73 self.tx
74 .send(SessionRequest::Sftp(SftpRequest::OpenWithMode(
75 OpenWithMode {
76 filename: filename.try_into().map_err(into_invalid_data)?,
77 opts,
78 },
79 reply,
80 )))
81 .await?;
82 let mut result = rx.recv().await??;
83 result.initialize_sender(self.tx.clone());
84 Ok(result)
85 }
86
87 pub async fn open<T, E>(&self, filename: T) -> SftpChannelResult<File>
89 where
90 T: TryInto<Utf8PathBuf, Error = E>,
91 E: Into<Box<dyn std::error::Error + Send + Sync>>,
92 {
93 self.open_with_mode(
94 filename,
95 OpenOptions {
96 read: true,
97 write: None,
98 mode: 0,
99 ty: OpenFileType::File,
100 },
101 )
102 .await
103 }
104
105 pub async fn create<T, E>(&self, filename: T) -> SftpChannelResult<File>
107 where
108 T: TryInto<Utf8PathBuf, Error = E>,
109 E: Into<Box<dyn std::error::Error + Send + Sync>>,
110 {
111 self.open_with_mode(
112 filename,
113 OpenOptions {
114 read: false,
115 write: Some(WriteMode::Write),
116 mode: 0o666,
117 ty: OpenFileType::File,
118 },
119 )
120 .await
121 }
122
123 pub async fn open_dir<T, E>(&self, filename: T) -> SftpChannelResult<Dir>
125 where
126 T: TryInto<Utf8PathBuf, Error = E>,
127 E: Into<Box<dyn std::error::Error + Send + Sync>>,
128 {
129 let (reply, rx) = bounded(1);
130 self.tx
131 .send(SessionRequest::Sftp(SftpRequest::OpenDir(
132 filename.try_into().map_err(into_invalid_data)?,
133 reply,
134 )))
135 .await?;
136 let mut result = rx.recv().await??;
137 result.initialize_sender(self.tx.clone());
138 Ok(result)
139 }
140
141 pub async fn read_dir<T, E>(
146 &self,
147 filename: T,
148 ) -> SftpChannelResult<Vec<(Utf8PathBuf, Metadata)>>
149 where
150 T: TryInto<Utf8PathBuf, Error = E>,
151 E: Into<Box<dyn std::error::Error + Send + Sync>>,
152 {
153 let (reply, rx) = bounded(1);
154 self.tx
155 .send(SessionRequest::Sftp(SftpRequest::ReadDir(
156 filename.try_into().map_err(into_invalid_data)?,
157 reply,
158 )))
159 .await?;
160 let result = rx.recv().await??;
161 Ok(result)
162 }
163
164 pub async fn create_dir<T, E>(&self, filename: T, mode: i32) -> SftpChannelResult<()>
166 where
167 T: TryInto<Utf8PathBuf, Error = E>,
168 E: Into<Box<dyn std::error::Error + Send + Sync>>,
169 {
170 let (reply, rx) = bounded(1);
171 self.tx
172 .send(SessionRequest::Sftp(SftpRequest::CreateDir(
173 CreateDir {
174 filename: filename.try_into().map_err(into_invalid_data)?,
175 mode,
176 },
177 reply,
178 )))
179 .await?;
180 let result = rx.recv().await??;
181 Ok(result)
182 }
183
184 pub async fn remove_dir<T, E>(&self, filename: T) -> SftpChannelResult<()>
186 where
187 T: TryInto<Utf8PathBuf, Error = E>,
188 E: Into<Box<dyn std::error::Error + Send + Sync>>,
189 {
190 let (reply, rx) = bounded(1);
191 self.tx
192 .send(SessionRequest::Sftp(SftpRequest::RemoveDir(
193 filename.try_into().map_err(into_invalid_data)?,
194 reply,
195 )))
196 .await?;
197 let result = rx.recv().await??;
198 Ok(result)
199 }
200
201 pub async fn metadata<T, E>(&self, filename: T) -> SftpChannelResult<Metadata>
203 where
204 T: TryInto<Utf8PathBuf, Error = E>,
205 E: Into<Box<dyn std::error::Error + Send + Sync>>,
206 {
207 let (reply, rx) = bounded(1);
208 self.tx
209 .send(SessionRequest::Sftp(SftpRequest::Metadata(
210 filename.try_into().map_err(into_invalid_data)?,
211 reply,
212 )))
213 .await?;
214 let result = rx.recv().await??;
215 Ok(result)
216 }
217
218 pub async fn symlink_metadata<T, E>(&self, filename: T) -> SftpChannelResult<Metadata>
220 where
221 T: TryInto<Utf8PathBuf, Error = E>,
222 E: Into<Box<dyn std::error::Error + Send + Sync>>,
223 {
224 let (reply, rx) = bounded(1);
225 self.tx
226 .send(SessionRequest::Sftp(SftpRequest::SymlinkMetadata(
227 filename.try_into().map_err(into_invalid_data)?,
228 reply,
229 )))
230 .await?;
231 let result = rx.recv().await??;
232 Ok(result)
233 }
234
235 pub async fn set_metadata<T, E>(&self, filename: T, metadata: Metadata) -> SftpChannelResult<()>
237 where
238 T: TryInto<Utf8PathBuf, Error = E>,
239 E: Into<Box<dyn std::error::Error + Send + Sync>>,
240 {
241 let (reply, rx) = bounded(1);
242 self.tx
243 .send(SessionRequest::Sftp(SftpRequest::SetMetadata(
244 SetMetadata {
245 filename: filename.try_into().map_err(into_invalid_data)?,
246 metadata,
247 },
248 reply,
249 )))
250 .await?;
251 let result = rx.recv().await??;
252 Ok(result)
253 }
254
255 pub async fn symlink<T1, T2, E1, E2>(&self, path: T1, target: T2) -> SftpChannelResult<()>
257 where
258 T1: TryInto<Utf8PathBuf, Error = E1>,
259 T2: TryInto<Utf8PathBuf, Error = E2>,
260 E1: Into<Box<dyn std::error::Error + Send + Sync>>,
261 E2: Into<Box<dyn std::error::Error + Send + Sync>>,
262 {
263 let (reply, rx) = bounded(1);
264 self.tx
265 .send(SessionRequest::Sftp(SftpRequest::Symlink(
266 Symlink {
267 path: path.try_into().map_err(into_invalid_data)?,
268 target: target.try_into().map_err(into_invalid_data)?,
269 },
270 reply,
271 )))
272 .await?;
273 let result = rx.recv().await??;
274 Ok(result)
275 }
276
277 pub async fn read_link<T, E>(&self, path: T) -> SftpChannelResult<Utf8PathBuf>
279 where
280 T: TryInto<Utf8PathBuf, Error = E>,
281 E: Into<Box<dyn std::error::Error + Send + Sync>>,
282 {
283 let (reply, rx) = bounded(1);
284 self.tx
285 .send(SessionRequest::Sftp(SftpRequest::ReadLink(
286 path.try_into().map_err(into_invalid_data)?,
287 reply,
288 )))
289 .await?;
290 let result = rx.recv().await??;
291 Ok(result)
292 }
293
294 pub async fn canonicalize<T, E>(&self, path: T) -> SftpChannelResult<Utf8PathBuf>
296 where
297 T: TryInto<Utf8PathBuf, Error = E>,
298 E: Into<Box<dyn std::error::Error + Send + Sync>>,
299 {
300 let (reply, rx) = bounded(1);
301 self.tx
302 .send(SessionRequest::Sftp(SftpRequest::Canonicalize(
303 path.try_into().map_err(into_invalid_data)?,
304 reply,
305 )))
306 .await?;
307 let result = rx.recv().await??;
308 Ok(result)
309 }
310
311 pub async fn rename<T1, T2, E1, E2>(
313 &self,
314 src: T1,
315 dst: T2,
316 opts: RenameOptions,
317 ) -> SftpChannelResult<()>
318 where
319 T1: TryInto<Utf8PathBuf, Error = E1>,
320 T2: TryInto<Utf8PathBuf, Error = E2>,
321 E1: Into<Box<dyn std::error::Error + Send + Sync>>,
322 E2: Into<Box<dyn std::error::Error + Send + Sync>>,
323 {
324 let (reply, rx) = bounded(1);
325 self.tx
326 .send(SessionRequest::Sftp(SftpRequest::Rename(
327 Rename {
328 src: src.try_into().map_err(into_invalid_data)?,
329 dst: dst.try_into().map_err(into_invalid_data)?,
330 opts,
331 },
332 reply,
333 )))
334 .await?;
335 let result = rx.recv().await??;
336 Ok(result)
337 }
338
339 pub async fn remove_file<T, E>(&self, file: T) -> SftpChannelResult<()>
341 where
342 T: TryInto<Utf8PathBuf, Error = E>,
343 E: Into<Box<dyn std::error::Error + Send + Sync>>,
344 {
345 let (reply, rx) = bounded(1);
346 self.tx
347 .send(SessionRequest::Sftp(SftpRequest::RemoveFile(
348 file.try_into().map_err(into_invalid_data)?,
349 reply,
350 )))
351 .await?;
352 let result = rx.recv().await??;
353 Ok(result)
354 }
355}
356
357#[derive(Debug)]
358pub(crate) enum SftpRequest {
359 OpenWithMode(OpenWithMode, Sender<SftpChannelResult<File>>),
360 OpenDir(Utf8PathBuf, Sender<SftpChannelResult<Dir>>),
361 ReadDir(
362 Utf8PathBuf,
363 Sender<SftpChannelResult<Vec<(Utf8PathBuf, Metadata)>>>,
364 ),
365 CreateDir(CreateDir, Sender<SftpChannelResult<()>>),
366 RemoveDir(Utf8PathBuf, Sender<SftpChannelResult<()>>),
367 Metadata(Utf8PathBuf, Sender<SftpChannelResult<Metadata>>),
368 SymlinkMetadata(Utf8PathBuf, Sender<SftpChannelResult<Metadata>>),
369 SetMetadata(SetMetadata, Sender<SftpChannelResult<()>>),
370 Symlink(Symlink, Sender<SftpChannelResult<()>>),
371 ReadLink(Utf8PathBuf, Sender<SftpChannelResult<Utf8PathBuf>>),
372 Canonicalize(Utf8PathBuf, Sender<SftpChannelResult<Utf8PathBuf>>),
373 Rename(Rename, Sender<SftpChannelResult<()>>),
374 RemoveFile(Utf8PathBuf, Sender<SftpChannelResult<()>>),
375
376 File(FileRequest),
378 Dir(DirRequest),
379}
380
381#[derive(Debug)]
382pub(crate) struct OpenWithMode {
383 pub filename: Utf8PathBuf,
384 pub opts: OpenOptions,
385}
386
387#[derive(Debug)]
388pub(crate) struct CreateDir {
389 pub filename: Utf8PathBuf,
390 pub mode: i32,
391}
392
393#[derive(Debug)]
394pub(crate) struct SetMetadata {
395 pub filename: Utf8PathBuf,
396 pub metadata: Metadata,
397}
398
399#[derive(Debug)]
400pub(crate) struct Symlink {
401 pub path: Utf8PathBuf,
402 pub target: Utf8PathBuf,
403}
404
405#[derive(Debug)]
406pub(crate) struct Rename {
407 pub src: Utf8PathBuf,
408 pub dst: Utf8PathBuf,
409 pub opts: RenameOptions,
410}
411
412#[cfg(feature = "ssh2")]
413impl From<ssh2::Error> for SftpChannelError {
414 fn from(err: ssh2::Error) -> Self {
415 use std::convert::TryFrom;
416 match SftpError::try_from(err) {
417 Ok(x) => Self::Sftp(x),
418 Err(x) => Self::Ssh2(x),
419 }
420 }
421}
422
423#[cfg(feature = "libssh-rs")]
424impl From<libssh_rs::Error> for SftpChannelError {
425 fn from(err: libssh_rs::Error) -> Self {
426 Self::LibSsh(err)
427 }
428}