fix: preserve non-UTF-8 bytes in OS, config and CLI layers

Zig treats paths, arguments and environment values as byte slices; the
port converted them lossily in many places. Now (final-Z1.md):
- argv, env (incl. present non-UTF-8 values), HOME, PATH lookup,
  passwd, resources dir, TMPDIR, cgroup, config path canonicalization,
  +validate-config, +list-themes and Zig-string escapes (\xFF) keep raw
  bytes. String-based config values carry bytes via ghostty_os::osbytes,
  a bijective escape (noncharacter U+FDD0 + byte) that leaves all valid
  UTF-8 - including Nerd Font private-use icons - unchanged.
- os/file rlimit fallbacks exist on all targets.
- i18n locale canonicalization is an exact port of libintl's tables
  (e.g. pa-Guru -> pa_IN).
- New ghostty-build-info crate is the single source of version, channel,
  commit and build mode for all consumers.

Test Plan:
- cargo test -p ghostty-os -p ghostty-config -p ghostty-cli
  (incl. randomized osbytes round-trip property tests)
This commit is contained in:
ddidderr committed 2026-10-12 08:14:02 +02:00
1 parent 3f3f6680a5
commit 8519f1ba1b
32 files changed
+1464 -333

No files matched your search

+39
View File
@@ -243,6 +243,28 @@ pub fn detect(args: &[String]) -> Result<Option<Action>, DetectError> {
detect_iter::<Action, _, _>(args.iter())
}
/// Convert raw OS arguments (Zig `[]const u8` argv) into the argument strings
/// used by the CLI layers. No bytes are lost: invalid UTF-8 bytes use the
/// reversible escape encoding of `ghostty_os::osbytes`, which the config
/// layer (`cli_args`) decodes back to the original bytes.
#[must_use]
pub fn os_args_to_strings(args: &[std::ffi::OsString]) -> Vec<String> {
args.iter()
.map(|a| ghostty_os::osbytes::os_to_config_string(a))
.collect()
}
/// [`detect`] over raw OS arguments.
pub fn detect_os(args: &[std::ffi::OsString]) -> Result<Option<Action>, DetectError> {
detect(&os_args_to_strings(args))
}
/// [`run_from_args`] over raw OS arguments (byte-preserving).
#[must_use]
pub fn run_from_os_args(args: &[std::ffi::OsString]) -> Option<u8> {
run_from_args(&os_args_to_strings(args))
}
/// The CLI entrypoint logic of `main_ghostty.zig` for actions: detect the
/// action from `args` (the full argv), print the errors Ghostty prints for
/// invalid invocations and run the action.
@@ -338,6 +360,23 @@ mod tests {
);
}
#[cfg(unix)]
#[test]
fn os_args_keep_non_utf8_bytes_and_still_dispatch() {
use std::os::unix::ffi::OsStringExt;
let args = vec![
std::ffi::OsString::from("ghostty"),
std::ffi::OsString::from_vec(b"--title=\xff".to_vec()),
std::ffi::OsString::from("+version"),
];
assert_eq!(Some(Action::Version), detect_os(&args).unwrap());
let strings = os_args_to_strings(&args);
assert_eq!(
ghostty_os::osbytes::config_str_to_bytes(&strings[1]),
b"--title=\xff"
);
}
#[test]
fn names_round_trip() {
for a in Action::ALL {