f2tfsができたよ としぁ Adventしたね Calendar 2022 #toshi_a解凍

みなさん、ファイルシステムは好きですか。パソコンを使っていれば、毎日のようにお世話になっているはずで、その意味ではファイルシステムってご飯のようなものですね。なので、みんなファイルシステムのこと大好きだと思います。

世の中、いろんなご飯があるように、Linuxにもその他のOSにもたくさんのファイルシステムがあります。変わった食べものがあるように、変わったファイルシステムもあれば、変な自炊するようにサクサクおもしろファイルシステムを作ることができます。

ということで、まずは今回作ったf2tfsの概略図です。f2fsではないです。

f:id:meech:20220415091853p:plain

どんなファイルシステムなの

図だけでは、わけわからんでしょうから、実際使ってみましょうね。

まずはmount pointを指定してスクリプトを動かしましょう。

$ sudo ./f2tfs.sh /mnt/f2t

mountしたとこを見てみましょう。 user_id というファイルしかないですね。つまらないファイルシステムだ? いえいえ、ここに謎の数字を書いてみましょう。

$ cd /mnt/f2t
$ ls -li
total 1
2 -rw-r--r-- 1 root root 2 Apr 14 20:15 user_id
$ echo 15926668 > user_id

するとどうでしょう。謎の数字のファイルたちがでてきましたね。中身を見てみますと、どうにもておい文字列が出てきました。やったあ。

$ ls -li
total 11
1509907199010611200 -rw-r--r-- 1 root root  91 Apr  1 23:55 1509907199010611200
1509908201327763469 -rw-r--r-- 1 root root  51 Apr  1 23:59 1509908201327763469
1509912170800328706 -rw-r--r-- 1 root root 112 Apr  2 00:14 1509912170800328706
1509912225514885122 -rw-r--r-- 1 root root  22 Apr  2 00:15 1509912225514885122
1510963930205544450 -rw-r--r-- 1 root root  59 Apr  4 21:54 1510963930205544450
1510974849564372993 -rw-r--r-- 1 root root  47 Apr  4 22:37 1510974849564372993
1510974859496501253 -rw-r--r-- 1 root root  47 Apr  4 22:37 1510974859496501253
1510975098974453763 -rw-r--r-- 1 root root  55 Apr  4 22:38 1510975098974453763
1510983572399689730 -rw-r--r-- 1 root root  95 Apr  4 23:12 1510983572399689730
1510983608713981954 -rw-r--r-- 1 root root  31 Apr  4 23:12 1510983608713981954
1510990036564529160 -rw-r--r-- 1 root root 108 Apr  4 23:37 1510990036564529160
1510991713069789186 -rw-r--r-- 1 root root 104 Apr  4 23:44 1510991713069789186
1510991766572347392 -rw-r--r-- 1 root root 154 Apr  4 23:44 1510991766572347392
1511337205486452738 -rw-r--r-- 1 root root  47 Apr  5 22:37 1511337205486452738
1511337272939286529 -rw-r--r-- 1 root root  19 Apr  5 22:37 1511337272939286529
1511337376295317506 -rw-r--r-- 1 root root  37 Apr  5 22:38 1511337376295317506
1511337725479497730 -rw-r--r-- 1 root root  82 Apr  5 22:39 1511337725479497730
1511337915154333700 -rw-r--r-- 1 root root  71 Apr  5 22:40 1511337915154333700
1511339259349073922 -rw-r--r-- 1 root root  85 Apr  5 22:45 1511339259349073922
1511339988646252548 -rw-r--r-- 1 root root  35 Apr  5 22:48 1511339988646252548
                  2 -rw-r--r-- 1 root root   9 Apr 14 20:17 user_id
$ cat 1510983608713981954
これめっちゃきらい!

さて、なんとこれらのファイルには書くこともできます。だって"f2tfs"ですからね。

$ echo 1 > 1511337915154333700

するとどうでしょう。ツイがふぁぼられてますね! F2T! F2T!!

f:id:meech:20220415091932p:plain

ちなみに他の値は書けません。"0"書くとあんふぁぼしたりしません。ふぁぼるためだけの存在なので。

中身のはなし

さて、このようにf2tfsはツイ一トを読んだりふぁぼったりできるファイルシステムなわけですが、その中身はどうなっているのでしょう。実は動かしていたスクリプトは、ファイルシステム部分のラッパとかではなく本体です。そうです、簡単にサクッと作りたかったので、今回は簡単でおなじみのシェルスクリプトFUSEファイルシステムを作りました。

