1use crate::{util::run_ssh2_fn, Error};
2use futures::prelude::*;
3use smol::Async;
4use ssh2::{self, FileStat, OpenFlags, OpenType};
5use std::{
6 io::{self, Read, Seek, Write},
7 net::TcpStream,
8 path::{Path, PathBuf},
9 pin::Pin,
10 sync::Arc,
11 task::{Context, Poll},
12};
13
14pub struct Sftp {
16 inner: ssh2::Sftp,
17 stream: Arc<Async<TcpStream>>,
18}
19
20pub struct File {
22 inner: ssh2::File,
23 stream: Arc<Async<TcpStream>>,
24}
25
26impl Sftp {
27 pub(crate) fn new(sftp: ssh2::Sftp, stream: Arc<Async<TcpStream>>) -> Self {
28 Self {
29 inner: sftp,
30 stream,
31 }
32 }
33
34 pub async fn open_mode(
36 &self,
37 filename: &Path,
38 flags: ssh2::OpenFlags,
39 mode: i32,
40 open_type: ssh2::OpenType,
41 ) -> Result<File, Error> {
42 let file = run_ssh2_fn(&self.stream, || {
43 self.inner.open_mode(filename, flags, mode, open_type)
44 })
45 .await?;
46 Ok(File::new(file, self.stream.clone()))
47 }
48
49 pub async fn open(&self, filename: &Path) -> Result<File, Error> {
51 self.open_mode(filename, OpenFlags::READ, 0o644, OpenType::File)
52 .await
53 }
54
55 pub async fn create(&self, filename: &Path) -> Result<File, Error> {
57 self.open_mode(
58 filename,
59 OpenFlags::WRITE | OpenFlags::TRUNCATE,
60 0o644,
61 OpenType::File,
62 )
63 .await
64 }
65
66 pub async fn opendir(&self, dirname: &Path) -> Result<File, Error> {
68 self.open_mode(dirname, OpenFlags::READ, 0, OpenType::Dir)
69 .await
70 }
71
72 pub async fn readdir(&self, dirname: &Path) -> Result<Vec<(PathBuf, FileStat)>, Error> {
74 let mut dir = self.opendir(dirname).await?;
75 let mut ret = Vec::new();
76 loop {
77 match dir.readdir().await {
78 Ok((filename, stat)) => {
79 if &*filename == Path::new(".") || &*filename == Path::new("..") {
80 continue;
81 }
82
83 ret.push((dirname.join(&filename), stat))
84 }
85 Err(Error::SSH2(ref e)) if e.code() == -16 => {
86 break;
87 }
88 Err(e) => {
89 return Err(e);
90 }
91 }
92 }
93 Ok(ret)
94 }
95
96 pub async fn mkdir(&self, filename: &Path, mode: i32) -> Result<(), Error> {
98 run_ssh2_fn(&self.stream, || self.inner.mkdir(filename, mode)).await
99 }
100
101 pub async fn rmdir(&self, filename: &Path) -> Result<(), Error> {
103 run_ssh2_fn(&self.stream, || self.inner.rmdir(filename)).await
104 }
105
106 pub async fn stat(&self, filename: &Path) -> Result<ssh2::FileStat, Error> {
108 run_ssh2_fn(&self.stream, || self.inner.stat(filename)).await
109 }
110
111 pub async fn lstat(&self, filename: &Path) -> Result<ssh2::FileStat, Error> {
113 run_ssh2_fn(&self.stream, || self.inner.lstat(filename)).await
114 }
115
116 pub async fn setstat(&self, filename: &Path, stat: ssh2::FileStat) -> Result<(), Error> {
118 run_ssh2_fn(&self.stream, || self.inner.setstat(filename, stat.clone())).await
119 }
120
121 pub async fn symlink(&self, path: &Path, target: &Path) -> Result<(), Error> {
123 run_ssh2_fn(&self.stream, || self.inner.symlink(path, target)).await
124 }
125
126 pub async fn readlink(&self, path: &Path) -> Result<PathBuf, Error> {
128 run_ssh2_fn(&self.stream, || self.inner.readlink(path)).await
129 }
130
131 pub async fn realpath(&self, path: &Path) -> Result<PathBuf, Error> {
133 run_ssh2_fn(&self.stream, || self.inner.realpath(path)).await
134 }
135
136 pub async fn rename(
138 &self,
139 src: &Path,
140 dst: &Path,
141 flags: Option<ssh2::RenameFlags>,
142 ) -> Result<(), Error> {
143 run_ssh2_fn(&self.stream, || self.inner.rename(src, dst, flags)).await
144 }
145
146 pub async fn unlink(&self, file: &Path) -> Result<(), Error> {
148 run_ssh2_fn(&self.stream, || self.inner.unlink(file)).await
149 }
150
151 pub async fn shutdown(mut self) -> Result<(), Error> {
153 run_ssh2_fn(&self.stream.clone(), || self.inner.shutdown()).await
154 }
155}
156
157impl File {
158 pub(crate) fn new(file: ssh2::File, stream: Arc<Async<TcpStream>>) -> Self {
159 Self {
160 inner: file,
161 stream,
162 }
163 }
164
165 pub async fn setstat(&mut self, stat: FileStat) -> Result<(), Error> {
167 run_ssh2_fn(&self.stream.clone(), || self.inner.setstat(stat.clone())).await
168 }
169
170 pub async fn stat(&mut self) -> Result<FileStat, Error> {
172 run_ssh2_fn(&self.stream.clone(), || self.inner.stat()).await
173 }
174
175 #[allow(missing_docs)]
176 pub async fn readdir(&mut self) -> Result<(PathBuf, FileStat), Error> {
186 run_ssh2_fn(&self.stream.clone(), || self.inner.readdir()).await
187 }
188
189 pub async fn fsync(&mut self) -> Result<(), Error> {
191 run_ssh2_fn(&self.stream.clone(), || self.inner.fsync()).await
192 }
193
194 pub async fn close(mut self) -> Result<(), Error> {
196 run_ssh2_fn(&self.stream.clone(), || self.inner.close()).await
197 }
198}
199
200impl AsyncRead for File {
201 fn poll_read(
202 mut self: Pin<&mut Self>,
203 cx: &mut Context<'_>,
204 buf: &mut [u8],
205 ) -> Poll<io::Result<usize>> {
206 self.stream
207 .clone()
208 .read_with(|_s| self.inner.read(buf))
209 .boxed()
210 .poll_unpin(cx)
211 }
212}
213
214impl AsyncWrite for File {
215 fn poll_write(
216 mut self: Pin<&mut Self>,
217 cx: &mut Context<'_>,
218 buf: &[u8],
219 ) -> Poll<Result<usize, io::Error>> {
220 self.stream
221 .clone()
222 .write_with(|_s| self.inner.write(buf))
223 .boxed()
224 .poll_unpin(cx)
225 }
226
227 fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), io::Error>> {
228 self.stream
229 .clone()
230 .write_with(|_s| self.inner.flush())
231 .boxed()
232 .poll_unpin(cx)
233 }
234
235 fn poll_close(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<io::Result<()>> {
236 Poll::Ready(Ok(()))
237 }
238}
239
240impl Seek for File {
241 fn seek(&mut self, pos: io::SeekFrom) -> Result<u64, io::Error> {
242 self.inner.seek(pos)
243 }
244}