|
|
nono 1.3.0
//
// nono
// Copyright (C) 2020 nono project
// Licensed under nono-license.txt
//
//
// UFS 読み込み
//
#include "ufs.h"
#include "ffs_dir.h"
#include "scsidev.h"
//
// ファイルシステムハンドルっぽいもの
//
// コンストラクタ
Filesys::Filesys(Device *parent_)
{
parent = parent_;
fs = &fs0;
}
// デストラクタ
Filesys::~Filesys()
{
}
// パーティションを FFS としてオープンする(マウントのようなイメージ)。
// start, size はこのパーティションの開始セクタとセクタ数。
// 成功すれば true を返す。
// 失敗すれば errstr にエラーメッセージをセットして false を返す。
bool
Filesys::Mount(SCSIDisk *hd_, uint32 start, uint32 size)
{
hd = hd_;
part_start = start;
part_size = size;
// FFS の先頭にはブートブロックがあり、続けてスーパーブロックがある。
// ブートブロックはブートローダのための領域で、ここもファイルシステムの
// 内側という認識のようだ。
//
// ブートブロックの大きさはおそらくファイルシステムごとに固定で、
// FFSv1(?) なら 8KB のようだ。ここは LUNA-I の ROM が解釈できるのと
// 同じファイルシステムだけ扱えればよいので、8KB 固定とする。
// 続いてスーパーブロックがある。ここにこのファイルシステムのパラメータ
// が書いてある。
//
// +-------------+ 0 <- part_start (パーティション先頭)
// | BootBlock |
// +-------------+ 8KB
// | SuperBlock |
// | fs_iblkno ---+
// +-------------+ |
// : : |
// |
// : : |
// +-------------+ <-+
// | InodeBlock? |
//
// スーパーブロックの構造は <ufs/ffs/fs.h> の struct fs 参照。
// スーパーブロックを読む (ここはまだ PeekBlock() は使えない)
hd->Peek(&fs0, part_start * 512 + 8192, sizeof(fs0));
// 読み込んだスーパーブロックはゲストエンディアン(BE) なので
// 使う前に (もう面倒なのでほぼ全部) ホストエンディアンにしておく。
// ヘッダに定義してあるマクロをそのまま使いたいからだけど、正気かなこれ。
fs->fs_firstfield = be32toh(fs->fs_firstfield);
fs->fs_sblkno = be32toh(fs->fs_sblkno);
fs->fs_cblkno = be32toh(fs->fs_cblkno);
fs->fs_iblkno = be32toh(fs->fs_iblkno);
fs->fs_dblkno = be32toh(fs->fs_dblkno);
fs->fs_old_cgoffset = be32toh(fs->fs_old_cgoffset);
fs->fs_old_cgmask = be32toh(fs->fs_old_cgmask);
fs->fs_old_time = be32toh(fs->fs_old_time);
fs->fs_old_size = be32toh(fs->fs_old_size);
fs->fs_old_dsize = be32toh(fs->fs_old_dsize);
fs->fs_ncg = be32toh(fs->fs_ncg);
fs->fs_bsize = be32toh(fs->fs_bsize);
fs->fs_fsize = be32toh(fs->fs_fsize);
fs->fs_frag = be32toh(fs->fs_frag);
fs->fs_minfree = be32toh(fs->fs_minfree);
fs->fs_old_rotdelay = be32toh(fs->fs_old_rotdelay);
fs->fs_old_rps = be32toh(fs->fs_old_rps);
fs->fs_bmask = be32toh(fs->fs_bmask);
fs->fs_fmask = be32toh(fs->fs_fmask);
fs->fs_bshift = be32toh(fs->fs_bshift);
fs->fs_fshift = be32toh(fs->fs_fshift);
fs->fs_maxcontig = be32toh(fs->fs_maxcontig);
fs->fs_maxbpg = be32toh(fs->fs_maxbpg);
fs->fs_fragshift = be32toh(fs->fs_fragshift);
fs->fs_fsbtodb = be32toh(fs->fs_fsbtodb);
fs->fs_sbsize = be32toh(fs->fs_sbsize);
//fs->fs_spare1[2]
fs->fs_nindir = be32toh(fs->fs_nindir);
fs->fs_inopb = be32toh(fs->fs_inopb);
fs->fs_old_nspf = be32toh(fs->fs_old_nspf);
fs->fs_optim = be32toh(fs->fs_optim);
fs->fs_old_npsect = be32toh(fs->fs_old_npsect);
fs->fs_old_interleave = be32toh(fs->fs_old_interleave);
fs->fs_old_trackskew = be32toh(fs->fs_old_trackskew);
fs->fs_id[0] = be32toh(fs->fs_id[0]);
fs->fs_id[1] = be32toh(fs->fs_id[1]);
fs->fs_old_csaddr = be32toh(fs->fs_old_csaddr);
fs->fs_cssize = be32toh(fs->fs_cssize);
fs->fs_cgsize = be32toh(fs->fs_cgsize);
fs->fs_spare2 = be32toh(fs->fs_spare2);
fs->fs_old_nsect = be32toh(fs->fs_old_nsect);
fs->fs_old_spc = be32toh(fs->fs_old_spc);
fs->fs_old_ncyl = be32toh(fs->fs_old_ncyl);
fs->fs_old_cpg = be32toh(fs->fs_old_cpg);
fs->fs_ipg = be32toh(fs->fs_ipg);
fs->fs_fpg = be32toh(fs->fs_fpg);
//struct csum fs_old_cstotal
//u_char fs_fsmnt[MAXMNTLEN]
//u_char fs_volname[MAXVOLLEN]
fs->fs_swuid = be64toh(fs->fs_swuid);
fs->fs_pad = be32toh(fs->fs_pad);
fs->fs_cgrotor = be32toh(fs->fs_cgrotor);
//void *fs_ocsp[NOCSPTRS]
//struct csum *fs_csp
//uint32_t *fs_maxcluster
//u_char *fs_active
fs->fs_old_cpc = be32toh(fs->fs_old_cpc);
fs->fs_maxbsize = be32toh(fs->fs_maxbsize);
fs->fs_journal_flags = be32toh(fs->fs_journal_flags);
//uint64_t fs->fs_journallocs[4]
fs->fs_quota_magic = be32toh(fs->fs_quota_magic);
//uint64_t fs->fs_quotafile[2]
//uint64_t fs->fs_sparecon64[9]
fs->fs_sblockloc = be64toh(fs->fs_sblockloc);
//struct csum_total fs_cstotal
fs->fs_time = be64toh(fs->fs_time);
fs->fs_size = be64toh(fs->fs_size);
fs->fs_dsize = be64toh(fs->fs_dsize);
fs->fs_csaddr = be64toh(fs->fs_csaddr);
fs->fs_pendingblocks = be64toh(fs->fs_pendingblocks);
fs->fs_pendinginodes = be32toh(fs->fs_pendinginodes);
// uint32_t fs->fs_snapinum[FSMAXSNAP]
fs->fs_avgfilesize = be32toh(fs->fs_avgfilesize);
fs->fs_avgfpdir = be32toh(fs->fs_avgfpdir);
fs->fs_save_cgsize = be32toh(fs->fs_save_cgsize);
// uint32_t fs->fs_sparecon32[26]
fs->fs_flags = be32toh(fs->fs_flags);
fs->fs_contigsumsize = be32toh(fs->fs_contigsumsize);
fs->fs_maxsymlinklen = be32toh(fs->fs_maxsymlinklen);
fs->fs_old_inodefmt = be32toh(fs->fs_old_inodefmt);
fs->fs_maxfilesize = be64toh(fs->fs_maxfilesize);
fs->fs_qbmask = be64toh(fs->fs_qbmask);
fs->fs_qfmask = be64toh(fs->fs_qfmask);
fs->fs_state = be32toh(fs->fs_state);
fs->fs_old_postblformat = be32toh(fs->fs_old_postblformat);
fs->fs_old_nrpos = be32toh(fs->fs_old_nrpos);
// uint32_t fs->fs_spare5[2]
fs->fs_magic = be32toh(fs->fs_magic);
// マジック
if (fs->fs_magic != FS_UFS1_MAGIC) {
// マジックが違った場合、どう違うか分かる分には表示したい。
if (fs->fs_magic == FS_UFS1_MAGIC_SWAPPED) {
errstr = "Byte-swapped FFSv1 not supported";
return false;
}
// FFSv2。マジックフィールドで v1/v2 を区別できるのにそもそも
// スーパーブロックの位置が違う…。
struct fs fs2;
hd->Peek(&fs2, part_start * 512 + 65536, sizeof(fs2));
if (fs2.fs_magic == FS_UFS2_MAGIC) {
errstr = "FFSv2 not supported";
return false;
}
if (fs2.fs_magic == FS_UFS2_MAGIC_SWAPPED) {
errstr = "FFSv2 (and byte-swapped) not supported";
return false;
}
// どれでもなければとりあえず生値を表示
errstr = string_format("Bad superblock magic 0x%08x (!= 0x%08x)",
fs->fs_magic, FS_UFS1_MAGIC);
return false;
}
// なんとなく主要なパラメータを表示してみるか
if (parent->loglevel >= 2) {
uint32 sblkno = fs->fs_sblkno; // super block in filesys
uint32 cblkno = fs->fs_cblkno; // cylinder group block in filesys
uint32 iblkno = fs->fs_iblkno; // inode block in filesys
uint32 dblkno = fs->fs_dblkno; // data block in filesys
uint32 bsize = fs->fs_bsize; // size of basic blocks
uint32 ipg = fs->fs_ipg; // inodes per group
uint32 fpg = fs->fs_fpg; // blocks per group x fs_frag
uint32 old_cgoffset = fs->fs_old_cgoffset; // cg. offset in cyl.
uint32 fsbtodb= fs->fs_fsbtodb; // shift constant
parent->putmsgn("%s fs.fs_sblkno=0x%x (0x%x)",
__func__, sblkno, sblkno * 512);
parent->putmsgn("%s fs.fs_cblkno=0x%x (0x%x)",
__func__, cblkno, cblkno * 512);
parent->putmsgn("%s fs.fs_dblkno=0x%x (0x%x)",
__func__, dblkno, dblkno * 512);
parent->putmsgn("%s fs.fs_iblkno=0x%x (0x%x)",
__func__, iblkno, iblkno * 512);
parent->putmsgn("%s fs.fs_bsize=0x%x", __func__, bsize);
parent->putmsgn("%s fs.fs_ipg=0x%x", __func__, ipg);
parent->putmsgn("%s fs.fs_fpg=0x%x", __func__, fpg);
parent->putmsgn("%s fs.fs_old_cgoffset=0x%x", __func__, old_cgoffset);
parent->putmsgn("%s fs.fs_fsbtodb=%d", __func__, fsbtodb);
}
return true;
}
// ファイル名からファイルをオープンする。
// オープンできれば file を埋めて true を返す。
// オープンできなければ errstr にエラーメッセージをセットして false を返す。
bool
Filesys::OpenFile(inodefile& file, const std::string& filename_)
{
std::string filename(filename_);
std::vector<std::string> path;
int idx = 0;
inodefile dir(parent);
enum {
STATE_REG, // 通常ファイル (終了条件)
STATE_PATH, // パス解析
STATE_DIR, // ディレクトリ読み込み
STATE_FIND, // ディレクトリからファイルを検索
STATE_LINK, // シンボリックリンク
} state;
// DKFILE の先頭に '/' がなければここで足しておく。
// ファイル名を分解する際にルートディレクトリマークが必要なので。
if (filename[0] != '/') {
filename.insert(0, 1, '/');
}
state = STATE_PATH;
while (state != STATE_REG) {
switch (state) {
case STATE_PATH:
// ファイル名をパスに分解
// filename にパス名をセットしてからここに来ること。
parent->putmsgf(1, lstr("%s Path filename=|%s|",
lam_func, filename.c_str()));
if (filename.empty()) {
errstr = "Filename empty?";
return false;
}
path.clear();
for (int s = 0; s < filename.size(); ) {
int p = filename.find('/', s);
if (p == std::string::npos) {
p = filename.size();
}
// '/' が連続していなければパスコンポーネント
if (p != s) {
path.push_back(filename.substr(s, p - s));
}
s = p + 1;
}
idx = 0;
if (parent->loglevel >= 1) {
for (int i = 0; i < path.size(); i++) {
parent->putmsgn("%s path[%d]=|%s|",
__func__, i, path[i].c_str());
}
}
// パスが '/' から始まっていればルートディレクトリの読み込みから。
// そうでなければ現在のディレクトリでファイルの検索から。
if (filename[0] == '/') {
// ルートディレクトリの inode 番号は伝統的に #2 と
// いうことになっている。
dir.ino = 2;
state = STATE_DIR;
} else {
state = STATE_FIND;
}
break;
case STATE_DIR:
// inode 番号からディレクトリエントリを取得。
// dir.ino をセットしてからここに来ること。
parent->putmsgf(1, lstr("%s Readdir ino=%d", lam_func, dir.ino));
Readi(dir);
// 中身(ディレクトリエントリ) を取得
ReadData(dir);
state = STATE_FIND;
break;
case STATE_FIND:
// ディレクトリエントリ dir.data から
// ファイル名 path[idx] を探す。
parent->putmsgf(1, lstr("%s FindFile %s",
lam_func, path[idx].c_str()));
file.ino = dir.FindFile(path[idx]);
if (file.ino == 0) {
errstr = string_format("file not found");
return false;
}
// ファイルの inode 情報を取得。
Readi(file);
switch (be16toh(file.di.di_mode) & IFMT) {
case IFREG: // 通常ファイル
state = STATE_REG;
break;
case IFLNK: // シンボリックリンク
state = STATE_LINK;
break;
case IFDIR: // ディレクトリ
dir.ino = file.ino;
idx++;
// パスの最後のコンポーネントがまだディレクトリならエラー
if (idx >= path.size()) {
errstr = "is a directory";
return false;
}
state = STATE_DIR;
break;
default: // それ以外は対応してないし来ないはず
errstr = string_format("invalid filemode %06o",
be16toh(file.di.di_mode));
return false;
}
break;
case STATE_LINK:
// リンク先ファイル名を取り出す
parent->putmsgf(1, lstr("%s Readlink", lam_func));
ReadData(file);
filename = std::string((const char *)file.data.data(),
file.data.size());
// パス解析へ
state = STATE_PATH;
break;
default:
VMPANIC("corrupted state=%d", (int)state);
}
}
// 通常ファイルに到達
if (idx != path.size() - 1) {
// パスの最後のコンポーネントじゃなければエラー
errstr = "path not found";
return false;
}
return true;
}
// inode(.di) で指定されたファイル(またはディレクトリ)の本文を
// inode.data に返す。
// 今の所エラーは起きない。
void
Filesys::ReadData(inodefile& inode)
{
// di (ufs1_dinode) にはこの inode (ファイルまたはディレクトリ)の
// データの格納ブロックが記録されている。ディレクトリとは中身が
// ディレクトリエントリであるファイルと同じ構造のもの。
// di_db[] (直接ブロック)
// di_ib[] (間接ブロック)
// di_blocks (実際に使用しているブロック数)
//
// 直接ブロック di_db[] は12要素の配列で、データブロックの位置が
// 記録されている (未使用なら 0 のようだ)。
// データブロックは fs->fs_bsize でたぶん基本 4KB のやつ。
// これもパーティション先頭からのセクタ番号で表記される。なので
// ディスク上で連続しているファイルなら di_db[N] と di_db[N + 1] は
// 8 ずつ増えてるのが見えるはず。
// 例えば以下のサンプル断片は、直接ブロック2つでセクタ 100 から
// セクタ 115 までの連続する 16KB を所有している様子を示している。
//
// #100 #101 #102 #107
// +------+------+------+- .. -+------+
// di_db[0] = 100 ---> | Data | Data | Data | | Data |
// +------+------+------+- .. -+------+
//
// #108 #115
// +------+------+------+- .. -+------+
// di_db[1] = 108 ---> | Data | Data | Data | | Data |
// +------+------+------+- .. -+------+
//
// 間接ブロック di_ib[0], [1], [2] はそれぞれ一次、二次、三次の
// 間接記録になっている。一次間接だとこういう感じ。
//
// #120
// +--------+ +------+- ..
// di_ib[0] = 120 ---> | #200 ------> #200 | Data |
// | | +------+- ..
// | #208 ------> #208 | Data |
// +--------+ +------+- ..
//
uint32 ino = inode.ino;
struct ufs1_dinode *di = &inode.di;
std::vector<uint8>& data = inode.data;
uint64 bsize64 = (uint64)fs->fs_bsize;
uint64 di_size = be64toh(di->di_size);
uint32 di_blocks = be32toh(di->di_blocks);
// di->di_db[] にファイル(またはディレクトリ)の内容の位置が並んでいる
if (parent->loglevel >= 2) {
parent->putmsgn("%s di[%d].di_blocks=%u", __func__, ino, di_blocks);
std::string dbstr;
for (int i = 0; i < UFS_NDADDR; i++) {
dbstr += string_format(" %u", be32toh(di->di_db[i]));
}
parent->putmsgn("%s di[%d].di_db[]=%s, di_ib[]=%u %u %u",
__func__, ino, dbstr.c_str() + 1,
be32toh(di->di_ib[0]),
be32toh(di->di_ib[1]),
be32toh(di->di_ib[2]));
}
uint64 offset = 0; // 次に読み込む位置 (ここまでに読み込んだバイト数)
// サイズが分かっているのであらかじめ確保しておく
data.resize(di_size);
// 直接ブロック
for (int i = 0; i < countof(di->di_db); i++) {
uint64 remain = di_size - offset; // 残りバイト数
uint32 nbytes = std::min(remain, bsize64); // 今回読み込み分
PeekBlock(&data[offset], be32toh(di->di_db[i]), nbytes);
offset += nbytes;
if (offset >= di_size) {
return;
}
}
// 一次間接ブロック
std::vector<uint32> ib0buf(fs->fs_bsize / sizeof(uint32));
PeekBlock(&ib0buf[0], be32toh(di->di_ib[0]), fs->fs_bsize);
if (parent->loglevel >= 2) {
std::string distr;
for (const auto& x : ib0buf) {
if (x == 0)
break;
distr += string_format(" %u", be32toh(x));
}
parent->putmsgn("%s di[%d].di_ib[0]=%s",
__func__, ino, distr.c_str() + 1);
}
for (const auto& x : ib0buf) {
uint64 remain = di_size - offset; // 残りバイト数
uint32 nbytes = std::min(remain, bsize64); // 今回読み込み分
PeekBlock(&data[offset], be32toh(x), nbytes);
offset += nbytes;
if (offset >= di_size) {
return;
}
}
// 二次、三次ブロックは未対応
if (offset < di_size) {
PANIC("di_ib[1] not supported");
}
}
// inode.ino で指定された disk inode 情報を inode.di に返す。
// 今の所エラーは起きない。
void
Filesys::Readi(inodefile& inode)
{
// di は inode のディスク上の記録構造で 128 バイト長。
// 詳細は <ufs/ufs/dinode.h> の ufs1_dinode 参照。
// 基本的には inode ブロックにこの dinode が配列のように並んでいるが
// シリンダグループを越えるためには謎のマクロを使う。
// sys/lib/libsa/ufs.c 参照。
//
// dinode のうち、ここで関係するかも知れないのは
// di_mode (のうちファイルモード)
// di_size (ファイルサイズ)
// di_db[] (直接ブロック)
// di_ib[] (間接ブロック)
// di_blocks (ブロック数)
// くらいか。
uint32 ino = inode.ino;
// ino が含まれるブロックを読み込む。
// ino_to_fsba(fs, ino) が指定の ino が含まれるブロックを返す。
// ino_to_fsbo(fs, ino) はそのブロック内での ino のオフセットを返す。
// 例えば inode=2 の fsba が 0x18、fsbo が 2 なら、0x18ブロック目の
// [2] からつまり256バイト目からとなる。
std::vector<char> buf(fs->fs_bsize);
uint64 fsba = ino_to_fsba(fs, ino);
uint32 fsbo = ino_to_fsbo(fs, ino);
PeekBlock(&buf[0], fsba, buf.size());
// ino で示される inode のディスク上構造を
// (ゲストエンディアンのまま) di にコピーして持っておく。
struct ufs1_dinode *di = &inode.di;
memcpy(di, &((ufs1_dinode *)(&buf[0]))[fsbo], sizeof(*di));
uint16 di_mode = be16toh(di->di_mode);
uint16 di_nlink = be16toh(di->di_nlink);
uint64 di_size = be64toh(di->di_size);
parent->putmsgf(2,
lstr("%s di[%d].di_mode=%05o di_nlink=%d di_size=%" PRIu64,
lam_func, ino, di_mode, di_nlink, di_size));
}
// ディスクの指定ブロック b の先頭から len バイト読み出して buf に返す。
// buf は少なくとも len バイト確保してあること。
void
Filesys::PeekBlock(void *buf, uint32 b, uint32 len) const
{
// FFS_FSBTODB(fs, b) がブロック番号 b をセクタ番号に変換する。
uint32 sector = part_start + FFS_FSBTODB(fs, b);
hd->Peek(buf, sector * 512, len);
}
//
// inode 情報クラス
//
// コンストラクタ
inodefile::inodefile(Device *parent_)
{
parent = parent_;
}
// デストラクタ
inodefile::~inodefile()
{
}
// ディレクトリエントリ dirbuf から name で指定されるファイル名に対応する
// inode 番号を返す。見付からなければ 0 を返す。
uint32
inodefile::FindFile(const std::string& name)
{
// ディレクトリエントリは struct direct が可変長で並んでいる。
// ../host/ffs_dir.h (元は NetBSD の <ufs/ufs/dir.h>) 参照。
// d->d_reclen がこの1つ分のエントリの長さなので、次のエントリ位置は
// このエントリの頭から d->d_reclen を足したところになる。
// 最終エントリの d_reclen を足すとバッファ末尾になるようになっている
// ようだ。
const char *dp = (const char *)(data.data());
const char *dend = dp + data.size();
uint16 reclen;
for (; dp < dend; dp += reclen) {
const struct direct *d = (const struct direct *)dp;
uint32 file_ino = be32toh(d->d_fileno);
reclen = be16toh(d->d_reclen);
std::string str = string_format("ino=%u reclen=%u name=|%s|",
file_ino, reclen, d->d_name);
if (d->d_reclen == 0) {
parent->putmsgf(1, lstr("%s reclen=0 !", lam_func));
break;
}
if (strcmp(name.c_str(), d->d_name) == 0) {
parent->putmsgf(2, lstr("%s %s found", lam_func, str.c_str()));
return file_ino;
} else {
parent->putmsgf(2, lstr("%s %s skip", lam_func, str.c_str()));
}
}
parent->putmsgf(2, lstr("%s %s not found", lam_func, name.c_str()));
return 0;
}
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.