いつかまたハマるかもしれないので覚書
ハマったこと
未だにLinux 32bit環境で開発することがあるのですが、とあるシステムでハマりました。
そのシステムはファイルをC言語のプログラムでなんやかんやするのですが、
数GByteレベルの大きいサイズを扱うことになり試してみるとまるで動かない。
ファイルがでかいので1回再現させるのにも時間がかかってイライラしながらデバッグしてわかった原因はとてもシンプル。
ファイルが大きすぎてfopen出来ない!
Linux 32bitだと2GByteくらいだそうです。(自分の環境だけかも
まあそりゃ上限はあるだろうけど、実際に出くわすことになるとは…
しかもこのシステムでは、C言語のプログラムがファイル分割するためのアプリケーション。
それがファイルを開くことすらできないという。
まじかよ…
解決案
2018/04/23 追記
@angel_p_57さんからのコメントで、
_FILE_OFFSET_BITSというコンパイルオプションがあるということをお聞きしました。
manページや手元にあったOSSコードに倣い、以下をCFLAGSに追加ところ、無事そのままfopenが出来るように!
-D_FILE_OFFSET_BITS=64
いや~、オプション一つで動作が大きく変えられるなんて、素晴らしいですねglibc!
番外: glibcを甘く見ていた僕の解決案
色々ファイル分割方法を調べて行きついたのがsplitコマンド。
これはLinuxでファイルを分割するためのコマンドで、これのおかげで
splitコマンドだけはうまく動作していました。
じゃあどうやって対処しようか。
ベストはsplitの中身を見てやるのがいいのでしょうが、時間がないので
splitコマンドを利用してfopenのwrapperを作ろう!
対処
fopen代わりのopen IFで、サイズが大きかったら/usr/bin/splitで分割してtmpディレクトリに保存
→fread時にfread中に終端まで行ったら次のファイルをopenして続きをreadって感じにしました。
//fopenのラッパー
void * large_freader_open(const char *path, unsigned long maxsize) {
//....
//サイズを見て、大きすぎたらseparate_fileでファイル分割
unsigned long fsize = get_size(path);
if(fsize<=maxsize) {
//same as fopen
handle->fp=fopen(path, "r");
handle->max_index=1;
} else {
//separate file and open file as order
separate_file(path, maxsize, handle);
handle->fp = freader_fopen(handle);
}
//..
//returnはFILE *ではなく内部の構造体を返す
return handle;
//..
}
static void separate_file(const char *path, unsigned long maxsize, struct large_freader_s *handle) {
//...
//tmpディレクトリを取得してファイル分割
char name[FNAME_MAX];
get_current_dirname(handle, name, FNAME_MAX);
snprintf(cmd, sizeof(cmd), "/usr/bin/split -d --suffix-length=6 -b %lu %s %s", maxsize, path, name);
//...
}
//freadのラッパー
size_t large_freader_read(void * prt, size_t size,void * stream) {
//...
//普通にfreadして
size_t ret = fread(prt, 1, size, handle->fp);
if(ret == size) {
//read success, return normaly
return ret;
}
//サイズ分読んでないなら次のファイルに移動
freader_fclose(handle);
//move to next
handle->cur_index++;
//全ファイル読んでるなら終わり
if(IS_LAST_FILE(handle)) {
//finish to read
return ret;
}
//そうじゃないなら次をopenしてread
handle->fp = freader_fopen(handle);
if(handle->fp) {
ret += fread(((char *)prt)+ret, 1, size-ret, handle->fp);
}
return ret;
}
//内部でのfopen処理
static FILE * freader_fopen(struct large_freader_s *handle) {
//splitしたファイル名を取得してfopen。close時にはファイル削除します。
char dname[FNAME_MAX];
get_current_fname(handle, dname, FNAME_MAX);
return fopen(dname, "r");
}
自宅で一晩で作成し、githubに公開しました。
wrtie時のファイル分割も行いたかったので、read/writeのラッパーライブラリになっています。
-D_FILE_OFFSET_BITS=64がある今、writeしか使い道がないな…
家のPCは64bitで32bitでがっつり試せてないので、別環境で利用してみて気付いた問題は直していきます。
反省
2018/04/23 追記
・通常思いがけないことがあったら、まずはコンパイルオプションに何かないか調べてみよう!
そうでないと、
・fopenが使えないのでサイズをとるにもpopen→lsとやりざるを得ないのが非常にダサい
・32bit環境での動作保証に不安
・大体32bitシステムの限界だから頑張るとこじゃなくね?
etc
ダサい解決案に頼らざるを得なくなります。