# 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)
| 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.