Введение в использование unix shell в рекавери

от
Android    прошивка

Эта статья будет полезна тем кто хочет писать свои установщики/патчи/оптимизаторы для recovery андроида. Перед прочтением рекомендуется знать bash-скриптинг хотя бы минимально.

Install zip: как работает?
Когда вы в twrp или cwm выбираете архив для прошивки, update-binary из него распаковывается и запускается, передавая ему 3 переменные:
$1 - API level: число от 1 до 3, нам это не пригодится
$2 - Дескриптор pipe для обратной связи с рекавери
$3 - путь к zip файлу.

Структура zip архива
В корне зипа обязательно должен быть файл META-INF/com/google/android/update-binary. Не смотря на то что он называется binary, он может быть и shell-скриптом. Кроме того, в папке META-INF могут располагаться файлы сертификатов (если зип подписан). Все остальные файлы пользователь кладет в архив по своему усмотрению.

Update-binary: Edify vs Unix Shell
Update-binary - исполняемый файл, который запускается в первую очередь. От него зависит что будет делать зип архив.
В настоящий момент популярно 2 типа update-binary: бинарник, написанный на C неким Edify, и скрипт на unix shell. В случае с edify, в папке с update-binary должен лежать файл updater-script, который вы пишите сами. Мануал по updater-script для edify можно посмотреть вот тут. Недостатки edify - во-первых, не на всех рекавери он может запуститься, а во-вторых, edify script не удобен. Сравните сами:
Удаление папки:
Edify: delete_recursive("/path/folder");
shell: rm -r /path/folder
Выставление разрешений:
Edify: set_perm("0777","file");
Shell: chmod 777 file
В этой статье будет рассмотрен скриптинг именно на shell.

Hello, world!
Казалось бы, что может быть проще. echo "Hello world"? А вот и нет! В рекавери все нужно отправлять в /proc/self/fd/$2. Так что сразу запишем этот путь в переменную, например, outfd. echo "hello world" > $outfd? И опять не правильно! outfd имеет свои команды. Итак, рассмотрим готовый пример:

  1. #!/sbin/sh
  2. outfd=/proc/self/fd/$2
  3. ui_print() {
  4.     echo "ui_print $1" > $outfd
  5.     echo "ui_print" > $outfd
  6. }
  7. ui_print "Hello, world!"
Как видите, в начале скрипта мы создали функцию ui_print, которая облегчит процесс передачи текста на экран. echo тут выполняется 2 раза, чтобы не было проблем с переносом строк. И, как вы успели заметить, первая строчка всегда начинается с #!/sbin/sh - такой путь к интерпретатору в рекавери (в самом андроиде путь /system/bin/sh)

Прошиваем что-нибудь
Положите пустой файл foo.txt в корень архива. Нам нужно скопировать его в /data/local/ update-binary будет выглядеть вот так:
  1. #!/sbin/sh
  2. ZIP="$3"
  3. mkdir -p /tmp/test # создать папку /tmp/test
  4. cd /tmp/test # перейти в нее
  5. unzip -o $ZIP # распаковать туда наш архив
  6. cp foo.txt /data/local # скопировать файл
Тут я обошелся без ui_print за ненадобностью. Ну из комментариев вроде все понятно, объяснять ничего не нужно.
Важно: иногда unzip не работает с архивами, сделанными ES проводником. Используйте Zarchiver.
Еще кое что: Когда вы распаковываете файл в /tmp, вы распаковываете его в ОЗУ. Если ваш архив слишком большой, распаковывайте его в /data/local.

Подводные камни
1.Если вам что-то нужно изменить в каком-то разделе, смонтируйте его командой mount.
mount /system
mount /data
и т.п. В конце скрипта лучше всего их потом отмонтировать (umount /data например).
2. Не используйте кириллицу в скрипте, кроме названий файлов.

