このブログの更新は Twitterアカウント @m_hiyama で通知されます。
Follow @m_hiyama

メールでのご連絡は hiyama{at}chimaira{dot}org まで。

はじめてのメールはスパムと判定されることがあります。最初は、信頼されているドメインから差し障りのない文面を送っていただけると、スパムと判定されにくいと思います。

参照用 記事

最小抽象ファイルシステムの仕様案

「新ユニット結成のお知らせ + GAE上でファイルシステムの模倣」にて:

既存のファイルシステムもどきの仕様は考慮したほうが良さそうだけど、僕の要求としては、非常に素朴な(誰でもが常識と直感として持っている)ファイルシステムのメンタルモデルを表現できることかな。

めんどくさいので既存仕様の調査はしないで腹案を提示します。これから述べるファイルシステムの名前は、mafs (minimum abstract filesystem) です。mafsはプログラミング言語中立です。

とりあえず思ったまま/考えたままを以下に。
内容:

  1. 認証と認可
  2. バイナリデータ
  3. IOモデル
  4. ロッキング
  5. ファイル名
  6. カレントディレクト
  7. オーナーとパーミッション
  8. ユニークID、ハードリンク、シンボリックリンク
  9. 属性またはメタデータ
  10. ディレクト
  11. ファイルとディレクトリの扱い
  12. 例外(エラー)
  13. オプションや曖昧性は絞ろう
  14. あとはアクション

認証と認可

mafsは、ユーザーが正統であるかどうかは判断する認証(authentication)には関与しません。既に認証は済んでいるとして話をします。ただし、認可(authorization、アクセス制御)はmafsの仕事なので、認証により確認したユーザーのアクセス権限を象徴するデータである認可トークンは必要だとします。

認可トークンの型はAuthoriTokenで、これは、アプリケーションプログラムからは不透明(opaque)で変更不可能なデータです。AuthoriTokenの実体が何であるか(整数か文字列か、もっと複雑な構造か、とか)を知る必要はありませんし、知っちゃいけません。(当面は、OWNERとGUESTの2つの値だったりするだろうが。)

mafsのAPIの第1引数には認可トークンを入れて、これをもとにアクセス制御がされるとしましょう。したがって、mafsを呼ぶ側は認可トークンを取得可能な環境にいないとダメなわけです。とはいえ、認可トークンは何でもいいので、定数0とかに決め打ちした実装でも文句はいいません。

バイナリデータ

たいていのプログラミング言語は、バイト配列のようなバイナリデータを扱えます。文字データしか扱えないときは、Base64エンコードするとか、なんとかガムバってバイナリデータを扱えるようにしてください。

以下、Binaryは生々のバイナリデータを表す型だとします。

IOモデル

伝統的に、ファイルはopenしてcloseするストリームとして扱われます。しかし、open/closeってけっこうめんどくさいし、最近はメモリもだいぶたくさん使えるので、バイト/バイトブロック、文字/行の単位ではなくて、ファイル全体をドカッと読み書きする方式だけでもいいんじゃないの、と思います。

という事情で、ストリームから(or ストリームへ)、チマチマ読む(or 書く)モデルは採用しないことにします。

ロッキング

読み出し要求は複数のスレッドから来てもいいでしょう。mafs自体がスレッドを作るかとか、要求をキューイングするかとかは実装依存だとして、気にしないことにします。

書き込みの際はロックしないとマズイですが、いちいち明示的なロック/アンロックはめんどくさいので、もし競合したら、どれかひとつの書き込み要求が実行されて、他は失敗だとします。

書き込みが失敗したときは、それなりの例外が発生するので、失敗したことは検出できます。もちろん、書き込みは、完全に書けたか、まったく書けないかのどちらかです。書き込みの単位はファイル1個分です。

1個のファイルよりも大きな単位でのトランザクションは、、、うーん、いらないんじゃない。ファイルシステムはそこまでやらないでしょ。

ファイル名

ちゃんと考えると、ものすごくめんどくさいですよね。なので、今は決めないことにします。URI仕様かなんかの構文をそのまま採用しようかな。とりあえず、'/'が区切り記号であることくらいは仮定します。

