| name | magisk-native |
| description | Guidelines, architecture, C++/Rust FFI bindings, development conventions, and build workflows for the Magisk native codebase (native/ subproject). Use when working on, modifying, building, debugging, or testing native C, C++, and Rust code in native/ (including magisk, magiskinit, magiskboot, magiskpolicy, resetprop, zygisk, and native crates/modules). |
Magisk Native Subproject Guidelines
Guidelines and workflows for developing and building native C, C++, and Rust components in the native/ subproject.
1. Environment & Build Requirements
- General Guidelines: Always follow the top-level
AGENTS.md and magisk-git for general repository rules, environment execution setup, and commit control policies.
- Working Directory: Execute commands from repo root via
./build.py. Standalone tool executions (e.g. cargo, rustc, ndk-build) MUST be prefixed with scripts/env.py.
- Codegen Requirement: ALWAYS run
./build.py gen before editing native sources to generate FFI bindings, headers, and flags (flags.h, flags.rs, *-rs.hpp, *-rs.cpp, protobuf generated modules) without performing a full build.
2. Component Architecture
Native C, C++, and Rust source modules under native/src/:
base/: System wrappers, logging, custom string abstractions (Utf8CStr), mount helpers, and common utilities (C++/Rust).
boot/: Boot image parsing, unpacking, repacking, and ramdisk patching logic (magiskboot) (Rust/C++).
core/: Magisk daemon (magiskd), Zygisk engine, su implementation, applets, and system properties (resetprop) (C++/Rust).
init/: Early boot magiskinit, ramdisk patching, SELinux pre-init (C/C++/Rust).
sepolicy/: SELinux policy engine (libpolicy) and magiskpolicy CLI (C++/Rust).
external/: Embedded dependencies (cxx-rs, selinux, crt0, system_properties, lsplt, lz4-sys, xz-embedded).
Key Native Binary Output Targets
magisk: Core daemon, Zygisk, su, and applets executable.
magiskinit: Early init replacement executable (static).
magiskboot: Boot image patcher executable (static).
magiskpolicy: SELinux policy tool executable.
resetprop: System property reader/writer executable.
3. Build System Orchestration
The build process follows a two-stage hybrid pipeline orchestrated by build.py:
1. dump_flags_native() --> Outputs flags.h & flags.rs to native/out/generated/
2. build_rust_src() --> Cargo build outputs lib<tgt>.a for each target ABI
Cargo build.rs runs cxx_gen to produce *-rs.hpp / *-rs.cpp
Cargo build.rs runs pb-rs to generate Protobuf bindings
Static libraries moved to native/out/<arch>/lib<tgt>-rs.a
3. build_cpp_src() --> ndk-build runs using Android.mk & Application.mk
Android-rs.mk imports lib<tgt>-rs.a as PREBUILT_STATIC_LIBRARY
Compiles C/C++ sources + *-rs.cpp bridge files + cxx.cc
4. clean_elf() --> tools/elf-cleaner strips incompatible ELF dynamic tags
4. FFI Architecture & Mechanics
- Bridge Engine: C++/Rust FFI uses
cxx (cxx-rs) via #[cxx::bridge] modules declared in crate lib.rs files.
- Header & Source Generation:
codegen.rs (gen_cxx_binding()) invokes cxx_gen in crate build.rs scripts.
- Automatically generates C++ bridge headers (
*-rs.hpp) and source wrappers (*-rs.cpp) directly in each crate directory.
- Generates bindings for
base-rs, core-rs, init-rs, boot-rs, and policy-rs.
- Linking: Generated
*-rs.cpp bridge code and cxx.cc are compiled directly by ndk-build alongside native C++ source files, linking against the compiled Rust static library (lib<tgt>-rs.a).
5. Build Targets & Commands (from Root)
- Build All Native Binaries:
./build.py native
- Build Specific Target(s):
./build.py native [magisk|magiskinit|magiskboot|magiskpolicy|resetprop]
- Rust Clippy Lint:
./build.py clippy
- Cargo Commands:
./build.py cargo check, ./build.py cargo test
- Generate Bindings & IDE Files:
./build.py gen
- Clean Native Artifacts:
./build.py clean native
6. Rust & C++ Conventions
- Rust Edition & Profile: Rust Edition 2024. Profile configured with
panic = "immediate-abort" across dev and release profiles.
- C++ Standard: C++20 with
libc++ static linking.
- Clippy Rules:
unwrap_used = "deny" in workspace configuration. Do NOT use .unwrap() in Rust code. Use ?, unwrap_or, unwrap_or_else, or pattern matching.
- Incremental Build Protection: File writers (
write_if_diff) skip rewriting identical generated files to preserve compilation timestamps and avoid unnecessary rebuilds.
7. Magisk Rust Patterns & Non-Standard Idioms
AI models modifying or writing Rust code in native/src/ MUST follow these Magisk-specific idioms:
-
Custom String Handling (Utf8CStr & cstr!):
- Standard C/C++ APIs require null-terminated UTF-8 strings. Do NOT create intermediate
CString allocations.
- Use
&Utf8CStr for string references, Utf8CString for heap buffers, and Utf8CStrBufArr<N> for stack buffers (base/cstr.rs).
- Use
cstr!("literal") for compile-time null-terminated static strings.
- Use
StringExt::nul_terminate for in-place null byte termination using reserved string capacity.
-
Instant Logging Error Model (LoggedError):
- Application logic does NOT use standard error enums or
anyhow.
- Errors are logged immediately at the failure point via
log_err!, .log(), or .log_with_msg() and converted into zero-sized LoggedError / LoggedResult<T>.
- In debug builds,
#[track_caller] logs the file and line number of error sites automatically. Use .silent() to silence expected failures.
-
Libc System Call Conversions (LibcReturn):
- When calling raw libc or C system APIs in Rust or
xwrap.rs, use LibcReturn::into_os_result or check_err to automatically map negative integer returns or null pointers into OsResult or LoggedResult.
-
Zero Allocation & Binary Size Rules:
- Prefer custom
argh derivation (base/argh.rs) for command-line parsing instead of adding standard argument dependencies.
- Avoid
serde overhead; use procedural Encodable/Decodable binary traits over UNIX domain sockets for IPC.
-
Nightly Synchronization & IPC:
- Magisk uses nightly features (
unix_socket_ancillary_data, nonpoison_mutex).
- Use non-poisoning mutexes/condvars for daemon threading and / for raw file descriptor passing.