libcontainer/
notify_socket.rs1use std::env;
2use std::io::prelude::*;
3use std::os::fd::FromRawFd;
4use std::os::unix::io::AsRawFd;
5use std::os::unix::net::{UnixListener, UnixStream};
6use std::path::{Path, PathBuf};
7
8use nix::unistd::{self, close};
9
10pub const NOTIFY_FILE: &str = "notify.sock";
11
12#[derive(Debug, thiserror::Error)]
13pub enum NotifyListenerError {
14 #[error("failed to chdir {path} while creating notify socket: {source}")]
15 Chdir { source: nix::Error, path: PathBuf },
16 #[error("invalid path: {0}")]
17 InvalidPath(PathBuf),
18 #[error("failed to bind notify socket: {name}")]
19 Bind {
20 source: std::io::Error,
21 name: String,
22 },
23 #[error("failed to connect to notify socket: {name}")]
24 Connect {
25 source: std::io::Error,
26 name: String,
27 },
28 #[error("failed to get cwd")]
29 GetCwd(#[source] std::io::Error),
30 #[error("failed to accept notify listener")]
31 Accept(#[source] std::io::Error),
32 #[error("failed to close notify listener")]
33 Close(#[source] nix::errno::Errno),
34 #[error("failed to read notify listener")]
35 Read(#[source] std::io::Error),
36 #[error("failed to send start container")]
37 SendStartContainer(#[source] std::io::Error),
38}
39
40type Result<T> = std::result::Result<T, NotifyListenerError>;
41
42pub struct NotifyListener {
43 socket: UnixListener,
44}
45
46impl NotifyListener {
47 pub fn new(socket_path: &Path) -> Result<Self> {
48 tracing::debug!(?socket_path, "create notify listener");
49 let workdir = socket_path
56 .parent()
57 .ok_or_else(|| NotifyListenerError::InvalidPath(socket_path.to_owned()))?;
58 let socket_name = socket_path
59 .file_name()
60 .ok_or_else(|| NotifyListenerError::InvalidPath(socket_path.to_owned()))?;
61 let cwd = env::current_dir().map_err(NotifyListenerError::GetCwd)?;
62 tracing::debug!(?cwd, "the cwd to create the notify socket");
63 unistd::chdir(workdir).map_err(|e| NotifyListenerError::Chdir {
64 source: e,
65 path: workdir.to_owned(),
66 })?;
67 let stream = UnixListener::bind(socket_name).map_err(|e| NotifyListenerError::Bind {
68 source: e,
69 name: socket_name.to_str().unwrap().to_owned(),
71 })?;
72 unistd::chdir(&cwd).map_err(|e| NotifyListenerError::Chdir {
73 source: e,
74 path: cwd,
75 })?;
76
77 Ok(Self { socket: stream })
78 }
79
80 pub fn wait_for_container_start(&self) -> Result<()> {
81 match self.socket.accept() {
82 Ok((mut socket, _)) => {
83 let mut response = String::new();
84 socket
85 .read_to_string(&mut response)
86 .map_err(NotifyListenerError::Read)?;
87 tracing::debug!("received: {}", response);
88 }
89 Err(e) => Err(NotifyListenerError::Accept(e))?,
90 }
91
92 Ok(())
93 }
94
95 pub fn close(&self) -> Result<()> {
96 close(self.socket.as_raw_fd()).map_err(NotifyListenerError::Close)?;
97 Ok(())
98 }
99}
100
101impl Clone for NotifyListener {
102 fn clone(&self) -> Self {
103 let fd = self.socket.as_raw_fd();
104 let socket = unsafe { UnixListener::from_raw_fd(fd) };
114 Self { socket }
115 }
116}
117
118pub struct NotifySocket {
119 path: PathBuf,
120}
121
122impl NotifySocket {
123 pub fn new<P: Into<PathBuf>>(socket_path: P) -> Self {
124 Self {
125 path: socket_path.into(),
126 }
127 }
128
129 pub fn notify_container_start(&mut self) -> Result<()> {
130 tracing::debug!("notify container start");
131 let cwd = env::current_dir().map_err(NotifyListenerError::GetCwd)?;
132 let workdir = self
133 .path
134 .parent()
135 .ok_or_else(|| NotifyListenerError::InvalidPath(self.path.to_owned()))?;
136 unistd::chdir(workdir).map_err(|e| NotifyListenerError::Chdir {
137 source: e,
138 path: workdir.to_owned(),
139 })?;
140 let socket_name = self
141 .path
142 .file_name()
143 .ok_or_else(|| NotifyListenerError::InvalidPath(self.path.to_owned()))?;
144 let mut stream =
145 UnixStream::connect(socket_name).map_err(|e| NotifyListenerError::Connect {
146 source: e,
147 name: socket_name.to_str().unwrap().to_owned(),
149 })?;
150 stream
151 .write_all(b"start container")
152 .map_err(NotifyListenerError::SendStartContainer)?;
153 tracing::debug!("notify finished");
154 unistd::chdir(&cwd).map_err(|e| NotifyListenerError::Chdir {
155 source: e,
156 path: cwd,
157 })?;
158 Ok(())
159 }
160}
161
162#[cfg(test)]
163mod test {
164 use tempfile::tempdir;
165
166 use super::*;
167
168 #[test]
169 fn test_notify_listener_clone() {
172 let tempdir = tempdir().unwrap();
173 let socket_path = tempdir.path().join("notify.sock");
174 let listener = NotifyListener::new(&socket_path).unwrap();
176 let mut socket = NotifySocket::new(socket_path.clone());
177 let thread_handle = std::thread::spawn({
181 move || {
182 listener.wait_for_container_start().unwrap();
185 }
186 });
187
188 socket.notify_container_start().unwrap();
189 thread_handle.join().unwrap();
190 }
191}