Bsdiff,Bspatch 的差分增量升级(基于Win和Linux)

目录

         背景

内容

准备工作

在windows平台上

在linux平台上

正式工作

生成差分文件思路

作用差分文件思路

在保持相同目录结构进行差分增量升级

服务端(生成差分文件)

客户端(作用差分文件)

12月6日更新(修复bug)

执行分析

注意事项和关注的地方:


背景

像常见的Android 的linux平台,游戏,系统更新都会用到这一种方式。

以我自己的理解,这种方式有些像git中的版本管理, 以最少的时间进行版本管理.核心在于如何去记录文件的差异.

服务器端:

通过

 bsdiff  old  new  patchfile_path 

生成差分文件.一般以.patch的文件命名.

客户端: 根据patch文件 通过

bspatch oldfile newfile patchfile_path

一般情况下,本以为可以直接通过压缩包的形式去进行, 安卓平台的.apk文件是可以的,单片机的可执行hex等格式的文件也是可以的. 但通过压缩的压缩包则可能会有隐患. 通过开会讨论以及本人查询资料发现 会因为压缩算法,压缩文件顺序的不一样而导致差分包出现问题.

原因有

主要原因有:

1. 不同的压缩算法会产生不同的压缩数据。即使原始数据相同,通过不同算法压缩结果也不完全一样。这会直接影响bsdiff的比较结果。

2. 即使使用同一压缩算法,压缩文件内原始数据的顺序改变也可能改变压缩效果。压缩算法利用重复模式来达到压缩效果。顺序改变会打乱这种模式。

3. bsdiff是按顺序比较数据生成差分的。所以就算压缩原理数据相同,其在压缩文件中的顺序变化也会导致bsdiff生成不同的差分补丁。

4. 压缩算法本身就利用了字典及顺序来提高压缩率。这与bsdiff的工作原理有一定冲突。综上,为了生成一致的bsdiff补丁,同一个数据生成压缩包时需要保证使用同一算法和稳定的顺序。否则差分结果可能会有较大变化。一般需要压缩数据再差分时,需要注意控制这两个因素,或者考虑在解压后对原始数据文件差分。

所以,考虑解压后保持相同的目录结构进行差分,即为生成的.patch文件和原工程有相同的目录.

所以需要写一个脚本,生成一个差分文件夹(目录),这个差分文件夹与原工程有相同的目录结构.

后面再根据这个差分文件夹进行升级,即为patch文件与原文件作用生成新文件,新目标和原目标相同.通过这种服务器上生成差分包,客户端上作用差分包的形式,差分包可以压缩,在客户端上解压缩,这样能更快更合理.

所以,总共需要有2个bash脚本,一个放服务器上,生成差分包.一个放客户端上,在收到差分包后进行本地升级.

内容

bsdiff和bspatch去官网上截至2023年10月27日没有下载源码的权限,所以得去别的地方找找源码.

准备工作

在windows平台上

参考

whistle713/bsdiff-win: bsdiff Windows binaries and Visual Studio 2015/2019 project. (github.com)

里面有提供能够在windows平台上允许的.exe可执行文件.

在linux平台上

参考

红橙Darren视频笔记 bsdiff bspatch 使用(Linux下)_洌冰的博客-CSDN博客

完成编译

正式工作

这里需要考虑到旧的目标和新的目标的一些特殊情况.

  1. 新目标有新增文件的情况
  2. 新目标有删除原来旧文件的情况
  3. 新目标和旧目标的目录和文件都能对上,只是有变化.
  4. 旧目标和新目标有 大小为0 bytes 文件的情况(bsdiff失效)

相信一般的升级都会遇到 1.2.3.4所有情况,

对于第4种情况,不清楚是不是bsdiff的版本问题还是linux系统的问题,我在本地的liunx没有这个问题.

bsdiff在处理 大小为 0 bytes的文件时在linux上报错

报错

bsdiff:mmap()  xxx:Invalid argument

思路:

对于第一种和第二种情况.

新目标新增: 在旧目标中生成一个相同名字的文件,不过大小为0 bytes

新目标有删除有原来旧文件的情况: 在新目标中生成一个相同名字的文件,大小依然为0bytes

这样的话,只要不出现 4 的这种问题都是能够通过bsdiff 生成相应的bspatch文件的.

生成差分文件思路

1.同步旧目标(对应新目标有文件增加时)

2.同步新目标(对应新目标删除了旧文件时)

3.递归遍历目标中的每一个文件,在另一个目标中进行查找, 可以直接通过bsdiiff 生成差分文件,

即使是两个相同的文件,也会生成patch文件,只不过bspatch 作用这个patch文件时并不会起作用,这样是非常方便了,都不需要进行判断了。这样表现为每一个文件都有对应的差分文件.(这个需要再我的代码上改一改)

