Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/cli/src/self_update.rs
3309 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::{fs, path::Path};
7
use tempfile::tempdir;
8
9
use crate::{
10
constants::{VSCODE_CLI_COMMIT, VSCODE_CLI_QUALITY},
11
options::Quality,
12
update_service::{unzip_downloaded_release, Platform, Release, TargetKind, UpdateService},
13
util::{
14
command::new_std_command,
15
errors::{wrap, AnyError, CodeError, CorruptDownload},
16
http,
17
io::{ReportCopyProgress, SilentCopyProgress},
18
},
19
};
20
21
pub struct SelfUpdate<'a> {
22
commit: &'static str,
23
quality: Quality,
24
platform: Platform,
25
update_service: &'a UpdateService,
26
}
27
28
static OLD_UPDATE_EXTENSION: &str = "Updating CLI";
29
30
impl<'a> SelfUpdate<'a> {
31
pub fn new(update_service: &'a UpdateService) -> Result<Self, AnyError> {
32
let commit = VSCODE_CLI_COMMIT
33
.ok_or_else(|| CodeError::UpdatesNotConfigured("unknown build commit"))?;
34
35
let quality = VSCODE_CLI_QUALITY
36
.ok_or_else(|| CodeError::UpdatesNotConfigured("no configured quality"))
37
.and_then(|q| {
38
Quality::try_from(q).map_err(|_| CodeError::UpdatesNotConfigured("unknown quality"))
39
})?;
40
41
let platform = Platform::env_default().ok_or_else(|| {
42
CodeError::UpdatesNotConfigured("Unknown platform, please report this error")
43
})?;
44
45
Ok(Self {
46
commit,
47
quality,
48
platform,
49
update_service,
50
})
51
}
52
53
/// Gets the current release
54
pub async fn get_current_release(&self) -> Result<Release, AnyError> {
55
self.update_service
56
.get_latest_commit(self.platform, TargetKind::Cli, self.quality)
57
.await
58
}
59
60
/// Gets whether the given release is what this CLI is built against
61
pub fn is_up_to_date_with(&self, release: &Release) -> bool {
62
release.commit == self.commit
63
}
64
65
/// Cleans up old self-updated binaries. Should be called with regularity.
66
/// May fail if old versions are still running.
67
pub fn cleanup_old_update(&self) -> Result<(), std::io::Error> {
68
let current_path = std::env::current_exe()?;
69
let old_path = current_path.with_extension(OLD_UPDATE_EXTENSION);
70
if old_path.exists() {
71
fs::remove_file(old_path)?;
72
}
73
74
Ok(())
75
}
76
77
/// Updates the CLI to the given release.
78
pub async fn do_update(
79
&self,
80
release: &Release,
81
progress: impl ReportCopyProgress,
82
) -> Result<(), AnyError> {
83
// 1. Download the archive into a temporary directory
84
let tempdir = tempdir().map_err(|e| wrap(e, "Failed to create temp dir"))?;
85
let stream = self.update_service.get_download_stream(release).await?;
86
let archive_path = tempdir.path().join(stream.url_path_basename().unwrap());
87
http::download_into_file(&archive_path, progress, stream).await?;
88
89
// 2. Unzip the archive and get the binary
90
let target_path =
91
std::env::current_exe().map_err(|e| wrap(e, "could not get current exe"))?;
92
let staging_path = target_path.with_extension(".update");
93
let archive_contents_path = tempdir.path().join("content");
94
// unzipping the single binary is pretty small and fast--don't bother with passing progress
95
unzip_downloaded_release(&archive_path, &archive_contents_path, SilentCopyProgress())?;
96
copy_updated_cli_to_path(&archive_contents_path, &staging_path)?;
97
98
// 3. Copy file metadata, make sure the new binary is executable\
99
copy_file_metadata(&target_path, &staging_path)
100
.map_err(|e| wrap(e, "failed to set file permissions"))?;
101
validate_cli_is_good(&staging_path)?;
102
103
// Try to rename the old CLI to the tempdir, where it can get cleaned up by the
104
// OS later. However, this can fail if the tempdir is on a different drive
105
// than the installation dir. In this case just rename it to ".old".
106
if fs::rename(&target_path, tempdir.path().join("old-code-cli")).is_err() {
107
fs::rename(
108
&target_path,
109
target_path.with_extension(OLD_UPDATE_EXTENSION),
110
)
111
.map_err(|e| wrap(e, "failed to rename old CLI"))?;
112
}
113
114
fs::rename(&staging_path, &target_path)
115
.map_err(|e| wrap(e, "failed to rename newly installed CLI"))?;
116
117
Ok(())
118
}
119
}
120
121
fn validate_cli_is_good(exe_path: &Path) -> Result<(), AnyError> {
122
let o = new_std_command(exe_path)
123
.args(["--version"])
124
.output()
125
.map_err(|e| CorruptDownload(format!("could not execute new binary, aborting: {e}")))?;
126
127
if !o.status.success() {
128
let msg = format!(
129
"could not execute new binary, aborting. Stdout:\n\n{}\n\nStderr:\n\n{}",
130
String::from_utf8_lossy(&o.stdout),
131
String::from_utf8_lossy(&o.stderr),
132
);
133
134
return Err(CorruptDownload(msg).into());
135
}
136
137
Ok(())
138
}
139
140
fn copy_updated_cli_to_path(unzipped_content: &Path, staging_path: &Path) -> Result<(), AnyError> {
141
let unzipped_files = fs::read_dir(unzipped_content)
142
.map_err(|e| wrap(e, "could not read update contents"))?
143
.collect::<Vec<_>>();
144
if unzipped_files.len() != 1 {
145
let msg = format!(
146
"expected exactly one file in update, got {}",
147
unzipped_files.len()
148
);
149
return Err(CorruptDownload(msg).into());
150
}
151
152
let archive_file = unzipped_files[0]
153
.as_ref()
154
.map_err(|e| wrap(e, "error listing update files"))?;
155
fs::copy(archive_file.path(), staging_path)
156
.map_err(|e| wrap(e, "error copying to staging file"))?;
157
Ok(())
158
}
159
160
#[cfg(target_os = "windows")]
161
fn copy_file_metadata(from: &Path, to: &Path) -> Result<(), std::io::Error> {
162
let permissions = from.metadata()?.permissions();
163
fs::set_permissions(to, permissions)?;
164
Ok(())
165
}
166
167
#[cfg(not(target_os = "windows"))]
168
fn copy_file_metadata(from: &Path, to: &Path) -> Result<(), std::io::Error> {
169
use std::os::unix::ffi::OsStrExt;
170
use std::os::unix::fs::MetadataExt;
171
172
let metadata = from.metadata()?;
173
fs::set_permissions(to, metadata.permissions())?;
174
175
// based on coreutils' chown https://github.com/uutils/coreutils/blob/72b4629916abe0852ad27286f4e307fbca546b6e/src/chown/chown.rs#L266-L281
176
let s = std::ffi::CString::new(to.as_os_str().as_bytes()).unwrap();
177
let ret = unsafe { libc::chown(s.as_ptr(), metadata.uid(), metadata.gid()) };
178
if ret != 0 {
179
return Err(std::io::Error::last_os_error());
180
}
181
182
Ok(())
183
}
184
185