Skip to content

Commit 2cf2110

Browse files
authored
Merge pull request microsoft#185385 from microsoft/cli-service-improvements
Cli service improvements
2 parents 4f5584d + 8a006c7 commit 2cf2110

File tree

12 files changed

+131
-33
lines changed

12 files changed

+131
-33
lines changed

cli/src/commands/args.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
use std::{fmt, path::PathBuf};
77

88
use crate::{constants, log, options, tunnels::code_server::CodeServerArgs};
9-
use clap::{ValueEnum, Args, Parser, Subcommand};
9+
use clap::{Args, Parser, Subcommand, ValueEnum};
1010
use const_format::concatcp;
1111

1212
const CLI_NAME: &str = concatcp!(constants::PRODUCT_NAME_LONG, " CLI");

cli/src/commands/tunnels.rs

Lines changed: 38 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44
*--------------------------------------------------------------------------------------------*/
55

66
use async_trait::async_trait;
7-
use base64::{Engine as _, engine::general_purpose as b64};
7+
use base64::{engine::general_purpose as b64, Engine as _};
8+
use serde::Serialize;
89
use sha2::{Digest, Sha256};
910
use std::{str::FromStr, time::Duration};
1011
use sysinfo::Pid;
@@ -247,26 +248,39 @@ pub async fn kill(ctx: CommandContext) -> Result<i32, AnyError> {
247248
.map_err(|e| e.into())
248249
}
249250

251+
#[derive(Serialize)]
252+
pub struct StatusOutput {
253+
pub tunnel: Option<protocol::singleton::TunnelState>,
254+
pub service_installed: bool,
255+
}
256+
250257
pub async fn status(ctx: CommandContext) -> Result<i32, AnyError> {
251-
let status = do_single_rpc_call::<_, protocol::singleton::Status>(
258+
let tunnel_status = do_single_rpc_call::<_, protocol::singleton::Status>(
252259
&ctx.paths.tunnel_lockfile(),
253260
ctx.log.clone(),
254261
protocol::singleton::METHOD_STATUS,
255262
protocol::EmptyObject {},
256263
)
257264
.await;
258265

259-
match status {
260-
Err(CodeError::NoRunningTunnel) => {
261-
ctx.log.result(CodeError::NoRunningTunnel.to_string());
262-
Ok(1)
263-
}
264-
Err(e) => Err(e.into()),
265-
Ok(s) => {
266-
ctx.log.result(serde_json::to_string(&s).unwrap());
267-
Ok(0)
268-
}
269-
}
266+
let service_installed = create_service_manager(ctx.log.clone(), &ctx.paths)
267+
.is_installed()
268+
.await
269+
.unwrap_or(false);
270+
271+
ctx.log.result(
272+
serde_json::to_string(&StatusOutput {
273+
service_installed,
274+
tunnel: match tunnel_status {
275+
Ok(s) => Some(s.tunnel),
276+
Err(CodeError::NoRunningTunnel) => None,
277+
Err(e) => return Err(e.into()),
278+
},
279+
})
280+
.unwrap(),
281+
);
282+
283+
Ok(0)
270284
}
271285

272286
/// Removes unused servers.
@@ -331,6 +345,13 @@ async fn serve_with_csa(
331345
log = log.tee(log_broadcast.clone());
332346
log::install_global_logger(log.clone()); // re-install so that library logs are captured
333347

348+
debug!(
349+
log,
350+
"Starting tunnel with `{} {}`",
351+
APPLICATION_NAME,
352+
std::env::args().collect::<Vec<_>>().join(" ")
353+
);
354+
334355
// Intentionally read before starting the server. If the server updated and
335356
// respawn is requested, the old binary will get renamed, and then
336357
// current_exe will point to the wrong path.
@@ -421,7 +442,10 @@ async fn serve_with_csa(
421442

422443
return Ok(exit.code().unwrap_or(1));
423444
}
424-
Next::Exit => return Ok(0),
445+
Next::Exit => {
446+
debug!(log, "Tunnel shut down");
447+
return Ok(0);
448+
}
425449
Next::Restart => continue,
426450
}
427451
}

cli/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@ pub mod tunnels;
1818
pub mod update_service;
1919
pub mod util;
2020

21-
mod download_cache;
2221
mod async_pipe;
22+
mod download_cache;
2323
mod json_rpc;
2424
mod msgpack_rpc;
2525
mod rpc;

cli/src/log.rs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,9 +159,21 @@ pub struct FileLogSink {
159159
file: Arc<std::sync::Mutex<std::fs::File>>,
160160
}
161161

