/dev/zeroに対抗して/dev/0を作る
読み出したら zero fillならぬ0 fillをするデバイスを作る。とはいっても、デバイスファイルの登録とか面倒だし、SystemTapでちょっと小細工をする。
出来たのが以下。
global fillstr; global candidate; global readpos; %{ #include <linux/string.h> %} function basename:string (path:string) %{ const char *p, *s = (void *)(unsigned long)THIS->path; p = strrchr(s, '/'); if (!p) p = s; else p++; if (p) strlcpy(THIS->__retvalue, p, MAXSTRINGLEN); %} function write_string (buf:long, str:string, len:long, pos:long) %{ char *buf = (void *)(unsigned long)THIS->buf; int64_t len = THIS->len, slen = strlen(THIS->str); int64_t cnt, pos = THIS->pos; if (pos % slen) { memcpy(buf, (const char *)THIS->str + (pos % slen), slen - (pos % slen)); buf += slen - (pos % slen); len -= slen - (pos % slen); } for (cnt = 0; (cnt + 1) * slen < len; cnt++) strlcpy(buf + (cnt * slen), THIS->str, slen + 1); memcpy(buf + (cnt * slen), THIS->str, len - cnt * slen); %} probe syscall.open { candidate[pid()] = basename(filename) } probe kernel.function("memory_open") { fillstr[pid()] = candidate[pid()] } probe kernel.function("read_zero").return { if (fillstr[pid()] == "zero") next len = $return if (len > 0) { write_string($buf, fillstr[pid()], len, readpos[tid()]) readpos[tid()] += len; } }
使い方は、このスクリプトをGuruモードで走らせてから/dev/zeroから/dev/0へSymbolic linkを張るだけ。
stap -gF zerohook.stp ln -s /dev/zero /dev/0 less /dev/0
適当に長いbasenameでも使えるし、/devにある必要すらないので、ln -s /dev/zero hogehugaとかするとhogehuga fillされたデータが出てくる。
基本アイデアは、system callのopenをフックしてbasenameを取得、次に/dev/zeroの処理をフックして、渡されたバッファを先ほどのbasenameで埋め尽くすというもの。
工夫というほどでもないが、単にフックするだけだと他のプロセスやスレッドのopenと重なる場合があるので、tidごとに変数を分けるようにしている。