Skip to content

Commit 73f0cd6

Browse files
committed
feat: ListenerOptions overhaul
Includes a stub for `try_overwrite`.
1 parent f25215f commit 73f0cd6

File tree

9 files changed

+366
-220
lines changed

9 files changed

+366
-220
lines changed

src/local_socket/listener/options.rs

Lines changed: 115 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -7,28 +7,34 @@ use {
77
local_socket::{traits, Listener, ListenerNonblockingMode, Name},
88
Sealed, TryClone,
99
},
10-
std::io,
10+
std::{fmt::Debug, io},
1111
};
1212

1313
/// A builder for [local socket listeners](traits::Listener), including [`Listener`].
14-
#[derive(Debug)]
1514
pub struct ListenerOptions<'n> {
1615
pub(crate) name: Name<'n>,
17-
pub(crate) nonblocking: ListenerNonblockingMode,
18-
pub(crate) reclaim_name: bool,
16+
flags: u8,
1917
#[cfg(unix)]
20-
pub(crate) mode: Option<libc::mode_t>,
18+
mode: libc::mode_t,
2119
#[cfg(windows)]
2220
pub(crate) security_descriptor: Option<SecurityDescriptor>,
2321
}
2422
impl Sealed for ListenerOptions<'_> {}
2523

24+
const SHFT_NONBLOCKING_ACCEPT: u8 = 0;
25+
const SHFT_NONBLOCKING_STREAM: u8 = 1;
26+
const SHFT_RECLAIM_NAME: u8 = 2;
27+
const SHFT_TRY_OVERWRITE: u8 = 3; // TODO
28+
const SHFT_HAS_MODE: u8 = 4;
29+
30+
const ALL_BITS: u8 = (1 << 5) - 1;
31+
const NONBLOCKING_BITS: u8 = (1 << SHFT_NONBLOCKING_ACCEPT) | (1 << SHFT_NONBLOCKING_STREAM);
32+
2633
impl TryClone for ListenerOptions<'_> {
2734
fn try_clone(&self) -> io::Result<Self> {
2835
Ok(Self {
2936
name: self.name.clone(),
30-
nonblocking: self.nonblocking,
31-
reclaim_name: self.reclaim_name,
37+
flags: self.flags,
3238
#[cfg(unix)]
3339
mode: self.mode,
3440
#[cfg(windows)]
@@ -48,10 +54,9 @@ impl ListenerOptions<'_> {
4854
pub fn new() -> Self {
4955
Self {
5056
name: Name::invalid(),
51-
nonblocking: ListenerNonblockingMode::Neither,
52-
reclaim_name: true,
57+
flags: 1 << SHFT_RECLAIM_NAME,
5358
#[cfg(unix)]
54-
mode: None,
59+
mode: 0,
5560
#[cfg(windows)]
5661
security_descriptor: None,
5762
}
@@ -63,14 +68,82 @@ impl<'n> ListenerOptions<'n> {
6368
builder_setters! {
6469
/// Sets the name the server will listen on.
6570
name: Name<'n>,
66-
/// Selects the nonblocking mode to be used by the listener.
67-
///
68-
/// The default value is `Neither`.
69-
nonblocking: ListenerNonblockingMode,
70-
/// Sets whether [name reclamation](Listener#name-reclamation) is to happen or not.
71-
///
72-
/// This is enabled by default.
73-
reclaim_name: bool,
71+
}
72+
/// Selects the nonblocking mode to be used by the listener.
73+
///
74+
/// The default value is `Neither`.
75+
#[must_use = builder_must_use!()]
76+
#[inline(always)]
77+
#[allow(clippy::as_conversions)]
78+
pub fn nonblocking(mut self, nonblocking: ListenerNonblockingMode) -> Self {
79+
self.flags = (self.flags & (ALL_BITS ^ NONBLOCKING_BITS)) | nonblocking as u8;
80+
self
81+
}
82+
/// Sets whether [name reclamation](Listener#name-reclamation) is to happen or not.
83+
///
84+
/// This is enabled by default.
85+
#[must_use = builder_must_use!()]
86+
#[inline(always)]
87+
#[allow(clippy::as_conversions)]
88+
pub fn reclaim_name(mut self, reclaim_name: bool) -> Self {
89+
self.flags = (self.flags & (ALL_BITS ^ (1 << SHFT_RECLAIM_NAME)))
90+
| ((reclaim_name as u8) << SHFT_RECLAIM_NAME);
91+
self
92+
}
93+
/// Sets whether an attempt to handle [`AddrInUse`](std::io::ErrorKind::AddrInUse) errors by
94+
/// overwriting an existing listener (in the same manner as in
95+
/// [name reclamation](Listener#name-reclamation)) is to be made or not.
96+
///
97+
/// If this is enabled, name reclamation will be performed on behalf of a previous listener,
98+
/// even if it is still running and accepting connections, thereby displacing it from the
99+
/// socket name so that the newly created listener could take its place.
100+
///
101+
/// This is disabled by default.
102+
///
103+
/// ## Platform-specific behavior
104+
/// ### Unix
105+
/// On Unix, this deletes the socket file if an `AddrInUse` error is encountered. The previous
106+
/// listener, if it is still listening on its socket, is not (and in fact cannot be) notified
107+
/// of this in any way.
108+
///
109+
/// The deletion suffers from an unavoidable TOCTOU race between the `AddrInUse` error being
110+
/// observed and the socket file being deleted, since another process may replace the socket
111+
/// file with a different file, causing Interprocess to delete that file instead. Note that
112+
/// this generally has no inadvertent privilege escalation implications, as the privileges
113+
/// required for renaming a file are the same as the ones required for deleting it, but the
114+
/// behavior may still be surprising in this (admittedly rather artificial) edge case.
115+
///
116+
/// ### Windows
117+
/// Does nothing (meaning the error goes unhandled), as named pipes cannot be overwritten.
118+
#[must_use = builder_must_use!()]
119+
#[inline(always)]
120+
#[allow(clippy::as_conversions)]
121+
pub fn try_overwrite(mut self, try_overwrite: bool) -> Self {
122+
self.flags = (self.flags & (ALL_BITS ^ (1 << SHFT_TRY_OVERWRITE)))
123+
| ((try_overwrite as u8) << SHFT_TRY_OVERWRITE);
124+
self
125+
}
126+
#[cfg(unix)]
127+
#[inline(always)]
128+
pub(crate) fn set_mode(&mut self, mode: libc::mode_t) {
129+
self.flags |= 1 << SHFT_HAS_MODE;
130+
self.mode = mode;
131+
}
132+
}
133+
134+
/// Option getters.
135+
impl ListenerOptions<'_> {
136+
pub(crate) fn get_nonblocking_accept(&self) -> bool {
137+
self.flags & (1 << SHFT_NONBLOCKING_ACCEPT) != 0
138+
}
139+
pub(crate) fn get_nonblocking_stream(&self) -> bool {
140+
self.flags & (1 << SHFT_NONBLOCKING_STREAM) != 0
141+
}
142+
pub(crate) fn get_reclaim_name(&self) -> bool { self.flags & (1 << SHFT_RECLAIM_NAME) != 0 }
143+
pub(crate) fn get_try_overwrite(&self) -> bool { self.flags & (1 << SHFT_TRY_OVERWRITE) != 0 }
144+
#[cfg(unix)]
145+
pub(crate) fn get_mode(&self) -> Option<libc::mode_t> {
146+
(self.flags & (1 << SHFT_HAS_MODE) != 0).then_some(self.mode)
74147
}
75148
}
76149

@@ -108,3 +181,27 @@ impl Default for ListenerOptions<'_> {
108181
#[inline]
109182
fn default() -> Self { Self::new() }
110183
}
184+
185+
impl Debug for ListenerOptions<'_> {
186+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
187+
let mut dbs = f.debug_struct("ListenerOptions");
188+
let nonblocking = ListenerNonblockingMode::from_bool(
189+
self.get_nonblocking_accept(),
190+
self.get_nonblocking_stream(),
191+
);
192+
dbs.field("name", &self.name)
193+
.field("nonblocking", &nonblocking)
194+
.field("reclaim_name", &self.get_reclaim_name())
195+
.field("try_overwrite", &self.get_try_overwrite());
196+
#[cfg(unix)]
197+
{
198+
// FIXME not octal
199+
dbs.field("mode", &self.get_mode());
200+
}
201+
#[cfg(windows)]
202+
{
203+
dbs.field("security_descriptor", &self.security_descriptor);
204+
}
205+
dbs.finish()
206+
}
207+
}

src/misc.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,14 @@ pub(crate) unsafe fn assume_slice_init_mut<T>(s: &mut [MaybeUninit<T>]) -> &mut
232232

233233
#[inline(always)]
234234
pub(crate) fn contains_nuls(s: &[u8]) -> bool {
235-
unsafe { libc::strnlen(s.as_ptr().cast(), s.len()) != s.len() }
235+
#[cfg(unix)]
236+
{
237+
unsafe { libc::strnlen(s.as_ptr().cast(), s.len()) != s.len() }
238+
}
239+
#[cfg(not(unix))]
240+
{
241+
s.contains(&0)
242+
}
236243
}
237244
#[inline(always)]
238245
pub(crate) const unsafe fn assume_nonzero_slice(s: &[u8]) -> &[NonZeroU8] {

0 commit comments

Comments
 (0)