2009年11月20日金曜日

自分で作るネットワークストレージ (2)

ファイルシステムを作ると言っても、OSネイティブのファイルシステムを作るのは大変です。しかしながら、最近はFUSE (Filesystem in Userspace)を使って手軽にファイルシステムを試作できるようになりました。以前だったら躊躇していたところですが、気軽に試せそうです。

FUSEはOS内部で発生したファイルシステムイベントをユーザー空間のプログラムに通知し、そこで必要な操作を代行させることでOSネイティブのファイルシステムと同様の操作性をファイルシステム利用者に提供しています。イベントの処理といっても、実際はイベントに対応したコールバック関数を記述する形になりますから、特に実装が複雑なわけではありません。FUSEでは様々なイベントに対応するコールバック関数が定義されていますが、すべての関数を実装しなければならないわけではありません。たとえば、シンボリックリンクをサポートしないのであれば、シンボリックリンクに関係する関数を実装する必要はありません。とりあえず今回対応するのは以下の関数です。試作にMacOS XのXcodeテンプレートを使うので、関数名がCocoa APIっぽく書かれていますが、簡単に読み替えることができると思います。

  • ディレクトリ一覧 (contentsOfDirectoryAtPath:error:, opendir(2), readdir(2)など)
  • ファイル情報取得 (attributesOfItemAtPath:userData:error:, stat(2))
  • ファイル情報設定 (setAttributes:ofItemAtPath:userData:error:, chmod(2)など)
  • ファイルオープン (openFileAtPath:mode:userData:error:, open(2))
  • ファイルクローズ (releaseFileAtPath:userData:, close(2))
  • ファイル読み込み (readFileAtPath:userData:buffer:size:offset:error:, read(2))
  • ファイル書き込み (writeFileAtPath:userData:buffer:size:offset:error:, write(2))
  • 新規ディレクトリ作成 (createDirectoryAtPath:attributes:error:, mkdir(2))
  • 新規ファイル作成 (createFileAtPath:attributes:userData:error:, open(2))
  • ファイル移動 (moveItemAtPath:toPath:error:, rename(2))
  • ディレクトリ削除 (removeDirectoryAtPath:error:, rmdir(2))
  • ファイル削除 (removeItemAtPath:error:, unlink(2))

FUSEで読み書き可能なファイルシステムを作る場合、上記の関数を実装すればとりあえず動きます。あとは、これらのイベントの裏で、適切に版管理システムと連携させてあげればよいわけです。版管理システムが提供するインターフェースは以下のようなものになるでしょう。

  • チェックアウト (checkout)
  • 更新 (update)
  • 追加 (add)
  • 複製 (copy)
  • 移動 (move)
  • 削除 (delete)
  • コミット (commit)

今回、版管理システムとしてSubversionを念頭に置いています。ですが、Subversionと同様の概念で構築されているシステムであれば、上記のような抽象化された版管理インターフェースと実際の動作を対応させることで、差し替えもできるのではないかと思います。

FUSEが提供するファイル操作と、版管理システムが提供するファイル操作をじっと見比べてみると、お互いの機能が似ていることがわかります。FUSE側に存在せず、版管理側に存在する操作としてチェックアウトとコミット、逆にFUSEには存在し、版管理システムに存在しない操作としてはファイルのオープンやクローズ、ファイル情報の操作などがあります。これらの差を埋めれば二つのシステムが相互に繋がりそうです。Subversionを前提にがっつり実装してもよいのですが、将来の拡張性を考えて、版管理抽象層を定義し、以下のAPIを版管理抽象層からFUSEファイルシステム側に提供することにしました。

  • 初期化 (revisionControlSetup:)
  • チェックアウト (revisionControlSetup:)
  • 追加 (revisionControllAdd:)
  • 削除 (revisionControlRemove:)
  • 複製 (revisionControlCopy:)
  • 更新 (revisionControlUpdate:)
  • コミット (revisionControlCommit:)
  • タッチ (revisionControlTouch:)
  • 終了処理 (revisionControlCleanup:)
  • 実パス名取得 (revisionControlRealPathForPath:)
  • 制限ファイル名取得 (revisionControlNameIsReservedAtPath:)

いくつか、版管理システムの操作一覧にないAPIを追加しています。初期化と終了処理はよいとして、タッチ、実パス名取得、制限ファイル名取得は、手元の作業コピーでの操作と版管理されているデータを結びつけるために必要となります。

今回、版管理システムを基礎としてファイルシステムを構築するので、ファイルへの操作はオリジナルのファイルではなく、手元に作られた作業コピーに対して行われます。作業結果は、最終的に版管理システムにコミットしなければならないので、どのファイルが操作されたのか履歴を記録しておく必要があります。タッチAPIは、作業コピーで修正されたファイルを版管理抽象層に通知します。

実パス名取得APIは、版管理システム内で使われているパス名を、作業コピー上でのパス名に変換するためのAPIです。今回の試作では、版管理システム内のディレクトリ構造を、手元のPCの特定ディレクトリにマウントして使うことを想定しています。FUSEから通知されるパスはマウントされたディレクトリからの総体パスになりますが、実際に操作できるファイルは作業コピーとして取り出したファイルパスになります。作業コピーを取り出した場所は版管理抽象層しか知らないので、実際のファイルパス名を知りたい場合に抽象層に問い合わせるAPIが必要になります。

制限ファイル名取得APIは、ファイルシステムとして利用できないファイル名を判断するためのAPIです。たとえば、版管理システムとしてSubversionを使う場合、.svnという名前のファイル名、ディレクトリ名は使えません。このAPIを通じて、後ろで使われている版管理システムで禁止されているファイル名を取得します。

とりあえずFUSE関数を上述の版管理抽象層APIで実装してみると、以下のようになります。

  • (FUSE初期化関数)
    • 初期化
    • チェックアウトあるいは更新
  • ディレクトリ一覧
    • 更新
  • ファイル情報取得
    • (作業コピーのファイル情報を取得)
  • ファイル情報設定
    • (作業コピーのファイル情報を取得)
  • ファイルオープン
    • タッチ
  • ファイルクローズ
    • コミット
  • ファイル読み込み
    • (作業コピーのファイルを読み込み)
  • ファイル書き込み
    • (作業コピーのファイルに書き込み)
  • 新規ディレクトリ作成
    • (作業コピーにディレクトリ作成)
    • 追加
  • 新規ファイル作成
    • (作業コピーにファイル作成)
    • 追加
  • ファイル移動
    • (移動元を移動先に)複製
    • (移動元を)削除
  • ディレクトリ削除
    • 削除
  • ファイル削除
    • 削除
  • (FUSE終了関数)
    • コミット
    • 終了処理

まぁ、いろいろと問題はあるのですが、一応動くものにはなります。ソースコードsourceforgeに公開していますので、興味のある人は覗いてみてください。sourceforgeに登録しているコードは、上記以上の機能も実装していますが、それはまた別の機会に。

次回に続きます。

0 件のコメント: