Openwrt squafs文件系统及sysupgrade升级探究

来源:互联网 发布:江苏清华紫光软件集团 编辑:程序博客网 时间:2024/05/04 00:26

Openwrt squafs文件系统及sysupgrade升级探究2016-02-01 11:11:45

http://blog.chinaunix.net/uid-29767867-id-5606128.html 一位大神的文章膜拜一下

分类: LINUX

1.平台简介:

    硬件平台: QCA9531
    软件平台: Openwrt-trunk    Kernel-4.1.15

2.软件调试:

    对MIPS而言,在Openwrt框架中的文件系统格式主要使用jffs2和squash两种格式。

2.1 squash文件系统:

      SquashFS 是一套基于Linux内核使用的压缩只读文件系统。该文件系统能够压缩系统内的文档,inode以及目录,文件最大支持2^64字节。

      SquashFS是基于GPL协议的开源软件。初始的版本使用GZIP压缩,2.6.34版本内核增加了支持LZMA和LZO压缩,并且在2.6.38内核版
本上增加支持XZ压缩。


2.2 sysupgrade升级

      sysupgrade为Openwrt中的一个升级工具,其是一个shell脚本来实现的,具体功能我们根据源码进行分析:

--/package/base-files/files/sbin/sysupgrade

#!/bin/sh
. /lib/functions.sh                 #NOTE:在shell脚本中,"."类似于bash中的source命令,主要用于导入一些功能函数和变量
. /lib/functions/system.sh

#NOTE:
    上述导入的两个脚本,分别位于如下位置:
      package/base-files/files/lib/functions.sh            主要包含一些config操作方法
       package/base-files/files/lib/function.system.sh  主要包含一些mtd和mac操作的方法

# initialize defaults
RAMFS_COPY_BIN=""       # extra programs for temporary ramfs root
RAMFS_COPY_DATA=""      # extra data files
export MTD_CONFIG_ARGS=""
export INTERACTIVE=0
export VERBOSE=1
export SAVE_CONFIG=1
export SAVE_OVERLAY=0
export DELAY=
export CONF_IMAGE=
export CONF_BACKUP_LIST=0
export CONF_BACKUP=
export CONF_RESTORE=
export NEED_IMAGE=
export HELP=0
export FORCE=0
export TEST=0

# parse options
while [ -n "$1" ]; do
        case "$1" in
                -i) export INTERACTIVE=1;;
                -d) export DELAY="$2"; shift;;
                -v) export VERBOSE="$(($VERBOSE + 1))";;
                -q) export VERBOSE="$(($VERBOSE - 1))";;
                -n) export SAVE_CONFIG=0;;
                -c) export SAVE_OVERLAY=1;;
                -b|--create-backup) export CONF_BACKUP="$2" NEED_IMAGE=1; shift;;
                -r|--restore-backup) export CONF_RESTORE="$2" NEED_IMAGE=1; shift;;
                -l|--list-backup) export CONF_BACKUP_LIST=1; break;;
                -f) export CONF_IMAGE="$2"; shift;;
                -F|--force) export FORCE=1;;
                -T|--test) export TEST=1;;
                -h|--help) export HELP=1; break;;
                -*)
                        echo "Invalid option: $1"
                        exit 1
                ;;
                *) break;;
        esac
        shift;                 #NOTE:shift用于位置参数左移
done


export CONFFILES=/tmp/sysupgrade.conffiles
export CONF_TAR=/tmp/sysupgrade.tgz