162+
const FILE_LOG_SIZE_LIMIT: u64 = 1024 * 1024 * 10; // 10MB
163+
162164
impl FileLogSink {
163165
pub fn new(level: Level, path: &Path) -> std::io::Result<Self> {
164-
let file = std::fs::File::create(path)?;
166+
// Truncate the service log occasionally to avoid growing infinitely
167+
if matches!(path.metadata(), Ok(m) if m.len() > FILE_LOG_SIZE_LIMIT) {
168+
// ignore errors, can happen if another process is writing right now
169+
let _ = std::fs::remove_file(path);
170+
}
171+
172+
let file = std::fs::OpenOptions::new()
173+
.append(true)
174+
.create(true)
175+
.open(path)?;
176+
165177
Ok(Self {
166178
level,
167179
file: Arc::new(std::sync::Mutex::new(file)),

cli/src/tunnels/service.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,9 @@ pub trait ServiceManager {
4141
/// Show logs from the running service to standard out.
4242
async fn show_logs(&self) -> Result<(), AnyError>;
4343

44+
/// Gets whether the tunnel service is installed.
45+
async fn is_installed(&self) -> Result<bool, AnyError>;
46+
4447
/// Unregisters the current executable as a service.
4548
async fn unregister(&self) -> Result<(), AnyError>;
4649
}

cli/src/tunnels/service_linux.rs

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ use crate::{
1717
constants::{APPLICATION_NAME, PRODUCT_NAME_LONG},
1818
log,
1919
state::LauncherPaths,
20-
util::errors::{wrap, AnyError},
20+
util::errors::{wrap, AnyError, DbusConnectFailedError},
2121
};
2222

2323
use super::ServiceManager;
@@ -40,7 +40,7 @@ impl SystemdService {
4040
async fn connect() -> Result<Connection, AnyError> {
4141
let connection = Connection::session()
4242
.await
43-
.map_err(|e| wrap(e, "error creating dbus session"))?;
43+
.map_err(|e| DbusConnectFailedError(e.to_string()))?;
4444
Ok(connection)
4545
}
4646

@@ -110,9 +110,27 @@ impl ServiceManager for SystemdService {
110110

111111
info!(self.log, "Tunnel service successfully started");
112112

113+
if std::env::var("SSH_CLIENT").is_ok() || std::env::var("SSH_TTY").is_ok() {
114+
info!(self.log, "Tip: run `sudo loginctl enable-linger $USER` to ensure the service stays running after you disconnect.");
115+
}
116+
113117
Ok(())
114118
}
115119

120+
async fn is_installed(&self) -> Result<bool, AnyError> {
121+
let connection = SystemdService::connect().await?;
122+
let proxy = SystemdService::proxy(&connection).await?;
123+
let state = proxy
124+
.get_unit_file_state(SystemdService::service_name_string())
125+
.await;
126+
127+
if let Ok(s) = state {
128+
Ok(s == "enabled")
129+
} else {
130+
Ok(false)
131+
}
132+
}
133+
116134
async fn run(
117135
self,
118136
launcher_paths: crate::state::LauncherPaths,
@@ -219,6 +237,8 @@ trait SystemdManagerDbus {
219237
force: bool,
220238
) -> zbus::Result<(bool, Vec<(String, String, String)>)>;
221239

240+
fn get_unit_file_state(&self, file: String) -> zbus::Result<String>;
241+
222242
fn link_unit_files(
223243
&self,
224244
files: Vec<String>,

cli/src/tunnels/service_macos.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,11 @@ impl ServiceManager for LaunchdService {
7575
handle.run_service(self.log, launcher_paths).await
7676
}
7777

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+
7883
async fn unregister(&self) -> Result<(), crate::util::errors::AnyError> {
7984
let service_file = get_service_file_path()?;
8085

cli/src/tunnels/service_windows.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,11 @@ impl CliServiceManager for WindowsService {
114114
Ok(())
115115
}
116116

117+
async fn is_installed(&self) -> Result<bool, AnyError> {
118+
let key = WindowsService::open_key()?;
119+
Ok(key.get_raw_value(TUNNEL_ACTIVITY_NAME).is_ok())
120+
}
121+
117122
async fn unregister(&self) -> Result<(), AnyError> {
118123
let key = WindowsService::open_key()?;
119124
key.delete_value(TUNNEL_ACTIVITY_NAME)

cli/src/util/errors.rs

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -442,6 +442,26 @@ macro_rules! makeAnyError {
442442
};
443443
}
444444

445+
#[derive(Debug)]
446+
pub struct DbusConnectFailedError(pub String);
447+
448+
impl Display for DbusConnectFailedError {
449+
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
450+
let mut str = String::new();
451+
str.push_str("Error creating dbus session. This command uses systemd for managing services, you should check that systemd is installed and under your user.");
452+
453+
if std::env::var("WSL_DISTRO_NAME").is_ok() {
454+
str.push_str("\n\nTo enable systemd on WSL, check out: https://devblogs.microsoft.com/commandline/systemd-support-is-now-available-in-wsl/.\n\n");
455+
}
456+
457+
str.push_str("If running `systemctl status` works, systemd is ok, but your session dbus may not be. You might need to:\n\n- Install the `dbus-user-session` package, and reboot if it was not installed\n- Start the user dbus session with `systemctl --user enable dbus --now`.\n\nThe error encountered was: ");
458+
str.push_str(&self.0);
459+
str.push('\n');
460+
461+
write!(f, "{}", str)
462+
}
463+
}
464+
445465
/// Internal errors in the VS Code CLI.
446466
/// Note: other error should be migrated to this type gradually
447467
#[derive(Error, Debug)]
@@ -522,7 +542,8 @@ makeAnyError!(
522542
MissingHomeDirectory,
523543
OAuthError,
524544
InvalidRpcDataError,
525-
CodeError
545+
CodeError,
546+
DbusConnectFailedError
526547
);
527548

528549
impl From<reqwest::Error> for AnyError {

cli/src/util/tar.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use crate::util::errors::{wrap, WrappedError};
66

77
use flate2::read::GzDecoder;
88
use std::fs;
9-
use std::io::{Seek, SeekFrom};
9+
use std::io::Seek;
1010
use std::path::{Path, PathBuf};
1111
use tar::Archive;
1212

@@ -65,7 +65,7 @@ where
6565

6666
// reset since skip logic read the tar already:
6767
tar_gz
68-
.seek(SeekFrom::Start(0))
68+
.rewind()
6969
.map_err(|e| wrap(e, "error resetting seek position"))?;
7070

7171
let tar = GzDecoder::new(tar_gz);

0 commit comments

Comments
 (0)