Pull to refresh

Работа с cron под Android и добавление shell-скрипта в автозапуск при загрузке устройства

Reading time13 min
Views17K


В связи с тем, что мобильные устройства уже давно имеют обширный функционал, то задачи автоматизации можно смело переносить и на них. И, как нельзя лучше, здесь так же хорошо подходит cron для их выполнения. Но если в «обычных» Linux системах настройка cron занимает мало времени, то Android устройство требует более сложной работы по его настройке.

Если тебе интересна тема автоматизации и ты хочешь, чтобы твои shell-скрипты запускались сразу же после загрузки устройства, да еще и могли бы запускаться по таймеру — добро пожаловать под кат!

Предисловие


Я занимаюсь автоматизацией мобильных устройств под Android. И во время выполнения автоматических скриптов происходит множество непредвиденных ситуаций, даже если для тестирования используются одинаковые устройства.

Самые популярные проблемы:

0. Скрипт автоматизации выполняет не то, что ты хотел
1. Мобильное приложение автоматически выгружается
2. Автоматическая перезагрузка телефона
3. Мобильное приложение автоматически не запускается после перезапуска
4. Wi-Fi модуль произвольно отключается, не находит сеть, не подключается к сети
5. Мобильная сеть неожиданно пропала
6. Телефон ушел в спящий режим
7. Отпал прокси или сам сервер или сервер вернул странный ответ

Из-за этого приходится постоянно следить за устройством и отлавливать эти непредвиденные ситуации.



Таким образом, я пришел к тому, что cron с «правильными» скриптами позволит отследить программные сбои и восстановить скрипт автоматизации или запустить его заново. Но как оказалось, хотя Android содержит ядро Linux, но есть особые нюансы, с которыми пришлось разбираться. Итак, давайте приступим к настройке!

Настройка Cron


Настраиваем окружение


  1. Устанавливаем adb для доступа к устройству через shell используя usb-провод.
  2. Открываем раздел Для разработчиков. Для этого заходим в раздел О телефоне и делаем несколько нажатий на Номер сборки или на что-то похожее.
  3. Заходим в раздел Для разработчиков и включаем его. Подключаем устройство к компьютеру и разрешаем доступ на этом компьютере к данному устройству.
  4. Добавляем root для вашего устройства. Самые распространенные варианты — это SuperSu, Magisk и Kingroot. Зайдите на 4pda и найдите вариант root'а под ваше устройство. К сожалению, универсального root'а не существует.
  5. Устанавливаем BusyBox (так же есть на 4pda, например), так как он как раз содержит в себе cron-программу.

