From fc3187a781f50a38b037b2a3b9c1985edebf5e21 Mon Sep 17 00:00:00 2001 From: ClSlaid Date: Fri, 20 Oct 2023 00:23:16 +0800 Subject: [PATCH] feat: extend file list PDU to transfer UNIX PERM 1. used 4 bytes out of a reserved 16 bytes section to store perm u32 2. add FLAGS_FD_UNIX_MODE: u32 = 0x08, used with flags, indicating this message is from UNIX peer Signed-off-by: ClSlaid --- libs/clipboard/src/platform/fuse.rs | 66 +++++++++++++++++-- .../src/platform/linux/local_file.rs | 53 +++++++++++++-- libs/clipboard/src/platform/linux/mod.rs | 1 - 3 files changed, 106 insertions(+), 14 deletions(-) diff --git a/libs/clipboard/src/platform/fuse.rs b/libs/clipboard/src/platform/fuse.rs index dc400379e..0dbfc91d9 100644 --- a/libs/clipboard/src/platform/fuse.rs +++ b/libs/clipboard/src/platform/fuse.rs @@ -50,6 +50,12 @@ const BLOCK_SIZE: u32 = 128 * 1024; /// read only permission const PERM_READ: u16 = 0o444; +/// read and write permission +const PERM_RW: u16 = 0o644; +/// only self can read and readonly +const PERM_SELF_RO: u16 = 0o400; +/// rwx +const PERM_RWX: u16 = 0o755; /// max length of file name const MAX_NAME_LEN: usize = 255; @@ -143,6 +149,11 @@ impl fuser::Filesystem for FuseClient { let mut server = self.server.lock(); server.release(req, ino, fh, _flags, _lock_owner, _flush, reply) } + + fn getattr(&mut self, req: &fuser::Request<'_>, ino: u64, reply: fuser::ReplyAttr) { + let mut server = self.server.lock(); + server.getattr(req, ino, reply) + } } /// fuse server @@ -494,6 +505,18 @@ impl fuser::Filesystem for FuseServer { reply.ok(); return; } + + fn getattr(&mut self, _req: &fuser::Request<'_>, ino: u64, reply: fuser::ReplyAttr) { + let files = &self.files; + let Some(entry) = files.get(ino as usize - 1) else { + reply.error(libc::ENOENT); + log::error!("fuse: getattr: entry not found"); + return; + }; + + let attr = (&entry.attributes).into(); + reply.attr(&std::time::Duration::default(), &attr) + } } impl FuseServer { @@ -614,8 +637,13 @@ impl FileDescription { // skip reserved 32 bytes bytes.advance(32); let attributes = bytes.get_u32_le(); - // skip reserved 16 bytes - bytes.advance(16); + + // in original specification, this is 16 bytes reserved + // we use the last 4 bytes to store the file mode + // skip reserved 12 bytes + bytes.advance(12); + let perm = bytes.get_u32_le() as u16; + // last write time from 1601-01-01 00:00:00, in 100ns let last_write_time = bytes.get_u64_le(); // file size @@ -632,6 +660,8 @@ impl FileDescription { CliprdrError::ConversionFailure })?; + let from_unix = flags & 0x08 != 0; + let valid_attributes = flags & 0x04 != 0; if !valid_attributes { return Err(CliprdrError::InvalidRequest { @@ -641,6 +671,25 @@ impl FileDescription { // todo: check normal, hidden, system, readonly, archive... let directory = attributes & 0x10 != 0; + let normal = attributes == 0x80; + let hidden = attributes & 0x02 != 0; + let readonly = attributes & 0x01 != 0; + + let perm = if from_unix { + // as is + perm + // cannot set as is... + } else if normal { + PERM_RWX + } else if readonly { + PERM_READ + } else if hidden { + PERM_SELF_RO + } else if directory { + PERM_RWX + } else { + PERM_RW + }; let kind = if directory { FileType::Directory @@ -677,7 +726,7 @@ impl FileDescription { creation_time: last_modified, size, - perm: PERM_READ, + perm, }; Ok(desc) @@ -859,7 +908,7 @@ impl FuseNode { last_metadata_changed: SystemTime::UNIX_EPOCH, creation_time: SystemTime::UNIX_EPOCH, size: 0, - perm: PERM_READ, + perm: PERM_RWX, } } else { file.clone() @@ -910,13 +959,14 @@ pub struct InodeAttributes { last_metadata_changed: std::time::SystemTime, creation_time: std::time::SystemTime, kind: FileType, + perm: u16, // not implemented _xattrs: BTreeMap, Vec>, } impl InodeAttributes { - pub fn new(inode: u64, size: u64, kind: FileType) -> Self { + pub fn new(inode: u64, size: u64, perm: u16, kind: FileType) -> Self { Self { inode, size, @@ -925,6 +975,7 @@ impl InodeAttributes { last_metadata_changed: std::time::SystemTime::now(), creation_time: std::time::SystemTime::now(), kind, + perm, _xattrs: BTreeMap::new(), } } @@ -938,13 +989,14 @@ impl InodeAttributes { creation_time: desc.creation_time, last_accessed: SystemTime::now(), kind: desc.kind, + perm: desc.perm, _xattrs: BTreeMap::new(), } } pub fn new_root() -> Self { - Self::new(FUSE_ROOT_ID, 0, FileType::Directory) + Self::new(FUSE_ROOT_ID, 0, PERM_RWX, FileType::Directory) } pub fn access(&mut self) { @@ -970,7 +1022,7 @@ impl From<&InodeAttributes> for fuser::FileAttr { kind: value.kind.into(), // read only - perm: PERM_READ, + perm: value.perm, nlink: 1, // set to current user diff --git a/libs/clipboard/src/platform/linux/local_file.rs b/libs/clipboard/src/platform/linux/local_file.rs index 710224c1e..4bc2abc0a 100644 --- a/libs/clipboard/src/platform/linux/local_file.rs +++ b/libs/clipboard/src/platform/linux/local_file.rs @@ -1,4 +1,7 @@ -use std::{collections::HashSet, fs::File, path::PathBuf, time::SystemTime}; +use std::{ + collections::HashSet, fs::File, os::unix::prelude::PermissionsExt, path::PathBuf, + time::SystemTime, +}; use hbb_common::{ bytes::{BufMut, BytesMut}, @@ -8,6 +11,18 @@ use utf16string::WString; use crate::{platform::LDAP_EPOCH_DELTA, CliprdrError}; +/// has valid file attributes +const FLAGS_FD_ATTRIBUTES: u32 = 0x04; +/// has valid file size +const FLAGS_FD_SIZE: u32 = 0x40; +/// has valid last write time +const FLAGS_FD_LAST_WRITE: u32 = 0x20; +/// show progress +const FLAGS_FD_PROGRESSUI: u32 = 0x4000; +/// transferred from unix, contains file mode +/// P.S. this flag is not used in windows +const FLAGS_FD_UNIX_MODE: u32 = 0x08; + #[derive(Debug)] pub(super) struct LocalFile { pub path: PathBuf, @@ -17,6 +32,7 @@ pub(super) struct LocalFile { pub size: u64, pub last_write_time: SystemTime, pub is_dir: bool, + pub perm: u32, pub read_only: bool, pub hidden: bool, pub system: bool, @@ -34,11 +50,17 @@ impl LocalFile { let is_dir = mt.is_dir(); let read_only = mt.permissions().readonly(); let system = false; - let hidden = false; + let hidden = if path.to_string_lossy().starts_with('.') { + true + } else { + false + }; let archive = false; - let normal = !is_dir; + let normal = !(is_dir || read_only || system || hidden || archive); let last_write_time = mt.modified().unwrap_or(SystemTime::UNIX_EPOCH); + let perm = mt.permissions().mode(); + let name = path .display() .to_string() @@ -66,6 +88,7 @@ impl LocalFile { read_only, system, hidden, + perm, archive, normal, }) @@ -109,7 +132,11 @@ impl LocalFile { &self.name ); - let flags = 0x4064; + let flags = FLAGS_FD_SIZE + | FLAGS_FD_LAST_WRITE + | FLAGS_FD_ATTRIBUTES + | FLAGS_FD_PROGRESSUI + | FLAGS_FD_UNIX_MODE; // flags, 4 bytes buf.put_u32_le(flags); @@ -117,8 +144,16 @@ impl LocalFile { buf.put(&[0u8; 32][..]); // file attributes, 4 bytes buf.put_u32_le(file_attributes); - // 16 bytes reserved - buf.put(&[0u8; 16][..]); + + // NOTE: this is not used in windows + // in the specification, this is 16 bytes reserved + // lets use the last 4 bytes to store the file mode + // + // 12 bytes reserved + buf.put(&[0u8; 12][..]); + // file permissions, 4 bytes + buf.put_u32_le(self.perm); + // last write time, 8 bytes buf.put_u64_le(win32_time); // file size (high) @@ -201,6 +236,7 @@ mod file_list_test { last_write_time: std::time::SystemTime::UNIX_EPOCH, read_only: false, is_dir, + perm: 0o754, hidden: false, system: false, archive: false, @@ -263,6 +299,11 @@ mod file_list_test { assert_eq!(parsed[3].name.to_str().unwrap(), "b/c.txt"); } + assert!(parsed[0].perm & 0o777 == 0o754); + assert!(parsed[1].perm & 0o777 == 0o754); + assert!(parsed[2].perm & 0o777 == 0o754); + assert!(parsed[3].perm & 0o777 == 0o754); + Ok(()) } diff --git a/libs/clipboard/src/platform/linux/mod.rs b/libs/clipboard/src/platform/linux/mod.rs index 0f5b7d763..8f04cabc6 100644 --- a/libs/clipboard/src/platform/linux/mod.rs +++ b/libs/clipboard/src/platform/linux/mod.rs @@ -131,7 +131,6 @@ impl ClipboardContext { let mnt_opts = [ MountOption::FSName("rustdesk-cliprdr-fs".to_string()), - MountOption::RO, MountOption::NoAtime, ]; log::info!(