Боремося зі Status 7. Як працює механізм OTA-оновлення і чому він дає збої



Досить часто юзери, які звикли рутовать прошивки, встановлювати різного роду системний софт, змінювати ядра і по-іншому знущатися над прошивкою, виявляють, що встановити OTA-оновлення неможливо. Воно просто не встає, лаючись на змінені системні файли, неправильні цифрові ключі і всяке інше. У цій статті я розповім про саму механіці оновлення, причини виникнення проблем і про те, як їх вирішити.

Як це працює

Першими нові версії Android традиційно отримують останні з пристроїв Nexus. Коли нова версія прошивки готова для широкої публіки, повний образ розміщується за адресою developers.google.com/android/nexus/images. Незабаром після цього починається поширення прошивки по повітрю. розповідає один з розробників Google Ден Моррилл (Dan Morrill), спочатку ОТА розсилається на 1% пристроїв. Це відбувається рандомно, незалежно від регіону та місця покупки телефону/планшета. В цей час відловлюються баги, що дозволяє призупинити оновлення при наявності критичних помилок у великого числа користувачів.

Далі протягом пари тижнів оновлення поширюється 25, 50, 100% користувачів. Тобто на першому етапі шанс на отримання оновлення має один пристрій зі ста. Якщо оновлення не отримано, то пристрій випадає зі списку і неодноразове повторне натискання на кнопку «Перевірити наявність оновлень» автоматично переносить пристрій в кінець списку. Коли запускається новий етап розсилки, натискання на кнопку дає наступний шанс отримати оновлення вже 25%. Так як пристрій сам перевіряє наявність оновлень раз на добу (або при перезавантаженні), то натискання на кнопку може «вистрілити» раніше, ніж це сталося б сама по собі. Але знову-таки перевірка буде тільки один раз. Подальші натискання не допоможуть. Це не та ситуація, коли «хто перший натиснув, той перший отримав». У будь-якому разі оновлення по повітрю прийде всім протягом пари тижнів. Самі нетерплячі можуть прошити оновлення руками (про це нижче).


Повідомлення про наявність оновлення

Форсуємо оновлення

Прискорити отримання оновлень можна двома способами. Перший — очищення даних Google Services Framework з подальшою перезавантаженням пристрою. Вкрай не рекомендований спосіб, який засуджують навіть інженери Гугла. Цей спосіб викликає безліч негативних ефектів, головний з яких — зміна ідентифікатора для GCM (Google Cloud Messenger). Цей ідентифікатор потрібен у всіх програмах Гугла і безлічі інших додатків, що використовують функції push-повідомлень. І якщо в деяких програмах побороти ефекти відносно легко, то для багатьох інших наслідки можуть бути сумні. Всі програми просто перестануть приймати push-повідомлення, засновані на GCM, поки не отримають новий ідентифікатор. Деякі програми роблять перевірку часто, деякі рідко. Для частини допоможе очищення даних програми. А ті програми, які використовують GCM ID в якості ідентифікатора на своїх серверах, можуть мати більш глибокі проблеми.


Стоковий recovery

Другий — встановлення оновлення руками через консоль відновлення. Незабаром після запуску ОТА в профільних темах пристроїв на ресурсах 4PDA і XDA з'являються файли виду хеш.signed-hammerhead-LRX21O-from-KTU84P.c1a33561.zip в назві яких міститься хеш файлу, марка пристрою, а також версії прошивок для оновлення (на яку, з якою). На компі необхідно мати папку з утилітами ADB і fastboot. Я використовую останні версії з Android SDK. В ту ж папку потрібно покласти скачаний архів з ОТА-оновленням. Також необхідно мати правильно встановлені драйвери для пристроїв, які можуть конфліктувати з раніше встановленими драйверами для інших пристроїв.

Сам пристрій слід перевести в режим відновлення (recovery). Для цього на вимкненому пристрої затискаємо одночасно кнопки <Power + VolDown> і потрапляємо в завантажувач, кнопкою гучності вибираємо Recovery mode, входимо в нього кнопкою Power. З'явиться лежачий Android зі знаком оклику. Це не помилка, лякатися не варто. Необхідно на цьому екрані коротко натиснути <Power + VolUp>, після чого і завантажиться стоковий рекавері. У ньому необхідно вибрати кнопками гучності пункт apply update from ADB і підтвердити кнопкою включення. Далі необхідно підключити телефон/планшет до компу. Запускаємо консоль, переходимо в папку з ADB і архівом оновлення і вводимо наступну команду (для файлу, наведеного вище):

