1use std::{
2 env,
3 fs::File,
4 io::{BufRead, BufReader, Write},
5 path::PathBuf,
6};
7
8#[cfg(unix)]
9use std::{
10 fs,
11 os::unix::{
12 fs::{FileTypeExt, PermissionsExt},
13 net::{UnixListener, UnixStream},
14 },
15 path::Path,
16};
17
18#[cfg(windows)]
19use std::{
20 ffi::OsStr,
21 io::Read,
22 os::windows::{
23 ffi::OsStrExt,
24 io::{AsRawHandle, FromRawHandle, OwnedHandle},
25 },
26 ptr,
27 time::{Duration, Instant},
28};
29
30use serde::{Deserialize, Serialize, de::DeserializeOwned};
31use thiserror::Error;
32
33#[cfg(windows)]
34use windows_sys::Win32::{
35 Foundation::{
36 ERROR_BROKEN_PIPE, ERROR_IO_PENDING, ERROR_NO_DATA, ERROR_PIPE_BUSY, ERROR_PIPE_CONNECTED,
37 INVALID_HANDLE_VALUE, WAIT_ABANDONED, WAIT_OBJECT_0,
38 },
39 Storage::FileSystem::{
40 CreateFileW, FILE_FLAG_FIRST_PIPE_INSTANCE, FILE_FLAG_OVERLAPPED, FILE_GENERIC_READ,
41 FILE_GENERIC_WRITE, OPEN_EXISTING, PIPE_ACCESS_DUPLEX, ReadFile, WriteFile,
42 },
43 System::{
44 IO::{GetOverlappedResult, OVERLAPPED},
45 Pipes::{
46 ConnectNamedPipe, CreateNamedPipeW, PIPE_READMODE_BYTE, PIPE_REJECT_REMOTE_CLIENTS,
47 PIPE_TYPE_BYTE, PIPE_UNLIMITED_INSTANCES, PIPE_WAIT, WaitNamedPipeW,
48 },
49 Threading::{CreateEventW, CreateMutexW, INFINITE, ReleaseMutex, WaitForSingleObject},
50 },
51};
52
53pub const PROTOCOL_VERSION: u32 = 1;
54
55pub type Result<T> = std::result::Result<T, IpcError>;
56
57#[cfg(unix)]
58pub type IpcListener = UnixListener;
59
60#[cfg(unix)]
61pub type IpcStream = UnixStream;
62
63#[cfg(windows)]
64pub struct IpcListener {
65 pipe_name: String,
66 pending: File,
67 event: OwnedHandle,
68}
69
70#[cfg(windows)]
76pub struct IpcStream {
77 file: File,
78 event: OwnedHandle,
79}
80
81#[cfg(windows)]
82impl IpcStream {
83 fn new(file: File) -> std::io::Result<Self> {
84 Ok(Self {
85 file,
86 event: create_event()?,
87 })
88 }
89
90 pub fn try_clone(&self) -> std::io::Result<Self> {
91 Self::new(self.file.try_clone()?)
92 }
93
94 fn overlapped(&self) -> OVERLAPPED {
95 let mut overlapped: OVERLAPPED = unsafe { std::mem::zeroed() };
96 overlapped.hEvent = self.event.as_raw_handle();
97 overlapped
98 }
99
100 fn finish(&self, overlapped: &mut OVERLAPPED, started: i32) -> std::io::Result<usize> {
101 if started == 0 {
102 let error = std::io::Error::last_os_error();
103 match error.raw_os_error() {
104 Some(code) if code == ERROR_IO_PENDING as i32 => {}
105 Some(code) if code == ERROR_BROKEN_PIPE as i32 => return Ok(0),
106 _ => return Err(error),
107 }
108 }
109
110 let mut transferred = 0;
111 let completed = unsafe {
112 GetOverlappedResult(self.file.as_raw_handle(), overlapped, &mut transferred, 1)
113 };
114 if completed == 0 {
115 let error = std::io::Error::last_os_error();
116 if error.raw_os_error() == Some(ERROR_BROKEN_PIPE as i32) {
117 return Ok(0);
118 }
119
120 return Err(error);
121 }
122
123 Ok(transferred as usize)
124 }
125}
126
127#[cfg(windows)]
128impl Read for IpcStream {
129 fn read(&mut self, buffer: &mut [u8]) -> std::io::Result<usize> {
130 let mut overlapped = self.overlapped();
131 let started = unsafe {
132 ReadFile(
133 self.file.as_raw_handle(),
134 buffer.as_mut_ptr(),
135 buffer.len() as u32,
136 ptr::null_mut(),
137 &mut overlapped,
138 )
139 };
140
141 self.finish(&mut overlapped, started)
142 }
143}
144
145#[cfg(windows)]
146impl Write for IpcStream {
147 fn write(&mut self, buffer: &[u8]) -> std::io::Result<usize> {
148 let mut overlapped = self.overlapped();
149 let started = unsafe {
150 WriteFile(
151 self.file.as_raw_handle(),
152 buffer.as_ptr(),
153 buffer.len() as u32,
154 ptr::null_mut(),
155 &mut overlapped,
156 )
157 };
158
159 self.finish(&mut overlapped, started)
160 }
161
162 fn flush(&mut self) -> std::io::Result<()> {
163 Ok(())
164 }
165}
166
167#[cfg(windows)]
168fn create_event() -> std::io::Result<OwnedHandle> {
169 let event = unsafe { CreateEventW(ptr::null(), 1, 0, ptr::null()) };
170 if event.is_null() {
171 return Err(std::io::Error::last_os_error());
172 }
173
174 Ok(unsafe { OwnedHandle::from_raw_handle(event) })
175}
176
177#[derive(Debug, Error)]
178pub enum IpcError {
179 #[error("{0}")]
180 Io(#[from] std::io::Error),
181 #[error("{0}")]
182 Json(#[from] serde_json::Error),
183 #[error("socket path is occupied by a non-socket file: {0}")]
184 OccupiedSocketPath(PathBuf),
185 #[error("{0} is not set")]
186 MissingEnvironment(&'static str),
187 #[error("{0}")]
188 Protocol(String),
189}
190
191#[cfg(unix)]
192#[derive(Clone, Debug, Serialize)]
193pub struct RuntimePaths {
194 pub runtime_dir: PathBuf,
195 pub socket_path: PathBuf,
196}
197
198#[cfg(windows)]
199#[derive(Clone, Debug, Serialize)]
200pub struct RuntimePaths {
201 pub pipe_name: String,
202 pub startup_mutex_name: String,
203}
204
205#[cfg(unix)]
206impl RuntimePaths {
207 pub fn discover() -> Result<Self> {
208 let runtime_dir = runtime_root()?.join("analyzed");
209
210 Ok(Self {
211 socket_path: runtime_dir.join("daemon.sock"),
212 runtime_dir,
213 })
214 }
215
216 pub fn ensure_runtime_dir(&self) -> Result<()> {
217 fs::create_dir_all(&self.runtime_dir)?;
218 fs::set_permissions(&self.runtime_dir, fs::Permissions::from_mode(0o700))?;
219 Ok(())
220 }
221}
222
223#[cfg(windows)]
224impl RuntimePaths {
225 pub fn discover() -> Result<Self> {
226 let username =
227 env::var("USERNAME").map_err(|_| IpcError::MissingEnvironment("USERNAME"))?;
228
229 Ok(Self {
230 pipe_name: format!(r"\\.\pipe\analyzed.{username}"),
231 startup_mutex_name: format!(r"Global\analyzed.{username}.startup"),
232 })
233 }
234}
235
236#[derive(Debug, Serialize, Deserialize)]
237pub struct ClientInfo {
238 pub protocol_version: u32,
239 pub client_version: String,
240}
241
242impl ClientInfo {
243 pub fn current() -> Self {
244 Self {
245 protocol_version: PROTOCOL_VERSION,
246 client_version: env!("CARGO_PKG_VERSION").to_owned(),
247 }
248 }
249}
250
251#[derive(Debug, Serialize, Deserialize)]
252#[serde(tag = "type", content = "payload", rename_all = "snake_case")]
253pub enum DaemonRequest {
254 Hello(ClientInfo),
255 Lsp(ClientInfo),
256 Stop(ClientInfo),
257}
258
259impl DaemonRequest {
260 pub fn hello() -> Self {
261 Self::Hello(ClientInfo::current())
262 }
263
264 pub fn stop() -> Self {
265 Self::Stop(ClientInfo::current())
266 }
267
268 pub fn lsp() -> Self {
269 Self::Lsp(ClientInfo::current())
270 }
271
272 pub fn client_info(&self) -> &ClientInfo {
273 match self {
274 Self::Hello(client) | Self::Lsp(client) | Self::Stop(client) => client,
275 }
276 }
277}
278
279#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd, Serialize, Deserialize)]
280pub struct BackendKey {
281 pub shared_world: SharedWorldKey,
282 pub workspace_view: WorkspaceViewKey,
283}
284
285#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd, Serialize, Deserialize)]
286pub struct SharedWorldKey {
287 pub rust_analyzer_version: String,
288 pub toolchain: Option<String>,
289 pub sysroot: Option<String>,
290 pub cargo_target: Option<String>,
291 pub config: SharedWorldConfigKey,
292 pub load: SharedWorldLoadKey,
293}
294
295#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd, Serialize, Deserialize)]
296pub struct SharedWorldConfigKey {
297 pub cargo: CargoConfigKey,
298}
299
300#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd, Serialize, Deserialize)]
301pub struct CargoConfigKey {
302 pub all_targets: bool,
303 pub features: String,
304 pub target: Option<String>,
305 pub sysroot: Option<String>,
306 pub sysroot_src: Option<String>,
307 pub rustc_source: Option<String>,
308 pub extra_includes: Vec<String>,
309 pub cfg_overrides: String,
310 pub wrap_rustc_in_build_scripts: bool,
311 pub invocation_strategy: String,
312 pub run_build_script_command: String,
313 pub extra_args: Vec<String>,
314 pub extra_env: Vec<(String, Option<String>)>,
315 pub target_dir_config: String,
316 pub set_test: bool,
317 pub no_deps: bool,
318 pub metadata_extra_args: Vec<String>,
319}
320
321#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd, Serialize, Deserialize)]
322pub struct SharedWorldLoadKey {
323 pub load_out_dirs_from_check: bool,
324 pub proc_macro_server: ProcMacroServerKey,
325 pub prefill_caches: bool,
326 pub num_worker_threads: u16,
327 pub proc_macro_processes: u16,
328}
329
330#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd, Serialize, Deserialize)]
331pub enum ProcMacroServerKey {
332 None,
333 Sysroot,
334 Explicit(String),
335}
336
337#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd, Serialize, Deserialize)]
338pub struct WorkspaceViewKey {
339 pub workspace_roots: Vec<String>,
340 pub analysis: AnalysisConfigKey,
341}
342
343#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd, Serialize, Deserialize)]
344pub struct AnalysisConfigKey {
345 pub initialization_options: Option<String>,
346 pub workspace_configuration: Option<String>,
347}
348
349#[derive(Clone, Debug, Serialize, Deserialize)]
350pub struct BackendSnapshot {
351 pub key: BackendKey,
352 pub client_sessions: usize,
353 pub overlay_sessions: usize,
354 pub overlay_files: usize,
355 pub workspace_loads: Vec<WorkspaceSnapshot>,
356}
357
358#[derive(Clone, Debug, Serialize, Deserialize)]
359pub struct WorkspaceSnapshot {
360 pub root: String,
361 pub manifest: String,
362 pub packages: usize,
363 pub files: usize,
364 pub proc_macro_server: bool,
365}
366
367#[derive(Clone, Debug, Serialize, Deserialize)]
368pub struct DaemonSnapshot {
369 pub pid: u32,
370 pub started_at_unix_seconds: u64,
371 pub client_sessions: usize,
372 pub backend_sessions: Vec<BackendSnapshot>,
373 pub workspaces: usize,
374 pub workspace_loads: Vec<WorkspaceSnapshot>,
375}
376
377#[derive(Clone, Debug, Serialize, Deserialize)]
378pub struct Hello {
379 pub ok: bool,
380 pub pid: u32,
381 pub protocol_version: u32,
382 pub daemon_version: String,
383 pub rust_analyzer_version: String,
384 pub capabilities: Vec<String>,
385 pub state: Option<DaemonSnapshot>,
386}
387
388impl Hello {
389 pub fn with_state(state: DaemonSnapshot, rust_analyzer_version: String) -> Self {
390 Self {
391 ok: true,
392 pid: state.pid,
393 protocol_version: PROTOCOL_VERSION,
394 daemon_version: env!("CARGO_PKG_VERSION").to_owned(),
395 rust_analyzer_version,
396 capabilities: vec!["lsp".to_owned()],
397 state: Some(state),
398 }
399 }
400}
401
402#[derive(Clone, Debug, Serialize, Deserialize)]
403pub struct Stop {
404 pub accepted: bool,
405 pub pid: u32,
406}
407
408#[derive(Clone, Debug, Serialize, Deserialize)]
409pub struct LspSession {
410 pub accepted: bool,
411 pub pid: u32,
412}
413
414#[derive(Clone, Debug, Serialize, Deserialize)]
415pub struct ProtocolError {
416 pub message: String,
417}
418
419#[derive(Clone, Debug, Serialize, Deserialize)]
420#[serde(tag = "type", content = "payload", rename_all = "snake_case")]
421pub enum DaemonResponse {
422 Hello(Hello),
423 Lsp(LspSession),
424 Stop(Stop),
425 Error(ProtocolError),
426}
427
428#[cfg(unix)]
429pub struct StartupLock {
430 _file: File,
431}
432
433#[cfg(unix)]
434impl StartupLock {
435 pub fn acquire(paths: &RuntimePaths) -> Result<Self> {
436 paths.ensure_runtime_dir()?;
437 let file = File::open(&paths.runtime_dir)?;
438 file.lock()?;
439
440 Ok(Self { _file: file })
441 }
442}
443
444#[cfg(windows)]
445pub struct StartupLock {
446 mutex: OwnedHandle,
447}
448
449#[cfg(windows)]
450impl StartupLock {
451 pub fn acquire(paths: &RuntimePaths) -> Result<Self> {
452 let name = wide_null(&paths.startup_mutex_name);
453 let handle = unsafe { CreateMutexW(ptr::null(), 0, name.as_ptr()) };
454 if handle.is_null() {
455 return Err(std::io::Error::last_os_error().into());
456 }
457
458 let mutex = unsafe { OwnedHandle::from_raw_handle(handle) };
459 match unsafe { WaitForSingleObject(mutex.as_raw_handle(), INFINITE) } {
460 WAIT_OBJECT_0 | WAIT_ABANDONED => Ok(Self { mutex }),
461 _ => Err(std::io::Error::last_os_error().into()),
462 }
463 }
464}
465
466#[cfg(windows)]
467impl Drop for StartupLock {
468 fn drop(&mut self) {
469 unsafe { ReleaseMutex(self.mutex.as_raw_handle()) };
470 }
471}
472
473pub fn connect_hello(paths: &RuntimePaths) -> Result<Hello> {
474 match request(paths, &DaemonRequest::hello())? {
475 DaemonResponse::Hello(hello) => Ok(hello),
476 DaemonResponse::Error(error) => Err(IpcError::Protocol(error.message)),
477 response => Err(IpcError::Protocol(format!(
478 "unexpected daemon response: {response:?}"
479 ))),
480 }
481}
482
483pub fn request_stop(paths: &RuntimePaths) -> Result<Stop> {
484 match request(paths, &DaemonRequest::stop())? {
485 DaemonResponse::Stop(stop) => Ok(stop),
486 DaemonResponse::Error(error) => Err(IpcError::Protocol(error.message)),
487 response => Err(IpcError::Protocol(format!(
488 "unexpected daemon response: {response:?}"
489 ))),
490 }
491}
492
493pub fn connect_lsp_session(paths: &RuntimePaths) -> Result<IpcStream> {
494 let mut stream = connect_stream(paths)?;
495 write_json_line(&mut stream, &DaemonRequest::lsp())?;
496
497 match read_json_line(&mut stream)? {
498 DaemonResponse::Lsp(session) if session.accepted => Ok(stream),
499 DaemonResponse::Lsp(session) => Err(IpcError::Protocol(format!(
500 "daemon rejected lsp session for pid {}",
501 session.pid
502 ))),
503 DaemonResponse::Error(error) => Err(IpcError::Protocol(error.message)),
504 response => Err(IpcError::Protocol(format!(
505 "unexpected daemon response: {response:?}"
506 ))),
507 }
508}
509
510pub fn request(paths: &RuntimePaths, request: &DaemonRequest) -> Result<DaemonResponse> {
511 let mut stream = connect_stream(paths)?;
512 write_json_line(&mut stream, request)?;
513 read_json_line(&mut stream)
514}
515
516#[cfg(unix)]
517pub fn bind_listener(paths: &RuntimePaths) -> Result<IpcListener> {
518 paths.ensure_runtime_dir()?;
519 remove_stale_socket(&paths.socket_path)?;
520
521 Ok(UnixListener::bind(&paths.socket_path)?)
522}
523
524#[cfg(windows)]
525pub fn bind_listener(paths: &RuntimePaths) -> Result<IpcListener> {
526 Ok(IpcListener {
527 pending: create_pipe(&paths.pipe_name, true)?,
528 pipe_name: paths.pipe_name.clone(),
529 event: create_event()?,
530 })
531}
532
533#[cfg(unix)]
534pub fn accept_client(listener: &mut IpcListener) -> Result<IpcStream> {
535 let (stream, _) = listener.accept()?;
536 Ok(stream)
537}
538
539#[cfg(windows)]
540pub fn accept_client(listener: &mut IpcListener) -> Result<IpcStream> {
541 listener.accept()
542}
543
544pub fn read_json_line<T>(stream: &mut IpcStream) -> Result<T>
545where
546 T: DeserializeOwned,
547{
548 let mut reader = BufReader::new(stream.try_clone()?);
549 let mut line = String::new();
550 reader.read_line(&mut line)?;
551
552 Ok(serde_json::from_str(&line)?)
553}
554
555pub fn write_json_line<T>(stream: &mut IpcStream, value: &T) -> Result<()>
556where
557 T: Serialize,
558{
559 serde_json::to_writer(&mut *stream, value)?;
560 stream.write_all(b"\n")?;
561 stream.flush()?;
562
563 Ok(())
564}
565
566#[cfg(unix)]
567fn connect_stream(paths: &RuntimePaths) -> Result<IpcStream> {
568 Ok(UnixStream::connect(&paths.socket_path)?)
569}
570
571#[cfg(windows)]
572fn connect_stream(paths: &RuntimePaths) -> Result<IpcStream> {
573 connect_pipe(&paths.pipe_name)
574}
575
576#[cfg(unix)]
577fn remove_stale_socket(path: &Path) -> Result<()> {
578 if path.try_exists()? {
579 let file_type = fs::symlink_metadata(path)?.file_type();
580 if !file_type.is_socket() {
581 return Err(IpcError::OccupiedSocketPath(path.to_path_buf()));
582 }
583
584 fs::remove_file(path)?;
585 }
586
587 Ok(())
588}
589
590#[cfg(target_os = "macos")]
591fn runtime_root() -> Result<PathBuf> {
592 runtime_env("TMPDIR")
593}
594
595#[cfg(all(unix, not(target_os = "macos")))]
596fn runtime_root() -> Result<PathBuf> {
597 runtime_env("XDG_RUNTIME_DIR")
598}
599
600#[cfg(unix)]
601fn runtime_env(name: &'static str) -> Result<PathBuf> {
602 env::var_os(name)
603 .filter(|path| !path.is_empty())
604 .map(PathBuf::from)
605 .ok_or(IpcError::MissingEnvironment(name))
606}
607
608#[cfg(windows)]
609impl IpcListener {
610 fn accept(&mut self) -> Result<IpcStream> {
611 loop {
612 match connect_pending_pipe(&self.pending, &self.event) {
613 Ok(()) => {
614 let next = create_pipe(&self.pipe_name, false)?;
615 return Ok(IpcStream::new(std::mem::replace(&mut self.pending, next))?);
616 }
617 Err(IpcError::Io(error)) if error.raw_os_error() == Some(ERROR_NO_DATA as i32) => {
621 let next = create_pipe(&self.pipe_name, false)?;
622 self.pending = next;
623 }
624 Err(error) => return Err(error),
625 }
626 }
627 }
628}
629
630#[cfg(windows)]
631fn create_pipe(pipe_name: &str, first_instance: bool) -> Result<File> {
632 let pipe_name = wide_null(pipe_name);
633 let first_instance = if first_instance {
634 FILE_FLAG_FIRST_PIPE_INSTANCE
635 } else {
636 0
637 };
638 let handle = unsafe {
639 CreateNamedPipeW(
640 pipe_name.as_ptr(),
641 PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED | first_instance,
642 PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT | PIPE_REJECT_REMOTE_CLIENTS,
643 PIPE_UNLIMITED_INSTANCES,
644 65_536,
645 65_536,
646 0,
647 ptr::null_mut(),
648 )
649 };
650 if handle == INVALID_HANDLE_VALUE {
651 return Err(std::io::Error::last_os_error().into());
652 }
653
654 Ok(unsafe { File::from_raw_handle(handle) })
655}
656
657#[cfg(windows)]
658fn connect_pending_pipe(file: &File, event: &OwnedHandle) -> Result<()> {
659 let mut overlapped: OVERLAPPED = unsafe { std::mem::zeroed() };
660 overlapped.hEvent = event.as_raw_handle();
661
662 let connected = unsafe { ConnectNamedPipe(file.as_raw_handle(), &mut overlapped) };
663 if connected != 0 {
664 return Ok(());
665 }
666
667 let error = std::io::Error::last_os_error();
668 match error.raw_os_error() {
669 Some(code) if code == ERROR_PIPE_CONNECTED as i32 => Ok(()),
670 Some(code) if code == ERROR_IO_PENDING as i32 => {
671 let mut transferred = 0;
672 let completed = unsafe {
673 GetOverlappedResult(file.as_raw_handle(), &overlapped, &mut transferred, 1)
674 };
675 if completed == 0 {
676 return Err(std::io::Error::last_os_error().into());
677 }
678
679 Ok(())
680 }
681 _ => Err(error.into()),
682 }
683}
684
685#[cfg(windows)]
686fn connect_pipe(pipe_name: &str) -> Result<IpcStream> {
687 let wide_name = wide_null(pipe_name);
688 let deadline = Instant::now() + Duration::from_secs(1);
689
690 loop {
691 let handle = unsafe {
692 CreateFileW(
693 wide_name.as_ptr(),
694 FILE_GENERIC_READ | FILE_GENERIC_WRITE,
695 0,
696 ptr::null_mut(),
697 OPEN_EXISTING,
698 FILE_FLAG_OVERLAPPED,
699 ptr::null_mut(),
700 )
701 };
702 if handle != INVALID_HANDLE_VALUE {
703 return Ok(IpcStream::new(unsafe { File::from_raw_handle(handle) })?);
704 }
705
706 let error = std::io::Error::last_os_error();
707 if error.raw_os_error() != Some(ERROR_PIPE_BUSY as i32) {
708 return Err(error.into());
709 }
710
711 let remaining = deadline.saturating_duration_since(Instant::now());
715 if remaining.is_zero() {
716 return Err(error.into());
717 }
718
719 unsafe { WaitNamedPipeW(wide_name.as_ptr(), remaining.as_millis().max(1) as u32) };
720 }
721}
722
723#[cfg(windows)]
724fn wide_null(value: &str) -> Vec<u16> {
725 OsStr::new(value)
726 .encode_wide()
727 .chain(std::iter::once(0))
728 .collect()
729}