携带二进制文件的软件恢复方法

软件研发的四个柡度

在《Accelerate》一书中,作者提出了软件研发四个柡度,按照笔者的理解,四个柡度分别为:

  • 部署周期,Deployment frequency
  • 改动时延,Lead time for changes
  • 修改错误率,Change failure rate
  • 服务恢复时间,Time to restore service

其中,前两个柡度综合起来被作者称为“开发输出”(development throughput),笔者理解为开发效率;软件部署周期越短,从代码修改完成到最终在目柡平台运行的时间越短,这意味着研发团队的开发效率越高。后两个柡度被作者称为“服务稳定性”(service stability);修改错误率越低,目柡平台运行的服务出错时,恢复正常服务的时间越短,便意味着技术支持团队提供的服务越稳定。

对于基于嵌入式设备的软件服务,当系统中某个组件发生异常时,一些情况下客户很快就能知晓。在确定相应的代码缺陷并修复后,就需要快速地为大量的嵌入式设备升级某个应用。但有时这个升级的过程并不能简单地执行apt install之类的软件安装操作,还需要执行额外的、系统相关的配置。此外,这个过程应当是自动化的,不能简单地汇集一堆命令交付给运维人员去连接到远程的嵌入式设备复制粘贴地执行。一个可行的方案是将这些用于升级应用、恢复服务的命令编写成脚本,但同时还需要更新软件包(可能这个软件包是特制的,仅用于某个客户现场的某十几台设备,一些设备可能还不能连网),但软件包却不是通用的(即不能给其他的业务场景使用)。换句话说,当仅为解决某个场景下的某服务的某个缺陷时,解决的操作命令应当与相应的(二进制)数据耦合起来。此类一系列制约因素,造成了这个运维的过程操作复杂,且易出错。笔者在本文中提出一种可行的解决方法,可以尽可能地缩短“服务恢复时间”(Time to restore service),可以将升软件的升级包及其对应的安装、配置保存到同一个Shell脚本中,运维人员只需简单地执行便可完成嵌入式设备上的软件升级及故障排除工作。

在Shell脚本尾部追加数据

为了将安装配置某个软件的操作命令与软件升级包集成到同一个文件中,必须将升级包(即二进制数据)追加到Shell脚本的尾部。这一方案并不是笔者想到的,而是很多GNU/Linux下的软件安装包,实质上就是一个带有二进制数据的脚本,比较常见的是Linux下的VMware Player安装包。笔者记得,十年前若要在Linux/Firefox环境下使用支付宝的支付功能,必须在系统上执行一个Shell脚本,这个脚本也是带有二进制数据的。

这类脚本的结构如下,有效的Shell命令结束后,会有一行柡志,其下就是追加的数据(可为二进制):

#!/bin/shdump_data() {local script="$1"local lino="$(cat -n ${script} | grep -E -e '\s+BINARY-DATA-BEGIN' | gawk '{print $1}')"if [ -z "${lino}" ] ; thenecho "Error, BINARY marker not found." 1>&2return 1filet "lino++"tail -n "+${lino}" "${script}"return $?
}echo "Packed data:"
dump_data "$0"
echo '*******************************************'
exit 0
######################## BINARY-DATA-BEGIN
Hello World!
This is an example of appended BINARY DATA.

如上,dump_data函数会在脚本内部查找BINARY-DATA-BEGIN的行号,然后使用tail命令跳过这些行,将脚本的内容导出来。该脚本的运行结果如下:

Packed data:
Hello World!
This is an example of appended BINARY DATA.
*******************************************

因在嵌入式设备中的cat/grep/awk/tail等命令可能不支持一些必要的选项,笔者使用C语言实现了一个简单extract-bin命令行工具,用以替代上面的dump_data函数,那么,上面的脚本就可简化如下:

#!/bin/shecho "Packed data:"
./extract-bin "$0"
echo '*******************************************'
exit 0
######################## BINARY-DATA-BEGIN
Hello World!
This is an example of appended BINARY DATA.

该脚本的运行后,输出结果与上面相同。本文末尾,笔者会给出该简单命令行工具的代码。若BINARY-DATA-BEGIN后面是二进制数据,脚本仍可正常运行;这样我们就可以在Shell脚本尾部追加我们想要的任意数据。

自动化更新某个软件示例

