本博客倡导开放源代码,在此公布之程序源代码如无特别声明均采用GNU通用公共 许可证(GPL)

乐在其中

分享学习Linux的乐趣

  IT博客 :: 首页 :: 新随笔 :: 联系 :: 聚合  :: 管理 ::
  23 随笔 :: 0 文章 :: 401 评论 :: 0 Trackbacks
开放源码的好处是人人都能修改源码,无论是修正错误还是增加新功能。基于开源项目的修改通常是以补丁(patch)的形式发布。其实补丁就是diff命令的输出。diff命令对两个文本文件进行比较,然后输出它们的差别。diff的输出有多种可选的格式,如context, ed, normal以及unified,这几种格式都可以用作补丁。下面我们来看一些实例。

比如我们有下面这段简单的程序hello.c:
#include<stdio.h>
int main(int argc, char* argv[]) {
        printf(
"Hello, world!");
}

可是我们发现这个程序有个小小的问题:打印Hello, world!后没有换行。我们希望把它修改成下面的样子:
#include<stdio.h>
int main(int argc, char* argv[]) {
        printf(
"Hello, world!\n");
}

如果我们为这个修改制作一个补丁,可以按下面的步骤进行:
1. 备份未修改的程序,将hello.c复制到hello.c.orig。为了保留文件的时间戳,用了-p参数。
$ cp -p hello.c hello.c.orig

2. 修改hello.c,给printf加上换行符“\n”。

3. 用diff命令生成补丁。由于diff命令输出到标准输出设备,我们将其重定向到hello.patch。参数-u用于指定unified输出格式。
$ diff -u hello.c.orig hello.c > hello.patch
请注意:在diff命令行上旧文件在前,新文件在后。

补丁文件hello.patch内容如下:
--- hello.c.orig        2010-11-23 10:12:09.725254213 +0800
+++ hello.c     2010-11-23 10:13:07.453254213 +0800
@@ -1,4 +1,4 @@
 #include<stdio.h>
 
int main(int argc, char* argv[]) {
-       printf("Hello, world!");
+       printf("Hello, world!\n"
);
 }

这就是只包含单个文件的最简单的补丁。如果我们修改个多个文件,怎样才能把这些修改放在一个补丁文件里呢?其实我们可以用diff比较两个目录,请看下面的例子。

在old目录下有如下的hello.c程序:
old/hello.c
#include<stdio.h>
int main(int argc, char* argv[]) {
        printf(
"Hello, world!\n");
}

我们希望把"Hello, world!\n"字符串定义为宏放到头文件里。在new目录下有修改后的hello.c以及新头文件hello.h。
new/hello.c
#include<stdio.h>
#include 
"hello.h"
int main(int argc, char* argv[]) {
        printf(
"%s\n", GREETING);
}

new/hello.h
#ifndef _HELLO_H
#define GREETING "Hello, world!"
#endif

用diff比较两个目录时通常会使用-r参数,它告诉diff不仅要比较指定目录下的文件,还要比较其子目录,也就是要比较整个目录树。另外我们还会用-N参数,这个参数在增加或删除文件时特别有用,它使得diff把不存在的文件当作空文件处理。diff在比较目录时会把两个指定目录下相同路径及文件名的文件逐一进行比较。
$ diff -ruN old new > hello.patch2

hello.patch2:
diff -ruN old/hello.c new/hello.c
--- old/hello.c 2010-11-23 13:46:06.217254213 +0800
+++ new/hello.c 2010-11-23 13:48:05.557254212 +0800
@@ 
-1,4 +1,5 @@
 #include
<stdio.h>
+#include "hello.h"
 
int main(int argc, char* argv[]) {
-       printf("Hello, world!\n");
+       printf("%s\n", GREETING);
 }
diff 
-ruN old/hello.h new/hello.h
--- old/hello.h 1970-01-01 08:00:00.000000000 +0800
+++ new/hello.h 2010-11-23 13:48:05.557254212 +0800
@@ 
-0,0 +1,3 @@
+#ifndef _HELLO_H
+#define GREETING "Hello, world!"
+#endif

注意看这个补丁中old/hello.h的时间戳:1970-01-01 08:00:00.000000000 +0800。这表明这个文件其实不存在。

下面讲讲如何打补丁。以hello.patch2为例,比如在old目录下打这个补丁:
方法一
$ cd old
$ patch 
-p1 < ../hello.patch2
patching file hello.c
patching file hello.h
$
或者
方法二
$ cd old
$ cat ..
/hello.patch2 | patch -p1
patching file hello.c
patching file hello.h
$

patch命令从标准输入设备上读入补丁,因此我们可以用输入重定向(方法一)或者管道(方法二)来为patch输入补丁。用管道的好处是我们可以方便的处理压缩的补丁,如下例所示:
$ bzcat /path/to/patch.bz2 | patch -p1

参数-p1的目的是使patch在匹配文件时去掉最前面的一层的目录。在上例中就是把路径old/hello.c变为hello.c。用这个参数通常是由于制作补丁时的顶层目录名与我们要打补丁的目标目录名不同。