カレントディレクト

相対パスを解釈するためには、カレントディレクトリの概念が必要ですが、ファイルシステム側でカレントディレクトリを管理するのも変な話なので、カレント概念はありません。

パスは常に絶対パス。長いパスに対する効率は悪くなるけど、そのぶん安全だからね。

オーナーとパーミッション

mafsでは、ファイルシステム全体が特定のオーナーにより占有されていると前提するので、フィイル/ディレクトリごとのオーナーは考えません。また、ファイルシステムにアクセスするユーザー情報も事前に認可トークンに変換されているので、「現在アクセスしているユーザー」という概念もありません。ユーザー概念がないので、当然にグループ概念もありません。

実装上はファイルごとにパーミッションが付随しているかもしれませんが、認可トークンとパスとアクションから何らかのメカニズムでアクセスの可否が決まるので、個々のファイルの属性としてのパーミッションは考えません。

ユニークID、ハードリンク、シンボリックリンク

inode番号のように(今ならUUID使うか)、ファイルシステム内のエンティティ(通常ファイルまたはディレクトリ)を識別するユニークIDがあると便利かも知れません。ユニークIDがあれば、ハードリンクが実装できます。

ユニークIDがなくても、シンボリックリンク(ファイル名の別名)なら、なんとでもなります。あれば確かに便利ですよね。

でも、今回は見送ることにします。ファイルシステム内に存在する名前を持つエンティティは、通常ファイルとディレクトリの2種類だけです。

属性またはメタデータ

ファイルの名前以外のメタデータは次のようなものでしょう。(タイムスタンプ型も環境やプログラミング言語によって表現は変わりりますが、少なくとも秒までは識別できる時刻値です。)

名前 値のデータ型 必須か 説明
contentLength ゼロ以上の整数 YES 内容のバイトサイズ
contentType 文字列 NO 内容のメディアタイプ
created タイムスタンプ NO ファイルが作られた時
lastModified タイムスタンプ YES ファイルが最後に変更された時
readOnly 真偽値 YES 読み取り専用かどうか

以下は、あれば便利だなと思えるメタデータ、すべてオプショナル。

名前 値のデータ型 説明
isText 真偽値 内容がテキストデータかどうか
textEncoding 文字列 内容がテキストのとき、その文字エンコーディングスキーム
hidden 真偽値 通常は見せないファイルかどうか
executable 真偽値 実行可能ファイルかどうか

isTextやtextEncodingがないと、ファイル名とか周辺の状況、内容バイトの先頭とかから推測をするしかありません。できればサポートして欲しいところ。executableは普通の用途では要らなそうですが、僕は欲しいのです。

ディレクト

ディレクトリに関してはメタデータの規約が少し変わります。

名前 必須か 説明
contentLength NO あってもよいが、たいして意味がない
contentType - 無意味
created NO ファイルと同じ意味
lastModified YES ディレクトリエントリーが最後に変更された時
readOnly YES ファイルと同じ意味
hidden NO ファイルと同じ意味
executable - 無意味*1

ファイルとディレクトリの扱い

今までに出てきたメタデータ属性を列挙すると:

  1. contentLength
  2. contentType
  3. created
  4. lastModified
  5. readOnly
  6. isText
  7. textEncoding
  8. hidden
  9. executable

このうちのいくつかはディレクトリに対して意味がありません。ファイルかディレクトリかを区別するフラグを加えて、不明または不要な属性の値は不在値(nullとかundefinedとかNoneとか)にする、ってやり方があります。

でも、なんかイヤなんだなー。ファイルとディレクトリの操作を、別な関数/メソッドに割り当て、フラグのようなデータは使わない、という方針にはできます(そうしたい)。でも、ディレクトリエントリーの一覧をデータにしたいなら、やっぱりメタデータデータ形式が必要になります。

それで、ここはユニオン型を使うことにします。僕はもう、ユニオン型(バリアント型)をバシバシ使っちゃおう、と割り切ったのですよ。

「JSONスキーマのローカル構文に関する思案・試案」で示した型定義構文を使うことにします。ただし、多少構文(つうか気分)が変わってますが、読めばわかると思います。remarkはプログラムには理解できない、人間向けの注釈です。


