Dezynsekcja w praktyce

Od dobrych paru tygodni mam jakiś wstręt do androidowych wnętrzności. Po części to efekt presji rzeczywistości, po części – jakaś głupia blokada. Waham się czy przejść już na kolejną wersję Androida (Gingerbread czyli 2.3) czy pozostać z FroYo. Jednak odłóżmy rozterki 'młodego’ wyrobnika na bok i zajmijmy się tępieniem robali czyli dezynsekcją właśnie.

Chyba przedostatnia wersja Androidowego ROMu Laszlo opublikowana na forum XDA miała dość ciekawy błąd mianowicie nie widziała aplikacji zainstalowanych na partycji Ext.

Dorabiając z Fireratem natywną obsługę SdExt prawie w ogóle nie zwracaliśmy uwagi na to czy stosowna partycja karty w ogóle istnieje (o podmontowaniu jej we właściwym miejscu nawet nie wspomnę). Ubocznym efektem (i, do pewnego stopnia, powodem moich programistycznych wypocin) był fakt, że telefon potrafił wpadać w bootloop (czyli w kółko się restartował).

Pierwszym krokiem było zmodyfikowanie Volda (VOLume Daemon – usługa systemowa zarządzająca 'dyskami’), tak by montował odpowiednią partycję pod /sd-ext i sprzedał informację o sukcesie (lub porażce) reszcie systemu. Jak już umiał przekazać informację to trzeba było sprawić by framework wiedział co z tą informacją zrobić i tak powstała funkcja o nazwie getSdExtState(). Tak prosta, że nie sposób popełnić w niej błędu. Od razu też wykorzystałem ją do sprawdzania dostępności /sd-ext przed próbą skanowania zainstalowanych tam aplikacji przy starcie systemu. Kawałek kodu tak trywialny, że nawet nie przyszło mi do głowy jakieś większe testowanie.

if (Environment.getSdExtState().equals(Environment.MEDIA_MOUNTED)) {
   scanDirLI(mSdExtInstallDir, PackageParser.PARSE_ON_SDEXT, scanMode);
}

Jak podmontowane to skanuj, jak nie – to nie. Prawda, że proste? W samym frameworku jest parę newralgicznych miejsc (związanych z instalacją aplikacji) w których użyłem identycznego testu. Świat stał się lepszym miejscem :-) a ja mogłem się zająć dorabianiem informacji o SdExt w Ustawieniach czy czymś takim. Mniej więcej w tym momencie Laszlo zapewne zsynchronizował swoje źródła z moimi i wypuścił na świat kolejną wersję swojego ROMu. Chyba w sobotę Firerat dał mi znać na Twitterze, że ludzie mają kłopoty z Apps2SdExt na najnowszym FbL. Zerknąłem na forum i, na podstawie zamieszczonych tam postów, doszedłem do wniosku, że dochodzi do tzw race condition. Vold działa asynchronicznie względem reszty systemu i w czasie gdy jeszcze zajmuje się sprawdzaniem systemów plikowych uruchamiane są pozostałe komponenty systemu takie jak usługa PackageManagera. To ona właśnie przeprowadza początkowy skan katalogów z aplikacjami. Skoro nie widać aplikacji zainstalowanych na SdExt to (patrząc na powyższy kawałek kodu) getSdExtState zwraca coś innego niż MEDIA_MOUNTED i system nawet nie próbuje zajrzeć do /sd-ext/app. Wniosek z tego jest taki, że Vold jeszcze nie zdążył pozałatwiać swoich spraw z /sd-ext. Problem znany zatem teraz wystarczy znaleźć rozwiązanie. Na szybko Firerat przekompilował framework.jar z wywalonym sprawdzaniem getSdExtState(), wrzucił na XDA i 'rozwiązał’ problem (to był akurat weekend i, zdaje się, byłem po jakimś większym maratonie Singstara w towarzystwie sąsiadów i paru butelek Dark Whisky :-)).

To, co zrobił Firerat było jednak tylko obejściem problemu a nie jego rozwiązaniem. Poczytałem jeszcze raz posty na forum, popsioczyłem, że nikt z pomstujących nie zdobył się na złapanie logów ze startu systemu i doszedłem do identycznego wniosku jak wcześniej: Race condition. Na swoim telefonie nie miałem problemu z brakiem aplikacji z SdExt ale też miałem wtedy zainstalowaną wersję bardzo rozwojową (czyt: powiązaną sznureczkiem i podpartą patyczkiem z każdej strony). Złapałem logi z restartu swojego telefonu i patrzyłem w nie jak sroka w gnat. No nijak nie było szans na race condition. Zgodnie z tym co widziałem w logach /sd-ext było już dawno podmontowane. Przecież nie da się zrobić błędu w 6 linijkach kodu z jakich składa się getSdExt():

    /**
     * Gets the current state of SD-Ext
     * Note: this call should be deprecated as it doesn't support
     * multiple volumes.
     */
   public static String getSdExtState() {
       try {
           if (mMntSvc == null) {
               mMntSvc = IMountService.Stub.asInterface(ServiceManager.getService("mount"));
           }
           return mMntSvc.getVolumeState(getSdExtDirectory().toString());
       } catch (Exception rex) {
           return Environment.MEDIA_REMOVED;
       }
   }

