категории | RSS

Использование Shell-скрипта в качестве update-binary в Zip-обновлениях системы Android

Новый способ создания update.zip для Android.

Когда я попробовал себя в написании хоть сколько нибудь сложного мода для Android - пришлось вплотную (а вернее с размаху носом) столкнуться с языком edify - на нём пишется updater-script в Android flashable пакетах. И столкновение это не прошло незамеченным для моей психики, вызвав ощущение собственной неполноценности, связанный с этим butthurt и лютобешеную ненависть к этому языку.
Соответственно я начал искать иные пути, чтобы использовать милый сердцу за свою мощь, красоту и простоту язык Unix Shell скриптов. И теперь хочу поделиться с Вами опытом.

Целью моих изысканий было найти возможность отображения состояния хода установки обновления на экране телефона, ведь без этого можно было и так использовать скрипт Shell, вызыывая его из edify скрипта. Собственно это и было успешно достигнуто.
Плюс несколько уменьшен размер Zip-обновления, оно занимает 91кБ, против 231 кБ у edify-версии.

Немного теории о том, как происходит установка Zip-архива из Recovery.
Recovery извлекает из архива с обновлением файл META-INF/com/google/android/update-binary в /tmp, делает ему chmod 777 и запускает передавая три параметра:
1. API level. Это число от 1 до 3 (в моём случае это было 2). Для нас полностью бесполезен.
2. Дескриптор Pipe для обратной связи с Recovery. С ним подробнее разберёмся позднее.
3. Полное имя Zip-обновления. Тут я полагаю всё ясно.

Немного поковырявшись в исходниках update-binary от edify и изучив состояние системы при установке и происходящие в ней процессы, был рождён следующий код


#!/sbin/sh
interface="/proc/$$/fd/$2"
zip="$3"
boot="mmcblk0p20"
set_progress() ( echo set_progress 0.$1 > $interface )
ui_print() ( echo ui_print $1 > $interface )

