Add windows & unix optional metadata (#96)

pull/98/head
Chip Senkbeil 2 years ago committed by GitHub
parent 0308343794
commit 3794466dd0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Shell support introduced for ssh & distant servers, including a new shell
command for distant cli
- Support for JSON communication of ssh auth during launch (cli)
- Add windows and unix metadata files to overall metadata response data
### Changed
- Replace cbor library with alternative as old cbor lib has been abandoned

1
Cargo.lock generated

@ -529,6 +529,7 @@ name = "distant-core"
version = "0.16.0"
dependencies = [
"assert_fs",
"bitflags",
"bytes",
"chacha20poly1305",
"ciborium",

@ -12,6 +12,7 @@ readme = "README.md"
license = "MIT OR Apache-2.0"
[dependencies]
bitflags = "1.3.2"
bytes = "1.1.0"
chacha20poly1305 = "0.9.0"
ciborium = "0.2.0"

@ -1,3 +1,4 @@
use bitflags::bitflags;
use derive_more::{Display, Error, IsVariant};
use portable_pty::PtySize as PortablePtySize;
use serde::{Deserialize, Serialize};
@ -531,6 +532,274 @@ pub struct Metadata {
#[serde(serialize_with = "serialize_u128_option")]
#[serde(deserialize_with = "deserialize_u128_option")]
pub modified: Option<u128>,
/// Represents metadata that is specific to a unix remote machine
pub unix: Option<UnixMetadata>,
/// Represents metadata that is specific to a windows remote machine
pub windows: Option<WindowsMetadata>,
}
/// Represents unix-specific metadata about some path on a remote machine
#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct UnixMetadata {
/// Represents whether or not owner can read from the file
pub owner_read: bool,
/// Represents whether or not owner can write to the file
pub owner_write: bool,
/// Represents whether or not owner can execute the file
pub owner_exec: bool,
/// Represents whether or not associated group can read from the file
pub group_read: bool,
/// Represents whether or not associated group can write to the file
pub group_write: bool,
/// Represents whether or not associated group can execute the file
pub group_exec: bool,
/// Represents whether or not other can read from the file
pub other_read: bool,
/// Represents whether or not other can write to the file
pub other_write: bool,
/// Represents whether or not other can execute the file
pub other_exec: bool,
}
impl From<u32> for UnixMetadata {
/// Create from a unix mode bitset
fn from(mode: u32) -> Self {
let flags = UnixFilePermissionFlags::from_bits_truncate(mode);
Self {
owner_read: flags.contains(UnixFilePermissionFlags::OWNER_READ),
owner_write: flags.contains(UnixFilePermissionFlags::OWNER_WRITE),
owner_exec: flags.contains(UnixFilePermissionFlags::OWNER_EXEC),
group_read: flags.contains(UnixFilePermissionFlags::GROUP_READ),
group_write: flags.contains(UnixFilePermissionFlags::GROUP_WRITE),
group_exec: flags.contains(UnixFilePermissionFlags::GROUP_EXEC),
other_read: flags.contains(UnixFilePermissionFlags::OTHER_READ),
other_write: flags.contains(UnixFilePermissionFlags::OTHER_WRITE),
other_exec: flags.contains(UnixFilePermissionFlags::OTHER_EXEC),
}
}
}
impl From<UnixMetadata> for u32 {
/// Convert to a unix mode bitset
fn from(metadata: UnixMetadata) -> Self {
let mut flags = UnixFilePermissionFlags::empty();
if metadata.owner_read {
flags.insert(UnixFilePermissionFlags::OWNER_READ);
}
if metadata.owner_write {
flags.insert(UnixFilePermissionFlags::OWNER_WRITE);
}
if metadata.owner_exec {
flags.insert(UnixFilePermissionFlags::OWNER_EXEC);
}
if metadata.group_read {
flags.insert(UnixFilePermissionFlags::GROUP_READ);
}
if metadata.group_write {
flags.insert(UnixFilePermissionFlags::GROUP_WRITE);
}
if metadata.group_exec {
flags.insert(UnixFilePermissionFlags::GROUP_EXEC);
}
if metadata.other_read {
flags.insert(UnixFilePermissionFlags::OTHER_READ);
}
if metadata.other_write {
flags.insert(UnixFilePermissionFlags::OTHER_WRITE);
}
if metadata.other_exec {
flags.insert(UnixFilePermissionFlags::OTHER_EXEC);
}
flags.bits
}
}
impl UnixMetadata {
pub fn is_readonly(self) -> bool {
!(self.owner_read || self.group_read || self.other_read)
}
}
bitflags! {
struct UnixFilePermissionFlags: u32 {
const OWNER_READ = 0o400;
const OWNER_WRITE = 0o200;
const OWNER_EXEC = 0o100;
const GROUP_READ = 0o40;
const GROUP_WRITE = 0o20;
const GROUP_EXEC = 0o10;
const OTHER_READ = 0o4;
const OTHER_WRITE = 0o2;
const OTHER_EXEC = 0o1;
}
}
/// Represents windows-specific metadata about some path on a remote machine
#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct WindowsMetadata {
/// Represents whether or not a file or directory is an archive
pub archive: bool,
/// Represents whether or not a file or directory is compressed
pub compressed: bool,
/// Represents whether or not the file or directory is encrypted
pub encrypted: bool,
/// Represents whether or not a file or directory is hidden
pub hidden: bool,
/// Represents whether or not a directory or user data stream is configured with integrity
pub integrity_stream: bool,
/// Represents whether or not a file does not have other attributes set
pub normal: bool,
/// Represents whether or not a file or directory is not to be indexed by content indexing
/// service
pub not_content_indexed: bool,
/// Represents whether or not a user data stream is not to be read by the background data
/// integrity scanner
pub no_scrub_data: bool,
/// Represents whether or not the data of a file is not available immediately
pub offline: bool,
/// Represents whether or not a file or directory is not fully present locally
pub recall_on_data_access: bool,
/// Represents whether or not a file or directory has no physical representation on the local
/// system (is virtual)
pub recall_on_open: bool,
/// Represents whether or not a file or directory has an associated reparse point, or a file is
/// a symbolic link
pub reparse_point: bool,
/// Represents whether or not a file is a sparse file
pub sparse_file: bool,
/// Represents whether or not a file or directory is used partially or exclusively by the
/// operating system
pub system: bool,
/// Represents whether or not a file is being used for temporary storage
pub temporary: bool,
}
impl From<u32> for WindowsMetadata {
/// Create from a windows file attribute bitset
fn from(file_attributes: u32) -> Self {
let flags = WindowsFileAttributeFlags::from_bits_truncate(file_attributes);
Self {
archive: flags.contains(WindowsFileAttributeFlags::ARCHIVE),
compressed: flags.contains(WindowsFileAttributeFlags::COMPRESSED),
encrypted: flags.contains(WindowsFileAttributeFlags::ENCRYPTED),
hidden: flags.contains(WindowsFileAttributeFlags::HIDDEN),
integrity_stream: flags.contains(WindowsFileAttributeFlags::INTEGRITY_SYSTEM),
normal: flags.contains(WindowsFileAttributeFlags::NORMAL),
not_content_indexed: flags.contains(WindowsFileAttributeFlags::NOT_CONTENT_INDEXED),
no_scrub_data: flags.contains(WindowsFileAttributeFlags::NO_SCRUB_DATA),
offline: flags.contains(WindowsFileAttributeFlags::OFFLINE),
recall_on_data_access: flags.contains(WindowsFileAttributeFlags::RECALL_ON_DATA_ACCESS),
recall_on_open: flags.contains(WindowsFileAttributeFlags::RECALL_ON_OPEN),
reparse_point: flags.contains(WindowsFileAttributeFlags::REPARSE_POINT),
sparse_file: flags.contains(WindowsFileAttributeFlags::SPARSE_FILE),
system: flags.contains(WindowsFileAttributeFlags::SYSTEM),
temporary: flags.contains(WindowsFileAttributeFlags::TEMPORARY),
}
}
}
impl From<WindowsMetadata> for u32 {
/// Convert to a windows file attribute bitset
fn from(metadata: WindowsMetadata) -> Self {
let mut flags = WindowsFileAttributeFlags::empty();
if metadata.archive {
flags.insert(WindowsFileAttributeFlags::ARCHIVE);
}
if metadata.compressed {
flags.insert(WindowsFileAttributeFlags::COMPRESSED);
}
if metadata.encrypted {
flags.insert(WindowsFileAttributeFlags::ENCRYPTED);
}
if metadata.hidden {
flags.insert(WindowsFileAttributeFlags::HIDDEN);
}
if metadata.integrity_stream {
flags.insert(WindowsFileAttributeFlags::INTEGRITY_SYSTEM);
}
if metadata.normal {
flags.insert(WindowsFileAttributeFlags::NORMAL);
}
if metadata.not_content_indexed {
flags.insert(WindowsFileAttributeFlags::NOT_CONTENT_INDEXED);
}
if metadata.no_scrub_data {
flags.insert(WindowsFileAttributeFlags::NO_SCRUB_DATA);
}
if metadata.offline {
flags.insert(WindowsFileAttributeFlags::OFFLINE);
}
if metadata.recall_on_data_access {
flags.insert(WindowsFileAttributeFlags::RECALL_ON_DATA_ACCESS);
}
if metadata.recall_on_open {
flags.insert(WindowsFileAttributeFlags::RECALL_ON_OPEN);
}
if metadata.reparse_point {
flags.insert(WindowsFileAttributeFlags::REPARSE_POINT);
}
if metadata.sparse_file {
flags.insert(WindowsFileAttributeFlags::SPARSE_FILE);
}
if metadata.system {
flags.insert(WindowsFileAttributeFlags::SYSTEM);
}
if metadata.temporary {
flags.insert(WindowsFileAttributeFlags::TEMPORARY);
}
flags.bits
}
}
bitflags! {
struct WindowsFileAttributeFlags: u32 {
const ARCHIVE = 0x20;
const COMPRESSED = 0x800;
const ENCRYPTED = 0x4000;
const HIDDEN = 0x2;
const INTEGRITY_SYSTEM = 0x8000;
const NORMAL = 0x80;
const NOT_CONTENT_INDEXED = 0x2000;
const NO_SCRUB_DATA = 0x20000;
const OFFLINE = 0x1000;
const RECALL_ON_DATA_ACCESS = 0x400000;
const RECALL_ON_OPEN = 0x40000;
const REPARSE_POINT = 0x400;
const SPARSE_FILE = 0x200;
const SYSTEM = 0x4;
const TEMPORARY = 0x100;
const VIRTUAL = 0x10000;
}
}
pub(crate) fn deserialize_u128_option<'de, D>(deserializer: D) -> Result<Option<u128>, D::Error>

