ssh2/
sftp.rs

1use libc::{c_int, c_long, c_uint, c_ulong, size_t};
2use parking_lot::{Mutex, MutexGuard};
3use std::convert::TryFrom;
4use std::ffi::CString;
5use std::io::prelude::*;
6use std::io::{self, ErrorKind, SeekFrom};
7use std::mem;
8use std::path::{Path, PathBuf};
9use std::ptr::null_mut;
10use std::sync::Arc;
11
12use util;
13use {raw, Error, ErrorCode, SessionInner};
14
15/// A handle to a remote filesystem over SFTP.
16///
17/// Instances are created through the `sftp` method on a `Session`.
18pub struct Sftp {
19    inner: Option<Arc<SftpInnerDropWrapper>>,
20}
21/// This contains an Option so that we're able to disable the Drop hook when dropping manually,
22/// while still dropping all the fields of SftpInner (which we couldn't do with `mem::forget`)
23struct SftpInnerDropWrapper(Option<SftpInner>);
24struct SftpInner {
25    raw: *mut raw::LIBSSH2_SFTP,
26    sess: Arc<Mutex<SessionInner>>,
27}
28
29// Sftp is both Send and Sync; the compiler can't see it because it
30// is pessimistic about the raw pointer.  We use Arc/Mutex to guard accessing
31// the raw pointer so we are safe for both.
32unsafe impl Send for Sftp {}
33unsafe impl Sync for Sftp {}
34
35struct LockedSftp<'sftp> {
36    raw: *mut raw::LIBSSH2_SFTP,
37    sess: MutexGuard<'sftp, SessionInner>,
38}
39
40/// A file handle to an SFTP connection.
41///
42/// Files behave similarly to `std::old_io::File` in that they are readable and
43/// writable and support operations like stat and seek.
44///
45/// Files are created through `open`, `create`, and `open_mode` on an instance
46/// of `Sftp`.
47pub struct File {
48    inner: Option<FileInner>,
49}
50struct FileInner {
51    raw: *mut raw::LIBSSH2_SFTP_HANDLE,
52    sftp: Arc<SftpInnerDropWrapper>,
53}
54
55// File is both Send and Sync; the compiler can't see it because it
56// is pessimistic about the raw pointer.  We use Arc/Mutex to guard accessing
57// the raw pointer so we are safe for both.
58unsafe impl Send for File {}
59unsafe impl Sync for File {}
60
61struct LockedFile<'file> {
62    raw: *mut raw::LIBSSH2_SFTP_HANDLE,
63    sess: MutexGuard<'file, SessionInner>,
64}
65
66/// Metadata information about a remote file.
67///
68/// Fields are not necessarily all provided
69#[derive(Debug, Clone, Eq, PartialEq)]
70#[allow(missing_copy_implementations)]
71pub struct FileStat {
72    /// File size, in bytes of the file.
73    pub size: Option<u64>,
74    /// Owner ID of the file
75    pub uid: Option<u32>,
76    /// Owning group of the file
77    pub gid: Option<u32>,
78    /// Permissions (mode) of the file
79    pub perm: Option<u32>,
80    /// Last access time of the file
81    pub atime: Option<u64>,
82    /// Last modification time of the file
83    pub mtime: Option<u64>,
84}
85
86/// An enum representing a type of file.
87#[derive(PartialEq)]
88pub enum FileType {
89    /// Named pipe (S_IFIFO)
90    NamedPipe,
91    /// Character device (S_IFCHR)
92    CharDevice,
93    /// Block device (S_IFBLK)
94    BlockDevice,
95    /// Directory (S_IFDIR)
96    Directory,
97    /// Regular file (S_IFREG)
98    RegularFile,
99    /// Symbolic link (S_IFLNK)
100    Symlink,
101    /// Unix domain socket (S_IFSOCK)
102    Socket,
103    /// Other filetype (does not correspond to any of the other ones)
104    Other(c_ulong),
105}
106
107bitflags! {
108    /// Options that can be used to configure how a file is opened
109    #[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Clone, Copy)]
110    pub struct OpenFlags: c_ulong {
111        /// Open the file for reading.
112        const READ = raw::LIBSSH2_FXF_READ;
113        /// Open the file for writing. If both this and `Read` are specified,
114        /// the file is opened for both reading and writing.
115        const WRITE = raw::LIBSSH2_FXF_WRITE;
116        /// Force all writes to append data at the end of the file.
117        const APPEND = raw::LIBSSH2_FXF_APPEND;
118        /// If this flag is specified, then a new file will be created if one
119        /// does not already exist (if `Truncate` is specified, the new file
120        /// will be truncated to zero length if it previously exists).
121        const CREATE = raw::LIBSSH2_FXF_CREAT;
122        /// Forces an existing file with the same name to be truncated to zero
123        /// length when creating a file by specifying `Create`. Using this flag
124        /// implies the `Create` flag.
125        const TRUNCATE = raw::LIBSSH2_FXF_TRUNC | Self::CREATE.bits();
126        /// Causes the request to fail if the named file already exists. Using
127        /// this flag implies the `Create` flag.
128        const EXCLUSIVE = raw::LIBSSH2_FXF_EXCL | Self::CREATE.bits();
129    }
130}
131
132bitflags! {
133    /// Options to `Sftp::rename`.
134    #[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Clone, Copy)]
135    pub struct RenameFlags: c_long {
136        /// In a rename operation, overwrite the destination if it already
137        /// exists. If this flag is not present then it is an error if the
138        /// destination already exists.
139        const OVERWRITE = raw::LIBSSH2_SFTP_RENAME_OVERWRITE;
140        /// Inform the remote that an atomic rename operation is desired if
141        /// available.
142        const ATOMIC = raw::LIBSSH2_SFTP_RENAME_ATOMIC;
143        /// Inform the remote end that the native system calls for renaming
144        /// should be used.
145        const NATIVE = raw::LIBSSH2_SFTP_RENAME_NATIVE;
146    }
147}
148
149/// How to open a file handle with libssh2.
150#[derive(Copy, Clone)]
151pub enum OpenType {
152    /// Specify that a file shoud be opened.
153    File = raw::LIBSSH2_SFTP_OPENFILE as isize,
154    /// Specify that a directory should be opened.
155    Dir = raw::LIBSSH2_SFTP_OPENDIR as isize,
156}
157
158impl Sftp {
159    pub(crate) fn from_raw_opt(
160        raw: *mut raw::LIBSSH2_SFTP,
161        err: Option<Error>,
162        sess: &Arc<Mutex<SessionInner>>,
163    ) -> Result<Self, Error> {
164        if raw.is_null() {
165            Err(err.unwrap_or_else(Error::unknown))
166        } else {
167            Ok(Self {
168                inner: Some(Arc::new(SftpInnerDropWrapper(Some(SftpInner {
169                    raw,
170                    sess: Arc::clone(sess),
171                })))),
172            })
173        }
174    }
175
176    /// Open a handle to a file.
177    ///
178    /// The mode will represent the permissions for the file ([Wikipedia](<https://en.wikipedia.org/wiki/File-system_permissions#Numeric_notation>)).
179    pub fn open_mode<T: AsRef<Path>>(
180        &self,
181        filename: T,
182        flags: OpenFlags,
183        mode: i32,
184        open_type: OpenType,
185    ) -> Result<File, Error> {
186        let filename = CString::new(util::path2bytes(filename.as_ref())?)?;
187
188        let locked = self.lock()?;
189        unsafe {
190            let ret = raw::libssh2_sftp_open_ex(
191                locked.raw,
192                filename.as_ptr() as *const _,
193                filename.as_bytes().len() as c_uint,
194                flags.bits() as c_ulong,
195                mode as c_long,
196                open_type as c_int,
197            );
198            if ret.is_null() {
199                let rc = raw::libssh2_session_last_errno(locked.sess.raw);
200                Err(Self::error_code_into_error(locked.sess.raw, locked.raw, rc))
201            } else {
202                Ok(File::from_raw(self, ret))
203            }
204        }
205    }
206
207    /// Helper to open a file in the `Read` mode.
208    pub fn open<T: AsRef<Path>>(&self, filename: T) -> Result<File, Error> {
209        self.open_mode(filename, OpenFlags::READ, 0o644, OpenType::File)
210    }
211
212    /// Helper to create a file in write-only mode with truncation.
213    pub fn create(&self, filename: &Path) -> Result<File, Error> {
214        self.open_mode(
215            filename,
216            OpenFlags::WRITE | OpenFlags::TRUNCATE,
217            0o644,
218            OpenType::File,
219        )
220    }
221
222    /// Helper to open a directory for reading its contents.
223    pub fn opendir<T: AsRef<Path>>(&self, dirname: T) -> Result<File, Error> {
224        self.open_mode(dirname, OpenFlags::READ, 0, OpenType::Dir)
225    }
226
227    /// Convenience function to read the files in a directory.
228    ///
229    /// The returned paths are all joined with `dirname` when returned, and the
230    /// paths `.` and `..` are filtered out of the returned list.
231    pub fn readdir<T: AsRef<Path>>(&self, dirname: T) -> Result<Vec<(PathBuf, FileStat)>, Error> {
232        let mut dir = self.opendir(dirname.as_ref())?;
233        let mut ret = Vec::new();
234        loop {
235            match dir.readdir() {
236                Ok((filename, stat)) => {
237                    if &*filename == Path::new(".") || &*filename == Path::new("..") {
238                        continue;
239                    }
240
241                    ret.push((dirname.as_ref().join(&filename), stat))
242                }
243                Err(ref e) if e.code() == ErrorCode::Session(raw::LIBSSH2_ERROR_FILE) => break,
244                Err(e) => {
245                    if e.code() != ErrorCode::Session(raw::LIBSSH2_ERROR_EAGAIN) {
246                        return Err(e);
247                    }
248                }
249            }
250        }
251        Ok(ret)
252    }
253
254    /// Create a directory on the remote file system.
255    ///
256    /// The mode will set the permissions of the new directory ([Wikipedia](<https://en.wikipedia.org/wiki/File-system_permissions#Numeric_notation>)).
257    pub fn mkdir(&self, filename: &Path, mode: i32) -> Result<(), Error> {
258        let filename = CString::new(util::path2bytes(filename)?)?;
259        let locked = self.lock()?;
260        Self::rc(&locked, unsafe {
261            raw::libssh2_sftp_mkdir_ex(
262                locked.raw,
263                filename.as_ptr() as *const _,
264                filename.as_bytes().len() as c_uint,
265                mode as c_long,
266            )
267        })
268    }
269
270    /// Remove a directory from the remote file system.
271    pub fn rmdir(&self, filename: &Path) -> Result<(), Error> {
272        let filename = CString::new(util::path2bytes(filename)?)?;
273        let locked = self.lock()?;
274        locked.sess.rc(unsafe {
275            raw::libssh2_sftp_rmdir_ex(
276                locked.raw,
277                filename.as_ptr() as *const _,
278                filename.as_bytes().len() as c_uint,
279            )
280        })
281    }
282
283    /// Get the metadata for a file, performed by stat(2)
284    pub fn stat(&self, filename: &Path) -> Result<FileStat, Error> {
285        let filename = CString::new(util::path2bytes(filename)?)?;
286        let locked = self.lock()?;
287        unsafe {
288            let mut ret = mem::zeroed();
289            Self::rc(
290                &locked,
291                raw::libssh2_sftp_stat_ex(
292                    locked.raw,
293                    filename.as_ptr() as *const _,
294                    filename.as_bytes().len() as c_uint,
295                    raw::LIBSSH2_SFTP_STAT,
296                    &mut ret,
297                ),
298            )
299            .map(|_| FileStat::from_raw(&ret))
300        }
301    }
302
303    /// Get the metadata for a file, performed by lstat(2)
304    pub fn lstat(&self, filename: &Path) -> Result<FileStat, Error> {
305        let filename = CString::new(util::path2bytes(filename)?)?;
306        let locked = self.lock()?;
307        unsafe {
308            let mut ret = mem::zeroed();
309            Self::rc(
310                &locked,
311                raw::libssh2_sftp_stat_ex(
312                    locked.raw,
313                    filename.as_ptr() as *const _,
314                    filename.as_bytes().len() as c_uint,
315                    raw::LIBSSH2_SFTP_LSTAT,
316                    &mut ret,
317                ),
318            )
319            .map(|_| FileStat::from_raw(&ret))
320        }
321    }
322
323    /// Set the metadata for a file.
324    pub fn setstat(&self, filename: &Path, stat: FileStat) -> Result<(), Error> {
325        let filename = CString::new(util::path2bytes(filename)?)?;
326        let locked = self.lock()?;
327        Self::rc(&locked, unsafe {
328            let mut raw = stat.raw();
329            raw::libssh2_sftp_stat_ex(
330                locked.raw,
331                filename.as_ptr() as *const _,
332                filename.as_bytes().len() as c_uint,
333                raw::LIBSSH2_SFTP_SETSTAT,
334                &mut raw,
335            )
336        })
337    }
338
339    /// Create a symlink at `target` pointing at `path`.
340    pub fn symlink(&self, path: &Path, target: &Path) -> Result<(), Error> {
341        let path = CString::new(util::path2bytes(path)?)?;
342        let target = CString::new(util::path2bytes(target)?)?;
343        let locked = self.lock()?;
344        locked.sess.rc(unsafe {
345            raw::libssh2_sftp_symlink_ex(
346                locked.raw,
347                path.as_ptr() as *const _,
348                path.as_bytes().len() as c_uint,
349                target.as_ptr() as *mut _,
350                target.as_bytes().len() as c_uint,
351                raw::LIBSSH2_SFTP_SYMLINK,
352            )
353        })
354    }
355
356    /// Read a symlink at `path`.
357    pub fn readlink(&self, path: &Path) -> Result<PathBuf, Error> {
358        self.readlink_op(path, raw::LIBSSH2_SFTP_READLINK)
359    }
360
361    /// Resolve the real path for `path`.
362    pub fn realpath(&self, path: &Path) -> Result<PathBuf, Error> {
363        self.readlink_op(path, raw::LIBSSH2_SFTP_REALPATH)
364    }
365
366    fn readlink_op(&self, path: &Path, op: c_int) -> Result<PathBuf, Error> {
367        let path = CString::new(util::path2bytes(path)?)?;
368        let mut ret = Vec::<u8>::with_capacity(128);
369        let mut rc;
370        let locked = self.lock()?;
371        loop {
372            rc = unsafe {
373                raw::libssh2_sftp_symlink_ex(
374                    locked.raw,
375                    path.as_ptr() as *const _,
376                    path.as_bytes().len() as c_uint,
377                    ret.as_ptr() as *mut _,
378                    ret.capacity() as c_uint,
379                    op,
380                )
381            };
382            if rc == raw::LIBSSH2_ERROR_BUFFER_TOO_SMALL {
383                let cap = ret.capacity();
384                ret.reserve(cap * 2);
385            } else {
386                break;
387            }
388        }
389        Self::rc(&locked, rc).map(move |_| {
390            unsafe { ret.set_len(rc as usize) }
391            mkpath(ret)
392        })
393    }
394
395    /// Rename a filesystem object on the remote filesystem.
396    ///
397    /// The semantics of this command typically include the ability to move a
398    /// filesystem object between folders and/or filesystem mounts. If the
399    /// `Overwrite` flag is not set and the destfile entry already exists, the
400    /// operation will fail.
401    ///
402    /// Use of the other flags (Native or Atomic) indicate a preference (but
403    /// not a requirement) for the remote end to perform an atomic rename
404    /// operation and/or using native system calls when possible.
405    ///
406    /// If no flags are specified then all flags are used.
407    pub fn rename(&self, src: &Path, dst: &Path, flags: Option<RenameFlags>) -> Result<(), Error> {
408        let flags =
409            flags.unwrap_or(RenameFlags::ATOMIC | RenameFlags::OVERWRITE | RenameFlags::NATIVE);
410        let src = CString::new(util::path2bytes(src)?)?;
411        let dst = CString::new(util::path2bytes(dst)?)?;
412        let locked = self.lock()?;
413        Self::rc(&locked, unsafe {
414            raw::libssh2_sftp_rename_ex(
415                locked.raw,
416                src.as_ptr() as *const _,
417                src.as_bytes().len() as c_uint,
418                dst.as_ptr() as *const _,
419                dst.as_bytes().len() as c_uint,
420                flags.bits(),
421            )
422        })
423    }
424
425    /// Remove a file on the remote filesystem
426    pub fn unlink(&self, file: &Path) -> Result<(), Error> {
427        let file = CString::new(util::path2bytes(file)?)?;
428        let locked = self.lock()?;
429        Self::rc(&locked, unsafe {
430            raw::libssh2_sftp_unlink_ex(
431                locked.raw,
432                file.as_ptr() as *const _,
433                file.as_bytes().len() as c_uint,
434            )
435        })
436    }
437
438    fn lock(&self) -> Result<LockedSftp, Error> {
439        match self.inner.as_ref() {
440            Some(sftp_inner_drop_wrapper) => {
441                let sftp_inner = sftp_inner_drop_wrapper
442                    .0
443                    .as_ref()
444                    .expect("Never unset until shutdown, in which case inner is also unset");
445                let sess = sftp_inner.sess.lock();
446                Ok(LockedSftp {
447                    sess,
448                    raw: sftp_inner.raw,
449                })
450            }
451            None => Err(Error::from_errno(ErrorCode::Session(
452                raw::LIBSSH2_ERROR_BAD_USE,
453            ))),
454        }
455    }
456
457    // This method is used by the async ssh crate
458    #[doc(hidden)]
459    pub fn shutdown(&mut self) -> Result<(), Error> {
460        // We cannot shutdown the SFTP if files are still open, etc, as these store a ref to the sftp in libssh2.
461        // We have to make sure we are the last reference to it.
462        match self.inner.take() {
463            Some(sftp_inner_arc) => {
464                // We were not already un-initialized
465                match Arc::try_unwrap(sftp_inner_arc) {
466                    Ok(mut sftp_inner_wrapper) => {
467                        // Early drop
468                        let sftp_inner = sftp_inner_wrapper.0.take().expect(
469                            "We were holding an Arc<SftpInnerDropWrapper>, \
470                                    so nobody could unset this (set on creation)",
471                        );
472                        sftp_inner
473                            .sess
474                            .lock()
475                            .rc(unsafe { raw::libssh2_sftp_shutdown(sftp_inner.raw) })?;
476                        Ok(())
477                    }
478                    Err(sftp_inner_arc) => {
479                        // We are failing shutdown as there are files left open, keep this object usable
480                        self.inner = Some(sftp_inner_arc);
481                        Err(Error::from_errno(ErrorCode::Session(
482                            raw::LIBSSH2_ERROR_BAD_USE,
483                        )))
484                    }
485                }
486            }
487            None => {
488                // We have already shut this down. Shutting down twice is a mistake from the caller code
489                Err(Error::from_errno(ErrorCode::Session(
490                    raw::LIBSSH2_ERROR_BAD_USE,
491                )))
492            }
493        }
494    }
495
496    fn error_code_into_error(
497        session_raw: *mut raw::LIBSSH2_SESSION,
498        sftp_raw: *mut raw::LIBSSH2_SFTP,
499        rc: libc::c_int,
500    ) -> Error {
501        if rc >= 0 {
502            Error::unknown()
503        } else if rc == raw::LIBSSH2_ERROR_SFTP_PROTOCOL {
504            let actual_rc = unsafe { raw::libssh2_sftp_last_error(sftp_raw) };
505            // TODO: This conversion from `c_ulong` to `c_int` should not be
506            // necessary if the constants `LIBSSH2_FX_*` in the `-sys` crate
507            // are typed as `c_ulong`, as they should be.
508            if let Ok(actual_rc) = libc::c_int::try_from(actual_rc) {
509                Error::from_errno(ErrorCode::SFTP(actual_rc))
510            } else {
511                Error::unknown()
512            }
513        } else {
514            Error::from_session_error_raw(session_raw, rc)
515        }
516    }
517
518    fn error_code_into_result(
519        session_raw: *mut raw::LIBSSH2_SESSION,
520        sftp_raw: *mut raw::LIBSSH2_SFTP,
521        rc: libc::c_int,
522    ) -> Result<(), Error> {
523        if rc >= 0 {
524            Ok(())
525        } else {
526            Err(Self::error_code_into_error(session_raw, sftp_raw, rc))
527        }
528    }
529
530    fn rc(locked: &LockedSftp, rc: libc::c_int) -> Result<(), Error> {
531        Self::error_code_into_result(locked.sess.raw, locked.raw, rc)
532    }
533}
534
535impl Drop for SftpInnerDropWrapper {
536    fn drop(&mut self) {
537        // Check we were not early-dropped
538        if let Some(inner) = self.0.take() {
539            let sess = inner.sess.lock();
540            // Set ssh2 to blocking during the drop
541            let was_blocking = sess.is_blocking();
542            sess.set_blocking(true);
543            // The shutdown statement can go wrong and return an error code, but we are too late
544            // in the execution to recover it.
545            let _shutdown_result = unsafe { raw::libssh2_sftp_shutdown(inner.raw) };
546            sess.set_blocking(was_blocking);
547        }
548    }
549}
550
551impl File {
552    /// Wraps a raw pointer in a new File structure tied to the lifetime of the
553    /// given session.
554    ///
555    /// This consumes ownership of `raw`.
556    unsafe fn from_raw(sftp: &Sftp, raw: *mut raw::LIBSSH2_SFTP_HANDLE) -> File {
557        File {
558            inner: Some(FileInner {
559                raw,
560                sftp: Arc::clone(
561                    &sftp
562                        .inner
563                        .as_ref()
564                        .expect("Cannot open file after sftp shutdown"),
565                ),
566            }),
567        }
568    }
569
570    /// Set the metadata for this handle.
571    pub fn setstat(&mut self, stat: FileStat) -> Result<(), Error> {
572        let locked = self.lock()?;
573        self.rc(&locked, unsafe {
574            let mut raw = stat.raw();
575            raw::libssh2_sftp_fstat_ex(locked.raw, &mut raw, 1)
576        })
577    }
578
579    /// Get the metadata for this handle.
580    pub fn stat(&mut self) -> Result<FileStat, Error> {
581        let locked = self.lock()?;
582        unsafe {
583            let mut ret = mem::zeroed();
584            self.rc(&locked, raw::libssh2_sftp_fstat_ex(locked.raw, &mut ret, 0))
585                .map(|_| FileStat::from_raw(&ret))
586        }
587    }
588
589    #[allow(missing_docs)] // sure wish I knew what this did...
590    pub fn statvfs(&mut self) -> Result<raw::LIBSSH2_SFTP_STATVFS, Error> {
591        let locked = self.lock()?;
592        unsafe {
593            let mut ret = mem::zeroed();
594            self.rc(&locked, raw::libssh2_sftp_fstatvfs(locked.raw, &mut ret))
595                .map(move |_| ret)
596        }
597    }
598
599    /// Reads a block of data from a handle and returns file entry information
600    /// for the next entry, if any.
601    ///
602    /// Note that this provides raw access to the `readdir` function from
603    /// libssh2. This will return an error when there are no more files to
604    /// read, and files such as `.` and `..` will be included in the return
605    /// values.
606    ///
607    /// Also note that the return paths will not be absolute paths, they are
608    /// the filenames of the files in this directory.
609    pub fn readdir(&mut self) -> Result<(PathBuf, FileStat), Error> {
610        let locked = self.lock()?;
611
612        // libssh2 through 1.10.0 skips entries if the buffer
613        // is not large enough: it's not enough to resize and try again
614        // on getting an error. So, we make it quite large here.
615        // See <https://github.com/alexcrichton/ssh2-rs/issues/217>.
616        let mut buf = Vec::<u8>::with_capacity(4 * 1024);
617        let mut stat = unsafe { mem::zeroed() };
618        let mut rc;
619        loop {
620            rc = unsafe {
621                raw::libssh2_sftp_readdir_ex(
622                    locked.raw,
623                    buf.as_mut_ptr() as *mut _,
624                    buf.capacity() as size_t,
625                    null_mut(),
626                    0,
627                    &mut stat,
628                )
629            };
630            if rc == raw::LIBSSH2_ERROR_BUFFER_TOO_SMALL {
631                let cap = buf.capacity();
632                buf.reserve(cap * 2);
633            } else {
634                break;
635            }
636        }
637        if rc == 0 {
638            Err(Error::new(
639                ErrorCode::Session(raw::LIBSSH2_ERROR_FILE),
640                "no more files",
641            ))
642        } else {
643            self.rc(&locked, rc).map(move |_| {
644                unsafe {
645                    buf.set_len(rc as usize);
646                }
647                (mkpath(buf), FileStat::from_raw(&stat))
648            })
649        }
650    }
651
652    /// This function causes the remote server to synchronize the file data and
653    /// metadata to disk (like fsync(2)).
654    ///
655    /// For this to work requires fsync@openssh.com support on the server.
656    pub fn fsync(&mut self) -> Result<(), Error> {
657        let locked = self.lock()?;
658        self.rc(&locked, unsafe { raw::libssh2_sftp_fsync(locked.raw) })
659    }
660
661    fn lock(&self) -> Result<LockedFile, Error> {
662        match self.inner.as_ref() {
663            Some(file_inner) => {
664                let sftp_inner = file_inner.sftp.0.as_ref().expect(
665                    "We are holding an Arc<SftpInnerDropWrapper>, \
666                        so nobody could unset this (set on creation)",
667                );
668                let sess = sftp_inner.sess.lock();
669                Ok(LockedFile {
670                    sess,
671                    raw: file_inner.raw,
672                })
673            }
674            None => Err(Error::from_errno(ErrorCode::Session(
675                raw::LIBSSH2_ERROR_BAD_USE,
676            ))),
677        }
678    }
679
680    #[doc(hidden)]
681    pub fn close(&mut self) -> Result<(), Error> {
682        let rc = {
683            let locked = self.lock()?;
684            self.rc(&locked, unsafe {
685                raw::libssh2_sftp_close_handle(locked.raw)
686            })
687        };
688
689        // If EGAIN was returned, we'll need to call this again to complete the operation.
690        // If any other error was returned, or if it completed OK, we must not use the
691        // handle again.
692        match rc {
693            Err(e) if e.code() == ErrorCode::Session(raw::LIBSSH2_ERROR_EAGAIN) => Err(e),
694            rc => {
695                self.inner = None;
696                rc
697            }
698        }
699    }
700
701    fn rc(&self, locked: &LockedFile, rc: libc::c_int) -> Result<(), Error> {
702        if let Some(file_inner) = self.inner.as_ref() {
703            let sftp_inner = file_inner.sftp.0.as_ref().expect(
704                "We are holding an Arc<SftpInnerDropWrapper>, \
705                        so nobody could unset this (set on creation)",
706            );
707            Sftp::error_code_into_result(locked.sess.raw, sftp_inner.raw, rc)
708        } else if rc < 0 {
709            Err(Error::from_errno(ErrorCode::Session(rc)))
710        } else {
711            Ok(())
712        }
713    }
714}
715
716impl Read for File {
717    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
718        let locked = self.lock()?;
719        let rc = unsafe {
720            raw::libssh2_sftp_read(locked.raw, buf.as_mut_ptr() as *mut _, buf.len() as size_t)
721        };
722        if rc < 0 {
723            let rc = rc as libc::c_int;
724            if let Some(file_inner) = self.inner.as_ref() {
725                let sftp_inner = file_inner.sftp.0.as_ref().expect(
726                    "We are holding an Arc<SftpInnerDropWrapper>, \
727                        so nobody could unset this (set on creation)",
728                );
729                Err(Sftp::error_code_into_error(locked.sess.raw, sftp_inner.raw, rc).into())
730            } else {
731                Err(Error::from_errno(ErrorCode::Session(rc)).into())
732            }
733        } else {
734            Ok(rc as usize)
735        }
736    }
737}
738
739impl Write for File {
740    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
741        let locked = self.lock()?;
742        let rc = unsafe {
743            raw::libssh2_sftp_write(locked.raw, buf.as_ptr() as *const _, buf.len() as size_t)
744        };
745        if rc < 0 {
746            let rc = rc as libc::c_int;
747            if let Some(file_inner) = self.inner.as_ref() {
748                let sftp_inner = file_inner.sftp.0.as_ref().expect(
749                    "We are holding an Arc<SftpInnerDropWrapper>, \
750                        so nobody could unset this (set on creation)",
751                );
752                Err(Sftp::error_code_into_error(locked.sess.raw, sftp_inner.raw, rc).into())
753            } else {
754                Err(Error::from_errno(ErrorCode::Session(rc)).into())
755            }
756        } else {
757            Ok(rc as usize)
758        }
759    }
760
761    fn flush(&mut self) -> io::Result<()> {
762        Ok(())
763    }
764}
765
766impl Seek for File {
767    /// Move the file handle's internal pointer to an arbitrary location.
768    ///
769    /// libssh2 implements file pointers as a localized concept to make file
770    /// access appear more POSIX like. No packets are exchanged with the server
771    /// during a seek operation. The localized file pointer is simply used as a
772    /// convenience offset during read/write operations.
773    ///
774    /// You MUST NOT seek during writing or reading a file with SFTP, as the
775    /// internals use outstanding packets and changing the "file position"
776    /// during transit will results in badness.
777    fn seek(&mut self, how: SeekFrom) -> io::Result<u64> {
778        let next = match how {
779            SeekFrom::Start(pos) => pos,
780            SeekFrom::Current(offset) => {
781                let locked = self.lock()?;
782                let cur = unsafe { raw::libssh2_sftp_tell64(locked.raw) };
783                (cur as i64 + offset) as u64
784            }
785            SeekFrom::End(offset) => match self.stat() {
786                Ok(s) => match s.size {
787                    Some(size) => (size as i64 + offset) as u64,
788                    None => return Err(io::Error::new(ErrorKind::Other, "no file size available")),
789                },
790                Err(e) => return Err(io::Error::new(ErrorKind::Other, e)),
791            },
792        };
793        let locked = self.lock()?;
794        unsafe { raw::libssh2_sftp_seek64(locked.raw, next) }
795        Ok(next)
796    }
797}
798
799impl Drop for File {
800    fn drop(&mut self) {
801        // Set ssh2 to blocking if the file was not closed yet (by .close()).
802        if let Some(file_inner) = self.inner.take() {
803            let sftp_inner = file_inner.sftp.0.as_ref().expect(
804                "We are holding an Arc<SftpInnerDropWrapper>, \
805                    so nobody could unset this (set on creation)",
806            );
807            let sess_inner = sftp_inner.sess.lock();
808            let was_blocking = sess_inner.is_blocking();
809            sess_inner.set_blocking(true);
810            // The close statement can go wrong and return an error code, but we are too late
811            // in the execution to recover it.
812            let _close_handle_result = unsafe { raw::libssh2_sftp_close_handle(file_inner.raw) };
813            sess_inner.set_blocking(was_blocking);
814        }
815    }
816}
817
818impl FileStat {
819    /// Returns the file type for this filestat.
820    pub fn file_type(&self) -> FileType {
821        FileType::from_perm(self.perm.unwrap_or(0) as c_ulong)
822    }
823
824    /// Returns whether this metadata is for a directory.
825    pub fn is_dir(&self) -> bool {
826        self.file_type().is_dir()
827    }
828
829    /// Returns whether this metadata is for a regular file.
830    pub fn is_file(&self) -> bool {
831        self.file_type().is_file()
832    }
833
834    /// Creates a new instance of a stat from a raw instance.
835    pub fn from_raw(raw: &raw::LIBSSH2_SFTP_ATTRIBUTES) -> FileStat {
836        fn val<T: Copy>(raw: &raw::LIBSSH2_SFTP_ATTRIBUTES, t: &T, flag: c_ulong) -> Option<T> {
837            if raw.flags & flag != 0 {
838                Some(*t)
839            } else {
840                None
841            }
842        }
843
844        FileStat {
845            size: val(raw, &raw.filesize, raw::LIBSSH2_SFTP_ATTR_SIZE),
846            uid: val(raw, &raw.uid, raw::LIBSSH2_SFTP_ATTR_UIDGID).map(|s| s as u32),
847            gid: val(raw, &raw.gid, raw::LIBSSH2_SFTP_ATTR_UIDGID).map(|s| s as u32),
848            perm: val(raw, &raw.permissions, raw::LIBSSH2_SFTP_ATTR_PERMISSIONS).map(|s| s as u32),
849            mtime: val(raw, &raw.mtime, raw::LIBSSH2_SFTP_ATTR_ACMODTIME).map(|s| s as u64),
850            atime: val(raw, &raw.atime, raw::LIBSSH2_SFTP_ATTR_ACMODTIME).map(|s| s as u64),
851        }
852    }
853
854    /// Convert this stat structure to its raw representation.
855    pub fn raw(&self) -> raw::LIBSSH2_SFTP_ATTRIBUTES {
856        fn flag<T>(o: &Option<T>, flag: c_ulong) -> c_ulong {
857            if o.is_some() {
858                flag
859            } else {
860                0
861            }
862        }
863
864        raw::LIBSSH2_SFTP_ATTRIBUTES {
865            flags: flag(&self.size, raw::LIBSSH2_SFTP_ATTR_SIZE)
866                | flag(&self.uid, raw::LIBSSH2_SFTP_ATTR_UIDGID)
867                | flag(&self.gid, raw::LIBSSH2_SFTP_ATTR_UIDGID)
868                | flag(&self.perm, raw::LIBSSH2_SFTP_ATTR_PERMISSIONS)
869                | flag(&self.atime, raw::LIBSSH2_SFTP_ATTR_ACMODTIME)
870                | flag(&self.mtime, raw::LIBSSH2_SFTP_ATTR_ACMODTIME),
871            filesize: self.size.unwrap_or(0),
872            uid: self.uid.unwrap_or(0) as c_ulong,
873            gid: self.gid.unwrap_or(0) as c_ulong,
874            permissions: self.perm.unwrap_or(0) as c_ulong,
875            atime: self.atime.unwrap_or(0) as c_ulong,
876            mtime: self.mtime.unwrap_or(0) as c_ulong,
877        }
878    }
879}
880
881impl FileType {
882    /// Test whether this file type represents a directory.
883    pub fn is_dir(&self) -> bool {
884        self == &FileType::Directory
885    }
886
887    /// Test whether this file type represents a regular file.
888    pub fn is_file(&self) -> bool {
889        self == &FileType::RegularFile
890    }
891
892    /// Test whether this file type represents a symbolic link.
893    pub fn is_symlink(&self) -> bool {
894        self == &FileType::Symlink
895    }
896
897    fn from_perm(perm: c_ulong) -> Self {
898        match perm & raw::LIBSSH2_SFTP_S_IFMT {
899            raw::LIBSSH2_SFTP_S_IFIFO => FileType::NamedPipe,
900            raw::LIBSSH2_SFTP_S_IFCHR => FileType::CharDevice,
901            raw::LIBSSH2_SFTP_S_IFDIR => FileType::Directory,
902            raw::LIBSSH2_SFTP_S_IFBLK => FileType::BlockDevice,
903            raw::LIBSSH2_SFTP_S_IFREG => FileType::RegularFile,
904            raw::LIBSSH2_SFTP_S_IFLNK => FileType::Symlink,
905            raw::LIBSSH2_SFTP_S_IFSOCK => FileType::Socket,
906            other => FileType::Other(other),
907        }
908    }
909}
910
911#[cfg(unix)]
912fn mkpath(v: Vec<u8>) -> PathBuf {
913    use std::ffi::OsStr;
914    use std::os::unix::prelude::*;
915    PathBuf::from(OsStr::from_bytes(&v))
916}
917#[cfg(windows)]
918fn mkpath(v: Vec<u8>) -> PathBuf {
919    use std::str;
920    PathBuf::from(str::from_utf8(&v).unwrap())
921}