openwrtluci固件升级流程
openwrt luci固件升级流程
⽂档说明:
本⽂档简述了openwrt sdk的升级功能流程,从页⾯传⼊升级⽂件到升级⽂件检测,再到调⽤升级脚本进⾏升级,升级完成后,进⾏系统重启。最后简述了如何添加升级⽂件标识,标识包括⾃⼰定义的字段,以及升级软件的md5值。达到防⽌⽤户⽤其他openwrt sdk编译出来的固件刷新我们的系统,以及防⽌升级软件被破坏。
⽂档需要⽤到的⽂件及在sdk中的路径(某些⽂件路径可能随项⽬的不同⽽改动):
./package/ralink/ui/luci-mtk/src/modules/admin-full/luasrc/controller/admin/system.lua
./target/linux/ramips/image/ Makefile
./package/base-files/files/lib/upgrade/common.sh
./package/base-files/files/lib/functions.sh
.
/package/base-files/files/sbin/sysupgrade
./target/linux/ramips/base-files/lib/ramips.sh
./target/linux/ramips/base-files/lib/upgrade/platform.sh
⽂档正⽂:
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函数,获取board name,ramips_board_name函数从⽂件/tmp/sysinfo/board_name 中获取board name,若没有则为unknown,并返回给调⽤者,我⽤的板是ralink-soc。
platform_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=4 count=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最后的处理就是调⽤升级脚本:
Shell
fork_exec("killall dropbear uhttpd; sleep 1; /sbin/sysupgrade %s %q" %{ keep, image_tmp })
1
fork_exec("killall dropbear uhttpd; sleep 1; /sbin/sysupgrade %s %q" %{ keep, image_tmp })
这条语句,先清除dropbear 和uhttpd进程,再等待1秒,最后调⽤升级脚本sysupgrade,传过去的参数就是keep:是否要保存配置;image_tmp:升级⽂件/tmp/firmware.img。
OK,到这⾥system.lua⽂件中关于升级前的准备⼯作都完成了,视线请转到升级脚本sysupgrade上。
5.运⾏升级脚本
实现在⽂件sysupgrade中。
脚本开始,像所有的主体处理程序⼀样,会对传进来的参数进⾏处理。下⾯对这些参数的介绍:
-i 开启交互模式
-d 重启前延迟,延迟秒数是传进来的
-v 会打印sysupgrade脚本中的⼀些信息,脚本中默认打印
-q 与-v相反
-n 升级后不保存配置,默认保存配置
-c 保存所有的改动配置⽂件到/etc/
-b ⽤f中指定的⽂件,创建.格式备份⽂件
-r ⽤上步创建的.⽂件,恢复配置
eval是做什么的-l 列出将会备份的⽂件列表
-f 从.恢复配置
-F 即使升级⽂件检测失败,也要升级,这个参数是危险的,慎⽤
-T 验证升级⽂件和.配置⽂件,但不升级
-h 打印帮助信息
这些参数的使⽤在脚本中都有介绍,不再多讲。
接下来:
[ -z “$ARGV” -a -z “$NEED_IMAGE” -o $HELP -gt 0 ],
意思是:如果没有升级⽂件参数,且没有命令⾏参数-b(create-backup),-r(restore-backup),或者带有-h(help)参数,则打印帮助信息。这个条件为真的话,会在终端打印帮助信息,退出脚本。
接下来:
[ -n “$ARGV” -a -n “$NEED_IMAGE” ],意思是:不要指定-b或-r(创建配置、恢复配置)的同时,指定升级⽂件。为真的话,打印提⽰信息,退出脚本。
接下来:
[ “$CONF_BACKUP” = “-” ] && export VERBOSE=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
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
f
Sending TERM to remaining processes ... dnsmasq ubusd btnd logd netifd uhttpd ntpd Sending KILL to remaining processes ...
Switching
Performing
Unlocking firmware ...
Writing from <stdin> to firmware ...
Appending jffs2 data from /
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
Sending TERM to remaining processes ... dnsmasq ubusd btnd logd netifd uhttpd ntpd Sending KILL to remaining processes ...
Switching
Performing
Unlocking firmware ...
Writing from <stdin> to firmware ...
Appending jffs2 data from /
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"
exit 0
fi
如果需要列出配置⽂件列表,就 调⽤add_uci_conffiles函数⽣成列表,并打印到终端。函数add_uci_conffiles(),出需要保存的配置⽂件。通过在⽂件/f中 ,/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.”
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,检测platform_check_image命令是否存在,为了 下步做准备。不到的话,脚本 退出,升级终⽌。
接下来:
for check in $sysupgrade_image_check;
do( eval “$check \”\$ARGV\”” ) || { ###通过board name 和image magic number来判断升级⽂件是否合法
if [ $FORCE -eq 1 ]; then ####检测失败了,但是因为设置了-F选项,强制升级,停⽌检测
echo “Image check ‘$check’ failed but –force given – will update anyway!”
break
else ###检测失败,且不要求强制升级,脚本退出,停⽌升级
echo “Image check ‘$check’ failed.”
exit 1
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 . 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 ###默认升级保存配置
[ $TEST -eq 1 ] || do_save_conffiles
export SAVE_CONFIG=1
else
export SAVE_CONFIG=0
fi
这段的 意思是,如果需要从⽂件中恢复配置,就开始处理。这个⽂件是从命令⾏参数中传进来的。get_magic_word函数在⽂件common.sh中,这个函数与前⾯所讲的get_magic_long函数实现基本⼀样,所 不同的是get_magic_word函数利⽤dd bs=2 count=1,获取头2个字节,⽽不是4个。
接下来“
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论