Skip to content

Commit 2ec3183

Browse files
authored
Merge pull request #50 from lnicola/socket-activation
Improve handling of systemd activation
2 parents 6575d56 + d07bf5e commit 2ec3183

File tree

5 files changed

+63
-80
lines changed

5 files changed

+63
-80
lines changed

Cargo.lock

+3-3
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+2-2
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ uuid = "0.7.4"
1717
threadpool = "1.7.1"
1818
std-semaphore = "0.1.0"
1919
signal-hook = "0.1.10"
20-
sd-notify = { version = "0.1.0", optional = true }
20+
sd-notify = { version = "0.1.1" }
2121
toml = "0.4.2"
2222
serde = { version = "1.0", features = ["derive"] }
2323
env_logger = "0.7.1"
@@ -42,4 +42,4 @@ mbed = []
4242

4343
# Feature to compile the PARSEC binary to be executed as a systemd daemon.
4444
# This feature is only available on Linux.
45-
systemd-daemon = ["sd-notify"]
45+
systemd-daemon = []

README.md

+5-5
Original file line numberDiff line numberDiff line change
@@ -128,19 +128,19 @@ You will need to understand the [**wire protocol specification**](docs/wire_prot
128128
The software is provided under Apache-2.0. Contributions to this project are accepted under the same license.
129129

130130
This project uses the following third party crates:
131-
* serde (Apache-2.0)
131+
* serde (MIT and Apache-2.0)
132132
* bindgen (BSD-3-Clause)
133133
* cargo\_toml (Apache-2.0)
134134
* toml (MIT and Apache-2.0)
135135
* rand (MIT and Apache-2.0)
136136
* base64 (MIT and Apache-2.0)
137-
* uuid (Apache-2.0)
138-
* threadpool (Apache-2.0)
137+
* uuid (MIT and Apache-2.0)
138+
* threadpool (MIT and Apache-2.0)
139139
* std-semaphore (MIT and Apache-2.0)
140140
* num\_cpus (MIT and Apache-2.0)
141141
* signal-hook (MIT and Apache-2.0)
142-
* sd-notify (Apache-2.0)
143-
* log (Apache-2.0)
142+
* sd-notify (MIT and Apache-2.0)
143+
* log (MIT and Apache-2.0)
144144
* env\_logger (MIT and Apache-2.0)
145145

146146
This project uses the following third party libraries:

src/bin/main.rs

-1
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,6 @@ fn main() -> Result<(), Error> {
5757

5858
info!("PARSEC is ready.");
5959

60-
#[cfg(feature = "systemd-daemon")]
6160
// Notify systemd that the daemon is ready, the start command will block until this point.
6261
let _ = sd_notify::notify(true, &[sd_notify::NotifyState::Ready]);
6362

src/front/domain_socket.rs

+53-69
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ static SOCKET_PATH: &str = "/tmp/security-daemon-socket";
3131
///
3232
/// Only works on Unix systems.
3333
pub struct DomainSocketListener {
34-
listener: Option<UnixListener>,
34+
listener: UnixListener,
3535
timeout: Duration,
3636
}
3737

@@ -42,36 +42,39 @@ impl DomainSocketListener {
4242
/// - if a file/socket exists at the path specified for the socket and `remove_file`
4343
/// fails
4444
/// - if binding to the socket path fails
45-
fn init(&mut self) {
46-
if cfg!(feature = "systemd-daemon") {
47-
// The PARSEC service is socket activated (see parsec.socket file).
48-
// systemd creates the PARSEC service giving it an initialised socket as the file
49-
// descriptor number 3 (see sd_listen_fds(3) man page).
50-
// If an instance of PARSEC compiled with the "systemd-daemon" feature is run directly
51-
// instead of by systemd, this call will still work but the next accept call on the
52-
// UnixListener will generate a Linux error 9 (Bad file number), as checked below.
53-
unsafe {
54-
self.listener = Some(UnixListener::from_raw_fd(3));
55-
}
56-
} else {
57-
let socket = Path::new(SOCKET_PATH);
45+
pub fn new(timeout: Duration) -> Self {
46+
// If this PARSEC instance was socket activated (see the `parsec.socket`
47+
// file), the listener will be opened by systemd and passed to the
48+
// process.
49+
// If PARSEC was service activated or not started under systemd, this
50+
// will return `0`.
51+
let listener =
52+
match sd_notify::listen_fds().expect("Could not retrieve listener from systemd") {
53+
0 => {
54+
let socket = Path::new(SOCKET_PATH);
5855

59-
if socket.exists() {
60-
fs::remove_file(&socket).unwrap();
61-
}
56+
if socket.exists() {
57+
fs::remove_file(&socket).unwrap();
58+
}
6259

63-
let listener_val = match UnixListener::bind(SOCKET_PATH) {
64-
Ok(listener) => listener,
65-
Err(err) => panic!(err),
66-
};
60+
let listener =
61+
UnixListener::bind(SOCKET_PATH).expect("Could not bind listen socket");
62+
listener
63+
.set_nonblocking(true)
64+
.expect("Could not set the socket as non-blocking");
6765

68-
// Set the socket as non-blocking.
69-
listener_val
70-
.set_nonblocking(true)
71-
.expect("Could not set the socket as non-blocking");
66+
listener
67+
}
68+
1 => {
69+
// No need to set the socket as non-blocking, parsec.service
70+
// already requests that.
71+
let nfd = sd_notify::SD_LISTEN_FDS_START;
72+
unsafe { UnixListener::from_raw_fd(nfd) }
73+
}
74+
_ => panic!("Received too many file descriptors"),
75+
};
7276

73-
self.listener = Some(listener_val);
74-
}
77+
Self { listener, timeout }
7578
}
7679
}
7780

@@ -81,44 +84,30 @@ impl Listen for DomainSocketListener {
8184
}
8285

8386
fn accept(&self) -> Option<Box<dyn ReadWrite + Send>> {
84-
if let Some(listener) = &self.listener {
85-
let stream_result = listener.accept();
86-
match stream_result {
87-
Ok((stream, _)) => {
88-
if let Err(err) = stream.set_read_timeout(Some(self.timeout)) {
89-
error!("Failed to set read timeout ({})", err);
90-
None
91-
} else if let Err(err) = stream.set_write_timeout(Some(self.timeout)) {
92-
error!("Failed to set write timeout ({})", err);
93-
None
94-
} else if let Err(err) = stream.set_nonblocking(false) {
95-
error!("Failed to set stream as blocking ({})", err);
96-
None
97-
} else {
98-
Some(Box::from(stream))
99-
}
100-
}
101-
Err(err) => {
102-
if cfg!(feature = "systemd-daemon") {
103-
// When run as a systemd daemon, a file descriptor mapping to the Domain Socket
104-
// should have been passed to this process.
105-
if let Some(os_error) = err.raw_os_error() {
106-
// On Linux, 9 is EBADF (Bad file number)
107-
if os_error == 9 {
108-
panic!("The Unix Domain Socket file descriptor (number 3) should have been given to this process.");
109-
}
110-
}
111-
}
112-
// Check if the error is because no connections are currently present.
113-
if err.kind() != ErrorKind::WouldBlock {
114-
// Only log the real errors.
115-
error!("Failed to connect with a UnixStream ({})", err);
116-
}
87+
let stream_result = self.listener.accept();
88+
match stream_result {
89+
Ok((stream, _)) => {
90+
if let Err(err) = stream.set_read_timeout(Some(self.timeout)) {
91+
error!("Failed to set read timeout ({})", err);
92+
None
93+
} else if let Err(err) = stream.set_write_timeout(Some(self.timeout)) {
94+
error!("Failed to set write timeout ({})", err);
11795
None
96+
} else if let Err(err) = stream.set_nonblocking(false) {
97+
error!("Failed to set stream as blocking ({})", err);
98+
None
99+
} else {
100+
Some(Box::from(stream))
101+
}
102+
}
103+
Err(err) => {
104+
// Check if the error is because no connections are currently present.
105+
if err.kind() != ErrorKind::WouldBlock {
106+
// Only log the real errors.
107+
error!("Failed to connect with a UnixStream ({})", err);
118108
}
109+
None
119110
}
120-
} else {
121-
panic!("The Unix Domain Socket has not been initialised.");
122111
}
123112
}
124113
}
@@ -139,12 +128,7 @@ impl DomainSocketListenerBuilder {
139128
}
140129

141130
pub fn build(self) -> DomainSocketListener {
142-
let mut listener = DomainSocketListener {
143-
timeout: self.timeout.expect("FrontEndHandler missing"),
144-
listener: None,
145-
};
146-
listener.init();
147-
148-
listener
131+
let timeout = self.timeout.expect("The listener timeout was not set");
132+
DomainSocketListener::new(timeout)
149133
}
150134
}

0 commit comments

Comments
 (0)