每特17劃

及時當勉勵 2004/06/07

為什麼刷 LineageOS 時是用 `.zip` 而不是 `.img`? — 2022-10-11

為什麼刷 LineageOS 時是用 `.zip` 而不是 `.img`?

近期在 Hacking Thursday 聚會和 Pellaeon Lin 在協助阿寬刷 LineageOS 的過程中,阿寬問道:

為什麼是刷 `.zip` 檔,而不是 `.img` 檔?

這個疑問題的背景是在,LineageOS 的刷機過程中的兩個步驟:

  • fastboot flash boot lineage-19.1-20220925-recovery-flame.img
  • adb sideload lineage-19.1-20220925-nightly-flame-signed.zip

前者,是將 image 檔案資料寫進 boot 分割區,容易理解。但後者卻不同,是採用 .zip 檔而不採用 .img 的方式寫入。為什麼呢?

當下,我簡單的回應說,這個 .zip 檔裡,還是有包 image 檔案,Android 的工具會解壓縮並解析裡面的描述檔,然後再依裡面的參數去刷 image 。因為當時是憑印象回答,且自己之前也有些疑惑沒解開,回想起來總感到心虛。所以決定進一步爬一下 source code 來確認跟檢驗一下。

Q: 這個 zip 檔案內容是什麼?

解開 lineage-19.1-20220925-nightly-flame-signed.zip 並瞧了一下,結構大致如下:

.
├── apex_info.pb
├── care_map.pb
├── META-INF
│   └── com
│       └── android
│           ├── metadata
│           ├── metadata.pb
│           └── otacert
├── payload.bin
└── payload_properties.txt

其中 payload.bin 就是將刷入的 image 檔案,同時有 payload_properties.txt 輔助 checksum 檢查。

FILE_HASH=WlmU5qsJP4G449TJz3DYQjQuHiPKCJJGj/7fyHcICRI=
FILE_SIZE=1094414259
METADATA_HASH=Husi2CZRsqijFVEd5aWTK4gnxiN3kTGKfmd8EliY8dU=
METADATA_SIZE=134083

另外,還有 META-INF/com/android/metadata 的檔案,描述參數如下:

ota-property-files=payload_metadata.bin:41:134350,payload.bin:41:1094414259,payload_properties.txt:1094414352:156,care_map.pb:1094414549:732,metadata:1094416436:610,metadata.pb:1094415385:992  
ota-required-cache=0
ota-streaming-property-files=payload.bin:41:1094414259,payload_properties.txt:1094414352:156,care_map.pb:1094414549:732,metadata:1094416436:610,metadata.pb:1094415385:992  
ota-type=AB
post-build=google/flame/flame:12/SQ3A.220705.003.A1/8672226:user/release-keys
post-build-incremental=6fd1676fd5
post-sdk-level=32
post-security-patch-level=2022-09-05
post-timestamp=1664086215
pre-device=flame

Q: adb 的 source code 放在那裡?

因為 .zip 是透過 adb sideload 指令執行,所以先查 adb source code。但找 adb 的 source code 花了點時間。一開始 google 搜尋找到 platform/system/core/adb/adb.c 的路徑,但在最新的 git repository 卻找不到。

後循線索發現:

  • 先是 adb/adb.c 於 2015-02-25 時 (commit: bac3474) 改為 C++ 的 adb/adb.cpp
  • 再於 2020-10-23 (commit: a88ef8c) 從 system/core/adb 搬到 packages/modules/adb

所以目前 source code 的最新位置是在:

https://android.googlesource.com/platform/packages/modules/adb

Q: 這些參數和檔案是在那裡處理的?

查了 adb 的 source code 之後發現 adb sideload 只是將 .zip 檔案傳進去,並非真正處理的地方。真正處理的地方,後來發現是在 recovery 相關的程式碼,位置如下:

https://android.googlesource.com/platform/bootable/recovery

其中,在 install/install.cpp 有函式呼叫流程如下:

… => InstallPackage() => VerifyAndInstallPackage() => TryUpdateBinary()

而在 TryUpdateBinary() 的函式中,有分成兩個 cases:

  • SetUpAbUpdateCommands() ,這是用於 AB type 的 case
    • 這個 case 會根據 payload_properties.txt, payload.bin 作刷 image 的動作
  • SetUpNonAbUpdateCommands() , 這是用於 AB type 的 case
    • 這個 case 會去找這個檔案 META-INF/com/google/android/update-binary 並執行之

對應的 source 區段如下:

  if (auto setup_result =
          package_is_ab
              ? SetUpAbUpdateCommands(package_path, zip, pipe_write.get(), &args)
              : SetUpNonAbUpdateCommands(package_path, zip, retry_count, pipe_write.get(), &args);
      !setup_result) {
    log_buffer->push_back(android::base::StringPrintf("error: %d", kUpdateBinaryCommandFailure));
    return INSTALL_CORRUPT;
  }

