mirror of https://github.com/chipsenkbeil/distant
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
659 lines
22 KiB
Rust
659 lines
22 KiB
Rust
use bitflags::bitflags;
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
use crate::utils;
|
|
|
|
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
|
|
#[serde(default, deny_unknown_fields, rename_all = "snake_case")]
|
|
pub struct SetPermissionsOptions {
|
|
/// Whether or not to exclude symlinks from traversal entirely, meaning that permissions will
|
|
/// not be set on symlinks (usually resolving the symlink and setting the permission of the
|
|
/// referenced file or directory) that are explicitly provided or show up during recursion.
|
|
#[serde(skip_serializing_if = "utils::is_false")]
|
|
pub exclude_symlinks: bool,
|
|
|
|
/// Whether or not to traverse symlinks when recursively setting permissions. Note that this
|
|
/// does NOT influence setting permissions when encountering a symlink as most platforms will
|
|
/// resolve the symlink before setting permissions.
|
|
#[serde(skip_serializing_if = "utils::is_false")]
|
|
pub follow_symlinks: bool,
|
|
|
|
/// Whether or not to set the permissions of the file hierarchies rooted in the paths, instead
|
|
/// of just the paths themselves.
|
|
#[serde(skip_serializing_if = "utils::is_false")]
|
|
pub recursive: bool,
|
|
}
|
|
|
|
/// Represents permissions to apply to some path on a remote machine
|
|
///
|
|
/// When used to set permissions on a file, directory, or symlink,
|
|
/// only fields that are set (not `None`) will be applied.
|
|
///
|
|
/// On `Unix` platforms, this translates directly into the mode that
|
|
/// you would find with `chmod`. On all other platforms, this uses the
|
|
/// write flags to determine whether or not to set the readonly status.
|
|
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct Permissions {
|
|
/// Represents whether or not owner can read from the file
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub owner_read: Option<bool>,
|
|
|
|
/// Represents whether or not owner can write to the file
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub owner_write: Option<bool>,
|
|
|
|
/// Represents whether or not owner can execute the file
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub owner_exec: Option<bool>,
|
|
|
|
/// Represents whether or not associated group can read from the file
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub group_read: Option<bool>,
|
|
|
|
/// Represents whether or not associated group can write to the file
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub group_write: Option<bool>,
|
|
|
|
/// Represents whether or not associated group can execute the file
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub group_exec: Option<bool>,
|
|
|
|
/// Represents whether or not other can read from the file
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub other_read: Option<bool>,
|
|
|
|
/// Represents whether or not other can write to the file
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub other_write: Option<bool>,
|
|
|
|
/// Represents whether or not other can execute the file
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub other_exec: Option<bool>,
|
|
}
|
|
|
|
impl Permissions {
|
|
/// Creates a set of [`Permissions`] that indicate readonly status.
|
|
///
|
|
/// ```
|
|
/// use distant_protocol::Permissions;
|
|
///
|
|
/// let permissions = Permissions::readonly();
|
|
/// assert_eq!(permissions.is_readonly(), Some(true));
|
|
/// assert_eq!(permissions.is_writable(), Some(false));
|
|
/// ```
|
|
pub fn readonly() -> Self {
|
|
Self {
|
|
owner_write: Some(false),
|
|
group_write: Some(false),
|
|
other_write: Some(false),
|
|
|
|
owner_read: Some(true),
|
|
group_read: Some(true),
|
|
other_read: Some(true),
|
|
|
|
owner_exec: None,
|
|
group_exec: None,
|
|
other_exec: None,
|
|
}
|
|
}
|
|
/// Creates a set of [`Permissions`] that indicate globally writable status.
|
|
///
|
|
/// ```
|
|
/// use distant_protocol::Permissions;
|
|
///
|
|
/// let permissions = Permissions::writable();
|
|
/// assert_eq!(permissions.is_readonly(), Some(false));
|
|
/// assert_eq!(permissions.is_writable(), Some(true));
|
|
/// ```
|
|
pub fn writable() -> Self {
|
|
Self {
|
|
owner_write: Some(true),
|
|
group_write: Some(true),
|
|
other_write: Some(true),
|
|
|
|
owner_read: Some(true),
|
|
group_read: Some(true),
|
|
other_read: Some(true),
|
|
|
|
owner_exec: None,
|
|
group_exec: None,
|
|
other_exec: None,
|
|
}
|
|
}
|
|
|
|
/// Returns true if the permission set has a value specified for each permission (no `None`
|
|
/// settings).
|
|
///
|
|
/// ```
|
|
/// use distant_protocol::Permissions;
|
|
///
|
|
/// let permissions = Permissions {
|
|
/// owner_write: Some(true),
|
|
/// group_write: Some(false),
|
|
/// other_write: Some(true),
|
|
/// owner_read: Some(false),
|
|
/// group_read: Some(true),
|
|
/// other_read: Some(false),
|
|
/// owner_exec: Some(true),
|
|
/// group_exec: Some(false),
|
|
/// other_exec: Some(true),
|
|
/// };
|
|
/// assert!(permissions.is_complete());
|
|
/// ```
|
|
pub fn is_complete(&self) -> bool {
|
|
self.owner_read.is_some()
|
|
&& self.owner_write.is_some()
|
|
&& self.owner_exec.is_some()
|
|
&& self.group_read.is_some()
|
|
&& self.group_write.is_some()
|
|
&& self.group_exec.is_some()
|
|
&& self.other_read.is_some()
|
|
&& self.other_write.is_some()
|
|
&& self.other_exec.is_some()
|
|
}
|
|
|
|
/// Returns `true` if permissions represent readonly, `false` if permissions represent
|
|
/// writable, and `None` if no permissions have been set to indicate either status.
|
|
///
|
|
/// ```
|
|
/// use distant_protocol::Permissions;
|
|
///
|
|
/// assert_eq!(
|
|
/// Permissions { owner_write: Some(true), ..Default::default() }.is_readonly(),
|
|
/// Some(false)
|
|
/// );
|
|
///
|
|
/// assert_eq!(
|
|
/// Permissions { owner_write: Some(false), ..Default::default() }.is_readonly(),
|
|
/// Some(true)
|
|
/// );
|
|
///
|
|
/// assert_eq!(
|
|
/// Permissions { ..Default::default() }.is_writable(),
|
|
/// None
|
|
/// );
|
|
/// ```
|
|
#[inline]
|
|
pub fn is_readonly(&self) -> Option<bool> {
|
|
// Negate the writable status to indicate whether or not readonly
|
|
self.is_writable().map(|x| !x)
|
|
}
|
|
|
|
/// Returns `true` if permissions represent ability to write, `false` if permissions represent
|
|
/// inability to write, and `None` if no permissions have been set to indicate either status.
|
|
///
|
|
/// ```
|
|
/// use distant_protocol::Permissions;
|
|
///
|
|
/// assert_eq!(
|
|
/// Permissions { owner_write: Some(true), ..Default::default() }.is_writable(),
|
|
/// Some(true)
|
|
/// );
|
|
///
|
|
/// assert_eq!(
|
|
/// Permissions { owner_write: Some(false), ..Default::default() }.is_writable(),
|
|
/// Some(false)
|
|
/// );
|
|
///
|
|
/// assert_eq!(
|
|
/// Permissions { ..Default::default() }.is_writable(),
|
|
/// None
|
|
/// );
|
|
/// ```
|
|
#[inline]
|
|
pub fn is_writable(&self) -> Option<bool> {
|
|
match (self.owner_write, self.group_write, self.other_write) {
|
|
(None, None, None) => None,
|
|
(owner, group, other) => {
|
|
Some(owner.unwrap_or(false) || group.unwrap_or(false) || other.unwrap_or(false))
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Applies `other` settings to `self`, overwriting any of the permissions in `self` with `other`.
|
|
///
|
|
/// ```
|
|
/// use distant_protocol::Permissions;
|
|
///
|
|
/// let mut a = Permissions {
|
|
/// owner_read: Some(true),
|
|
/// owner_write: Some(false),
|
|
/// owner_exec: None,
|
|
/// ..Default::default()
|
|
/// };
|
|
///
|
|
/// let b = Permissions {
|
|
/// owner_read: Some(false),
|
|
/// owner_write: None,
|
|
/// owner_exec: Some(true),
|
|
/// ..Default::default()
|
|
/// };
|
|
///
|
|
/// a.apply_from(&b);
|
|
///
|
|
/// assert_eq!(a, Permissions {
|
|
/// owner_read: Some(false),
|
|
/// owner_write: Some(false),
|
|
/// owner_exec: Some(true),
|
|
/// ..Default::default()
|
|
/// });
|
|
/// ```
|
|
#[inline]
|
|
pub fn apply_from(&mut self, other: &Self) {
|
|
macro_rules! apply {
|
|
($key:ident) => {{
|
|
if let Some(value) = other.$key {
|
|
self.$key = Some(value);
|
|
}
|
|
}};
|
|
}
|
|
|
|
apply!(owner_read);
|
|
apply!(owner_write);
|
|
apply!(owner_exec);
|
|
apply!(group_read);
|
|
apply!(group_write);
|
|
apply!(group_exec);
|
|
apply!(other_read);
|
|
apply!(other_write);
|
|
apply!(other_exec);
|
|
}
|
|
|
|
/// Applies `self` settings to `other`, overwriting any of the permissions in `other` with
|
|
/// `self`.
|
|
///
|
|
/// ```
|
|
/// use distant_protocol::Permissions;
|
|
///
|
|
/// let a = Permissions {
|
|
/// owner_read: Some(true),
|
|
/// owner_write: Some(false),
|
|
/// owner_exec: None,
|
|
/// ..Default::default()
|
|
/// };
|
|
///
|
|
/// let mut b = Permissions {
|
|
/// owner_read: Some(false),
|
|
/// owner_write: None,
|
|
/// owner_exec: Some(true),
|
|
/// ..Default::default()
|
|
/// };
|
|
///
|
|
/// a.apply_to(&mut b);
|
|
///
|
|
/// assert_eq!(b, Permissions {
|
|
/// owner_read: Some(true),
|
|
/// owner_write: Some(false),
|
|
/// owner_exec: Some(true),
|
|
/// ..Default::default()
|
|
/// });
|
|
/// ```
|
|
#[inline]
|
|
pub fn apply_to(&self, other: &mut Self) {
|
|
Self::apply_from(other, self)
|
|
}
|
|
|
|
/// Converts a Unix `mode` into the permission set.
|
|
pub fn from_unix_mode(mode: u32) -> Self {
|
|
let flags = UnixFilePermissionFlags::from_bits_truncate(mode);
|
|
Self {
|
|
owner_read: Some(flags.contains(UnixFilePermissionFlags::OWNER_READ)),
|
|
owner_write: Some(flags.contains(UnixFilePermissionFlags::OWNER_WRITE)),
|
|
owner_exec: Some(flags.contains(UnixFilePermissionFlags::OWNER_EXEC)),
|
|
group_read: Some(flags.contains(UnixFilePermissionFlags::GROUP_READ)),
|
|
group_write: Some(flags.contains(UnixFilePermissionFlags::GROUP_WRITE)),
|
|
group_exec: Some(flags.contains(UnixFilePermissionFlags::GROUP_EXEC)),
|
|
other_read: Some(flags.contains(UnixFilePermissionFlags::OTHER_READ)),
|
|
other_write: Some(flags.contains(UnixFilePermissionFlags::OTHER_WRITE)),
|
|
other_exec: Some(flags.contains(UnixFilePermissionFlags::OTHER_EXEC)),
|
|
}
|
|
}
|
|
|
|
/// Converts to a Unix `mode` from a permission set. For any missing setting, a 0 bit is used.
|
|
///
|
|
/// ```
|
|
/// use distant_protocol::Permissions;
|
|
///
|
|
/// assert_eq!(Permissions {
|
|
/// owner_read: Some(true),
|
|
/// owner_write: Some(true),
|
|
/// owner_exec: Some(true),
|
|
/// group_read: Some(true),
|
|
/// group_write: Some(true),
|
|
/// group_exec: Some(true),
|
|
/// other_read: Some(true),
|
|
/// other_write: Some(true),
|
|
/// other_exec: Some(true),
|
|
/// }.to_unix_mode(), 0o777);
|
|
///
|
|
/// assert_eq!(Permissions {
|
|
/// owner_read: Some(true),
|
|
/// owner_write: Some(false),
|
|
/// owner_exec: Some(false),
|
|
/// group_read: Some(true),
|
|
/// group_write: Some(false),
|
|
/// group_exec: Some(false),
|
|
/// other_read: Some(true),
|
|
/// other_write: Some(false),
|
|
/// other_exec: Some(false),
|
|
/// }.to_unix_mode(), 0o444);
|
|
///
|
|
/// assert_eq!(Permissions {
|
|
/// owner_exec: Some(true),
|
|
/// group_exec: Some(true),
|
|
/// other_exec: Some(true),
|
|
/// ..Default::default()
|
|
/// }.to_unix_mode(), 0o111);
|
|
/// ```
|
|
pub fn to_unix_mode(&self) -> u32 {
|
|
let mut flags = UnixFilePermissionFlags::empty();
|
|
|
|
macro_rules! is_true {
|
|
($opt:expr) => {{
|
|
$opt.is_some() && $opt.unwrap()
|
|
}};
|
|
}
|
|
|
|
if is_true!(self.owner_read) {
|
|
flags.insert(UnixFilePermissionFlags::OWNER_READ);
|
|
}
|
|
if is_true!(self.owner_write) {
|
|
flags.insert(UnixFilePermissionFlags::OWNER_WRITE);
|
|
}
|
|
if is_true!(self.owner_exec) {
|
|
flags.insert(UnixFilePermissionFlags::OWNER_EXEC);
|
|
}
|
|
|
|
if is_true!(self.group_read) {
|
|
flags.insert(UnixFilePermissionFlags::GROUP_READ);
|
|
}
|
|
if is_true!(self.group_write) {
|
|
flags.insert(UnixFilePermissionFlags::GROUP_WRITE);
|
|
}
|
|
if is_true!(self.group_exec) {
|
|
flags.insert(UnixFilePermissionFlags::GROUP_EXEC);
|
|
}
|
|
|
|
if is_true!(self.other_read) {
|
|
flags.insert(UnixFilePermissionFlags::OTHER_READ);
|
|
}
|
|
if is_true!(self.other_write) {
|
|
flags.insert(UnixFilePermissionFlags::OTHER_WRITE);
|
|
}
|
|
if is_true!(self.other_exec) {
|
|
flags.insert(UnixFilePermissionFlags::OTHER_EXEC);
|
|
}
|
|
|
|
flags.bits()
|
|
}
|
|
}
|
|
|
|
#[cfg(unix)]
|
|
impl From<std::fs::Permissions> for Permissions {
|
|
/// Converts [`std::fs::Permissions`] into [`Permissions`] using
|
|
/// [`std::os::unix::fs::PermissionsExt::mode`] to supply the bitset.
|
|
fn from(permissions: std::fs::Permissions) -> Self {
|
|
use std::os::unix::prelude::*;
|
|
Self::from_unix_mode(permissions.mode())
|
|
}
|
|
}
|
|
|
|
#[cfg(not(unix))]
|
|
impl From<std::fs::Permissions> for Permissions {
|
|
/// Converts [`std::fs::Permissions`] into [`Permissions`] using the `readonly` flag.
|
|
///
|
|
/// This will not set executable flags, but will set all read and write flags with write flags
|
|
/// being `false` if `readonly`, otherwise set to `true`.
|
|
fn from(permissions: std::fs::Permissions) -> Self {
|
|
if permissions.readonly() {
|
|
Self::readonly()
|
|
} else {
|
|
Self::writable()
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(unix)]
|
|
impl From<Permissions> for std::fs::Permissions {
|
|
/// Converts [`Permissions`] into [`std::fs::Permissions`] using
|
|
/// [`std::os::unix::fs::PermissionsExt::from_mode`].
|
|
fn from(permissions: Permissions) -> Self {
|
|
use std::os::unix::prelude::*;
|
|
std::fs::Permissions::from_mode(permissions.to_unix_mode())
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn should_be_able_to_serialize_minimal_permissions_to_json() {
|
|
let permissions = Permissions {
|
|
owner_read: None,
|
|
owner_write: None,
|
|
owner_exec: None,
|
|
group_read: None,
|
|
group_write: None,
|
|
group_exec: None,
|
|
other_read: None,
|
|
other_write: None,
|
|
other_exec: None,
|
|
};
|
|
|
|
let value = serde_json::to_value(permissions).unwrap();
|
|
assert_eq!(value, serde_json::json!({}));
|
|
}
|
|
|
|
#[test]
|
|
fn should_be_able_to_serialize_full_permissions_to_json() {
|
|
let permissions = Permissions {
|
|
owner_read: Some(true),
|
|
owner_write: Some(false),
|
|
owner_exec: Some(true),
|
|
group_read: Some(false),
|
|
group_write: Some(true),
|
|
group_exec: Some(false),
|
|
other_read: Some(true),
|
|
other_write: Some(false),
|
|
other_exec: Some(true),
|
|
};
|
|
|
|
let value = serde_json::to_value(permissions).unwrap();
|
|
assert_eq!(
|
|
value,
|
|
serde_json::json!({
|
|
"owner_read": true,
|
|
"owner_write": false,
|
|
"owner_exec": true,
|
|
"group_read": false,
|
|
"group_write": true,
|
|
"group_exec": false,
|
|
"other_read": true,
|
|
"other_write": false,
|
|
"other_exec": true,
|
|
})
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn should_be_able_to_deserialize_minimal_permissions_from_json() {
|
|
let value = serde_json::json!({});
|
|
|
|
let permissions: Permissions = serde_json::from_value(value).unwrap();
|
|
assert_eq!(
|
|
permissions,
|
|
Permissions {
|
|
owner_read: None,
|
|
owner_write: None,
|
|
owner_exec: None,
|
|
group_read: None,
|
|
group_write: None,
|
|
group_exec: None,
|
|
other_read: None,
|
|
other_write: None,
|
|
other_exec: None,
|
|
}
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn should_be_able_to_deserialize_full_permissions_from_json() {
|
|
let value = serde_json::json!({
|
|
"owner_read": true,
|
|
"owner_write": false,
|
|
"owner_exec": true,
|
|
"group_read": false,
|
|
"group_write": true,
|
|
"group_exec": false,
|
|
"other_read": true,
|
|
"other_write": false,
|
|
"other_exec": true,
|
|
});
|
|
|
|
let permissions: Permissions = serde_json::from_value(value).unwrap();
|
|
assert_eq!(
|
|
permissions,
|
|
Permissions {
|
|
owner_read: Some(true),
|
|
owner_write: Some(false),
|
|
owner_exec: Some(true),
|
|
group_read: Some(false),
|
|
group_write: Some(true),
|
|
group_exec: Some(false),
|
|
other_read: Some(true),
|
|
other_write: Some(false),
|
|
other_exec: Some(true),
|
|
}
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn should_be_able_to_serialize_minimal_permissions_to_msgpack() {
|
|
let permissions = Permissions {
|
|
owner_read: None,
|
|
owner_write: None,
|
|
owner_exec: None,
|
|
group_read: None,
|
|
group_write: None,
|
|
group_exec: None,
|
|
other_read: None,
|
|
other_write: None,
|
|
other_exec: None,
|
|
};
|
|
|
|
// NOTE: We don't actually check the output here because it's an implementation detail
|
|
// and could change as we change how serialization is done. This is merely to verify
|
|
// that we can serialize since there are times when serde fails to serialize at
|
|
// runtime.
|
|
let _ = rmp_serde::encode::to_vec_named(&permissions).unwrap();
|
|
}
|
|
|
|
#[test]
|
|
fn should_be_able_to_serialize_full_permissions_to_msgpack() {
|
|
let permissions = Permissions {
|
|
owner_read: Some(true),
|
|
owner_write: Some(false),
|
|
owner_exec: Some(true),
|
|
group_read: Some(true),
|
|
group_write: Some(false),
|
|
group_exec: Some(true),
|
|
other_read: Some(true),
|
|
other_write: Some(false),
|
|
other_exec: Some(true),
|
|
};
|
|
|
|
// NOTE: We don't actually check the output here because it's an implementation detail
|
|
// and could change as we change how serialization is done. This is merely to verify
|
|
// that we can serialize since there are times when serde fails to serialize at
|
|
// runtime.
|
|
let _ = rmp_serde::encode::to_vec_named(&permissions).unwrap();
|
|
}
|
|
|
|
#[test]
|
|
fn should_be_able_to_deserialize_minimal_permissions_from_msgpack() {
|
|
// NOTE: It may seem odd that we are serializing just to deserialize, but this is to
|
|
// verify that we are not corrupting or preventing issues when serializing on a
|
|
// client/server and then trying to deserialize on the other side. This has happened
|
|
// enough times with minor changes that we need tests to verify.
|
|
let buf = rmp_serde::encode::to_vec_named(&Permissions {
|
|
owner_read: None,
|
|
owner_write: None,
|
|
owner_exec: None,
|
|
group_read: None,
|
|
group_write: None,
|
|
group_exec: None,
|
|
other_read: None,
|
|
other_write: None,
|
|
other_exec: None,
|
|
})
|
|
.unwrap();
|
|
|
|
let permissions: Permissions = rmp_serde::decode::from_slice(&buf).unwrap();
|
|
assert_eq!(
|
|
permissions,
|
|
Permissions {
|
|
owner_read: None,
|
|
owner_write: None,
|
|
owner_exec: None,
|
|
group_read: None,
|
|
group_write: None,
|
|
group_exec: None,
|
|
other_read: None,
|
|
other_write: None,
|
|
other_exec: None,
|
|
}
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn should_be_able_to_deserialize_full_permissions_from_msgpack() {
|
|
// NOTE: It may seem odd that we are serializing just to deserialize, but this is to
|
|
// verify that we are not corrupting or preventing issues when serializing on a
|
|
// client/server and then trying to deserialize on the other side. This has happened
|
|
// enough times with minor changes that we need tests to verify.
|
|
let buf = rmp_serde::encode::to_vec_named(&Permissions {
|
|
owner_read: Some(true),
|
|
owner_write: Some(false),
|
|
owner_exec: Some(true),
|
|
group_read: Some(true),
|
|
group_write: Some(false),
|
|
group_exec: Some(true),
|
|
other_read: Some(true),
|
|
other_write: Some(false),
|
|
other_exec: Some(true),
|
|
})
|
|
.unwrap();
|
|
|
|
let permissions: Permissions = rmp_serde::decode::from_slice(&buf).unwrap();
|
|
assert_eq!(
|
|
permissions,
|
|
Permissions {
|
|
owner_read: Some(true),
|
|
owner_write: Some(false),
|
|
owner_exec: Some(true),
|
|
group_read: Some(true),
|
|
group_write: Some(false),
|
|
group_exec: Some(true),
|
|
other_read: Some(true),
|
|
other_write: Some(false),
|
|
other_exec: Some(true),
|
|
}
|
|
);
|
|
}
|
|
}
|