libssh_rs/
sftp.rs

1use crate::{Error, SessionHolder, SshResult};
2use libssh_rs_sys as sys;
3use std::convert::TryInto;
4use std::ffi::{CStr, CString};
5use std::os::raw::{c_char, c_int};
6use std::sync::{Arc, Mutex, MutexGuard};
7use std::time::{Duration, SystemTime};
8use thiserror::Error;
9
10#[derive(Error, Debug, PartialEq, Eq)]
11#[error("Sftp error code {}", .0)]
12pub struct SftpError(u32);
13
14impl SftpError {
15    pub(crate) fn from_session(sftp: sys::sftp_session) -> Self {
16        let code = unsafe { sys::sftp_get_error(sftp) as u32 };
17        Self(code)
18    }
19
20    pub(crate) fn result<T>(sftp: sys::sftp_session, status: i32, res: T) -> SshResult<T> {
21        if status == sys::SSH_OK as i32 {
22            Ok(res)
23        } else {
24            Err(Error::Sftp(SftpError::from_session(sftp)))
25        }
26    }
27}
28
29pub struct Sftp {
30    pub(crate) sess: Arc<Mutex<SessionHolder>>,
31    pub(crate) sftp_inner: sys::sftp_session,
32}
33
34unsafe impl Send for Sftp {}
35
36impl Drop for Sftp {
37    fn drop(&mut self) {
38        let (_sess, sftp) = self.lock_session();
39        unsafe {
40            sys::sftp_free(sftp);
41        }
42    }
43}
44
45impl Sftp {
46    fn lock_session(&self) -> (MutexGuard<SessionHolder>, sys::sftp_session) {
47        (self.sess.lock().unwrap(), self.sftp_inner)
48    }
49
50    pub(crate) fn init(&self) -> SshResult<()> {
51        let (_sess, sftp) = self.lock_session();
52        let res = unsafe { sys::sftp_init(sftp) };
53        SftpError::result(sftp, res, ())
54    }
55
56    /// Create a directory.
57    /// `mode` specifies the permission bits to use on the directory.
58    /// They will be modified by the effective umask on the server.
59    pub fn create_dir(&self, filename: &str, mode: sys::mode_t) -> SshResult<()> {
60        let filename = CString::new(filename)?;
61        let (_sess, sftp) = self.lock_session();
62        let res = unsafe { sys::sftp_mkdir(sftp, filename.as_ptr(), mode) };
63        SftpError::result(sftp, res, ())
64    }
65
66    /// Canonicalize `filename`, resolving relative directory references
67    /// and symlinks.
68    pub fn canonicalize(&self, filename: &str) -> SshResult<String> {
69        let filename = CString::new(filename)?;
70        let (_sess, sftp) = self.lock_session();
71        let res = unsafe { sys::sftp_canonicalize_path(sftp, filename.as_ptr()) };
72        if res.is_null() {
73            Err(Error::Sftp(SftpError::from_session(sftp)))
74        } else {
75            let result = unsafe { CStr::from_ptr(res) }.to_string_lossy().to_string();
76            unsafe { sys::ssh_string_free_char(res) };
77            Ok(result)
78        }
79    }
80
81    /// Change the permissions of a file
82    pub fn chmod(&self, filename: &str, mode: sys::mode_t) -> SshResult<()> {
83        let filename = CString::new(filename)?;
84        let (_sess, sftp) = self.lock_session();
85        let res = unsafe { sys::sftp_chmod(sftp, filename.as_ptr(), mode) };
86        SftpError::result(sftp, res, ())
87    }
88
89    /// Change the ownership of a file.
90    pub fn chown(&self, filename: &str, owner: sys::uid_t, group: sys::gid_t) -> SshResult<()> {
91        let filename = CString::new(filename)?;
92        let (_sess, sftp) = self.lock_session();
93        let res = unsafe { sys::sftp_chown(sftp, filename.as_ptr(), owner, group) };
94        SftpError::result(sftp, res, ())
95    }
96
97    /// Read the payload of a symlink
98    pub fn read_link(&self, filename: &str) -> SshResult<String> {
99        let filename = CString::new(filename)?;
100        let (_sess, sftp) = self.lock_session();
101        let res = unsafe { sys::sftp_readlink(sftp, filename.as_ptr()) };
102        if res.is_null() {
103            Err(Error::Sftp(SftpError::from_session(sftp)))
104        } else {
105            let result = unsafe { CStr::from_ptr(res) }.to_string_lossy().to_string();
106            unsafe { sys::ssh_string_free_char(res) };
107            Ok(result)
108        }
109    }
110
111    /// Change certain metadata attributes of the named file.
112    pub fn set_metadata(&self, filename: &str, metadata: &SetAttributes) -> SshResult<()> {
113        let filename = CString::new(filename)?;
114        let (_sess, sftp) = self.lock_session();
115        let mut attributes: sys::sftp_attributes_struct = unsafe { std::mem::zeroed() };
116
117        if let Some(size) = metadata.size {
118            attributes.size = size;
119            attributes.flags |= sys::SSH_FILEXFER_ATTR_SIZE;
120        }
121
122        if let Some((uid, gid)) = metadata.uid_gid {
123            attributes.uid = uid;
124            attributes.gid = gid;
125            attributes.flags |= sys::SSH_FILEXFER_ATTR_UIDGID;
126        }
127
128        if let Some(perms) = metadata.permissions {
129            attributes.permissions = perms;
130            attributes.flags |= sys::SSH_FILEXFER_ATTR_PERMISSIONS;
131        }
132
133        if let Some((atime, mtime)) = metadata.atime_mtime {
134            attributes.atime = atime
135                .duration_since(SystemTime::UNIX_EPOCH)
136                .expect("SystemTime to always be > UNIX_EPOCH")
137                .as_secs()
138                .try_into()
139                .unwrap();
140            attributes.mtime = mtime
141                .duration_since(SystemTime::UNIX_EPOCH)
142                .expect("SystemTime to always be > UNIX_EPOCH")
143                .as_secs()
144                .try_into()
145                .unwrap();
146            attributes.flags |= sys::SSH_FILEXFER_ATTR_ACMODTIME;
147        }
148
149        let res = unsafe { sys::sftp_setstat(sftp, filename.as_ptr(), &mut attributes) };
150        SftpError::result(sftp, res, ())
151    }
152
153    /// Retrieve metadata for a file, traversing symlinks
154    pub fn metadata(&self, filename: &str) -> SshResult<Metadata> {
155        let filename = CString::new(filename)?;
156        let (_sess, sftp) = self.lock_session();
157        let attr = unsafe { sys::sftp_stat(sftp, filename.as_ptr()) };
158        if attr.is_null() {
159            Err(Error::Sftp(SftpError::from_session(sftp)))
160        } else {
161            Ok(Metadata { attr })
162        }
163    }
164
165    /// Retrieve metadata for a file, without traversing symlinks.
166    pub fn symlink_metadata(&self, filename: &str) -> SshResult<Metadata> {
167        let filename = CString::new(filename)?;
168        let (_sess, sftp) = self.lock_session();
169        let attr = unsafe { sys::sftp_lstat(sftp, filename.as_ptr()) };
170        if attr.is_null() {
171            Err(Error::Sftp(SftpError::from_session(sftp)))
172        } else {
173            Ok(Metadata { attr })
174        }
175    }
176
177    /// Rename a file from `filename` to `new_name`
178    pub fn rename(&self, filename: &str, new_name: &str) -> SshResult<()> {
179        let filename = CString::new(filename)?;
180        let new_name = CString::new(new_name)?;
181        let (_sess, sftp) = self.lock_session();
182        let res = unsafe { sys::sftp_rename(sftp, filename.as_ptr(), new_name.as_ptr()) };
183        SftpError::result(sftp, res, ())
184    }
185
186    /// Remove a file or an empty directory
187    pub fn remove_file(&self, filename: &str) -> SshResult<()> {
188        let filename = CString::new(filename)?;
189        let (_sess, sftp) = self.lock_session();
190        let res = unsafe { sys::sftp_unlink(sftp, filename.as_ptr()) };
191        SftpError::result(sftp, res, ())
192    }
193
194    /// Remove an empty directory
195    pub fn remove_dir(&self, filename: &str) -> SshResult<()> {
196        let filename = CString::new(filename)?;
197        let (_sess, sftp) = self.lock_session();
198        let res = unsafe { sys::sftp_rmdir(sftp, filename.as_ptr()) };
199        SftpError::result(sftp, res, ())
200    }
201
202    /// Create a symlink on the server.
203    /// `target` is the filename of the symlink to be created,
204    /// and `dest` is the payload of the symlink.
205    pub fn symlink(&self, target: &str, dest: &str) -> SshResult<()> {
206        let target = CString::new(target)?;
207        let dest = CString::new(dest)?;
208        let (_sess, sftp) = self.lock_session();
209        let res = unsafe { sys::sftp_symlink(sftp, target.as_ptr(), dest.as_ptr()) };
210        SftpError::result(sftp, res, ())
211    }
212
213    /// Open a file on the server.
214    /// `accesstype` corresponds to the `open(2)` `flags` parameter
215    /// and controls whether the file is opened for read/write and so on.
216    /// `mode` specified the permission bits to use when creating a new file;
217    /// they will be modified by the effective umask on the server side.
218    pub fn open(
219        &self,
220        filename: &str,
221        accesstype: OpenFlags,
222        mode: sys::mode_t,
223    ) -> SshResult<SftpFile> {
224        let filename = CString::new(filename)?;
225        let (_sess, sftp) = self.lock_session();
226        let res = unsafe { sys::sftp_open(sftp, filename.as_ptr(), accesstype.bits(), mode) };
227        if res.is_null() {
228            Err(Error::Sftp(SftpError::from_session(sftp)))
229        } else {
230            Ok(SftpFile {
231                sess: Arc::clone(&self.sess),
232                file_inner: res,
233                sftp: sftp,
234            })
235        }
236    }
237
238    /// Open a directory to obtain directory entries
239    pub fn open_dir(&self, filename: &str) -> SshResult<SftpDir> {
240        let filename = CString::new(filename)?;
241        let (_sess, sftp) = self.lock_session();
242        let res = unsafe { sys::sftp_opendir(sftp, filename.as_ptr()) };
243        if res.is_null() {
244            Err(Error::Sftp(SftpError::from_session(sftp)))
245        } else {
246            Ok(SftpDir {
247                sess: Arc::clone(&self.sess),
248                dir_inner: res,
249                sftp: sftp,
250            })
251        }
252    }
253
254    /// Convenience function that reads all of the directory entries
255    /// into a Vec.  If you need to deal with very large directories,
256    /// you may wish to directly use [open_dir](#method.open_dir)
257    /// and manually iterate the directory contents.
258    pub fn read_dir(&self, filename: &str) -> SshResult<Vec<Metadata>> {
259        let dir = self.open_dir(filename)?;
260        let mut res = vec![];
261        while let Some(item) = dir.read_dir() {
262            res.push(item?);
263        }
264        Ok(res)
265    }
266}
267
268pub struct SftpFile {
269    pub(crate) sess: Arc<Mutex<SessionHolder>>,
270    pub(crate) file_inner: sys::sftp_file,
271    pub(crate) sftp: sys::sftp_session,
272}
273
274unsafe impl Send for SftpFile {}
275
276impl Drop for SftpFile {
277    fn drop(&mut self) {
278        let (_sess, file) = self.lock_session();
279        unsafe {
280            sys::sftp_close(file);
281        }
282    }
283}
284
285impl SftpFile {
286    fn lock_session(&self) -> (MutexGuard<SessionHolder>, sys::sftp_file) {
287        (self.sess.lock().unwrap(), self.file_inner)
288    }
289
290    pub fn set_blocking(&self, blocking: bool) {
291        let (_sess, file) = self.lock_session();
292        if blocking {
293            unsafe { sys::sftp_file_set_blocking(file) }
294        } else {
295            unsafe { sys::sftp_file_set_nonblocking(file) }
296        }
297    }
298
299    /// Retrieve metadata for the file
300    pub fn metadata(&self) -> SshResult<Metadata> {
301        let (_sess, file) = self.lock_session();
302        let attr = unsafe { sys::sftp_fstat(file) };
303        if attr.is_null() {
304            Err(Error::Sftp(SftpError::from_session(self.sftp)))
305        } else {
306            Ok(Metadata { attr })
307        }
308    }
309}
310
311fn io_err_from_sftp(sftp: sys::sftp_session, reason: &str) -> std::io::Error {
312    use std::io::ErrorKind;
313    let res = unsafe { sys::sftp_get_error(sftp) };
314    let kind = match res as u32 {
315        sys::SSH_FX_OK => ErrorKind::Other,
316        sys::SSH_FX_EOF => ErrorKind::UnexpectedEof,
317        sys::SSH_FX_NO_SUCH_FILE => ErrorKind::NotFound,
318        sys::SSH_FX_PERMISSION_DENIED => ErrorKind::PermissionDenied,
319        sys::SSH_FX_FAILURE => ErrorKind::Other,
320        sys::SSH_FX_BAD_MESSAGE => ErrorKind::Other,
321        sys::SSH_FX_NO_CONNECTION => ErrorKind::NotConnected,
322        sys::SSH_FX_CONNECTION_LOST => ErrorKind::ConnectionReset,
323        sys::SSH_FX_OP_UNSUPPORTED => ErrorKind::Unsupported,
324        sys::SSH_FX_INVALID_HANDLE => ErrorKind::Other,
325        sys::SSH_FX_NO_SUCH_PATH => ErrorKind::NotFound,
326        sys::SSH_FX_FILE_ALREADY_EXISTS => ErrorKind::AlreadyExists,
327        sys::SSH_FX_WRITE_PROTECT => ErrorKind::Other,
328        sys::SSH_FX_NO_MEDIA => ErrorKind::Other,
329        _ => ErrorKind::Other,
330    };
331    std::io::Error::new(kind, format!("{}: sftp error code {}", reason, res))
332}
333
334impl std::io::Read for SftpFile {
335    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
336        let (_sess, file) = self.lock_session();
337
338        let res = unsafe { sys::sftp_read(file, buf.as_mut_ptr() as _, buf.len()) };
339
340        if res >= 0 {
341            Ok(res as usize)
342        } else {
343            let err = io_err_from_sftp(self.sftp, "read");
344            if err.kind() == std::io::ErrorKind::UnexpectedEof {
345                Ok(0)
346            } else {
347                Err(err)
348            }
349        }
350    }
351}
352
353impl std::io::Write for SftpFile {
354    fn flush(&mut self) -> std::io::Result<()> {
355        let (_sess, file) = self.lock_session();
356        let res = unsafe { sys::sftp_fsync(file) };
357        if res == 0 {
358            Ok(())
359        } else {
360            Err(io_err_from_sftp(self.sftp, "fsync"))
361        }
362    }
363
364    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
365        let (_sess, file) = self.lock_session();
366
367        let res = unsafe { sys::sftp_write(file, buf.as_ptr() as _, buf.len()) };
368
369        if res >= 0 {
370            Ok(res as usize)
371        } else {
372            let err = io_err_from_sftp(self.sftp, "write");
373            if err.kind() == std::io::ErrorKind::UnexpectedEof {
374                Ok(0)
375            } else {
376                Err(err)
377            }
378        }
379    }
380}
381
382impl std::io::Seek for SftpFile {
383    fn seek(&mut self, pos: std::io::SeekFrom) -> std::io::Result<u64> {
384        let (_sess, file) = self.lock_session();
385        match pos {
386            std::io::SeekFrom::Start(p) => {
387                let res = unsafe { sys::sftp_seek64(file, p) };
388                if res == 0 {
389                    Ok(p)
390                } else {
391                    Err(io_err_from_sftp(self.sftp, "seek"))
392                }
393            }
394            std::io::SeekFrom::End(p) => {
395                let end = self.metadata().map_err(|e| e)?.len().ok_or_else(|| {
396                    std::io::Error::new(
397                        std::io::ErrorKind::Other,
398                        "metadata didn't return the length",
399                    )
400                })?;
401                let target = if p < 0 {
402                    end.saturating_sub(p.abs() as u64)
403                } else {
404                    end.saturating_add(p as u64)
405                };
406                let res = unsafe { sys::sftp_seek64(file, target) };
407                if res == 0 {
408                    Ok(target)
409                } else {
410                    Err(io_err_from_sftp(self.sftp, "seek"))
411                }
412            }
413            std::io::SeekFrom::Current(p) => {
414                let current = unsafe { sys::sftp_tell64(file) };
415                let target = if p < 0 {
416                    current.saturating_sub(p.abs() as u64)
417                } else {
418                    current.saturating_add(p as u64)
419                };
420                let res = unsafe { sys::sftp_seek64(file, target) };
421                if res == 0 {
422                    Ok(target)
423                } else {
424                    Err(io_err_from_sftp(self.sftp, "seek"))
425                }
426            }
427        }
428    }
429
430    fn stream_position(&mut self) -> std::io::Result<u64> {
431        let (_sess, file) = self.lock_session();
432        let current = unsafe { sys::sftp_tell64(file) };
433        Ok(current)
434    }
435}
436
437/// Change multiple file attributes at once.
438/// If a field is_some, then its value will be applied
439/// to the file on the server side.  If it is_none, then
440/// that particular field will be left unmodified.
441#[derive(Debug, Clone, PartialEq, Eq)]
442pub struct SetAttributes {
443    /// Change the file length
444    pub size: Option<u64>,
445    /// Change the ownership (chown)
446    pub uid_gid: Option<(sys::uid_t, sys::gid_t)>,
447    /// Change the permissions (chmod)
448    pub permissions: Option<u32>,
449    /// Note that the protocol/libssh implementation has
450    /// 1-second granularity for access and mtime
451    pub atime_mtime: Option<(SystemTime, SystemTime)>,
452}
453
454/// Represents metadata about a file.
455/// libssh returns this in a couple of contexts, and not all
456/// fields are used in all contexts.
457pub struct Metadata {
458    attr: sys::sftp_attributes,
459}
460
461impl Drop for Metadata {
462    fn drop(&mut self) {
463        unsafe { sys::sftp_attributes_free(self.attr) }
464    }
465}
466
467impl Metadata {
468    fn attr(&self) -> &sys::sftp_attributes_struct {
469        unsafe { &*self.attr }
470    }
471
472    pub fn len(&self) -> Option<u64> {
473        if self.attr().flags & sys::SSH_FILEXFER_ATTR_SIZE != 0 {
474            Some(self.attr().size)
475        } else {
476            None
477        }
478    }
479
480    fn name_helper(&self, name: *const c_char) -> Option<&str> {
481        if name.is_null() {
482            None
483        } else {
484            unsafe { CStr::from_ptr(name) }.to_str().ok()
485        }
486    }
487
488    pub fn name(&self) -> Option<&str> {
489        self.name_helper(self.attr().name)
490    }
491
492    /// libssh docs say that this is the ls -l output on openssh
493    /// servers, but is unreliable with other servers
494    pub fn long_name(&self) -> Option<&str> {
495        self.name_helper(self.attr().longname)
496    }
497
498    /// Set in openssh version 4 and up
499    pub fn owner(&self) -> Option<&str> {
500        self.name_helper(self.attr().owner)
501    }
502
503    /// Set in openssh version 4 and up
504    pub fn group(&self) -> Option<&str> {
505        self.name_helper(self.attr().group)
506    }
507
508    /// Flags the indicate which attributes are present.
509    /// Is a bitmask of `SSH_FILEXFER_ATTR_XXX` constants
510    pub fn flags(&self) -> u32 {
511        self.attr().flags
512    }
513
514    /// The owner uid of the file
515    pub fn uid(&self) -> Option<u32> {
516        if self.attr().flags & sys::SSH_FILEXFER_ATTR_UIDGID != 0 {
517            Some(self.attr().uid)
518        } else {
519            None
520        }
521    }
522
523    /// The owner gid of the file
524    pub fn gid(&self) -> Option<u32> {
525        if self.attr().flags & sys::SSH_FILEXFER_ATTR_UIDGID != 0 {
526            Some(self.attr().gid)
527        } else {
528            None
529        }
530    }
531
532    /// The unix mode_t permission bits
533    pub fn permissions(&self) -> Option<u32> {
534        if self.attr().flags & sys::SSH_FILEXFER_ATTR_PERMISSIONS != 0 {
535            Some(self.attr().permissions)
536        } else {
537            None
538        }
539    }
540
541    /// The type of the file decoded from the permissions
542    pub fn file_type(&self) -> Option<FileType> {
543        if self.attr().flags & sys::SSH_FILEXFER_ATTR_PERMISSIONS != 0 {
544            Some(match self.attr().type_ as u32 {
545                sys::SSH_FILEXFER_TYPE_SPECIAL => FileType::Special,
546                sys::SSH_FILEXFER_TYPE_SYMLINK => FileType::Symlink,
547                sys::SSH_FILEXFER_TYPE_REGULAR => FileType::Regular,
548                sys::SSH_FILEXFER_TYPE_DIRECTORY => FileType::Directory,
549                sys::SSH_FILEXFER_TYPE_UNKNOWN | _ => FileType::Unknown,
550            })
551        } else {
552            None
553        }
554    }
555
556    /// The last-accessed time
557    pub fn accessed(&self) -> Option<SystemTime> {
558        let duration = if self.attr().flags & sys::SSH_FILEXFER_ATTR_ACCESSTIME != 0 {
559            Duration::from_secs(self.attr().atime64)
560                + Duration::from_nanos(
561                    if self.attr().flags & sys::SSH_FILEXFER_ATTR_SUBSECOND_TIMES != 0 {
562                        self.attr().atime_nseconds.into()
563                    } else {
564                        0
565                    },
566                )
567        } else if self.attr().flags & sys::SSH_FILEXFER_ATTR_ACMODTIME != 0 {
568            Duration::from_secs(self.attr().atime.into())
569        } else {
570            return None;
571        };
572        SystemTime::UNIX_EPOCH.checked_add(duration)
573    }
574
575    /// The file creation time
576    pub fn created(&self) -> Option<SystemTime> {
577        let duration = if self.attr().flags & sys::SSH_FILEXFER_ATTR_CREATETIME != 0 {
578            Duration::from_secs(self.attr().createtime)
579                + Duration::from_nanos(
580                    if self.attr().flags & sys::SSH_FILEXFER_ATTR_SUBSECOND_TIMES != 0 {
581                        self.attr().createtime_nseconds.into()
582                    } else {
583                        0
584                    },
585                )
586        } else {
587            return None;
588        };
589        SystemTime::UNIX_EPOCH.checked_add(duration)
590    }
591
592    /// The file modification time
593    pub fn modified(&self) -> Option<SystemTime> {
594        let duration = if self.attr().flags & sys::SSH_FILEXFER_ATTR_MODIFYTIME != 0 {
595            Duration::from_secs(self.attr().mtime64)
596                + Duration::from_nanos(
597                    if self.attr().flags & sys::SSH_FILEXFER_ATTR_SUBSECOND_TIMES != 0 {
598                        self.attr().mtime_nseconds.into()
599                    } else {
600                        0
601                    },
602                )
603        } else if self.attr().flags & sys::SSH_FILEXFER_ATTR_ACMODTIME != 0 {
604            Duration::from_secs(self.attr().mtime.into())
605        } else {
606            return None;
607        };
608        SystemTime::UNIX_EPOCH.checked_add(duration)
609    }
610}
611
612pub struct SftpDir {
613    pub(crate) sess: Arc<Mutex<SessionHolder>>,
614    pub(crate) dir_inner: sys::sftp_dir,
615    pub(crate) sftp: sys::sftp_session,
616}
617
618unsafe impl Send for SftpDir {}
619
620impl Drop for SftpDir {
621    fn drop(&mut self) {
622        let (_sess, dir) = self.lock_session();
623        unsafe {
624            sys::sftp_closedir(dir);
625        }
626    }
627}
628
629impl SftpDir {
630    fn lock_session(&self) -> (MutexGuard<SessionHolder>, sys::sftp_dir) {
631        (self.sess.lock().unwrap(), self.dir_inner)
632    }
633
634    /// Read the next entry from the directory.
635    /// Returns None if there are no more entries.
636    pub fn read_dir(&self) -> Option<SshResult<Metadata>> {
637        let (_sess, dir) = self.lock_session();
638        let attr = unsafe { sys::sftp_readdir(self.sftp, dir) };
639        if attr.is_null() {
640            if unsafe { sys::sftp_dir_eof(dir) } == 1 {
641                None
642            } else {
643                Some(Err(Error::Sftp(SftpError::from_session(self.sftp))))
644            }
645        } else {
646            Some(Ok(Metadata { attr }))
647        }
648    }
649}
650
651#[derive(Clone, Copy, Debug, PartialEq, Eq)]
652pub enum FileType {
653    Special,
654    Symlink,
655    Regular,
656    Directory,
657    Unknown,
658}
659
660bitflags::bitflags! {
661     /// Bitflags that indicate options for opening a sftp file.
662    pub struct OpenFlags: c_int {
663        /// The file should be opened as read-only.
664        const READ_ONLY = libc::O_RDONLY;
665        /// The file should be opened as write-only.
666        const WRITE_ONLY = libc::O_WRONLY;
667        /// The file should be opened as read and write.
668        ///
669        /// Note that this is a different value than `READ_ONLY | WRITE_ONLY`, which is a logic error.
670        const READ_WRITE = libc::O_RDWR;
671        /// Create the file if it does not exist.
672        const CREATE = libc::O_CREAT;
673        /// When used with `CREATE`, this flag ensures that a new file is created.
674        const EXCLUSIVE = libc::O_EXCL;
675        /// If the file exists, truncate it.
676        const TRUNCATE = libc::O_TRUNC;
677        /// Before each write, the file offset is set to the end of the file.
678        const APPEND = libc::O_APPEND;
679        /// Create a new file, failing if it already exists.
680        ///
681        /// This is an alias for `CREATE | EXCLUSIVE`.
682        const CREATE_NEW = libc::O_CREAT | libc::O_EXCL;
683    }
684}