export ARGV="$*"                #NOTE:参数
export ARGC="$#"               #NOTE:参数数量
(#NOTE: 通常使用sysupgrade工具进行升级时,只携带一个参数镜像名,对应这里则为 ARGV为镜像名,ARGC为1)

[ -z "$ARGV" -a -z "$NEED_IMAGE" -o $HELP -gt 0 ] && {  #NOTE: 判定使用帮助条件
        cat <Usage: $0 [...]
       $0 [-q] [-i]

upgrade-option:
        -d    add a delay before rebooting
        -f   restore configuration from .tar.gz (file or url)
        -i           interactive mode
        -c           attempt to preserve all changed files in /etc/
        -n           do not save configuration over reflash
        -T | --test
                     Verify image and config .tar.gz but do not actually flash.
        -F | --force
                     Flash image even if image checks fail, this is dangerous!
        -q           less verbose
        -v           more verbose
        -h | --help  display this help

backup-command:
        -b | --create-backup
                     create .tar.gz of files specified in sysupgrade.conf
                     then exit. Does not flash an image. If file is '-',
                     i.e. stdout, verbosity is set to 0 (i.e. quiet).
        -r | --restore-backup
                     restore a .tar.gz created with sysupgrade -b
                     then exit. Does not flash an image. If file is '-',
                     the archive is read from stdin.
        -l | --list-backup
                     list the files that would be backed up when calling
                     sysupgrade -b. Does not create a backup file.

EOF
        exit 1
}

[ -n "$ARGV" -a -n "$NEED_IMAGE" ] && {             #NOTE:判定备份参数使用
        cat <<-EOF
                -b|--create-backup and -r|--restore-backup do not perform a firmware upgrade.
                Do not specify both -b|-r and a firmware image.
        EOF
        exit 1
}

# prevent messages from clobbering the tarball when using stdout
[ "$CONF_BACKUP" = "-" ] && export VERBOSE=0

add_uci_conffiles() {
        local file="$1"
        ( find $(sed -ne '/^[[:space:]]*$/d; /^#/d; p' \
                /etc/sysupgrade.conf /lib/upgrade/keep.d/* 2>/dev/null) \
                -type f -o -type l 2>/dev/null;
          opkg list-changed-conffiles ) | sort -u > "$file"
#NOTE:上语句由主要由4部分组成:
  (1)sed分:利用正则表达式‘/^[[:space:]]*$/d; /^#/d; p’在/etc/sysupgrade.conf /lib/upgrade/keep.d/*过滤中相应
          配置文件,具体过滤出的文件可通过复制sed命令执行进行查看。
  (2)find部分: 在sed过滤出的文件中继续过滤出文件类型为普通文件和链接文件的两种文件。
  (3)opkg部分: 列出配置文件有改变的文件。
  (4)sort部分:  将最终过滤出的文件排序后导入file中,即file变量最终记录由变化的配置文件的列表
        return 0
}

add_overlayfiles() {
        local file="$1"
        if [ -d /overlay/upper ]; then
                local overlaydir="/overlay/upper"
        else
                local overlaydir="/overlay"
        fi
        find $overlaydir/etc/ -type f -o -type l | sed \
                -e 's,^/overlay\/upper/,/,' \
                -e 's,^/overlay/,/,' \
                -e '\,/META_[a-zA-Z0-9]*$,d' \
                -e '\,/functions.sh$,d' \
                -e '\,/[^/]*-opkg$,d' \
        > "$file"
        return 0
}

# hooks
sysupgrade_image_check="platform_check_image"     #NOTE:此处定义sysupgrade_image_check为下面check做准备
[ $SAVE_OVERLAY = 0 -o ! -d /overlay/etc ] && \    #NOTE:默认情况下SAVE_OVERLAY为0,即sysupgrade_init_conffiles="add_uci_conffiles"
        sysupgrade_init_conffiles="add_uci_conffiles" || \
        sysupgrade_init_conffiles="add_overlayfiles"

include /lib/upgrade                   #NOTE:包含lib/upgrade目录下的所有文件

[ "$1" = "nand" ] && nand_upgrade_stage2 $@  
#NOTE:如果参数1为"nand",则调用后面的函数,这里参数1为镜像名,所以不会调用后者

do_save_conffiles() {
        local conf_tar="${1:-$CONF_TAR}" #若调用该函数时没有传入参数时,使用CONF_TAR的值,否则使用传入的参数值

        [ -z "$(rootfs_type)" ] && {   
   #NOTE:该函数定义在package/base-files/files
/common.sh中,正常情况下rootfs_type的值为overlay
                echo "Cannot save config while running from ramdisk."
                ask_bool 0 "Abort" && exit
                return 0
        }
        run_hooks "$CONFFILES" $sysupgrade_init_conffiles #NOTE:以参数CONFFILES调用钩子函数sysupgrade_init_conffiles
        ask_bool 0 "Edit config file list" && vi "$CONFFILES"

        v "Saving config files..."
        [ "$VERBOSE" -gt 1 ] && TAR_V="v" || TAR_V=""
        tar c${TAR_V}zf "$conf_tar" -T "$CONFFILES" 2>/dev/null

        rm -f "$CONFFILES"
}

if [ $CONF_BACKUP_LIST -eq 1 ]; then #NOTE:若只指定镜像名,则CONF_BACKUP_LIST值为0,条件不满足。
        add_uci_conffiles "$CONFFILES"
        cat "$CONFFILES"
        rm -f "$CONFFILES"
        exit 0
fi

if [ -n "$CONF_BACKUP" ]; then   #NOTE:若只指定镜像名,则CONF_BACKUP值为空,条件不满足。
        do_save_conffiles "$CONF_BACKUP"
        exit $?
fi

if [ -n "$CONF_RESTORE" ]; then  #NOTE:若只指定镜像名,则CONF_RESTORE值为空,条件不满足。
        if [ "$CONF_RESTORE" != "-" ] && [ ! -f "$CONF_RESTORE" ]; then
                echo "Backup archive '$CONF_RESTORE' not found."
                exit 1
        fi

        [ "$VERBOSE" -gt 1 ] && TAR_V="v" || TAR_V=""
        tar -C / -x${TAR_V}zf "$CONF_RESTORE"
        exit $?
fi

type platform_check_image >/dev/null 2>/dev/null || {#NOTE:检测平台是是否实现platform_check_image方法
        echo "Firmware upgrade is not implemented for this platform."
        exit 1
}

for check in $sysupgrade_image_check; do
        ( eval "$check \"\$ARGV\"" ) || {
(#NOTE:此处调用上面初始化的函数platform_check_image并传入镜像名进行校验,该工具函数通过include /lib/upgrade
              导入,该工具函数位置为target/linux/ar71xx/base-files/lib/upgrade/platform.sh,当移植一个新平台时,利用
              sysupgrade工具升级失败,往往即为此处校验错误,具体参见后续的platform.sh中的实现。
)

                if [ $FORCE -eq 1 ]; then     #NOTE: 判定是否强制升级
                        echo "Image check '$check' failed but --force given - will update anyway!"
                        break
                else
                        echo "Image check '$check' failed."
                        exit 1
                fi
        }
done

if [ -n "$CONF_IMAGE" ]; then  #NOTE:若只指定镜像名,则CONF_IMAGE值为空,条件不满足,可通过-f选项指定
        case "$(get_magic_word $CONF_IMAGE cat)" in
                # .gz files
                1f8b) ;;
                *)
                        echo "Invalid config file. Please use only .tar.gz files"
                        exit 1
                ;;
        esac
        get_image "$CONF_IMAGE" "cat" > "$CONF_TAR"
        export SAVE_CONFIG=1
elif ask_bool $SAVE_CONFIG "Keep config files over reflash"; then #NOTE:SAVE_CONFIG默认为1,满足条件
        [ $TEST -eq 1 ] || do_save_conffiles #NOTE:TEST默认为0,这里执行do_save_conffiles保存文件
        export SAVE_CONFIG=1
else
        export SAVE_CONFIG=0
fi

if [ $TEST -eq 1 ]; then
        exit 0
fi

run_hooks "" $sysupgrade_pre_upgrade
(#NOTE:调用钩子函数sysupgrade_pre_upgrade,该函数定义在target/linux/ar71xx/base-files/lib/upgrade/platform.sh
      中,其定义方式如下:
                  append sysupgrade_pre_upgrade disable_watchdog
             其中定义函数append定义在/package/base-files/files/lib/functions.sh,中,其内容如下:
                    append() {
                        local var="$1"
                        local value="$2"
                        local sep="${3:- }"
                        eval "export ${NO_EXPORT:+-n} -- \"$var=\${$var:+\${$var}\${value:+\$sep}}\$value\""
            上述调用替换的结果为:
         export -n  sysupgrade_pre_upgrade=disable_watchdog
            即调用sysupgrade_pre_upgrade,则是调用disable_watchdog.

)
# Some platforms/devices may want different sysupgrade process, e.g. without
# killing processes yet or calling ubus system upgrade method.
# This is needed e.g. on NAND devices where we just want to trigger stage1 at
# this point.
if type 'platform_pre_upgrade' >/dev/null 2>/dev/null; then
        platform_pre_upgrade "$ARGV"
fi

ubus call system upgrade
(#NOTE:此处利用ubus调用升级命令
)
touch /tmp/sysupgrade

if [ ! -f /tmp/failsafe ] ; then
        kill_remaining TERM
        sleep 3
        kill_remaining KILL
fi

if [ -n "$(rootfs_type)" ]; then
        v "Switching to ramdisk..."
        run_ramfs '. /lib/functions.sh; include /lib/upgrade; do_upgrade'
else
        do_upgrade
fi

        sysupgrade镜像的校验检测是按平台实现的,下述即为校验的入口:
--/target/linux/ar71xx/base-files/lib/upgrade/platform.sh
. /lib/ar71xx.sh
.......

platform_check_image()
{
        local board=$(ar71xx_board_name)     
(#NOTE:变量ar71xx_board_name从target/linux/ar71xx/base-files/lib/ar71xx.sh中引入,详情参见后续board_name的引入)

      local magic="$(get_magic_word "$1")"
        local magic_long="$(get_magic_long "$1")"
(#NOTE:函数get_magic_word和函数get_magic_long来自./package/base-files/files/lib/upgrade/common.sh
      函数get_magic_word用于获取镜像的二进制前两个字节;函数get_magic_long用于获取镜像的二进制的前四个字节;
      可分别使用hexdump -n 2 和 hexdump -n 4 进行查看。
     对tplink平台设备而言,此magic即为512字节头的第一个成员,
              uint32_t        version;
              其在tools/firmware-utils/src/mktplinkfw.c中的初始化流程如下:
               #define HEADER_VERSION_V1       0x01000000
               #define HEADER_VERSION_V2       0x02000000

              static uint32_t opt_hdr_ver = 1;

              static int check_options(void)
              {       
                    ......
                    if (opt_hdr_ver == 1) {       #NOTE:该变量目前只支持两个版本,使用-m参数指定处理:sscanf(optarg, "%u", &opt_hdr_ver);
                            hdr_ver = HEADER_VERSION_V1;
                    } else if (opt_hdr_ver == 2) {
                            hdr_ver = HEADER_VERSION_V2;
                    } else {
                           ERR("invalid header version '%u'", opt_hdr_ver);
                           return -1;
                    }
                    return 0;
              }
         
              static void fill_header(char *buf, int len)
              {
                     ......
                     hdr->version = htonl(hdr_ver);
                     ......
              }

)
        [ "$#" -gt 1 ] && return 1   #NOTE:该工具只接收一个参数,即镜像名

        case "$board" in       #NOTE:从这里可以看出,平台按board进行匹配
        all0315n | \
        all0258n | \
        cap324 | \
        cap4200ag | \
        cr3000 |\
        cr5000)
                platform_check_image_allnet "$1" && return 0
                return 1
                ;;
         .....

        tl-wr941nd | \
        tl-wr941nd-v5 | \
        tl-wr941nd-v6 | \
        tl-wr1041n-v2 | \
        tl-wr1043nd | \
        tl-wr1043nd-v2 | \
        tl-wr2543n)
                local magic_ver="0100"   #NOTE:tplinkHEADER_VERSION_V1,可用hexdump -n 2 查看

                case "$board" in
                tl-wdr6500-v2)
                        magic_ver="0200"
                        ;;
                esac

                [ "$magic" != "$magic_ver" ] && {  #NOTE:此处进行magic比较,即对tplink头中的version字段的比较
                        echo "Invalid image type."
                        return 1
                }

                local hwid
                local imageid

                hwid=$(tplink_get_hwid)
                imageid=$(tplink_get_image_hwid "$1")
(#NOTE: 上述两个函数,用于取tplink的hwid,tplink_get_image函数从512字节的头部中取, 而tplink_get_hwid的取值
     方式如下:
     tplink_get_hwid() {
             local part
        part=$(find_mtd_part u-boot)
             [ -z "$part" ] && return 1
             dd if=$part bs=4 count=1 skip=81728 2>/dev/null | hexdump -v -n 4 -e '1/1 "%02x"'
     
             Shit!! 暂时不知道那个81728的skip是怎么算出来的???

     }
)
                [ "$hwid" != "$imageid" ] && {
                        echo "Invalid image, hardware ID mismatch, hw:$hwid image:$imageid."
                        return 1
                }

                local boot_size

                boot_size=$(tplink_get_image_boot_size "$1")     #NOTE: tplink 512字节的头部中的size字段检测
                [ "$boot_size" != "00000000" ] && {
                        echo "Invalid image, it contains a bootloader."
                        return 1
                }

                return 0
                ;;

            ......

       wnr2200)
                [ "$magic_long" != "32323030" ] && {
                        echo "Invalid image type."
                        return 1
                }
                return 0
                ;;

        esac

        echo "Sysupgrade is not yet supported on $board."
        return 1
}
(#NOTE:从上面过程可以看出,platform_check_image以ar71xx_board_name为类别标识主要检查了version,hwid,和size)。 

      上述提及到sysupgrade工具升级过程中很关键的一个因素local board=$(ar71xx_board_name)中的ar71xx_board_name.
该变量为后续判定的内别标识,因此有必要研究一下该变量是如何生成的?
      下面是该变量源自target/linux/ar71xx/base-files/lib/ar71xx.sh中,其定义如下:
--target/linux/ar71xx/base-files/lib/ar71xx.sh

ar71xx_board_name() {
        local name

        [ -f /tmp/sysinfo/board_name ] && name=$(cat /tmp/sysinfo/board_name)  #NOTE: 依赖于/tmp/sysinfo/board_name中的内容
        [ -z "$name" ] && name="unknown"

        echo "$name"
}

      从上述函数可以看出,ar71xx_board_name本质上是以来文件/tmp/sysinfo/board_name中的内容来确定的,那么这个文件又是
如何产生的呢? 答案就在该文件中的ar71xx_board_detect()函数中,该函数的定义如下:ar71xx_board_detect() {
        local machine
        local name

        machine=$(awk 'BEGIN{FS="[ \t]+:[ \t]"} /machine/ {print $2}' /proc/cpuinfo)  #NOTE: machine即为/proc/cpuinfo中的对应字段取值

        case "$machine" in            #NOTE:下述以machine字段为匹配项目,为name赋值,进而创建board_name
        *"Oolite V1.0")
                name="oolite"
                ;;
                 ......
        *"HiWiFi HC6361")
                name="hiwifi-hc6361"
                ;;
        esac
 

        [ -z "$AR71XX_MODEL" ] && [ "${machine:0:8}" = 'TP-LINK ' ] && \
                tplink_board_detect "$machine"

        [ -z "$name" ] && name="unknown"

        [ -z "$AR71XX_BOARD_NAME" ] && AR71XX_BOARD_NAME="$name"     #NOTE: 将name赋值给AR71XX_BOARD_NAME
        [ -z "$AR71XX_MODEL" ] && AR71XX_MODEL="$machine"                      #NOTE: AR71XX_MODEL即为匹配的模式

        [ -e "/tmp/sysinfo/" ] || mkdir -p "/tmp/sysinfo/"

        echo "$AR71XX_BOARD_NAME" > /tmp/sysinfo/board_name                 #NOTE:根据AR71XX_BOARD_NAME创建/tmp/sysinfo/board_name文件
        echo "$AR71XX_MODEL" > /tmp/sysinfo/model                                    #NOTE:根据AR71XX_MODEL创建/tmp/sysinfo/model文件
}

#NOTE:
      上述大体上可以看出以/tmp/sysinfo/model即,/proc/cpuinfo中的machine字段的值为匹配模式进行匹配,匹配成功后产生/tmp/sysinfo/board_name
么问题来了,/proc/cpuinfo中的machine字段又是如何决定的呢?
      这里涉及到一个宏函数MIPS_MACHINE(_type,_id,_name,_setup),其参数如下:
                    参数1: 该参数需要在machtypes.h中进行定义。
                    参数2: 该参数用于匹配cmdline中的board参数。
                    参数3: 该参数为名字标识,设置为字符串即可。
                    参数4: 该参数为函数指针,参数2匹配成功后即调用该参数。
       这里的参数3即是 /proc/cpuinfo中的machine字段。

        现在回过来头捋以下,sysupgrade围绕boar_name的相关流程
                                                                                                                                                      MIPS_MACHINE(,,name,) -> /proc/cpuinfo
                                                                                                                                                      /
       sysupgrade --> sysupgrade_image_check -->     \  /    <--  ar71xx_board_name <-- ar71xx_board_detect="" -="">/tmp/sysinfo/board_name
                                                                                     V                                                (case "$machine" in)               /tmp/sysinfo/model
                                                               platform_check_image: local board=$(ar71xx_board_name)                                              
                                                                  (case "$board" in)

        从上述的分析可以看出,添加一个新平台时需要进行如下动作:
         (1) 在target/linux/ar71xx/base-files/lib/ar71xx.sh的ar71xx_board_detect()函数中,需要根据MIPS_MACHINE(,,name,)
               中的那么为case "$machine" in 添加对应的case语句来赋值board_name。
         (2) 在target/linux/ar71xx/base-files/lib/upgrade/platform.sh的platform_check_image()函数中,需要根据(1)中赋值的
               board_name,为case "$board" in添加对应的case语句,这里需要注意匹配对应的magic_ver。
0 0