1 // Copyright (c) 2024 Huawei Technologies Co.,Ltd. All rights reserved.
2 //
3 // StratoVirt is licensed under Mulan PSL v2.
4 // You can use this software according to the terms and conditions of the Mulan
5 // PSL v2.
6 // You may obtain a copy of Mulan PSL v2 at:
7 //         http://license.coscl.org.cn/MulanPSL2
8 // THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY
9 // KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
10 // NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
11 // See the Mulan PSL v2 for more details.
12 
13 use std::{
14     env,
15     io::{Read, Write},
16     os::unix::{
17         io::AsRawFd,
18         net::{UnixListener, UnixStream},
19     },
20     path::PathBuf,
21 };
22 
23 use anyhow::{anyhow, bail, Context, Result};
24 use nix::unistd::{self, chdir};
25 
26 use crate::utils::OzonecErr;
27 
28 pub const NOTIFY_SOCKET: &str = "notify.sock";
29 
30 pub struct NotifyListener {
31     socket: UnixListener,
32 }
33 
34 impl NotifyListener {
new(root: PathBuf) -> Result<Self>35     pub fn new(root: PathBuf) -> Result<Self> {
36         // The length of path of Unix domain socket has the limit 108, which is smaller then
37         // the maximum length of file on Linux (255).
38         let cwd = env::current_dir().with_context(|| OzonecErr::GetCurDir)?;
39         chdir(&root).with_context(|| "Failed to chdir to root directory")?;
40         let listener =
41             UnixListener::bind(NOTIFY_SOCKET).with_context(|| "Failed to bind notify socket")?;
42         chdir(&cwd).with_context(|| "Failed to chdir to previous working directory")?;
43         Ok(Self { socket: listener })
44     }
45 
wait_for_start_container(&self) -> Result<()>46     pub fn wait_for_start_container(&self) -> Result<()> {
47         match self.socket.accept() {
48             Ok((mut socket, _)) => {
49                 let mut response = String::new();
50                 socket
51                     .read_to_string(&mut response)
52                     .with_context(|| "Invalid response from notify socket")?;
53             }
54             Err(e) => {
55                 bail!("Failed to accept on notify socket: {}", e);
56             }
57         }
58         Ok(())
59     }
60 
close(&self) -> Result<()>61     pub fn close(&self) -> Result<()> {
62         Ok(unistd::close(self.socket.as_raw_fd())?)
63     }
64 }
65 
66 pub struct NotifySocket {
67     path: PathBuf,
68 }
69 
70 impl NotifySocket {
new(path: &PathBuf) -> Self71     pub fn new(path: &PathBuf) -> Self {
72         Self { path: path.into() }
73     }
74 
notify_container_start(&mut self) -> Result<()>75     pub fn notify_container_start(&mut self) -> Result<()> {
76         let cwd = env::current_dir().with_context(|| OzonecErr::GetCurDir)?;
77         let root_path = self
78             .path
79             .parent()
80             .ok_or_else(|| anyhow!("Invalid notify socket path"))?;
81         chdir(root_path).with_context(|| "Failed to chdir to root directory")?;
82 
83         let mut stream =
84             UnixStream::connect(NOTIFY_SOCKET).with_context(|| "Failed to connect notify.sock")?;
85         stream.write_all(b"start container")?;
86         chdir(&cwd).with_context(|| "Failed to chdir to previous working directory")?;
87 
88         Ok(())
89     }
90 }
91 
92 #[cfg(test)]
93 mod test {
94     use std::fs::{create_dir_all, remove_dir_all};
95 
96     use nix::sys::wait::{waitpid, WaitStatus};
97 
98     use crate::linux::process::clone_process;
99 
100     use super::*;
101 
102     #[test]
test_notify_socket()103     fn test_notify_socket() {
104         remove_dir_all("/tmp/ozonec").unwrap_or_default();
105 
106         let root = PathBuf::from("/tmp/ozonec/notify_socket");
107         create_dir_all(&root).unwrap();
108 
109         let socket_path = root.join(NOTIFY_SOCKET);
110         let mut socket = NotifySocket::new(&socket_path);
111         let listener = NotifyListener::new(root.clone()).unwrap();
112         let child = clone_process("notify_socket", || {
113             listener.wait_for_start_container().unwrap();
114             Ok(1)
115         })
116         .unwrap();
117         socket.notify_container_start().unwrap();
118 
119         match waitpid(child, None) {
120             Ok(WaitStatus::Exited(_, s)) => {
121                 assert_eq!(s, 1);
122             }
123             Ok(_) => (),
124             Err(e) => {
125                 panic!("Failed to waitpid for child process: {e}");
126             }
127         }
128     }
129 }
130