cloudfox-coreshift-core 2.0.0

Low-level Linux and Android systems primitives for CoreShift (CloudFox)
Documentation
# CoreShift-Core — Restructuring Plan

> **Status: executed (steps 1–5c completed).** Target layout for the public API
> cleanup. Mirrors the `RESTRUCTURE.md` executed in CoreShift-FocusSource.
> **Renames are allowed**; the two consumers (CoreShift-FocusSource,
> CoreShift-FocusD) are updated in lockstep in the same pass. Step 5b (the
> binder monolith physical split) is deferred — the API renames and DisplayInfo
> work landed; splitting the 2000-line unsafe FFI file is a lower-risk,
> later change.

Goal: make `cloudfox-coreshift-core`'s public API coherent — one error type,
one fd type, no split-brain modules, and no misleading names — while keeping
the crate a policy-neutral primitives library. This is a *surface* restructure:
module grouping + naming + error/fd unification. No behavior change.

## Current pain points

1. **Split-brain modules.** `proc` + `uid` both parse `/proc/<pid>/status`
   (`read_proc_status`/`read_proc_cmdline` vs `proc_uid`/`proc_stat`/
   `proc_stat_at`); `fs` + `safe_fs` are both filesystem primitives
   (`read_to_string` vs `read_nofollow`, `path_exists` vs `ensure_state_dir`);
   `process` (raw `fork`/`setuid` syscalls) and `spawn` (`Process`,
   `SpawnOptions`) are the same concern split with no stated boundary.
2. **Two error models.** `safe_fs` and `android_property` return `io::Result`
   (32 occurrences) while everything else returns `Result<_, CoreError>`.
   Callers can't write one error-handling path.
3. **Two fd types.** Binder methods return `std::os::fd::OwnedFd`
   (`open_with_observer`, `FpsListener::open`, `TaskStackListener::open`) while
   every other module returns `reactor::Fd`. `UnixListenerFd`/`UnixStreamFd`
   additionally expose `pub fd: Fd`, leaking the wrapper.
4. **Misleading / inconsistent names.**
   - `netlink` module contains only kobject-uevent socket helpers.
   - `android_property` repeats the `android_` prefix on every function
     (`android_property_get`, `android_property_set`, …) — a module should say
     that once.
   - `SignalRuntime::signalfd_new` vs `Reactor::setup_signalfd` — same concept,
     two names.
   - `log` exports duplicate macro families (`log_info!`/`alog_info!`,
     `log_warn!`/`alog_warn!`).
   - `ActivityManagerBinder`/`DisplayManagerBinder` suffix "Binder" on every
     type though the module is already `binder`.
   - Top-level Android modules are split (`binder`, `dex`, `android_property`)
     with inconsistent cfg gating (`binder` stubs per-target, `dex` not gated).
5. **Error module naming.** `error::errno` consts (`EAGAIN`, `EPIPE`, …)
   duplicate `libc` names and live behind an awkward `errno` submodule.
6. **Reactor conflates two things.** `reactor` holds both the fd primitive
   (`Fd`, `Token`, `Event`) *and* the epoll engine (`Reactor`). `Fd` is used by
   fs/proc/socket/spawn — it is a primitive, not reactor-specific.

## Target layout

```
src/
  lib.rs                 crate docs + deliberate public API surface (re-exports)
  error.rs               CoreError + errno (unchanged, single error type)
  fd.rs                  Fd, Token, Event, FdKind         [split out of reactor]
  reactor.rs             Reactor only (epoll engine)
  fs.rs                  path_fingerprint, read_to_string, write_atomic,
                         read_nofollow, open_append_nofollow, remove_nofollow,
                         ensure_state_dir, fadvise, readahead, FADV_*  [fs+safe_fs]
  proc.rs                status, cmdline, parse_status, stat, uid, chown,
                         effective_uid, clock_ticks_per_second     [proc+uid]
  process.rs             fork, setsid, setpgid, setuid/gid, redirect_*, prctl
  spawn.rs               Process, SpawnOptions(Builder), Output, ExitStatus,
                         RunningProcess, spawn, spawn_start
  signal.rs              SignalRuntime, signalfd_new, SIG*, shutdown helpers
  inotify.rs             init, watch, read_events, decode_events, InotifyEvent
  uevent.rs              open, recv, drain_battery            [renamed from netlink]
  socket.rs              UnixListener, UnixStream, UnixConnectResult, bind,
                         connect, socketpair, chmod, PeerCred [unix_socket→socket]
  binder/
    mod.rs               re-export surface (cfg-gated)
    imp/                 ActivityManager, DisplayManager, FpsListener,
                         TaskStackListener, RawBinderService, TxCodes,
                         resolve_tx_codes, resolve_display_info
  android/
    mod.rs               property + dex grouped under one Android namespace
    property.rs          get, set, find, read, serial, wait   [android_property]
    dex.rs               tx-code resolution                    [moved from root]
  drm.rs                 DrmCard (unchanged)
  io.rs                  DrainState + buffer/writer internals
  log.rs                 Logger, LogBackend, log_info!/log_warn!/alog_*!
```

