Preserve Windows readonly file modes
ci / test (push) Canceled after 0s
ci / fuzz-smoke (push) Canceled after 0s
ci / windows-client (push) Canceled after 0s
ci / package-release (linux-x86_64, ubuntu-latest, , , ) (push) Canceled after 0s
ci / package-release (macos-aarch64, macos-14, , , ) (push) Canceled after 0s
ci / package-release (macos-x86_64, macos-13, , , ) (push) Canceled after 0s
ci / package-release (windows-aarch64, windows-latest, aarch64, windows, aarch64-pc-windows-msvc) (push) Canceled after 0s
ci / package-release (windows-x86_64, windows-latest, , , ) (push) Canceled after 0s
ci / remote-bench (push) Canceled after 0s
ci / publish-gitea-release (push) Canceled after 0s

This commit is contained in:
DuProcess
2026-07-16 20:00:17 -04:00
parent 53d31a88e1
commit 6eb0392aaa
+57 -3
View File
@@ -3811,7 +3811,19 @@ fn mode_for(path: &Path, preserve: bool) -> Result<Option<u32>> {
} }
} }
#[cfg(not(unix))] #[cfg(windows)]
fn mode_for(path: &Path, preserve: bool) -> Result<Option<u32>> {
if !preserve {
return Ok(None);
}
let metadata = fs::metadata(path)?;
Ok(Some(windows_mode_from_readonly(
metadata.is_dir(),
metadata.permissions().readonly(),
)))
}
#[cfg(not(any(unix, windows)))]
fn mode_for(_path: &Path, _preserve: bool) -> Result<Option<u32>> { fn mode_for(_path: &Path, _preserve: bool) -> Result<Option<u32>> {
Ok(None) Ok(None)
} }
@@ -3830,11 +3842,40 @@ fn set_local_mode(path: &Path, mode: Option<u32>, preserve: bool) -> Result<()>
fs::set_permissions(path, permissions).with_context(|| format!("chmod {}", path.display())) fs::set_permissions(path, permissions).with_context(|| format!("chmod {}", path.display()))
} }
#[cfg(not(unix))] #[cfg(windows)]
fn set_local_mode(path: &Path, mode: Option<u32>, preserve: bool) -> Result<()> {
if !preserve {
return Ok(());
}
let Some(mode) = mode else {
return Ok(());
};
let mut permissions = fs::metadata(path)?.permissions();
permissions.set_readonly(windows_readonly_from_mode(mode));
fs::set_permissions(path, permissions)
.with_context(|| format!("set readonly mode {}", path.display()))
}
#[cfg(not(any(unix, windows)))]
fn set_local_mode(_path: &Path, _mode: Option<u32>, _preserve: bool) -> Result<()> { fn set_local_mode(_path: &Path, _mode: Option<u32>, _preserve: bool) -> Result<()> {
Ok(()) Ok(())
} }
#[cfg_attr(not(any(test, windows)), allow(dead_code))]
fn windows_mode_from_readonly(is_dir: bool, readonly: bool) -> u32 {
match (is_dir, readonly) {
(true, true) => 0o555,
(true, false) => 0o777,
(false, true) => 0o444,
(false, false) => 0o666,
}
}
#[cfg_attr(not(any(test, windows)), allow(dead_code))]
fn windows_readonly_from_mode(mode: u32) -> bool {
mode & 0o222 == 0
}
fn parse_local_forwards(raw: &[String]) -> Result<Vec<LocalForward>> { fn parse_local_forwards(raw: &[String]) -> Result<Vec<LocalForward>> {
raw.iter().map(|value| parse_local_forward(value)).collect() raw.iter().map(|value| parse_local_forward(value)).collect()
} }
@@ -10100,7 +10141,7 @@ mod tests {
toml_bare_key_or_quoted, top_trace_events, trace_report_warnings, unix_update_script, toml_bare_key_or_quoted, top_trace_events, trace_report_warnings, unix_update_script,
update_binary_version_for_installer, update_installer_url, update_version_status, update_binary_version_for_installer, update_installer_url, update_version_status,
upsert_managed_block, valid_forward_host, vscode_safe_alias, wake_repaint_retry_deadline, upsert_managed_block, valid_forward_host, vscode_safe_alias, wake_repaint_retry_deadline,
windows_update_script, windows_mode_from_readonly, windows_readonly_from_mode, windows_update_script,
}; };
use dosh::config::{ClientConfig, CommandExtension, HostConfig}; use dosh::config::{ClientConfig, CommandExtension, HostConfig};
use dosh::native::EnvVar; use dosh::native::EnvVar;
@@ -10186,6 +10227,19 @@ mod tests {
assert!(local_symlink_target_is_dir(&link, "missing-dir\\")); assert!(local_symlink_target_is_dir(&link, "missing-dir\\"));
} }
#[test]
fn windows_mode_mapping_preserves_readonly_semantics() {
assert_eq!(windows_mode_from_readonly(false, true), 0o444);
assert_eq!(windows_mode_from_readonly(false, false), 0o666);
assert_eq!(windows_mode_from_readonly(true, true), 0o555);
assert_eq!(windows_mode_from_readonly(true, false), 0o777);
assert!(windows_readonly_from_mode(0o444));
assert!(windows_readonly_from_mode(0o555));
assert!(!windows_readonly_from_mode(0o644));
assert!(!windows_readonly_from_mode(0o755));
}
#[test] #[test]
fn cleanup_stream_state_removes_all_per_stream_state_only_for_target() { fn cleanup_stream_state_removes_all_per_stream_state_only_for_target() {
let stream_id = 42; let stream_id = 42;