Q: 那麼系統是如何判別 type 是 AB 或否的呢?

答案是,在之前的 META-INF/com/android/metadata 檔案中,有一欄參數寫有:

ota-type=AB

這參數會在 install/install.cppTryUpdateBinary() 裡採值如下:

  bool package_is_ab = get_value(metadata, "ota-type") == OtaTypeToString(OtaType::AB);

Q: 非 AB type 會怎麼處理?

依前述的流程,它會被系統分類為AB type,而進到 SetUpNonAbUpdateCommands() 的流程去,去找到並執行 META-INF/com/google/android/update-binary 這個檔案。

以另一個可能在刷 LineageOS 時會用到的套件為例:

https://mirrorbits.lineageos.org/tools/copy-partitions-20220613-signed.zip

其檔案結構如下:

.
└── META-INF
    └── com
        ├── android
        │   └── otacert
        └── google
            └── android
                ├── update-binary
                └── updater-script

其中最重要的是 META-INF/com/google/android/update-binary 檔案,這個 Android 會解開並執行之 。若他是 binary excutable 檔,則執行 binary 。而此處它是個 Linux Shebang 執行腳本:

#!/sbin/sh

#####################################################
# Flashize Runtime (2016-04-06)                     #
# Copyright 2016, Lanchon                           #
#####################################################

#####################################################
# The Flashize Runtime is free software licensed    #
# under GNU's Lesser General Public License (LGPL)  #
# version 3 and any later version.                  #
# ------------------------------------------------- #
# Note: The code appended to the Flashize Runtime,  #
# if any, is independently licensed.                #
#####################################################

if [ "$1" != "lanchon-magic" ]; then
    export FLASHIZE_VERSION='2016-04-06'
    log=''
    if [ -f /tmp/flashize-log ]; then
        log="$(cat /tmp/flashize-log)"
        if [ -z "$log" ]; then
            log=/tmp/flashize.log
        fi
    fi
    if [ "$log" == "-" ]; then
        log=""
    fi

...

        echo "Partition $partition"
  	    inactive=$(echo $active | sed "s/${suffix_active}\$/${suffix_swap}/");
        part_active=$(readlink -fn $active);
        part_inactive=$(readlink -fn $inactive);
        if [[ -n "$part_active" && -n "$part_active" && "$active" != "$part_active" && "$inactive" != "$part_inactive" ]]; then
          blockdev --setrw $part_inactive
          dd if=$part_active of=$part_inactive bs=4k
        fi
    fi
done

依 Linux Shebang 的慣例規則,會由 /sbin/sh 載入並執行之。

另一個 updater-script 在此處並未觸發,似乎在別的 use case 會用到。這個留待之後有遇到時,再繼續探討。

Q: 為何不直接寫入 image 而要包成 zip 檔跟層層檔案參數呢?

回到問題的動機面,有很多方法可以寫入需要的 binary data。為何要包裝成這個樣子,這樣不是找自己麻煩嗎?

其實這樣的作法,是韌體更新作業的常態。因為 Reflash/Update/Upgrade Firmware 的作業,通常出現在嵌入法系統(Embedded System), 單晶片(MCU), IoT, … 等情境。有很多狀況一旦環節有錯,就可能再也無法開機,例如:

  • 刷錯檔案 => 於是需要一些 Checksum 機制來驗證
  • 在更新過程中,少了某個檔案 => 需要類似像 Manifest 檔案的清單機制
  • ARCH, type, … 參數搞錯了 => 需要有 metadata 檔案描述這個 image 的性質跟類別
  • 有些 Firmware 版本之間有衝突 => 需要 Firmware Version,跟 Dependencies 檢查機制

把上述種種因素都考慮進來之後,就能理解單靠一個 image 檔本身,是作不到防呆的。不難想見像是 BIOS 的更新檔案,一些 4G/LTE modem 的更新檔, … 等,其更新檔案通常會以打包過的壓縮檔的形式來呈現。

補充

總結爬完 source code 的內容,和之前的印象差不多一致,總算是解掉心中一個懸念。

需要注意到此處討論到的 case ,都是在特殊模式(recovery, sideload, rescue, …)下運行的。跟一般正常運行時,所用的 adb install <some package.apk> 的情況不同。

Reference:

Linux 上的 Shebang 是在那裡定義的? — 2022-08-02

Linux 上的 Shebang 是在那裡定義的?

在 Linux 的指令執行檔的開頭,常常會看到以 #! 字串開頭的宣告,像是:

  • #!/bin/bash
  • #!/usr/bin/env python3
  • #!/bin/sh

