nu_plugin_core/communication_mode/
mod.rs1use std::ffi::OsStr;
2use std::io::{Stdin, Stdout};
3use std::process::{Child, ChildStdin, ChildStdout, Command, Stdio};
4use std::sync::atomic::{AtomicBool, Ordering};
5
6use nu_protocol::ShellError;
7#[cfg(feature = "local-socket")] use nu_protocol::shell_error::io::IoError;
9
10#[cfg(feature = "local-socket")]
11mod local_socket;
12
13#[cfg(feature = "local-socket")]
14use local_socket::*;
15
16pub static SUPPRESS_STDERR: AtomicBool = AtomicBool::new(false);
22
23#[derive(Debug, Clone)]
31pub enum CommunicationMode {
32 Stdio,
34 #[cfg(feature = "local-socket")]
36 LocalSocket(std::ffi::OsString),
37}
38
39impl CommunicationMode {
40 #[cfg(feature = "local-socket")]
42 pub fn local_socket(plugin_exe: &std::path::Path) -> CommunicationMode {
43 use std::hash::{Hash, Hasher};
44 use std::time::SystemTime;
45
46 let mut hasher = std::collections::hash_map::DefaultHasher::new();
50
51 plugin_exe.hash(&mut hasher);
52 SystemTime::now().hash(&mut hasher);
53
54 let unique_id = format!("{:016x}", hasher.finish());
55
56 CommunicationMode::LocalSocket(make_local_socket_name(&unique_id))
57 }
58
59 pub fn args(&self) -> Vec<&OsStr> {
60 match self {
61 CommunicationMode::Stdio => vec![OsStr::new("--stdio")],
62 #[cfg(feature = "local-socket")]
63 CommunicationMode::LocalSocket(path) => {
64 vec![OsStr::new("--local-socket"), path.as_os_str()]
65 }
66 }
67 }
68
69 pub fn setup_command_io(&self, command: &mut Command) {
70 match self {
71 CommunicationMode::Stdio => {
72 command.stdin(Stdio::piped());
74 command.stdout(Stdio::piped());
75 }
76 #[cfg(feature = "local-socket")]
77 CommunicationMode::LocalSocket(_) => {
78 command.stdin(Stdio::inherit());
81 command.stdout(Stdio::inherit());
82 }
83 }
84
85 if SUPPRESS_STDERR.load(Ordering::Relaxed) {
86 command.stderr(Stdio::null());
87 }
88 }
89
90 pub fn serve(&self) -> Result<PreparedServerCommunication, ShellError> {
91 match self {
92 CommunicationMode::Stdio => Ok(PreparedServerCommunication::Stdio),
94 #[cfg(feature = "local-socket")]
96 CommunicationMode::LocalSocket(name) => {
97 use interprocess::local_socket::ListenerOptions;
98
99 let listener = interpret_local_socket_name(name)
100 .and_then(|name| ListenerOptions::new().name(name).create_sync())
101 .map_err(|err| {
102 IoError::new_internal(
103 err,
104 format!(
105 "Could not interpret local socket name {:?}",
106 name.to_string_lossy()
107 ),
108 )
109 })?;
110 Ok(PreparedServerCommunication::LocalSocket { listener })
111 }
112 }
113 }
114
115 pub fn connect_as_client(&self) -> Result<ClientCommunicationIo, ShellError> {
116 match self {
117 CommunicationMode::Stdio => Ok(ClientCommunicationIo::Stdio(
118 std::io::stdin(),
119 std::io::stdout(),
120 )),
121 #[cfg(feature = "local-socket")]
122 CommunicationMode::LocalSocket(name) => {
123 let get_socket = || {
125 use interprocess::local_socket as ls;
126 use ls::traits::Stream;
127
128 interpret_local_socket_name(name)
129 .and_then(|name| ls::Stream::connect(name))
130 .map_err(|err| {
131 ShellError::Io(IoError::new_internal(
132 err,
133 format!(
134 "Could not interpret local socket name {:?}",
135 name.to_string_lossy()
136 ),
137 ))
138 })
139 };
140 let read_in = get_socket()?;
142 let write_out = get_socket()?;
143 Ok(ClientCommunicationIo::LocalSocket { read_in, write_out })
144 }
145 }
146 }
147}
148
149pub enum PreparedServerCommunication {
155 Stdio,
157 #[cfg(feature = "local-socket")]
159 LocalSocket {
160 listener: interprocess::local_socket::Listener,
161 },
162}
163
164impl PreparedServerCommunication {
165 pub fn connect(&self, child: &mut Child) -> Result<ServerCommunicationIo, ShellError> {
166 match self {
167 PreparedServerCommunication::Stdio => {
168 let stdin = child
169 .stdin
170 .take()
171 .ok_or_else(|| ShellError::PluginFailedToLoad {
172 msg: "Plugin missing stdin writer".into(),
173 })?;
174
175 let stdout = child
176 .stdout
177 .take()
178 .ok_or_else(|| ShellError::PluginFailedToLoad {
179 msg: "Plugin missing stdout writer".into(),
180 })?;
181
182 Ok(ServerCommunicationIo::Stdio(stdin, stdout))
183 }
184 #[cfg(feature = "local-socket")]
185 PreparedServerCommunication::LocalSocket { listener, .. } => {
186 use interprocess::local_socket::ListenerNonblockingMode;
187 use interprocess::local_socket::traits::{Listener, Stream};
188 use nu_utils::time::Instant;
189 use std::time::Duration;
190
191 const RETRY_PERIOD: Duration = Duration::from_millis(1);
192 const TIMEOUT: Duration = Duration::from_secs(10);
193
194 let start = Instant::now();
195
196 listener
201 .set_nonblocking(ListenerNonblockingMode::Accept)
202 .map_err(|err| {
203 IoError::new_internal(
204 err,
205 "Could not set non-blocking mode accept for listener",
206 )
207 })?;
208 let mut get_socket = || {
209 let mut result = None;
210 while let Ok(None) = child.try_wait() {
211 match listener.accept() {
212 Ok(stream) => {
213 stream.set_nonblocking(false).map_err(|err| {
216 IoError::new_internal(
217 err,
218 "Could not disable non-blocking mode for listener",
219 )
220 })?;
221 result = Some(stream);
222 break;
223 }
224 Err(err) => {
225 if !is_would_block_err(&err) {
226 return Err(ShellError::Io(IoError::new_internal(
229 err,
230 "Accepting new data from listener failed",
231 )));
232 }
233 }
234 }
235 if Instant::now().saturating_duration_since(start) > TIMEOUT {
236 return Err(ShellError::PluginFailedToLoad {
237 msg: "Plugin timed out while waiting to connect to socket".into(),
238 });
239 } else {
240 std::thread::sleep(RETRY_PERIOD);
241 }
242 }
243 if let Some(stream) = result {
244 Ok(stream)
245 } else {
246 Err(ShellError::PluginFailedToLoad {
248 msg: "Plugin exited without connecting".into(),
249 })
250 }
251 };
252 let write_in = get_socket()?;
254 let read_out = get_socket()?;
255 Ok(ServerCommunicationIo::LocalSocket { read_out, write_in })
256 }
257 }
258 }
259}
260
261pub enum ServerCommunicationIo {
263 Stdio(ChildStdin, ChildStdout),
264 #[cfg(feature = "local-socket")]
265 LocalSocket {
266 read_out: interprocess::local_socket::Stream,
267 write_in: interprocess::local_socket::Stream,
268 },
269}
270
271pub enum ClientCommunicationIo {
273 Stdio(Stdin, Stdout),
274 #[cfg(feature = "local-socket")]
275 LocalSocket {
276 read_in: interprocess::local_socket::Stream,
277 write_out: interprocess::local_socket::Stream,
278 },
279}