/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ごとに変数を分けるようにしている。