##main
cd /tmp/
set_progress 02
if grep -q shooteru /default.prop ; then ui_print "Shooteru device!" ; else ui_print "This is not shooteru device!" ; exit 1 ; fi
grep -q /system /proc/mounts || mount /system || exit 2
release=`grep 'ro.build.version.release' /system/build.prop|cut -d '=' -f 2`
set_progress 08
case $release in
4.1.1) ui_print "Android $release found, version is correct, continue";;
4.0.3) ui_print "Android $release found, version is correct, continue";;
*) ui_print "Android $release found, version is incorrect, break"; exit 4
esac
set_progress 10
ui_print "Extracting temporary files"
unzip -o $zip tmp/* -d / >/dev/null || exit 5
set_progress 20
chmod 0755 /tmp/mkbootimg || exit 6
chmod 0755 /tmp/unpackbootimg || exit 7
set_progress 30
ui_print "Unpacking Your's Boot"
/tmp/unpackbootimg /dev/block/$boot "/tmp/" || exit 8
set_progress 45
ui_print "Unpacking Your's ramdisk"
rm -r ramdisk
mkdir ramdisk || exit 9
cd ramdisk || exit 10
zcat /tmp/$boot-ramdisk.gz | cpio -i || exit 11
set_progress 55
ui_print "Updating ramdisk"
echo sys.usb.config=adb >> /tmp/ramdisk/default.prop
cp `which adbd` /tmp/ramdisk/sbin/adbd || exit 13
cp /system/*bin/sh /tmp/ramdisk/sbin || exit 14
chmod 0755 "/tmp/ramdisk/sbin/adbd" || exit 15
chmod 0755 "/tmp/ramdisk/sbin/sh" || exit 16
set_progress 60
ui_print "Repacking new ramdisk"
find . | cpio -o -H newc | gzip > /tmp/new-ramdisk.gz || exit 17
cd -
set_progress 70
ui_print "Preparing newboot.img"
/tmp/mkbootimg --kernel /tmp/$boot-zImage --ramdisk /tmp/new-ramdisk.gz --cmdline "`cat /tmp/$boot-cmdline`" --base `cat /tmp/$boot-base` --output /tmp/newboot.img || exit 18
set_progress 80
ui_print "Flashing newboot.img to $boot"
dd if=/dev/zero of=/dev/block/$boot bs=65536
set_progress 90
dd if=/tmp/newboot.img of=/dev/block/$boot bs=65536 || exit 19
set_progress 95
rm /tmp/newboot.img /tmp/new-ramdisk.gz /tmp/$boot* || exit 20
set_progress 98
umount /system || exit 23
set_progress 99


Давайте разберёмся в нём.
#!/sbin/sh
Строка, указывающся ядру, что делать со скриптом (вернее чем его исполнять). Указывает на интерпретатор команд.
interface="/proc/$$/fd/$2"
Определение переменной хранящей адрес канала по которому мы будем общаться с Recovery. $$ превращается в PID текущего процесса, а $2 - передвавемый нам из Recovery номер дескрпптора канала. Всё, что мы будем писать в него Recovery будет воспринимать как команды.
zip="$3"
Получаем в переменную имя зипа, оно нам ещё пригодится.
boot="mmcblk0p20"
Переменная будет хранить имя файла устройства, хранящего boot.img.
set_progress() ( echo set_progress 0.$1 > $interface )
ui_print() ( echo ui_print $1 > $interface )
Две строки, определяющие функции по работе с интерфейсом Recovery, аналогичные одноимённым командам edify. Как видим, используются две команды для этого интерфейса, ui_print (выводит текст в окошко на экране) и set_progress (передвигает прогресс-бар).
cd /tmp/
set_progress 02
Мы готовимся к работе, переходя в каталог /tmp/ и передвигаем прогресс-бар на уровень 2 процента. Далее я не буду описывать команду set_progress, её смысл одинаков везде.
if grep -q shooteru /default.prop ; then ui_print "Shooteru device!" ; else ui_print "This is not shooteru device!" ; exit 1 ; fi
Проверяем, запущен ли скрипт на правильном устройстве (ведь работа скрипта для одного устройства на другом может закончиться необратимым повреждением этого устройства) и прерываем процесс если это не так. Аналог getprop из edify, но неполный. Заставить настоящий gerprop работать у меня не вышло.
Обратите внимание на то, что мы пользуемся функцией ui_print которую определили выше. Далее я не буду описывать ui_print, из за его очевидности.
Также обратите внимание на команду exit 1 - она предназначена для облегчения отладки скрипта, и одновременно прерывания его работы в случае если что то пошло не так как надо. В случае ошибки при работе скрипта он будет прерван, а Recovery выведет код ошибки, по которому будет можно легко найти место где эта ошибка произошла.
grep -q /system /proc/mounts || mount /system || exit 2
Проверяем, смонтирован ли раздел /system, монтируем его если это не так, и прерываем работу с кодом 2 если монтирование не удалось. Далее я не буду описывать команду exit #, её смысл ясен и так.
release=`grep 'ro.build.version.release' /system/build.prop|cut -d '=' -f 2`
Получаем в переменную версию Android установленную на устройстве. Аналог getprop_file из edify.
case $release in
4.1.1) ui_print "Android $release found, version is correct, continue";;
4.0.3) ui_print "Android $release found, version is correct, continue";;
*) ui_print "Android $release found, version is incorrect, break"; exit 4
esac
И тут же проверяем её. Это просто образец, на самом деле этот мод будет работать на любом устройстве и прошивке, где есть кастомное Recovery и установлен Busybox.
unzip -o $zip tmp/* -d / >/dev/null || exit 5
Извлекаем из Zip-архива с обновлением каталог tmp/ со всеми файлами в каталог / (корень ФС, параметр -d) с перезаписью файлов при необходимости (параметр -o)
chmod 0755 /tmp/mkbootimg || exit 6
chmod 0755 /tmp/unpackbootimg || exit 7
Придаём извлеченным файлам атрибут исполняемости, чтобы их можно было запустить.
/tmp/unpackbootimg /dev/block/$boot "/tmp/" || exit 8
Распаковываем boot.img прямо из памяти устройства в каталог /tmp/
rm -r ramdisk
Удаляем (на случай если какой-то скрипт ранее уже создал его) каталог ramdisk со всем его содержимым
mkdir ramdisk || exit 9
Создаём этот каталог
cd ramdisk || exit 10
И переходим в него.
Обратите внимание, что после rm -r нет проверки на ошибку. Это сделано специально, ошибка здесь допустима, и более того - в нормальном режиме rm -r обязан выдать ошибку.
zcat /tmp/$boot-ramdisk.gz | cpio -i || exit 11
Распаковываем GZip-упакованный CPIO-образ Ramidsk (также известен как initrd) в текущий каталог (ramdisk).
echo sys.usb.config=adb >> /tmp/ramdisk/default.prop
Записываем в конец файла /tmp/ramdisk/default.prop строку sys.usb.config=adb
cp `which adbd` /tmp/ramdisk/sbin/adbd || exit 13
Находим полный путь к файлу adbd командой which и копируем его в /tmp/ramdisk/sbin/adbd
cp /system/*bin/sh /tmp/ramdisk/sbin || exit 14
Копируем файл sh из каталога /system/bin/, /system/xbin/ или /system/xbin/ (уж где то shell то в системе обязан быть!) в /tmp/ramdisk/sbin/
chmod 0755 "/tmp/ramdisk/sbin/adbd" || exit 15
chmod 0755 "/tmp/ramdisk/sbin/sh" || exit 16
Придаём файлам права на исполняемость
find . | cpio -o -H newc | gzip > /tmp/new-ramdisk.gz || exit 17
Упаквываем образ нового ramdisk
cd -
И возвращаемся в предыдущий текущий каталог
/tmp/mkbootimg --kernel /tmp/$boot-zImage --ramdisk /tmp/new-ramdisk.gz --cmdline "`cat /tmp/$boot-cmdline`" --base `cat /tmp/$boot-base` --output /tmp/newboot.img || exit 18
Создаём новый boot.img
dd if=/dev/zero of=/dev/block/$boot bs=65536
Стираем старый boot.img, заполняя раздел для него нулями
dd if=/tmp/newboot.img of=/dev/block/$boot bs=65536 || exit 19
И записываем туда новый boot.img (на самом деле это всё вовсе не обязательно делать, можно было просто прописать --output /dev/block/$boot в команде создающей boot.img)
rm /tmp/newboot.img /tmp/new-ramdisk.gz /tmp/$boot* /tmp/ramdisk/ /tmp/mkbootimg /tmp/unpackbootimg || exit 20
Удаляем всё после себя (не знаю как вас, а меня учили прибирать за собой)
umount /system || exit 23
Размонтируем /system/

Всё. А мод этот добавляет поддержку ADB на значительно более ранней стадии загрузки системы, примерно спустя 8 секунд после включения, что даст возможность отлаживать процесс загрузки с гораздо более раннего момента, и даже вмешиваться в него.
Статья оригинальная, автор идеи, разработок и технической реализации - я. Статья будет выложена также на форуме 4pda (под другим ником), не считайте это плагиатом. Скрина нет, так как скринить по сути нечего.
Прикрепленный файл #1:
Внимание! У вас нет прав для просмотра скрытого текста.
(90, 9 Kb)

Arago
2012-09-10T09:36:59Z

Здесь находятся
всего 0. За сутки здесь было 0 человек

Комментарии 5

#5   Arago    

А есть нечто подобное для прошивки radio.img? Через updater-script не получается прошить потому что не известна точка монтирования.
Если это тот Radio.img щ котором я думаю - в штатном режиме смонтируй его с опцией -o loop - увидишь файлы в нём (это тупо VFAT раздел). Затем mount|grep dev и ищи файло из этого раздела по точкам монтирования - увидишь его - запоминай раздел который смонтирован по этому пути и зашивай в него обораз через updater-binary или что угодно, да хоть DD.


0 ответить

#5   fly380    

А есть нечто подобное для прошивки radio.img? Через updater-script не получается прошить потому что не известна точка монтирования.


0 ответить

#5   sinicyn    

Честно дочитал до половины ))
Еслиб еще кто нашел способ cwm на огонек s (2012) поставить, цены бы ему не было..


0 ответить

#5   Arago    

Нужны тесты по работоспособности технологии на разных устройствах и Recovery. Тестировалось на HTC Evo3D GSM (shooteru) с установленным Recovery TWRP 2.2.2.0 и ROM Android 4.0.3 Sense 3.6.


0 ответить

#5   Usernokiamen    

Толком не вкурил, еще почитаю, но способ интересный, не спорю. Спасибо!


0 ответить

Яндекс.Метрика