UNIX 6th code reading - unix/namei.c

はじめに

今回はunix/namei.cを見ていきます。

システムの中で、ファイルがどのように扱われているかが見えてくると思います。

namei( )

以前のエントリで書いたように、ファイルはディレクトリによって木構造として管理されています。

パスを使うことで、各ファイルを指定できます。例えば絶対パスを使って/home/hoge/fuga.txt、または相対パスを使ってhoge/fuga.txtなどのように指定することができます。

namei( )の仕事はパスを解析して、該当ファイル・ディレクトリのinodeを取得することです。処理のおおまかな流れはこんな感じです。

  1. 与えられたパスの先頭を見て、'/'ならば絶対パスと判断してroot directoryを探索開始ディレクトリにし、それ以外ならば相対パスと判断してカレントディレクトリを探索開始ディレクトリとする
  2. ディレクトリ内のエントリを探索し、パスの先頭要素名(home/hoge/fuga.txtの場合'home')に合致するファイル・ディレクトリを探す。見つけたら、iget( )を使ってそのエントリが示すinode#のinodeを取得する
  3. パスのポインタを進め(home/hoge/fuga.txtだったらhoge/fuga.txt)、2.に戻る


上記をパスの終端が来るまで続けます。

絵で描くとこんな感じです。

ディレクトリの各エントリの構成はこのようになっているということを思い出してください。

inode#(2bytes) filename(14bytes)

ディレクトリを辿るときに、以下のチェックも行います。

  • パスの不正チェック
    • ex1:/home/hoge/fuga.txtというパスが与えられたが、/home/hogeがファイルである場合Not Directory Error
    • ex2:/home/hoge/fuga.txtというパスが与えられたが、ファイルやディレクトリが存在しない場合No Entry Error
  • アクセス権限チェック(パーミッションチェック)。パーミッションはinodeのi_modeで管理されている(IREAD, IWRITE, IEXEC)
    • ディレクトリの実行権限(x)がなければ、そのディレクトリのエントリを探索することができない
    • namei( )には引数flagが与えられ、パスで表されているファイルを探しているのか、生成しようとしているのか、削除しようとしているのか、が伝えられる。該当ディレクトリ・ファイルがそれに対応したパーミッションを持っていないと、namei( )によってinodeを取得することが出来ない


パスから該当inodeへの変換をnamei( )のみが担うことで、上記チェックが容易になっていると思います。

さらに、user構造体(u)の情報が更新されます。

  • u.u_pdir : 新たにファイル・ディレクトリを生成しようとしているときにのみ、そのファイルの親ディレクトリのinodeが格納される。/home/hoge/fugaというファイルを生成しようとしているならば、/home/hogeのinodeが格納される
  • u.u_offset : 新たにファイル・ディレクトリを生成しようとしているときにもに、そのファイルの親ディレクトリの空エントリを指すディレクトリ中のオフセットが格納される。その空エントリに新たにファイル・ディレクトリを追加する
  • u.u_dbuf : ファイル・ディレクトリ名が格納される。/home/hoge/fugaというパス名ならば、u.u_dbufには'fuga'が格納される


これらの更新されたu情報を使って処理を行う関数も多く、それらの関数は事前にnamei( )を呼び出していることが前提となっています。(はっきり言って、これは非常にわかりづらい設計で、バグの温床になると思います)

まとめ : namei( )の役割

  • パス名に対応したinodeを取得
  • パス名の不正チェック
  • アクセス権限チェック
  • user構造体の更新

schar( ), uchar( )

namei( )中では、パス名をschar( )(7679行目)かuchar( )(7689行目)を使って取得します。
schar( ), uchar( )の違いは、パス名がカーネル空間のどこかに保存されているか、それとも、ユーザ空間のどこかに保存されているか、の違いです。
言い換えると、namei( )がカーネルから呼ばれているか、ユーザからシステムコール経由で呼ばれているかの違いです。

システムコール経由で呼ばれる場合、trap( )中でu.u_dirpにu.u_arg[0]を格納します。u.u_arg[0]はシステムコールに対する引数で、パス名を差すポインタが入っています。
uchar( )はこのu.u_dirpを使用し、fubyte( )を使ってパス名を差すポインタを1文字分ずつずらしながら、1文字ずつ返します。

コードメモ

namei( )
  • arguments
    • func : &ucharか&scharか。パス名がユーザ空間にあるかカーネル空間にあるか
    • flag : パス名が示すinodeを探したいのか、生成したいのか、削除したいのか
  • 7531-7533 : パス名の先頭を見て、'/'だったらdpにroot directoryを格納。そうでなければu.u_cdir(カレントディレクトリ)を格納。ここでdpは探索開始inode
  • 7534 : iget( )を読んで、inodeのロックが開くのを待ち、関連するファイルシステムがマウントされているのを確認し、参照カウンタをインクリメントし、inodeをロックする(Lions本404P参照)
  • 7535-7536 : パス名中のポインタが'/'を指していたらそれを無視し、ポインタを'/'の次の文字まで進め、それをcに格納する。この時点で例えばパス名が////home/fugaだったらcにはhが入っていて、パス名がfoo/varだったらcにはfが入っている
namei( )
  • arguments
    • func : &ucharか&scharか。パス名がユーザ空間にあるかカーネル空間にあるか
    • flag : パス名が示すinodeを探したいのか、生成したいのか、削除したいのか
  • 7531-7533 : パスの先頭を見て、'/'だったらdpにroot directoryを格納。そうでなければu.u_cdir(カレントディレクトリ)を格納。ここでdpは探索開始inode
  • 7534 : iget( )を読んで、inodeのロックが開くのを待ち、関連するファイルシステムがマウントされているのを確認し、参照カウンタをインクリメントし、inodeをロックする(Lions本404P参照)
  • 7535-7536 : パス名中のポインタが'/'を指していたらそれを無視し、ポインタを'/'の次の文字まで進め、それをcに格納する。この時点で例えばパス名が////home/fugaだったらcにはhが入っていて、パス名がfoo/varだったらcにはfが入っている
  • 7537-7540 : このif文中のc=='\0'は、パス名が'/'や'///'(正規表現で言うと'/+')とき真になる。root directoryは、読むのはokだが、生成したり削除するのは許されないということ
  • 7542 : cloopはパス名のu.u_dirpが差している1要素を取り出して色々処理をするロジックの先頭箇所を表す。この時点でdpには最も最近処理した要素のinodeが格納されている
  • 7548-7549 : エラーがあったらoutへ飛んでエラー処理。u.u_errorにはiget( )などで値(=error code)格納され得る
  • 7550-7551 : パス名の終端まで来ていたら、dpを返す。このdpが、例えばパス名が/home/hoge/fugaの場合、fuga該当するinode
  • 7559-7562 : dpがディレクトリでなければエラー処理。例えば/home/hoge/fugaというパス名に対し、hogeディレクトリでない場合などにエラー
  • 7563-7564 : dpに実行権限がなければエラー処理。例えば/home/hoge/fugaというパス名に対し、hogeディレクトリに実行権限がなければエラー
  • 7570-7577 : u.u_dbufにdpに入っている要素の次の要素名を詰める。/home/hoge/fugaとうパス名で、dpにhomeのinodeが入っている場合、u.u_dbufには'hoge'が格納される。u.u_dbufはDIRSIZ(=14, param.hで定義)文字しか格納できないので、14文字以降は無視される。14文字に満たない場合は、残りの文字はnull(\0)で埋められる。
  • 7578-7579 : 多重'/'は無視
  • 7580-7581 : u.u_errorに値が格納されていたらエラー処理。u.u_errorにはuchar( )で値が格納されうる
  • この時点で以下の状態になっている。パス名として/home/hoge/fugaが与えられていたとして、dpにhomeに該当するinodeが格納されていた場合、u.u_dbufには'hoge\0\0\0...'が格納されていて、パス名のポインタは'fuga'の先頭の'f'を指している
  • 7585-7590 : dpはディレクトリ(のinode)であり、これからdpディレクトリ中のエントリからu.u_dbufに格納された要素名に該当するものを探す。そのための初期設定をここで行っている。u.u_countはディレクトリ中のエントリ数。ディレクトリのファイルサイズはエントリ数 x 16(Bytes)なので、ファイルサイズを16(DIRSIZ+2)で割ればエントリ数を算出できる
  • 7592 : eloop.ディレクトリ中の1要素を調べるロジックの先頭
  • 7600 : ディレクトリ中のエントリを全て探し終わったが、u.u_dbufに格納されている文字列の名前を持つエントリが見つからなかったら。u.u_countはエントリを一つ辿るごとにデクリメントされる(7639行目)
    • 7601-7602 : bpがブロックバッファを持っていたら、そのブロックバッファを解放。bpは7625行目でブロックバッファが格納されうる
    • 7603-7603 : flagが1(ファイル・ディレクトリを生成しようとしている)で、パス名のポインタが最終端まで来ていたら。例えば、/home/hoge/fugaというパス名が与えられ、dpにはhogeに該当するinodeが格納され、u.u_dbufには'fuga\0\0...'が格納され、パス名のポインタが最終端('fuga'の後)まで来ている状態。/home/hogeディレクトリ中にfugaというファイル・ディレクトリを作成しようとしているところ
      • 7604-7605 : dp(ファイル・ディレクトリを新たに追加しようとしているディレクトリのinode)に書き込み属性(パーミッションのw)がなければエラー
      • 7606 : u.u_pdirにdpを格納。このu.u_pdirは別の関数で使用される
      • 7607-7609 : もしeoが0でなければ、u.u_offset[1]にeoから16(Bytes)引いた値を格納する。eoはdpディレクトリ中の空エントリの次のエントリを指しているので、そこから16(1エントリ分)を引くと、空エントリ(の先頭bytes)を指すようになる。そうでなければ(eoが0, つまりディレクトリエントリチュに空エントリがない)、dpのinode更新フラグを立てる。この更新フラグはなんのために立てられる??(なぜ立てられる??更新されていないのに)
      • 7610 : return
    • 7612 : ディレクトリ中のエントリで、読もうとしている、もしくは削除しようとしているエントリ、もしくはファイルを生成しようとしているがパス名の途中の要素(/home/hoge/fugaのhoge)が見つからなければENOENT(No entry, エントリが見つからない)エラー
  • 7622 : u.u_offset[1]が512bytes境界を指していたら。あるディレクトリのエントリ探索初回時に必ず実行されることに注意
    • 7623-7624 : bpが既にブロックバッファを持っていたら、それを解放
    • 7625-7626 : bread( )を使って、次のブロックを読み出す
  • 7636 : bcopy( )を使って、ブロックバッファからディレクトリの1エントリ分をu.u_dentにコピー。u.u_dent.u_inoがi number, u.u_dent.u_nameがファイル・ディレクトリ名に該当
  • 7638 : u.u_offset[1]に16(1エントリ分のサイズ)を足す。つまり1エントリを辿るごとにu.u_offset[1]は16増える
  • 7639 : u.u_countをデクリメント。つまり1エントリ辿るごとに、u.u_countは1減る
  • 7640 : u.u_dent.u_u_inoが0ならば。つまり、空きエントリならば
    • 7641-7643 : eoが0なら、eoにu.u_offset[1]を格納する。eoには、そのディレクトリ中の先頭空エントリの次のエントリの先頭オフセットが格納される。eloopに戻って次のエントリのチェックを行う
  • 7645 : u.u_dbufに格納されている文字列とu.u_dent.u_nameの文字列を比較し、一致していなければeloopに戻って次のエントリのチェックを行う。u.u_dent.u_name - u.u_dbufはu.u_dent.u_nameとu.u_dbufのアドレスの差分を算出しており、*cp(u.u_dbuf中のx番目の文字を指している)にこれを足すとu.u_dbufのx番目の文字を指すことができる
  • dpディレクトリ中に、u.u_dbufに該当するエントリが見つかった状態
  • 7655-7656 : bpがブロックバッファを持っていたら、それを解放。(bpがNULLなケースってあるのか?)
  • 7657-7661 : ファイル・ディレクトリを削除しようとしていて、パス名の終端まで辿っていれば、そのファイル・ディレクトリの親ディレクトリに書き込み権限(パーミッションのw)があるかチェックし、権限があればdpをreturn。/home/hoge/fugaというパス名が与えられていたとして、fugaを削除しようとしているとすると、hogeディレクトリの下にfugaというファイル・ディレクトリがあるのが確認でき、hogeに書き込み権限がある場合にhogeのinode pointerをreturnする。namei( )の呼び出し元では、このreturnされた親ディレクトリ中の該当エントリのi_numberをクリアすることで、該当ファイル・ディレクトリの削除を行う
  • 7662-7666 : dpが指すinodeをiput( )により解放し、新たにdpには先ほどディレクトリ中で発見したエントリに該当するinodeをiget( )によって格納する。iget( )に失敗したらNULLをreturn
  • 7667 : cloopに戻る

終わりに

ファイルはパスで管理されており、namei( )を見たことでどのようにパスからファイル・ディレクトリにマッピングされるかが理解できました。

また、パーミッションがどのように実現されているかも確認が出来ました。権限がないと、そのファイル・ディレクトリのinodeが取得できないのです。

次回はunix/subr.cを解説予定です。