• 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 (新目录)

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

    1. #!/bin/bash
    2. # check if two arguments are given
    3. if [ $# -ne 2 ]; then
    4. echo "Usage: $0 oldfolder newfolder"
    5. exit 1
    6. fi
    7. # check if the arguments are valid directories
    8. if [ ! -d "$1" ] || [ ! -d "$2" ]; then
    9. echo "Invalid directories"
    10. exit 2
    11. fi
    12. # if it is ended by '/' delete '/'
    13. inputemp=$1
    14. if test "${inputemp: -1}" == "/";then
    15. s1=${inputemp%/}
    16. else
    17. s1=$inputemp
    18. fi
    19. inputemp=$2
    20. if test "${inputemp: -1}" == "/";then
    21. s2=${inputemp%/}
    22. else
    23. s2=$inputemp
    24. fi
    25. # create a new directory for patch files
    26. patch_dir="patch_$(date +%Y%m%d%H%M%S)"
    27. mkdir -p "$patch_dir"
    28. # sync in new target
    29. find "$s1" -type f | while read oldfile; do
    30. # get the relative path of the file
    31. rel_path=${oldfile#$s1/}
    32. # get the corresponding file in the second directory
    33. newfile="$s2/$rel_path"
    34. # exist in old and not exist in new and create same name to instead in the new folder
    35. if [ ! -f "$newfile" ]; then
    36. echo -e "\033[0;36m [disapper in new]: $newfile Generate 0 Bytes to instead in new target \033[0m"
    37. mkdir -p "$(dirname $newfile)"
    38. > $newfile
    39. fi
    40. done
    41. # sync in old target
    42. find "$s2" -type f | while read newfile; do
    43. # get the relative path of the file
    44. rel_path=${newfile#$s2/}
    45. # get the corresponding file in the second directory
    46. oldfile="$s1/$rel_path"
    47. # exist in new and not exist in old and create same name to instead in the old folder
    48. if [ ! -f "$oldfile" ]; then
    49. echo -e "\033[0;36m [disapper in old]: $oldfile Generate 0 Bytes to instead in old target \033[0m"
    50. # create the parent directory if needed
    51. mkdir -p "$(dirname oldfile)"
    52. > $oldfile
    53. fi
    54. done
    55. # Generate patch
    56. find "$s1" -type f | while read oldfile; do
    57. # get the relative path of the file
    58. rel_path=${oldfile#$s1/}
    59. # get the corresponding file in the second directory
    60. newfile="$s2/$rel_path"
    61. # Haved sync and create the patch file name
    62. patch_file="$patch_dir/$rel_path.patch"
    63. # create the parent directory if needed
    64. mkdir -p "$(dirname "$patch_file")"
    65. # use bsdiff to generate the patch file
    66. oldmd5=$(md5sum $oldfile | awk '{print $1}')
    67. newmd5=$(md5sum $newfile | awk '{print $1}')
    68. if [ "$oldmd5" = "$newmd5" ]; then
    69. echo -e "\033[0;32m Don't Need to Change \033[0m"
    70. else
    71. bsdiff "$oldfile" "$newfile" "$patch_file"
    72. echo -e "\033[0;33mGenerated patch for $rel_path \033[0m"
    73. fi
    74. done
    75. echo "Done. Patch files are in $patch_dir"

    客户端(作用差分文件)

     调用

    脚本名 旧目标 新目标(也可以是旧目标 ,相当与替换旧目标) 差分目录
    1. #!/bin/bash
    2. # check if two arguments are given
    3. if [ $# -ne 3 ]; then
    4. echo "Usage: cmd oldfolder newfolder patchfolders"
    5. exit 1
    6. fi
    7. # new generate
    8. if [ ! -e "$2" ]; then
    9. mkdir $2
    10. fi
    11. # check if the arguments are valid directories
    12. if [ ! -d "$1" ] || [ ! -d "$3" ] ; then
    13. echo "Invalid directories"
    14. exit 2
    15. fi
    16. # if it is ended by '/' delete '/'
    17. inputemp=$1
    18. if test "${inputemp: -1}" == "/";then
    19. s1=${inputemp%/}
    20. else
    21. s1=$inputemp
    22. fi
    23. inputemp=$2
    24. if test "${inputemp: -1}" == "/";then
    25. s2=${inputemp%/}
    26. else
    27. s2=$inputemp
    28. fi
    29. inputemp=$3
    30. if test "${inputemp: -1}" == "/";then
    31. s3=${inputemp%/}
    32. else
    33. s3=$inputemp
    34. fi
    35. #loop item in path_item
    36. find "$s3" -type f -name "*.patch" | while read patch_item; do
    37. temp=${patch_item#$s3/}
    38. temp=${temp%.patch} #equal to temp=${temp:0:${#temp}-6}
    39. oldfile="$s1/$temp"
    40. newfile="$s2/$temp"
    41. mkdir -p "$(dirname "$newfile")"
    42. echo -e "\033[0;32m Generate $oldfile $newfile \033[0m"
    43. # execute bspatch
    44. bspatch "$oldfile" "$newfile" "$patch_item"
    45. done

    如何使用?

    一般调用过程

    1. diff -rq ./old ./new(此时会看到文件差异)
    2. ./gen ./old ./new
    3. ./upgrate ./old ./old ./patch_xxx 
    4. diff -rq ./old ./new (没有输出表示更新升级完毕)

    12月6日更新(修复bug)

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

    于是更新.

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

     

    gen.sh 

    1. #!/bin/bash
    2. #record waiting_for_delete_index
    3. wddi="waiting_for_dindex"
    4. GetAllfile()
    5. {
    6. local ret=()
    7. local links=$(find "$1" -type l)
    8. local normal_files=$(find "$1" -type f)
    9. while read -r item
    10. do
    11. ret+=($item)
    12. done <<< "$links"
    13. while read -r item
    14. do
    15. ret+=($item)
    16. done <<< "$normal_files"
    17. echo ${ret[@]}
    18. }
    19. # check if two arguments are given
    20. if [ $# -ne 2 ]; then
    21. echo "Usage: $0 oldfolder newfolder"
    22. exit 1
    23. fi
    24. # check if the arguments are valid directories
    25. if [ ! -d "$1" ] || [ ! -d "$2" ]; then
    26. echo "Invalid directories"
    27. exit 2
    28. fi
    29. # if it is ended by '/' delete '/'
    30. inputemp=$1
    31. if test "${inputemp: -1}" == "/";then
    32. s1=${inputemp%/}
    33. else
    34. s1=$inputemp
    35. fi
    36. inputemp=$2
    37. if test "${inputemp: -1}" == "/";then
    38. s2=${inputemp%/}
    39. else
    40. s2=$inputemp
    41. fi
    42. # create a new directory for patch files
    43. patch_dir="$(dirname $s1)"/patch_"$(echo "$s1" | awk -F'/' '{print $NF}')"
    44. echo patch_path: $patch_dir
    45. mkdir -p "$patch_dir"
    46. wddi="$(dirname $s1)"/"$wddi"
    47. if [ ! -f $wddi ];then
    48. touch "$wddi"
    49. else
    50. > $wddi
    51. fi
    52. # sync in new target
    53. all_files=`GetAllfile $s1`
    54. all_files=$(echo -n "$all_files" | tr ' ' '\n')
    55. echo "$all_files" | while read oldfile; do
    56. # get the relative path of the file
    57. rel_path=${oldfile#$s1/}
    58. # get the corresponding file in the second directory
    59. newfile="$s2/$rel_path"
    60. # exist in old and not exist in new and create same name to instead in the new folder
    61. if [ ! -f "$newfile" ]; then
    62. echo -e "\033[0;36m [disapper in new]: $newfile Generate 0 Bytes to instead in new target \033[0m"
    63. #appends not exist path recorded list
    64. if [ ! -d "$(dirname $newfile)" ];then
    65. mkdir -p "$(dirname $newfile)"
    66. echo "$(dirname $rel_path)" >> $wddi
    67. fi
    68. #write 0 bytes
    69. > $newfile
    70. fi
    71. done
    72. # sync in old target
    73. all_files=`GetAllfile $s2`
    74. all_files=$(echo -n "$all_files" | tr ' ' '\n')
    75. echo "$all_files" | while read newfile; do
    76. # get the relative path of the file
    77. rel_path=${newfile#$s2/}
    78. # get the corresponding file in the second directory
    79. oldfile="$s1/$rel_path"
    80. # exist in new and not exist in old and create same name to instead in the old folder
    81. if [ ! -f "$oldfile" ]; then
    82. echo -e "\033[0;36m [disapper in old]: $oldfile Generate 0 Bytes to instead in old target \033[0m"
    83. # create the parent directory if needed
    84. mkdir -p "$(dirname $oldfile)"
    85. > $oldfile
    86. fi
    87. done
    88. # Generate patch
    89. # when running in this poision ,old target is same as new target
    90. all_files=`GetAllfile $s1`
    91. all_files=$(echo -n $all_files | tr ' ' '\n')
    92. echo "$all_files" | while read oldfile; do
    93. # get the relative path of the file
    94. rel_path=${oldfile#$s1/}
    95. # get the corresponding file in the second directory
    96. newfile="$s2/$rel_path"
    97. # Haved sync and create the patch file name
    98. patch_file="$patch_dir/$rel_path.patch"
    99. # create the parent directory if needed
    100. mkdir -p "$(dirname "$patch_file")"
    101. # use bsdiff to generate the patch file
    102. oldmd5=$(md5sum $oldfile | awk '{print $1}')
    103. newmd5=$(md5sum $newfile | awk '{print $1}')
    104. if [ "$oldmd5" = "$newmd5" ]; then
    105. echo -e "\033[0;36m Don't Need to Change \033[0m"
    106. else
    107. bsdiff "$oldfile" "$newfile" "$patch_file"
    108. echo -e "\033[0;36mGenerated patch for $rel_path \033[0m"
    109. fi
    110. done
    111. # after generating patchs_files ,delete extra 0 bytes files by delete index
    112. if [ ! -f $wddi ];
    113. then
    114. exit 1
    115. fi
    116. cat $wddi | while read del_item;
    117. do
    118. realpath=$s2/$del_item
    119. if [ -d "$realpath" ] ;then
    120. echo $realpath
    121. rm -rf $realpath
    122. fi
    123. done
    124. echo "Done. Patch files are in $patch_dir"

    upgrate.sh

    1. #!/bin/bash
    2. # wdfi="waiting_for_delete"
    3. wddi="waiting_for_dindex"
    4. # check if two arguments are given
    5. if [ $# -ne 2 ]; then
    6. echo "Usage: upgrade oldfolder patch_path "
    7. echo "example ./upgrade ./test/old/ ./test/boot/"
    8. exit 1
    9. fi
    10. # check if the arguments are valid directories
    11. if [ ! -d "$1" -o ! -d "$2" ] ; then
    12. echo "Invalid directories"
    13. exit 2
    14. fi
    15. # if it is ended by '/' delete '/' => old folder
    16. inputemp=$1
    17. if test "${inputemp: -1}" == "/";then
    18. s1=${inputemp%/}
    19. else
    20. s1=$inputemp
    21. fi
    22. # if it is ended by '/' delete '/'=> patch_path
    23. inputemp=$2
    24. if test "${inputemp: -1}" == "/";then
    25. patch_path=${inputemp%/}
    26. else
    27. patch_path=$inputemp
    28. fi
    29. wddi="$patch_path/$wddi"
    30. echo wddi : "$wddi"
    31. patch_path="$patch_path"/patch_"$(echo "$s1" | awk -F'/' '{print $NF}')"
    32. echo find patch_path: $patch_path
    33. #loop item in path_item
    34. find "$patch_path" -type f -name "*.patch" | while read patch_item; do
    35. temp=${patch_item#$patch_path/}
    36. temp=${temp%.patch} #equal to temp=${temp:0:${#temp}-6}
    37. oldfile="$s1/$temp"
    38. newfile="$s1/$temp"
    39. mkdir -p "$(dirname "$newfile")"
    40. if [ ! -f "$newfile" ];
    41. then
    42. > $newfile
    43. fi
    44. echo -e "\033[0;36m Generate $oldfile $newfile \033[0m"
    45. # execute bspatch
    46. bspatch "$oldfile" "$newfile" "$patch_item"
    47. done
    48. #delete not exist index when processing to delete
    49. if [ ! -f $wddi ];
    50. then
    51. exit 1
    52. fi
    53. cat $wddi | while read del_item;
    54. do
    55. realpath=$s1/$del_item
    56. if [ -d "$realpath" ] ;then
    57. echo $realpath
    58. rm -rf $realpath
    59. fi
    60. done
    61. 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字节的文件.

  • 相关阅读:
    中倍健未来数智药房首获红杉基金1千万美元天使轮融资
    Github 2024-05-30开源项目日报Top10
    分析SSH登录日志
    RocketMQ如何保证消息被有序消费
    【C++】模板初阶
    单一职责原则
    OneNote 深度评测:使用资源、插件、模版
    workflow一次完成多个模型评价和比较
    用c++写一个高精度计算的乘法运算
    Python基础学习笔记1(AI Studio)
  • 原文地址:https://blog.csdn.net/PHILICS7/article/details/134077783