1. 程式人生 > >openwrt 網頁sysupgrade刷韌體流程

openwrt 網頁sysupgrade刷韌體流程

       一次偶然的機會,閱讀了openwrt網頁升級的實現細節,以實際操作流程,結合網路資料,整理了這篇流程。

      本文按照網頁升級韌體時涉及到的各個模組的先後順序進行介紹, openwrt 韌體的升級功能流程,從頁面傳入升級檔案到升級檔案檢測,再到呼叫升級指令碼進行升級,升級完成後,進行系統重啟。 在實際閱讀系統指令碼時,會發現網上的資料可能和手上的程式碼有所區別,這是版本不同一起的,但是以老王的經驗來看,核心流程還是一致的。

. web server

1MVC

     OpenWRT中採用LuCI作為它的Web interface介面框架,採用Lua語言.LUCI

採用MVCM-V-C ——Model--View--Controller,模式-檢視-控制器,這是一種範型,對應:

feeds\luci\modules\admin-full\luasrc下的controller  model  view三個資料夾

2、模組入口在controller/admin/下,在index()函式中,使用entry函式來完成每個模組函式的註冊

entry(path, target, title=nil, order=nil)

target主要分為三類:call(呼叫函式),template(呼叫htm,view下)和cbi(定義各種控制元件,在model下,屬於核心模組)

cbi.lua檔案中封裝了所有的控制元件元素,例如複選框,下拉列表等

也就是說,我們在pc端輸入192.168.29.1出現的網頁,事實上是由lua指令碼生成的,這裡我們注重介紹 /usr/lib/lua/luci/controller/admin/system.lua, 升級的核心操作都位於這個檔案中。

. system.lua

1.從頁面接受傳過來的升級檔案

作為整個流程的開始,功能實現在檔案system.lua中。

這是一個lua檔案,很容易在function index函式中找到系統升級功能的入口函式:action_flashops。在這個函式中首先通過fp = io.open(image_tmp, "w")

開啟升級臨時檔案:/tmp/firmware.img,開啟後通過fp:write(chunk)寫入頁面傳進來的升級檔案。到這裡,接受升級檔案完成

2.檢測升級檔案的合法性

實現同樣在檔案system.lua中。

在函式image_supported()中進行檢測,這個函式通過image magic number來檢測升級檔案是否合法。函式image_supported()會呼叫platform.sh指令碼中的platform_check_image函式,

platform_check_image函式呼叫ramips.sh指令碼中的ramips_board_name函式,獲取boardnameramips_board_name函式從檔案 /tmp/sysinfo/board_name 中獲取board name,若沒有則為unknown,並返回給呼叫者,我用的板是ralink-socplatform_check_image函式繼續呼叫common.sh指令碼中的get_magic_long函式,用以獲取升級檔案magic,就是升級檔案前4位。get_magic_long函式呼叫common.sh指令碼中的get_image函式用以獲取檔案/tmp/firmware.img內容,其實就是cat /tmp/firmware.img. 獲取到的內容,通過dd bs=4count=1,來獲取前4位,最後通過hexdump -v -n 4 -e '1/1 "%02x"'處理以十六進位制編碼返回呼叫者。獲取到的升級檔案magic,在platform_check_image函式中與27051956做對比,這個值是在韌體編譯的時候已經定好了的。如果相等,就是合法的升級檔案,繼續升級動作;不相等則為非法升級檔案,做一些後續處理並終止升級動作。到這裡檢測升級檔案合法性完成。

3.檢測升級檔案不合法後的處理

實現同樣在檔案system.lua中。

檢測到不合法後,通過nixio.fs.unlink(image_tmp)來刪除臨時檔案/tmp/firmware.img,並通過image_invalid= true,設定檢測失敗,用以通知頁面顯示提示資訊。終止升級。處理完成。

4.檢測升級檔案合法後的處理

實現同樣在檔案system.lua中。

檢測到升級檔案合法後,會獲取一些升級檔案相關的資訊,用以在頁面顯示:呼叫image_checksum(),獲取checksum;呼叫storage_size(),獲取可用空間大小;呼叫nixio.fs.stat(image_tmp).size,獲取升級檔案大小;以及頁面傳過來的是否儲存配置的值;其中,image_checksum()函式用的是md5sum命令,storage_size()函式是在系統檔案/proc/mtd中找到firmware分割槽大小。

