MD5とかSHA1とかではどうしても信用できない人の為(?)に2つ("以上"では無い)のバイナリファイルがまったく同じものであるかを調べる為のツールを作った。
[lugia@lugia-castle ~]$ diff --help 使用法: diff [オプション]... FILES 2つのファイルを行ごとに比較します。 -i --ignore-case ファイル内容の大文字小文字を無視。 --ignore-file-name-case ファイル名の大文字小文字を無視。 --no-ignore-file-name-case ファイル名の大文字小文字を区別。 -E --ignore-tab-expansion タブ展開の差を無視。 -b --ignore-space-change 空白数の差を無視。 -w --ignore-all-space 全空白を無視。 -B --ignore-blank-lines 空白だけの行の差を無視。 -i RE --ignore-matching-lines=RE REに一致するすべての行の差を無視。 --strip-trailing-cr 入力から行末キャリッジ・リターンを削除。 -a --text すべてテキストとして処理。 (後略)
でも用は果たせるかもしれないけど、わかりにくいから作った(ぁ
使い方:
bdiff [オプション] [比較ファイル1] [比較ファイル2]
1にある部分には冒頭に - (マイナス)が、2にある部分には冒頭に + (プラス)が付きます。
オプションは
bdiff -c [数値] -t [数値]
c と t は大文字でも可。
c のあとの数字は一度に読み込む量。32ビットの環境では 4GByte-1 まで指定できる。メモリもそれ相応の量が無いといけないので注意。
t のあとの数字は比較対象となる先頭からのデータ量。16EByte-1 *1まで指定できる。
数値にはスペースなしで単位が付けられる。小文字なら bit、大文字なら Byte。
英字記号 | 装飾子 |
---|---|
k, K | キロ |
m, M | メガ |
g, G | ギガ |
バイト単位で比較するので、ビット単位で指定した場合に生じた端数は切り捨てられる。
また無単位の場合には バイト として扱われる。
0バイトまたはなにも指定されていない場合は、
bdiff -c 16K
と同じ。比較サイズは小さい方のファイルサイズ。
「長い時間が掛かるかもしれません。よろしいですか?」と(gettext の和訳ファイルがなければ英語で)聞かれるので、y と答えると、比較が始まります。
違ったところを発見する度に出力するので、まったく同じなら無出力。
性能はこんなもん。@ Core 2 Duo 1.6GHz、SATA HDD ドライブ (TOSHIBA MK1234GSX)
当然 USB だったり、実際の感覚としては出力があるともっと遅くなる。
[lugia@lugia-castle Documents]$ time ./bdiff McE500-12_Katsuta_Dep.wav McE500-12_Katsuta_Dep.wav Compare Detail is ... Chunk Size : 16,384 Byte(s) Total Size : 15,067,436 Byte(s) File 1 (Size) : McE500-12_Katsuta_Dep.wav (15,067,436) File 2 (Size) : McE500-12_Katsuta_Dep.wav (15,067,436) Verifing may takes very long time. Are you sure? (y/n): y real 0m4.403s user 0m0.189s sys 0m0.018s
同じく PC-PSP 間で。USB ケーブルは製造者不明。PSP は PSP-3000。
USB2.0 の対応/非対応は一切不明。
[lugia@lugia-castle ~]$ time ./bdiff aaa.iso /media/disk/aaa.iso -c 1M Compare Detail is ... Chunk Size : 1,048,576 Byte(s) Total Size : 895,580,160 Byte(s) File 1 (Size) : aaa.iso (895,580,160) File 2 (Size) : /media/disk/aaa.iso (895,580,160) Verifing may takes very long time. Are you sure? (y/n): y real 1m36.864s user 0m12.221s sys 0m1.779s
違う場合には
-0000000000001cb0 61 73 65 3c 2f 62 3e 20 3c 2f 73 70 61 6e 3e 3c +0000000000001cb0 09 72 65 74 75 72 6e 20 30 34 30 30 3b 0a 20 20
のように出力される。左からファイルの別(-/+)、ファイルの先頭からのバイトオフセット、そこから 16 バイトのバイト列。
なお、データ量が莫大に膨らむ為、パッチには使えない。
↓ソースコード(続きを読む)。GPLv2 の内容さえ守ってくれれば、煮たり焼いたり使い易いように改造したりご自由にどうぞ。たぶん gcc + glibc の組合せでしかコンパイルできません。
-bdiff.cpp-
/* Explain: Binary Comparing Program Version: 1.0.1 License: GPLv2 or later. Author : Lugia Kun */ #include <iostream> #include <iomanip> #include <fstream> #include <string> #include <cstdlib> #include <cstring> #include <cerrno> #include <clocale> #include <libintl.h> #include <sys/types.h> #include <sys/stat.h> #include <unistd.h> using namespace std; #define _(a) gettext(a) #define __(a) (string)(a) class flags { unsigned long int chunk_size; unsigned long long int total_size; unsigned long long int file_size[2]; string file_name[2]; public: flags() { chunk_size = 0x0l; total_size = 0x0l; file_size[0] = 0x0l; file_size[1] = 0x0l; file_name[0] = ""; file_name[1] = ""; } void setChunk(unsigned long int size, unsigned char unit) { switch(unit) { case 'b': // bit chunk_size = size / 8; break; case 'k': // Kbit chunk_size = size * (1024 / 8); break; case 'm': // Mbit chunk_size = size * (1024 * 1024 / 8); break; case 'g': // Gbit chunk_size = size * (1024 * 1024 * 1024 / 8); break; case 'B': // Byte chunk_size = size; break; case 'K': // KByte chunk_size = size * 1024; break; case 'M': // MByte chunk_size = size * 1024 * 1024; break; case 'G': // GByte chunk_size = size * 1024 * 1024 * 1024; break; default: // Default is Byte. chunk_size = size; break; } } void setTotal(unsigned long long int size, unsigned char unit) { switch(unit) { case 'b': // bit total_size = size / 8; break; case 'k': // Kbit total_size = size * (1024 / 8); break; case 'm': // Mbit total_size = size * (1024 * 1024 / 8); break; case 'g': // Gbit total_size = size * (1024 * 1024 * 1024 / 8); break; case 'B': // Byte total_size = size; break; case 'K': // KByte total_size = size * 1024; break; case 'M': // MByte total_size = size * 1024 * 1024; break; case 'G': // GByte total_size = size * 1024 * 1024 * 1024; break; default: // Default is Byte. total_size = size; break; } } unsigned long int getChunkSize(void) { return chunk_size; } unsigned long long int getTotalSize(void) { return total_size; } void setFile(int id, string filename) { if(id >= 2) { throw __("System Error: Invalid Argument : flags::getFileSize()"); } struct stat dt; stat(filename.c_str(), &dt); if(errno != 0) { throw filename + ": " + strerror(errno); } file_size[id] = dt.st_size; file_name[id] = filename; } unsigned long long int getFileSize(unsigned int id) { if(id >= 2) { throw __("System Error: Invalid Argument : flags::getFileSize()"); } return file_size[id]; } string getFileName(unsigned int id) { if(id >= 2) { throw __("System Error: Invalid Argument : flags::getFileName()"); } return file_name[id]; } }; ifstream ifile[2]; int main(int argc, char** argv) { try { // setlocale(LC_ALL, ""); locale::global(locale("")); cout.imbue(locale("C")); cerr.imbue(locale("")); clog.imbue(locale("")); cin.imbue(locale("")); errno = 0; flags flgs; char* ret = NULL; unsigned long long int size = 0; unsigned char unit = '\0'; int c = 0; for(int i = 1; i < argc; i++) { if(*argv[i] == '-') { switch(*(argv[i] + 1)) { case 'c': case 'C': size = strtol(argv[i] + 2, &ret, 10); unit = *ret; if(i + 1 < argc && (size == 0 || ret == argv[i] + 2)) { size = strtol(argv[i + 1], &ret, 10); unit = *ret; if(size == 0 || ret == argv[i + 1]) { throw __("Chunk Size is not specified."); } else { i++; } } flgs.setChunk(size, unit); break; case 't': case 'T': size = strtoll(argv[i] + 2, &ret, 10); unit = *ret; if(i + 1 < argc && (size == 0 || ret == argv[i] + 2)) { size = strtoll(argv[i + 1], &ret, 10); unit = *ret; if(size == 0 || ret == argv[i + 1]) { throw __("Total Size is not specified."); } else { i++; } } flgs.setTotal(size, unit); break; default: throw (string) _("Unknown Option:") + " \'" + *(argv[i] + 1) + "\'"; break; } } else { if(c >= 2) { throw __("Too many files specified."); } flgs.setFile(c, argv[i]); if(errno != 0) { throw (string) argv[i] + ": " + strerror(errno); } c++; } } if(c < 2) { throw flgs; } if(flgs.getChunkSize() == 0) { flgs.setChunk(16, 'K'); // 16kByte } if(flgs.getTotalSize() == 0 || flgs.getFileSize(0) < flgs.getTotalSize() || flgs.getFileSize(1) < flgs.getTotalSize()) { if(flgs.getFileSize(0) > flgs.getFileSize(1)) { flgs.setTotal(flgs.getFileSize(1), 'B'); } else { flgs.setTotal(flgs.getFileSize(0), 'B'); } } clog << _("Compare Detail is ...") << endl; clog << _("Chunk Size : ") << setw(26) << flgs.getChunkSize() << _(" Byte(s)") << endl; clog << _("Total Size : ") << setw(26) << flgs.getTotalSize() << _(" Byte(s)") << endl; clog << _("File 1 (Size) : ") << left << setw(26) << flgs.getFileName(0); clog << " (" << flgs.getFileSize(0) << ")" << endl; clog << _("File 2 (Size) : ") << left << setw(26) << flgs.getFileName(1); clog << " (" << flgs.getFileSize(1) << ")" << endl; clog << _("Verifing may takes very long time. Are you sure? (y/n): "); char rep; cin >> rep; if(rep == 'n' || rep == 'N') { throw __("Verifing is canceled by user."); } if(errno != 0) { throw errno; } char* tmp[2]; for(int i = 0; i < 2; i++) { ifile[i].open(flgs.getFileName(i).c_str(), ifstream::binary); if(ifile[i].bad() || errno != 0) { throw flgs.getFileName(i) + ": " + strerror(errno); } tmp[i] = new char[flgs.getChunkSize()]; } for(unsigned long long int i = 0; i < flgs.getTotalSize(); i += flgs.getChunkSize()) { for(int j = 0; j < 2; j++) { if(ifile[j].eof() == true) { memset(tmp[j], 0x0, flgs.getChunkSize()); } else { ifile[j].read(tmp[j], flgs.getChunkSize()); if(ifile[j].bad() || errno != 0) { throw flgs.getFileName(j) + ": " + strerror(errno); } ifile[j].seekg(flgs.getChunkSize(), ifstream::cur); if(ifile[j].bad() || errno != 0) { throw flgs.getFileName(j) + ": " + strerror(errno); } } } for(unsigned long int j = 0; j < flgs.getChunkSize(); j += 16) { if(i + j >= flgs.getTotalSize()) break; bool check = false; for(int k = 0; k < 16; k++) { if(j + k < flgs.getChunkSize() && (*(tmp[0] + j + k) != *(tmp[1] + j + k))) { check = true; break; } } if(check) { for(int l = 0; l < 2; l++) { cout << ((l == 0) ? "-" : "+"); cout.fill('0'); cout << setw(16) << hex << i + j << " "; for(int k = 0; k < 16 && j + k < flgs.getChunkSize(); k++) { cout.fill('0'); cout << setw(2) << ((int)*(tmp[l] + j + k) & 0x000000ff) << " "; } cout << endl; } } } } for(int i = 0; i < 2; i++) { ifile[i].close(); delete [] tmp[i]; } return 0; } catch(bad_alloc) { cerr << argv[0] << ": " << strerror(ENOMEM) << endl; return ENOMEM; } catch(int err) { cerr << argv[0] << ": " << strerror(err) << endl; return err; } catch(string str) { cerr << argv[0] << ": " << _(str.c_str()) << endl; return 0300; } catch(...) { cerr << __("Usage: ") << argv[0] << __(" [options] file1 file2") << endl; return 0400; } }
変更履歴
バージョン | 変更内容 |
---|---|
1.0.0 | 初版 |
1.0.1 | メモリ不足の場合のエラーメッセージを変更。"-c 4g"のオーバーフローエラーを修正。 |