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
15pub struct Sftp {
19 inner: Option<Arc<SftpInnerDropWrapper>>,
20}
21struct SftpInnerDropWrapper(Option<SftpInner>);
24struct SftpInner {
25 raw: *mut raw::LIBSSH2_SFTP,
26 sess: Arc<Mutex<SessionInner>>,
27}
28
29unsafe 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
40pub struct File {
48 inner: Option<FileInner>,
49}
50struct FileInner {
51 raw: *mut raw::LIBSSH2_SFTP_HANDLE,
52 sftp: Arc<SftpInnerDropWrapper>,
53}
54
55unsafe 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#[derive(Debug, Clone, Eq, PartialEq)]
70#[allow(missing_copy_implementations)]
71pub struct FileStat {
72 pub size: Option<u64>,
74 pub uid: Option<u32>,
76 pub gid: Option<u32>,
78 pub perm: Option<u32>,
80 pub atime: Option<u64>,
82 pub mtime: Option<u64>,
84}
85
86#[derive(PartialEq)]
88pub enum FileType {
89 NamedPipe,
91 CharDevice,
93 BlockDevice,
95 Directory,
97 RegularFile,
99 Symlink,
101 Socket,
103 Other(c_ulong),
105}
106
107bitflags! {
108 #[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Clone, Copy)]
110 pub struct OpenFlags: c_ulong {
111 const READ = raw::LIBSSH2_FXF_READ;
113 const WRITE = raw::LIBSSH2_FXF_WRITE;
116 const APPEND = raw::LIBSSH2_FXF_APPEND;
118 const CREATE = raw::LIBSSH2_FXF_CREAT;
122 const TRUNCATE = raw::LIBSSH2_FXF_TRUNC | Self::CREATE.bits();
126 const EXCLUSIVE = raw::LIBSSH2_FXF_EXCL | Self::CREATE.bits();
129 }
130}
131
132bitflags! {
133 #[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Clone, Copy)]
135 pub struct RenameFlags: c_long {
136 const OVERWRITE = raw::LIBSSH2_SFTP_RENAME_OVERWRITE;
140 const ATOMIC = raw::LIBSSH2_SFTP_RENAME_ATOMIC;
143 const NATIVE = raw::LIBSSH2_SFTP_RENAME_NATIVE;
146 }
147}
148
149#[derive(Copy, Clone)]
151pub enum OpenType {
152 File = raw::LIBSSH2_SFTP_OPENFILE as isize,
154 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 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 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 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 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 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 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 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 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 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 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 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 pub fn readlink(&self, path: &Path) -> Result<PathBuf, Error> {
358 self.readlink_op(path, raw::LIBSSH2_SFTP_READLINK)
359 }
360
361 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 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 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 #[doc(hidden)]
459 pub fn shutdown(&mut self) -> Result<(), Error> {
460 match self.inner.take() {
463 Some(sftp_inner_arc) => {
464 match Arc::try_unwrap(sftp_inner_arc) {
466 Ok(mut sftp_inner_wrapper) => {
467 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 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 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 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 if let Some(inner) = self.0.take() {
539 let sess = inner.sess.lock();
540 let was_blocking = sess.is_blocking();
542 sess.set_blocking(true);
543 let _shutdown_result = unsafe { raw::libssh2_sftp_shutdown(inner.raw) };
546 sess.set_blocking(was_blocking);
547 }
548 }
549}
550
551impl File {
552 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 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 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)] 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 pub fn readdir(&mut self) -> Result<(PathBuf, FileStat), Error> {
610 let locked = self.lock()?;
611
612 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 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 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 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 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 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 pub fn file_type(&self) -> FileType {
821 FileType::from_perm(self.perm.unwrap_or(0) as c_ulong)
822 }
823
824 pub fn is_dir(&self) -> bool {
826 self.file_type().is_dir()
827 }
828
829 pub fn is_file(&self) -> bool {
831 self.file_type().is_file()
832 }
833
834 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 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 pub fn is_dir(&self) -> bool {
884 self == &FileType::Directory
885 }
886
887 pub fn is_file(&self) -> bool {
889 self == &FileType::RegularFile
890 }
891
892 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}