use crate::common::CreationMode;
use crate::directory::DirectoryOptions;
use crate::file::FileOptions;
use crate::node::NodeOptions;
use crate::service::ServiceOptions;
use crate::symlink::SymlinkOptions;
use fidl_fuchsia_io as fio;
use zx_status::Status;
pub trait ProtocolsExt: ToFileOptions + ToNodeOptions + Sync + 'static {
fn is_dir_allowed(&self) -> bool;
fn is_file_allowed(&self) -> bool;
fn is_symlink_allowed(&self) -> bool;
fn is_any_node_protocol_allowed(&self) -> bool;
fn creation_mode(&self) -> CreationMode;
fn rights(&self) -> Option<fio::Operations>;
fn to_directory_options(&self) -> Result<DirectoryOptions, Status>;
fn to_symlink_options(&self) -> Result<SymlinkOptions, Status>;
fn to_service_options(&self) -> Result<ServiceOptions, Status>;
fn get_representation(&self) -> bool;
fn is_append(&self) -> bool;
fn is_truncate(&self) -> bool;
fn create_directory(&self) -> bool;
fn is_node(&self) -> bool;
}
impl ProtocolsExt for fio::OpenFlags {
fn is_dir_allowed(&self) -> bool {
!self.contains(fio::OpenFlags::NOT_DIRECTORY)
}
fn is_file_allowed(&self) -> bool {
!self.contains(fio::OpenFlags::DIRECTORY)
}
fn is_symlink_allowed(&self) -> bool {
!self.contains(fio::OpenFlags::DIRECTORY)
}
fn is_any_node_protocol_allowed(&self) -> bool {
!self.intersects(fio::OpenFlags::DIRECTORY | fio::OpenFlags::NOT_DIRECTORY)
}
fn creation_mode(&self) -> CreationMode {
if self.contains(fio::OpenFlags::CREATE) {
if self.contains(fio::OpenFlags::CREATE_IF_ABSENT) {
CreationMode::Always
} else {
CreationMode::AllowExisting
}
} else {
CreationMode::Never
}
}
fn rights(&self) -> Option<fio::Operations> {
if self.contains(fio::OpenFlags::CLONE_SAME_RIGHTS) {
None
} else {
let mut rights = fio::Operations::GET_ATTRIBUTES | fio::Operations::CONNECT;
if self.contains(fio::OpenFlags::RIGHT_READABLE) {
rights |= fio::R_STAR_DIR;
}
if self.contains(fio::OpenFlags::RIGHT_WRITABLE) {
rights |= fio::W_STAR_DIR;
}
if self.contains(fio::OpenFlags::RIGHT_EXECUTABLE) {
rights |= fio::X_STAR_DIR;
}
Some(rights)
}
}
fn to_directory_options(&self) -> Result<DirectoryOptions, Status> {
assert!(!self.intersects(fio::OpenFlags::NODE_REFERENCE));
let mut flags = *self;
if flags.intersects(fio::OpenFlags::DIRECTORY) {
flags &= !fio::OpenFlags::DIRECTORY;
}
if flags.intersects(fio::OpenFlags::NOT_DIRECTORY) {
return Err(Status::NOT_FILE);
}
if flags.intersects(fio::OpenFlags::POSIX_EXECUTABLE) {
flags |= fio::OpenFlags::RIGHT_EXECUTABLE;
}
if flags.intersects(fio::OpenFlags::POSIX_WRITABLE) {
flags |= fio::OpenFlags::RIGHT_WRITABLE;
}
flags &= !(fio::OpenFlags::POSIX_WRITABLE | fio::OpenFlags::POSIX_EXECUTABLE);
let allowed_flags = fio::OpenFlags::DESCRIBE
| fio::OpenFlags::CREATE
| fio::OpenFlags::CREATE_IF_ABSENT
| fio::OpenFlags::DIRECTORY
| fio::OpenFlags::RIGHT_READABLE
| fio::OpenFlags::RIGHT_WRITABLE
| fio::OpenFlags::RIGHT_EXECUTABLE;
let prohibited_flags = fio::OpenFlags::APPEND | fio::OpenFlags::TRUNCATE;
if flags.intersects(prohibited_flags) {
return Err(Status::INVALID_ARGS);
}
if flags.intersects(!allowed_flags) {
return Err(Status::NOT_SUPPORTED);
}
let mut rights = fio::Rights::GET_ATTRIBUTES;
if flags.contains(fio::OpenFlags::RIGHT_READABLE) {
rights |= fio::R_STAR_DIR;
}
if flags.contains(fio::OpenFlags::RIGHT_WRITABLE) {
rights |= fio::W_STAR_DIR;
}
if flags.contains(fio::OpenFlags::RIGHT_EXECUTABLE) {
rights |= fio::X_STAR_DIR;
}
Ok(DirectoryOptions { rights })
}
fn to_symlink_options(&self) -> Result<SymlinkOptions, Status> {
if self.intersects(fio::OpenFlags::DIRECTORY) {
return Err(Status::NOT_DIR);
}
let optional = fio::OpenFlags::NOT_DIRECTORY
| fio::OpenFlags::DESCRIBE
| fio::OpenFlags::RIGHT_WRITABLE
| fio::OpenFlags::RIGHT_EXECUTABLE;
if *self & !optional != fio::OpenFlags::RIGHT_READABLE {
return Err(Status::INVALID_ARGS);
}
Ok(SymlinkOptions)
}
fn to_service_options(&self) -> Result<ServiceOptions, Status> {
if self.intersects(fio::OpenFlags::DIRECTORY) {
return Err(Status::NOT_DIR);
}
if self.intersects(!fio::OpenFlags::DESCRIBE.union(fio::OpenFlags::NOT_DIRECTORY)) {
return Err(Status::INVALID_ARGS);
}
Ok(ServiceOptions)
}
fn get_representation(&self) -> bool {
false
}
fn is_append(&self) -> bool {
self.contains(fio::OpenFlags::APPEND)
}
fn is_truncate(&self) -> bool {
self.contains(fio::OpenFlags::TRUNCATE)
}
fn create_directory(&self) -> bool {
self.contains(fio::OpenFlags::DIRECTORY)
}
fn is_node(&self) -> bool {
self.contains(fio::OpenFlags::NODE_REFERENCE)
}
}
impl ProtocolsExt for fio::Flags {
fn is_dir_allowed(&self) -> bool {
self.contains(fio::Flags::PROTOCOL_DIRECTORY) || self.is_any_node_protocol_allowed()
}
fn is_file_allowed(&self) -> bool {
self.contains(fio::Flags::PROTOCOL_FILE) || self.is_any_node_protocol_allowed()
}
fn is_symlink_allowed(&self) -> bool {
self.contains(fio::Flags::PROTOCOL_SYMLINK) || self.is_any_node_protocol_allowed()
}
fn is_any_node_protocol_allowed(&self) -> bool {
self.intersection(fio::MASK_KNOWN_PROTOCOLS).is_empty()
|| self.contains(fio::Flags::PROTOCOL_NODE)
}
fn creation_mode(&self) -> CreationMode {
if self.contains(fio::Flags::FLAG_MUST_CREATE) {
CreationMode::Always
} else if self.contains(fio::Flags::FLAG_MAYBE_CREATE) {
CreationMode::AllowExisting
} else {
CreationMode::Never
}
}
fn rights(&self) -> Option<fio::Operations> {
Some(flags_to_rights(self))
}
fn to_directory_options(&self) -> Result<DirectoryOptions, Status> {
if !self.is_dir_allowed() {
if self.is_file_allowed() && !self.is_symlink_allowed() {
return Err(Status::NOT_FILE);
} else {
return Err(Status::WRONG_TYPE);
}
}
let mut updated_flags = *self;
if updated_flags.contains(fio::Flags::PERM_INHERIT_WRITE) {
updated_flags |= fio::INHERITED_WRITE_PERMISSIONS.to_flags();
}
if updated_flags.contains(fio::Flags::PERM_INHERIT_EXECUTE) {
updated_flags |= fio::Flags::PERM_EXECUTE;
}
if updated_flags.intersects(fio::Flags::FILE_APPEND | fio::Flags::FILE_TRUNCATE) {
return Err(Status::INVALID_ARGS);
}
Ok(DirectoryOptions { rights: flags_to_rights(&updated_flags) })
}
fn to_symlink_options(&self) -> Result<SymlinkOptions, Status> {
if !self.is_symlink_allowed() {
return Err(Status::WRONG_TYPE);
}
if !self.rights().unwrap().contains(fio::Operations::GET_ATTRIBUTES) {
return Err(Status::INVALID_ARGS);
}
Ok(SymlinkOptions)
}
fn to_service_options(&self) -> Result<ServiceOptions, Status> {
if !self.difference(fio::Flags::PROTOCOL_SERVICE).is_empty()
&& !self.contains(fio::Flags::PROTOCOL_NODE)
{
return if self.is_dir_allowed() {
Err(Status::NOT_DIR)
} else if self.is_file_allowed() {
Err(Status::NOT_FILE)
} else {
Err(Status::WRONG_TYPE)
};
}
Ok(ServiceOptions)
}
fn get_representation(&self) -> bool {
self.contains(fio::Flags::FLAG_SEND_REPRESENTATION)
}
fn is_append(&self) -> bool {
self.contains(fio::Flags::FILE_APPEND)
}
fn is_truncate(&self) -> bool {
self.contains(fio::Flags::FILE_TRUNCATE)
}
fn create_directory(&self) -> bool {
self.contains(fio::Flags::PROTOCOL_DIRECTORY)
}
fn is_node(&self) -> bool {
self.contains(fio::Flags::PROTOCOL_NODE)
}
}
pub trait ToFileOptions {
fn to_file_options(&self) -> Result<FileOptions, Status>;
}
impl ToFileOptions for fio::OpenFlags {
fn to_file_options(&self) -> Result<FileOptions, Status> {
assert!(!self.intersects(fio::OpenFlags::NODE_REFERENCE));
if self.contains(fio::OpenFlags::DIRECTORY) {
return Err(Status::NOT_DIR);
}
let flags_without_rights = self.difference(
fio::OpenFlags::RIGHT_READABLE
| fio::OpenFlags::RIGHT_WRITABLE
| fio::OpenFlags::RIGHT_EXECUTABLE,
);
const ALLOWED_FLAGS: fio::OpenFlags = fio::OpenFlags::DESCRIBE
.union(fio::OpenFlags::CREATE)
.union(fio::OpenFlags::CREATE_IF_ABSENT)
.union(fio::OpenFlags::APPEND)
.union(fio::OpenFlags::TRUNCATE)
.union(fio::OpenFlags::POSIX_WRITABLE)
.union(fio::OpenFlags::POSIX_EXECUTABLE)
.union(fio::OpenFlags::NOT_DIRECTORY);
if flags_without_rights.intersects(!ALLOWED_FLAGS) {
return Err(Status::NOT_SUPPORTED);
}
let mut prohibited_flags = fio::OpenFlags::empty();
if !self.intersects(fio::OpenFlags::RIGHT_WRITABLE) {
prohibited_flags |= fio::OpenFlags::TRUNCATE
}
if self.intersects(prohibited_flags) {
return Err(Status::INVALID_ARGS);
}
Ok(FileOptions {
rights: {
let mut rights = fio::Operations::GET_ATTRIBUTES;
if self.contains(fio::OpenFlags::RIGHT_READABLE) {
rights |= fio::Operations::READ_BYTES;
}
if self.contains(fio::OpenFlags::RIGHT_WRITABLE) {
rights |= fio::Operations::WRITE_BYTES | fio::Operations::UPDATE_ATTRIBUTES;
}
if self.contains(fio::OpenFlags::RIGHT_EXECUTABLE) {
rights |= fio::Operations::EXECUTE;
}
rights
},
is_append: self.contains(fio::OpenFlags::APPEND),
})
}
}
impl ToFileOptions for fio::Flags {
fn to_file_options(&self) -> Result<FileOptions, Status> {
if !self.is_file_allowed() {
if self.is_dir_allowed() && !self.is_symlink_allowed() {
return Err(Status::NOT_DIR);
} else {
return Err(Status::WRONG_TYPE);
}
}
if self.contains(fio::Flags::FILE_TRUNCATE) && !self.contains(fio::Flags::PERM_WRITE) {
return Err(Status::INVALID_ARGS);
}
const ALLOWED_RIGHTS: fio::Operations = fio::Operations::empty()
.union(fio::Operations::GET_ATTRIBUTES)
.union(fio::Operations::READ_BYTES)
.union(fio::Operations::WRITE_BYTES)
.union(fio::Operations::UPDATE_ATTRIBUTES)
.union(fio::Operations::EXECUTE);
Ok(FileOptions {
rights: flags_to_rights(self).intersection(ALLOWED_RIGHTS),
is_append: self.contains(fio::Flags::FILE_APPEND),
})
}
}
impl ToFileOptions for FileOptions {
fn to_file_options(&self) -> Result<FileOptions, Status> {
Ok(*self)
}
}
pub trait ToNodeOptions {
fn to_node_options(&self, dirent_type: fio::DirentType) -> Result<NodeOptions, Status>;
}
impl ToNodeOptions for fio::OpenFlags {
fn to_node_options(&self, dirent_type: fio::DirentType) -> Result<NodeOptions, Status> {
let allowed_rights =
fio::OPEN_RIGHTS | fio::OpenFlags::POSIX_WRITABLE | fio::OpenFlags::POSIX_EXECUTABLE;
if self.intersects(!(fio::OPEN_FLAGS_ALLOWED_WITH_NODE_REFERENCE | allowed_rights)) {
Err(Status::INVALID_ARGS)
} else if self.contains(fio::OpenFlags::DIRECTORY)
&& dirent_type != fio::DirentType::Directory
{
Err(Status::NOT_DIR)
} else {
Ok(NodeOptions { rights: fio::Operations::GET_ATTRIBUTES })
}
}
}
impl ToNodeOptions for fio::Flags {
fn to_node_options(&self, dirent_type: fio::DirentType) -> Result<NodeOptions, Status> {
const ALLOWED_FLAGS: fio::Flags = fio::Flags::FLAG_SEND_REPRESENTATION
.union(fio::MASK_KNOWN_PERMISSIONS)
.union(fio::MASK_KNOWN_PROTOCOLS);
if self.intersects(!ALLOWED_FLAGS) {
return Err(Status::INVALID_ARGS);
}
if self.intersects(fio::MASK_KNOWN_PROTOCOLS.difference(fio::Flags::PROTOCOL_NODE)) {
if dirent_type == fio::DirentType::Directory {
if !self.intersects(fio::Flags::PROTOCOL_DIRECTORY) {
if self.intersects(fio::Flags::PROTOCOL_FILE) {
return Err(Status::NOT_FILE);
} else {
return Err(Status::WRONG_TYPE);
}
}
} else if dirent_type == fio::DirentType::File {
if !self.intersects(fio::Flags::PROTOCOL_FILE) {
if self.intersects(fio::Flags::PROTOCOL_DIRECTORY) {
return Err(Status::NOT_DIR);
} else {
return Err(Status::WRONG_TYPE);
}
}
} else if dirent_type == fio::DirentType::Symlink {
if !self.intersects(fio::Flags::PROTOCOL_SYMLINK) {
return Err(Status::WRONG_TYPE);
}
}
}
Ok(NodeOptions {
rights: flags_to_rights(self).intersection(fio::Operations::GET_ATTRIBUTES),
})
}
}
impl ToNodeOptions for NodeOptions {
fn to_node_options(&self, _dirent_type: fio::DirentType) -> Result<NodeOptions, Status> {
Ok(*self)
}
}
fn flags_to_rights(flags: &fio::Flags) -> fio::Rights {
fio::Rights::from_bits_truncate(flags.bits())
}
pub trait ToFlags {
fn to_flags(&self) -> fio::Flags;
}
impl ToFlags for fio::Operations {
fn to_flags(&self) -> fio::Flags {
fio::Flags::from_bits_truncate(self.bits())
}
}