$ adb sideload хеш.signed-hammerhead-LRX21O-from-KTU84P.c1a33561.zip

Після цього на телефон встановиться ОТА і він перезавантажиться.

Блок-вставка: Як завантажити оновлення через стільникову мережу

Повідомлення про доступність ОТА може прийти, коли пристрій не підключено до Wi-Fi. При цьому з'явиться позначка, що файл доступний для скачування по Wi-Fi до певної дати (близько тижня), а сама кнопка «Завантажити» буде неактивна. Це зроблено для економії грошей юзера. Якщо підключення до Wi-Fi найближчим часом не передбачається, то можна обдурити телефон і викачати оновлення через 3G/4G, просто перевівши дату в телефоні вперед, не пізніше дати, зазначеної в повідомленні, і перевантаживши пристрій.

INFO

Під стокового (stock — з магазину) прошивкою розуміється наявність заводського ядра, recovery, відсутність модифікацій, отриманих у тому числі за допомогою root.

Модифікована прошивка

Якщо у тебе розблоковано завантажувач, варто кастомный recovery, отриманий root, який активно використовують різні програми, і застосовані різні модифікації, то з імовірністю 99% оновлення не встановиться. Навіть при поверненні стокового recovery при прошивці через ADB буде видавати помилку Status 7. Кастомный recovery також буде писати помилку, лаючись на змінені файли. Подолати цю проблему можна, повернувши смартфон до заводській прошивці, але це не наш метод. Ми розберемося з нею, расковыряв файл оновлення, з'ясуємо, на якому місці спотикається установка, і усунемо проблему. І все це на прикладі самого великого оновлення Nexus 5 — з версії 4.4.4 (KTU84P) на 5.0 (LRX21O).

Механіка роботи ОТА

Отже, оновлення з 4.4.4 на 5.0 стало найбільшим за останній час з вагою архіву в 491 Мб. У зв'язку зі зміною Dalvik на ART практично весь код був модифікований. Так що ж містить архів? Як видно на скріншоті «Файли з архіву з оновленням до 5.0», всередині архіву знаходяться образи бутлоадера (різні розділи), каталоги META-INF, patch і system.


Файли з архіву з оновленням до 5.0

Для мінімізації кількості трафіку і зменшення навантаження на сервери, а також для зниження витрат кінцевого користувача структура оновлення побудована так, що файли з великою кількістю змін або написані з нуля знаходяться в каталозі system і змінюються цілком. А файли з невеликими за мірками Гугла змінами не замінюються, а патчатся, тобто змінюються шматки коду всередині файлу. Ці файли знаходяться всередині каталогу patch і мають розширення.р. Це добре видно, якщо порівняти файли в /system/bin і /patch/system/bin. При цьому для створення патча використовується добре знайомий юниксоидам bsdiff, що дозволяє з двох бінарників отримати дельту (файл з різницею між файлами).