而我下面并没有这么做,而是根据md5的值判断文件不同后再生成对应的patch文件.

作用差分文件思路

直接遍历生成的差分文件目录结构,调用bspatch.

在保持相同目录结构进行差分增量升级

服务端(生成差分文件)

调用.

 ./gen.sh(脚本名) ./old(旧目录) ./new (新目录)

最终会生成一个以日期后缀的差分文件的目录(和原目录保持相同的目录结构) 

#!/bin/bash

# check if two arguments are given
if [ $# -ne 2 ]; then
  echo "Usage: $0 oldfolder newfolder"
  exit 1
fi

# check if the arguments are valid directories
if [ ! -d "$1" ] || [ ! -d "$2" ]; then
  echo "Invalid directories"
  exit 2
fi

# if it is ended by '/' delete '/'
inputemp=$1
if test "${inputemp: -1}" == "/";then
    s1=${inputemp%/}
else 
    s1=$inputemp
fi

inputemp=$2
if test "${inputemp: -1}" == "/";then
    s2=${inputemp%/}
else 
    s2=$inputemp
fi

 


# create a new directory for patch files
patch_dir="patch_$(date +%Y%m%d%H%M%S)"
mkdir -p "$patch_dir"

# sync in new target
find "$s1" -type f | while read oldfile; do
  # get the relative path of the file
  rel_path=${oldfile#$s1/}
  # get the corresponding file in the second directory
  newfile="$s2/$rel_path"
  # exist in old and not exist in new and create same name to instead in the new folder 
  if [ ! -f "$newfile" ]; then
    echo -e "\033[0;36m [disapper in new]: $newfile Generate 0 Bytes to instead in new target \033[0m"
     mkdir -p "$(dirname $newfile)"
     > $newfile  
  fi

done


# sync in old target
find "$s2" -type f | while read newfile; do
  # get the relative path of the file
  rel_path=${newfile#$s2/}
  # get the corresponding file in the second directory
  oldfile="$s1/$rel_path"
  # exist in new and not exist in old and create same name to instead in the old folder 
  if [ ! -f "$oldfile" ]; then
  echo -e "\033[0;36m [disapper in old]: $oldfile Generate 0 Bytes to instead in old target  \033[0m"
  #  create the parent directory if needed
    mkdir -p "$(dirname oldfile)"
    > $oldfile  
  fi

done


# Generate patch 
find "$s1" -type f | while read oldfile; do

  # get the relative path of the file
  rel_path=${oldfile#$s1/}
  # get the corresponding file in the second directory
  newfile="$s2/$rel_path"
  # Haved sync and create the patch file name
  patch_file="$patch_dir/$rel_path.patch"
  # create the parent directory if needed
  mkdir -p "$(dirname "$patch_file")"
  # use bsdiff to generate the patch file
  oldmd5=$(md5sum $oldfile | awk '{print $1}')
  newmd5=$(md5sum $newfile | awk '{print $1}')

  if [ "$oldmd5" = "$newmd5" ]; then
      
  echo -e "\033[0;32m Don't Need to Change \033[0m"

  else
      bsdiff "$oldfile" "$newfile" "$patch_file"
      echo -e "\033[0;33mGenerated patch for $rel_path \033[0m"
  fi
   

done

echo "Done. Patch files are in $patch_dir"

客户端(作用差分文件)

 调用

脚本名 旧目标 新目标(也可以是旧目标 ,相当与替换旧目标) 差分目录
#!/bin/bash


# check if two arguments are given
if [ $# -ne 3 ]; then
  echo "Usage: cmd  oldfolder newfolder patchfolders"
  exit 1
fi

# new generate 
if [ ! -e "$2" ]; then
mkdir $2
fi

# check if the arguments are valid directories
if [ ! -d "$1" ] || [ ! -d "$3" ] ; then
  echo "Invalid directories"
  exit 2
fi

# if it is ended by '/' delete '/'
inputemp=$1
if test "${inputemp: -1}" == "/";then
    s1=${inputemp%/}
else 
    s1=$inputemp
fi

inputemp=$2
if test "${inputemp: -1}" == "/";then
    s2=${inputemp%/}
else 
    s2=$inputemp
fi


inputemp=$3
if test "${inputemp: -1}" == "/";then
    s3=${inputemp%/}
else 
    s3=$inputemp
fi






#loop item in path_item
find "$s3" -type f -name "*.patch" | while read patch_item; do
    temp=${patch_item#$s3/}
    temp=${temp%.patch}  #equal to   temp=${temp:0:${#temp}-6}
    oldfile="$s1/$temp"
    newfile="$s2/$temp"
    mkdir -p "$(dirname "$newfile")"
    echo -e "\033[0;32m Generate $oldfile $newfile \033[0m"
    
# execute bspatch
    bspatch "$oldfile" "$newfile" "$patch_item"
  

 

done

如何使用?

一般调用过程

diff -rq ./old ./new(此时会看到文件差异)

./gen ./old ./new

./upgrate ./old ./old ./patch_xxx 

diff -rq ./old ./new (没有输出表示更新升级完毕)

12月6日更新(修复bug)

出现bug,找不到软链接文件,差分升级无法作用在存在的软连接文件.

于是更新.

  •  解决找不到软连接文件的问题,现在能找到所有文件了(包括软链接和隐藏文件)
  •  删除同步过程中的空目录和空文件(0字节文件)

 

gen.sh 

#!/bin/bash


#record waiting_for_delete_index
wddi="waiting_for_dindex"
GetAllfile()
{
        local ret=()
        local links=$(find "$1" -type l)
        local normal_files=$(find "$1" -type f)
        while read -r item
        do
            ret+=($item)
        done <<< "$links"
        while read -r item
        do
            ret+=($item)
        done <<< "$normal_files"
        echo ${ret[@]}
}


# check if two arguments are given
if [ $# -ne 2 ]; then
  echo "Usage: $0 oldfolder newfolder"
  exit 1
fi

# check if the arguments are valid directories
if [ ! -d "$1" ] || [ ! -d "$2" ]; then
  echo "Invalid directories"
  exit 2
fi

# if it is ended by '/' delete '/'
inputemp=$1
if test "${inputemp: -1}" == "/";then
    s1=${inputemp%/}
else 
    s1=$inputemp
fi

inputemp=$2
if test "${inputemp: -1}" == "/";then
    s2=${inputemp%/}
else 
    s2=$inputemp
fi

 


# create a new directory for patch files
patch_dir="$(dirname $s1)"/patch_"$(echo  "$s1" | awk -F'/' '{print $NF}')"
echo  patch_path: $patch_dir 
mkdir -p "$patch_dir"

wddi="$(dirname $s1)"/"$wddi"

if [ ! -f $wddi ];then
  touch "$wddi"
else 
  > $wddi 
fi

# sync in new target
all_files=`GetAllfile $s1`
all_files=$(echo  -n "$all_files" | tr ' ' '\n')
echo "$all_files" | while read oldfile; do
  # get the relative path of the file
  rel_path=${oldfile#$s1/}
  # get the corresponding file in the second directory
  newfile="$s2/$rel_path"
  # exist in old and not exist in new and create same name to instead in the new folder 
  if [ ! -f "$newfile" ]; then
    echo -e "\033[0;36m [disapper in new]: $newfile Generate 0 Bytes to instead in new target \033[0m"

  #appends not exist path recorded list
    if [ ! -d "$(dirname $newfile)" ];then
         mkdir -p "$(dirname $newfile)"
         echo "$(dirname $rel_path)" >> $wddi    
    fi
  #write 0 bytes
     > $newfile  
  fi

done


# sync in old target
all_files=`GetAllfile $s2`
all_files=$(echo  -n "$all_files" | tr ' ' '\n')
echo "$all_files" | while read newfile; do
  # get the relative path of the file
  rel_path=${newfile#$s2/}
  # get the corresponding file in the second directory
  oldfile="$s1/$rel_path"
  # exist in new and not exist in old and create same name to instead in the old folder 
  if [ ! -f "$oldfile" ]; then
  echo -e "\033[0;36m [disapper in old]: $oldfile Generate 0 Bytes to instead in old target  \033[0m"
  #  create the parent directory if needed
    mkdir -p "$(dirname $oldfile)"
    > $oldfile  
  fi

done


# Generate patch 
# when running  in this poision ,old target is same as new target
all_files=`GetAllfile $s1`
all_files=$(echo  -n $all_files | tr ' ' '\n')
echo "$all_files"  | while read oldfile; do
  # get the relative path of the file
  rel_path=${oldfile#$s1/}
  # get the corresponding file in the second directory
  newfile="$s2/$rel_path"
  # Haved sync and create the patch file name
  patch_file="$patch_dir/$rel_path.patch"
  # create the parent directory if needed
  mkdir -p "$(dirname "$patch_file")"
  # use bsdiff to generate the patch file
  oldmd5=$(md5sum $oldfile | awk '{print $1}')
  newmd5=$(md5sum $newfile | awk '{print $1}')

  if [ "$oldmd5" = "$newmd5" ]; then
      
  echo -e "\033[0;36m Don't Need to Change \033[0m"

  else
      bsdiff "$oldfile" "$newfile" "$patch_file"
      echo -e "\033[0;36mGenerated patch for $rel_path \033[0m"
  fi
done


# after generating patchs_files ,delete extra 0 bytes files by delete index
if [ ! -f $wddi ];
then 
    exit 1
fi
cat $wddi | while read del_item;
do
    realpath=$s2/$del_item
    if [  -d "$realpath" ] ;then
        echo $realpath
        rm -rf $realpath
    fi
done

echo "Done. Patch files are in $patch_dir"

upgrate.sh

#!/bin/bash

# wdfi="waiting_for_delete"
wddi="waiting_for_dindex"
# check if two arguments are given
if [ $# -ne 2 ]; then
  echo "Usage: upgrade  oldfolder  patch_path "
  echo "example ./upgrade   ./test/old/ ./test/boot/"
  exit 1
fi



# check if the arguments are valid directories
if [ ! -d "$1" -o  ! -d "$2" ] ; then
  echo "Invalid directories"
  exit 2
fi

# if it is ended by '/' delete '/' => old folder 
inputemp=$1
if test "${inputemp: -1}" == "/";then
    s1=${inputemp%/}
else 
    s1=$inputemp
fi

# if it is ended by '/' delete '/'=> patch_path
inputemp=$2
if test "${inputemp: -1}" == "/";then
    patch_path=${inputemp%/}
else 
    patch_path=$inputemp
fi 

wddi="$patch_path/$wddi"
echo    wddi : "$wddi"

patch_path="$patch_path"/patch_"$(echo  "$s1" | awk -F'/' '{print $NF}')"
echo find patch_path:  $patch_path

 

#loop item in path_item
find "$patch_path" -type f -name "*.patch" | while read patch_item; do
    temp=${patch_item#$patch_path/}
    temp=${temp%.patch}  #equal to   temp=${temp:0:${#temp}-6}
    oldfile="$s1/$temp"
    newfile="$s1/$temp"
    mkdir -p "$(dirname "$newfile")"
    if [ ! -f "$newfile" ];
    then
    > $newfile
    fi
    echo -e "\033[0;36m Generate $oldfile $newfile \033[0m"
    
# execute bspatch
    bspatch "$oldfile" "$newfile" "$patch_item"
done


#delete not exist index when  processing  to delete 
if [ ! -f $wddi ];
then 
    exit 1
fi
cat $wddi | while read del_item;
do
    realpath=$s1/$del_item
    if [  -d "$realpath" ] ;then
        echo $realpath
        rm -rf $realpath
    fi
done
echo "done"

执行分析

这样在服务端上执行

./gen  ./test/old  ./test/new (old是旧版本,new为新版本),则在test下生成patch_old,和waiting_for_dindex.

./upgrade  旧文件位置  pathch文件父路径(不包括pathch文件名)

注意事项和关注的地方:
  1. 在生成patch文件的时候,也就是 ./gen 脚本 ,先让旧目录和新目录进行了同步,最终根据每一个文件生成patch文件,并把这些patch文件单独放在pathch_旧目录名 这个目录下,这个目录和旧目录(新目录有相同的目录结构.
  2. 在./gen脚本中,因为做了同步,如果存在新版本中删除旧目录,则会将被删除的旧目录同步到新目录中,并把被删除的旧目录存放在一个清单列表中(如wddi),最终在打完patch后会在服务器端删除这些同步的内容.
  3. ./upgrade脚本 接受传入一个旧目录,和一个patch目录的父路径(不包括patch目录名),这样会在patch的父目录中寻找以旧目录后缀结尾的patch目录,然后遍历这个patch目录依次调用bspatch升级, 最终在客户端调用升级完成后会根据清单去删除同步过程中产生旧目录,0字节的文件.

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
差分升级算法是用于在软件版本升级时,通过比较旧版本和新版本的差异来减少升级包的大小,从而减少下载和安装的时间和带宽消耗。bsdff和hdiff是两种常用的差分升级算法。 bsdff算法是一种基于二进制的差分算法,它通过将新旧版本进行二进制比较,找出它们之间的差异,并生成一个差分文件。这个差分文件中只包含了需要修改的部分内容,因此相对于整个新版本的升级包,它的大小更小。在升级时,只需将差分文件与旧版本文件进行合并,就可以得到完整的新版本文件。bsdff算法在处理二进制文件时非常高效,因此在很多软件升级中被广泛使用。 hdiff算法是一种基于压缩的差分算法,它采用了压缩和哈希技术来减少差异数据的大小和提高匹配效率。与bsdff算法不同,hdiff算法生成的差分文件不仅包含了需要修改的内容,还包含了一些辅助信息,比如哈希表。这样在升级时,需要将差分文件与旧版本文件和辅助信息一起使用,才能得到完整的新版本文件。hdiff算法在处理文本文件时效果较好,特别是对于较大的文件或者稍有复杂度的文件。 综上所述,bsdff算法和hdiff算法都可以实现差分升级的功能,但它们在处理文件类型和效率上有所差异。在选择使用哪种算法时,需要考虑到具体的应用场景和需求,综合评估使用二进制比较还是压缩和哈希技术更适合。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值