Настройка ручного запуска


  1. Подключаемся к телефону с помощью adb shell (если adb у вас не прописана в переменной среды окружения, то добавьте полный путь.
  2. Переходим в режим root с помощью команды su
  3. Проверяем наличие cron-программы и посмотрим настройки с помощью команды crond -h

результат выполнения
crond: invalid option -- h
BusyBox v1.29.2-Stericson (2018-08-12 11:19:12 EDT) multi-call binary.

Usage: crond -fbS -l N -d N -L LOGFILE -c DIR

	-f	Foreground
	-b	Background (default)
	-S	Log to syslog (default)
	-l N	Set log level. Most verbose 0, default 8
	-d N	Set log level, log to stderr
	-L FILE	Log to FILE
	-c DIR	Cron dir. Default:/var/spool/cron/crontabs


Как видно из последней строки, инструкции по умолчанию должны храниться в директории /var/spool/cron/crontabs, которая автоматически не создается и если мы запустим команду
crond -b
и потом проверим запустился ли процесс через
ps | grep crond
, то его там может и не быть, т.к. он не смог получить какие-либо инструкции. Поэтому давайте выполним команду
crond -b -fd0
и посмотрим в чем причина. Скорее всего у вас будет подобная ошибка:
crond: can't change directory to '/var/spool/cron/crontabs': No such file or directory. В данном случае это нормально, т.к. в будущем мы сами укажем путь до исолняемого crontab файла.

4. Создадим простой crontab файл:

mkdir /data/crontab
echo "*/1 * * * * echo 'text' >> /sdcard/test.txt" > /data/crontab/root

Теперь у нас есть задание, которое каждую минуту будет добавлять слово text в файл /sdcard/test.txt
Запускаем:
crond -b -fd0 -c /data/crontab
и получаем следующий лог:

crond: crond (busybox 1.29.2-Stericson) started, log level 0
crond: ignoring file 'root' (no such user)
...

Конечно, немного удивляет, ведь если мы выполним команду whoami то в результате она вернёт root.

5. Добавим пользователя root, раз crond просит:

mount -o remount,rw /system;
echo "root:x:0:0::/system/etc/crontabs:/system/bin/sh" >> /system/etc/passwd;
mount -o remount,ro /system;

Из-за отсутствия данного файла, я понял, что в Android системе он совсем не задействован. Если вы уверены в том, где вы будете хранить свои crontab файлы, то вы можете заменить строку /system/etc/crontabs на нужную вам. Снова выполняем команду

crond -b -fd0 -c /data/crontab

И получаем следующее:

crond: user:root entry:*/1 * * * * echo 'text' >> /sdcard/test.txt
111111111111111111111111111111111111111111111111111111111111
111111111111111111111111
11111111111111111111111111111111
111111111111
1111111
crond: wakeup dt=16
crond: file root:
crond:  line echo 'text' >> /sdcard/test.txt
crond:  job: 0 echo 'text' >> /sdcard/test.txt
crond: can't change directory to '/system/etc/crontabs'
crond: can't change directory to '/var/spool/cron': No such file or directory
crond: USER root pid 12849 cmd echo 'text' >> /sdcard/test.txt

Хотя, если верить логу задача в crond прописалась, но в моем случае файл не создался. Решить проблему можно очень просто:

mkdir -p /system/etc/crontabs

Ну хочет он, чтобы существовала там директория, кто мы такие, чтобы ему запрещать! Запускаем снова и видим:

crond: user:root entry:*/1 * * * * echo 'text' >> /sdcard/test.txt
111111111111111111111111111111111111111111111111111111111111
111111111111111111111111
11111111111111111111111111111111
111111111111
1111111
crond: wakeup dt=12
crond: file root:
crond:  line echo 'text' >> /sdcard/test.txt
crond:  job: 0 echo 'text' >> /sdcard/test.txt
crond: child running /system/bin/sh
crond: USER root pid 13033 cmd echo 'text' >> /sdcard/test.txt

Ошибки ушли, и появилась строка crond: child running /system/bin/sh. Наконец-то cron у нас успешно завелся, и можно переходить ко второй части!

Автоматическая загрузка shell-скрипта


В Linux системе есть директория init.d, которая отвечает за автозапуск сразу же после загрузки системы, поэтому попробуем идти по этому пути!

1. Проверяем, существует ли данная директория у вас на устройстве (это /etc/init.d либо /system/etc/init.d — это тот же смонтированный раздел etc ). В моем случае её нет. Ну что, тогда создаем:

mount -o remount,rw /system
mkdir /system/etc/init.d
chmod 0755 /system/etc/init.d
mount -o remount,ro /system

Теперь добавим туда какой-нибудь простенький скрипт, например:

echo "echo 'Init.d is working !!!' >> /sdcard/init_test.log" > /system/etc/init.d/90my_script
chmod 777 /system/etc/init.d/90my_script

Перезагружаем устройство и смотрим, случилось ли чудо… К сожалению, у меня файл не появился.

Исследуем систему дальше и ищем какой-нибудь init файл, который может запускать скрипты после запуска. У меня на устройстве оказался файл в /init.rc. Ну что, попробуем его изменить и перезагрузим устройство:

mount -o remount,rw /
echo "echo 'Init.d is working !!!' >> /sdcard/init_test.log" >> /init.rc
mount -o remount,ro /
reboot

Но файл опять не создался. Идем смотреть на файл /init.rc и наша запись пропала и файл как бы и не менялся, т.к. дата создания стоит совсем какая-то странная (в моем случае 01 янв. 70 05:00:00).

Продолжаем разбираться, и оказывается что данный файл храниться в boot.img, и каждый раз достается из него. И для того, чтобы изменить функционал файла init.rc нужно выполнить все это.

Но есть более простой способ, который поможет решить данную задачу. Для этого способа мы можем использовать следующий shell-скрипт (скажем спасибо Ryuinferno):

Shell-скрипт
#!/system/bin/sh
#Script to enable init.d by Ryuinferno @ XDA

error_msg(){
echo "You do not need this mod..."
sleep 1
echo "If you are reapplying, please delete these files if present:"
echo "/system/bin/sysinit"
sleep 1
echo "/system/etc/install-recovery.sh"
sleep 1
echo "/system/etc/install-recovery-2.sh"
sleep 1
echo "And run again..."
sleep 1
echo "If init.d is still not working, read the FAQ part in my thread..."
sleep 1
echo "Aborting..."
mount -o remount,ro -t auto /system
echo ""
echo "Ryuinferno @ XDA"
exit 1
}

echo "Init.d Enabler by Ryuinferno @ XDA"
echo ""
sleep 1

id=`id`; 
id=`echo ${id#*=}`; 
id=`echo ${id%%\(*}`; 
id=`echo ${id%% *}`
if [ "$id" != "0" ] && [ "$id" != "root" ]; then
	echo "Script NOT running as root!"
	sleep 1
	echo "Superuser access not granted!"
	sleep 1
	echo "Please type 'su' first before running this script..."
	exit 1
else
	echo "Hello Supaa User! :P"
	echo ""
	sleep 1
fi

if [ ! "'which busybox'" ]; then
	echo "busybox NOT INSTALLED!"
	sleep 1
	echo "Please install busybox first!"
	exit 1
else
	echo "busybox found!"
	sleep 1
fi

bbb=0

if [ ! "`which grep`" ]; then 
	bbb=1
	echo "grep applet NOT FOUND!"
	sleep 1
	else 
	echo "Awesome! grep found! :D"
	sleep 1
fi

if [ ! "`which run-parts`" ]; then 
	bbb=1
	echo "run-parts applet NOT FOUND!"
	sleep 1
	else
	echo "Good! run-parts found! :)"
	echo ""
	sleep 1
fi

if [ $bbb -eq 1 ] ; then
	echo ""
	echo "Required applets are NOT FOUND!"
	echo ""
	sleep 1
	echo "Please reinstall busybox!"
	exit 1
fi

echo "Great! Let's proceed..."
echo ""
sleep 1
echo "Press enter to continue..."
read enterKey

clear
sleep 1
echo "Mounting system as rewritable..."
mount -o remount,rw -t auto /system

sleep 1 
echo "Removing old sysinit file"
rm /system/bin/sysinit

sleep 1
echo ""
echo "Checking for the presence of sysinit in /system/bin..."
sleep 1
if [ -e /system/bin/sysinit ]; then
	echo "sysinit found..."
	if [ -z "`cat /system/bin/sysinit | grep "init.d"`" ]; then
		echo "Adding lines to sysinit..."
		echo "" >> /system/bin/sysinit
		echo "# init.d support" >> /system/bin/sysinit
		echo "" >> /system/bin/sysinit
		echo "export PATH=/sbin:/system/sbin:/system/bin:/system/xbin" >> /system/bin/sysinit
		echo "run-parts /system/etc/init.d" >> /system/bin/sysinit 
		echo "" >> /system/bin/sysinit
	else
		echo ""
		echo "Your sysinit should already be running the scripts in init.d folder at boot..."
		error_msg
	fi
else
	echo "sysinit not found, creating file..."
	echo "#!/system/bin/sh" > /system/bin/sysinit
	echo "# init.d support" >> /system/bin/sysinit
	echo "" >> /system/bin/sysinit
	echo "export PATH=/sbin:/system/sbin:/system/bin:/system/xbin" >> /system/bin/sysinit
	echo "run-parts /system/etc/init.d" >> /system/bin/sysinit 
	echo "" >> /system/bin/sysinit
fi

sleep 1
echo "Setting correct permissions and ownership for sysinit..."
chmod 755 /system/bin/sysinit
chown 0.2000 /system/bin/sysinit

sleep 1
echo ""
echo "Checking for the presence of install-recovery.sh..."
sleep 1
if [ -f /system/etc/install-recovery.sh ] && [ -z "`cat /system/etc/install-recovery.sh | grep "daemon"`" ]; then
	if [ ! -z "`cat /system/etc/install-recovery.sh | grep "init.d"`" ];then
		echo "Your install-recovery.sh seems to be already modified for init.d..."
		error_msg
	fi
	echo "install-recovery.sh found, renaming it as install-recovery-2.sh..."
	mv /system/etc/install-recovery.sh /system/etc/install-recovery-2.sh
	echo "Recreating install-recovery.sh..."
	echo "#!/system/bin/sh" > /system/etc/install-recovery.sh
	echo "# init.d support" >> /system/etc/install-recovery.sh
	echo "" >> /system/etc/install-recovery.sh
	echo "/system/bin/sysinit" >> /system/etc/install-recovery.sh
	echo "" >> /system/etc/install-recovery.sh
	echo "# excecuting extra commands" >> /system/etc/install-recovery.sh
	echo "/system/etc/install-recovery-2.sh" >> /system/etc/install-recovery.sh
	echo "" >> /system/etc/install-recovery.sh
elif [ -f /system/etc/install-recovery.sh ] && [ ! -z "`cat /system/etc/install-recovery.sh | grep "daemon"`" ]; then
	if [ -f /system/etc/install-recovery-2.sh ] && [ ! -z "`cat /system/etc/install-recovery-2.sh | grep "init.d"`" ];then
		echo "Your install-recovery-2.sh seems to be already modified for init.d..."
		error_msg
	fi
	echo "install-recovery.sh is used for superuser, using install-recovery-2.sh instead..."
	if [ -f /system/etc/install-recovery-2.sh ]; then
		echo "" >> /system/etc/install-recovery-2.sh
		echo "# init.d support" >> /system/etc/install-recovery-2.sh
		echo "/system/bin/sysinit" >> /system/etc/install-recovery-2.sh
		echo "" >> /system/etc/install-recovery-2.sh
	else
		echo "#!/system/bin/sh" > /system/etc/install-recovery-2.sh
		echo "# init.d support" >> /system/etc/install-recovery-2.sh
		echo "" >> /system/etc/install-recovery-2.sh
		echo "/system/bin/sysinit" >> /system/etc/install-recovery-2.sh
		echo "" >> /system/etc/install-recovery-2.sh
	fi
	if [ -z "`cat /system/etc/install-recovery.sh | grep "install-recovery-2.sh"`" ]; then
		echo "" >> /system/etc/install-recovery.sh
		echo "# extra commands" >> /system/etc/install-recovery.sh
		echo "/system/etc/install-recovery-2.sh" >> /system/etc/install-recovery.sh
		echo "" >> /system/etc/install-recovery.sh
	fi
else
	echo "install-recovery.sh not found, creating it..."
	echo "#!/system/bin/sh" > /system/etc/install-recovery.sh
	echo "# init.d support" >> /system/etc/install-recovery.sh
	echo "" >> /system/etc/install-recovery.sh
	echo "/system/bin/sysinit" >> /system/etc/install-recovery.sh
	echo "" >> /system/etc/install-recovery.sh
fi

sleep 1
echo "Setting the correct permissions and ownership for install-recovery.sh..."
echo "Also for install-recovery-2.sh if it exists..."
chmod 755 /system/etc/install-recovery.sh
chown 0.0 /system/etc/install-recovery.sh
if [ -f /system/etc/install-recovery-2.sh ]; then
	chmod 755 /system/etc/install-recovery-2.sh
	chown 0.0 /system/etc/install-recovery-2.sh
fi

sleep 1
echo ""
echo "Checking for the presence of the init.d folder..."
sleep 1
if [ -d /system/etc/init.d ]; then
	echo "init.d folder found..."
else 
	echo "init.d folder not found, creating the folder..."
	mkdir /system/etc/init.d
fi

sleep 1
echo ""
echo "Creating basic init.d scripts..."
echo "#!/system/bin/sh" > /system/etc/init.d/08setperm
echo "#set correct permissions to /system/etc/init.d folder" >> /system/etc/init.d/08setperm
echo "" >> /system/etc/init.d/08setperm
echo "mount -o remount,rw -t auto /system" >> /system/etc/init.d/08setperm
echo "chmod -R 777 /system/etc/init.d" >> /system/etc/init.d/08setperm
echo "mount -o remount,ro -t auto /system" >> /system/etc/init.d/08setperm
echo "" >> /system/etc/init.d/08setperm

echo "#!/system/bin/sh" > /system/etc/init.d/00test
echo "#init.d test" >> /system/etc/init.d/00test
echo "" >> /system/etc/init.d/00test
echo "if [ -f /data/Test.log ]; then" >> /system/etc/init.d/00test
echo "rm /data/Test.log" >> /system/etc/init.d/00test
echo "fi" >> /system/etc/init.d/00test
echo "" >> /system/etc/init.d/00test
echo 'echo "Init.d is working !!!" >> /data/Test.log' >> /system/etc/init.d/00test
echo 'echo "excecuted on $(date +"%d-%m-%Y %r" )" >> /data/Test.log' >> /system/etc/init.d/00test
echo "" >> /system/etc/init.d/00test

sleep 1
echo "Creating permissive SELinux script..."
sleep 1
echo "#!/system/bin/sh" >> /system/etc/init.d/01permissive
echo "#Init.d Permissive SELinux" >> /system/etc/init.d/01permissive
echo "" >> /system/etc/init.d/01permissive
echo "busybox mount -o remount,rw -t auto /system" >> /system/etc/init.d/01permissive
echo "" >> /system/etc/init.d/01permissive
echo "setenforce 0" >> /system/etc/init.d/01permissive
echo "SELINUX=permissive" >> /system/etc/init.d/01permissive
echo "" >> /system/etc/init.d/01permissive

sleep 1
echo "Setting correct permissions and ownership for init.d folder and scipts..."
chmod 777 /system/etc/init.d
chmod 777 /system/etc/init.d/08setperm
chmod 777 /system/etc/init.d/00test
chmod 777 /system/etc/init.d/01permissive
chown 0.0 /system/etc/init.d
chown 0.0 /system/etc/init.d/08setperm
chown 0.0 /system/etc/init.d/00test
chown 0.0 /system/etc/init.d/01permissive

sleep 1
echo ""
echo "Mounting system as read-only..."
mount -o remount,ro -t auto /system
sleep 1
echo ""
echo "Done!!!"
sleep 1
echo "Please reboot at least twice before checking /data..."
sleep 1
echo "If init.d is working, you will see a Test.log in /data..."
sleep 1
echo ""
echo "Enjoy!!! =)"
echo "Ryuinferno @ XDA 2013"
exit


Приступаем к внедрению скрипта! В моем случае он будет называться init.sh.
1. Загружаем файл на sdcard мобильного устройства:
adb push /tmp/init.sh /sdcard

2. Копируем в память мобильного устройства и устанавливаем нужные права:
adb shell
su
cp /sdcard/init.sh /data/init.sh
chmod 777 /data/init.sh

3. Запускаем на выполнение:
/data/init.sh

И обращаем внимание на лог, который выводится. Вот мой лог:
Лог выполнения
Init.d Enabler by Ryuinferno @ XDA

Hello Supaa User! :P

busybox found!
Awesome! grep found! :D
Good! run-parts found! :)