Poza tym w innych miejscach kodu funkcja ta działa perfekcyjnie. Tak się zafiksowałem na tym race condition, że dobre dwa dni nad tym spędziłem. Albo raczej zmarnowałem. Zmarnowałem bo z całego logu (około 150+ KB tekstu) skoncentrowałem się wyłącznie na części związanej ze PackageManagerem i skanowaniem /sd-ext/app. W końcu mój umysł zaczął rejestrować inne linijki logu i wyłapał coś takiego:

I/sysproc (  147): System server: starting Android runtime.
I/sysproc (  147): System server: starting Android services.
I/SystemServer(  147): Entered the Android system server!
I/sysproc (  147): System server: entering thread pool.
I/SystemServer(  147): Entropy Service
I/SystemServer(  147): Power Manager
I/SystemServer(  147): Activity Manager
I/ActivityManager(  147): Memory class: 16
D/dalvikvm(  147): GC_FOR_MALLOC freed 641 objects / 80848 bytes in 138ms
D/dalvikvm(  147): GC_FOR_MALLOC freed 2131 objects / 317920 bytes in 159ms
I/SystemServer(  147): Telephony Registry
I/SystemServer(  147): Package Manager
D/dalvikvm(  147): GC_FOR_MALLOC freed 5971 objects / 222640 bytes in 181ms
I/dalvikvm(  147): DexOpt: source file mod time mismatch (3e4c9ef4 vs 3e618cf4)
D/installd(  101): DexInv: --- BEGIN '/system/framework/android.test.runner.jar' ---
D/dalvikvm(  163): DexOpt: load 111ms, verify 722ms, opt 13ms

Kilkanaście linijek niżej pojawia się:

I/PackageManager(  147): Pruning dalvik file: sd-ext@app@com.twitter.android-2.apk@classes.dex
I/PackageManager(  147): Pruning dalvik file: sd-ext@app@com.keramidas.TitaniumBackup-1.apk@classes.dex
D/PackageManager(  147): Scanning app dir /system/framework
D/PackageManager(  147): Scanning app dir /system/app
D/dalvikvm(  147): GC_FOR_MALLOC freed 4987 objects / 280408 bytes in 193ms
W/PackageParser(  147): No actions in intent filter at /system/app/Bluetooth.apk Binary XML file line #124
I/PackageManager(  147): /system/app/CMParts.apk changed; collecting certs

I jeszcze kilkadziesiąt linijek niżej:

D/dalvikvm(  147): GC_EXPLICIT freed 7597 objects / 563168 bytes in 221ms
I/SystemServer(  147): Account Manager
I/SystemServer(  147): Content Manager
I/SystemServer(  147): System Content Providers
I/SystemServer(  147): Battery Service
I/SystemServer(  147): Lights Service
I/SystemServer(  147): Vibrator Service
I/SystemServer(  147): Alarm Manager
D/AlarmManagerService(  147): Kernel timezone updated to -60 minutes west of GMT
I/SystemServer(  147): Init Watchdog
I/SystemServer(  147): Sensor Service
I/SystemServer(  147): Window Manager
I/SystemServer(  147): Bluetooth Service
I/SystemServer(  147): Device Policy
I/bluedroid(  147): Starting hciattach daemon
I/SystemServer(  147): Status Bar
I/SystemServer(  147): Clipboard Service
I/SystemServer(  147): Input Method Service
I/SystemServer(  147): NetStat Service
I/SystemServer(  147): NetworkManagement Service
I/SystemServer(  147): Connectivity Service
V/ConnectivityService(  147): ConnectivityService starting up
D/ConnectivityService(  147): getMobileDataEnabled returning true
V/ConnectivityService(  147): Starting Wifi Service.
I/WifiService(  147): WifiService starting up with Wi-Fi disabled
D/Tethering(  147): Tethering starting
D/NetworkManagmentService(  147): Registering observer
I/bluedroid(  147): Starting bluetoothd deamon
I/SystemServer(  147): Throttle Service
I/SystemServer(  147): Accessibility Manager
I/SystemServer(  147): Mount Service
I/SystemServer(  147): Notification Manager

Widać?
Nie widać?? Hmmm… A teraz?

