Skip to main content

detcore_model/
fd.rs

1/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 * All rights reserved.
4 *
5 * This source code is licensed under the BSD-style license found in the
6 * LICENSE file in the root directory of this source tree.
7 */
8
9use serde::Deserialize;
10use serde::Serialize;
11
12use crate::pid::DetTid;
13
14/// For now we use the definiton of `RawFd` from `std::os`.
15// (Workaround: reexporting this type directly triggers a rust-anlazer glitch.)
16pub type RawFd = std::os::unix::io::RawFd;
17
18/// Nondeterministic "physical" inode
19pub type RawInode = u64;
20
21/// Deterministic "virtual" inode.
22///
23/// Deliberately a newtype rather than an alias for [`RawInode`]. As an alias
24/// the two were the same type to the compiler, so a host inode could be used
25/// wherever a deterministic one was required and nothing diagnosed it; that is
26/// how raw host inodes reached guest-visible `ResourceID`s.
27///
28/// There is intentionally no `From<RawInode>` impl. The only supported way to
29/// turn a host inode into a `DetInode` is the determinization boundary in
30/// `tool_global` (`determinize_inode` -> `add_inode`), which mints values from
31/// a monotonic counter. [`DetInode::mint`] exists for that boundary and for the
32/// handful of compile-time constants; every call site is a deliberate,
33/// auditable assertion that the value is already deterministic.
34#[derive(
35    Debug,
36    Clone,
37    Copy,
38    PartialEq,
39    Eq,
40    Hash,
41    PartialOrd,
42    Ord,
43    Serialize,
44    Deserialize
45)]
46pub struct DetInode(RawInode);
47
48impl DetInode {
49    /// Assert that `value` is a deterministic inode.
50    ///
51    /// Reserved for the determinization boundary and for compile-time
52    /// constants. Passing a host inode here reintroduces the leak this newtype
53    /// exists to prevent.
54    pub const fn mint(value: RawInode) -> Self {
55        Self(value)
56    }
57
58    /// The underlying integer, for writing into guest-visible stat buffers.
59    pub const fn as_raw(self) -> RawInode {
60        self.0
61    }
62}
63
64impl std::fmt::Display for DetInode {
65    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
66        write!(f, "{}", self.0)
67    }
68}
69
70/// Identity of a Linux descriptor table (`files_struct`).
71#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
72pub struct FilesId {
73    creator: DetTid,
74    generation: u64,
75}
76
77impl FilesId {
78    /// Create the first descriptor table owned by a task.
79    pub const fn initial(creator: DetTid) -> Self {
80        Self {
81            creator,
82            generation: 0,
83        }
84    }
85
86    /// Create a copied descriptor table for a newly created task.
87    pub const fn forked(creator: DetTid) -> Self {
88        Self::initial(creator)
89    }
90
91    /// Create the replacement table installed by exec.
92    pub fn for_exec(self, creator: DetTid) -> Self {
93        let generation = if self.creator == creator {
94            self.generation + 1
95        } else {
96            0
97        };
98        Self {
99            creator,
100            generation,
101        }
102    }
103}
104
105/// Identity of one numeric descriptor slot within a descriptor table.
106#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
107pub struct FdSlot {
108    /// Descriptor table containing the slot.
109    pub files: FilesId,
110    /// Numeric descriptor within the table.
111    pub fd: RawFd,
112}
113
114/// Identity of a Linux open file description (`struct file`).
115#[derive(
116    Debug,
117    Clone,
118    Copy,
119    PartialEq,
120    Eq,
121    PartialOrd,
122    Ord,
123    Hash,
124    Serialize,
125    Deserialize
126)]
127pub struct OpenFileId {
128    creator: DetTid,
129    sequence: u64,
130}
131
132const SOCKET_SEQUENCE_DOMAIN: u64 = 1 << 63;
133
134impl OpenFileId {
135    /// Create an identity from the task that observed the open and its local sequence.
136    pub const fn new(creator: DetTid, sequence: u64) -> Self {
137        assert!(sequence < SOCKET_SEQUENCE_DOMAIN);
138        Self { creator, sequence }
139    }
140
141    /// Create a socket identity from a backend-independent socket-open sequence.
142    pub const fn new_socket(creator: DetTid, sequence: u64) -> Self {
143        assert!(sequence < SOCKET_SEQUENCE_DOMAIN);
144        Self {
145            creator,
146            sequence: SOCKET_SEQUENCE_DOMAIN | sequence,
147        }
148    }
149
150    /// Whether this identity came from the socket-specific allocation domain.
151    pub const fn is_socket(self) -> bool {
152        self.sequence & SOCKET_SEQUENCE_DOMAIN != 0
153    }
154
155    // TODO-HUMAN-REVIEW(PR-886): Review stable socket-cookie identity encoding.
156    /// Encode the per-task socket-open sequence as a deterministic socket cookie.
157    ///
158    /// Linux promises that live socket cookies are unique and that descriptor aliases
159    /// for one open file description share a cookie. Detcore's virtual task IDs and a
160    /// socket-specific sequence provide those same properties for realistic descriptor
161    /// counts while avoiding the kernel's host-global cookie allocator. The sequence is
162    /// independent of regular-file opens because backend loaders do not expose the same
163    /// dynamic-linker file operations to Detcore.
164    pub fn deterministic_socket_cookie(self) -> u64 {
165        let creator = self.creator.as_raw() as u32 as u64;
166        (creator << 32) | (self.sequence & u32::MAX as u64)
167    }
168}
169
170#[cfg(test)]
171mod tests {
172    use super::*;
173
174    #[test]
175    fn deterministic_socket_cookies_track_open_file_identity() {
176        let first = OpenFileId::new_socket(DetTid::from_raw(3), 7);
177        let alias = first;
178        let next = OpenFileId::new_socket(DetTid::from_raw(3), 8);
179        let other_task = OpenFileId::new_socket(DetTid::from_raw(4), 7);
180
181        assert_ne!(first.deterministic_socket_cookie(), 0);
182        assert!(first.is_socket());
183        assert!(!OpenFileId::new(DetTid::from_raw(3), 7).is_socket());
184        assert_eq!(
185            first.deterministic_socket_cookie(),
186            alias.deterministic_socket_cookie()
187        );
188        assert_ne!(
189            first.deterministic_socket_cookie(),
190            next.deterministic_socket_cookie()
191        );
192        assert_ne!(
193            first.deterministic_socket_cookie(),
194            other_task.deterministic_socket_cookie()
195        );
196        assert_ne!(first, OpenFileId::new(DetTid::from_raw(3), 7));
197        assert_eq!(first.deterministic_socket_cookie(), (3_u64 << 32) | 7);
198    }
199}