接下來如果使用者選擇進行升級檔案,則會現在頁面上列印一些提示資訊,用於提示使用者:正在升級,不要斷開電源等等。

檔案system.lua最後的處理就是呼叫升級指令碼:

fork_exec("killall dropbear uhttpd;  sleep 1; /sbin/sysupgrade %s %q %s %s" %{ keep, image_tmp, size, checksum})

這條語句,先清除dropbear uhttpd程序,再等待1秒,最後呼叫升級指令碼sysupgrade,傳過去的引數就是keep:是否要儲存配置;image_tmp:升級檔案/tmp/firmware.img size代表升級檔案的大小, checksum代表md5值。

OK,到這裡system.lua檔案中關於升級前的準備工作都完成了,視線請轉到升級指令碼/sbin/sysupgrade

. sysupgrade指令碼

指令碼開始,像所有的主體處理程式一樣,會對傳進來的引數進行處理。下面對這些引數的介紹:

-i       開啟互動模式

-d      重啟前延遲,延遲秒數是傳進來的

-v      會列印sysupgrade指令碼中的一些資訊,指令碼中預設列印

-q      -v相反

-n      升級後不儲存配置,預設儲存配置

-c      儲存所有的改動配置檔案到/etc/

-b      sysupgrade.conf中指定的檔案,建立.tar.gz格式備份檔案

-r      用上步建立的.tar.gz檔案,恢復配置

-l       列出將會備份的檔案列表

-f       .tar.gz恢復配置

-F      即使升級檔案檢測失敗,也要升級,這個引數是危險的,慎用

-T      驗證升級檔案和.tar.gz配置檔案,但不升級

-h      列印幫助資訊

這些引數的使用在指令碼中都有介紹,不再多講。

接下來:[ -z "$ARGV" -a -z "$NEED_IMAGE" -o $HELP -gt 0],意思是:如果沒有升級檔案引數,且沒有命令列引數-bcreate-backup)-rrestore-backup),或者帶有-hhelp)引數,則列印幫助資訊。這個條件為真的話,會在終端列印幫助資訊,退出指令碼。

接下來:[ -n "$ARGV" -a -n "$NEED_IMAGE" ],意思是:不要指定-b-r(建立配置、恢復配置)的同時,指定升級檔案。為真的話,列印提示資訊,退出指令碼。

接下來:[ "$CONF_BACKUP" = "-" ] && exportVERBOSE=0,意思是:選擇備份配置但傳進來的檔案為“-”時,不列印備份檔案時的過程。

下面展示一下-v選項的作用:

-v時的升級過程:

root@OpenWrt:/# sysupgrade -i -v/tmp/firmware.img 

Keep config files over reflash (Y/n): y

Edit config file list (y/N): n

Saving config files...

etc/config/dhcp

etc/config/dropbear

etc/config/firewall

etc/config/fstab

etc/config/luci

etc/config/network

etc/config/system

etc/config/ucitrack

etc/config/uhttpd

etc/config/wireless

etc/dropbear/dropbear_dss_host_key

etc/dropbear/dropbear_rsa_host_key

etc/group

etc/hosts

etc/inittab

etc/passwd

etc/profile

etc/rc.local

etc/shells

etc/sysctl.conf

Sending TERM to remaining processes ...dnsmasq ubusd btnd logd netifd uhttpd ntpd

Sending KILL to remaining processes ... 

Switching to ramdisk...

Performing system upgrade...

Unlocking firmware ...

Writing from <stdin> to firmware...     

Appending jffs2 data from/tmp/sysupgrade.tgz to firmware...

Writing from <stdin> to firmware...     

Upgrade completed

Reboot (Y/n):

不帶-v時的升級過程:

root@OpenWrt:/# sysupgrade -i/tmp/firmware.img 

Keep config files over reflash (Y/n): y

Edit config file list (y/N): n

Saving config files...

Sending TERM to remaining processes ...dnsmasq ubusd btnd logd netifd uhttpd ntpd

Sending KILL to remaining processes ... 

