最近我提交了一分损坏的文件到资源库上,被问到数据是否可恢复。根据提出的描述开始着手调查和修复问题,我认为其他人也会对解决的过程感兴趣,或者遇到相同状况后可以帮助到你。
我开启了检查(fsck),发现问题出现在一个类上(我过去经常用 $pack 和$obj 命令使得输出可读,而且因为我也更倾向于他们):
- $ git fsck
- error: $pack SHA1 checksum mismatch
- error: index CRC mismatch for object $obj from $pack at offset 51653873
- error: inflate: data stream error (incorrect data check)
- error: cannot unpack $obj from $pack at offset 51653873
错误的信息意味着一个字节在某一个地方混乱了,推测是类里面描述过的(因为检验和的索引和zlib都失败了)
通过阅读zlib源代码,我发现“检查不正确数据”意味着adler-32
算法检验zlib数据末端没有匹配已经增加的数据。因此通过zlib压缩数据是没有用的,因为到每次末尾都会有错误,此时我们了也解到这个crc文件不能匹配。这个出错的字节存在类文件的任何地方。
第一件事情我从packfile中pull出损坏的文件。我需要知道到底它多大的类,然后我发现了下面的信息:
- $ git show-index <$idx | cut -d' ' -f1 | sort -n | grep -A1 51653873
- 51653873
- 51664736
Show-index命令可以列出类和他们的偏移量。我们除了偏移量以外通通抛弃,然后排列,这样我们感兴趣的偏移量(这个位置是从上面fsck命令得出来的)是遵循下一个对象的偏移量。现在我们知道类文件长度是10863字节,然后我们可以抓取它:
- dd if=$pack of=object bs=1 skip=51653873 count=10863
我检查了文件的十六进制,搜索任何明显的错误(例如,一个4K大小运行中的0是文件系统冲突很好的信号)。但是所有的地方看起来都很合理。
注意到“object”文件不适合被zlib直接压缩;它本身包含git类信息头,而且是可变长度的。我们想要去掉它,所以开始直接使用zlib操 作数据。你可以用你手工的方式(格式化信息的描述在Documentation/technical/pack-format.txt里面),或者你也可 以用debugger搞定它。我选择了后者,创建了一个验证的包如下:# pack magic and version
- # pack magic and version
- printf 'PACK\0\0\0\2' >tmp.pack
- # pack has one object
- printf '\0\0\0\1' >>tmp.pack
- # now add our object data
- cat object >>tmp.pack
- # and then append the pack trailer
- /path/to/git.git/test-sha1 -b <tmp.pack >trailer
- cat trailer >>tmp.pack
然后在debugger中运行”git index-pack tmp.pack” 命令(会停在unpack_raw_entry)。做到这里,我发现有3个自己的信息头(信息头本身有一个完整的类型和大小)。然后我用下面语句去掉了这些内容:
- dd if=object of=zlib bs=1 skip=3
我用一个自定义的C程序过的zlib的解压锁结果。但是它报错了,我得到准确的输出数字字节(也就是说,它匹配了上面解码的git的信息头大小)。 但是”git hash-object” 命令的结果并没有相同的shal值。所以现在仍然有一些错误的字节是我不知道的。这个文件发生在C源程序代码, 我希望我能注意到一些明显的错误,但是我没有成功,我甚至都编译成功了!
我也试过和在资源库相同路径下其他版本的文件作比较,希望有不一样和不合理的部分。不幸运的是,这碰巧是唯一的修订文件,在这个资源库中。所以我没有任何东西可以作比较。
于是我采取不同的措施,猜测着冲突是由限制单个字节引起的,我写了交换每个字节的程序,是这解压缩得到结果。因为这个类文件压缩过后仅有10K,花了很多时间解压出之后的结果是2.5M。
这是我用的程序:
- #include <stdio.h>
- #include <unistd.h>
- #include <string.h>
- #include <signal.h>
- #include <zlib.h>
- static int try_zlib(unsigned char *buf, int len)
- {
- /* make this absurdly large so we don't have to loop */
- static unsigned char out[1024*1024];
- z_stream z;
- int ret;
- memset(&z, 0, sizeof(z));
- inflateInit(&z);
- z.next_in = buf;
- z.avail_in = len;
- z.next_out = out;
- z.avail_out = sizeof(out);
- ret = inflate(&z, 0);
- inflateEnd(&z);
- return ret >= 0;
- }
- /* eye candy */
- static int counter = 0;
- static void progress(int sig)
- {
- fprintf(stderr, "\r%d", counter);
- alarm(1);
- }
- int main(void)
- {
- /* oversized so we can read the whole buffer in */
- unsigned char buf[1024*1024];
- int len;
- unsigned i, j;
- signal(SIGALRM, progress);
- alarm(1);
- len = read(0, buf, sizeof(buf));
- for (i = 0; i < len; i++) {
- unsigned char c = buf[i];
- for (j = 0; j <= 0xff; j++) {
- buf[i] = j;
- counter++;
- if (try_zlib(buf, len))
- printf("i=%d, j=%x\n", i, j);
- }
- buf[i] = c;
- }
- alarm(0);
- fprintf(stderr, "\n");
- return 0;
- }
编译和运行的结果:
- gcc -Wall -Werror -O3 munge.c -o munge -lz
- ./munge <zlib
有一些错误出现(如果你在zlib的信息头中得到”no data”信息,zlib认为它运行的很好:))。但是我中途得到了下面的信息:
- i=5642, j=c7
等到运行完结束,末尾获得了更多的记录(整理crc文件匹配了我们损坏的文件)。有一个很好的机会,在中间的记录,就是源代码的问题
在一个十六进制编辑器中对字节稍微做了一些调整,zlib解压缩(毫无错误!),然后管道输出”git hash-object”,报告了损坏文件的shal值,成功了!
我修正packfile文件本身:
- chmod +w $pack
- printf '\xc7' | dd of=$pack bs=1 seek=51659518 conv=notrunc
- chmod -w $pack
发现’\xc7′来自与替换我们的“munge”程序。把原来的对象偏移(51653873)导出了偏移量51659518 。通过“munge”(5642)增加了代替部分的偏移量,然后增加了之前去掉的3字节的git信息头。
最后,”git fsck” 清理干净。
关于冲突本身来说,我很幸运它确实是一个字节。实际上,证明就是单独的位。0xc7字节发生冲突成为0xc5.所以推测由错误的硬件引起,或者物理射线。
紧接着,中止关注于解压缩的输出是错的吗?我本会一直看前面的部分这样永远不会发现它。下面是解压缩后冲突数据的不同,真正的数据文件:
- - cp = strtok (arg, "+");
- + cp = strtok (arg, ".");
调整了一个字节,最终仍然是有效的,可读的C碰巧做了完全不同的事情!一次不同尝试会造成幸运的日子,看看zlib的输出可能确实有用,正如大多数随机的改变也许就会破坏C代码。
但更重要的是,git的hash、检验和引起了在另外一个系统中,很容易不被检测问题。这个结果仍然会编译,但是可能就引起一个有趣的bug(归咎于一些随机性的提交)
原文链接:http://thread.gmane.org/gmane.comp.version-control.git/236238