1use crate::common::CreationMode;
6use crate::directory::DirectoryOptions;
7use crate::file::FileOptions;
8use crate::node::NodeOptions;
9use crate::service::ServiceOptions;
10use crate::symlink::SymlinkOptions;
11use flex_fuchsia_io as fio;
12use zx_status::Status;
13
14pub trait ProtocolsExt: ToFileOptions + ToNodeOptions + Send + Sync + 'static {
16 fn is_dir_allowed(&self) -> bool;
18
19 fn is_file_allowed(&self) -> bool;
21
22 fn is_symlink_allowed(&self) -> bool;
24
25 fn creation_mode(&self) -> CreationMode;
27
28 fn rights(&self) -> Option<fio::Operations>;
32
33 fn to_directory_options(&self) -> Result<DirectoryOptions, Status>;
35
36 fn to_symlink_options(&self) -> Result<SymlinkOptions, Status>;
38
39 fn to_service_options(&self) -> Result<ServiceOptions, Status>;
41
42 fn is_append(&self) -> bool;
44
45 fn is_truncate(&self) -> bool;
47
48 fn create_directory(&self) -> bool;
50
51 fn is_node(&self) -> bool;
53
54 fn create_unnamed_temporary_in_directory_path(&self) -> bool;
56}
57
58impl ProtocolsExt for fio::OpenFlags {
59 fn is_dir_allowed(&self) -> bool {
60 !self.contains(fio::OpenFlags::NOT_DIRECTORY)
61 }
62
63 fn is_file_allowed(&self) -> bool {
64 !self.contains(fio::OpenFlags::DIRECTORY)
65 }
66
67 fn is_symlink_allowed(&self) -> bool {
68 !self.contains(fio::OpenFlags::DIRECTORY)
69 }
70
71 fn creation_mode(&self) -> CreationMode {
72 if self.contains(fio::OpenFlags::CREATE) {
73 if self.contains(fio::OpenFlags::CREATE_IF_ABSENT) {
74 CreationMode::Always
75 } else {
76 CreationMode::AllowExisting
77 }
78 } else {
79 CreationMode::Never
80 }
81 }
82
83 fn rights(&self) -> Option<fio::Operations> {
84 if self.contains(fio::OpenFlags::CLONE_SAME_RIGHTS) {
85 None
86 } else {
87 let mut rights = fio::Operations::GET_ATTRIBUTES
88 | fio::Operations::CONNECT
89 | fio::Operations::TRAVERSE;
90 if self.contains(fio::OpenFlags::RIGHT_READABLE) {
91 rights |= fio::R_STAR_DIR;
92 }
93 if self.contains(fio::OpenFlags::RIGHT_WRITABLE) {
94 rights |= fio::W_STAR_DIR;
95 }
96 if self.contains(fio::OpenFlags::RIGHT_EXECUTABLE) {
97 rights |= fio::X_STAR_DIR;
98 }
99 Some(rights)
100 }
101 }
102
103 fn to_directory_options(&self) -> Result<DirectoryOptions, Status> {
109 assert!(!self.intersects(fio::OpenFlags::NODE_REFERENCE));
110
111 let mut flags = *self;
112
113 if flags.intersects(fio::OpenFlags::DIRECTORY) {
114 flags &= !fio::OpenFlags::DIRECTORY;
115 }
116
117 if flags.intersects(fio::OpenFlags::NOT_DIRECTORY) {
118 return Err(Status::NOT_FILE);
119 }
120
121 if flags.intersects(fio::OpenFlags::POSIX_EXECUTABLE) {
124 flags |= fio::OpenFlags::RIGHT_EXECUTABLE;
125 }
126 if flags.intersects(fio::OpenFlags::POSIX_WRITABLE) {
127 flags |= fio::OpenFlags::RIGHT_WRITABLE;
128 }
129 flags &= !(fio::OpenFlags::POSIX_WRITABLE | fio::OpenFlags::POSIX_EXECUTABLE);
130
131 let allowed_flags = fio::OpenFlags::DESCRIBE
132 | fio::OpenFlags::CREATE
133 | fio::OpenFlags::CREATE_IF_ABSENT
134 | fio::OpenFlags::DIRECTORY
135 | fio::OpenFlags::RIGHT_READABLE
136 | fio::OpenFlags::RIGHT_WRITABLE
137 | fio::OpenFlags::RIGHT_EXECUTABLE;
138
139 let prohibited_flags = fio::OpenFlags::APPEND | fio::OpenFlags::TRUNCATE;
140
141 if flags.intersects(prohibited_flags) {
142 return Err(Status::INVALID_ARGS);
143 }
144
145 if flags.intersects(!allowed_flags) {
146 return Err(Status::NOT_SUPPORTED);
147 }
148
149 let mut rights = fio::Rights::GET_ATTRIBUTES | fio::Rights::TRAVERSE;
152 if flags.contains(fio::OpenFlags::RIGHT_READABLE) {
153 rights |= fio::R_STAR_DIR;
154 }
155 if flags.contains(fio::OpenFlags::RIGHT_WRITABLE) {
156 rights |= fio::W_STAR_DIR;
157 }
158 if flags.contains(fio::OpenFlags::RIGHT_EXECUTABLE) {
159 rights |= fio::X_STAR_DIR;
160 }
161
162 Ok(DirectoryOptions { rights })
163 }
164
165 fn to_symlink_options(&self) -> Result<SymlinkOptions, Status> {
166 if self.intersects(fio::OpenFlags::DIRECTORY) {
167 return Err(Status::NOT_DIR);
168 }
169
170 let optional = fio::OpenFlags::NOT_DIRECTORY
173 | fio::OpenFlags::DESCRIBE
174 | fio::OpenFlags::RIGHT_WRITABLE
175 | fio::OpenFlags::RIGHT_EXECUTABLE;
176
177 if *self & !optional != fio::OpenFlags::RIGHT_READABLE {
178 return Err(Status::INVALID_ARGS);
179 }
180
181 let mut rights = fio::Operations::GET_ATTRIBUTES;
182 if self.contains(fio::OpenFlags::RIGHT_READABLE) {
183 rights |= fio::Operations::READ_BYTES;
184 }
185 if self.contains(fio::OpenFlags::RIGHT_WRITABLE) {
186 rights |= fio::Operations::WRITE_BYTES | fio::Operations::UPDATE_ATTRIBUTES;
187 }
188 if self.contains(fio::OpenFlags::RIGHT_EXECUTABLE) {
189 rights |= fio::Operations::EXECUTE;
190 }
191
192 Ok(SymlinkOptions { rights })
193 }
194
195 fn to_service_options(&self) -> Result<ServiceOptions, Status> {
196 if self.intersects(fio::OpenFlags::DIRECTORY) {
197 return Err(Status::NOT_DIR);
198 }
199
200 if self.intersects(!fio::OpenFlags::DESCRIBE.union(fio::OpenFlags::NOT_DIRECTORY)) {
201 return Err(Status::INVALID_ARGS);
202 }
203
204 Ok(ServiceOptions)
205 }
206
207 fn is_append(&self) -> bool {
208 self.contains(fio::OpenFlags::APPEND)
209 }
210
211 fn is_truncate(&self) -> bool {
212 self.contains(fio::OpenFlags::TRUNCATE)
213 }
214
215 fn create_directory(&self) -> bool {
216 self.contains(fio::OpenFlags::DIRECTORY)
217 }
218
219 fn is_node(&self) -> bool {
220 self.contains(fio::OpenFlags::NODE_REFERENCE)
221 }
222
223 fn create_unnamed_temporary_in_directory_path(&self) -> bool {
224 false
225 }
226}
227
228impl ProtocolsExt for fio::Flags {
229 fn is_dir_allowed(&self) -> bool {
230 self.contains(fio::Flags::PROTOCOL_DIRECTORY)
231 || self.intersection(fio::MASK_KNOWN_PROTOCOLS).is_empty()
232 }
233
234 fn is_file_allowed(&self) -> bool {
235 self.contains(fio::Flags::PROTOCOL_FILE)
236 || self.intersection(fio::MASK_KNOWN_PROTOCOLS).is_empty()
237 }
238
239 fn is_symlink_allowed(&self) -> bool {
240 self.contains(fio::Flags::PROTOCOL_SYMLINK)
241 || self.intersection(fio::MASK_KNOWN_PROTOCOLS).is_empty()
242 }
243
244 fn creation_mode(&self) -> CreationMode {
245 #[cfg(fuchsia_api_level_at_least = "HEAD")]
246 {
247 if self.contains(fio::Flags::FLAG_CREATE_AS_UNNAMED_TEMPORARY) {
248 if self.contains(fio::Flags::FLAG_MUST_CREATE) {
249 return CreationMode::UnlinkableUnnamedTemporary;
250 }
251 return CreationMode::UnnamedTemporary;
252 }
253 }
254 if self.contains(fio::Flags::FLAG_MUST_CREATE) {
255 CreationMode::Always
256 } else if self.contains(fio::Flags::FLAG_MAYBE_CREATE) {
257 CreationMode::AllowExisting
258 } else {
259 CreationMode::Never
260 }
261 }
262
263 fn rights(&self) -> Option<fio::Operations> {
264 Some(flags_to_rights(self))
265 }
266
267 fn to_directory_options(&self) -> Result<DirectoryOptions, Status> {
268 if !self.is_dir_allowed() {
270 return Err(if self.is_file_allowed() { Status::NOT_FILE } else { Status::WRONG_TYPE });
271 }
272
273 let mut updated_flags = *self;
277 if updated_flags.contains(fio::Flags::PERM_INHERIT_WRITE) {
278 updated_flags |=
279 fio::Flags::from_bits_truncate(fio::INHERITED_WRITE_PERMISSIONS.bits());
280 }
281 if updated_flags.contains(fio::Flags::PERM_INHERIT_EXECUTE) {
282 updated_flags |= fio::Flags::PERM_EXECUTE;
283 }
284
285 if updated_flags.intersects(fio::Flags::FILE_APPEND | fio::Flags::FILE_TRUNCATE) {
287 return Err(Status::INVALID_ARGS);
288 }
289
290 Ok(DirectoryOptions { rights: flags_to_rights(&updated_flags) })
291 }
292
293 fn to_symlink_options(&self) -> Result<SymlinkOptions, Status> {
294 if !self.is_symlink_allowed() {
295 return Err(Status::WRONG_TYPE);
296 }
297
298 let rights = self.rights().unwrap();
300 if !rights.contains(fio::Operations::GET_ATTRIBUTES) {
301 return Err(Status::INVALID_ARGS);
302 }
303 Ok(SymlinkOptions { rights })
304 }
305
306 fn to_service_options(&self) -> Result<ServiceOptions, Status> {
307 assert!(!self.contains(fio::Flags::PROTOCOL_NODE));
310 if !self
311 .intersection(fio::MASK_KNOWN_PROTOCOLS)
312 .difference(fio::Flags::PROTOCOL_SERVICE)
313 .is_empty()
314 {
315 return if self.is_dir_allowed() {
316 Err(Status::NOT_DIR)
317 } else if self.is_file_allowed() {
318 Err(Status::NOT_FILE)
319 } else {
320 Err(Status::WRONG_TYPE)
321 };
322 }
323
324 if !self.difference(fio::Flags::PROTOCOL_SERVICE).is_empty() {
325 return Err(Status::INVALID_ARGS);
326 }
327
328 Ok(ServiceOptions)
329 }
330
331 fn is_append(&self) -> bool {
332 self.contains(fio::Flags::FILE_APPEND)
333 }
334
335 fn is_truncate(&self) -> bool {
336 self.contains(fio::Flags::FILE_TRUNCATE)
337 }
338
339 fn create_directory(&self) -> bool {
340 self.contains(fio::Flags::PROTOCOL_DIRECTORY)
341 }
342
343 fn is_node(&self) -> bool {
344 self.contains(fio::Flags::PROTOCOL_NODE)
345 }
346
347 fn create_unnamed_temporary_in_directory_path(&self) -> bool {
348 self.creation_mode() == CreationMode::UnnamedTemporary
349 || self.creation_mode() == CreationMode::UnlinkableUnnamedTemporary
350 }
351}
352
353pub trait ToFileOptions: Send + 'static {
354 fn to_file_options(&self) -> Result<FileOptions, Status>;
355}
356
357impl ToFileOptions for fio::OpenFlags {
358 fn to_file_options(&self) -> Result<FileOptions, Status> {
359 assert!(!self.intersects(fio::OpenFlags::NODE_REFERENCE));
360
361 if self.contains(fio::OpenFlags::DIRECTORY) {
362 return Err(Status::NOT_DIR);
363 }
364
365 let flags_without_rights = self.difference(
367 fio::OpenFlags::RIGHT_READABLE
368 | fio::OpenFlags::RIGHT_WRITABLE
369 | fio::OpenFlags::RIGHT_EXECUTABLE,
370 );
371 const ALLOWED_FLAGS: fio::OpenFlags = fio::OpenFlags::DESCRIBE
372 .union(fio::OpenFlags::CREATE)
373 .union(fio::OpenFlags::CREATE_IF_ABSENT)
374 .union(fio::OpenFlags::APPEND)
375 .union(fio::OpenFlags::TRUNCATE)
376 .union(fio::OpenFlags::POSIX_WRITABLE)
377 .union(fio::OpenFlags::POSIX_EXECUTABLE)
378 .union(fio::OpenFlags::NOT_DIRECTORY);
379 if flags_without_rights.intersects(!ALLOWED_FLAGS) {
380 return Err(Status::NOT_SUPPORTED);
381 }
382
383 let mut prohibited_flags = fio::OpenFlags::empty();
385 if !self.intersects(fio::OpenFlags::RIGHT_WRITABLE) {
386 prohibited_flags |= fio::OpenFlags::TRUNCATE
387 }
388 if self.intersects(prohibited_flags) {
389 return Err(Status::INVALID_ARGS);
390 }
391
392 Ok(FileOptions {
393 rights: {
394 let mut rights = fio::Operations::GET_ATTRIBUTES;
395 if self.contains(fio::OpenFlags::RIGHT_READABLE) {
396 rights |= fio::Operations::READ_BYTES;
397 }
398 if self.contains(fio::OpenFlags::RIGHT_WRITABLE) {
399 rights |= fio::Operations::WRITE_BYTES | fio::Operations::UPDATE_ATTRIBUTES;
400 }
401 if self.contains(fio::OpenFlags::RIGHT_EXECUTABLE) {
402 rights |= fio::Operations::EXECUTE;
403 }
404 rights
405 },
406 is_append: self.contains(fio::OpenFlags::APPEND),
407 #[cfg(fuchsia_api_level_at_least = "HEAD")]
408 is_linkable: true,
409 })
410 }
411}
412
413impl ToFileOptions for fio::Flags {
414 fn to_file_options(&self) -> Result<FileOptions, Status> {
415 if !self.is_file_allowed() {
417 if self.is_dir_allowed() && !self.is_symlink_allowed() {
418 return Err(Status::NOT_DIR);
419 } else {
420 return Err(Status::WRONG_TYPE);
421 }
422 }
423
424 if self.contains(fio::Flags::FILE_TRUNCATE) && !self.contains(fio::Flags::PERM_WRITE_BYTES)
426 {
427 return Err(Status::INVALID_ARGS);
428 }
429
430 const ALLOWED_RIGHTS: fio::Operations = fio::Operations::empty()
432 .union(fio::Operations::GET_ATTRIBUTES)
433 .union(fio::Operations::READ_BYTES)
434 .union(fio::Operations::WRITE_BYTES)
435 .union(fio::Operations::UPDATE_ATTRIBUTES)
436 .union(fio::Operations::EXECUTE);
437
438 Ok(FileOptions {
439 rights: flags_to_rights(self).intersection(ALLOWED_RIGHTS),
440 is_append: self.contains(fio::Flags::FILE_APPEND),
441 #[cfg(fuchsia_api_level_at_least = "HEAD")]
442 is_linkable: !self.contains(
443 fio::Flags::FLAG_CREATE_AS_UNNAMED_TEMPORARY | fio::FLAG_TEMPORARY_AS_NOT_LINKABLE,
444 ),
445 })
446 }
447}
448
449impl ToFileOptions for FileOptions {
450 fn to_file_options(&self) -> Result<FileOptions, Status> {
451 Ok(*self)
452 }
453}
454
455pub trait ToNodeOptions: Send + 'static {
456 fn to_node_options(&self, dirent_type: fio::DirentType) -> Result<NodeOptions, Status>;
457}
458
459impl ToNodeOptions for fio::OpenFlags {
460 fn to_node_options(&self, dirent_type: fio::DirentType) -> Result<NodeOptions, Status> {
461 let allowed_rights =
465 fio::OPEN_RIGHTS | fio::OpenFlags::POSIX_WRITABLE | fio::OpenFlags::POSIX_EXECUTABLE;
466 if self.intersects(!(fio::OPEN_FLAGS_ALLOWED_WITH_NODE_REFERENCE | allowed_rights)) {
467 Err(Status::INVALID_ARGS)
468 } else if self.contains(fio::OpenFlags::DIRECTORY)
469 && dirent_type != fio::DirentType::Directory
470 {
471 Err(Status::NOT_DIR)
472 } else {
473 Ok(NodeOptions { rights: fio::Operations::GET_ATTRIBUTES })
474 }
475 }
476}
477
478impl ToNodeOptions for fio::Flags {
479 fn to_node_options(&self, dirent_type: fio::DirentType) -> Result<NodeOptions, Status> {
480 const ALLOWED_FLAGS: fio::Flags = fio::Flags::FLAG_SEND_REPRESENTATION
484 .union(fio::MASK_KNOWN_PERMISSIONS)
485 .union(fio::MASK_KNOWN_PROTOCOLS);
486
487 if self.intersects(!ALLOWED_FLAGS) {
488 return Err(Status::INVALID_ARGS);
489 }
490
491 if self.intersects(fio::MASK_KNOWN_PROTOCOLS.difference(fio::Flags::PROTOCOL_NODE)) {
494 if dirent_type == fio::DirentType::Directory {
495 if !self.intersects(fio::Flags::PROTOCOL_DIRECTORY) {
496 if self.intersects(fio::Flags::PROTOCOL_FILE) {
497 return Err(Status::NOT_FILE);
498 } else {
499 return Err(Status::WRONG_TYPE);
500 }
501 }
502 } else if dirent_type == fio::DirentType::File {
503 if !self.intersects(fio::Flags::PROTOCOL_FILE) {
504 if self.intersects(fio::Flags::PROTOCOL_DIRECTORY) {
505 return Err(Status::NOT_DIR);
506 } else {
507 return Err(Status::WRONG_TYPE);
508 }
509 }
510 } else if dirent_type == fio::DirentType::Symlink {
511 if !self.intersects(fio::Flags::PROTOCOL_SYMLINK) {
512 return Err(Status::WRONG_TYPE);
513 }
514 }
515 }
516
517 Ok(NodeOptions {
518 rights: flags_to_rights(self).intersection(fio::Operations::GET_ATTRIBUTES),
519 })
520 }
521}
522
523impl ToNodeOptions for NodeOptions {
524 fn to_node_options(&self, _dirent_type: fio::DirentType) -> Result<NodeOptions, Status> {
525 Ok(*self)
526 }
527}
528
529fn flags_to_rights(flags: &fio::Flags) -> fio::Rights {
530 fio::Rights::from_bits_truncate(flags.bits())
531}