I/SystemServer(  147): Entropy Service
I/SystemServer(  147): Power Manager
I/SystemServer(  147): Activity Manager
I/SystemServer(  147): Telephony Registry
I/SystemServer(  147): Package Manager
I/SystemServer(  147): Account Manager
I/SystemServer(  147): Content Manager
I/SystemServer(  147): System Content Providers
I/SystemServer(  147): Battery Service
I/SystemServer(  147): Lights Service
I/SystemServer(  147): Vibrator Service
I/SystemServer(  147): Alarm Manager
I/SystemServer(  147): Init Watchdog
I/SystemServer(  147): Sensor Service
I/SystemServer(  147): Window Manager
I/SystemServer(  147): Bluetooth Service
I/SystemServer(  147): Device Policy
I/SystemServer(  147): Status Bar
I/SystemServer(  147): Clipboard Service
I/SystemServer(  147): Input Method Service
I/SystemServer(  147): NetStat Service
I/SystemServer(  147): NetworkManagement Service
I/SystemServer(  147): Connectivity Service
I/SystemServer(  147): Throttle Service
I/SystemServer(  147): Accessibility Manager
I/SystemServer(  147): Mount Service

Jeszcze nie widać? No, dobra. Widzisz kiedy jest startowany Mount Service? A teraz zobacz gdzie jest Package Manager. Między jednym a drugim wpisem mija dobrych kilkanaście – kilkadziesiąt sekund.  W tym czasie skanowane są aplikacje systemowe (/system/app) i użytkownika (/data/app i ewentualnie /sd-ext/app). Poprawność działania getSdExtState() jest uzależniona od działania Mount Service a w chwili próby skanowania /sd-ext/app ta usługa jeszcze nie działa. I to właśnie było źródło problemu :-)

W systemie Android część usług została zaliczona do usług krytycznych (w powyższym wyciągu z logów od Entropy Service do Bluetooth Service). Na moje nieszczęście Mount Service nie trafił do tej kategorii. Z punktu widzenia twórców Androida nie było takiej potrzeby. Usługa ta jest potrzebna tylko do dostępu do kart pamięci. Zmiana kategorii tej usługi nie jest za bardzo możliwa bez potężnej ingerencji w nią samą i szereg innych komponentów a to oznacza więcej potencjalnych bugów. Najprostszym rozwiązaniem było porozmawianie (prawie) bezpośrednio z Voldem. Ubocznym skutkiem jest potencjalne wydłużenie startu systemu o maksymalnie 20 sekund. W skrajnej sytuacji może się okazać, że i tak aplikacje zainstalowane na SdExt się nie pokażą (w przypadku kłopotów z filesystemem). Wygląda to mniej więcej tak. Zdaję sobie sprawę z braków tego rozwiązania. Właściwie jest to tylko nieco hakerskie obejście. Prawidłowym rozwiązaniem jest skanowanie aplikacji z SdExt już po starcie systemu, tak jak to jest robione z aplikacjami zainstalowanymi na partycji FAT karty. Mam już nawet mgliste pojęcie jak to zrobić :-) Jako bonus dostaniecie wtedy możliwość odmontowania i wyjęcia karty pamięci bez wyłączania telefonu :-) Cieszycie się?

 

  1. FxJ

    Mam nadzieję że wszystko się powiedzie. Nie zapominajcie o G1 chłopaki ;]

  2. APair

    Powodzenia i fajnie się czytało :-). W moim telefonie i tak aby wyjąć kartę, trzeba wyciągnąć baterię… więc nic nie pomoże :-))). No i na razie zostaję przy standardowych romach, ale to dla mnie „normalne”, mój typ tak ma że jak już coś skonfiguruję to lubię żeby sobie działało samo z siebie bez dłubania. Żeby się pobawić w eksperymenty musiałbym mieć 2 telefony :), tak aby przynajmniej jeden działał w każdym momencie :-). Ale chylę głowę przed twoimi eksperymentami, dobra robota – może kiedyś będę miał 2 telefony :-)

  3. newhousik

    Ja tam mogę się ustosunkować tylko do pierwszej części :) . Gdy pierwszy raz wgrałem nowy system po rocie był już 2.2 froyo, i tak jakoś z nim żyłem, gdy zrobiło się nudno zainteresowałem się chińskimi romami, tam odkryłem 2.1 ;). Ale znając sytuacje z 1.6 ( gdzie połowa aplikacji już nie chce działać, to 2.1 i 2.2 też tak skończą – chyba). Wersje systemu 2.3 na G1działają coraz lepiej, więc trzymam kciuki za to że się za nie weźmiesz. Jeżeli tak to poproszę równolegle pokrojoną wersje, bez usług google:). Gdy kupie tablecik. pozwoli mi to z G1 zrobić zwykły telefon ( no z dodatkami ) Mam nadzieje Że dorówna Sony E. k800i :)

Leave a Reply