Introduction
For developers working with embedded Linux systems, particularly those customizing Android-based platforms, understanding the intricate boot process is paramount. Android traditionally relies on its own init system, driven by configuration files like init.rc and its imported counterparts. These files define services, actions, and triggers that orchestrate the device’s startup. However, when migrating to a more standard Linux environment or adopting systemd on an Android-derived system, the challenge lies in translating these complex boot dependencies and actions into systemd unit files.
This article provides an expert-level guide on reverse engineering Android’s init.rc files and porting their complex boot dependencies to systemd. We’ll explore how to map Android’s declarative service definitions and event-driven actions to systemd‘s powerful and highly configurable unit types, focusing on maintaining the delicate order and functionality crucial for a stable system.
Understanding Android’s init.rc
The init.rc file, located typically at /init.rc in the root filesystem or within the ramdisk, is the primary configuration file for Android’s init process. It uses a custom language with several key constructs:
- Services: Define long-running daemons. Each service has a name, executable path, and optional arguments and attributes (e.g., user, group, capabilities, class).
- Actions: Blocks of commands executed in response to specific triggers (e.g.,
on boot,on property:name=value,on class_start:name). - Commands: Primitive operations like
exec,start,stop,setprop,mount,chmod,chown, etc., executed within actions or services. - Properties: System-wide key-value pairs that influence system behavior and trigger actions.
- Imports: Allow modularity by including other
.rcfiles (e.g.,import /init.usb.rc).
Here’s a simplified example of an init.rc service and action:
# Service definition
service my_daemon /vendor/bin/my_daemon_exec
class main
user system
group system wifi
capabilities NET_ADMIN
onrestart restart_log_service
# Action triggered on boot
on boot
setprop sys.usb.config adb
start log_service
mount debugfs /sys/kernel/debug /sys/kernel/debug type debugfs
exec /vendor/bin/configure_hw_features
systemd Fundamentals
systemd, the prevalent init system in modern Linux distributions, manages system boot, services, and various other components through ‘unit files’. Key unit types relevant to this porting process include:
- Service Units (.service): Define daemons or applications.
- Target Units (.target): Group related units and define synchronization points.
- Mount Units (.mount): Define filesystem mount points.
- Path Units (.path): Trigger actions when specific filesystem paths change.
- Socket Units (.socket): Define system sockets for service activation.
systemd excels in managing dependencies and execution order through directives like Requires=, Wants=, After=, Before=, PartOf=, and conditional execution using Condition* directives.
Reverse Engineering init.rc: A Step-by-Step Approach
Step 1: Identify Key Services, Actions, and Triggers
Begin by meticulously parsing the primary init.rc file and all files it imports. Create a comprehensive list of:
- All
servicedeclarations: Note their executable paths, arguments,user,group,capabilities, andclass. - All
onaction blocks: Identify the triggers (e.g.,boot,fs,property:name=value) and the commands executed within them. - All
execcommands: These represent one-shot scripts or binaries that must run at specific points. - All
mountcommands: Note the source, destination, type, and options. - All
setpropcommands: These modify system properties.
Step 2: Map init.rc Services to systemd Service Units
Each Android service translates directly to a .service unit. Consider the my_daemon example:
# my_daemon.service
[Unit]
Description=My Custom Daemon
After=network.target multi-user.target
[Service]
ExecStart=/vendor/bin/my_daemon_exec
User=system
Group=system wifi
CapabilityBoundingSet=CAP_NET_ADMIN
Restart=on-failure
# For 'onrestart restart_log_service'
# This is complex and might require a custom script in ExecStopPost
# or a separate .path unit to detect service failure.
[Install]
WantedBy=multi-user.target
User=andGroup=map directly. If the group doesn’t exist, create it.capabilities NET_ADMINmaps toCapabilityBoundingSet=CAP_NET_ADMIN. For multiple capabilities, list them space-separated.class mainsuggests it’s part of the core system. It can be targeted by a custom.targetunit (e.g.,android-main.target) whichmulti-user.target`Wants=`.onrestart restart_log_serviceis more complex.systemd‘sRestart=handles basic restarts. For conditional actions on restart, you might need a wrapper script, or utilizeExecStopPostto trigger another service.
Step 3: Translate init.rc Actions and Triggers to systemd Dependencies and Events
This is the most challenging part, requiring a deep understanding of systemd‘s dependency model.
on boot: Commands underon boottypically run very early. For services, useWantedBy=multi-user.targetor a custom target thatmulti-user.targetdepends on. For one-shot commands (exec), create a.serviceunit withType=oneshotand useExecStart=. AddRemainAfterExit=yesif its success implies a lasting state.
# configure_hw_features.service
[Unit]
Description=Configure Hardware Features
After=multi-user.target
[Service]
Type=oneshot
ExecStart=/vendor/bin/configure_hw_features
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target
on property:name=value: This is highly dynamic.systemddoesn’t have a direct equivalent for arbitrary property changes.- Polling Service: Create a small
.serviceunit that periodically polls the property and triggers another service (e.g., usingsystemctl start another-service.service). This is less efficient. - udev Rules: If the property change is tied to a hardware event, a
udevrule can detect the event and trigger a.serviceunit. - Custom Agent: Implement a lightweight agent that monitors Android properties (if using a hybrid system) and signals
systemdwhen a specific property changes. execin actions: As shown withconfigure_hw_features.service, these becomeType=oneshotservice units. TheirAfter=andWants=directives determine when they run.start: Insystemd, this meansWants=.serviceor `Requires=.service` from the unit that needs to start it.
Step 4: Handling File System Mounts and Permissions
mount [options]: Translate to.mountunits or entries in/etc/fstab.
# debugfs.mount
[Unit]
Description=Debug Filesystem Mount
Requires=systemd-modules-load.service
After=systemd-modules-load.service
[Mount]
What=debugfs
Where=/sys/kernel/debug
Type=debugfs
Options=defaults
[Install]
WantedBy=multi-user.target
chmodandchown: For persistent files, configure permissions through package installation. For temporary files or files created at runtime, useExecStartPre/ExecStartPostin a relevant service unit, orsystemd-tmpfilesfor managing temporary and volatile files.
# Example within a .service file
[Service]
ExecStartPre=/bin/mkdir -p /data/misc/mydir
ExecStartPre=/bin/chown system:system /data/misc/mydir
ExecStartPre=/bin/chmod 0770 /data/misc/mydir
ExecStart=/vendor/bin/my_daemon_exec
Step 5: Managing Properties and Environment Variables
setprop: If these are simple environment variables, define them in the[Service]section usingEnvironment=VAR=value. For system-wide properties, consider creating a small `oneshot` service that writes to a known configuration file or uses a custom `property-manager` service if the Android property system must be emulated.
Advanced Scenarios and Best Practices
- Class Management: Android’s
class_start/class_stopcan be emulated by creating custom.targetunits (e.g.,android-main.target,android-core.target). Services belonging to a class would haveWantedBy=.target. Starting/stopping a class would involvesystemctl start android-main.target. - Debugging: Use
journalctl -xeufor detailed logs.systemd-analyze blame,systemd-analyze critical-chainhelp identify boot bottlenecks. - Security: Leverage
systemd‘s extensive security directives:NoNewPrivileges=yes,PrivateTmp=yes,ReadOnlyDirectories=,CapabilityBoundingSet=,AmbientCapabilities=,RestrictAddressFamilies=, etc., to harden your services beyond whatinit.rctraditionally offers. - Custom Targets: Define specific
.targetunits for different boot phases or functional groups of services to mirror Android’s more granular startup phases (e.g.,early-init,late-init,boot,fs,post-fs).
# android-early-init.target
[Unit]
Description=Android Early Init Target
Documentation=man:systemd.special(7)
RefuseManualStart=yes
[Install]
WantedBy=basic.target
Conclusion
Porting complex Android init.rc boot dependencies to systemd is a non-trivial but highly rewarding process. It requires a systematic approach to dissecting init.rc logic and carefully mapping it to systemd‘s powerful and declarative unit system. While direct equivalents don’t always exist, especially for dynamic property-based triggers, systemd offers robust alternatives through its dependency management, conditional execution, and the flexibility to create custom units and targets. By adopting systemd, you gain enhanced control, better debugging capabilities, and a more standardized, maintainable, and secure boot environment for your embedded Linux systems.
Android Mobile Specs & Compare Directory
Are you researching mobile hardware properties, processor SoCs, GPU chipsets, or RAM configurations? Access our complete specs catalog to compare up to 5 devices side-by-side!
Compare Devices Specs →