Саме ж чари відбувається з волі updater-script, який знаходиться в /META-INF/com/google/android. Саме його ми і розглянемо докладніше. Сам файл важить 463 Кб і містить рядки коду, що відповідають за процес застосування ОТА-оновлення (насправді це скриптова мова Edify, інтерпретатор якого знаходиться в тому ж каталозі і носить ім'я update-binary. — Прим. ред.). Ось що він містить у нашому випадку. Спочатку монтується розділ /system (досить стандартна для Linux рядок монтування, схожа з тими, що знаходяться в /etc/fstab):

mount("ext4", "EMMC", "/dev/block/platform/msm_sdcc.1/by-name/system", "/system", "max_batch_time=0,commit=1,data=ordered,barrier=1,errors=panic,nodelalloc");

Далі скрипт перевіряє модель пристрою і версію прошивки за допомогою читання системної змінної ro.build.fingerprint (зверни увагу, що він не бере її з файлу /system/build.prop, а запитує самого recovery, тому оновлення не можна поставити з допомогою консолі відновлення кастомних, хоча до 5.0 це було можливо). Тут і далі крапки це скорочені рядки:

getprop("ro.build.fingerprint") == "google/hammerhead/hammerhead:4.4.4/KTU84P/1227136:user/release-keys" ||
getprop("ro.build.fingerprint") == "google/hammerhead/hammerhead:5.0/LRX21O/1570415:user/release-keys" ||
abort("Package expects build fingerprint of google/hammerhead/hammerhead:4.4.4 ...");
getprop("ro.product.device") == "hammerhead" || abort("This package is for \"hammerhead\" devices ...");

Як видно вище, на «нерідне» пристрій оновлення не встане, зате його можна повторно накотити на версію 5.0. Також скрипт перевіряє, чи підписана прошивка офіційними ключами Google (release-keys). З-за цього у багатьох користувачів виникають проблеми. Далі починається перевірка наявності та цілісності окремих файлів за допомогою звірки хешів SHA-1. Для цього використовуються дві функції: sha1_check(), що приймає в якості аргументів ім'я файлу і хеш, і apply_patch_check(), приймаюча три аргументи: ім'я файлу, і два хеш. Перша використовується просто для перевірки цілісності файлу, друга перевіряє, чи не був файл вже пропатчен. Для простоти довгі хеші у коді нижче замінені на три крапки:

sha1_check(read_file ("system/app/Drive/Drive.apk"), ...) || 
apply_patch_check("/system/app/Drive.apk", ...) || abort("\"/system/app/Drive.apk\" has unexpected contents.");
sha1_check(read_file("system/app/Drive/lib/arm/libdocsimageutils.so"), ...) || 
apply_patch_check("/system/lib/libdocsimageutils.so", ...) || abort ("\"/system/lib/libdocsimageutils.so\" has unexpected contents.");

Для прикладу показані тільки дві перевірки. За фактом перевіряються всі файли, які підлягають заміні або зміни патчем. У коді видно, що оновлення видасть помилку, якщо, наприклад, був змінений або видалений файл /system/app/Drive.апк. В кінці блоку перевірки скрипт перевіряє ядро, доступне місце в /system і радіо:

apply_patch_check("EMMC:/dev/block/platform/msm_sdcc.1/by-name/boot:8908800:...") || abort("...");
apply_patch_space(23999236) || abort("Not enough free space on /system to apply patches.");
apply_patch_check("EMMC:/dev/block/platform/msm_sdcc.1/by-name/modem:46499328:...") || abort("..."); 

Тобто це оновлення не встане, якщо стоїть кастомное ядро або модифікація радіо. Наступним кроком йде видалення старих файлів з пристрою перед їх заміною на нові і видалення файлів, які не потрібні на новій прошивці:

delete("/system/app/BasicDreams/", "/system/app/BasicDreams/arm/", ...);

Далі патчатся всі необхідні файли з попередньою перевіркою хеш SHA-1. Патчінг виконується за допомогою функції apply_patch(), яка приймає імена файлів для патчінга і кілька хешів: хеш оригіналу, хеш патча і хеш результату. Останнім аргументом йде ім'я файлу з патчем. Як і раніше, все хеші у коді нижче скорочені до крапки:

sha1_check(read_file("system/app/Drive/Drive.apk"), ...) || 
apply_patch("/system/app/Drive.apk", "-", ..., package_extract_file("patch/system/app/Drive.апк.p"));

Останнім патчі ядро і RAM-диск:

apply_patch("EMMC:/dev/block/platform/msm_sdcc.1/by-name/boot:..., package_extract_file("patch/boot.img.p"));

Наступний блок переносить на пристрій файли, які не потрапляють під патч і повинні бути замінені. Частина з них потім переміщається:

package_extract_dir("system", "/system");
rename("system/app/KoreanIME.apk", "system/app/KoreanIME/KoreanIME.apk");
rename("system/framework/wm.odex", "system/framework/arm/wm.odex");
...

Видаляються непотрібні файли, розставляються симлинки, права доступу і прапори (тут три крапки замінені саме права доступу і прапори):

delete("/system/etc/firmware/wcd9320/wcd9320_mbhc.bin", ...);
symlink("/data/misc/audio/mbhc.bin", "/system/etc/firmware/wcd9320/wcd9320_mbhc.bin");
symlink("/data/misc/audio/wcd9320_anc.bin", "/system/etc/firmware/wcd9320/wcd9320_anc.bin");
...
set_metadata_recursive("/system/bin", ...);
set_metadata("/system/bin/app_process32", ...);

Прошиваються бутлоадер і супутні розділи:

package_extract_file("завантажувач-flag.txt", "/dev/block/platform/msm_sdcc.1/by-name/misc");
package_extract_file("bootloader.aboot.img", "/dev/block/platform/msm_sdcc.1/by-name/aboot");
package_extract_file("bootloader.rpm.img", "/dev/block/platform/msm_sdcc.1/by-name/rpm");
...

Патчити радіо/модем:

apply_patch("EMMC:/dev/block/platform/msm_sdcc.1/by-name/modem:..., package_extract_file("radio.img.p")); 

Останнім змінюється build.prop, в який записується в тому числі нова версія прошивки. Зроблено це для того, щоб при виникненні помилки на самому останньому етапі, коли майже всі файли вже перенесені, перервати оновлення і зберегти номер поточної версії прошивки в файлі на пристрої. Тоді при натисканні кнопки «Перевірити оновлення» можна запустити його знову.

apply_patch("/system/build.prop", "-", ..., package_extract_file("patch/system/build.prop.p"));
set_metadata("/system/build.prop", ...); 

В кінці скрипта розділ /system переключається, і починається перевірка правильності застосування оновлення, звіряється SHA-1 хеш нових файлів і /system размонтируется:

unmount("/system");
mount("ext4", "EMMC", "/dev/block/platform/msm_sdcc.1/by-name/system", "/system", "");
assert(sha1_check(read_file("/system/app/CalendarGooglePrebuilt/CalendarGooglePrebuilt.apk"), ...));
assert(sha1_check(read_file("/system/app/CaptivePortalLogin/CaptivePortalLogin.apk"), ...));
...
unmount("/system");

Після чого пристрій перевантажується в нову систему.


Updater-script як він є

Кастомный recovery

До недавнього часу прошити архів ОТА-оновлення в більшості випадків (якщо не було перевірки recovery для його заміни) можна було з кастомного recovery, просто закинувши файл на пристрій і вибравши install zip. Але починаючи зі скрипта для оновлення 5.0 скрипт змінився. Попередні версії перевіряли файл /system/build.prop:

file_getprop("/system/build.prop", "ro.build.fingerprint")

Поточні скрипти перевіряють не файл, а значення системної змінної безпосередньо, запитуючи його у recovery:

getprop("ro.build.fingerprint")

А якщо розібрати кастомный recovery (для прикладу TWRP версії 2.8.0.0), то можна побачити наступні рядки:

ro.build.description=omni_hammerhead-eng 4.4.4 KTU84P eng.dees_troy.20140910.125240 test-keys
ro.build.fingerprint=Android/omni_hammerhead/hammerhead:4.4.4/KTU84P/eng.dees_troy.20140910.125240:eng/test-keys

Версія TWRP 2.8.6.1 має в коді наступні рядки (зверни увагу на слово omni у другому рядку, розробник TWRP з ніком Dees Troy — ще й один із активних розробників OmniROM):

ro.build.id=LRX22G
ro.build.display.id=omni_hammerhead-eng 5.0.2 LRX22G eng.dees_troy.20150403.145211 test-keys
ro.build.version.incremental=eng.dees_troy.20150403.145211

А останні версії CWM Touch і Philz підписані так:

ro.build.description=hammerhead-user 4.4 KRT16M 893803 release-keys
ro.build.fingerprint=google/hammerhead/hammerhead:4.4/KRT16M/893803:user/release-keys

Саме ці значення і повертає при перевірці скрипт, перериваючи оновлення на самому початку і видаючи помилку про невідповідність версії Android на пристрої.


Ось яку відповідь ти отримаєш при спробі встановити оновлення 5.0.2 на Nexus 7 з кастомного recovery

Оновлення 4.4.3–4.4.4

Для порівняння можна навести попереднє оновлення з версії KTU84M на KTU84P. Оновлення дрібне і важить всього 2,5 Мб. В основному стосується поліпшень безпеки. Якщо відкрити архів, то можна побачити, що патчити тільки невелика кількість системних файлів і радіо, відповідно, скрипт і перевіряє тільки їх. Це оновлення нормально встановлювалося з рутом, кастомным ядром і працюючим Xposed Framework, так як на наявність змін все це не перевіряється.

Оновлення для Nexus 6 і Nexus 9

У останніх пристроїв від Google структура скрипта докорінно інша. Для цих і (судячи з усього) наступних пристроїв Nexus Google додала в складальний скрипт, формує ОТА-оновлення, функцію генерації поблочного оновлення. Таке оновлення звіряє і оновлює не окремі файли, а блоки у файловій системі /system. Далі в прикладі «66,...,524256» — це довгі списки адрес блоків:

if range_sha1("/dev/block/platform/msm_sdcc.1/by-name/system", "66,...,524256") == "..." then
block_image_update("/dev/block/platform/msm_sdcc.1/by-name/system", package_extract_file("system.transfer.list"), "system.new.dat", "system.patch.dat");

Це дозволило інженерам Google істотно спростити і прискорити застосування ОТА-оновлення для кінцевих пристроїв, а сам updater-script тепер займає всього 5 Кб. Але це обернулося головним болем для просунутих користувачів. Адже тепер будь-які зміни в системному розділі викличуть збій. Включаючи наявність зайвих файлів. Навіть факт монтування системи R/W призведе до зміни хеш суперблоку ФС.

Висновок

Підводячи підсумки статті, можна зробити наступні висновки:

  1. Права суперкористувача самі по собі не впливають на успішне застосування оновлення. Впливають ті зміни, які користувач і програми вносять у систему, маючи ці права. Часто ці зміни неможливо відстежити і повернути.
  2. Вплинуть root і внесені в систему зміни на успішне оновлення, залежить кожного разу від того, що саме змінюється в системі при оновленні і які файли перевіряє скрипт. Якщо система змінювалася, заморожувалися/відключалися непотрібні системні програми через Titanium Backup, змінювалися ядра, ставилося кастомный recovery, Xposed Framework, Lucky Patcher, freedom, franco.Kernel updater, моди на дзвонилку і всілякі покращувалки для звуку, інша бутанимация, системні шрифти і так далі. Все це може вплинути на оновлення.
  3. При модифікації системи завжди залишай оригінальні файли для бекапа, якщо хочеш оновлюватися через ОТА. Копіюй в хмару, перейменовуй як завгодно. Можна зробити Nandroid-бекап розділу /system (про Nandroid читай в попередньому номері).
  4. Якщо пам'ятаєш, що міняв в системі, можна відкотитися назад майже завжди. Recovery завжди пише помилку, на що лається оновлення. Погугливши назва файлу помилку, іноді можна знайти, яка прога його змінює. Наприклад, /system/bin/thermal-engine-hh та /system/lib/power.msm8974.so замінює franco.Kernel updater і не повертає його навіть при прошивці стокового ядра і знесення самого додатка.
  5. Для успішного застосування ОТА необхідно повернути в систему оригінальні файли. Самий вірний спосіб — це прошити system.img, стічне ядро і recovery перед тим, як встановлювати оновлення (дані і додатки не загубляться).
  6. Ну і головний висновок. Якщо є рут і багато модифікацій — не мучся, а відразу ший повний образ нової прошивки, видаливши ключ-w в flash-all.bat для збереження даних. Починаючи з оновлення до версії 5.0, залишається дуже маленька ймовірність обдурити скрипт. Та й таке оновлення може мати «блокове» структуру, яка передбачає наявність тільки повного стоку для застосування.

Пара слів від редактора

До недавнього часу OTA-оновлення в кастомних прошивках (CyanogenMod, Paranoid) завжди приходили у вигляді zip'а з повною версією прошивки і було абсолютно неважливо, які зміни вносилися в систему до цього. Прошивка завжди встановлювалася заново (з збереженням даних юзера і gapps, природно), проте в CyanogenMod 11 з'явилася функція інкрементальних оновлень, але набагато більш проста в порівнянні з тією, що використовується Google. Оновлення просто перевіряє цілісність прошивки і замінює ті файли, які змінилися з минулого версії (зазвичай нічний складання), без всяких патчів. Причому, якщо ти пропустиш одне з оновлень, наступне по-старому прийде у вигляді повного оновлення. Просто і зручно.

Більш цікавий метод використовується в OmniROM. Для оновлення вона використовує бінарні патчі, але зовсім не так, як це робить Google. Перше OTA-оновлення завжди завантажується повністю, після чого зберігається на карті пам'яті, прошивається, але не видаляється з карти. Наступне OTA-оновлення вже приходить у вигляді єдиного бінарного патча, після чого патч накладається на збережене в минулий раз на карті пам'яті оновлення і вже воно прошивається. Родзинка цього методу в тому, що патч накладається не на систему, а на файл з минулим оновленням і смартфон кожен раз прошивається як з нуля (але зі збереженням даних і налаштувань). Майже ідеальний метод — трафік економиться, а турбуватися про конфлікти із зміненою системою не треба.


Екран установки оновлень CyanogenMod 12
image

Вперше надруковано в журналі Хакер #196.
Автор: Дмитро «BRADA» Подкопаєв


Підпишись на «Хакер»

Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.