vfs/directory/
common.rs

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.
4
5//! Common utilities used by several directory implementations.
6
7use crate::common::stricter_or_same_rights;
8use crate::directory::entry::EntryInfo;
9
10use 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;
16
17/// 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,
23    mut flags: fio::OpenFlags,
24) -> Result<fio::OpenFlags, Status> {
25    if flags & (fio::OpenFlags::NOT_DIRECTORY | fio::OpenFlags::DIRECTORY)
26        == fio::OpenFlags::NOT_DIRECTORY | fio::OpenFlags::DIRECTORY
27    {
28        return Err(Status::INVALID_ARGS);
29    }
30
31    // Can only specify OPEN_FLAG_CREATE_IF_ABSENT if OPEN_FLAG_CREATE is also specified.
32    if flags.intersects(fio::OpenFlags::CREATE_IF_ABSENT)
33        && !flags.intersects(fio::OpenFlags::CREATE)
34    {
35        return Err(Status::INVALID_ARGS);
36    }
37
38    // Can only use CLONE_FLAG_SAME_RIGHTS when calling Clone.
39    if flags.intersects(fio::OpenFlags::CLONE_SAME_RIGHTS) {
40        return Err(Status::INVALID_ARGS);
41    }
42
43    // Remove POSIX flags when the respective rights are not available ("soft fail").
44    if !parent_flags.intersects(fio::OpenFlags::RIGHT_EXECUTABLE) {
45        flags &= !fio::OpenFlags::POSIX_EXECUTABLE;
46    }
47    if !parent_flags.intersects(fio::OpenFlags::RIGHT_WRITABLE) {
48        flags &= !fio::OpenFlags::POSIX_WRITABLE;
49    }
50
51    // Can only use CREATE flags if the parent connection is writable.
52    if flags.intersects(fio::OpenFlags::CREATE)
53        && !parent_flags.intersects(fio::OpenFlags::RIGHT_WRITABLE)
54    {
55        return Err(Status::ACCESS_DENIED);
56    }
57
58    if stricter_or_same_rights(parent_flags, flags) {
59        Ok(flags)
60    } else {
61        Err(Status::ACCESS_DENIED)
62    }
63}
64
65/// 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 {
75    let header_size = size_of::<u64>() + size_of::<u8>() + size_of::<u8>();
76
77    assert_eq_size!(u64, usize);
78
79    if buf.len() + header_size + name.len() > max_bytes as usize {
80        return false;
81    }
82
83    assert!(
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    );
92
93    assert!(
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    );
98
99    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");
105
106    true
107}
108
109#[cfg(test)]
110mod tests {
111    use super::check_child_connection_flags;
112    use crate::test_utils::build_flag_combinations;
113    use crate::ProtocolsExt;
114
115    use fidl_fuchsia_io as fio;
116    use zx_status::Status;
117
118    fn 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    }
123
124    #[track_caller]
125    fn ncvf_ok(flags: fio::OpenFlags, expected_new_flags: fio::OpenFlags) {
126        let res = new_connection_validate_flags(flags);
127        match res {
128            Ok(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            ),
135            Err(status) => panic!("new_connection_validate_flags failed.  Status: {}", status),
136        }
137    }
138
139    #[track_caller]
140    fn ncvf_err(flags: fio::OpenFlags, expected_status: Status) {
141        let res = new_connection_validate_flags(flags);
142        match res {
143            Ok(new_flags) => panic!(
144                "new_connection_validate_flags should have failed.  \
145                    Got new flags: {:X}",
146                new_flags
147            ),
148            Err(status) => assert_eq!(expected_status, status),
149        }
150    }
151
152    #[test]
153    fn new_connection_validate_flags_posix() {
154        for 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        ) {
160            let mut expected_rights = open_flags & fio::OpenFlags::RIGHT_READABLE;
161            if open_flags.intersects(fio::OpenFlags::POSIX_WRITABLE) {
162                expected_rights |= fio::OpenFlags::RIGHT_WRITABLE
163            }
164            if open_flags.intersects(fio::OpenFlags::POSIX_EXECUTABLE) {
165                expected_rights |= fio::OpenFlags::RIGHT_EXECUTABLE
166            }
167            ncvf_ok(open_flags, expected_rights);
168        }
169    }
170
171    #[test]
172    fn new_connection_validate_flags_append() {
173        ncvf_err(fio::OpenFlags::RIGHT_WRITABLE | fio::OpenFlags::APPEND, Status::INVALID_ARGS);
174    }
175
176    #[test]
177    fn new_connection_validate_flags_truncate() {
178        ncvf_err(fio::OpenFlags::RIGHT_WRITABLE | fio::OpenFlags::TRUNCATE, Status::INVALID_ARGS);
179    }
180
181    #[test]
182    fn check_child_connection_flags_create_flags() {
183        assert_eq!(
184            check_child_connection_flags(fio::OpenFlags::RIGHT_WRITABLE, fio::OpenFlags::CREATE,),
185            Ok(fio::OpenFlags::CREATE)
186        );
187        assert_eq!(
188            check_child_connection_flags(
189                fio::OpenFlags::RIGHT_WRITABLE,
190                fio::OpenFlags::CREATE | fio::OpenFlags::CREATE_IF_ABSENT,
191            ),
192            Ok(fio::OpenFlags::CREATE | fio::OpenFlags::CREATE_IF_ABSENT)
193        );
194
195        assert_eq!(
196            check_child_connection_flags(fio::OpenFlags::empty(), fio::OpenFlags::CREATE),
197            Err(Status::ACCESS_DENIED),
198        );
199        assert_eq!(
200            check_child_connection_flags(
201                fio::OpenFlags::empty(),
202                fio::OpenFlags::CREATE | fio::OpenFlags::CREATE_IF_ABSENT,
203            ),
204            Err(Status::ACCESS_DENIED),
205        );
206
207        // Need to specify OPEN_FLAG_CREATE if passing OPEN_FLAG_CREATE_IF_ABSENT.
208        assert_eq!(
209            check_child_connection_flags(
210                fio::OpenFlags::RIGHT_WRITABLE,
211                fio::OpenFlags::CREATE_IF_ABSENT,
212            ),
213            Err(Status::INVALID_ARGS),
214        );
215    }
216
217    #[test]
218    fn check_child_connection_flags_invalid() {
219        // Cannot specify both OPEN_FLAG_DIRECTORY and OPEN_FLAG_NOT_DIRECTORY.
220        assert_eq!(
221            check_child_connection_flags(
222                fio::OpenFlags::empty(),
223                fio::OpenFlags::DIRECTORY | fio::OpenFlags::NOT_DIRECTORY,
224            ),
225            Err(Status::INVALID_ARGS),
226        );
227
228        // Cannot specify CLONE_FLAG_SAME_RIGHTS when opening a resource (only permitted via clone).
229        assert_eq!(
230            check_child_connection_flags(
231                fio::OpenFlags::empty(),
232                fio::OpenFlags::CLONE_SAME_RIGHTS,
233            ),
234            Err(Status::INVALID_ARGS),
235        );
236    }
237}