とはいえ、シェルスクリプトで全部自力でFUSEやったり、Twitter APIやったりするのはめんどいです。そこで、FUSEとのやりとりには以下の booze を使いました。一方、 Twitterとのやりとりはmikuterにお願いしています。

github.com

boozeはbashへのプラグインとしてできていて、builtinとして"booze"コマンドが追加されます。まず、連想配列を作ります。f2t_ops[read]=f2t_read など、システムコール名をキーとして関数名をつっこみます。

f2t_read() {
...
}

f2t_write() {
...
}

declare -A f2t_ops
for name in ${BOOZE_CALL_NAMES[@]}; do
        if [ "`type -t f2t_$name`" == "function" ]; then
                f2t_ops[$name]=f2t_$name
        fi
done

booze -o use_ino f2t_ops "$1"

これでbooze側から、システムコールに対応する関数が呼ばれてくるので、適宜関数を実装していきます。

一方、Twitterとのやりとりにはmikutterを使役します。mikutter-modeとかいうよくわからないリポジトリプラグインを入れると、mikutterがD-Busでサービスを開始します。

github.com

gdbusコマンドを使うとどんなメソッドがあるかわかります。

$ gdbus introspect -e -d org.mikutter.dynamic -o /org/mikutter/MyInstance
node /org/mikutter/MyInstance {
  interface org.freedesktop.DBus.Properties {
...
  };
  interface org.mikutter.eval {
    methods:
      ruby(in  a(sv) param,
           out s result);
    signals:
    properties:
  };
};

はい。org.mikutter.evalの中にrubyというメソッドがあります。mikutterの中でrubyがevalされるというゴリゴリやべえメソッドです。入力の型が"a(sv)"となっています。つまり、string("s")とvariant("v")のタプル("(sv)")の配列("a(sv)")です。なので入力がちょっと面倒なのですが、以下のように使います。

$ gdbus call -e -d org.mikutter.dynamic -o /org/mikutter/MyInstance -m org.mikutter.eval.ruby '[("code", <"p 42">), ("file", <"org.mikutter.eval">)]'
('42',)

これをこんな感じで、rubyコードを入れて使います。

mikcall() {
    read -r -d '' rb
    arg="[(\"code\", <\"${rb@E}\">), (\"file\", <\"org.mikutter.eval\">)]"

    log "${arg}"

    sudo -u ${DBUS_USER} gdbus call -a ${DBUS_SESSION_BUS_ADDRESS} \
        -d org.mikutter.dynamic \
        -o /org/mikutter/MyInstance \
        -m org.mikutter.eval.ruby \
        "${arg}" 2>>${LOG_FILE}
}

mikcall_tweets() {
    mikcall <<EOM
tw = Plugin.collect(:worlds).to_a[1]
result = nil
done = false
task = tw.user_timeline(:user_id => ${USER_ID}).next { |res|
  result = res
  done = true
}
while !done
  sleep 0.1
end
result.map { |msg|
  \\\\"#{msg.id} #{msg.created.to_i} #{msg.modified.to_i} #{msg.message}\\\\"
}.join(\\\\"<>\\\\")
EOM
}

sleep 0.1のループとかださいことしてますが、delayer-deferredよくわからんかったんじゃ。

びみょうなはまりどころ

びみょうなはまりどころを簡単に紹介しておきます。

inode番号とツイートのIDを一致させたかった

一致させたいよね。statでinode番号を教えても、デフォルトではFUSEは無視します。inode番号を使ってもらうには"-o use_ino"をmount optionにいれねばならぬのですが、boozeコマンドはそれを受け付けてくれません。なので、以下のpatchを当ててやりましょう。

diff --git a/booze.c b/booze.c
index 1231dbe2fde6..43c6d0c253da 100644
--- a/booze.c
+++ b/booze.c
@@ -639,7 +639,7 @@ static int booze_builtin(WORD_LIST* args)
    fuse_argv[0] = "booze";
    fuse_argv[1] = "-s";
 
