[ASUS ZenFone 8] bnsmb:
How to run a script at shutdown

To define additional startup scripts via Magisk the Magisk directories /data/adb/service.d and /data/adb/post-fs-data.d can be used. Unfortunately there is no equivalent for scripts that should be executed during shutdown.

So we must use other methods to implement these kind of scripts.

Using the overlay feature of Magisk to run a script at shutdown

Introduction

in Android it is possible to define actions that will be executed when certain conditions are satisfied.

These definitions are done in the file init.rc (and other .rc files) using the Android Init Language.

And this feature can be used to execute a command when the phone is shutting down.

Note:

For details about the Android Init Language used for these files see here https://android.googlesource.com/platform/system/core/+/master/init/README.md

The .rc files used by Android are in the directories

/system/etc/init​

/vendor/etc/init​

/odm/etc/init​

Note: The first .rc file read is /system/etc/init/hw/init.rc

Unfortunately it's useless to change the .rc files in these directories using the Magisk features to change files in the directory /system because these files are processed by the OS before the new files are "created" by Magisk.

Therefor the overlay functionality from Magisk must be used to create additional .rc files (see the section Root Directory Overlay System on this page https://github.com/topjohnwu/Magisk/blob/master/docs/guides.md for details about this Magisk Feature).

Preparation

To be able to restore the original boot partition in case of an error create an image of the original boot partition from the phone on your PC before starting the development:

CUR_SLOT=$( adb shell getprop ro.boot.slot_suffix )
adb shell su - -c dd if=/dev/block/by-name/boot${CUR_SLOT} | cat >boot${CUR_SLOT}.img

e.g.

[ OmniRomDev - xtrnaw7@t15g /data/develop/android/test ] $ CUR_SLOT=$( adb shell getprop ro.boot.slot_suffix )

[ OmniRomDev - xtrnaw7@t15g /data/develop/android/test ] $ echo ${CUR_SLOT}
_b
[ OmniRomDev - xtrnaw7@t15g /data/develop/android/test ] $

[ OmniRomDev - xtrnaw7@t15g /data/develop/android/test ] $ adb shell su - -c dd if=/dev/block/by-name/boot${CUR_SLOT} | cat >boot${CUR_SLOT}.img
196608+0 records in
196608+0 records out
100663296 bytes (96 M) copied, 2.668147 s, 36 M/s
[ OmniRomDev - xtrnaw7@t15g /data/develop/android/test ]

[ OmniRomDev - xtrnaw7@t15g /data/develop/android/test ] $ ls -ltr boot${CUR_SLOT}.img
-rw-r--r--. 1 xtrnaw7 xtrnaw7 100663296 Oct 1 12:13 boot_b.img
[ OmniRomDev - xtrnaw7@t15g /data/develop/android/test ] $

To trouble shoot issues with this approach it is highly recommended to create an Magisk init script in the directory

/data/adb/post-fs-data.d

to fetch and store the Android logs into a persistent file. Use these commands to create the script:

cat >/data/adb/post-fs-data.d/0001logcatboot <<-EOT

mkdir -p /cache/logs

# backup the OS logs from before the reboot:
#
[ -r /cache/logs/log ] && mv /cache/logs/log /cache/logs/oldlog

/system/bin/logcat -r 102400 -n 9 -v threadTime -f /cache/logs/log >/cache/logs/info.log 2>/cache/logs/err.log &
EOT

chmod 755 /data/adb/post-fs-data.d/0001logcatboot

Using this script the log messages from before the last reboot are stored in the file /cache/logs/oldlog.

To activate the script the phone must be rebooted.

Check the contents of the directory /cache/logs/log after the reboot as user root to be sure that it works.

shell@ASUS_I006D:/ $ su - -c ls -ltr /cache/logs
total 205008
-rw-rw-rw- 1 root root 0 1970-01-06 08:16 info.log
-rw-rw-rw- 1 root root 0 1970-01-06 08:16 err.log
-rw-r----- 1 root root 4707523 2022-10-01 17:29 log
shell@ASUS_I006D:/ $

Details

The trigger in the .rc files for the action that should be done while shutting down is

on shutdown

The trigger can be used more then once; the OS will execute all defined actions for the trigger in the order they are found in the rc files.

The action to run an executable in the .rc file is

In Android SELinux is enabled by default. Therefor it's neccessary to use the correct SELinux context for the files used.

(Note: The SELinux context for the init process executing the action is u:r:init:0 )

It's quite difficult to find the correct SELinux contexts in Android for this approach therefor it's better to use the general SELinux context defined by Magisk: u:r:magisk:s0 .

Implementation

Note:

All commands must be done as user root in an session on the phone or in an adb session.

So first create the neccessary directories and files:

mkdir -p /data/init_scripts

mkdir -p /data/init_scripts/log

Create the script to execute on shutdown:

cat >/data/init_scripts/my_shutdown.sh <<-\EOT
#!/system/bin/sh

SHUTDOWN_LOG="/data/init_scripts/log/myshutdown.$$.log"

echo "$0: Shutdown with parameter \"$*\" started at $( date ) " >>${SHUTDOWN_LOG}

echo "*** id : " >>${SHUTDOWN_LOG} 2>&1
id >>${SHUTDOWN_LOG} 2>&1

# ... add necessary commands ...

EOT
chmod 755 /data/init_scripts/my_shutdown.sh

Correct the SELinux context:

chcon -R u:r:magisk:s0 /data/init_scripts/

Check the result

root@ASUS_I006D:/ # find /data/init_scripts/ -exec ls -lZd {} \;
drwxr-xr-x 3 root root u:r:magisk:s0 3452 2022-10-01 16:12 /data/init_scripts/
-rwxr-xr-x 1 root root u:r:magisk:s0 637 2022-10-01 16:12 /data/init_scripts/my_shutdown.sh
drwxr-xr-x 2 root root u:r:magisk:s0 3452 2022-10-01 16:16 /data/init_scripts/log
root@ASUS_I006D:/ #

Create a working directory:

#
# create a working directory
#
mkdir -p /data/adb/workdir
cd /data/adb/workdir

Now create the additional .rc file:

#
# change the current directory to the working directory
#
cd /data/adb/workdir

cat >init.custom.rc <<-\EOT
on shutdown
exec u:r:magisk:s0 -- /system/bin/sh /data/init_scripts/my_shutdown.sh 0008

on early-init
setprop my_custom_rc_file loaded
EOT

Note:

The additional trigger for early-init is for testing the new .rc file (see the trouble shooting section below for details). Magisk supports more then one .rc file; the name of the .rc file is meaningless but the extension must be .rc.

And now add the new file to the ramdisk on the boot partition:

#
# change the current directory to the working directory
#
cd /data/adb/workdir

# get the current active slot
#
CURRENT_SLOT=$( getprop ro.boot.slot_suffix )

echo "The current active slot is: ${CURRENT_SLOT}"

# copy the boot partition from the active slot to a file
#
dd if=/dev/block/by-name/boot${CURRENT_SLOT} of=./boot_root.img

# unpack the image file
#
/data/adb/magisk/magiskboot unpack ./boot_root.img

# add the new dirs and files to the ramdisk from the boot partition
#
/data/adb/magisk/magiskboot cpio ramdisk.cpio \
"mkdir 0700 overlay.d" \
"add 0700 overlay.d/init.custom.rc init.custom.rc"

# recreate the image file for the boot partition
#
/data/adb/magisk/magiskboot repack boot_root.img

# write the corrected image file to the boot partition
#
dd if=./new-boot.img of=/dev/block/by-name/boot${CURRENT_SLOT}

Note:

The commands to unpack and pack the ramdisk manually using the cpio command are (if NOT using the Magisk binary magiskboot):

RAMDISK=$PWD/ramdisk

mkdir ${RAMDISK}
cd ${RAMDISK}

# unpack the ramdisk
#
cpio -idm <../ramdisk.cpio

# ... do what ever is necessary with the files/dirs in ${RAMDISK}

# pack the ramdisk again
#
cd ${RAMDISK}
find . | cpio -o >../ramdisk.cpio

Now reboot the phone to activate the new .rc config and after the reboot check that the .rc file was processed

getprop my_custom_rc_file

e.g

shell@ASUS_I006D:/ $ getprop my_custom_rc_file
loaded
shell@ASUS_I006D:/ $

If the property defined in the .rc file, my_custom_rc_file, is not set something went wrong and you should check the OS logs and double check your config.

If the new property is defined you can test the shutdown action by rebooting the phone again.

While doing this reboot the new shutdown script should be executed and after the reboot is done there should be the log file from the shutdown script:

shell@ASUS_I006D:/ $ su -

root@ASUS_I006D:/ # ls -l /data/init_scripts/log
total 0
-rw------- 1 root root 179 2022-10-01 18:23 myshutdown.4617.log

root@ASUS_I006D:/ # cat /data/init_scripts/log/myshutdown.4617.log
/data/init_scripts/my_shutdown.sh: Shutdown with parameter "0008" started at Sat Oct 1 18:23:14 CEST 2022
*** id :
uid=0(root) gid=0(root) groups=0(root) context=u:r:magisk:s0
root@ASUS_I006D:/ #

That's it.

Note that you can change the script executed while doing the shutdown without changing the boot image again.

But you should always test the script before rebooting -- an error in your script may stop the reboot.

To change the additional .rc files it's necessary to recreate the ramdisk and boot partition.

The filesystems for /data and for /sdcard are still mounted while executing the actions for the trigger "on shutdown" .

To log the current environment while executing the shutdown script you can add code like this to the script:

(
echo
echo "*** Environment while executing the shutdown script ..."
echo

echo "*** pwd: "
pwd
echo

echo "*** id: "
id
echo

echo "*** df -h: "
df -h
echo

echo "*** ps -efZ : "
ps -efZ
echo

echo "*** env: "
env
echo

echo "*** set: "
set
echo
) >>/data/init_scripts/log/myshutdown_env.log 2>&1

To create a directory in which other actions from the .rc file (like write) can write with SELinux enabled use one of the SELInux contexts the init process can write to, e.g:

mkdir /data/system_data
chcon u:object_r:system_data_file:s0 /data/system_data

Now the .rc config

on shutdown
write /data/system_data/myshutdown.log Shutdown_started\n

will work.

See the file ./plat_file_contexts in the ramdisk from the boot partition for other existing SELinux contexts, e.g.:

root@ASUS_I006D:/data/adb/test # /data/adb/magisk/magiskboot cpio ramdisk.cpio "extract plat_file_contexts plat_file_contexts" <
Loading cpio: [ramdisk.cpio]
Extract [plat_file_contexts] to [plat_file_contexts]

root@ASUS_I006D:/data/adb/test # ls -l plat_file_contexts
-rw-r--r-- 1 root root 40490 2022-10-03 16:27 plat_file_contexts
root@ASUS_I006D:/data/adb/test #

Please be aware that these changes will be gone after the next OS update. But on the other hand it's quite easy to create a script to re-install the shutdown script without user intervention.

Trouble Shooting

The main reason for problems with this approach are invalid SELinux contexts. Therefor you should test your script in permissive SELinux mode if it does not work like expected. To do that temporary disable SELinux before rebooting (SELinux will be automatically enabled again after the reboot), e.g.:

# set SELinux to permissive
#
setenforce 0

reboot

and check the log messages in the directory /cache/logs/oldlog for SELinux related messages:

su - -c grep deny /cache/logs/oldlog

Note that you can not disable SELinux in an action in an .rc file.

To check if your additional .rc file is processed by Magisk add a statement like these to the custom .rc file in the overlay directory:

on early-init
setprop sys.example.foo bar

If this statement is processed by Magisk and Android the property sys.example.foo should be defined after the reboot, e.g.:

root@ASUS_I006D:/ # getprop sys.example.foo
bar
root@ASUS_I006D:/ #

To check if the "on shutdown" trigger is processed use :

on shutdown
write /sdcard/Download/myshutdown.log Shutdown_started\n

and reboot with disabled SELinux:

setenforce 0
reboot

If the "on shutdown" trigger in your .rc file is processed there should exist the file

/sdcard/Download/myshutdown.log

after the reboot

If the shutdown of the phone hangs open another adb session to the phone and kill the script (the adb daemon should still run while the shutdown script is running).

If the phone does not boot anymore with the new shutdown script reboot the phone from the TWRP image and fix / delete the new shutdown script. Or reflash the boot partition with the image file created before starting the development.

In general you should carefully check your .rc file for syntax errors -- entries in the file after the first syntax error will be ignored

Useful URLs

I used ideas and code from the web pages listed below for this HowTo:

How to run an executable on boot and keep it running?

How to run an Android init service with superuser SELinux context?

Magisk overlay - execute a script or copy files

History

03.10.2022 /bs

added code about to extract a single file (plat_file_contexts) from the ramdisk cpio image using magiskboot

20.06.2024 /bs

fixed some typos in the commands