1use bitflags::bitflags;
2
3bitflags! {
4 struct FileTypeFlags: u32 {
5 const DIR = 0o040000;
6 const FILE = 0o100000;
7 const SYMLINK = 0o120000;
8 }
9}
10
11bitflags! {
12 struct FilePermissionFlags: u32 {
13 const OWNER_READ = 0o400;
14 const OWNER_WRITE = 0o200;
15 const OWNER_EXEC = 0o100;
16 const GROUP_READ = 0o40;
17 const GROUP_WRITE = 0o20;
18 const GROUP_EXEC = 0o10;
19 const OTHER_READ = 0o4;
20 const OTHER_WRITE = 0o2;
21 const OTHER_EXEC = 0o1;
22 }
23}
24
25#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
27pub enum FileType {
28 Dir,
29 File,
30 Symlink,
31 Other,
32}
33
34impl FileType {
35 pub fn is_dir(self) -> bool {
37 matches!(self, Self::Dir)
38 }
39
40 pub fn is_file(self) -> bool {
42 matches!(self, Self::File)
43 }
44
45 pub fn is_symlink(self) -> bool {
47 matches!(self, Self::Symlink)
48 }
49
50 pub fn from_unix_mode(mode: u32) -> Self {
52 let flags = FileTypeFlags::from_bits_truncate(mode);
53 if flags.contains(FileTypeFlags::DIR) {
54 Self::Dir
55 } else if flags.contains(FileTypeFlags::FILE) {
56 Self::File
57 } else if flags.contains(FileTypeFlags::SYMLINK) {
58 Self::Symlink
59 } else {
60 Self::Other
61 }
62 }
63
64 pub fn to_unix_mode(self) -> u32 {
66 let flags = match self {
67 FileType::Dir => FileTypeFlags::DIR,
68 FileType::File => FileTypeFlags::FILE,
69 FileType::Symlink => FileTypeFlags::SYMLINK,
70 FileType::Other => FileTypeFlags::empty(),
71 };
72
73 flags.bits
74 }
75}
76
77#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
79pub struct FilePermissions {
80 pub owner_read: bool,
81 pub owner_write: bool,
82 pub owner_exec: bool,
83
84 pub group_read: bool,
85 pub group_write: bool,
86 pub group_exec: bool,
87
88 pub other_read: bool,
89 pub other_write: bool,
90 pub other_exec: bool,
91}
92
93impl FilePermissions {
94 pub fn is_readonly(self) -> bool {
95 !(self.owner_read || self.group_read || self.other_read)
96 }
97
98 pub fn from_unix_mode(mode: u32) -> Self {
100 let flags = FilePermissionFlags::from_bits_truncate(mode);
101 Self {
102 owner_read: flags.contains(FilePermissionFlags::OWNER_READ),
103 owner_write: flags.contains(FilePermissionFlags::OWNER_WRITE),
104 owner_exec: flags.contains(FilePermissionFlags::OWNER_EXEC),
105 group_read: flags.contains(FilePermissionFlags::GROUP_READ),
106 group_write: flags.contains(FilePermissionFlags::GROUP_WRITE),
107 group_exec: flags.contains(FilePermissionFlags::GROUP_EXEC),
108 other_read: flags.contains(FilePermissionFlags::OTHER_READ),
109 other_write: flags.contains(FilePermissionFlags::OTHER_WRITE),
110 other_exec: flags.contains(FilePermissionFlags::OTHER_EXEC),
111 }
112 }
113
114 pub fn to_unix_mode(self) -> u32 {
116 let mut flags = FilePermissionFlags::empty();
117
118 if self.owner_read {
119 flags.insert(FilePermissionFlags::OWNER_READ);
120 }
121 if self.owner_write {
122 flags.insert(FilePermissionFlags::OWNER_WRITE);
123 }
124 if self.owner_exec {
125 flags.insert(FilePermissionFlags::OWNER_EXEC);
126 }
127
128 if self.group_read {
129 flags.insert(FilePermissionFlags::GROUP_READ);
130 }
131 if self.group_write {
132 flags.insert(FilePermissionFlags::GROUP_WRITE);
133 }
134 if self.group_exec {
135 flags.insert(FilePermissionFlags::GROUP_EXEC);
136 }
137
138 if self.other_read {
139 flags.insert(FilePermissionFlags::OTHER_READ);
140 }
141 if self.other_write {
142 flags.insert(FilePermissionFlags::OTHER_WRITE);
143 }
144 if self.other_exec {
145 flags.insert(FilePermissionFlags::OTHER_EXEC);
146 }
147
148 flags.bits
149 }
150}
151
152#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
154pub struct Metadata {
155 pub ty: FileType,
157
158 pub permissions: Option<FilePermissions>,
160
161 pub size: Option<u64>,
163
164 pub uid: Option<u32>,
166
167 pub gid: Option<u32>,
169
170 pub accessed: Option<u64>,
172
173 pub modified: Option<u64>,
175}
176
177impl Metadata {
178 pub fn is_dir(self) -> bool {
180 self.ty.is_dir()
181 }
182
183 pub fn is_file(self) -> bool {
185 self.ty.is_file()
186 }
187
188 pub fn is_symlink(self) -> bool {
190 self.ty.is_symlink()
191 }
192}
193
194#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
196pub struct OpenOptions {
197 pub read: bool,
199
200 pub write: Option<WriteMode>,
202
203 pub mode: i32,
205
206 pub ty: OpenFileType,
208}
209
210#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
212pub enum OpenFileType {
213 Dir,
214 File,
215}
216
217#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
219pub enum WriteMode {
220 Append,
222
223 Write,
225}
226
227#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
229pub struct RenameOptions {
230 pub overwrite: bool,
232
233 pub atomic: bool,
235
236 pub native: bool,
238}
239
240impl Default for RenameOptions {
241 fn default() -> Self {
243 Self {
244 overwrite: true,
245 atomic: true,
246 native: true,
247 }
248 }
249}
250
251#[cfg(feature = "ssh2")]
253mod ssh2_impl {
254 use super::*;
255 use ::ssh2::{
256 FileStat as Ssh2FileStat, FileType as Ssh2FileType, OpenFlags as Ssh2OpenFlags,
257 OpenType as Ssh2OpenType, RenameFlags as Ssh2RenameFlags,
258 };
259
260 impl From<OpenFileType> for Ssh2OpenType {
261 fn from(ty: OpenFileType) -> Self {
262 match ty {
263 OpenFileType::Dir => Self::Dir,
264 OpenFileType::File => Self::File,
265 }
266 }
267 }
268
269 impl From<RenameOptions> for Ssh2RenameFlags {
270 fn from(opts: RenameOptions) -> Self {
271 let mut flags = Self::empty();
272
273 if opts.overwrite {
274 flags |= Self::OVERWRITE;
275 }
276
277 if opts.atomic {
278 flags |= Self::ATOMIC;
279 }
280
281 if opts.native {
282 flags |= Self::NATIVE;
283 }
284
285 flags
286 }
287 }
288
289 impl From<OpenOptions> for Ssh2OpenFlags {
290 fn from(opts: OpenOptions) -> Self {
291 let mut flags = Self::empty();
292
293 if opts.read {
294 flags |= Self::READ;
295 }
296
297 match opts.write {
298 Some(WriteMode::Write) => flags |= Self::WRITE | Self::TRUNCATE,
299 Some(WriteMode::Append) => flags |= Self::WRITE | Self::APPEND | Self::CREATE,
300 None => {}
301 }
302
303 flags
304 }
305 }
306
307 impl From<Ssh2FileType> for FileType {
308 fn from(ft: Ssh2FileType) -> Self {
309 if ft.is_dir() {
310 Self::Dir
311 } else if ft.is_file() {
312 Self::File
313 } else if ft.is_symlink() {
314 Self::Symlink
315 } else {
316 Self::Other
317 }
318 }
319 }
320
321 impl From<Ssh2FileStat> for Metadata {
322 fn from(stat: Ssh2FileStat) -> Self {
323 Self {
324 ty: FileType::from(stat.file_type()),
325 permissions: stat.perm.map(FilePermissions::from_unix_mode),
326 size: stat.size,
327 uid: stat.uid,
328 gid: stat.gid,
329 accessed: stat.atime,
330 modified: stat.mtime,
331 }
332 }
333 }
334
335 impl From<Metadata> for Ssh2FileStat {
336 fn from(metadata: Metadata) -> Self {
337 let ft = metadata.ty;
338
339 Self {
340 perm: metadata
341 .permissions
342 .map(|p| p.to_unix_mode() | ft.to_unix_mode()),
343 size: metadata.size,
344 uid: metadata.uid,
345 gid: metadata.gid,
346 atime: metadata.accessed,
347 mtime: metadata.modified,
348 }
349 }
350 }
351}
352
353#[cfg(feature = "libssh-rs")]
354mod libssh_impl {
355 use super::*;
356 use std::time::SystemTime;
357
358 impl From<libssh_rs::FileType> for FileType {
359 fn from(ft: libssh_rs::FileType) -> Self {
360 match ft {
361 libssh_rs::FileType::Directory => Self::Dir,
362 libssh_rs::FileType::Regular => Self::File,
363 libssh_rs::FileType::Symlink => Self::Symlink,
364 _ => Self::Other,
365 }
366 }
367 }
368
369 fn sys_time_to_unix(t: SystemTime) -> u64 {
370 t.duration_since(SystemTime::UNIX_EPOCH)
371 .expect("UNIX_EPOCH < SystemTime")
372 .as_secs()
373 }
374
375 fn unix_to_sys(u: u64) -> SystemTime {
376 SystemTime::UNIX_EPOCH + std::time::Duration::from_secs(u)
377 }
378
379 impl From<libssh_rs::Metadata> for Metadata {
380 fn from(stat: libssh_rs::Metadata) -> Self {
381 Self {
382 ty: stat
383 .file_type()
384 .map(FileType::from)
385 .unwrap_or(FileType::Other),
386 permissions: stat.permissions().map(FilePermissions::from_unix_mode),
387 size: stat.len(),
388 uid: stat.uid(),
389 gid: stat.gid(),
390 accessed: stat.accessed().map(sys_time_to_unix),
391 modified: stat.modified().map(sys_time_to_unix),
392 }
393 }
394 }
395
396 impl Into<libssh_rs::SetAttributes> for Metadata {
397 fn into(self) -> libssh_rs::SetAttributes {
398 let size = self.size;
399 let uid_gid = match (self.uid, self.gid) {
400 (Some(uid), Some(gid)) => Some((uid, gid)),
401 _ => None,
402 };
403 let permissions = self.permissions.map(FilePermissions::to_unix_mode);
404 let atime_mtime = match (self.accessed, self.modified) {
405 (Some(a), Some(m)) => {
406 let a = unix_to_sys(a);
407 let m = unix_to_sys(m);
408 Some((a, m))
409 }
410 _ => None,
411 };
412 libssh_rs::SetAttributes {
413 size,
414 uid_gid,
415 permissions,
416 atime_mtime,
417 }
418 }
419 }
420}