1// Copyright 2019 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
45//! Common utilities used by several directory implementations.
67use crate::common::stricter_or_same_rights;
8use crate::directory::entry::EntryInfo;
910use byteorder::{LittleEndian, WriteBytesExt as _};
11use fidl_fuchsia_io as fio;
12use static_assertions::assert_eq_size;
13use std::io::Write as _;
14use std::mem::size_of;
15use zx_status::Status;
1617/// Directories need to make sure that connections to child entries do not receive more rights than
18/// the connection to the directory itself. Plus there is special handling of the OPEN_FLAG_POSIX_*
19/// flags. This function should be called before calling [`new_connection_validate_flags`] if both
20/// are needed.
21pub(crate) fn check_child_connection_flags(
22 parent_flags: fio::OpenFlags,
23mut flags: fio::OpenFlags,
24) -> Result<fio::OpenFlags, Status> {
25if flags & (fio::OpenFlags::NOT_DIRECTORY | fio::OpenFlags::DIRECTORY)
26 == fio::OpenFlags::NOT_DIRECTORY | fio::OpenFlags::DIRECTORY
27 {
28return Err(Status::INVALID_ARGS);
29 }
3031// Can only specify OPEN_FLAG_CREATE_IF_ABSENT if OPEN_FLAG_CREATE is also specified.
32if flags.intersects(fio::OpenFlags::CREATE_IF_ABSENT)
33 && !flags.intersects(fio::OpenFlags::CREATE)
34 {
35return Err(Status::INVALID_ARGS);
36 }
3738// Can only use CLONE_FLAG_SAME_RIGHTS when calling Clone.
39if flags.intersects(fio::OpenFlags::CLONE_SAME_RIGHTS) {
40return Err(Status::INVALID_ARGS);
41 }
4243// Remove POSIX flags when the respective rights are not available ("soft fail").
44if !parent_flags.intersects(fio::OpenFlags::RIGHT_EXECUTABLE) {
45 flags &= !fio::OpenFlags::POSIX_EXECUTABLE;
46 }
47if !parent_flags.intersects(fio::OpenFlags::RIGHT_WRITABLE) {
48 flags &= !fio::OpenFlags::POSIX_WRITABLE;
49 }
5051// Can only use CREATE flags if the parent connection is writable.
52if flags.intersects(fio::OpenFlags::CREATE)
53 && !parent_flags.intersects(fio::OpenFlags::RIGHT_WRITABLE)
54 {
55return Err(Status::ACCESS_DENIED);
56 }
5758if stricter_or_same_rights(parent_flags, flags) {
59Ok(flags)
60 } else {
61Err(Status::ACCESS_DENIED)
62 }
63}
6465/// A helper to generate binary encodings for the ReadDirents response. This function will append
66/// an entry description as specified by `entry` and `name` to the `buf`, and would return `true`.
67/// In case this would cause the buffer size to exceed `max_bytes`, the buffer is then left
68/// untouched and a `false` value is returned.
69pub(crate) fn encode_dirent(
70 buf: &mut Vec<u8>,
71 max_bytes: u64,
72 entry: &EntryInfo,
73 name: &str,
74) -> bool {
75let header_size = size_of::<u64>() + size_of::<u8>() + size_of::<u8>();
7677assert_eq_size!(u64, usize);
7879if buf.len() + header_size + name.len() > max_bytes as usize {
80return false;
81 }
8283assert!(
84 name.len() <= fio::MAX_NAME_LENGTH as usize,
85"Entry names are expected to be no longer than MAX_FILENAME ({}) bytes.\n\
86 Got entry: '{}'\n\
87 Length: {} bytes",
88 fio::MAX_NAME_LENGTH,
89 name,
90 name.len()
91 );
9293assert!(
94 fio::MAX_NAME_LENGTH <= u8::max_value() as u64,
95"Expecting to be able to store MAX_FILENAME ({}) in one byte.",
96 fio::MAX_NAME_LENGTH
97 );
9899 buf.write_u64::<LittleEndian>(entry.inode())
100 .expect("out should be an in memory buffer that grows as needed");
101 buf.write_u8(name.len() as u8).expect("out should be an in memory buffer that grows as needed");
102 buf.write_u8(entry.type_().into_primitive())
103 .expect("out should be an in memory buffer that grows as needed");
104 buf.write_all(name.as_ref()).expect("out should be an in memory buffer that grows as needed");
105106true
107}
108109#[cfg(test)]
110mod tests {
111use super::check_child_connection_flags;
112use crate::test_utils::build_flag_combinations;
113use crate::ProtocolsExt;
114115use fidl_fuchsia_io as fio;
116use zx_status::Status;
117118fn new_connection_validate_flags(flags: fio::OpenFlags) -> Result<fio::OpenFlags, Status> {
119 flags
120 .to_directory_options()
121 .map(|options| options.to_io1() | (flags & fio::OpenFlags::DESCRIBE))
122 }
123124#[track_caller]
125fn ncvf_ok(flags: fio::OpenFlags, expected_new_flags: fio::OpenFlags) {
126let res = new_connection_validate_flags(flags);
127match res {
128Ok(new_flags) => assert_eq!(
129 expected_new_flags, new_flags,
130"new_connection_validate_flags returned unexpected set of flags.\n\
131 Expected: {:X}\n\
132 Actual: {:X}",
133 expected_new_flags, new_flags
134 ),
135Err(status) => panic!("new_connection_validate_flags failed. Status: {}", status),
136 }
137 }
138139#[track_caller]
140fn ncvf_err(flags: fio::OpenFlags, expected_status: Status) {
141let res = new_connection_validate_flags(flags);
142match res {
143Ok(new_flags) => panic!(
144"new_connection_validate_flags should have failed. \
145 Got new flags: {:X}",
146 new_flags
147 ),
148Err(status) => assert_eq!(expected_status, status),
149 }
150 }
151152#[test]
153fn new_connection_validate_flags_posix() {
154for open_flags in build_flag_combinations(
155 fio::OpenFlags::empty(),
156 fio::OpenFlags::RIGHT_READABLE
157 | fio::OpenFlags::POSIX_EXECUTABLE
158 | fio::OpenFlags::POSIX_WRITABLE,
159 ) {
160let mut expected_rights = open_flags & fio::OpenFlags::RIGHT_READABLE;
161if open_flags.intersects(fio::OpenFlags::POSIX_WRITABLE) {
162 expected_rights |= fio::OpenFlags::RIGHT_WRITABLE
163 }
164if open_flags.intersects(fio::OpenFlags::POSIX_EXECUTABLE) {
165 expected_rights |= fio::OpenFlags::RIGHT_EXECUTABLE
166 }
167 ncvf_ok(open_flags, expected_rights);
168 }
169 }
170171#[test]
172fn new_connection_validate_flags_append() {
173 ncvf_err(fio::OpenFlags::RIGHT_WRITABLE | fio::OpenFlags::APPEND, Status::INVALID_ARGS);
174 }
175176#[test]
177fn new_connection_validate_flags_truncate() {
178 ncvf_err(fio::OpenFlags::RIGHT_WRITABLE | fio::OpenFlags::TRUNCATE, Status::INVALID_ARGS);
179 }
180181#[test]
182fn check_child_connection_flags_create_flags() {
183assert_eq!(
184 check_child_connection_flags(fio::OpenFlags::RIGHT_WRITABLE, fio::OpenFlags::CREATE,),
185Ok(fio::OpenFlags::CREATE)
186 );
187assert_eq!(
188 check_child_connection_flags(
189 fio::OpenFlags::RIGHT_WRITABLE,
190 fio::OpenFlags::CREATE | fio::OpenFlags::CREATE_IF_ABSENT,
191 ),
192Ok(fio::OpenFlags::CREATE | fio::OpenFlags::CREATE_IF_ABSENT)
193 );
194195assert_eq!(
196 check_child_connection_flags(fio::OpenFlags::empty(), fio::OpenFlags::CREATE),
197Err(Status::ACCESS_DENIED),
198 );
199assert_eq!(
200 check_child_connection_flags(
201 fio::OpenFlags::empty(),
202 fio::OpenFlags::CREATE | fio::OpenFlags::CREATE_IF_ABSENT,
203 ),
204Err(Status::ACCESS_DENIED),
205 );
206207// Need to specify OPEN_FLAG_CREATE if passing OPEN_FLAG_CREATE_IF_ABSENT.
208assert_eq!(
209 check_child_connection_flags(
210 fio::OpenFlags::RIGHT_WRITABLE,
211 fio::OpenFlags::CREATE_IF_ABSENT,
212 ),
213Err(Status::INVALID_ARGS),
214 );
215 }
216217#[test]
218fn check_child_connection_flags_invalid() {
219// Cannot specify both OPEN_FLAG_DIRECTORY and OPEN_FLAG_NOT_DIRECTORY.
220assert_eq!(
221 check_child_connection_flags(
222 fio::OpenFlags::empty(),
223 fio::OpenFlags::DIRECTORY | fio::OpenFlags::NOT_DIRECTORY,
224 ),
225Err(Status::INVALID_ARGS),
226 );
227228// Cannot specify CLONE_FLAG_SAME_RIGHTS when opening a resource (only permitted via clone).
229assert_eq!(
230 check_child_connection_flags(
231 fio::OpenFlags::empty(),
232 fio::OpenFlags::CLONE_SAME_RIGHTS,
233 ),
234Err(Status::INVALID_ARGS),
235 );
236 }
237}