1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
// Copyright 2018-2026 the Deno authors. MIT license.
use std::collections::HashMap;
use std::rc::Rc;
use crate::fs::File;
/// How an fd's lifetime is managed.
pub enum FdOwnership {
/// FdTable owns the File; dropping the entry closes the fd.
/// Used by fs.openSync, stdio fds 0/1/2, etc.
TableOwned(Rc<dyn File>),
/// An inherited extra stdio fd (fd >= 3 installed by the Node
/// `child_process` spawn path) that can be used by node:fs, but may still be
/// claimed later by libuv APIs such as net.Socket({ fd }).
///
/// The `File` here is a `dup()` of the inherited descriptor, not the original
/// numeric fd. Dropping this entry closes only the dup, so the original fd
/// stays open and remains claimable by libuv (this is what lets node:fs and a
/// later `net.Socket({ fd })` both work). The trade-off is that the original
/// fd is retained for the process lifetime unless libuv reclaims it, which
/// differs from Node, where node:fs `autoClose` closes the real fd.
InheritedExtraStdio(Rc<dyn File>),
/// A uv handle (e.g. uv_pipe_t) owns the fd; FdTable just tracks
/// that it exists for duplicate detection. The entry is removed
/// when uv_close fires, but no file is dropped.
UvOwned,
}
/// Central table tracking all known file descriptors.
///
/// Both Deno's resource table and Node's fd-based ops use this table
/// to look up files and detect duplicate registrations.
pub struct FdTable {
entries: HashMap<i32, FdOwnership>,
}
impl FdTable {
pub fn new() -> Self {
Self {
entries: HashMap::new(),
}
}
/// Register a TableOwned fd. Returns false if already registered.
pub fn register(&mut self, fd: i32, file: Rc<dyn File>) -> bool {
if self.entries.contains_key(&fd) {
return false;
}
self.entries.insert(fd, FdOwnership::TableOwned(file));
true
}
/// Register an inherited extra stdio fd. Returns false if already registered.
pub fn register_inherited_extra_stdio(
&mut self,
fd: i32,
file: Rc<dyn File>,
) -> bool {
if self.entries.contains_key(&fd) {
return false;
}
self
.entries
.insert(fd, FdOwnership::InheritedExtraStdio(file));
true
}
/// Register a UvOwned fd (tracked but not owned). Returns false if
/// already registered.
pub fn register_uv_owned(&mut self, fd: i32) -> bool {
if self.entries.contains_key(&fd) {
return false;
}
self.entries.insert(fd, FdOwnership::UvOwned);
true
}
/// Get the File for a TableOwned fd. Returns None for UvOwned or missing.
pub fn get(&self, fd: i32) -> Option<&Rc<dyn File>> {
match self.entries.get(&fd) {
Some(FdOwnership::TableOwned(file)) => Some(file),
Some(FdOwnership::InheritedExtraStdio(file)) => Some(file),
_ => None,
}
}
/// Remove an fd entry. For TableOwned, returns the File (caller drops
/// to close). For UvOwned, returns None (uv handle closes the fd).
pub fn remove(&mut self, fd: i32) -> Option<Rc<dyn File>> {
match self.entries.remove(&fd) {
Some(FdOwnership::TableOwned(file)) => Some(file),
Some(FdOwnership::InheritedExtraStdio(file)) => Some(file),
Some(FdOwnership::UvOwned) => None,
None => None,
}
}
/// Check if an fd is registered (either ownership type).
pub fn contains(&self, fd: i32) -> bool {
self.entries.contains_key(&fd)
}
/// Check if an fd is an inherited extra stdio descriptor.
pub fn is_inherited_extra_stdio(&self, fd: i32) -> bool {
matches!(
self.entries.get(&fd),
Some(FdOwnership::InheritedExtraStdio(_))
)
}
/// Check whether a libuv stream wrap (`PipeWrap::open`, `TCPWrap::open`)
/// may adopt `fd`. Stdio fds (0-2) are pre-registered and may be re-opened;
/// inherited extra stdio fds may be adopted (e.g. via `net.Socket({ fd })`)
/// and are consumed by `finish_uv_adopt` only once the uv open actually
/// claims the fd, so a failed open leaves the fd usable by node:fs. Any
/// other tracked fd is a duplicate.
///
/// Returns `Some(was_inherited)` if adoption may proceed (pass the flag to
/// `finish_uv_adopt` on success), or `None` if the fd is already tracked
/// and the caller should reject with `EEXIST`.
pub fn begin_uv_adopt(&self, fd: i32) -> Option<bool> {
if self.is_inherited_extra_stdio(fd) {
Some(true)
} else if self.contains(fd) && !(0..=2).contains(&fd) {
None
} else {
Some(false)
}
}
/// Record a successful uv adoption started with `begin_uv_adopt`. Drops the
/// inherited entry if there was one (libuv now owns the original fd; only
/// the node:fs dup is released, and its close is deferred until any
/// `Rc<dyn File>` clone held by an in-flight node:fs stream drops), then
/// tracks the fd as UvOwned so it can't be re-adopted by another wrap.
pub fn finish_uv_adopt(&mut self, fd: i32, was_inherited: bool) {
if was_inherited {
self.remove(fd);
}
self.register_uv_owned(fd);
}
}
impl Default for FdTable {
fn default() -> Self {
Self::new()
}
}