mirror of
https://github.com/zyycn/codex-proxy-rs.git
synced 2026-11-06 08:06:19 +00:00
fix(update): keep read-only official plugin files during extract (#595)
* fix(update): keep read-only official plugin files during extract Docker Desktop returns EPERM when chmod runs again on an official plugin file that tar already unpacked as mode 0444. Fixes #594 * test(update): cover writable official plugin archives Co-authored-by: Codex <noreply@openai.com> --------- Co-authored-by: le-czs <243511553+le-czs@users.noreply.github.com> Co-authored-by: Zyy <zyycn@users.noreply.github.com> Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
co-authored by
Codex
le-czs
Zyy
parent
aa003f7d90
commit
28eb4708b5
@@ -86,11 +86,7 @@ pub(crate) fn extract_release(
|
||||
.unpack(&target)
|
||||
.map_err(|error| internal("无法解压官方插件文件").with_source(error))?;
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use std::os::unix::fs::PermissionsExt as _;
|
||||
fs::set_permissions(&target, fs::Permissions::from_mode(0o444))
|
||||
.map_err(|error| internal("无法设置官方插件文件权限").with_source(error))?;
|
||||
}
|
||||
ensure_official_plugin_file_readonly(&target)?;
|
||||
found_official_manifest |= relative == Path::new("plugin-release-manifest.json");
|
||||
continue;
|
||||
}
|
||||
@@ -128,6 +124,24 @@ pub(crate) fn extract_release(
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
fn ensure_official_plugin_file_readonly(path: &Path) -> Result<(), OperationError> {
|
||||
use std::os::unix::fs::PermissionsExt as _;
|
||||
|
||||
// tar 解包已对打开的文件描述符套用归档模式
|
||||
// Docker Desktop 把 macOS 目录挂进容器后,0444 文件再次 chmod 会返回 EPERM,权限已满足时不必再改
|
||||
let current_mode = fs::symlink_metadata(path)
|
||||
.map_err(|error| internal("无法设置官方插件文件权限").with_source(error))?
|
||||
.permissions()
|
||||
.mode()
|
||||
& 0o777;
|
||||
if current_mode == 0o444 {
|
||||
return Ok(());
|
||||
}
|
||||
fs::set_permissions(path, fs::Permissions::from_mode(0o444))
|
||||
.map_err(|error| internal("无法设置官方插件文件权限").with_source(error))
|
||||
}
|
||||
|
||||
fn unsafe_archive_path(path: &Path) -> bool {
|
||||
path.is_absolute()
|
||||
|| path.components().any(|component| match component {
|
||||
|
||||
@@ -419,13 +419,14 @@ enum ChecksumKind {
|
||||
fn release_archive(kind: ArchiveKind) -> Vec<u8> {
|
||||
let encoder = GzEncoder::new(Vec::new(), Compression::default());
|
||||
let mut tar = Builder::new(encoder);
|
||||
append_file(&mut tar, BINARY_NAME, b"new-binary", false);
|
||||
append_file(&mut tar, "web/dist/index.html", b"new-web", false);
|
||||
append_file(&mut tar, BINARY_NAME, b"new-binary", 0o755, false);
|
||||
append_file(&mut tar, "web/dist/index.html", b"new-web", 0o755, false);
|
||||
if !matches!(kind, ArchiveKind::MissingOfficialManifest) {
|
||||
append_file(
|
||||
&mut tar,
|
||||
"plugins/official/plugin-release-manifest.json",
|
||||
b"new-manifest",
|
||||
0o444,
|
||||
false,
|
||||
);
|
||||
}
|
||||
@@ -433,16 +434,18 @@ fn release_archive(kind: ArchiveKind) -> Vec<u8> {
|
||||
&mut tar,
|
||||
"plugins/official/new-plugin.tar.gz",
|
||||
b"new-plugin",
|
||||
0o644,
|
||||
false,
|
||||
);
|
||||
if let ArchiveKind::UnsafePath(name) = kind {
|
||||
append_file(&mut tar, name, b"escape", true);
|
||||
append_file(&mut tar, name, b"escape", 0o755, true);
|
||||
}
|
||||
if matches!(kind, ArchiveKind::NestedOfficialAsset) {
|
||||
append_file(
|
||||
&mut tar,
|
||||
"plugins/official/nested/plugin.tar.gz",
|
||||
b"nested",
|
||||
0o444,
|
||||
false,
|
||||
);
|
||||
}
|
||||
@@ -468,14 +471,16 @@ fn append_symlink(tar: &mut Builder<GzEncoder<Vec<u8>>>, name: &str, target: &st
|
||||
tar.append(&header, std::io::empty()).expect("append link");
|
||||
}
|
||||
|
||||
fn append_file(tar: &mut Builder<GzEncoder<Vec<u8>>>, name: &str, data: &[u8], unsafe_path: bool) {
|
||||
fn append_file(
|
||||
tar: &mut Builder<GzEncoder<Vec<u8>>>,
|
||||
name: &str,
|
||||
data: &[u8],
|
||||
mode: u32,
|
||||
unsafe_path: bool,
|
||||
) {
|
||||
let mut header = Header::new_gnu();
|
||||
header.set_entry_type(EntryType::Regular);
|
||||
header.set_mode(if name.starts_with("plugins/official/") {
|
||||
0o444
|
||||
} else {
|
||||
0o755
|
||||
});
|
||||
header.set_mode(mode);
|
||||
header.set_size(u64::try_from(data.len()).expect("size"));
|
||||
header
|
||||
.set_path(if unsafe_path { "safe" } else { name })
|
||||
|
||||
@@ -471,6 +471,17 @@ async fn rollback_should_restore_binary_web_and_version_state() {
|
||||
& 0o777,
|
||||
0o555
|
||||
);
|
||||
for name in ["plugin-release-manifest.json", "new-plugin.tar.gz"] {
|
||||
assert_eq!(
|
||||
fs::metadata(fixture.official().join(name))
|
||||
.expect("official plugin file")
|
||||
.permissions()
|
||||
.mode()
|
||||
& 0o777,
|
||||
0o444,
|
||||
"{name}"
|
||||
);
|
||||
}
|
||||
}
|
||||
service
|
||||
.rollback(Arc::new(AllowingUpdatePreflight))
|
||||
|
||||
Reference in New Issue
Block a user