Rationale:
- **`fd.rs`**`Fd` is a cross-cutting primitive (used by fs/proc/socket/
  spawn/binder), not a reactor concept. `Token`/`Event` move with it.
- **`fs.rs`** merges the two filesystem modules; symlink-safe primitives keep
  their `*_nofollow`/`write_atomic` names but share one module and one error
  type.
- **`proc.rs`** merges `proc`+`uid`; pid probing and path ownership are one
  concern ("ownership and `/proc/<pid>` probes").
- **`process.rs`** keeps raw syscalls; **`spawn.rs`** keeps the high-level
  spawn API. Both stay, the boundary is documented in module docs.
- **`uevent.rs`** — the module is a kobject-uevent socket, name it that.
- **`android/`** groups all Android-only surface (`binder`, `dex`, `property`)
  under one namespace with a single `#[cfg(target_os = "android")]` story.
  `android::property` drops the redundant `android_property_` prefixes
  (→ `property::get`, `property::set`, …).
- **`socket.rs`**`UnixListener`/`UnixStream` (drop the wrapper-leaking `fd`
  field and the redundant `Unix` prefix; the module is the namespace).
  `connect`/`bind` are unambiguous module functions.
- **`binder`** keeps its name (it *is* the Android binder API). Types drop the
  `Binder` suffix: `ActivityManager`, `DisplayManager`, `FpsListener`,
  `TaskStackListener`. `DisplayManager` gains `info()``DisplayInfo`
  (refresh rate + interactive state via `getDynamicDisplayInfoFromId` /
  `getDisplayStats`) for the FPS/R normalization work.

## Name map (old → new)

| Module | Old | New |
|---|---|---|
| fd | `reactor::Fd` | `fd::Fd` |
| fd | `reactor::Token` | `fd::Token` |
| fd | `reactor::Event` | `fd::Event` |
| reactor | `reactor::Reactor` | `reactor::Reactor` |
| fs | `safe_fs::write_atomic` | `fs::write_atomic` |
| fs | `safe_fs::read_nofollow` | `fs::read_nofollow` |
| fs | `safe_fs::open_append_nofollow` | `fs::open_append_nofollow` |
| fs | `safe_fs::remove_nofollow` | `fs::remove_nofollow` |
| fs | `safe_fs::ensure_state_dir` | `fs::ensure_state_dir` |
| proc | `uid::proc_stat` | `proc::stat` |
| proc | `uid::proc_stat_at` | `proc::stat_at` |
| proc | `uid::proc_uid` | `proc::uid` |
| proc | `uid::proc_uid_at` | `proc::uid_at` |
| proc | `uid::path_stat` | `proc::path_stat` |
| proc | `uid::path_lstat` | `proc::path_lstat` |
| proc | `uid::path_uid` | `proc::path_uid` |
| proc | `uid::effective_uid` | `proc::effective_uid` |
| proc | `uid::chown_path` | `proc::chown` |
| proc | `uid::PathStat` | `proc::Stat` |
| uevent | `netlink::uevent_open` | `uevent::open` |
| uevent | `netlink::uevent_recv` | `uevent::recv` |
| uevent | `netlink::uevent_recv_raw` | `uevent::recv_raw` |
| uevent | `netlink::uevent_drain_battery` | `uevent::drain_battery` |
| socket | `unix_socket::UnixListenerFd` | `socket::UnixListener` |
| socket | `unix_socket::UnixStreamFd` | `socket::UnixStream` |
| socket | `unix_socket::bind_unix_listener` | `socket::bind` |
| socket | `unix_socket::connect_unix_stream` | `socket::connect` |
| socket | `unix_socket::connect_unix_stream_named` | `socket::connect_named` |
| socket | `unix_socket::chmod_unix_socket` | `socket::chmod` |
| socket | `unix_socket::chmod_socket_path` | `socket::chmod_path` |
| android | `android_property::android_property_get` | `android::property::get` |
| android | `android_property::android_property_set` | `android::property::set` |
| android | `android_property::android_property_find` | `android::property::find` |
| android | `android_property::android_property_read` | `android::property::read` |
| android | `android_property::android_property_serial` | `android::property::serial` |
| android | `android_property::android_property_wait` | `android::property::wait` |
| android | `dex::*` | `android::dex::*` |
| binder | `binder::ActivityManagerBinder` | `binder::ActivityManager` |
| binder | `binder::DisplayManagerBinder` | `binder::DisplayManager` |
| binder | `binder::FpsListener` | `binder::FpsListener` |
| binder | `binder::TaskStackListener` | `binder::TaskStackListener` |
| binder | `binder::RawBinderService` | `binder::RawBinderService` |

## Steps

### 1. Extract `src/fd.rs` from `reactor` (`Fd`, `Token`, `Event`)

- Move `Fd`, `Token`, `Event` verbatim into `src/fd.rs`; `reactor` re-exports
  nothing (`Fd`/`Token`/`Event` imports across the crate switch to `crate::fd`).
- Update all in-crate uses (`fs`, `proc`, `socket`, `spawn`, `signal`,
  `inotify`, `uevent`, `io`, `drm`, binder `imp`, `tests.rs`).
