async_ssh2/
sftp.rs

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
14/// See [`Sftp`](ssh2::Sftp).
15pub struct Sftp {
16    inner: ssh2::Sftp,
17    stream: Arc<Async<TcpStream>>,
18}
19
20/// See [`File`](ssh2::File).
21pub 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    /// See [`open_mode`](ssh2::Sftp::open_mode).
35    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    /// See [`open`](ssh2::Sftp::open).
50    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    /// See [`create`](ssh2::Sftp::create).
56    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    /// See [`opendir`](ssh2::Sftp::opendir).
67    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    /// See [`readdir`](ssh2::Sftp::readdir).
73    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    /// See [`mkdir`](ssh2::Sftp::mkdir).
97    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    /// See [`rmdir`](ssh2::Sftp::rmdir).
102    pub async fn rmdir(&self, filename: &Path) -> Result<(), Error> {
103        run_ssh2_fn(&self.stream, || self.inner.rmdir(filename)).await
104    }
105
106    /// See [`stat`](ssh2::Sftp::stat).
107    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    /// See [`lstat`](ssh2::Sftp::lstat).
112    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    /// See [`setstat`](ssh2::Sftp::setstat).
117    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    /// See [`symlink`](ssh2::Sftp::symlink).
122    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    /// See [`readlink`](ssh2::Sftp::readlink).
127    pub async fn readlink(&self, path: &Path) -> Result<PathBuf, Error> {
128        run_ssh2_fn(&self.stream, || self.inner.readlink(path)).await
129    }
130
131    /// See [`realpath`](ssh2::Sftp::realpath).
132    pub async fn realpath(&self, path: &Path) -> Result<PathBuf, Error> {
133        run_ssh2_fn(&self.stream, || self.inner.realpath(path)).await
134    }
135
136    /// See [`rename`](ssh2::Sftp::rename).
137    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    /// See [`unlink`](ssh2::Sftp::unlink).
147    pub async fn unlink(&self, file: &Path) -> Result<(), Error> {
148        run_ssh2_fn(&self.stream, || self.inner.unlink(file)).await
149    }
150
151    /// See [`unlink`](ssh2::Sftp::unlink).
152    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    /// See [`setstat`](ssh2::File::setstat).
166    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    /// See [`stat`](ssh2::File::stat).
171    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    /// See [`statvfs`](ssh2::File::statvfs).
177    // TODO
178    /*
179    pub async fn statvfs(&mut self) -> Result<raw::LIBSSH2_SFTP_STATVFS, Error> {
180        run_ssh2_fn(&self.stream.clone(), || self.inner.statvfs().await
181    }
182    */
183
184    /// See [`readdir`](ssh2::File::readdir).
185    pub async fn readdir(&mut self) -> Result<(PathBuf, FileStat), Error> {
186        run_ssh2_fn(&self.stream.clone(), || self.inner.readdir()).await
187    }
188
189    /// See [`fsync`](ssh2::File::fsync).
190    pub async fn fsync(&mut self) -> Result<(), Error> {
191        run_ssh2_fn(&self.stream.clone(), || self.inner.fsync()).await
192    }
193
194    /// See [`close`](ssh2::File::close).
195    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}