Great! Let's proceed...

Press enter to continue...
Mounting system as rewritable...
Removing old sysinit file
rm: /system/bin/sysinit: No such file or directory

Checking for the presence of sysinit in /system/bin...
sysinit not found, creating file...
Setting correct permissions and ownership for sysinit...

Checking for the presence of install-recovery.sh...
install-recovery.sh not found, creating it...
Setting the correct permissions and ownership for install-recovery.sh...
Also for install-recovery-2.sh if it exists...

Checking for the presence of the init.d folder...
init.d folder found...

Creating basic init.d scripts...
Creating permissive SELinux script...
Setting correct permissions and ownership for init.d folder and scipts...

Mounting system as read-only...

Done!!!
Please reboot at least twice before checking /data...
If init.d is working, you will see a Test.log in /data...

Enjoy!!! =)
Ryuinferno @ XDA 2013


Как видим из лога, ошибок нет, поэтому смело перезагружаем устройство! Возможно у кого-то уже все заработало и вы смогли найти файл /data/Test.log, но у меня его нет. Проверим директорию /system/etc/init.d используя команду ls:

00test
01permissive
08setperm

Как видим, задачи успешно созданы. Возможно все же придется менять boot.img, но давайте в начале проверим, а где у нас файл install-recovery.sh с помощью команды