這個開頭宣告的作用,是告訴系統要用什麼指令來執行該指令執行檔。 例如: #!/usr/bin/env python3 開頭的,就會以 /usr/bin/env python3 這個指令來執行。

不過,這開頭的 #! 這一行是那裡讀取並判讀的呢? 是 Bash? glibc? … or kernel?

答案是,直接由 Linux kernel 處理的,其對應的 source code 是在

fs/binfmt_script.c 裡的 load_script() 函式:

static int load_script(struct linux_binprm *bprm)
{
	const char *i_name, *i_sep, *i_arg, *i_end, *buf_end;
	struct file *file;
	int retval;

	/* Not ours to exec if we don't start with "#!". */
	if ((bprm->buf[0] != '#') || (bprm->buf[1] != '!'))
		return -ENOEXEC;

(出處: https://github.com/torvalds/linux/blob/v5.18/fs/binfmt_script.c#L41)

同時,Linux kernel config 有個設定選項 BINFMT_SCRIPT ,來決定是否支援 #! 這樣的用法。

(出處: https://github.com/torvalds/linux/blob/v5.18/fs/Kconfig.binfmt#L99)

可以使用下列指令來檢查系統的支援狀況:

zless /proc/config | grep -e "^CONFIG_BINFMT_SCRIPT="

預期會得到:

CONFIG_BINFMT_SCRIPT=y

一般來說,絕大多數的 Linux 系統都內建支援 #! 的語法。 但還是遇有一些 embedded system 或是資安考量的系統,會拿掉這個設定。 若在設定 at , cron 的排程設定時,發現程式未如預期執行時,可順便檢查這一個設定。

Q: 關於 Shebang 的歷史?

可參考維基百科的說明:

https://en.wikipedia.org/wiki/Shebang_(Unix)

Q: Linux 上大約何時支援?

從官方的 git 紀錄,支援 #! 宣告的功能,至少在 v2.6.12 時就己經存在:

https://github.com/torvalds/linux/blob/v2.6.12/fs/binfmt_script.c#L26

實際時間應該更早。 不過現下已經很少遇到這類舊系統,所以暫時沒再往前追溯。

Q: 為什麼有的會用 #!/usr/bin/env 開頭?

以 Bash 為例,有些系統會放在 /bin/bash 的位置,有些會放在 /usr/bin/bash。 若我的 script 寫 #!/bin/bash ,那遇到放在 /usr/bin/bash 位置的系統,就會路徑不符而失效。 所以,就有了這一支 env 的程式,他在所有系統都固定放在 /usr/bin/env 這個位置。 當用下述的指令時:

/usr/bin/env bash

env 就會根據該系統的環境設定(ex: $PATH, /etc/environment, …),去找出對應的 bash 的位置去執行。 如果一來,用上述的宣告在橫跨不同系統時,都能執行,而不需要再調整開頭的 Shebang 路徑。

Facebook post 2021-10-23 — 2021-10-23
Facebook post 2021-09-05 — 2021-09-05
Facebook post 2021-06-24 — 2021-06-24
Facebook post 2021-06-17 — 2021-06-17
Facebook post 2021-06-03 — 2021-06-03

Facebook post 2021-06-03

在 thingiverse 網站上搜尋 "mask strap" 關鍵字,可以找到不少口罩減壓帶的 3D 列印模型。我試了幾款,其中 https://www.thingiverse.com/thing:4284533 這個可解除耳朵束縳,固定較佳,列印也簡單(PLA即可)。提供給有需要的人參考。

Imgur

Comments:

  • Daniel Lin: Mat, 你白髮變好多
    • 李俊裕: 對啊,老了XD
      • Daniel Lin: 李俊裕 我只記得你年輕的樣子(太少見面)
  • Travis Yeh: 好久不見
    • 李俊裕: 希望疫情過后,有機會再來聚聚吧。 Stay safe!

Shares:

  • Shih-Yuan Lee
Facebook post 2021-05-29 — 2021-05-29
Facebook post 2021-05-28 — 2021-05-28

Facebook post 2021-05-28

轉貼: https://zh.cn.nikkei.com/industry/itelectric-appliance/44884-2021-05-28-08-40-00.html

小提醒: 還有第二頁

卡西歐借可穿戴設備提供跑步姿勢指導 日經中文網

Comments:

  • Phenix Fu: Garmin很早就有類似的裝備,只是不知道功能有沒有一樣
    • 李俊裕: 他上面寫說 "最大特點是可以在APP上用動畫重現自己跑步的姿勢" , 好像蠻屌的.
      • Phenix Fu: 李俊裕 對,仔細比了一下,就差在這功能!不知道是什麼神秘的演算法可以做到這圖😆
Facebook post 2021-05-24 — 2021-05-24