recipes: adopt brush shell into local/recipes with Redox port patches

Port the brush shell (reubeno/brush) to Redox and adopt the recipe as a
Red Bear local recipe under local/recipes/shells/brush, with the standard
recipes/shells/brush -> ../../local/recipes/shells/brush overlay symlink so
it takes priority over any upstream WIP copy (per local/AGENTS.md "Local
recipe priority vs upstream WIP"). Removes the old recipes/wip/shells/brush.

The dependency tree did not build for Redox: nix 0.31 only partially
cfg-enables Redox and the libc crate's Redox module omits POSIX symbols that
relibc provides. Fixes are reviewable unified diffs under
local/recipes/shells/brush/patches/:

  nix-0.31-redox.patch   cfg-enable resource/Id/waitid/from_siginfo, rlimit
                         import + repr(i32), SaFlags width casts, pty.
  libc-0.2-redox.patch   add idtype_t, P_*/CLD_*, rusage, getrusage/waitid/
                         forkpty externs, siginfo child accessors.
  brush-umask-redox.patch  mode_t is signed on Redox; cast, not u32::from.

The recipe runs `cargo fetch` to materialise registry sources before
patching (they are only unpacked during the build), resolves the right
crate version from Cargo.lock (brush pulls both nix 0.26 and 0.31), and
applies each patch idempotently, failing loudly on version drift. The
binary target is named `brush` (not `brush-shell`), so install it
explicitly. Verified: cooks from a pristine cargo cache using only the
patch files. brush is shipped in redbear-mini as a package for in-image
validation; the login shell stays zsh until brush is runtime-proven.
This commit is contained in:
2026-07-16 23:23:37 +09:00
parent 81a1d7d565
commit 9c97cefc57
8 changed files with 276 additions and 12 deletions
+1
View File
@@ -74,3 +74,4 @@ sources/x86_64-unknown-redox/
sources/*.tar.gz
Packages/*.pkgar
.slim/deepwork/
local/recipes/shells/brush/source
+4
View File
@@ -49,6 +49,10 @@ tlc = {}
#mc = {}
redbear-info = {}
# brush: Rust shell, candidate default login shell (validated in-image before
# switching the [users.*] shell over from zsh).
brush = {}
# Keep package builder utility in live environment.
cub = {}
cpufreqd = {}
@@ -0,0 +1,14 @@
--- a/brush-builtins/src/umask.rs
+++ b/brush-builtins/src/umask.rs
@@ -68,7 +68,10 @@
fn get_umask() -> Result<u32, brush_core::Error> {
let u = nix::sys::stat::umask(Mode::empty());
nix::sys::stat::umask(u);
- Ok(u32::from(u.bits()))
+ // mode_t is signed (c_int) on Redox and some other targets, so
+ // `u32::from` (widening only) does not apply; a value-preserving cast
+ // covers signed and unsigned mode_t alike.
+ Ok(u.bits() as u32)
}
}
}
@@ -0,0 +1,89 @@
--- a/src/unix/redox/mod.rs
+++ b/src/unix/redox/mod.rs
@@ -1289,6 +1289,12 @@
termp: *const termios,
winp: *const crate::winsize,
) -> c_int;
+ pub fn forkpty(
+ amaster: *mut c_int,
+ name: *mut c_char,
+ termp: *const termios,
+ winp: *const crate::winsize,
+ ) -> c_int;
// pwd.h
pub fn getpwent() -> *mut passwd;
@@ -1385,6 +1391,13 @@
pub fn setpriority(which: c_int, who: crate::id_t, prio: c_int) -> c_int;
pub fn getrlimit(resource: c_int, rlim: *mut crate::rlimit) -> c_int;
pub fn setrlimit(resource: c_int, rlim: *const crate::rlimit) -> c_int;
+ pub fn getrusage(resource: c_int, usage: *mut crate::rusage) -> c_int;
+ pub fn waitid(
+ idtype: crate::idtype_t,
+ id: crate::id_t,
+ infop: *mut siginfo_t,
+ options: c_int,
+ ) -> c_int;
// sys/socket.h
pub fn CMSG_DATA(cmsg: *const cmsghdr) -> *mut c_uchar;
@@ -1431,3 +1444,59 @@
// utmp.h
pub fn login_tty(fd: c_int) -> c_int;
}
+
+// REDBEAR_REDOX_WAITID: symbols relibc provides but the libc crate omits.
+pub type idtype_t = c_uint;
+
+// waitid idtype_t values.
+pub const P_ALL: idtype_t = 0;
+pub const P_PID: idtype_t = 1;
+pub const P_PGID: idtype_t = 2;
+
+// siginfo_t.si_code values (child status change).
+pub const CLD_EXITED: c_int = 1;
+pub const CLD_KILLED: c_int = 2;
+pub const CLD_DUMPED: c_int = 3;
+pub const CLD_TRAPPED: c_int = 4;
+pub const CLD_STOPPED: c_int = 5;
+pub const CLD_CONTINUED: c_int = 6;
+
+s! {
+ pub struct rusage {
+ pub ru_utime: crate::timeval,
+ pub ru_stime: crate::timeval,
+ pub ru_maxrss: c_long,
+ pub ru_ixrss: c_long,
+ pub ru_idrss: c_long,
+ pub ru_isrss: c_long,
+ pub ru_minflt: c_long,
+ pub ru_majflt: c_long,
+ pub ru_nswap: c_long,
+ pub ru_inblock: c_long,
+ pub ru_oublock: c_long,
+ pub ru_msgsnd: c_long,
+ pub ru_msgrcv: c_long,
+ pub ru_nsignals: c_long,
+ pub ru_nvcsw: c_long,
+ pub ru_nivcsw: c_long,
+ }
+}
+
+impl siginfo_t {
+ // relibc's siginfo layout places these fields after the leading
+ // si_signo/si_errno/si_code triple: si_pid at c_int index 3 (byte 12),
+ // si_uid at index 4 (byte 16), si_status at index 8 (byte 32). Read them
+ // out of the padding region to match.
+ pub unsafe fn si_pid(&self) -> crate::pid_t {
+ let p = self as *const siginfo_t as *const c_int;
+ unsafe { *p.offset(3) }
+ }
+ pub unsafe fn si_uid(&self) -> crate::uid_t {
+ let p = self as *const siginfo_t as *const c_int;
+ unsafe { *p.offset(4) as crate::uid_t }
+ }
+ pub unsafe fn si_status(&self) -> c_int {
+ let p = self as *const siginfo_t as *const c_int;
+ unsafe { *p.offset(8) }
+ }
+}
@@ -0,0 +1,89 @@
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -164,7 +164,7 @@
#![feature = "poll"]
pub mod poll;
}
-#[cfg(not(any(target_os = "redox", target_os = "fuchsia")))]
+#[cfg(not(target_os = "fuchsia"))]
feature! {
#![feature = "term"]
#[deny(missing_docs)]
--- a/src/sys/mod.rs
+++ b/src/sys/mod.rs
@@ -95,7 +95,6 @@
}
#[cfg(not(any(
- target_os = "redox",
target_os = "fuchsia",
target_os = "solaris",
target_os = "haiku"
--- a/src/sys/signal.rs
+++ b/src/sys/signal.rs
@@ -856,7 +856,7 @@
(*p).sa_flags = match handler {
#[cfg(not(target_os = "redox"))]
SigHandler::SigAction(_) => (flags | SaFlags::SA_SIGINFO).bits(),
- _ => (flags - SaFlags::SA_SIGINFO).bits(),
+ _ => (flags - SaFlags::SA_SIGINFO).bits() as _,
};
(*p).sa_mask = mask.sigset;
@@ -866,7 +866,7 @@
/// Returns the flags set on the action.
pub fn flags(&self) -> SaFlags {
- SaFlags::from_bits_truncate(self.sigaction.sa_flags)
+ SaFlags::from_bits_truncate(self.sigaction.sa_flags as _)
}
/// Returns the set of signals that are blocked during execution of the action's
--- a/src/sys/resource.rs
+++ b/src/sys/resource.rs
@@ -21,7 +21,8 @@
target_os = "aix",
target_os = "illumos",
all(target_os = "linux", not(target_env = "gnu")),
- target_os = "cygwin"
+ target_os = "cygwin",
+ target_os = "redox"
))]{
use libc::rlimit;
}
@@ -53,7 +54,8 @@
target_os = "aix",
target_os = "illumos",
all(target_os = "linux", not(any(target_env = "gnu", target_env = "uclibc"))),
- target_os = "cygwin"
+ target_os = "cygwin",
+ target_os = "redox"
), repr(i32))]
#[non_exhaustive]
pub enum Resource {
--- a/src/sys/wait.rs
+++ b/src/sys/wait.rs
@@ -242,6 +242,7 @@
target_os = "android",
target_os = "freebsd",
target_os = "haiku",
+ target_os = "redox",
all(target_os = "linux", not(target_env = "uclibc")),
))]
unsafe fn from_siginfo(siginfo: &libc::siginfo_t) -> Result<WaitStatus> {
@@ -327,6 +328,7 @@
target_os = "android",
target_os = "freebsd",
target_os = "haiku",
+ target_os = "redox",
all(target_os = "linux", not(target_env = "uclibc")),
))]
#[derive(Debug)]
@@ -355,6 +357,7 @@
target_os = "android",
target_os = "freebsd",
target_os = "haiku",
+ target_os = "redox",
all(target_os = "linux", not(target_env = "uclibc")),
))]
pub fn waitid(id: Id, flags: WaitPidFlag) -> Result<WaitStatus> {
+78
View File
@@ -0,0 +1,78 @@
[package]
name = "brush"
version = "0.1.0"
[source]
git = "https://github.com/reubeno/brush"
[build]
template = "custom"
script = """
# ---------------------------------------------------------------------------
# RedBear Redox port for brush.
#
# brush pulls `nix` 0.31.x and `libc` 0.2.x, neither of which builds for Redox
# as-is: upstream nix only partially cfg-enables Redox, and the `libc` crate's
# Redox module omits several POSIX symbols that relibc actually provides. The
# fixes live as reviewable unified diffs under patches/:
#
# patches/nix-0.31-redox.patch cfg-enable resource/Id/waitid/from_siginfo,
# rlimit import + repr(i32), SaFlags width
# casts, and pty for Redox.
# patches/libc-0.2-redox.patch add idtype_t, P_*/CLD_*, rusage, getrusage
# + waitid externs, siginfo child accessors.
# patches/brush-umask-redox.patch mode_t is signed on Redox; use a cast
# instead of u32::from (widening-only).
#
# Registry dependency sources are only unpacked during the build, so we run
# `cargo fetch` first to materialise them, then patch. apply_patch is
# idempotent (skips if already applied) but fails loudly if a patch does not
# apply cleanly -- the intended signal that a version bump moved the code.
# ---------------------------------------------------------------------------
PATCHES="${COOKBOOK_RECIPE}/patches"
# Materialise all dependency sources into the cargo registry before patching.
cargo fetch --manifest-path "${COOKBOOK_SOURCE}/Cargo.toml"
# Resolve the extracted registry directory for a crate whose Cargo.lock version
# begins with the given prefix (brush may depend on several majors of the same
# crate, e.g. nix 0.26 and 0.31; the patch targets one specific series).
crate_dir() {
local name="$1" prefix="$2" ver
ver="$(awk -v n="name = \\"$1\\"" -v p="$2" '
$0 == n { getline; gsub(/[^0-9.]/, "", $0);
if (index($0, p) == 1) { print; exit } }
' "${COOKBOOK_SOURCE}/Cargo.lock")"
[ -n "$ver" ] || { echo "crate_dir: ${name} ${prefix}* not found in Cargo.lock" >&2; return 1; }
local d
for d in "${HOME}/.cargo/registry/src/"*/"${name}-${ver}"; do
[ -d "$d" ] && { echo "$d"; return 0; }
done
echo "crate_dir: ${name}-${ver} not extracted" >&2
return 1
}
# apply_patch <target_dir> <patch_file>: apply forward, or skip if the patch is
# already applied (detected via a clean reverse dry-run). Any other failure is
# fatal.
apply_patch() {
local dir="$1" patch="$2"
if patch -p1 -R --dry-run -f -d "$dir" < "$patch" >/dev/null 2>&1; then
echo "apply_patch: $(basename "$patch") already applied in $dir, skipping"
return 0
fi
echo "apply_patch: applying $(basename "$patch") in $dir"
patch -p1 -f -d "$dir" < "$patch"
}
apply_patch "$(crate_dir nix 0.31)" "${PATCHES}/nix-0.31-redox.patch"
apply_patch "$(crate_dir libc 0.2)" "${PATCHES}/libc-0.2-redox.patch"
apply_patch "${COOKBOOK_SOURCE}" "${PATCHES}/brush-umask-redox.patch"
# The shell lives in the `brush-shell` package but its binary target is named
# `brush` (see brush-shell/Cargo.toml `[[bin]] name = "brush"`), so
# cookbook_cargo_packages (which copies a binary matching the package name)
# cannot find it. Build the package explicitly and install the `brush` binary.
bin_name="brush" bin_flags="--package brush-shell --bin brush" bin_final_name="brush" cookbook_cargo_build
"""
+1
View File
@@ -0,0 +1 @@
../../local/recipes/shells/brush
-12
View File
@@ -1,12 +0,0 @@
[package]
name = "brush"
version = "0.1.0"
#TODO redox is not supported by the procfs crate
[source]
git = "https://github.com/reubeno/brush"
[build]
template = "custom"
script = """
cookbook_cargo_packages brush-shell
"""