-  while ((opt = internal_getopt(args, "df")) != -1) {
+   while ((opt = internal_getopt(args, "dfo:")) != -1) {
        switch (opt) {
        case 'd':
            fuse_argv[argidx++] = "-d";
@@ -651,6 +651,12 @@ static int booze_builtin(WORD_LIST* args)
            fuse_argv = xrealloc(fuse_argv, (argidx + 1) * sizeof(char*));
            break;
 
+       case 'o':
+           fuse_argv = xrealloc(fuse_argv, (argidx + 2) * sizeof(char*));
+           fuse_argv[argidx++] = "-o";
+           fuse_argv[argidx++] = strdup(list_optarg);
+           break;
+
        default:
            xfree(fuse_argv);
            builtin_usage();

writeの関数では変数が設定できなかった

readの関数では標準出力に読まれるデータを出力します。ということは、writeの関数ではその逆で標準入力に書かれたデータが来ます。

ところが、これが問題で、入力を受けるために子プロセスが起動されてその中でwriteの関数が実行されます。ということは、その中で変数を更新してもそれは他の関数には反映されないんですねえ。めんどい。

ということで、こんな感じで処理しました。

JOURNAL=journal
load_journal() {
    test -e ${JOURNAL} || return 0

    source ${JOURNAL}
    rm -f ${JOURNAL}
}
add_journal() {
    local name="$1"
    value=$(eval "echo \$${name}")
    echo ${name}=${value@Q} >> ${JOURNAL}
}

f2t_read() {
    local path="$1"
    local readlen="$2"
    local offset="$3"

    check_file_path "$path" || return 1
    load_journal
...
}

f2t_write() {
...
        USER_ID="$(</dev/stdin)"
        add_journal USER_ID
...
}

pretty_inspectがうざかった

mikutter-modeのプラグインの元のコードは以下のように、"pretty_inspect"でいい感じに出力してくれてきれいです。でも、改行が入ってると"abc\n" + "def"みたいな感じになってbash側で読むのがめんどいんですよね。なので、以下のように変えました。

diff --git a/plugin/mikutter_mode/mikutter_mode.rb b/plugin/mikutter_mode/mikutter_mode.rb
index 2fa8372ce675..c4e2ea55403a 100644
--- a/plugin/mikutter_mode/mikutter_mode.rb
+++ b/plugin/mikutter_mode/mikutter_mode.rb
@@ -22,7 +22,8 @@ Plugin.create(:mikutter_mode) do
           notice "ruby code execute: \n#{file || code}"
           r = Server.main.instance_eval(code, file || "mikutter-mode onthefly executer")
           notice "returns: \n#{r.pretty_inspect}"
-          [r.pretty_inspect]
+          #[r.pretty_inspect]
+          [r.inspect]
         rescue Exception => e
           notice "exception:"
           notice e

tweetの区切り文字

前の方のrubyコードをよく読んでいたらお分かりですが、各ツイは" <本文>"の形で文字列され、さらにそれらを"<>"でjoinしたものが出力されます。古式ゆかしい巨大掲示板でよくやる方式ですね。

NULL文字でのjoinも考えたんですが、mikutter-modeのプラグインが死ぬのでやめました。

dbus-sendは使えない

D-Busたたくコマンドというとdbus-sendがありますが、今回の用途には使えません。dbus-sendではvariantを表現する方法がないのです。

DBUS_SESSION_BUS_ADDRESS わたすのめんどい

FUSEを動かす都合上、スクリプトはrootで動かす必要があります。一方で、mikutterをrootで動かすのはアレすぎるので、mikutterは一般ユーザの権限で動いています。なので DBUS_USER, DBUS_SESSION_BUS_ADDRESS を環境変数で渡して、うまいことやってやる必要があります。

capabilityをうまくやったりしたらいけるのかもしれないけれど、めんどかったので。

なんかmikutter側でアカウントをtwitterのに変えとかんとだめだね

うまくふぁぼれないみたい。いま気づいたよ。まあそもそも、"tw = Plugin.collect(:worlds).to_a[1]" これも雑なので。使いたくなったらうまく変えてね。

Adventしたよカレンダーまだあいてるよ!

としぁ Adventしたね Calendar 2022 #toshi_a解凍 まだあいてるよ! 登録してね!!

docs.google.com

それではまた

コードはこちら

github.com

では最後に

$ cd /mnt/f2t
$ ls [0-9]*
1509907199010611200  1510963930205544450  1510983572399689730  1510991766572347392  1511337725479497730
1509908201327763469  1510974849564372993  1510983608713981954  1511337205486452738  1511337915154333700
1509912170800328706  1510974859496501253  1510990036564529160  1511337272939286529  1511339259349073922
1509912225514885122  1510975098974453763  1510991713069789186  1511337376295317506  1511339988646252548
$ for x in [0-9]*; do echo 1 > $x; done