- `Reactor::setup_inotify` / `setup_signalfd` stay on `Reactor`.

### 2. Merge `fs` + `safe_fs``src/fs.rs`

- Move `safe_fs::*` into `fs.rs`. Convert its `io::Result` returns to
  `Result<_, CoreError>` (`CoreError::to_io_error` exists for callers that need
  an `io::Error`). Drop `src/safe_fs.rs`.
- Keep the symlink-attack rationale docs with the moved functions.

### 3. Merge `proc` + `uid``src/proc.rs`

- Move `uid::*` into `proc.rs`, renaming per the map (`proc_stat``stat`,
  `path_stat``path_stat`, `chown_path``chown`, `PathStat``Stat`). Drop
  `src/uid.rs`.

### 4. Rename `netlink``uevent`, `unix_socket``socket`

- Rename functions/types per the map. `src/unix_socket.rs``src/socket.rs`,
  `src/netlink.rs``src/uevent.rs`.
- `UnixListener`/`UnixStream` keep `&self` accessors (`fd()`, `as_raw_fd`)
  instead of the public `fd` field.

### 5. Group Android surface → `src/android/` + clean `binder`

- `src/android_property.rs``src/android/property.rs` with prefix-dropped
  function names (`get`, `set`, `find`, `read`, `serial`, `wait`);
  `AndroidPropertyStore`/`SystemAndroidPropertyStore` keep names.
- `src/dex.rs``src/android/dex.rs` (module now `android::dex`).
- `src/binder/` splits the `mod imp` monolith into per-service modules
  (`activity.rs`, `display.rs`, `fps.rs`, `task.rs`, `raw.rs`) plus shared
  `ndk.rs`/`parcel.rs`/`tx.rs`, and the host stubs become one `stubs.rs`
  generated by a `binder_stubs!` signature macro (kills the hand-duplicated
  stub block). Types drop the `Binder` suffix.
- `DisplayManager` gains `info(&self, display_id: i64) -> Result<DisplayInfo,
  CoreError>` and `DisplayInfo { active_mode_id, vsync_rate, peak_refresh_rate,
  render_frame_rate, is_interactive }` backed by `ISurfaceComposer
  .getDynamicDisplayInfoFromId` + `getDisplayStats` via a new
  `resolve_display_info()` dex txn (DEX-txn pattern already used for fps).
- `lib.rs` re-exports become the explicit surface:
  `pub use fd::{Fd, Token, Event}; pub use error::CoreError; pub mod reactor;`
  `pub mod fs; pub mod proc; pub mod process; pub mod spawn; pub mod signal;`
  `pub mod inotify; pub mod uevent; pub mod socket; pub mod drm; pub mod io;`
  `pub mod log; pub mod android; pub mod binder;` (binder gated on
  `target_os = "android"`, non-Android stubs behind the same gate).

### 6. Consumers in lockstep

- **CoreShift-FocusSource** (`Cargo.toml` → path dep during dev):
  `task.rs`, `daemon/fps.rs`, `daemon/fg.rs`, `daemon/wire.rs`, `focus.rs`,
  `sweep.rs`, `mailbox.rs`, `demand.rs`, `bin/observerd/*`.
- **CoreShift-FocusD** (`Cargo.toml` → path dep during dev): `binder_source.rs`,
  `daemon.rs`, `main.rs`, `resolver.rs`, `socket.rs`, `cache.rs`, `blocklist.rs`.
  (FocusD's *vendored* `safe_fs`/`unix_socket` copies are its own — untouched.)
- After the rename, restore published-version deps (core bump to 1.3.0).

### 7. Tests

- All existing core tests move with their modules (`tests.rs` keeps the crate
  walk, `tests/` integration suites unchanged apart from path renames).
- Add a compile-fail doc test asserting the surface (replacing the two stale
  `compile_fail` examples in `lib.rs`).

### 8. Verification

- `cargo fmt --check`, `cargo clippy --all-targets --all-features -- -D warnings`,
  `cargo test -j 1`, `cargo doc --no-deps` (matches `.github/workflows/ci.yml`).
- `cargo build --target aarch64-linux-android --target armv7-linux-androideabi`
  to prove the Android-only modules (`binder` imp, `android`) compile.
- Consumers: `cargo build` + `cargo test` in FocusSource and FocusD.

## Risks / notes

- **`Fd` is not `Clone`** — only `dup()`. Keep ownership rules as-is; do not
  introduce a `Clone` impl during the move.
- **`dex` is currently ungated** (compiles on host, Android-only in practice).
  Moving under `android/` keeps behavior; do not add host stubs for it.
- **Binder stubs must stay in sync** — the `binder_stubs!` macro is the
  mechanism; one signature list, both targets.
- **`DisplayInfo` needs `DisplayManager` open on the right service/context.**
  Gate the new method behind the same Android cfg as the rest of binder; the
  actual `getDynamicDisplayInfoFromId` txn must be validated on-device before
  FocusSource consumes `fps/R`.
- **No behavior change** — this is surface-only. All 84 unit/integration tests
  plus the Android cross-build must stay green at every step.