Архитектура процессора
Большинство Android-устройств имеют архитектуру ARMv7 или v6. Реже - x86, еще реже - mips, Некоторые программы/бинарники заточены именно под определенную архитектуру. Узнать ее легко:
getprop ro.product.cpu.abi
Но getprop не всегда возвращает то что нужно. В этом случае лучше брать значения из build.prop и default.prop.
  1. #!/sbin/sh
  2. outfd=/proc/self/fd/$2
  3. ui_print(){
  4.     echo "ui_print $1" > $outfd
  5.     echo "ui_print" > $outfd
  6. }
  7. getabi(){
  8.     grep ^ro.product.cpu.abi $1 | cut -d = -f 2
  9. }
  10. mount /system
  11. abi=`getprop ro.product.cpu.abi`
  12. case $abi in
  13.     arm*|x86*|mips*) ;;
  14.     *) abi=`getabi /system/build.prop` ;; # если getprop не вернул то что нужно
  15. esac
  16. case $abi in
  17.     arm*|x86*|mips*) ;;
  18.     *) abi=`getabi /default.prop` ;; # если build.prop не вернул то что нужно
  19. esac
  20. case $abi in
  21.     arm*) arch=arm ;;
  22.     x86*) arch=x86 ;;
  23.     mips*) arch=mips ;;
  24.     *) ui_print "Your architecture is not supported" ; exit 0 ;; # если архитектура не подошла
  25. esac
  26. ui_print "Using architecture: $arch"
.
Ну а что дальше с этим делать, думайте сами. Можно создать в корне зипа папки arm,x86,mips, положить в них соотв. файлы и скопировать их:
  1. cp $arch/file /system/file

Прогрессбар
Во время прошивки zip, внизу отображается прогрессбар. Мы можем изменять его "заполненность".
  1. echo "progress 1 0" > $outfd # 1 - макс. значение, 0 - минимальное. Их можно менять.
  2. set_progress(){
  3.     echo "set_progress $1" > $outfd # изменение значения прогрессбара
  4. }
  5. set_progress 0.2
  6. set_progress 0.5 # тут прогрессбар будет заполнен наполовину
  7. set_progress 0.8
  8. set_progress 1 # а тут он будет заполнен полностью.
Первую команду мы используем всего лишь один раз. При значениях 1 и 0 в set_progress мы можем использовать любые числа от 0 до 1. При значениях 0 и 2 - от 0 до 2. Ну вы поняли.
А вообще, изменения прогрессбара не обязательны. Их можно вообще не включать в скрипт - они сделаны для крастоты :)

Подводим итоги
Итак, задача: распаковать архив, скопировать бинарник в /data/local (с учетом архитектуры), сделать на него симлинк в /system/xbin и сделать его исполняемым. Поехали.
  1. #!/sbin/sh
  2. outfd=/proc/self/fd/$2
  3. zip="$3"
  4. ui_print(){
  5.     echo "ui_print $1" > $outfd
  6.     echo "ui_print" > $outfd
  7. }
  8. echo "progress 1 0" > $outfd
  9. set_progress(){
  10.     echo "set_progress $1" > $outfd
  11. }
  12. getabi(){
  13.     grep ^ro.product.cpu.abi $1 | cut -d = -f 2
  14. }
  15. set_progress 0.1
  16. ui_print "Mounting partitions..."
  17. mount /system
  18. mount /data
  19. set_progress 0.3
  20. ui_print "Extacting files..."
  21. mkdir -p /tmp/test
  22. cd /tmp/test
  23. unzip -o "$zip"
  24. ui_print "Installing binary"
  25. set_progress 0.5
  26. abi=`getprop ro.product.cpu.abi`
  27. case $abi in
  28.     arm*|x86*|mips*) ;;
  29.     *) abi=`getabi /system/build.prop` ;;
  30. esac
  31. case $abi in
  32.     arm*|x86*|mips*) ;;
  33.     *) abi=`getabi /default.prop` ;;
  34. esac
  35. case $abi in
  36.     arm*) arch=arm ;;
  37.     x86*) arch=x86 ;;
  38.     mips*) arch=mips ;;
  39. *) ui_print "Your architecture is not supported" ; exit 0 ;;
  40. esac
  41. ui_print "Using architecture: $arch"
  42. set_progress 0.7
  43. cp $arch/binary /data/local
  44. ui_print "Setting permissions..."
  45. chmod 755 /data/local/binary
  46. ui_print "Symlinking..."
  47. set_progress 0.9
  48. ln -s /data/local/binary /system/xbin/binary
  49. ui_print "Instalation complete!"
  50. set_progress 1
  51. exit 0

Что там и как - вы поймете, если прочитали всю статью :)

Данная статья всего лишь показала вам, какие функции, команды и фичи можно использовать в рекавери. Для написания полноценных скриптов, необходимо знание языка bash.
Удачи вам, и помните: неправильный скрипт может окирпичить устройство!
  • +8
  • views 5722