在制作补丁的实际操作中我们往往会面临一个困难:通常在修改一个软件包时为了便于制作补丁,我们会把原始的软件包复制到一个新的目录下进行修改,然后用diff比较原始目录和新目录。但是我们通常会在新目录下进行编译/调试,这会产生不少额外的文件,而diff会把这些文件当作新文件加入补丁里。

为了避免这个问题,我用这样方法解决:直接在原始目录下进行修改、编译及调试,只是保证做到如下两点。
  1. 在修改任何文件之前先做一个备份:cp -p <filename> <filename>.orig
  2. 在增加任何新文件之前touch一个空文件:touch <filename>.orig
其中请用实际的文件名替换<filename>和<newfile>。比如要修改的文件是hello.c,则备份文件名是hello.c.orig。

在制作补丁是用下面的shell脚本。这个脚本会先找出所有以.orig结尾的文件,接着创建两个临时目录,一个放原始文件,一个放修改后的文件,然后用diff生成补丁。这个脚本应该在源码目录之外执行,临时目录和补丁文件会放在当前目录下。

mkpatch:
#!/bin/sh
if [ -"$1" ] ; then
        echo 
"USAGE: mkpatch <source dir>"
        exit 
1
fi
SRC_PATH
="$1"
if [ ! -d  "${SRC_PATH}" ] ; then
        echo 
"FATAL: source directory '${SRC_PATH}' does not exist"
        exit 
1
fi
SRC_DIR
=`basename "${SRC_PATH}"`
WORK_DIR
=`pwd`
OLD_DIR
="${WORK_DIR}/${SRC_DIR}.old"
NEW_DIR
="${WORK_DIR}/${SRC_DIR}.new"
cd 
"${SRC_PATH}"
for f in `find . -name '*.orig' --type f` ;
do
        dir
=`dirname $f`;
        file
=`basename $f .orig`;
        odir
="${OLD_DIR}/$dir"
        ndir
="${NEW_DIR}/$dir"
        mkdir 
-"$odir";
        mkdir 
-"$ndir";
        osize
=`stat -"%s" "$f"`;
        
if [ "x$osize" = "x0" ] ; then
                echo 
"New file:  $dir/$file"
        
else
                echo 
"Modified file: $dir/$file"
                cp 
-"$dir/$file.orig" "$odir/$file";
        fi
        cp 
-"$dir/$file" "$ndir";
done

cd 
"$WORK_DIR"
echo 
-"Generating patch"
diff 
-ruN "${SRC_DIR}.old" "${SRC_DIR}.new" > "${SRC_DIR}.patch"
echo 
"done"


posted on 2010-11-25 10:35 gouzhuang 阅读(1457) 评论(8)  编辑 收藏 引用

评论

# re: 补丁的制作与应用 2010-11-25 21:40 z1022
@gouzhuang

以经看了你的文章。我大概了解patch 和 diff 的用法。但是有些地方还不明白。

如制作busybox 1.15.3 e2fsorigs补丁。 如何找寻旧文件做对比。没有旧文件,如何用diff command 。请简单描述一下给我学习学习。
  回复  更多评论
  

# re: 补丁的制作与应用 2010-11-26 09:08 gouzhuang
@z1022
旧文件在busybox-1.15.3/e2fsgrogs/old_e2fsprogs下面。我在做补丁时先用old_e2fsprogs替换e2fsprogs,操作如下:
cd busybox-1.15.3
mv e2fsprogs t
mv t/old_e2fsprogs e2fsprogs
rm -rf t

在打补丁前同样也需要这样做。  回复  更多评论
  

# re: 补丁的制作与应用 2010-11-26 11:50 z1022
@gouzhuang

但是如何用diff command 做 patch file?  回复  更多评论
  

# re: 补丁的制作与应用 2010-11-26 11:51 z1022
@gouzhuang

但是如何用diff command 做 e2fsprogs patch file?   回复  更多评论
  

# re: 补丁的制作与应用 2010-11-26 13:29 gouzhuang
@z1022
不太理解你的问题。请说详细一点。  回复  更多评论
  

# re: 补丁的制作与应用 2010-11-26 19:04 z1022
@gouzhuang

$ cd busybox-1.15.3/
$ mv e2fsprogs t
$ mv t/old_e2fsprogs e2fsprogs
$ rm -rf t

步骤之后
如何用command 如你一样制作以下

busybox-1.15.3-old_e2fsprogs.patch  回复  更多评论
  

# re: 补丁的制作与应用 2010-11-30 09:23 gouzhuang
@z1022
执行完上述步骤,只是准备好了原始源代码。然后根据需要来修改源码,编译调试通过后就可按照本文提供的方法制作补丁了。

如果你的问题是:如何修改源代码?这需要先理解源代码,然后才能修正其错误或增加新功能。至于这个e2fsprogs的补丁,我也是在网上找到的,出处是 http://blog.sina.com.cn/s/blog_593507fd0100gk8s.html 。在只是做了点小小的修改,使之能在我的编译环境下成功编译。  回复  更多评论
  

# re: 补丁的制作与应用 2010-12-21 16:34 bgs90@126.com
请问一下1073下面modprobe sata_mars,modprobe fuse,是什么意思?  回复  更多评论
  

只有注册用户登录后才能发表评论。