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