find / -name "install-recovery.sh"
...
/system/bin/install-recovery.sh
/system/etc/install-recovery.sh
...

Как можем заметить, у нас 2 файла, которые лежат в разных местах. По дате создания мы можем заметить, что скрипт создал файл в директории /system/etc/install-recovery.sh, хотя, возможно, в некоторых случаях он должен создавать его в /system/etc. Давайте переименуем файл в bin и скопируем файл из etc:

mount -o remount,rw /system
mv /install-recovery.sh /system/bin/install-recovery2.sh
cp /install-recovery.sh /system/bin/


UPD: Обратите внимание, что контекст безопасности у обоих файлов должен совпадать. И если вдруг при копировании у вас он сбился (хотя по идее, такого быть не должно), вам необходимо его будет восстановить (например, через утилиту chcon). Посмотреть полную информацию по файлу можно с помощью ls -lZ:
ls -lZ /system/etc/install-recovery.sh
#результат
-rwxr-xr-x root     root              u:object_r:system_file:s0 install-recovery.sh

Тут u:object_r:system_file:s0 и является контекстов безопасности.

И снова перезагружаем устройство… И вот, наконец-то долгожданный УСПЕХ! Файл /data/Test.log появился!

Раз все работает, идем в /system/etc/init.d и создаем shell-скрипт. А в нем как-раз запустим наш crond на выполнение:

echo "#!/system/bin/sh
crond -b -L /sdcard/error.log -c /data/crontab" > /system/etc/init.d/99cronstart
chmod 777 /system/etc/init.d/99cronstart
reboot

После загрузки проверяем, запустился ли crond:

ps | grep crond
root      414   1     9532   236   hrtimer_na 000dcf90 S crond

И на этом могли бы мы уже закончить, но давайте подождем минуту и посмотрим, произошла ли запись в наш файл… Ну как вы уже поняли, опять ни чего не сработало. Дело в том, что данный процесс нужно запустить от супер пользователя. Изменим скрипт в файле 99cronstart:

echo "#!/system/bin/sh
/su/bin/su -c crond -b -L /sdcard/error.log -c /data/crontab" > /system/etc/init.d/99cronstart
reboot


UPD: Возможно в вашем случае su будет иметь другой путь, тогда воспользуйтесь командой which su и замените путь, на ваш.

Теперь наше Android устройство поддерживает и задачи cron и может содержать shell-скрипты для автоматического запуска!

Ну и напоследок, скрипт, который будет запускать наше приложение, если его нет в процессах и сохранять информацию о том, что находилось на главном экране до запуска нашего приложения:

proc=$(ps | grep "com.test.app")
if [ "$proc" == "" ]; then
    dumpsys window | grep CurrentFocus > /sdcard/current_focus.dump
    sleep 1
    am start -n com.test.app/com.test.app.activities.MainActivity
fi
Tags:
Hubs:
+9
Comments9

Articles

Change theme settings