@ -424,6 +424,24 @@ async fn metadata(
} else {
FileType::Symlink
},
#[cfg(unix)]
unix: Some({
use std::os::unix::prelude::*;
let mode = metadata.mode();
crate::data::UnixMetadata::from(mode)
}),
#[cfg(not(unix))]
unix: None,
#[cfg(windows)]
windows: Some({
use std::os::windows::prelude::*;
let attributes = metadata.file_attributes();
crate::data::WindowsMetadata::from(attributes)
}),
#[cfg(not(windows))]
windows: None,
})))
}
@ -1991,6 +2009,74 @@ mod tests {
);
}
#[cfg(unix)]
#[tokio::test]
async fn metadata_should_include_unix_specific_metadata_on_unix_platform() {
let (conn_id, state, tx, mut rx) = setup(1);
let temp = assert_fs::TempDir::new().unwrap();
let file = temp.child("file");
file.write_str("some text").unwrap();
let req = Request::new(
"test-tenant",
vec![RequestData::Metadata {
path: file.path().to_path_buf(),
canonicalize: false,
resolve_file_type: false,
}],
);
process(conn_id, state, req, tx).await.unwrap();
let res = rx.recv().await.unwrap();
assert_eq!(res.payload.len(), 1, "Wrong payload size");
match &res.payload[0] {
ResponseData::Metadata(Metadata { unix, windows, .. }) => {
assert!(unix.is_some(), "Unexpectedly missing unix metadata on unix");
assert!(
windows.is_none(),
"Unexpectedly got windows metadata on unix"
);
}
x => panic!("Unexpected response: {:?}", x),
}
}
#[cfg(windows)]
#[tokio::test]
async fn metadata_should_include_unix_specific_metadata_on_windows_platform() {
let (conn_id, state, tx, mut rx) = setup(1);
let temp = assert_fs::TempDir::new().unwrap();
let file = temp.child("file");
file.write_str("some text").unwrap();
let req = Request::new(
"test-tenant",
vec![RequestData::Metadata {
path: file.path().to_path_buf(),
canonicalize: false,
resolve_file_type: false,
}],
);
process(conn_id, state, req, tx).await.unwrap();
let res = rx.recv().await.unwrap();
assert_eq!(res.payload.len(), 1, "Wrong payload size");
match &res.payload[0] {
ResponseData::Metadata(Metadata { unix, windows, .. }) => {
assert!(
windows.is_some(),
"Unexpectedly missing windows metadata on windows"
);
assert!(unix.is_none(), "Unexpectedly got unix metadata on windows");
}
x => panic!("Unexpected response: {:?}", x),
}
}
#[tokio::test]
async fn metadata_should_send_back_metadata_on_dir_if_exists() {
let (conn_id, state, tx, mut rx) = setup(1);

@ -4,7 +4,7 @@ use distant_core::{
data::{
DirEntry, Error as DistantError, FileType, Metadata, PtySize, RunningProcess, SystemInfo,
},
Request, RequestData, Response, ResponseData,
Request, RequestData, Response, ResponseData, UnixMetadata,
};
use futures::future;
use log::*;
@ -606,6 +606,18 @@ async fn metadata(
accessed: metadata.accessed.map(u128::from),
modified: metadata.modified.map(u128::from),
created: None,
unix: metadata.permissions.as_ref().map(|p| UnixMetadata {
owner_read: p.owner_read,
owner_write: p.owner_write,
owner_exec: p.owner_exec,
group_read: p.group_read,
group_write: p.group_write,
group_exec: p.group_exec,
other_read: p.other_read,
other_write: p.other_write,
other_exec: p.other_exec,
}),
windows: None,
})))
}

