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