fuse でオレオレファイルシステムを作ってみた (Haskell で)

TECHSCORE Advent Calendar 2015 の 8 日目の記事です。

fuse でオレオレファイルシステムを作ってみた

Unix 系 OS では、fuse というモジュールを使って、オレオレファイルシステムを作成することができます。 今回は、fuse と locate コマンドで作ってみたファイルシステムをネタに、どんな感じで開発できるか紹介します。

この内容に興味のない方は、最後の世界で一番かわいい猫の画像を好きなだけ眺めていってください。

検索用ファイルシステム

GUI のファイラーでは、検索をすると結果のファイルがずらずらと出てきて通常のディレクトリと同じような操作ができる、そういったものがよくあります。 しかし、locate などで検索しても、単にファイルパスが表示されるだけです。 そこで、(閲覧だけになりますが)似たようなことをするために、locate コマンドの結果を通常のディレクトリのように見ることができるファイルシステムを作ることにしました。 設定ファイルなどを CLI のファイラーの ranger などで、こんな感じ↓で次々に見たりすることが主な目的です。

find-service-files-on-ranger.png

(systemd の service ファイル検索して見ています)

仕組み

アクセスするパスのディレクトリ名を locate に渡すクエリとして、そのディレクトリにアクセスすると locate の検索結果が出るという仕組みにしました。

具体例

/locate がこのファイルシステムのマウント先で、pacman.log を検索したいとします。 この場合、以下の様にアクセスします。

/locate/pacman.log/ ディレクトリに var_-log_-pacman.log などのエントリが存在するように見える寸法です。 ちなみに、エントリ名はフルパスのスラッシュを _- といった風に適当に加工したものです。 デコードすることで、フルパスが得られるというわけです。

これらのエントリは、シンボリックリンクなので、このリンクにアクセスすることで、元のファイルなどにアクセスできます。

こうやってパスを打ち込む分には特にメリットがありませんが、上のスクリーンショットの様なファイラーでは、ちょっと便利というわけです。

どう実装するか

fuse での実装は、簡単です。 ファイルシステム上の機能(ファイル/ディレクトリの読み込み等々)を、関数を書くことで実装していくだけです。 不要な機能については、その機能は無いと返すだけで良いので、実質省略できます。

また、色々な言語のバインディング もあるので、大抵の言語で容易に実装できるようです。

そういうわけで(?)、今回は Haskell で実装しました。

実際の実装

実際に書いたコードを抜粋しつつ、軽く説明していきます。

ディレクトリとシンボリックリンクの情報を生成します。 リンクのほうは、getFileStatus でリンク元ファイルの情報を取得して、転記しています。 後述の関数で、これらを返すようにします。

FuseOperations が、要となるデータ型です。 fuseGetFileStat などが各機能で、これら自前の関数をバインドしてやります。 (引数の Semaphore と Cache は locate 結果のキャッシュに関するもので、fuse と直接関係はありません)

あとは、それぞれの機能を個別に実装していきます。 lfs〜というのがそれです。

ファイルの情報を取得する機能

locate 結果のファイルを取得しに来たときだけ、シンボリックの情報を生成して返しています。 他は、固定のディレクトリの情報を返しています。

procPath は、引数のパスが ルート/クエリ/結果のパス かで処理を分ける関数です。

ディレトクリを開くことが出来るか確認する機能

本来であれば、パスを見て判断するのでしょうが、アクセスできないディレクトリは提供しないので、無条件で成功を返しています。

ディレクトリのエントリを取得する機能

ディレクトリ名をクエリとして locate を実行し、シンボリックリンクとして返しています。 "/" などのアクセス時は、とりあえず失敗(存在しない = eNOENT)を返しています。

シンボリックリンクの解決機能

要するに readlink するものです。 指定のパスのリンクのリンク先を返します。 locate 結果のシンボリックリンクのリンク先を返しています。 これも、リンクでないはずのパスについては、失敗を返しています。

fixString は、リンク先のパスにマルチバイト文字が入っていると文字化けするという問題(バグ?)への workaround です。 本来は必要ないのだと思います。 後でちゃんと調べたいですね。

ファイルシステムの情報取得機能

とりあえず、サンプルと同じ値を返しています。 ファイル名が長くなりがちなので、fsStatMaxNameLength だけ、長めにしています。

ソースコード

全体のソースコードは、こちら (github.com) に用意しました。 fuse の開発用パッケージ、haskell のコンパイラの ghc と cabal があれば、make 一発でビルドできると思います。 (cabal sandbox 上でビルドします)

使いかた

オプションにマウント先を渡してやれば、それだけでバックグラウンドで動作します。 実行ファイルは、 dist/build/locatefs/locatefs に生成されていると思います。

問題点

なぜか偶に Segfault で落ちたり、妙なことになるようです。 なにかご存知のかたがいましたら、ぜひ御教示ください! 条件もまだわからず、謎です。

また、ファイル名のエスケープ方法も、もう少し見やすいものにしたいですね。

世界で一番かわいい猫

さて、ここまで読んでくれてお疲れさまでした。 あとは、世界で一番かわいいウチの猫の写真をお楽しみください。

かわいさがヤバイ!
Comments are closed, but you can leave a trackback: Trackback URL.

Advent Calendar 2015の連載記事

  1. TECHSCORE Advent Calendar 2015
  2. Redshift と PostgreSQL に同時に JDBC 接続する
  3. Lombok で Spice up your Java!
  4. 画像を指定するだけ!非デザイナーでも簡単にそれっぽい配色ができるツールを作ってみた
  5. 新卒文系エンジニアの記録:配属半年間の失敗を振り返ってみた
  6. 非同期処理のすすめ
  7. ioDrive2の導入で支える、そのIOPS - 導入検討編.
  8. GoでパイプラインからSlackに通知する
  9. fuse でオレオレファイルシステムを作ってみた (Haskell で)
  10. Erlang はじめました
  11. ちょっと地味なビルドとリリースの話 (レガシーシステム改革、はじめの一歩)
  12. Java8 最速 boolean[] to Stream 選手権
  13. Google Apps の Directory API にてWebブラウザを介さずに認証する
  14. 風データをビジュアルに表現する
  15. マイクロフレームワーク「Ninja」を使ってみる
  16. 赤ちゃんvimmerからよちよちvimmerにクラスチェンジを果たすためのTips
  17. PostgreSQL FDW を作ってSQLでログ検索してみた
  18. Goで偽名ジェネレータを作りました
  19. 書き込み中に削除されたファイルを救出する
  20. 運用情報更新のススメ
  21. ちゃんと読んでくれましたか?
  22. Presto コネクターを実装する 第三回
  23. Ruby2.3を触ってみる
  24. Git 困ったときのtips集
  25. 5分で読む入門編:Java 8 ラムダ式 コレクション編(2)リストの検索
  26. CloudFront (+ S3) + JWPLAYER で様々なデバイスのブラウザから動画をストリーミング再生する