File:  [Isaki's NoNo m68k/m88k emulator] / nono / vm / ufs.cpp
Revision 1.1.1.5 (vendor branch): download - view: text, annotated - select for diffs
Wed Apr 29 17:05:43 2026 UTC (2 months, 3 weeks ago) by root
Branches: MAIN, Isaki
CVS tags: v027, v026, v025, v024, v023, HEAD
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;
}

unix.superglobalmegacorp.com

This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.