Skip to content
Merged
Show file tree
Hide file tree
Changes from 54 commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
9066a22
feat(claw-code): implement unshare() syscall with CLONE_NEWUSER support
MuZhao2333 May 10, 2026
39590a0
feat(claw-code): implement /proc/self/{uid_map,gid_map,setgroups}
MuZhao2333 May 10, 2026
46d0804
test(claw-code): add uid_map procfs test case
MuZhao2333 May 10, 2026
5fafdac
test(claw-code): add gid_map procfs test case
MuZhao2333 May 10, 2026
3b2abdc
test(claw-code): add setgroups procfs test case
MuZhao2333 May 10, 2026
dad6958
chore(claw-code): fix cargo fmt formatting in proc.rs
MuZhao2333 May 10, 2026
7d6a15b
feat(claw-code): implement /proc/[pid]/cgroup
MuZhao2333 May 10, 2026
c5fba8e
test(claw-code): add integration test (smoke + diagnostic)
MuZhao2333 May 10, 2026
a2b5fd4
test(claw-code): switch integration test to Rust pipeline
MuZhao2333 May 11, 2026
98b4236
fix(claw-code): add [workspace] to integration test Cargo.toml
MuZhao2333 May 11, 2026
84eeb82
fix(claw-code)switch to x86
MuZhao2333 May 14, 2026
fa97f0e
fix(claw-code) epoll
MuZhao2333 May 15, 2026
c91d945
test(claw-code): add robust-01..11, use env vars for API credentials
MuZhao2333 May 16, 2026
5360b12
style: merge use alloc imports in mount.rs for rustfmt compliance
MuZhao2333 May 16, 2026
731f60e
chore: remove riscv64 config from claw-code integration test
MuZhao2333 May 16, 2026
2e16bfe
fix(claw-code): correct rootfs path in run-local.sh
MuZhao2333 May 16, 2026
c1d1e48
test(claw-code): add robust-12 multi-agent test
MuZhao2333 May 23, 2026
9cdcea7
fix(mount): remove extra closing brace from rebase conflict resolution
MuZhao2333 May 28, 2026
9aa4f46
style: fix cargo fmt formatting
MuZhao2333 May 28, 2026
fe600ad
feat(claw-code): register claw-code as StarryOS app
MuZhao2333 May 28, 2026
b5e1edd
fix(claw-code): gracefully skip tests when CLAW_API_KEY is not set
MuZhao2333 May 29, 2026
9ca6eac
merge: resolve CI workflow conflicts with upstream/dev
MuZhao2333 May 29, 2026
987833f
fix(claw-code): address PR review — add x86_64 goal configs, document…
MuZhao2333 May 29, 2026
6c294e1
style: fix cargo fmt formatting
MuZhao2333 May 29, 2026
6f09ca7
fix(claw-code): fix TOML escape in x86_64 goal config regex
MuZhao2333 May 29, 2026
f5206a4
fix(claw-code): use rcore-os/tgoskits release URL for claw binary
MuZhao2333 May 29, 2026
3d1ad39
fix(claw-code): use debugfs injection in prebuild.sh matching run-loc…
MuZhao2333 May 29, 2026
2f365f0
fix(claw-code): build claw from source instead of downloading pre-bui…
MuZhao2333 May 30, 2026
aeb0b88
fix(build-claw): remove broken CARGO_HOME override
MuZhao2333 May 30, 2026
dff6e82
fix(claw-code): use MuZhao2333/claw-code fork for source build
MuZhao2333 May 30, 2026
902d90d
fix(build.rs): parse only the last line of build-claw.sh output as bi…
MuZhao2333 May 30, 2026
eb43ed9
fix(claw-code): build claw in release mode to reduce binary size (avo…
MuZhao2333 May 30, 2026
ef4791b
fix(claw-code): add missing driver features to app build config, fix …
MuZhao2333 Jun 3, 2026
ac40e72
merge: resolve conflicts with upstream/dev (namespace rewrite)
MuZhao2333 Jun 3, 2026
ecc284b
fix(clippy): remove duplicate unshare match arm from merge
MuZhao2333 Jun 3, 2026
e530cfb
chore: retrigger CI
MuZhao2333 Jun 3, 2026
8a5f878
chore: retrigger CI
MuZhao2333 Jun 3, 2026
355ff4d
chore: retrigger CI
MuZhao2333 Jun 3, 2026
8a57c0f
fix(claw-code): review fixes — connect uid/gid_map to UserNamespace, …
MuZhao2333 Jun 4, 2026
bc1e0a4
fix(clippy): remove unnecessary mut on nsproxy
MuZhao2333 Jun 4, 2026
9a435f1
chore: retrigger CI
MuZhao2333 Jun 4, 2026
b84bb73
chore: retrigger CI
MuZhao2333 Jun 4, 2026
5020a13
fix(claw-code): split is_root into uid_mapped/gid_mapped, add syscall…
MuZhao2333 Jun 4, 2026
75b548b
fix(goal-04): rewrite setgroups test — do EPERM check before unshare,…
MuZhao2333 Jun 4, 2026
0250bf1
fix(goal-04): write newline-terminated strings to setgroups to match …
MuZhao2333 Jun 4, 2026
7d56acc
chore: retrigger CI
MuZhao2333 Jun 4, 2026
22ea44c
chore: retrigger CI
MuZhao2333 Jun 4, 2026
52e9f41
chore: retrigger CI
MuZhao2333 Jun 4, 2026
79efab0
chore: retrigger CI
MuZhao2333 Jun 4, 2026
f7e0e2c
chore: retrigger CI
MuZhao2333 Jun 4, 2026
587a0a5
fix(prebuild): remove manual rootfs copy — app framework handles it
MuZhao2333 Jun 4, 2026
b92c954
fix(prebuild): fall back to default workspace paths when STARRY_* env…
MuZhao2333 Jun 4, 2026
9094341
fix(qemu): add -snapshot to avoid rootfs write lock conflict
MuZhao2333 Jun 4, 2026
75d868d
fix(prebuild): inject claw into both alpine and claw-code rootfs imag…
MuZhao2333 Jun 4, 2026
93b2d97
fix(qemu): add file.locking=off to avoid QEMU file lock conflict
MuZhao2333 Jun 4, 2026
8c1a006
fix(prebuild): delete and recreate claw-code rootfs to avoid stale QE…
MuZhao2333 Jun 4, 2026
5afb33f
chore: retrigger CI
MuZhao2333 Jun 4, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -675,6 +675,8 @@ jobs:
main_pr_only: ${{ matrix.main_pr_only }}
fetch_depth: ${{ matrix.fetch_depth == 'full' && '0' || '1' }}
timeout_minutes: ${{ matrix.timeout_minutes || 360 }}
secrets:
CLAW_API_KEY: ${{ secrets.CLAW_API_KEY }}

publish_base_container:
name: Publish base container image
Expand Down
5 changes: 5 additions & 0 deletions .github/workflows/reusable-command.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ on:
required: false
type: number
default: 360
secrets:
CLAW_API_KEY:
required: false
jobs:
run_host:
if: >-
Expand Down Expand Up @@ -81,6 +84,7 @@ jobs:
- name: Run command
env:
STARRY_APK_REGION: ${{ inputs.apk_region }}
CLAW_API_KEY: ${{ secrets.CLAW_API_KEY }}
run: ${{ inputs.command }}

run_container:
Expand Down Expand Up @@ -152,4 +156,5 @@ jobs:
- name: Run command
env:
STARRY_APK_REGION: ${{ inputs.apk_region }}
CLAW_API_KEY: ${{ secrets.CLAW_API_KEY }}
run: ${{ inputs.command }}
14 changes: 14 additions & 0 deletions apps/starry/claw-code/build-x86_64-unknown-none.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
target = "x86_64-unknown-none"
env = {}
log = "Warn"
features = [
"ax-hal/x86-pc",
"qemu",
"ax-driver/plat-static",
"ax-driver/virtio-blk",
"ax-driver/virtio-net",
"ax-driver/virtio-gpu",
"ax-driver/virtio-input",
"ax-driver/virtio-socket",
]
plat_dyn = false
52 changes: 52 additions & 0 deletions apps/starry/claw-code/prebuild.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
#!/usr/bin/env bash
set -euo pipefail

CLAW_REPO="https://github.com/MuZhao2333/claw-code"
CACHE_DIR="${CLAW_CACHE_DIR:-${HOME}/.cache/claw-code-build}"
CLAW_SRC="$CACHE_DIR/repo"
TARGET="x86_64-unknown-linux-musl"
CLAW_BIN="$CACHE_DIR/claw"

WORKSPACE="${STARRY_WORKSPACE:-$(cd "$(dirname "${BASH_SOURCE[0]}")/../../.." && pwd)}"
ROOTFS_DIR="$WORKSPACE/tmp/axbuild/rootfs"
OVERLAY="${STARRY_OVERLAY_DIR:-$WORKSPACE/tmp/axbuild/starry-app/claw-code/overlay}"

echo "=== 1. Build claw from source ==="
if [ -f "$CLAW_BIN" ]; then
echo "claw binary cached at $CLAW_BIN"
else
mkdir -p "$CACHE_DIR"
rustup target add "$TARGET" 2>/dev/null || true
if [ ! -d "$CLAW_SRC" ]; then
echo "Cloning $CLAW_REPO ..."
git clone --depth 1 "$CLAW_REPO" "$CLAW_SRC"
fi
echo "Building claw for $TARGET (this may take a while)..."
(
cd "$CLAW_SRC/rust"
cargo build --workspace --release --target "$TARGET" --target-dir "$CACHE_DIR/target"
)
cp "$CACHE_DIR/target/$TARGET/release/claw" "$CLAW_BIN"
chmod +x "$CLAW_BIN"
echo "claw binary built: $CLAW_BIN"
fi

echo "=== 2. Inject claw into rootfs ==="
# Inject into all possible rootfs images so both older and newer xtask flows work.
inject_claw() {
local img="$1"
if [ -f "$img" ]; then
echo " injecting into $img ..."
debugfs -w "$img" -R "rm /usr/bin/claw" 2>/dev/null || true
debugfs -w "$img" -R "write $CLAW_BIN /usr/bin/claw"
debugfs -w "$img" -R "sif /usr/bin/claw mode 0100755"
fi
}
inject_claw "$ROOTFS_DIR/rootfs-x86_64-alpine.img"
inject_claw "$ROOTFS_DIR/rootfs-x86_64-claw-code.img"

echo "Injected claw into rootfs"

# Place a marker so the overlay is never empty (app framework requires it).
mkdir -p "$OVERLAY"
touch "${OVERLAY}/.claw-injected"
35 changes: 35 additions & 0 deletions apps/starry/claw-code/qemu-x86_64.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
timeout = 0
shell_prefix = "root@starry:"
uefi = false
to_bin = false
success_regex = []
fail_regex = []

args = [
"-nographic",
"-m", "512M",
"-device", "virtio-blk-pci,drive=disk0",
"-drive", "id=disk0,if=none,format=raw,file=${workspace}/tmp/axbuild/rootfs/rootfs-x86_64-claw-code.img",
"-device", "virtio-net-pci,netdev=net0",
"-netdev", "user,id=net0",
"-snapshot",
]

shell_init_cmd = """
export HOME=/root
export USER=root
export SHELL=/bin/sh
export TERM=xterm-256color
export PATH=/usr/local/bin:/usr/bin:/bin:/sbin
cat <<'STARRY_CLAW_USAGE'

StarryOS Claw interactive shell is ready.

常用命令:
claw

退出 QEMU:
Ctrl-a x

STARRY_CLAW_USAGE
"""
24 changes: 18 additions & 6 deletions os/StarryOS/axnsproxy/src/user.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,30 +14,42 @@ pub static ROOT_USER_NS: spin::LazyLock<Arc<SpinNoIrq<UserNamespace>>> =
/// `owner_uid` is the effective UID of the process that created the
/// namespace (0 for the root namespace).
///
/// When `is_root` is false (child namespace without configured mappings),
/// all UIDs/GIDs are reported as `nobody` (65534), matching Linux default
/// behaviour for unconfigured user namespaces.
/// When neither `uid_mapped` nor `gid_mapped` is true (child namespace
/// without configured mappings), all UIDs/GIDs are reported as `nobody`
/// (65534), matching Linux default behaviour for unconfigured user
/// namespaces. Writing uid_map sets `uid_mapped`, writing gid_map sets
/// `gid_mapped`, so the two sides are independent — a half-configured
/// namespace correctly returns 65534 for the unmapped side.
pub struct UserNamespace {
/// Effective UID of the namespace creator (0 for root namespace).
pub owner_uid: u32,
/// Whether this is the initial root user namespace. false for
/// namespaces created via unshare / clone(CLONE_NEWUSER) that
/// have not yet had uid_map / gid_map configured.
/// Whether this is the initial root user namespace or a child
/// namespace that had both uid_map and gid_map configured. Kept
/// for compatibility; new code should use `uid_mapped` /
/// `gid_mapped` for per-side overflow control.
pub is_root: bool,
/// Whether uid_map has been written — lifts UID-side overflow.
pub uid_mapped: bool,
/// Whether gid_map has been written — lifts GID-side overflow.
pub gid_mapped: bool,
}

impl UserNamespace {
pub fn new_root() -> Self {
Self {
owner_uid: 0,
is_root: true,
uid_mapped: true,
gid_mapped: true,
}
}

pub fn clone_ns(&self) -> Self {
Self {
owner_uid: self.owner_uid,
is_root: false,
uid_mapped: false,
gid_mapped: false,
}
}
}
131 changes: 130 additions & 1 deletion os/StarryOS/kernel/src/pseudofs/proc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ use crate::{
SimpleFileOperation, SimpleFs, SpecialFsFile,
},
task::{
AsThread, ProcessData, TaskStat, get_process_data, get_task, processes, tasks,
AsThread, ProcessData, TaskStat, Thread, get_process_data, get_task, processes, tasks,
tick_cpu_time,
},
};
Expand Down Expand Up @@ -762,6 +762,10 @@ impl SimpleDirOps for ThreadDir {
"comm",
"exe",
"fd",
"uid_map",
"gid_map",
"setgroups",
"cgroup",
"ns",
]
.into_iter()
Expand Down Expand Up @@ -901,6 +905,131 @@ impl SimpleDirOps for ThreadDir {
}),
)
.into(),
"uid_map" => SimpleFile::new_regular(
fs,
RwFile::new(move |req| match req {
SimpleFileOperation::Read => {
let thr = task.as_thread();
let cred = thr.cred();
let content = if thr.uid_map_written() || cred.euid != 65534 {
format!(" 0 {:>10} 4294967295\n", cred.uid)
} else {
"\n".to_string()
};
Ok(Some(content.into_bytes()))
}
SimpleFileOperation::Write(data) => {
let input =
core::str::from_utf8(data).map_err(|_| VfsError::InvalidInput)?;
// Linux uid_map format: <lower_uid> <upper_uid> <count>
// Maps UIDs in the parent namespace (lower_uid..lower_uid+count)
// to UIDs in this namespace (upper_uid..upper_uid+count).
//
// StarryOS simplified semantics: we do not maintain namespace
// UID mappings; instead we directly set the thread's credentials
// to the upper_uid value (the UID this namespace wants to see).
// For the common `0 0 1` case (map root to root) this is correct.
// For non-trivial mappings this is an intentional simplification
// — StarryOS does not implement full user namespacing.
let parts: Vec<&str> = input.split_whitespace().collect();
if parts.len() >= 3 {
let _mapped: u32 =
parts[0].parse().map_err(|_| VfsError::InvalidInput)?;
let orig: u32 = parts[1].parse().map_err(|_| VfsError::InvalidInput)?;
Comment thread
ZR233 marked this conversation as resolved.
let _count: u32 =
parts[2].parse().map_err(|_| VfsError::InvalidInput)?;
let thr = task.as_thread();
let mut cred = (*thr.cred()).clone();
cred.uid = orig;
Comment thread
ZR233 marked this conversation as resolved.
cred.euid = orig;
cred.suid = orig;
cred.fsuid = orig;
Thread::set_cred(thr, cred);
thr.set_uid_map_written(true);
Comment thread
ZR233 marked this conversation as resolved.
// Mark the user namespace as UID-mapped so
// getuid/geteuid/getresuid return the mapped
// value instead of 65534 (nobody).
let proc_data = &thr.proc_data;
let nsproxy = proc_data.nsproxy.lock();
nsproxy.user_ns.lock().uid_mapped = true;
}
Ok(None)
}
}),
)
.into(),
"gid_map" => SimpleFile::new_regular(
fs,
RwFile::new(move |req| match req {
SimpleFileOperation::Read => {
let thr = task.as_thread();
let cred = thr.cred();
let content = if thr.gid_map_written() || cred.egid != 65534 {
format!(" 0 {:>10} 4294967295\n", cred.gid)
} else {
"\n".to_string()
};
Ok(Some(content.into_bytes()))
}
SimpleFileOperation::Write(data) => {
let input =
core::str::from_utf8(data).map_err(|_| VfsError::InvalidInput)?;
// Linux gid_map format: <lower_gid> <upper_gid> <count>
// Same simplified semantics as uid_map above.
//
// StarryOS does not maintain namespace GID mappings;
// it directly sets the thread's credentials to upper_gid.
let parts: Vec<&str> = input.split_whitespace().collect();
Comment thread
ZR233 marked this conversation as resolved.
if parts.len() >= 3 {
let _mapped: u32 =
parts[0].parse().map_err(|_| VfsError::InvalidInput)?;
let orig: u32 = parts[1].parse().map_err(|_| VfsError::InvalidInput)?;
let _count: u32 =
parts[2].parse().map_err(|_| VfsError::InvalidInput)?;
let thr = task.as_thread();
let mut cred = (*thr.cred()).clone();
cred.gid = orig;
cred.egid = orig;
cred.sgid = orig;
cred.fsgid = orig;
Thread::set_cred(thr, cred);
thr.set_gid_map_written(true);
let proc_data = &thr.proc_data;
let nsproxy = proc_data.nsproxy.lock();
nsproxy.user_ns.lock().gid_mapped = true;
}
Ok(None)
}
}),
)
.into(),
"setgroups" => SimpleFile::new_regular(
fs,
RwFile::new(move |req| match req {
SimpleFileOperation::Read => {
let thr = task.as_thread();
let content = if thr.setgroups_deny() {
"deny\n"
} else {
"allow\n"
};
Ok(Some(content.as_bytes().to_vec()))
}
SimpleFileOperation::Write(data) => {
let input = core::str::from_utf8(data)
.map_err(|_| VfsError::InvalidInput)?
.trim();
if input == "deny" {
task.as_thread().set_setgroups_deny(true);
Comment thread
ZR233 marked this conversation as resolved.
} else if input == "allow" {
task.as_thread().set_setgroups_deny(false);
}
Ok(None)
}
}),
)
.into(),
"cgroup" => SimpleFile::new_regular(fs, move || Ok("0::/\n")).into(),
"ns" => SimpleDir::new_maker(
fs.clone(),
Arc::new(NsDir {
Expand Down
8 changes: 6 additions & 2 deletions os/StarryOS/kernel/src/syscall/fs/mount.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use alloc::sync::Arc;
use alloc::{string::String, sync::Arc};
use core::ffi::{c_char, c_void};

use ax_errno::{AxError, AxResult, LinuxError};
Expand Down Expand Up @@ -70,7 +70,11 @@ pub fn sys_mount(
) -> AxResult<isize> {
let source = vm_load_string(source)?;
let target = vm_load_string(target)?;
let fs_type = vm_load_string(fs_type)?;
let fs_type = if fs_type.is_null() {
String::new()
} else {
vm_load_string(fs_type)?
};
debug!("sys_mount <= source: {source:?}, target: {target:?}, fs_type: {fs_type:?}");

let propagation = flags & PROPAGATION_FLAGS;
Expand Down
Loading
Loading