分类: LINUX
2016-02-01 11:11:45
SquashFS 是一套基于Linux内核使用的压缩只读文件系统。该文件系统能够压缩系统内的文档,以及目录,文件最大支持2^64字节。
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 < $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 |
--/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:tplink HEADER_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)。 |
--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_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字段。 |