#!/system/bin/sh # chmod -R 755 /system/etc/init.d /system/su.d /su/su.d /sbin/supersu/su.d /magisk/.core/post-fs-data.d /sbin/.core/img/.core/post-fs-data.d /data/adb/post-fs-data.d # # ROM GApps Auto-Integration # osm0sis @ xda-developers debugging=0 dryrun=0 logbuff() { logbuff+="$($*)"; } writable() { touch $1/tmpfile 2>/dev/null; ret=$?; rm $1/tmpfile 2>/dev/null; echo $ret; } # figure out if the next integration will likely have enough free space in /system freespace-estimate() { free=$(df -Ph /system 2>>$out | tail -n 1 | awk '{ print $4 }'); case $free in *K) free=0;; *M) free=`echo $free | cut -dM -f1 | cut -d. -f1`;; *G) free=$((`echo $free | cut -dG -f1 | cut -d. -f1` * 1024));; esac; libsize=0; buffer=4; if [ "$nozipbins" ]; then for entry in $(unzip -l $1 lib/* 2>>$out | tail -n +4 | awk '{ print $1 }'); do case $entry in ---------) ;; # skip top line displayed in some unzip list edge cases --------) break;; *) libsize=$((libsize + entry));; esac; done; if [ $libsize -lt 1048576 ]; then libsize=1; else libsize=$((libsize / 1048576)); fi; else buffer=8; fi; datapksize=$(du -m $1 | awk '{ print $1 }'); sysapksize=$(du -m $2 | awk '{ print $1 }'); need=$((datapksize + libsize - sysapksize + buffer)); echo "free($free) ?> need($need) = datapksize($datapksize) + libsize($libsize) - sysapksize($sysapksize) + buffer($buffer)" >> $out; [ $free -gt $need ]; echo $?; } # determine if final replacement APK and library sizes will fit in /system freespace-precise() { free=$(df -Ph /system 2>>$out | tail -n 1 | awk '{ print $4 }'); case $free in *K) free=0;; *M) free=`echo $free | cut -dM -f1 | cut -d. -f1`;; *G) free=$((`echo $free | cut -dG -f1 | cut -d. -f1` * 1024));; esac; oldfiles=$(du -mc $1 2>>$out | tail -n1 | awk '{ print $1 }'); newfiles=$(du -mc $2 2>>$out | tail -n1 | awk '{ print $1 }'); echo "free($free) + oldfiles($oldfiles) = total_space($((free + oldfiles))) ?> newfiles($newfiles)" >> $out; [ $((free + oldfiles)) -gt $newfiles ]; echo $?; } # add support for Magisk by altering the actual mounted system partition [ -d /magisk/.core/mirror ] && magisk=/magisk/.core/mirror; [ -d /sbin/.core/mirror ] && magisk=/sbin/.core/mirror; [ -d /sbin/.magisk/mirror ] && magisk=/sbin/.magisk/mirror; [ -e /dev/*/.magisk/mirror ] && magisk=$(echo /dev/*/.magisk/mirror); # alter Magisk's post-fs-data.d PATH to prioritize actual mounted system over the included busybox [ "$magisk" ] && export PATH="$magisk/system/bin:$magisk/system/xbin:$PATH"; # group all commands and allow redirection of output for debugging out=/dev/null; [ "$debugging" == 1 ] && out=/data/local/tmp/debug-gappsint.log; ( # run in an asynchronous subshell to not impede the boot with su.d/post-fs-data.d { # loop counter timeout limits for device writability sdlimit=900; syslimit=120; # define working paths gtmp=/data/local/tmp/gapp; gbak=/data/local/tmp/gxml-bak; log=/sdcard/gapps-integrator.log; # create log/change filedate immediately to indicate script is active once /sdcard is available counter1=0; until [ "$(writable /sdcard; touch $log 2>/dev/null)" == 0 -o "$counter1" -gt "$sdlimit" ]; do sleep 1; counter1=$((counter1 + 1)); done& if [ "$dryrun" == 0 ]; then # wait for /system to become remountable/writable, abort if it does not counter2=0; until [ "$(mount --help 2>/dev/null)" ] && [ "$(mount -o rw,remount -t auto $magisk/system >>$out; writable $magisk/system; mount -o ro,remount -t auto $magisk/system >>$out)" == 0 -o "$counter2" -gt "$syslimit" ]; do sleep 1; counter2=$((counter2 + 1)); done; if [ "$counter2" -gt "$syslimit" ]; then logbuff echo -ne "\n\x20* Fatal error: $magisk/system did not become remountable/writable during boot\n\n"; abort=1; fi; fi; # log earliest accurate processing start timestamp once date stops returning epoch in early boot until [ "$(date 2>/dev/null)" ] && [ ! "$(date | grep '1970')" ] && [ ! "$(date | grep '1999')" ] && [ ! "$(date | grep '2000')" ]; do sleep 1; done; logbuff echo -n `date`; echo -ne "\n$(date) "; if [ "$dryrun" == 1 ]; then logbuff echo -ne "\n\x20* Dry run mode: No actual changes will be made to $magisk/system or /data"; echo -ne "--- Dry run mode! "; fi; # test for and try to ensure a working basic script environment, often broken in Marshmallow (AOSP) for i in awk basename cut dirname sed tail unzip which; do if [ ! "$(which $i)" ]; then logbuff echo -ne "\n\x20* Missing script environment element detected: $i"; abort=1; fi; done; if [ ! "$(echo testing | sed 's/test/pass/')" == "passing" ]; then logbuff echo -ne "\n\x20* Broken script environment element detected: sed"; if [ "$(echo testing | busybox sed 's/test/pass/')" == "passing" ]; then logbuff echo -ne "\n\x20* -- using busybox sed instead"; bb=busybox; else abort=1; fi; fi; touch /data/local/testfile1; touch /data/local/testfile2; if [ ! "$(chmod 666 /data/local/testfile*; echo $?)" == 0 ]; then logbuff echo -ne "\n\x20* Broken script environment element detected: chmod"; if [ "$(busybox chmod 666 /data/local/testfile*; echo $?)" == 0 ]; then logbuff echo -ne "\n\x20* -- using busybox chmod instead"; bb=busybox; else abort=1; fi; fi; rm -f /data/local/testfile*; # check for zip and zipalign binaries to determine integration method for i in zip zipalign; do if [ ! "$(which $i)" ]; then logbuff echo -ne "\n\x20* Missing binary for primary integration method: $i"; nozipbins=1; fi; done; [ "$nozipbins" ] && logbuff echo -ne "\n\x20* -- using fallback method instead"; # get SDK version to perform different actions due to /data/app layout changes sdkver=`getprop ro.build.version.sdk`; [ ! "$sdkver" ] && sdkver=$(grep '^ro.build.version.sdk' $magisk/system/build.prop | cut -d= -f2); # find new unintegrated Google Apps APKs in /data for i in $(ls /data/app/ | grep -E '^com.android|^com.chrome|^com.google.android|^com.google.earth|^com.motorola'); do # skip all if script environment is broken [ "$abort" ] && break; # find equivalent /system APK name and only process if it exists xml=/data/system/packages.xml; package=`echo $i | rev | cut -d- -f2- | rev`; sysapk=$(grep "updated-package name=\"$package\"" $xml | grep -o 'codePath=.*$' | cut -d\" -f2); logbuff echo -ne "\n/data/app/$i $sysapk"; if [ "$sysapk" ]; then # compare /data and /system APK versions and only integrate if /data is newer (necessary on Lollipop and above) datver=$(grep "codePath=\"/data/app/$i\"" $xml | grep -o 'version=.*$' | cut -d\" -f2); sysver=$(grep "codePath=\"$sysapk\"" $xml | grep -o 'version=.*$' | cut -d\" -f2); if [ "$datver" -gt "$sysver" ]; then logbuff echo -ne "\x20($datver > $sysver)"; echo -e "\n$i $magisk$sysapk"; rm -rf $gtmp; mkdir -p $gtmp; # KitKat (and below) support if [ "$sdkver" -le 20 ]; then datapk=/data/app/$i; # exclude APKs that cause breakage after integration case $package in com.google.android.apps.messaging|com.google.android.apps.photos|com.google.android.apps.plus|com.google.android.street|com.google.android.talk|com.google.android.youtube) logbuff echo -ne "\x20!"; continue;; esac; # skip current APK if it fails a free space check if [ "$(freespace-estimate $datapk $magisk$sysapk)" != 0 ]; then logbuff echo -ne "\x20*"; continue; fi; # remove libraries from copied APK and zipalign if the binaries exist if [ ! "$nozipbins" ]; then cp -fp $datapk $gtmp/preopt-$i; zip -d $gtmp/preopt-$i lib/*/*; zipalign 4 $gtmp/preopt-$i $gtmp/$i; datapk=$gtmp/$i; fi; # extract and force copy libraries to /system, abort if fails final total space check unzip /data/app/$i -d $gtmp lib/*; for j in $(ls $gtmp/lib/*/); do liblist="$liblist $magisk/system/lib/$j"; done; if [ "$(freespace-precise "$magisk$sysapk $liblist" "$datapk $gtmp/lib/*/*" )" != 0 ]; then logbuff echo -ne "\x20**"; continue; fi; $bb chmod 644 $gtmp/lib/*/*; if [ "$dryrun" == 0 ]; then mount -o rw,remount -t auto $magisk/system; rm -f $magisk$sysapk; cp -fp $gtmp/lib/*/* $magisk/system/lib/; # overwrite /system APK with new /data APK then fix permissions cp -f $datapk $magisk$sysapk; chown root.root $magisk$sysapk; $bb chmod 644 $magisk$sysapk; fi; # Lollipop support elif [ "$sdkver" -le 22 ]; then datapk=/data/app/$i/base.apk; sysname=`basename $sysapk`; # save time on boots after APK work has been completed but apps are awaiting optimization if [ ! -f /data/app/$i/integrated ]; then # skip current APK if it fails a free space check if [ "$(freespace-estimate $datapk $magisk$sysapk/$sysname.apk)" != 0 ]; then logbuff echo -ne "\x20*"; continue; fi; # remove libraries from copied APK and zipalign if the binaries exist if [ ! "$nozipbins" ]; then # workaround for Chrome not playing by the usual rules (per usual) case $package in com.android.chrome|com.chrome.*) if [ "$dryrun" == 0 ]; then mount -o rw,remount -t auto $magisk/system; rm -f $magisk$sysapk/lib/*/libchrome.so; fi;; *) cp -fp $datapk $gtmp/preopt-$i.apk; zip -d $gtmp/preopt-$i.apk lib/*/*; zipalign 4 $gtmp/preopt-$i.apk $gtmp/$i.apk; datapk=$gtmp/$i.apk;; esac; fi; # end nozipbins test # abort if fails final total space check if [ "$(freespace-precise "$magisk$sysapk" "$datapk /data/app/$i/lib" )" != 0 ]; then logbuff echo -ne "\x20**"; continue; fi; if [ "$dryrun" == 0 ]; then # force copy libraries to /system respecting symlinks then clean up empty files mount -o rw,remount -t auto $magisk/system; rm -f $magisk$sysapk/$sysname.apk; cp -RLf /data/app/$i/lib $magisk$sysapk; for j in `ls $magisk$sysapk/lib/*/*`; do [ ! -s $j ] && rm -f $j; done; # overwrite /system APK with new /data APK then fix permissions cp -fp $datapk $magisk$sysapk/$sysname.apk; chown -R root.root $magisk$sysapk; $bb chmod -R 755 $magisk$sysapk/lib; $bb chmod 644 $magisk$sysapk/$sysname.apk $magisk$sysapk/lib/*/*; # flag for cleanup on reboot following optimization touch /data/app/$i/integrated; fi; fi; # end integrated test if [ "$dryrun" == 0 ]; then # remove packages.xml entry for /data APK and ensure proper ownership/permissions md5sum $xml; if [ "$(grep "')" ]; then $bb sed -i "//d" $xml; fi; chown system:system $xml; $bb chmod 660 $xml; md5sum $xml; fi; # Marshmallow (and above) support elif [ "$sdkver" -ge 23 ]; then datapk=/data/app/$i/base.apk; sysname=`basename $sysapk`; # save time on boots after APK work has been completed but apps are awaiting optimization if [ ! -f /data/app/$i/integrated ]; then # skip current APK if it fails a free space check if [ "$(freespace-estimate $datapk $magisk$sysapk/$sysname.apk)" != 0 ]; then logbuff echo -ne "\x20*"; continue; fi; # decompress libraries within copied APK and zipalign if the binaries exist (maintains proper Marshmallow and above structure) if [ ! "$nozipbins" ]; then unzip $datapk -d $gtmp lib/*; cp -fp $datapk $gtmp/preopt-$i.apk; zip -d $gtmp/preopt-$i.apk lib/*/*; cd $gtmp; zip -r -0 -D $gtmp/preopt-$i.apk lib/; zipalign -p 4 $gtmp/preopt-$i.apk $gtmp/$i.apk; datapk=$gtmp/$i.apk; # abort if fails final total space check datoat=/data/app/$i/oat; [ -d $magisk$sysapk/oat ] || unset datoat; if [ "$(freespace-precise "$magisk$sysapk/$sysname.apk $magisk$sysapk/oat $magisk$sysapk/lib" "$datapk $datoat" )" != 0 ]; then logbuff echo -ne "\x20**"; continue; fi; if [ "$dryrun" == 0 ]; then # remove any unpacked libraries in the /system APK directory since they are no longer needed mount -o rw,remount -t auto $magisk/system; [ -d $magisk$sysapk/lib ] && rm -rf $magisk$sysapk/lib; fi; else # otherwise force copy libraries to /system respecting symlinks then clean up empty files, abort if fails final total space check datoat=/data/app/$i/oat; [ -d $magisk$sysapk/oat ] || unset datoat; if [ "$(freespace-precise "$magisk$sysapk/$sysname.apk $magisk$sysapk/oat $magisk$sysapk/lib" "$datapk $datoat /data/app/$i/lib" )" != 0 ]; then logbuff echo -ne "\x20**"; continue; fi; if [ -e /data/app/$i/lib/* -a "$dryrun" == 0 ]; then mount -o rw,remount -t auto $magisk/system; cp -RLf /data/app/$i/lib $magisk$sysapk; for j in `ls $magisk$sysapk/lib/*/*`; do [ ! -s $j ] && rm -f $j; done; fi; fi; # end nozipbins test if [ "$dryrun" == 0 ]; then # if necessary force copy APK odex file to /system mount -o rw,remount -t auto $magisk/system; if [ -d $magisk$sysapk/oat ]; then cp -fp /data/app/$i/oat $magisk$sysapk; cd $magisk$sysapk/oat/*/; mv base.odex $sysname.odex; fi; # overwrite /system APK with new /data APK then fix permissions cp -fp $datapk $magisk$sysapk/$sysname.apk; chown -R root.root $magisk$sysapk; $bb chmod -R 755 $magisk$sysapk/lib $magisk$sysapk/oat; $bb chmod 644 $magisk$sysapk/$sysname.apk $magisk$sysapk/lib/*/* $magisk$sysapk/oat/*/*; # flag for cleanup on reboot following optimization touch /data/app/$i/integrated; fi; fi; # end integrated test if [ "$dryrun" == 0 ]; then # remove packages.xml entry for /data APK and ensure proper ownership/permissions md5sum $xml; if [ "$(grep "')" ]; then $bb sed -i "//d" $xml; fi; chown system:system $xml; $bb chmod 660 $xml; md5sum $xml; # workaround for APKs that cause breakage after integration by temporarily removing runtime-permissions.xml to force rebuild case $package in com.google.android.calendar|com.google.android.inputmethod.*|com.google.android.play.games|com.google.android.tts|com.google.android.webview) logbuff echo -ne "\x20~"; if [ ! -d $gbak ]; then mkdir -p $gbak; for rtperm in /data/system/users/*/runtime-permissions.xml; do if [ -e "$rtperm" ]; then user=`basename $(dirname $rtperm)`; cp -fp $rtperm $gbak/$user; rm -f $rtperm; fi; done; fi;; esac; fi; fi; # end sdkver test if [ "$dryrun" == 0 ]; then mount -o ro,remount -t auto $magisk/system; fi; else if [ "$dryrun" == 0 ]; then # clean up any duplicate packages that are not the current active copy rm -rf /data/app/$i; fi; fi; # end datver+sysver test elif [ -f /data/app/$i/integrated ]; then # clean up to mimic pre-Lollipop (AOSP) behavior rm -rf /data/app/$package-*; fi; # end sysapk test done; # end find unintegrated loop # global cleanups required on Lollipop (and above) if [ "$sdkver" -ge 21 -a "$dryrun" == 0 ]; then # fix /system/lib permissions to ensure libs copied via symlink are correct mount -o rw,remount -t auto $magisk/system; [ "$magisk" ] && mount -o rw,remount -t auto /system; chown root.root $magisk/system/lib/*.so; $bb chmod 644 $magisk/system/lib/*.so; mount -o ro,remount -t auto $magisk/system; [ "$magisk" ] && mount -o ro,remount -t auto /system; fi; rm -rf $gtmp; # write buffered log once /sdcard is available, fallback to a known working location if this fails counter3=0; until [ "$(writable /sdcard)" == 0 -o "$counter3" -gt "$sdlimit" ]; do sleep 1; counter3=$((counter3 + 1)); done; [ "$counter3" -gt "$sdlimit" ] && log=/data/local/gapps-integrator.log; [ ! -s $log ] && echo -e "## GApps Auto-Integration Script Log\n#\n# ~ = forced permissions list rebuild,\n# * = not enough free space, ! = excluded\n" > $log; echo "$logbuff" >> $log; echo "---" >> $log; # limit log length to ~16Kb (when exceeded) by removing oldest entry [ "$(du -k $log | cut -f1)" -gt 16 ] && $bb sed -i "/$(grep -m1 -vE '^$|^#.*$' $log)/,/^\s*$/d" $log; # restore runtime-permissions.xml backup files to maintain granted user app permissions and reboot after boot is completed counter4=0; if [ -d "$gbak" -a "$dryrun" == 0 ]; then until [ "$(getprop sys.boot_completed)" == 1 -o "$counter4" -gt "$sdlimit" ]; do sleep 1; counter4=$((counter4 + 1)); done; if [ "$counter4" -gt "$sdlimit" ]; then echo "Ooops! Timeout for boot completion to restore user app runtime-permissions files" >> $log; echo "---" >> $log; fi; for user in `ls $gbak`; do cp -fp $gbak/$user /data/system/users/$user/runtime-permissions.xml; done; rm -rf $gbak; echo "Rebooting after restoring user app runtime-permissions files" >> $log; echo -e "---\n" >> $log; reboot; fi; echo -ne "\n" >> $log; echo "---"; }& ) >> $out 2>&1;