Why We Cannot Simply Overwrite the System Partition
Imagine you want to swap out the foundation of a house while someone is sleeping inside. You cannot just break the concrete without waking them up. Android faces a similar problem during a live update. The system partition holds the operating system currently executing all user tasks. The Linux kernel locks this partition as strictly read-only to prevent catastrophic crashes.
Try modifying a core binary through a root shell on a live device. The kernel immediately rejects the command with a "Read-only file system" error. We must find a way to write new OS data without actually touching the physical blocks the device relies on for current operations.
We solve this constraint using a Linux kernel framework called Device Mapper. This framework maps physical block devices to virtual block devices transparently. Specifically, Android relies on the dm-snapshot module to create temporary virtual mappings. This is the exact same underlying technology that makes full disk encryption work invisibly to the user.
Common Mistake: Engineers often assume the update engine writes individual files like you would with a
cpcommand. The engine actually operates at the block level underneath the filesystem. It writes raw blocks of data rather than interacting with file paths.
Now that we know we need a virtual mapping to bypass the read-only lock, we need a place to actually store the new data blocks as they are downloaded.
Intercepting Writes with the COW Device
If the kernel blocks us from writing to the system partition, where do the incoming update bytes go? They cannot vanish into thin air. We need a secondary location to hold the new data while preserving the illusion that we are updating the primary system.
This is where the Copy-on-Write protocol comes into play. The update engine instructs the dm-snapshot module to place a virtual mapping over the active system partition. Any read request for an unmodified file passes straight through this mapping down to the physical storage. The native speed remains completely unaffected.
When the update engine attempts to write a new OS block, the virtual mapping catches the request. The driver redirects those specific bytes into a hidden Copy-on-Write file residing on the writable user data partition. This COW device catches all the new changes while leaving the original physical blocks completely untouched.
We can visualize this exact divergence in data flow to understand the difference between reading and writing. The flowchart below illustrates how the virtual mapping acts as a traffic controller at the block level.
The mapping layer cleanly passes reads down to the physical system partition, while writes branch off to the temporary storage on the data partition.
Tip: The COW file does not contain a full copy of the operating system. It only stores the exact blocks that changed in the update, which explains why it easily fits alongside user data.
You can observe this process live during a system update.
adb shell snapshotctl dump
Running this command shows the COW file size growing as the update downloads in the background. The most frequent mistake here is checking the system partition size, which remains static during this phase.
Once the COW file contains all the new blocks, the device has everything it needs for the new OS, but the active system does not know about it yet until we reboot.
Tricking the System with a Virtual Overlay
How does the device boot into the new OS if the physical system partition remains completely unchanged? We cannot just swap the partition out after the kernel has fully loaded. We have to intercept the boot process before the core framework ever spins up.
During the early boot phase, the init process running from the ramdisk takes control. It checks the boot flags and detects that a system update finished downloading. Instead of mounting the raw physical partition, init instructs the kernel to mount the dm-snapshot virtual mapping as the primary system volume.
This virtual mapping behaves like a piece of transparent glass laid over a document, with new text written on the glass. When a system application requests a file, the driver checks the COW file first. If the file exists there, the kernel serves the updated version. If the block is unmodified, the request falls through to the original partition.
The following sequence diagram details the block resolution logic during a post-update boot. It shows exactly how the driver decides which physical location serves the requested data when an application needs a file.
The application has no idea that the mapping layer exists. It simply asks for a block and receives the correct version, unaware of whether the data came from the update payload or the original flash storage.
Warning: Because
initonly changes the mount point during startup, no actual files are moved or copied during the reboot screen. This makes the boot sequence effectively instantaneous compared to older Android versions.
Interview Note: Interviewers love asking why
inithandles the snapshot mount instead of the system framework. The framework resides on the system partition itself, so you cannot use the framework to mount the very partition it relies on to execute.
Running the OS through this virtual layer works flawlessly, but it forces the kernel to check two places for every file request, creating a performance debt that must be paid.
Paying the Performance Debt: The Final Merge
Why does a freshly updated device feel slightly sluggish right after unlocking the screen? The virtual overlay requires extra CPU cycles and storage seeks to resolve every single file request. We cannot leave the device in this fragmented state permanently.
Once the device successfully boots and reaches the home screen, a low-priority background thread called the snapshotctl daemon wakes up. This daemon begins the final merge process. It silently copies the updated blocks from the temporary COW file and physically overwrites the corresponding blocks on the actual system partition.
The merge requires significant disk activity but runs quietly in the background to avoid disrupting the user experience. You can picture it like moving into a new house and living out of boxes. You are fully functional, but it takes extra time to find your tools until you finally unpack everything into the permanent cabinets.
We can map out the complete lifecycle of the snapshot mapping from initial download to final cleanup. This state diagram visualizes the transition from a virtual overlay back to native block reads.
The system remains in the mounted state throughout the active user session until the background daemon finishes its physical block copies. After cleanup, the virtual overhead disappears entirely.
Did You Know: A sudden power loss during the merge process is perfectly safe. The device simply resumes copying the remaining blocks from the COW file upon the next boot, though it delays the return to full native performance.
This clever orchestration of device mapper blocks and background merging solves the offline downtime problem completely. The update process handles download, virtual mounting, and physical merging without ever locking the user out of their phone. But what happens if the newly downloaded operating system is fundamentally broken and fails to boot entirely? We need a fallback mechanism, which introduces the true power of Android's dual slot architecture.