Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/cli/src/tunnels/service_macos.rs
3314 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
use std::{
7
fs::{remove_file, File},
8
io::{self, Write},
9
path::{Path, PathBuf},
10
};
11
12
use async_trait::async_trait;
13
14
use crate::{
15
constants::APPLICATION_NAME,
16
log,
17
state::LauncherPaths,
18
util::{
19
command::capture_command_and_check_status,
20
errors::{wrap, AnyError, CodeError, MissingHomeDirectory},
21
},
22
};
23
24
use super::{service::tail_log_file, ServiceManager};
25
26
pub struct LaunchdService {
27
log: log::Logger,
28
log_file: PathBuf,
29
}
30
31
impl LaunchdService {
32
pub fn new(log: log::Logger, paths: &LauncherPaths) -> Self {
33
Self {
34
log,
35
log_file: paths.service_log_file(),
36
}
37
}
38
}
39
40
#[async_trait]
41
impl ServiceManager for LaunchdService {
42
async fn register(
43
&self,
44
exe: std::path::PathBuf,
45
args: &[&str],
46
) -> Result<(), crate::util::errors::AnyError> {
47
let service_file = get_service_file_path()?;
48
write_service_file(&service_file, &self.log_file, exe, args)
49
.map_err(|e| wrap(e, "error creating service file"))?;
50
51
info!(self.log, "Successfully registered service...");
52
53
capture_command_and_check_status(
54
"launchctl",
55
&["load", service_file.as_os_str().to_string_lossy().as_ref()],
56
)
57
.await?;
58
59
capture_command_and_check_status("launchctl", &["start", &get_service_label()]).await?;
60
61
info!(self.log, "Tunnel service successfully started");
62
63
Ok(())
64
}
65
66
async fn show_logs(&self) -> Result<(), AnyError> {
67
tail_log_file(&self.log_file).await
68
}
69
70
async fn run(
71
self,
72
launcher_paths: crate::state::LauncherPaths,
73
mut handle: impl 'static + super::ServiceContainer,
74
) -> Result<(), crate::util::errors::AnyError> {
75
handle.run_service(self.log, launcher_paths).await
76
}
77
78
async fn is_installed(&self) -> Result<bool, AnyError> {
79
let cmd = capture_command_and_check_status("launchctl", &["list"]).await?;
80
Ok(String::from_utf8_lossy(&cmd.stdout).contains(&get_service_label()))
81
}
82
83
async fn unregister(&self) -> Result<(), crate::util::errors::AnyError> {
84
let service_file = get_service_file_path()?;
85
86
match capture_command_and_check_status("launchctl", &["stop", &get_service_label()]).await {
87
Ok(_) => {}
88
// status 3 == "no such process"
89
Err(CodeError::CommandFailed { code: 3, .. }) => {}
90
Err(e) => return Err(wrap(e, "error stopping service").into()),
91
};
92
93
info!(self.log, "Successfully stopped service...");
94
95
capture_command_and_check_status(
96
"launchctl",
97
&[
98
"unload",
99
service_file.as_os_str().to_string_lossy().as_ref(),
100
],
101
)
102
.await?;
103
104
info!(self.log, "Tunnel service uninstalled");
105
106
if let Ok(f) = get_service_file_path() {
107
remove_file(f).ok();
108
}
109
110
Ok(())
111
}
112
}
113
114
fn get_service_label() -> String {
115
format!("com.visualstudio.{}.tunnel", APPLICATION_NAME)
116
}
117
118
fn get_service_file_path() -> Result<PathBuf, MissingHomeDirectory> {
119
match dirs::home_dir() {
120
Some(mut d) => {
121
d.push(format!("{}.plist", get_service_label()));
122
Ok(d)
123
}
124
None => Err(MissingHomeDirectory()),
125
}
126
}
127
128
fn write_service_file(
129
path: &PathBuf,
130
log_file: &Path,
131
exe: std::path::PathBuf,
132
args: &[&str],
133
) -> io::Result<()> {
134
let mut f = File::create(path)?;
135
let log_file = log_file.as_os_str().to_string_lossy();
136
// todo: we may be able to skip file logging and use the ASL instead
137
// if/when we no longer need to support older macOS versions.
138
write!(
139
&mut f,
140
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\
141
<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n\
142
<plist version=\"1.0\">\n\
143
<dict>\n\
144
<key>Label</key>\n\
145
<string>{}</string>\n\
146
<key>LimitLoadToSessionType</key>\n\
147
<string>Aqua</string>\n\
148
<key>ProgramArguments</key>\n\
149
<array>\n\
150
<string>{}</string>\n\
151
<string>{}</string>\n\
152
</array>\n\
153
<key>KeepAlive</key>\n\
154
<true/>\n\
155
<key>StandardErrorPath</key>\n\
156
<string>{}</string>\n\
157
<key>StandardOutPath</key>\n\
158
<string>{}</string>\n\
159
</dict>\n\
160
</plist>",
161
get_service_label(),
162
exe.into_os_string().to_string_lossy(),
163
args.join("</string><string>"),
164
log_file,
165
log_file
166
)?;
167
Ok(())
168
}
169
170