Switching to ramdisk...

Performing system upgrade...

Unlocking firmware ...

Writing from <stdin> to firmware...     

Appending jffs2 data from/tmp/sysupgrade.tgz to firmware...

Writing from <stdin> to firmware...     

Upgrade completed

Reboot (Y/n):

繼續分析:

if [ $CONF_BACKUP_LIST -eq 1 ]; then

         add_uci_conffiles"$CONFFILES"

         cat"$CONFFILES"

         rm-f "$CONFFILES"

         exit0

fi

如果需要列出配置檔案列表,就呼叫add_uci_conffiles函式生成列表,並列印到終端。函式add_uci_conffiles(),找出需要儲存的配置檔案。通過在檔案/etc/sysupgrade.conf中,/lib/upgrade/keep.d/*目錄下,以及命令opkg list-changed-conffiles的輸出中,找出配置 檔案,其中opkg list-changed-conffiles列出使用者修改的配置檔案。

接下來:

if [ -n "$CONF_BACKUP" ];then   

         do_save_conffiles"$CONF_BACKUP"

         exit$?

fi

如果需要建立配置備份檔案,則呼叫函式do_save_conffiles,生成配置檔案。函式do_save_conffiles(),打包上一部列出的配置檔案

接下來:

if [ -n "$CONF_RESTORE" ]; then###需要恢復配置

         if[ "$CONF_RESTORE" != "-" ] && [ ! -f"$CONF_RESTORE" ]; then ###判斷所需要的配置檔案是否存在

                   echo"Backup archive '$CONF_RESTORE' not found."

                   exit1

         fi

         ["$VERBOSE" -gt 1 ] && TAR_V="v" ||TAR_V=""

         tar-C / -x${TAR_V}zf "$CONF_RESTORE"

         exit$?

fi

經過一些判斷,解壓配置檔案包。

接下來:

type platform_check_image,檢測platform_check_image命令是否存在,為了 下步做準備。找不到的話,指令碼 退出,升級終止。

接下來:

for check in $sysupgrade_image_check; 

do( eval "$check\"\$ARGV\"" ) || {  ###通過board name image magicnumber來判斷升級檔案是否合法

if [ $FORCE -eq 1 ]; then  ####檢測失敗了,但是因為設定了-F選項,強制升級,停止檢測

                            echo"Image check '$check' failed but --force given - will update anyway!"

                            break

                   else   ###檢測失敗,且不要求強制升級,指令碼退出,停止升級

                            echo"Image check '$check' failed."

                            exit1

                   fi

         }

done

做升級檔案的檢測,$sysupgrade_image_check就是platform_check_image,這個 檢測在升級開始的 時候,已經做過了 ,這裡又做了一遍。如果檢測失敗了,但是設定了-F選項,強制升級,如果沒設定,就指令碼退出,停止升級。

接下來:

if [ -n "$CONF_IMAGE" ];then  ####需要從檔案中恢復配置

         case"$(get_magic_word $CONF_IMAGE cat)" in   ####獲取檔案內容,並拷貝2個位元組,且轉換為16進位制輸出

                   #.gz files

                   1f8b);;  ###檢測檔案失敗,退出指令碼,停止升級

                   *)

                            echo"Invalid config file. Please use only .tar.gz files"

                            exit1

                   ;;

         esac

         get_image"$CONF_IMAGE" "cat" > "$CONF_TAR"

         exportSAVE_CONFIG=1

elif ask_bool $SAVE_CONFIG "Keepconfig files over reflash"; then ###預設升級儲存配置

         [$TEST -eq 1 ] || do_save_conffiles

         exportSAVE_CONFIG=1

else

         exportSAVE_CONFIG=0

fi

這段的意思是,如果需要從檔案中恢復配置,就開始處理。這個檔案是從命令列引數中傳進來的。get_magic_word函式在檔案common.sh中,這個函式與前面所講的get_magic_long函式實現基本一樣,所不同的是get_magic_word函式利用dd bs=2 count=1,獲取頭2個位元組,而不是4個。

接下來

if [ $TEST -eq 1 ]; then  

         exit0

fi

這段的意思是:如果設定了-T選項,因為只是做了升級檔案和.tar.gz配置檔案的檢測,不需要升級,指令碼退出,停止升級。

接下來:

run_hooks ""$sysupgrade_pre_upgrade

 這句的意思是:執行函式sysupgrade_pre_upgrade。先介紹下run_hooks函式,定義在檔案common.sh中。

run_hooks() {

         localarg="$1"; shift

         forfunc in "$@"; do

                   eval"$func $arg"

         done

run_hooks函式是鉤子函式,其中傳過來的第一個引數是函式執行的引數,其餘引數為要執行的函式。

再介紹sysupgrade_pre_upgrade函式。定義在platform.sh檔案中。

#append sysupgrade_pre_upgrade  disable_watchdog

disable_watchdog() { 

         killallwatchdog

         (ps | grep -v 'grep' | grep '/dev/watchdog' ) && {

                   echo'Could not disable watchdog'

                   return1

         }

}

其實就是在升級前,去清除watchdog程序。不過這個函式被註釋掉了,所以不用管。

接下來:

ubus call system upgrade

openwrt ubus:為了在OpenWrt中提供守護程序和應用程式間的通訊,開發了ubus專案工程。它包含了守護程序、庫以及一些額外的幫助程式。

核心部分是ubusd守護程序,它提供了其他守護程序將自己註冊以及傳送訊息的介面。因為這個,介面通過使用Unixsocket來實現,並使用TLV(type-length-value)訊息。

用法:Usage: ubus [<options>] <command> [arguments...]

這句的意思是:呼叫註冊到ubus程序的system路徑下的update方法,update方法設定了upgrade_running變數值為1,使得在ubus上註冊的服務退出時無需等待。

do_upgrade $1 $2 $3  最後,會呼叫do_upgrade 這三個引數是由system.lua傳進來的,分別是

/tmp/firmware.img 4718596 74833bd4ece2239813a232f80d6ee855     $1代表韌體檔案, $2代表size $3代表md5

也就是說,sysupgrade完成了大量的檢測,備份等操作,最後呼叫do_upgrade來執行韌體升級, do_upgrade位於/lib/upgrade/common.sh

. common.sh

common.sh中組要是處理do_upgrade

do_upgrade() {

     v "Performing system upgrade..."

     if type 'platform_do_upgrade' >/dev/null 2>/dev/null; then

         platform_do_upgrade "$ARGV"

     else

         default_do_upgrade "$ARGV"

     fi

     v "Readback firmware and Check"

     mtd check $PART_NAME "$2" "$3"

     if [ $? -eq 1 ]; then

         v "Firmware Check Failed"

         exit 1

     else

         v "Upgrade completed"

         mtd create $PART_NAME success

         if [ $? -eq 1 ]; then

              v "Create Misc Info Failed"

              exit 1

         else

              reboot

         fi

     fi

}

其中 $PART_NAME firmwareB (或frimwareA, 在我們的系統中,存在frimwareA & frimwareB兩個韌體儲存位置, 這個在刷韌體時,會獲取上次刷寫韌體的位置,然後本次刷寫到另一個位置)

$2代表韌體size

$3代表md5

[形如 mtd check firmwareB 4718596 c09ad918ccfb8aa88ea1e2601fc1768e ]

get_current_partiton() {

     mtd=`cat /proc/cmdline | awk  '{print $2}' | awk -F= '{print $2}' | awk -F/ '{print $3}'`

     if [ "$mtd" == "mtdblock7" ] ; then

     #Current mtd is firmwareB, the target is firmwareA

         PART_NAME=firmwareA// 

     else

         PART_NAME=firmwareB

     fi

     echo $PART_NAME

}

//從我們測試現象來下,frimwareB代表//dev/mtdblock7, frimwareA代表mtdblock5

default_do_upgrade() {

     get_current_partiton

     sync

     if [ "$SAVE_CONFIG" -eq 1 ]; then

         get_image "$1" | mtd $MTD_CONFIG_ARGS -j "$CONF_TAR" write - "${PART_NAME:-image}"

     else

         get_image "$1" | mtd write - "${PART_NAME:-image}"

         if [ $? -eq 1 ]; then

              v "Write Flash Failed"

              exit 1

         fi

     fi

}

參考連結: