mirror of
https://github.com/microsoft/vscode.git
synced 2026-05-08 17:19:48 +01:00
796ee2bf3c
Fixes the bulk of https://github.com/microsoft/vscode-cli/issues/560
172 lines
4.3 KiB
Rust
172 lines
4.3 KiB
Rust
/*---------------------------------------------------------------------------------------------
|
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
|
*--------------------------------------------------------------------------------------------*/
|
|
|
|
use std::{
|
|
fs::{remove_file, File},
|
|
io::{self, Write},
|
|
path::{Path, PathBuf},
|
|
};
|
|
|
|
use async_trait::async_trait;
|
|
use tokio::sync::mpsc;
|
|
|
|
use crate::{
|
|
commands::tunnels::ShutdownSignal,
|
|
constants::APPLICATION_NAME,
|
|
log,
|
|
state::LauncherPaths,
|
|
util::{
|
|
command::capture_command_and_check_status,
|
|
errors::{wrap, AnyError, MissingHomeDirectory},
|
|
},
|
|
};
|
|
|
|
use super::{service::tail_log_file, ServiceManager};
|
|
|
|
pub struct LaunchdService {
|
|
log: log::Logger,
|
|
log_file: PathBuf,
|
|
}
|
|
|
|
impl LaunchdService {
|
|
pub fn new(log: log::Logger, paths: &LauncherPaths) -> Self {
|
|
Self {
|
|
log,
|
|
log_file: paths.service_log_file(),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[async_trait]
|
|
impl ServiceManager for LaunchdService {
|
|
async fn register(
|
|
&self,
|
|
exe: std::path::PathBuf,
|
|
args: &[&str],
|
|
) -> Result<(), crate::util::errors::AnyError> {
|
|
let service_file = get_service_file_path()?;
|
|
write_service_file(&service_file, &self.log_file, exe, args)
|
|
.map_err(|e| wrap(e, "error creating service file"))?;
|
|
|
|
info!(self.log, "Successfully registered service...");
|
|
|
|
capture_command_and_check_status(
|
|
"launchctl",
|
|
&["load", service_file.as_os_str().to_string_lossy().as_ref()],
|
|
)
|
|
.await?;
|
|
|
|
capture_command_and_check_status("launchctl", &["start", &get_service_label()]).await?;
|
|
|
|
info!(self.log, "Tunnel service successfully started");
|
|
|
|
Ok(())
|
|
}
|
|
|
|
async fn show_logs(&self) -> Result<(), AnyError> {
|
|
tail_log_file(&self.log_file).await
|
|
}
|
|
|
|
async fn run(
|
|
self,
|
|
launcher_paths: crate::state::LauncherPaths,
|
|
mut handle: impl 'static + super::ServiceContainer,
|
|
) -> Result<(), crate::util::errors::AnyError> {
|
|
let (tx, rx) = mpsc::unbounded_channel::<ShutdownSignal>();
|
|
tokio::spawn(async move {
|
|
tokio::signal::ctrl_c().await.ok();
|
|
tx.send(ShutdownSignal::CtrlC).ok();
|
|
});
|
|
|
|
handle.run_service(self.log, launcher_paths, rx).await
|
|
}
|
|
|
|
async fn unregister(&self) -> Result<(), crate::util::errors::AnyError> {
|
|
let service_file = get_service_file_path()?;
|
|
|
|
match capture_command_and_check_status("launchctl", &["stop", &get_service_label()]).await {
|
|
Ok(_) => {}
|
|
// status 3 == "no such process"
|
|
Err(AnyError::CommandFailed(e)) if e.output.status.code() == Some(3) => {}
|
|
Err(e) => return Err(e),
|
|
};
|
|
|
|
info!(self.log, "Successfully stopped service...");
|
|
|
|
capture_command_and_check_status(
|
|
"launchctl",
|
|
&[
|
|
"unload",
|
|
service_file.as_os_str().to_string_lossy().as_ref(),
|
|
],
|
|
)
|
|
.await?;
|
|
|
|
info!(self.log, "Tunnel service uninstalled");
|
|
|
|
if let Ok(f) = get_service_file_path() {
|
|
remove_file(f).ok();
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
fn get_service_label() -> String {
|
|
format!("com.visualstudio.{}.tunnel", APPLICATION_NAME)
|
|
}
|
|
|
|
fn get_service_file_path() -> Result<PathBuf, MissingHomeDirectory> {
|
|
match dirs::home_dir() {
|
|
Some(mut d) => {
|
|
d.push(format!("{}.plist", get_service_label()));
|
|
Ok(d)
|
|
}
|
|
None => Err(MissingHomeDirectory()),
|
|
}
|
|
}
|
|
|
|
fn write_service_file(
|
|
path: &PathBuf,
|
|
log_file: &Path,
|
|
exe: std::path::PathBuf,
|
|
args: &[&str],
|
|
) -> io::Result<()> {
|
|
let mut f = File::create(path)?;
|
|
let log_file = log_file.as_os_str().to_string_lossy();
|
|
// todo: we may be able to skip file logging and use the ASL instead
|
|
// if/when we no longer need to support older macOS versions.
|
|
write!(
|
|
&mut f,
|
|
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\
|
|
<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n\
|
|
<plist version=\"1.0\">\n\
|
|
<dict>\n\
|
|
<key>Label</key>\n\
|
|
<string>{}</string>\n\
|
|
<key>LimitLoadToSessionType</key>\n\
|
|
<string>Aqua</string>\n\
|
|
<key>ProgramArguments</key>\n\
|
|
<array>\n\
|
|
<string>{}</string>\n\
|
|
<string>{}</string>\n\
|
|
</array>\n\
|
|
<key>KeepAlive</key>\n\
|
|
<true/>\n\
|
|
<key>StandardErrorPath</key>\n\
|
|
<string>{}</string>\n\
|
|
<key>StandardOutPath</key>\n\
|
|
<string>{}</string>\n\
|
|
</dict>\n\
|
|
</plist>",
|
|
get_service_label(),
|
|
exe.into_os_string().to_string_lossy(),
|
|
args.join("</string><string>"),
|
|
log_file,
|
|
log_file
|
|
)?;
|
|
Ok(())
|
|
}
|