N03 — Write an init service
Add a native daemon to the boot sequence: an init.rc service block, a tiny binary, an SELinux domain and the property that proves it started. The most self-contained platform change you can make.
A daemon started by init is the smallest honest platform change. You do not touch the framework,
you do not touch Binder, and the failure modes are visible in logcat and the kernel log. It still
needs a full platform build and a boot, so it is a nerd project.
TL;DR#
initreads.rcfiles at boot and starts the services they declare.service <name> <path> [args]declares a service; the options areclass,user,group,oneshot,seclabelandsocket.on <trigger>declares an action;start <service>launches the service from it.class_start <class>starts a whole class at once, which is howhalandcorecome up.- A new service needs SELinux policy, or the exec is denied.
What you are building#
A small native binary that writes a property once at boot and then exits, started by init from an
.rc file you add. It is deliberately trivial: the value is not the program, it is the chain —
binary → Android.bp module → .rc service block → SELinux domain → property visible over adb.
The rc language in one table#
Every option below comes from the init README.1
| Syntax | Meaning |
|---|---|
service <name> <path> [ <argument> ]* | Declares a service. Names must be unique; a duplicate is ignored and logged. |
class <name> [ <name>* ] | Which class the service belongs to. Without it, the service is in default. |
user <username> | Drops to this UID before exec. Defaults to root. |
group <groupname> [ <groupname>* ] | Primary group plus supplemental groups. Defaults to root. |
oneshot | Do not restart the service when it exits. |
seclabel <context> | The SELinux context to exec under. |
socket <name> <type> <perm> [...] | Creates /dev/socket/<name> and passes the fd to the service. |
on <trigger> [&& <trigger>]* | Declares an action, run when the trigger fires. |
start <service> | Starts a service if it is not already running. Asynchronous. |
class_start <class> | Starts every service in the class. |
early-init, init, late-init, early-boot and boot are the built-in boot triggers, in that
order. boot runs after the early-boot actions complete. The platform init.rc is where you see
them used together: on early-init, on boot, and the class_start lines that bring up hal and
core.2
Steps#
- Write the daemon
A minimal C program that calls the property setter and returns. It does not need to be a long-lived process; a
oneshotservice that does one thing at boot is enough to prove the wiring. Keep it somewhere undersystem/ordevice/in the tree, in a directory with its ownAndroid.bp.You should seeA binary that runs, sets a property and exits cleanly. - Add the Android.bp module
Declare a
cc_binarywith the source file, and make sure the module is installed into the image (thesystempartition for a platform daemon). If the binary is not installed,initwill try to exec a path that does not exist and log the failure.You should seeThe module builds and its output is in the system image. - Write the service block
Add an
.rcfile, either appended to an existing device.rcor as a new file that is copied into the image. The following is illustrative — the names are invented for the guide:# Illustrative example, not a file from AOSP. service toy-daemon /system/bin/toy_daemon class late_start user system group system oneshot seclabel u:r:toy_daemon:s0 on property:sys.boot_completed=1 start toy-daemonRead the block as a sentence: "there is a service called
1toy-daemon, it is the binary at/system/bin/toy_daemon, it runs assystem, it does not restart, and it runs under this SELinux label." The action below it says "when the boot-completed property is set, start it".You should seeinit parses the block without a syntax error. - Hook it into a boot trigger
A
2serviceblock alone does not start anything. Either put it in a class that is started by an existingclass_startline, or add anonaction that callsstart.init.rcinsystem/coreshows the real pattern:on early-init,on boot, andclass_start hal/class_start core.Ordering is by trigger, so choose the trigger that matches your dependencies. A daemon that needs the data partition must not run at
early-init.You should seeThe service actually starts. - Declare the SELinux domain
Create a
.tefile in the device policy directory and declare the domain. The customize documentation shows the shape:type dhcp, domain;gives the new type thedomainattribute. For a daemon spawned byinit, theinit_daemon_domainmacro encodes the transition and the basic communication withinit.3The domain name must match the
seclabelin the service block. A mismatch shows up asavc: deniedon the exec, and the service fails to start even though the binary exists.You should seeNo `avc: denied` for the exec. - Point the build at your policy
Add your policy directory to the device's
BOARD_SEPOLICY_DIRSinBoardConfig.mk. The SELinux implement page gives the pattern:BOARD_SEPOLICY_DIRS += \ <root>/device/manufacturer/device-name/sepolicyPolicy from that directory is compiled into the final kernel policy.4
You should seeThe policy is compiled into the image. - Build and boot
Build the image and boot your target as in N01. The exact target string comes from the AOSP build page for your branch.5
You should seeThe device boots and the property appears. - Check the result
adb shell getprop | grep toy adb logcat -b all -s init adb shell dmesg | grep avcThe property proves the binary ran. The init log shows the service being started and its exit. The
1avcgrep is how you find a missing policy rule — an empty result is what you want. The init README is the grammar of everything you wrote above, and the place to look when a block is rejected.You should seeA property, a log line and a process that ran.
Build and check#
source build/envsetup.sh
lunch <your target from N01>
m
adb shell getprop | grep toy
adb logcat -b all -s init
adb shell dmesg | grep avcExample output
Illustrative — no build was executed in this run.<your target from N01> is a placeholder deliberately. The exact lunch target should be copied from
the AOSP build page for your branch.5
The files you touched#
Policy is a whole subject of its own; the SELinux overview is the entry point, and the implement page is the procedure you followed above.34
- README.mdthe authoritative description of the service and action syntax
- init.rcthe platform init.rc, with the early-init and boot actions
- the base policy; read it, do not edit it
- where your device-specific .te file and BOARD_SEPOLICY_DIRS point
Verification#
A service that started and exited cleanly leaves three traces: the property, the init log line, and
no denial in dmesg. A service that failed to start leaves a log line naming the reason — missing
binary, denied exec, or a policy transition with no matching domain. Read the init log first; it
usually names the problem.
What can go wrong#
- The binary is not in the image. The module is not installed, or it installed to a partition the service path does not point at.
- The service block never runs. A
serviceblock does not start anything by itself; you still need anonaction withstart, or aclass_startfor its class. avc: deniedon exec. Theseclabelhas no domain, or the domain is not allowed to execute the binary's label.- The service restarts forever. You did not mark it
oneshot, and it exits immediately, soinitrestarts it. The log fills with start/exit pairs. - It runs too early. The filesystem or the property service it depends on does not exist at that trigger. Move it later in the sequence.
- You edited
system/sepolicydirectly. The documentation says not to; add device policy under yourBOARD_SEPOLICY_DIRSinstead.4
Sources#
Sources
Every version number, API level, class name and date on this page is traced to one of these. Pages are re-verified against them; last_verified above says when.
- sourcesystem/core/init/README.mdandroid.googlesource.comaccessed 2026-10-27↩
- sourcesystem/core/rootdir/init.rcandroid.googlesource.comaccessed 2026-10-27↩
- docsSELinuxsource.android.comaccessed 2026-10-27↩
- docsImplement SELinuxsource.android.comaccessed 2026-10-27↩
- docsBuild Androidsource.android.comaccessed 2026-10-27↩
- sourceAndroid Code Search (cs.android.com)cs.android.comaccessed 2026-10-27↩