FileMetadata = object {
"contentLength" : integer(minimum = 0),
"contentType" : string(optional = true,
remark = "MIMEメディアタイプ"),
"created" : TimeStamp(optional = true),
"lastModified" : TimeStamp,
"readOnly" : boolean,
"isText" : boolean(optional = true),
"textEncoding" : string(optional = true,
remark = "文字エンコーディングスキーム名"),
"hidden" : boolean(optional = true, default = false),
"executable" : boolean(optional = true, default = false)
};

DirectoryMetadata = object {
"created" : TimeStamp(optional = true),
"lastModified" : TimeStamp,
"readOnly" : boolean,
"hidden" : boolean(optional = true, default = false)
};

DirectoryEntry = object {
"name" : string(remark = "ファイルのベース名"),
"metadata" :
union {
file => FileMetadata,
dir => DirectoryMetadata
}
};

DirectoryEntryList = array [ DirectoryEntry* ];

ディレクトリのときは、contentLengthの代わりに保持しているエントリーの個数とかがあると便利かな?

注意すべきは、メタデータディレクトリエントリ内に含まれるか、それともフィイルごとのヘッダに含まれるか、なんてのはどうでもいいことです。要求されたときに、名前とメタデータを並べたリストが返せればいいだけです。

例外(エラー)

ファイルシステムに対する要求が失敗したときは、例外が発生します。でも、例外機構や例外クラス(複雑な例外情報)をサポートしてないプログラミング言語もあるので、例外の種類は、整数、文字列、記号(シンボルアトム)などの列挙型で識別することにします。

もちろん、この例外の種別に付加情報を付けた、より複雑なデータ(構造体や例外オブジェクト)を投げることを禁止しません。要するに、C言語のerrnoのような方法でもかまわないということです。

さて、具体的な例外の種類ですが、POSIXのerrnoを拝借すればいいんじゃないでしょうか。

ファイルIOに関連しそうなエラーコードが、Erlangのマニュアル(http://www.erlang.org/doc/man/file.html)に列挙されていました。

  1. eacces - permission denied
  2. eagain - resource temporarily unavailable
  3. ebadf - bad file number
  4. ebusy - file busy
  5. edquot - disk quota exceeded
  6. eexist - file already exists
  7. efault - bad address in system call argument
  8. efbig - file too large
  9. eintr - interrupted system call
  10. einval - invalid argument
  11. eio - IO error
  12. eisdir - illegal operation on a directory
  13. eloop - too many levels of symbolic links
  14. emfile - too many open files
  15. emlink - too many links
  16. enametoolong - file name too long
  17. enfile - file table overflow
  18. enodev - no such device
  19. enoent - no such file or directory
  20. enomem - not enough memory
  21. enospc - no space left on device
  22. enotblk - block device required
  23. enotdir - not a directory
  24. enotsup - operation not supported
  25. enxio - no such device or address
  26. eperm - not owner
  27. epipe - broken pipe
  28. erofs - read-only file system
  29. espipe - invalid seek
  30. esrch - no such process
  31. estale - stale remote file handle
  32. exdev - cross-domain link

こんなに要らないけどさ。あーそれと、HTTPステータスコード(例えば、http://www.studyinghttp.net/status_code)も考慮したほうがいいかも。

オプションや曖昧性は絞ろう

と、ここまで書いて読み返してみると、オプションや曖昧性が多いですね。仕様にオプショナルな事項があると、たいてはロクでもないことになります。もう少し考えて、なるべくオプションを減らすつもりですが、今日の所は、いくつかの候補を挙げるという意味で、オプショナルなことを書いています。

あとはアクション

これで、周辺部分は(ぼんやりとだけど)決まってきたので、実際にファイルやディレクトリを操作するアクション(オペレーション)を決めればいいわけだ。基本はCRUD(create, read, update, delete)。ファイルだからmove (rename) もあるか。

疲れたから、アクションの部分はまた次。([追記]続きは「最小抽象ファイルシステムの仕様案 その2」[/追記]

*1:ディレクトリに対するexecutableの意味を、コジツケで無理矢理解釈することはしません。