Skip to main content

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
7#[cfg(any(fuchsia_api_level_at_least = "PLATFORM", not(fuchsia_api_level_at_least = "32")))]
8use crate::common::stricter_or_same_rights;
9use crate::directory::entry::EntryInfo;
10
11use flex_fuchsia_io as fio;
12use static_assertions::assert_eq_size;
13use std::mem::size_of;
14#[cfg(any(fuchsia_api_level_at_least = "PLATFORM", not(fuchsia_api_level_at_least = "32")))]
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.
21#[cfg(any(fuchsia_api_level_at_least = "PLATFORM", not(fuchsia_api_level_at_least = "32")))]
22pub(crate) fn check_child_connection_flags(
23    parent_flags: fio::OpenFlags,
24    mut flags: fio::OpenFlags,
25) -> Result<fio::OpenFlags, Status> {
26    if flags & (fio::OpenFlags::NOT_DIRECTORY | fio::OpenFlags::DIRECTORY)
27        == fio::OpenFlags::NOT_DIRECTORY | fio::OpenFlags::DIRECTORY
28    {
29        return Err(Status::INVALID_ARGS);
30    }
31
32    // Can only specify OPEN_FLAG_CREATE_IF_ABSENT if OPEN_FLAG_CREATE is also specified.
33    if flags.intersects(fio::OpenFlags::CREATE_IF_ABSENT)
34        && !flags.intersects(fio::OpenFlags::CREATE)
35    {
36        return Err(Status::INVALID_ARGS);
37    }
38
39    // Can only use CLONE_FLAG_SAME_RIGHTS when calling Clone.
40    if flags.intersects(fio::OpenFlags::CLONE_SAME_RIGHTS) {
41        return Err(Status::INVALID_ARGS);
42    }
43
44    // Remove POSIX flags when the respective rights are not available ("soft fail").
45    if !parent_flags.intersects(fio::OpenFlags::RIGHT_EXECUTABLE) {
46        flags &= !fio::OpenFlags::POSIX_EXECUTABLE;
47    }
48    if !parent_flags.intersects(fio::OpenFlags::RIGHT_WRITABLE) {
49        flags &= !fio::OpenFlags::POSIX_WRITABLE;
50    }
51
52    // Can only use CREATE flags if the parent connection is writable.
53    if flags.intersects(fio::OpenFlags::CREATE)
54        && !parent_flags.intersects(fio::OpenFlags::RIGHT_WRITABLE)
55    {
56        return Err(Status::ACCESS_DENIED);
57    }
58
59    if stricter_or_same_rights(parent_flags, flags) {
60        Ok(flags)
61    } else {
62        Err(Status::ACCESS_DENIED)
63    }
64}
65
66/// A helper to generate binary encodings for the ReadDirents response.  This function will append
67/// an entry description as specified by `entry` and `name` to the `buf`, and would return `true`.
68/// In case this would cause the buffer size to exceed `max_bytes`, the buffer is then left
69/// untouched and a `false` value is returned.
70pub(crate) fn encode_dirent(
71    buf: &mut Vec<u8>,
72    max_bytes: u64,
73    entry: &EntryInfo,
74    name: &str,
75) -> bool {
76    const HEADER_SIZE: usize = size_of::<u64>() + size_of::<u8>() + size_of::<u8>();
77
78    assert_eq_size!(u64, usize);
79
80    if buf.len() + HEADER_SIZE + name.len() > max_bytes as usize {
81        return false;
82    }
83
84    // TODO(https://fxbug.dev/293948129): `Sink` implementations should take a type that enforces
85    // this constraint. "." is valid here, so taking [`crate::Name`] directly isn't sufficient.
86    assert!(
87        name.len() <= fio::MAX_NAME_LENGTH as usize,
88        "Entry names are expected to be no longer than MAX_FILENAME ({}) bytes.\n\
89         Got entry: '{}'\n\
90         Length: {} bytes",
91        fio::MAX_NAME_LENGTH,
92        name,
93        name.len()
94    );
95
96    assert!(
97        fio::MAX_NAME_LENGTH <= u8::MAX as u64,
98        "Expecting to be able to store MAX_FILENAME ({}) in one byte.",
99        fio::MAX_NAME_LENGTH
100    );
101    buf.reserve(HEADER_SIZE + name.len());
102    buf.extend_from_slice(&entry.inode().to_le_bytes());
103    buf.push(name.len() as u8);
104    buf.push(entry.type_().into_primitive());
105    buf.extend_from_slice(name.as_bytes());
106
107    true
108}
109
110#[cfg(test)]
111mod tests {
112    use super::*;
113
114    #[cfg(any(fuchsia_api_level_at_least = "PLATFORM", not(fuchsia_api_level_at_least = "32")))]
115    #[fuchsia::test]
116    fn test_check_child_connection_flags() {
117        let rw_parent = fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::RIGHT_WRITABLE;
118        let ro_parent = fio::OpenFlags::RIGHT_READABLE;
119
120        // Conflicting DIRECTORY and NOT_DIRECTORY flags.
121        assert_eq!(
122            check_child_connection_flags(
123                rw_parent,
124                fio::OpenFlags::DIRECTORY | fio::OpenFlags::NOT_DIRECTORY
125            ),
126            Err(Status::INVALID_ARGS)
127        );
128
129        // CREATE_IF_ABSENT without CREATE.
130        assert_eq!(
131            check_child_connection_flags(rw_parent, fio::OpenFlags::CREATE_IF_ABSENT),
132            Err(Status::INVALID_ARGS)
133        );
134
135        // CLONE_SAME_RIGHTS is not allowed on Open.
136        assert_eq!(
137            check_child_connection_flags(rw_parent, fio::OpenFlags::CLONE_SAME_RIGHTS),
138            Err(Status::INVALID_ARGS)
139        );
140
141        // POSIX flags stripped when parent lacks corresponding rights.
142        assert_eq!(
143            check_child_connection_flags(
144                ro_parent,
145                fio::OpenFlags::RIGHT_READABLE
146                    | fio::OpenFlags::POSIX_WRITABLE
147                    | fio::OpenFlags::POSIX_EXECUTABLE
148            ),
149            Ok(fio::OpenFlags::RIGHT_READABLE)
150        );
151
152        // CREATE fails when parent is not writable.
153        assert_eq!(
154            check_child_connection_flags(ro_parent, fio::OpenFlags::CREATE),
155            Err(Status::ACCESS_DENIED)
156        );
157
158        // Child requesting more rights than parent fails with ACCESS_DENIED.
159        assert_eq!(
160            check_child_connection_flags(ro_parent, fio::OpenFlags::RIGHT_WRITABLE),
161            Err(Status::ACCESS_DENIED)
162        );
163
164        // Valid CREATE with writable parent succeeds.
165        assert_eq!(
166            check_child_connection_flags(
167                rw_parent,
168                fio::OpenFlags::CREATE
169                    | fio::OpenFlags::CREATE_IF_ABSENT
170                    | fio::OpenFlags::RIGHT_WRITABLE
171            ),
172            Ok(fio::OpenFlags::CREATE
173                | fio::OpenFlags::CREATE_IF_ABSENT
174                | fio::OpenFlags::RIGHT_WRITABLE)
175        );
176    }
177
178    #[fuchsia::test]
179    fn test_encode_dirent() {
180        let entry = EntryInfo::new(42, fio::DirentType::File);
181        let mut buf = Vec::new();
182
183        // Buffer too small returns false and leaves buf empty.
184        assert!(!encode_dirent(&mut buf, 10, &entry, "hello"));
185        assert!(buf.is_empty());
186
187        // Exact size (10 header bytes + 5 name bytes = 15) succeeds.
188        assert!(encode_dirent(&mut buf, 15, &entry, "hello"));
189        assert_eq!(buf.len(), 15);
190        assert_eq!(&buf[0..8], &42u64.to_le_bytes());
191        assert_eq!(buf[8], 5);
192        assert_eq!(buf[9], fio::DirentType::File.into_primitive());
193        assert_eq!(&buf[10..15], b"hello");
194    }
195}