FSOperation, FAT, Fileの解析 気付いた点: ・一度に1つのファイルしか開けないのか?ただ、このソースはずいぶん古いので現在では 解決されている問題かもしれない。 ・条件判定の表記法が変。これはおそらくif(ptr = 0) {... と書いてしまうミスを なくすためだと思われる。しかし、慣れていないため読みにくい。 ・かなりメモリをケチったプログラミングをしている。つまり、引数で与えられた文字列の 途中にヌル文字を入れて文字列の一部分を取り出すというやり方をしている。懸念すべきは char* path = "/APP/NECO.ELF"; という引数のように読み込み専用領域に保存された 固定文字列が引数に来たらどうするのだろうと思う。まあ、カーネルプログラムだから 何でもできるでしょ、と楽観的に考えてしまうのだが・・・。 ・よくtmpという変数が出てくる。ネーミングセンスとしてはあまりいいほうではないと思う。 (といいつつも自分のプログラムで思いっきりこのような名前を使っているんだなw) ・cursorというよくわからないパラメータがある。ディレクトリを見つけられたかどうかを 判定するために使われるそうだが、それなら見つからなかった時点で「見つかりませんでした」 という値を返せばいいじゃないかと思う。 ・ファイルを開いた瞬間ディスクから読み出す。これっておかしくないか? --------------------------------------------------------------- FSOperation::open --------------------------------------------------------------- bool FSOperation::open(char* path, int mode) { int entry; int cursor; if (this->isOpenFlag) { this->errorNo = FS_ALREADY_OPEN_ERROR; return false; } 既にファイルが開かれていないことを確認する。もしも既にファイルが開かれていたら エラーメッセージを出して終了する。これはファイルシステムを管理するクラスが、 1つ1つのファイルを管理するオブジェクトを1つしか持っていないためである。 (ファイルのリストはプロセスに持たせればいいのに) dir = searchFile(path, &entry, &cursor); if (dir == NULL) { this->errorNo = FS_FILE_NOT_FOUND; return false; } 次に、ファイルが存在するディレクトリとディスク上のFATエントリ番号を得ている。 ここで、ファイルが見つからなかったらエラーメッセージを出して終了する。 this->file = dir->getFile(entry); if (file == NULL) { this->errorNo = FS_FILE_OPEN_ERROR; freeDirectory(dir); return false; } ファイルが見つかればそのファイルのデータをFATテーブルを参照して見ていく。 ファイルを開くのに失敗すればエラーメッセージを出して終了する。 this->mode = mode; switch(mode) { case FILE_OPEN_APPEND_WRITE: this->file->seek(0, SEEK_END); break; case FILE_OPEN_NORMAL_WRITE: break; case FILE_OPEN_READ: break; default: break; } ここでファイルを開くモードを設定している。追記書き込みか、読み込み専用かということが 設定できる。 this->isOpenFlag = true; return true; } 最後に、ファイルが開いているということを示すフラグを設定して帰る。 --------------------------------------------------------------- FSOperation::read --------------------------------------------------------------- この関数は、開いたファイルから指定された大きさのデータをバッファーに読み込む。 もし、ファイルが開かれていないまたは読み込み時にエラーが生じたらfalseを返し、 読み込みが正常に終了すればtrueを返す。 bool FSOperation::read(byte* buf, int size) { if (!isOpenFlag) { this->errorNo = FS_FILE_IS_NOT_OPEN; return false; } if (!this->file->read(buf, size)) { freeDirectory(dir); return false; } return true; } 実際の読み込み処理はFatFileクラスのread関数が担当している。 --------------------------------------------------------------- FSOperation::searchFile --------------------------------------------------------------- この関数はopen関数からpathで指定されたファイルのディレクトリを探してくる。 見つからなければNULLを返すようになっている。尚、パスは"/"で区切る。 entryには目的のファイルがあるエントリの番号が入り、cursorには どこのディレクトリまでたどることが出来たのかを示すポインタが入る。 Directory* FSOperation::searchFile (char *path, int *entry, int *cursor) { Directory *p = this->current; int index = -1; for (int i = 0; '\0' != path[i]; i++) { if ('/' == path[i]) index = i; } まず、ディレクトリは階層構造をとることが出来る。よって、パスも"/"記号が重なっているから それを一つ一つ外していかなければならない。その前処理がこのループである。このループでは "/"記号を探していって、最後のスラッシュのある場所がindexに保存される。 *cursor = 0; if (-1 != index) { path[index] = '\0'; char *dir = path; if (0 == index) dir = "/"; もしスラッシュが一番初めにあれば、それはルートディレクトリにあるファイル名と解釈される。 よってディレクトリ名を"/"としてルートディレクトリのディレクトリエントリを得るようにする。 (かなり回りくどい処理だな。) int tmp = 0; p = trackingDirectory(dir, &tmp); if (NULL == p) { this->errorNo = FS_GET_DIR_ERROR; return NULL; } if ('\0' != dir[tmp]) { this->errorNo = FS_DIR_NOT_EXIST_ERROR; freeDirectory(p); return NULL; } ディレクトリ名が確定したらそのディレクトリを読み込む。目的のファイル名を格納している ディレクトリのディレクトリエントリを得るのが目的である。もしディレクトリが開けない、 またはディレクトリが見つからなければこの段階でNULLを返す。 *cursor = index + 1; } *entry = p->searchEntry((byte*)path+*cursor); 目的のディレクトリのディレクトリエントリを得ることが出来たのでそのディレクトリエントリから 目的のファイルのエントリを得る。 return p; } --------------------------------------------------------------- FSOperation::trackingDirectory --------------------------------------------------------------- この関数はFATファイルシステムの目玉である、階層型ディレクトリを実現するためにある。 pathで示されたディレクトリを探し出し、そのディレクトリを開いて返す。もし、 ディレクトリを開くときに何らかのエラーが生じたらNULLをかえす。cursorには どこまでのディレクトリまで開けたかという情報を保持している。 Directory* FSOperation::trackingDirectory (char *path, int *cursor) { Directory *p = this->current; int i = *cursor; int j; if ('/' == path[i]) { p = this->fat->getRootDirectory(); i++; } cursorで指し示されている変数(以下、cursorの変数ということにする。)の値をiに代入している。 path[i]='/'ならばpにルートディレクトリへのポインタをセットする。思えらく、cursorの変数の 値が0でなければ正常に動作しないのでは。 次のループでは目的のファイルを格納しているディレクトリまでルートディレクトリから 一つ一つ探していっている。例えば、"/home/TAKA/mona_v1.0/src/Makefile" というファイルを探していたとするとこの関数に与えられる引数は"/home/TAKA/mona_v1.0/src" となる。このループでは"home"、"TAKA"、"mona_v1.0"、"src"というディレクトリを順番に 探し当てて、最後に目的のディレクトリを得るようにしている。 while ('\0' != path[i]) { for (j = i; '\0' != path[j]; j++) if ('/' == path[j]) break; int next = j + 1; if ('\0' == path[j]) next = j; path[j] = '\0'; char *name = path+i; ここまでの処理で名前の切り出しを終える。まず、ループに入ってきたときはポインタpath+iの 指す文字列は"home/TAKA/mona_v1.0/src"となっている。次に、for分でスラッシュまたは ヌル文字を探してそこをヌル文字に置き換えるから"home\0TAKA/mona_v1.0/src"となるため 見かけ上"home"という文字列が現れる。このとき、nextにもう一段深い位置にあるディレクトリ の名前が始まる位置を記録している。 if (0 == strcmp(name, ".")) name = ".."; else if (0 == strcmp(name, "..")) name = "..."; 名前の切り出しが終わればその名前が"."か".."であるかどうかを確かめる。もし、 そのような名前があれば特殊な処理を要する。ピリオドを余分に付け加えている理由は、 処理の途中でピリオドが1つ減るからである。 int entry = p->searchEntry((byte*)name); 次に、指定された名前のディレクトリがあるかどうかを確かめ、あればそのディレクトリを 含むエントリが何番目のエントリなのかを調べる。この処理がsearchEntry関数で戻り値が エントリの番号である。もしエントリが見つからなければ直ちに処理を中断し、 呼び出しもとに戻る必要があるが、その前に消したスラッシュを元に戻す必要がある。 if (j != next) path[j] = '/'; エントリを探す処理が終われば、もう名前を分離する必要がなくなるので元の位置に スラッシュを書き戻す。ただ、初めからスラッシュが無ければこの処理は必要ない。 if (-1 == entry) break; もし、ファイルまたはディレクトリが見つからなければこの段階で呼び出し元に戻るようにする。 Directory *tmp = p->getDirectory(entry); if (NULL == tmp) { freeDirectory(p); return NULL; } freeDirectory(p); p = tmp; ディレクトリの情報を格納しているエントリが見つかったのでそのディレクトリを読み込む。 それと同時に親ディレクトリのディレクトリ情報を消去する。 i = next; } *cursor = i; return p; } 末端までのディレクトリを読み込んだらループを終了し、呼び出し元に戻る。 --------------------------------------------------------------- FatDirectory::searchEntry --------------------------------------------------------------- この関数はカレントディレクトリの中からファイル名がbfで表わされるファイルのエントリの 番号を返す。つまり、指定されたファイルが一番初めのエントリにあったら0を返し、 その次のエントリにあったら1を返す。もし、ファイルが見つからなければ-1を返す。 int FatDirectory::searchEntry (byte *bf) { // 与えられた名前を 8.3 形式にする byte name[SIZE_FILENAME + SIZE_EXTENTION]; expandFileName(name, bf); まず、名前の中にあるピリオドを解釈して8.3形式に変更する。これで、ディレクトリエントリの 初めの11文字と比較できるようになる。 byte *tmp = entrys; 次のループではディレクトリエントリ全体が入っているバッファーentrysから1つずつエントリを 持ってきてtmpに入れている。そして、tmpの初めの11文字と比較して見事に名前が一致すれば、 エントリの番号を返すようにする。 for (int entry = 0; tmp < end; entry++, tmp += 0x20) { if (MARK_DELETE == tmp[0] ) continue; if (ATTR_VFAT == ( tmp[ATTRIBUTE] & 0x3f )) continue; if (MARK_UNUSED == tmp[0]) break; まず、tmpで示されているエントリが消去されたか、拡張FATによるものなのか、未使用なのかを チェックしている。当然このようなエントリには目的とするファイルに関する情報は無いので 読み飛ばす。 // エントリの名前を比較する bool flag = false; int i; for (i = 0; i < SIZE_FILENAME + SIZE_EXTENTION; i++) { byte c1 = name[i]; byte c2 = tmp[i]; // SJIS の場合はエスケープする if (false == flag) { if ('A' <= c1 && 'Z' >= c1) { c1 |= 0x20; } else if (0x80 & c1) flag = true; if ('A' <= c2 && 'Z' >= c2) c2 |= 0x20; } else flag = false; if (c1 != c2) break; } if (SIZE_FILENAME + SIZE_EXTENTION == i) return entry; コメント分にあるようにファイル名を比較している。ただ、大文字小文字を区別しないので、 名前にある大文字は全て小文字に直している。ただし、シフトJIS文字が使われていた場合は 次の文字の大文字小文字は区別する、ということになるのでそのような変換は行わない。 というような処理を行っている。ここで、FSOperation::trackingDirectory関数では 文字列比較関数を使用していたのに何でこの関数では関数を呼び出さずに直接処理しているの? と疑問をもつだろうが、そこには一言で言い表すことが出来ない「大人の事情」がある。 } return -1; } --------------------------------------------------------------- FatDirectory::expandFileName --------------------------------------------------------------- この関数は与えられた名前を8.3形式に変換している。ピリオドで区切るだけだから 単純な処理に見えるが、8文字以上のファイル名が入力された場合も考慮しているため 少し複雑な処理になっている。 void FatDirectory::expandFileName (byte *name, byte *bf) { int i, j, index = -1; for (i = 0; '\0' != bf[i]; i++) { if ('.' == bf[i]) index = i; } まず、このループでどこにピリオドがあるのかを探している。もし、ピリオドが複数個あれば 一番最後のピリオドの位置がindexに保存される。また、ピリオドがまったく無ければ indexには-1が格納される。尚、iには文字列全体の長さが格納される。 int num = i; if (-1 != index) num = index; if (num > SIZE_FILENAME) num = SIZE_FILENAME; つぎに、ファイル名(拡張子を含まない)の長さを決定している。ピリオドが無ければ 先ほどのiの値がファイル名の長さになるし、ピリオドがあればピリオドまでの部分が ファイル名になってそれで長さも決まる。もし、ファイル名の長さが8文字以上であれば 強制的に8文字に収める。 for (i = 0; i < num; i++) name[i] = bf[i]; while (i < SIZE_FILENAME) name[i++] = ' '; ファイル名の長さが決まったのでnameにファイル名を書いていく。ファイル名の長さが8文字に 満たない場合、余った部分はスペースで補われる。 if (-1 != index) { for (j = index+1; '\0' != bf[j]; j++) { if (i >= SIZE_FILENAME + SIZE_EXTENTION) break; name[i++] = bf[j]; } } while (i < SIZE_FILENAME + SIZE_EXTENTION) name[i++] = ' '; } 最後に、拡張子も格納していく。拡張子はピリオドから3文字までの部分が保存されるように なっている。 --------------------------------------------------------------- FatDirectory::getFile --------------------------------------------------------------- File* FatDirectory::getFile (int entry) { byte *tmp = entrys + 0x20 * entry; tmpに目的のファイルのエントリが入っている。(だからtmpはやめろっていうの) if (false == isValid(tmp)) return NULL; ここで目的のエントリが無効、例えばエントリが変なところにあるとか、空白で始まっているとか 消去済みのしるしがついているか、空白のエントリなど普通のファイルを収められないような エントリであった場合はNULLを返す。 if (tmp[ATTRIBUTE] & ATTR_DIRECTORY) return NULL; if (tmp[ATTRIBUTE] & ATTR_VOLUME) return NULL; また、目的のエントリがディレクトリを指し示していたり、ボリュームラベルだったりということも あるのでこういったエントリの除外してNULLを返す。 dword cluster = *((word*)( tmp + LOW_CLUSTER )); if (0 == cluster) cluster = END_OF_CLUSTER; エントリの0x1A=LOW_CLUSTERバイト目から2バイト分がクラスタ番号である。これを読み取って クラスタ番号を得る。クラスタ番号が0であればファイルサイズ0とみなし、クラスタの終端に あることにする。 dword size = *((dword*)( tmp + FILESIZE )); エントリの0x1C=FILESIZEバイト目から4バイト分がファイルサイズである。こんなところで 32ビットにしてもなあ、と思ったりする。まあ、これのお陰でFAT32に移行できたのかと 思ったりする。 FatFile *file = new FatFile(); if (NULL == file) return NULL; if (false == file->initialize(fat, this, entry, cluster, size)) { delete file; return NULL; } ファイルサイズと先頭クラスタがが得られればFatFileクラスを作成して初期化して、それを 返す。 return file; } --------------------------------------------------------------- FatFile::initialize --------------------------------------------------------------- FATファイルシステム上のファイルクラスを初期化する。この段階でもうファイルを読み込む。 pはFAT管理システム、dはファイルを格納しているディレクトリ、cは先頭クラスタ番号、 sはファイルサイズとなっている。 bool FatFile::initialize (FAT *p, Directory *d, int e, dword c, dword s) { dword nbytes = p->getBytesPerSector(); dword sz = ( s + nbytes - 1 ) & ~nbytes; dword sects = sz / nbytes; まず、szに読み込む大きさを指定する。フロッピーには512倍と単位で保存されているので ファイルサイズを512で切り上げる。そして、sectsが読み込むセクタの数である。 byte *ptr = new byte [ sz + sects + sects*sizeof(dword) ]; if (NULL == ptr) return false; ここで読み込みに必要なメモリを確保しておく。 // ファイルをメモリに読み込む byte *tmp = ptr; dword *tmplba = (dword*)( ptr + sz + sects ); dword i = 0; last = 0; 以下、FATを参照してファイルをクラスタごとに読み込んでいく。ここで注目すべきはディレクトリエントリの ファイルサイズを「まったく当てにして」おらず、FATが続く限り読み込んでいく。 while (p->getNumberOfClusters() > c) { dword lba = p->getLbaFromCluster(c); dword num = p->getSectorsPerCluster(); まず、クラスタcの領域を読み込むためにLBAと1クラスタあたりのセクタ数を求めている。クラスタから LBAへの変換は次の式でなされる。 lba = データ領域の開始論理セクタ番号 + (c - 2) * 1クラスタあたりのセクタ数 となる。データ領域の開始論理セクタ番号はフロッピーディスクの場合 ブート領域:1セクタ FAT 領域:9セクタ×2=18セクタ ルートディレクトリエントリ:0xE0エントリ、つまり14セクタ あわせて33セクタある。よってデータ領域の開始論理セクタ番号は33ということになる。 また、1クラスタあたりのセクタ数は1となっている。 ここからクラスタを読み込んでいく。よほどのことが無い限りサイズが実際と違うということは無いので if文は読み飛ばしてもいいと思う。 for (dword n = 0; n < num; n++) { // サイズが足りなくなったのでメモリの拡大 このコードはもしもディレクトリエントリで表明していたサイズが違っていた場合の処理である。 この場合読み込むセクタ数をRESIZE_DELTAだけ伸ばして、メモリを再び確保して確保したメモリに 今まで読み込んだ内容をコピーしている。 if (i >= sects) { sects = i + RESIZE_DELTA; sz = nbytes * sects; byte *tmpptr = new byte [ sz + sects + sects*sizeof(dword) ]; if (NULL == tmpptr) { delete[] ptr; return false; } if (0 < i) { memcpy(tmpptr, ptr, nbytes*i); memcpy(tmpptr+sz+sects, tmplba, i * sizeof(dword)); } delete[] ptr; ptr = tmpptr; tmp = tmpptr + i * nbytes; tmplba = (dword*)( ptr + sz + sects ); } if (false == p->read(lba+n, tmp)) { delete[] ptr; return false; } tmp += nbytes; // 読み込んだセクタ位置を覚えておく tmplba[i++] = lba+n; ここでフロッピーからデータを読み込んでいる。FAT::read関数はフロッピーディスクドライバに対し 読み込みを実行させているだけなのでここでは説明しないつもりである。読み込みが終われば バッファーの後方を覚えているポインタtmpを進ませて、tmplbaにも読み込んだ論理セクタ番号を 記憶させている。 } last = c; c = p->getNextCluster(c); } fat = p; parent = d; file = ptr; flag = ptr + sz; lba = tmplba; fsize = s; sectors = i; pos = 0; entry = e; sizeChanged = false; // フラグクリア clearFlag(); return true; 読み込みが終わればもろもろの変数を更新して帰る。 } --------------------------------------------------------------- FatFile::read --------------------------------------------------------------- dword FatFile::read (byte *bf, dword sz) { if (fsize < pos + sz) sz = fsize - pos; if (0 < sz) { memcpy(bf, file+pos, sz); pos += sz; } return sz; }