@ -142,6 +142,8 @@ fn format_shell(data: ResponseData) -> ResponseOut {
accessed,
created,
modified,
unix,
windows,
}) => ResponseOut::StdoutLine(
format!(
concat!(
@ -151,7 +153,10 @@ fn format_shell(data: ResponseData) -> ResponseOut {
"Readonly: {}\n",
"Created: {}\n",
"Last Accessed: {}\n",
"Last Modified: {}",
"Last Modified: {}\n",
"{}",
"{}",
"{}",
),
canonicalized_path
.map(|p| format!("Canonicalized Path: {:?}\n", p))
@ -162,6 +167,70 @@ fn format_shell(data: ResponseData) -> ResponseOut {
created.unwrap_or_default(),
accessed.unwrap_or_default(),
modified.unwrap_or_default(),
unix.map(|u| format!(
concat!(
"Owner Read: {}\n",
"Owner Write: {}\n",
"Owner Exec: {}\n",
"Group Read: {}\n",
"Group Write: {}\n",
"Group Exec: {}\n",
"Other Read: {}\n",
"Other Write: {}\n",
"Other Exec: {}",
),
u.owner_read,
u.owner_write,
u.owner_exec,
u.group_read,
u.group_write,
u.group_exec,
u.other_read,
u.other_write,
u.other_exec
))
.unwrap_or_default(),
windows
.map(|w| format!(
concat!(
"Archive: {}\n",
"Compressed: {}\n",
"Encrypted: {}\n",
"Hidden: {}\n",
"Integrity Stream: {}\n",
"Normal: {}\n",
"Not Content Indexed: {}\n",
"No Scrub Data: {}\n",
"Offline: {}\n",
"Recall on Data Access: {}\n",
"Recall on Open: {}\n",
"Reparse Point: {}\n",
"Sparse File: {}\n",
"System: {}\n",
"Temporary: {}",
),
w.archive,
w.compressed,
w.encrypted,
w.hidden,
w.integrity_stream,
w.normal,
w.not_content_indexed,
w.no_scrub_data,
w.offline,
w.recall_on_data_access,
w.recall_on_open,
w.reparse_point,
w.sparse_file,
w.system,
w.temporary,
))
.unwrap_or_default(),
if unix.is_none() && windows.is_none() {
String::from("\n")
} else {
String::new()
}
)
.into_bytes(),
),

Loading…
Cancel
Save