國(guó)外開(kāi)發(fā)者:檢驗(yàn)并恢復(fù)損壞的git數(shù)據(jù)文件
最近我提交了一分損壞的文件到資源庫(kù)上,被問(wèn)到數(shù)據(jù)是否可恢復(fù)。根據(jù)提出的描述開(kāi)始著手調(diào)查和修復(fù)問(wèn)題,我認(rèn)為其他人也會(huì)對(duì)解決的過(guò)程感興趣,或者遇到相同狀況后可以幫助到你。
我開(kāi)啟了檢查(fsck),發(fā)現(xiàn)問(wèn)題出現(xiàn)在一個(gè)類上(我過(guò)去經(jīng)常用 $pack 和$obj 命令使得輸出可讀,而且因?yàn)槲乙哺鼉A向于他們):
- $ 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
錯(cuò)誤的信息意味著一個(gè)字節(jié)在某一個(gè)地方混亂了,推測(cè)是類里面描述過(guò)的(因?yàn)闄z驗(yàn)和的索引和zlib都失敗了)
通過(guò)閱讀zlib源代碼,我發(fā)現(xiàn)“檢查不正確數(shù)據(jù)”意味著adler-32
算法檢驗(yàn)zlib數(shù)據(jù)末端沒(méi)有匹配已經(jīng)增加的數(shù)據(jù)。因此通過(guò)zlib壓縮數(shù)據(jù)是沒(méi)有用的,因?yàn)榈矫看文┪捕紩?huì)有錯(cuò)誤,此時(shí)我們了也解到這個(gè)crc文件不能匹配。這個(gè)出錯(cuò)的字節(jié)存在類文件的任何地方。
第一件事情我從packfile中pull出損壞的文件。我需要知道到底它多大的類,然后我發(fā)現(xiàn)了下面的信息:
- $ git show-index <$idx | cut -d' ' -f1 | sort -n | grep -A1 51653873
- 51653873
- 51664736
Show-index命令可以列出類和他們的偏移量。我們除了偏移量以外通通拋棄,然后排列,這樣我們感興趣的偏移量(這個(gè)位置是從上面fsck命令得出來(lái)的)是遵循下一個(gè)對(duì)象的偏移量。現(xiàn)在我們知道類文件長(zhǎng)度是10863字節(jié),然后我們可以抓取它:
- dd if=$pack of=object bs=1 skip=51653873 count=10863
我檢查了文件的十六進(jìn)制,搜索任何明顯的錯(cuò)誤(例如,一個(gè)4K大小運(yùn)行中的0是文件系統(tǒng)沖突很好的信號(hào))。但是所有的地方看起來(lái)都很合理。
注意到“object”文件不適合被zlib直接壓縮;它本身包含git類信息頭,而且是可變長(zhǎng)度的。我們想要去掉它,所以開(kāi)始直接使用zlib操 作數(shù)據(jù)。你可以用你手工的方式(格式化信息的描述在Documentation/technical/pack-format.txt里面),或者你也可 以用debugger搞定它。我選擇了后者,創(chuàng)建了一個(gè)驗(yàn)證的包如下:# 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中運(yùn)行”git index-pack tmp.pack” 命令(會(huì)停在unpack_raw_entry)。做到這里,我發(fā)現(xiàn)有3個(gè)自己的信息頭(信息頭本身有一個(gè)完整的類型和大小)。然后我用下面語(yǔ)句去掉了這些內(nèi)容:
- dd if=object of=zlib bs=1 skip=3
我用一個(gè)自定義的C程序過(guò)的zlib的解壓鎖結(jié)果。但是它報(bào)錯(cuò)了,我得到準(zhǔn)確的輸出數(shù)字字節(jié)(也就是說(shuō),它匹配了上面解碼的git的信息頭大小)。 但是”git hash-object” 命令的結(jié)果并沒(méi)有相同的shal值。所以現(xiàn)在仍然有一些錯(cuò)誤的字節(jié)是我不知道的。這個(gè)文件發(fā)生在C源程序代碼, 我希望我能注意到一些明顯的錯(cuò)誤,但是我沒(méi)有成功,我甚至都編譯成功了!
我也試過(guò)和在資源庫(kù)相同路徑下其他版本的文件作比較,希望有不一樣和不合理的部分。不幸運(yùn)的是,這碰巧是唯一的修訂文件,在這個(gè)資源庫(kù)中。所以我沒(méi)有任何東西可以作比較。
于是我采取不同的措施,猜測(cè)著沖突是由限制單個(gè)字節(jié)引起的,我寫了交換每個(gè)字節(jié)的程序,是這解壓縮得到結(jié)果。因?yàn)檫@個(gè)類文件壓縮過(guò)后僅有10K,花了很多時(shí)間解壓出之后的結(jié)果是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;
- }
編譯和運(yùn)行的結(jié)果:
- gcc -Wall -Werror -O3 munge.c -o munge -lz
- ./munge <zlib
有一些錯(cuò)誤出現(xiàn)(如果你在zlib的信息頭中得到”no data”信息,zlib認(rèn)為它運(yùn)行的很好:))。但是我中途得到了下面的信息:
- i=5642, j=c7
等到運(yùn)行完結(jié)束,末尾獲得了更多的記錄(整理crc文件匹配了我們損壞的文件)。有一個(gè)很好的機(jī)會(huì),在中間的記錄,就是源代碼的問(wèn)題
在一個(gè)十六進(jìn)制編輯器中對(duì)字節(jié)稍微做了一些調(diào)整,zlib解壓縮(毫無(wú)錯(cuò)誤!),然后管道輸出”git hash-object”,報(bào)告了損壞文件的shal值,成功了!
我修正packfile文件本身:
- chmod +w $pack
- printf '\xc7' | dd of=$pack bs=1 seek=51659518 conv=notrunc
- chmod -w $pack
發(fā)現(xiàn)’\xc7′來(lái)自與替換我們的“munge”程序。把原來(lái)的對(duì)象偏移(51653873)導(dǎo)出了偏移量51659518 。通過(guò)“munge”(5642)增加了代替部分的偏移量,然后增加了之前去掉的3字節(jié)的git信息頭。
最后,”git fsck” 清理干凈。
關(guān)于沖突本身來(lái)說(shuō),我很幸運(yùn)它確實(shí)是一個(gè)字節(jié)。實(shí)際上,證明就是單獨(dú)的位。0xc7字節(jié)發(fā)生沖突成為0xc5.所以推測(cè)由錯(cuò)誤的硬件引起,或者物理射線。
緊接著,中止關(guān)注于解壓縮的輸出是錯(cuò)的嗎?我本會(huì)一直看前面的部分這樣永遠(yuǎn)不會(huì)發(fā)現(xiàn)它。下面是解壓縮后沖突數(shù)據(jù)的不同,真正的數(shù)據(jù)文件:
- - cp = strtok (arg, "+");
- + cp = strtok (arg, ".");
調(diào)整了一個(gè)字節(jié),最終仍然是有效的,可讀的C碰巧做了完全不同的事情!一次不同嘗試會(huì)造成幸運(yùn)的日子,看看zlib的輸出可能確實(shí)有用,正如大多數(shù)隨機(jī)的改變也許就會(huì)破壞C代碼。
但更重要的是,git的hash、檢驗(yàn)和引起了在另外一個(gè)系統(tǒng)中,很容易不被檢測(cè)問(wèn)題。這個(gè)結(jié)果仍然會(huì)編譯,但是可能就引起一個(gè)有趣的bug(歸咎于一些隨機(jī)性的提交)
原文鏈接:http://thread.gmane.org/gmane.comp.version-control.git/236238























