tl;dr
先頭 8000 バイト以内に NUL
が有ったらバイナリファイル。
Gitの実装
Gitの内蔵diffは FIRST_FEW_BYTES
だけ検索するようになっている。
#define FIRST_FEW_BYTES 8000
int buffer_is_binary(const char *ptr, unsigned long size)
{
if (FIRST_FEW_BYTES < size)
size = FIRST_FEW_BYTES;
return !!memchr(ptr, 0, size);
}
8000 ?
なんで8192とかじゃないんだろうか。この数値はGitが自前のdiffを備えた当初から8000に設定されている。
#define FIRST_FEW_BYTES 8000
static int mmfile_is_binary(mmfile_t *mf)
{
long sz = mf->size;
if (FIRST_FEW_BYTES < sz)
sz = FIRST_FEW_BYTES;
if (memchr(mf->ptr, 0, sz))
return 1;
return 0;
}
(前掲した現状のコードでは memchr
の返値を !!
でブール値にして返しているが、最初は return 1
と return 0
の分岐だったようだ。)
このコミットログでは、
- Detect and punt binary diff like GNU does;
のように書かれていて、この手法がGNU diffに由来することがわかる。
GNU diff
GNU diffで現在の形のアルゴリズムが導入されたのは'92年に遡る。
-static int
-binary_file_p (buf, size)
- char *buf;
- int size;
-{
- while (--size >= 0)
- if (!textchar[*buf++ & 0377])
- return 1;
- return 0;
-}
+#define binary_file_p(buf, size) (memchr (buf, '\0', size) != 0)
それまではprintableな文字かどうかを気にした実装だったが、急にゼロを検索する現在の形に替えている。
ちなみに、直後にGNU創始者であるリチャード・ストールマンは標準準拠でない memchr
実装を気にしたのか size!=0
のチェックを追加しているが、 このコードは結局元に戻されていて現存しない のはちょっと面白い。
-#define binary_file_p(buf, size) (memchr (buf, '\0', size) != 0)
+#define binary_file_p(buf, size) (size != 0 && memchr (buf, '\0', size) != 0)
libgit2
ちなみにlibgit2も当初は以前のGNU diffのようなprintableな文字かどうかを気にした実装になっていたが、今はGit互換のアルゴリズムを採用している。
-
https://github.com/libgit2/libgit2/issues/2176
- Libgit2 binary detection does not match core git
-
https://github.com/libgit2/libgit2/pull/2319/files#diff-938eea6140bea02d453ac7deb182615aL175
- 以前のlibgit2のバイナリファイル判定を削除し、Git互換のものに置き換えたPR
- 従来は
git_buf_text_is_binary
で、printableかどうかの判定を行っていた
...そもそも何で最初は真面目にprintable判定をしたんだろうか。
妥当性
このアルゴリズムの問題は NUL
が無いバイナリファイルを検出できない点となる。 ...そんなもん普通は無いだろうと言って良い気もする。そういうファイルを救おうとしてprintable判定を入れると、今度はUTF-8テキスト等をバイナリとして誤判別する可能性がある。
逆に、バイナリフォーマットを設計するときは意図的に NUL
を入れるべきなのかもしれない。この目的で NUL
を採用しているフォーマットはあんまり無い気もするが、例えば PNGは必ずIHDRチャンクから始まるためファイルの先頭にNUL文字が常に入る ことになる。同様に、executableの類もファイル内部に大抵ポインタの形で適当な数値を持っていてゼロに近い値が入るだろうし、圧縮ファイルであればランダムデータになるのでそれなりの確率でゼロが来ることが期待できる。
UTF-16とかは。。まぁ使わないで。。