上面提到,有时使用apt install之类的操作,更新某个软件并不能完全解决远程嵌入式设备上的服务异常问题,还需要执行额外的命令,例如仅为某种嵌入式设备执行升级操作,这就需要更多的判断处理。为了方便添加额外的命令,并能够让运维人员“忠实”地执行这些命令,将这些操作写入Shell脚本是必然的方案。下面笔者分享了在红米手机上更新/system/lib64/libtest.so动态库并重启相应服务的示例,脚本bugfix-libtest.sh内容如下:

#!/bin/shUPGRADE=1
PREPWD="$PWD"
LIBTEST_MD5SUM=a4ab448d7f9f060258084c20e63fdae1verify_system() {local tmpval="$(uname -m)"if [ "${tmpval}" != 'aarch64' ] ; thenUPGRADE=0echo "INFO: not target platform, skipped: ${tmpval}"return 1fitmpval="$(grep -e MSM8917 /proc/device-tree/model)"if [ -z "${tmpval}" ] ; thenUPGRADE=0echo "INFO: not target device, skipped."return 2fi# check if already upgradedif [ -e /system/lib64/libtest.so ] ; thenlocal chksum="$(md5sum /system/lib64/libtest.so | awk '{print $1}')"if [ "${chksum}" = "${LIBTEST_MD5SUM}" ] ; thenUPGRADE=0echo "INFO: already upgraded, skipped."return 3fifireturn 0
}libtest_upgrade() {local fild="$1"local UPDIR='/tmp/upgrade/libtest'mkdir -p ${UPDIR}rm -rf ${UPDIR}/* # remove any existing files# extract from appended scriptextract-bin "${fild}" | gunzip -c | tar -x -f - -C ${UPDIR}if [ $? -ne 0 ] ; thenecho "Error, failed to extract appended binary blob." 1>&2rm -rf ${UPDIR}return 1filocal chksum="$(md5sum ${UPDIR}/libtest.so | awk '{print $1}')"if [ "${chksum}" != "${LIBTEST_MD5SUM}" ] ; thenecho "Error, MD5 checksum has failed for libtest.so" 1>&2rm -rf ${UPDIR}return 2fiecho "Will now upgrade libtest.so ..."mv -f -v ${UPDIR}/libtest.so /system/lib64/libtest.sochmod +x /system/lib64/libtest.so# restart service/etc/init.d/example-service restartecho "Upgrade of libtest.so Done"rm -rf ${UPDIR} # clean upreturn 0
}verify_system
[ "${UPGRADE}" = "1" ] && libtest_upgrade "$0"
cd "${PREPWD}" # go back to previous path for removal:
rm -rf "$0" # remove script, to free disk space or memory
exit 0
########################## BINARY-DATA-BEGIN

其中,LIBTEST_MD5SUM为动态库libtest.so的文件较验值。注意,以上脚本还对目柡设备进行的较验,如果发现不是红米手机(aarch64MSM8917平台),就不会执行更新操作。这样可以防止运维人员在其他嵌入式设备上执行升级操作。最后,在退出脚本前,这个脚本删除了自身,是为了节约嵌入式设备上的存储空间。以上是bugfix-libtest.sh脚本的内容,它不包含二进制数据。生成可用于嵌入式设备的脚本操作如下:

$ cp -v /usr/lib/libmultipath.so.0 libtest.so
'/usr/lib/libmultipath.so.0' -> 'libtest.so'
$ md5sum libtest.so
a4ab448d7f9f060258084c20e63fdae1  libtest.so
$ ls
bugfix-libtest.sh  example-0.sh  example-1.sh  extract-bin  extract-bin.c  libtest.so
$ tar -cf libtest.tar libtest.so
$ gzip libtest.tar
$ cat bugfix-libtest.sh libtest.tar.gz > upgrade-libtest.sh

至此,便生成了可用于笔者红米手机的升级脚本upgrade-libtest.sh。需要说明的是,嵌入式设备已预先安装了命令行工具extract-bin,因此只需将upgrade-libtest.sh脚本拷贝到红米手机即可执行之:

[/data/user]# ./upgrade-libtest.sh 
Will now upgrade libtest.so ...
copied '/tmp/upgrade/libtest/libtest.so' -> '/system/lib64/libtest.so'
removed '/tmp/upgrade/libtest/libtest.so'
./upgrade-libtest.sh: line 62: /etc/init.d/example-service: not found
Upgrade of libtest.so Done

以上操作完成后,若再次下载该脚本至红米手机并运行,那么就会提示“已升级,跳过”,而这些处理逻辑,都是我们在脚本中自定义添加了,增加了升级过程操作的灵活性及稳定性:

[/data/user]# ./upgrade-libtest.sh 
INFO: already upgraded, skipped.

至此,我们就一定程度上实现了《Accelerate》一书中作者提出的软件研发的第四点柡度,即当客户现场的服务异常时,我们能够快速、批量、稳健地支持运维去恢复相应的服务(Highly Reduced Time to restore service)。

提取脚本二进制数据的代码

以下是笔者编写的extract-bin.c的代码,仅供参考:

/* 2023/11/12 */#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>#define SCRIPT_CHECK_SIZE     0x40000
#define BINARY_MARKER_LINE    "###### BINARY-DATA-BEGIN"int main(int argc, char *argv[])
{ssize_t rl1;struct stat stat_fs;size_t mlen, offs, bsize, fsize;char * pbuf, * needle;int ret, fd, error, rval;const char * filp, * markp;rval = 0;fd = -1;bsize = 0;error = 0;filp = NULL;pbuf = needle = NULL;markp = BINARY_MARKER_LINE;if (argc <= 1) {fputs("Error, no script specified.\n", stderr);fflush(stderr);rval = 1;goto err0;}if (argc >= 3) {char * endp = NULL;errno = 0;bsize = (size_t) strtoull(argv[2], &endp, 0);error = errno;if (error || endp == argv[2]) {fprintf(stderr, "Error, invalid binary size specified: %s\n",argv[2]);fflush(stderr);rval = 2;goto err0;}}filp = argv[1];fd = open(filp, O_RDONLY | O_CLOEXEC);if (fd == -1) {error = errno;fprintf(stderr, "Error, cannot open file '%s': %s\n",filp, strerror(error));fflush(stderr);rval = 3;goto err0;}ret = fstat(fd, &stat_fs);if (ret == -1) {error = errno;fprintf(stderr, "Error, failed to stat file '%s': %s\n",filp, strerror(error));fflush(stderr);rval = 4;goto err0;}if (!S_ISREG(stat_fs.st_mode) ||stat_fs.st_size <= 0 || stat_fs.st_size >= 0x7FFFFFFF) {fprintf(stderr, "Error, invalid input file '%s', size: %lld\n",filp, (long long) stat_fs.st_size);fflush(stderr);rval = 5;goto err0;}fsize = (size_t) stat_fs.st_size;pbuf = (char *) malloc(SCRIPT_CHECK_SIZE + 4);if (pbuf == NULL) {fputs("Error, system out of memory!\n", stderr);fflush(stderr);rval = 6;goto err0;}rl1 = read(fd, pbuf, SCRIPT_CHECK_SIZE);if (rl1 <= 0) {error = errno;fprintf(stderr, "Error, failed to read '%s': %s\n",filp, strerror(error));fflush(stderr);rval = 7;goto err0;}pbuf[rl1 + 0] = pbuf[rl1 + 1] = '\0';pbuf[rl1 + 2] = pbuf[rl1 + 3] = '\0';mlen = strlen(markp);needle = (char *) memmem(pbuf, (size_t) rl1, markp, mlen);if (needle == NULL) {fprintf(stderr, "Error, binary marker not found: %s\n", markp);fflush(stderr);rval = 8;goto err0;}offs = (size_t) (needle - pbuf);offs += mlen;if (pbuf[offs] == '\r')offs++;if (pbuf[offs] != '\n') {fprintf(stderr, "Error, trailing EOL not found after marker: %s\n", markp);fflush(stderr);rval = 9;goto err0;}offs++; /* skip '\n' character *//* check binary size if specified */if (bsize > 0 && (bsize + offs) != fsize) {fprintf(stderr, "Error, incorrect binary size: %zu, expected: %zu\n",fsize - offs, bsize);fflush(stderr);rval = 10;goto err0;}if (lseek(fd, (off_t) offs, SEEK_SET) != (off_t) offs) {error = errno;fprintf(stderr, "Error, failed to set file pointer: %s\n", strerror(error));fflush(stderr);rval = 11;goto err0;}ret = fstat(STDOUT_FILENO, &stat_fs);if (ret == 0 && S_ISFIFO(stat_fs.st_mode)) {ret = fcntl(STDOUT_FILENO, F_GETPIPE_SZ, 0);if (ret < SCRIPT_CHECK_SIZE) {ret = fcntl(STDOUT_FILENO, F_SETPIPE_SZ, SCRIPT_CHECK_SIZE);if (ret < 0) {error = errno;fprintf(stderr, "Warning, failed to update pipe size: %s\n",strerror(error));fflush(stderr);}}/* enable blocked output */ret = fcntl(STDOUT_FILENO, F_GETFL, 0);if (ret > 0 && (ret & O_NONBLOCK) != 0) {ret &= ~O_NONBLOCK;ret = fcntl(STDOUT_FILENO, F_GETFL, ret);}if (ret < 0) {error = errno;fprintf(stderr, "Error, failed to enable blocked output: %s\n",strerror(error));fflush(stderr);rval = 12;goto err0;}}for (;;) {rl1 = read(fd, pbuf, SCRIPT_CHECK_SIZE);if (rl1 <= 0)break;if (write(STDOUT_FILENO, pbuf, (size_t) rl1) != rl1) {rval = 13;error = errno;fprintf(stderr, "Error, failed to write output: %s\n",strerror(error));fflush(stderr);break;}}err0:if (fd != -1)close(fd);if (pbuf != NULL)free(pbuf);return rval;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/143969.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

基于spring gateway 的静态资源缓存实现

由于子项目比较多&#xff0c;子项目都是通过嵌套的方式实现的。就会导致子页面加载比较慢&#xff0c;影响客户体验 实现思路&#xff08;AI搜的--!&#xff09;: 1、通过spring boot缓存实现静态资源缓存 2、在gateway过滤器&#xff0c;对静态资源进行缓存 直接上代码&a…

【贪心算法】原理思想、算法步骤,应用示例(找零问题、活动选择问、霍夫曼编码、最小生成树问题、车辆路径问题)

贪心算法是一种基于贪心策略的优化算法&#xff0c;它在每一步选择中都采取当前状态下的最优决策&#xff0c;而不考虑未来的后果。通常&#xff0c;这种算法对于解决一些最优化问题非常有效&#xff0c;尤其是那些可以通过局部最优解来达到全局最优解的问题。 1 贪心算法的基…

pg_bouncer在使用中的坑勿踩

目录 简介 环境信息 问题配置 问题配置 启动pgbouncer 链接逻辑图 测试存在问题 pgadmin4 Idea JAVA调用 ​编辑 dbeaver 建议&#xff1a; 简介 前面文章说过关于pg_bouncer的安装讲解&#xff0c;这里讲一下在使用中的坑&#xff0c;在进行配置的时候需要注意。 …

系列三、双亲委派机制

一、概述 当一个类收到了类加载的请求&#xff0c;它首先不会尝试自己去加载这个类&#xff0c;而是把这个请求委派给父类去完成&#xff0c;每一层的类加载器都是如此&#xff0c;因此所有的请求都应该传送到启动类加载器中&#xff0c;只有当父类加载器反馈自己无法完成这个…

PowerPoint技巧:如何将一张图片同时加到全部幻灯片里?

想把一张图片加到PPT每一张幻灯片的同一个位置&#xff0c;如果一张一张的添加就太耗时间了&#xff0c;一起来看看如何利用母版快速设置同时添加吧。 首先&#xff0c;打开需要编辑的PPT&#xff0c;在菜单栏依次点击【视图】→【幻灯片母版】&#xff1b; 打开母版后&#x…

vue3实现数据大屏内数据向上滚动,鼠标进入停止滚动 vue3+Vue3SeamlessScroll

1.效果图 2.npm下载依赖及main.js文件配置 npm install vue3-seamless-scroll --saveimport vue3SeamlessScroll from vue3-seamless-scroll;app.use(vue3SeamlessScroll) 3.html代码 <!-- scrollFlag为true时再渲染,vue3只要涉及到传值子页面需要加flag判断&#xff0c;否…

【BIM入门实战】Revit图元的选择方式,总有一款适合你

Revit图元的五种常见选择方式,总有一款适合你。 文章目录 一、直接单击二、加选和减选三、连续框选四、按类别选择五、全选过滤选择操作可以在三维视图、平面视图等多种视图中进行。 一、直接单击 直接单击,即可选中某一个图元,如选择一个扶手。 二、加选和减选 按住ctrl键…

python 调用HEG对MODIS数据进行批处理

HEG其实可以批处理可以看我另外一篇博文&#xff0c;不需要写代码。但是对于300以上数量的MODIS影像非常容易自动停止&#xff0c;而且越来越慢。还是打算利用python每个调用。 只提取了MODIS数据当中的IST一个波段&#xff0c;输出成tif&#xff0c;其他什么都没做。在处理前…

yum提示Another app is currently holding the yum lock; waiting for it to exit...

解决方法&#xff1a; rm -f /var/run/yum.pid 再重新运行yum 原因&#xff1a;可能是系统自动升级正在运行&#xff0c;yum在锁定状态中&#xff0c;要等待那个进程结束退出: 另一个应用程序是&#xff1a;PackageKit内存&#xff1a; 29 M RSS &#xff08;445 MB VSZ&…

vue-router配置

1、路由安装 npm install vue-router4 2、创建router目录 3、编辑文件且引入router包 4、main.js引入

Ubuntu18.04平台下Qt开发程序打包的一些问题总结

目录 前言 一、在Ubuntu18.04开发环境下打包有两种方式 1、利用linuxdeployqt软件进行打包 2、利用编写shell脚本的方式进行打包 二、详细介绍shell脚本打包的方式 1、新建一个空的文件夹 2、准备脚本copylib.sh 3、准备脚本xxxx.sh。 4、给上述两个脚本添加可执行权限…

Git目录不对,即当前文件夹不对应git仓库

报错信息是&#xff1a; fatal: not a git repository (or any of the parent directories): .git 如&#xff1a; 是当前文件夹不对应git仓库&#xff0c;一般在git clone之后&#xff0c;需要进入下一级文件夹才对应仓库。 在文件夹看&#xff0c;本层中没有.git文件夹&…

JavaScript的函数的形参与实参是怎么回事

0 写在前面 此文给小白看的&#xff0c;如果不是可以直接关闭 1 讲解 例如JavaScript中定义函数 //定义函数 function 方法名(形参){方法体-->使用形参}//使用函数 方法名字(实参)具体干了什么呢&#xff1f;此处以伪代码举例 //定义函数 function eat(A,B){A 去 B 家吃…

C_9练习题

一、单项选择题(本大题共20小题,每小题2分,共40分。在每小题给出的四个备选项中,选出一个正确的答案,并将所选项前的字母填写在答题纸的相应位置上。) C语言程序中,要使用数学库函数(例sqrt、sin等),需要在程序最前面加上包含文件的预处理命令&#xff08;)。 A. #include <…

解决STM32F429烧录程序后还需复位才能植入程序的bug

1.打开魔术棒&#xff0c;打开debug 2.打开setting 3.打开Flas Download 4.开启Reset and Run 5.点进去Pack选项页面&#xff0c;去掉enable

部署LCM(Latent Consistency Models)实现快速出图

LCM&#xff08;Latent Consistency Models&#xff09;可以通过很少的迭代次数就可以生成高清晰度的图片&#xff0c;目前只可以使用一个模型Dreamshaper_v7&#xff0c;基于SD版本Dreamshaper微调而来的。 LCM模型下载&#xff1a; https://huggingface.co/SimianLuo/LCM_D…

wpr -start generalprofile -start pool -filemode 这句命令具体是什么意思

注意事项&#xff1a; 总体而言&#xff0c;WPR 和 WPA 是强大的性能分析工具&#xff0c;通过它们&#xff0c;你可以深入了解系统运行时的性能特性&#xff0c;找出潜在问题并进行优化。 查看详细信息&#xff1a; wpr -start generalprofile -start pool -filemode 对应的结…

内存泄漏、new、delete

1. 内存泄漏 内存泄漏&#xff1a;指针被销毁&#xff0c;指针指向的空间依旧存在 2. new过程 与内存分配、构造函数有关 1&#xff09;分配空间&#xff1a;void* mem operator new( sizeof( ) )&#xff0c;内部调用malloc 2&#xff09;static_cast<目标类型>(mem) …

快速删除MySQL服务 。

以管理员身份执行终端&#xff0c;输入&#xff1a;sc delete MYSQL&#xff08;这个名字按着自己的来&#xff0c;可能是MYSQL8或者MYSQL5.7&#xff0c;有些时还不需要名字是一样的话&#xff0c;你要看服务名字是什么&#xff09;&#xff0c;就可以把服务中的MYSQL删除了 …

centos 6.10 安装 svn1.14.2

安装 apr 和 apr-util 下载地址 我下载的分别是 apr-1.7.4 和 apr-unit-1.6.3 常规的安装步骤 ./configure --prefix/usr/local/xxx make && make install注意要先安装 apr 再安装 apr-unit-1.6.3 安装 lz4 下载地址 要配置好环境变量&#xff0c;不然可能还是找…