「新ユニット結成のお知らせ + GAE上でファイルシステムの模倣」にて:
既存のファイルシステムもどきの仕様は考慮したほうが良さそうだけど、僕の要求としては、非常に素朴な(誰でもが常識と直感として持っている)ファイルシステムのメンタルモデルを表現できることかな。
めんどくさいので既存仕様の調査はしないで腹案を提示します。これから述べるファイルシステムの名前は、mafs (minimum abstract filesystem) です。mafsはプログラミング言語中立です。
とりあえず思ったまま/考えたままを以下に。
内容:
- 認証と認可
- バイナリデータ
- IOモデル
- ロッキング
- ファイル名
- カレントディレクトリ
- オーナーとパーミッション
- ユニークID、ハードリンク、シンボリックリンク
- 属性またはメタデータ
- ディレクトリ
- ファイルとディレクトリの扱い
- 例外(エラー)
- オプションや曖昧性は絞ろう
- あとはアクション
認証と認可
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 |
ファイルとディレクトリの扱い
今までに出てきたメタデータ属性を列挙すると:
- contentLength
- contentType
- created
- lastModified
- readOnly
- isText
- textEncoding
- hidden
- 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)に列挙されていました。
- eacces - permission denied
- eagain - resource temporarily unavailable
- ebadf - bad file number
- ebusy - file busy
- edquot - disk quota exceeded
- eexist - file already exists
- efault - bad address in system call argument
- efbig - file too large
- eintr - interrupted system call
- einval - invalid argument
- eio - IO error
- eisdir - illegal operation on a directory
- eloop - too many levels of symbolic links
- emfile - too many open files
- emlink - too many links
- enametoolong - file name too long
- enfile - file table overflow
- enodev - no such device
- enoent - no such file or directory
- enomem - not enough memory
- enospc - no space left on device
- enotblk - block device required
- enotdir - not a directory
- enotsup - operation not supported
- enxio - no such device or address
- eperm - not owner
- epipe - broken pipe
- erofs - read-only file system
- espipe - invalid seek
- esrch - no such process
- estale - stale remote file handle
- exdev - cross-domain link
こんなに要らないけどさ。あーそれと、HTTPステータスコード(例えば、http://www.studyinghttp.net/status_code)も考慮したほうがいいかも。
オプションや曖昧性は絞ろう
と、ここまで書いて読み返してみると、オプションや曖昧性が多いですね。仕様にオプショナルな事項があると、たいてはロクでもないことになります。もう少し考えて、なるべくオプションを減らすつもりですが、今日の所は、いくつかの候補を挙げるという意味で、オプショナルなことを書いています。
あとはアクション
これで、周辺部分は(ぼんやりとだけど)決まってきたので、実際にファイルやディレクトリを操作するアクション(オペレーション)を決めればいいわけだ。基本はCRUD(create, read, update, delete)。ファイルだからmove (rename) もあるか。
疲れたから、アクションの部分はまた次。([追記]続きは「最小抽象ファイルシステムの仕様案 その2」。[/追記])