File:  [Isaki's NoNo m68k/m88k emulator] / nono / lib / config.cpp
Revision 1.1.1.21 (vendor branch): download - view: text, annotated - select for diffs
Wed Apr 29 17:05:57 2026 UTC (2 months, 3 weeks ago) by root
Branches: MAIN, Isaki
CVS tags: v027, HEAD
nono 1.7.0

//
// nono
// Copyright (C) 2020 nono project
// Licensed under nono-license.txt
//

//
// 設定
//

#include "config.h"
#include "fdc.h"
#include "mystring.h"

// グローバル参照用
Config *gConfig;

// コンストラクタ
Config::Config()
{
	// 設定項目はすべてここで用意すること。
	// ここで追加した順に表示されるので基本的にはアルファベット順に並べる
	// (先頭のドットは考慮しなくていい)。
	// ただし vmtype だけは特別なので最初に表示されたほうがいいだろう。

	Add("vmtype", "");

	// 以下概ねアルファベット順に並べる

	// X68030 CGROM ファイルパス。なければ内蔵 CGROM。
	Add("cgrom-image", "", VMCap::X68030);

	// VM 内の時計を同期する時間軸。
	// "real" か "virtual"。初期値は各 VM クラスで設定している。
	Add("clock-sync");

	// デバッガコンソール
	Add("debugger-driver", "none");
	Add("debugger-tcp-port", 0);

	// DIPSW エイリアス
	Add("dipsw-autoboot", "", VMCap::LUNA | VMCap::NEWS);
	Add("dipsw-serial", "", VMCap::LUNA | VMCap::NEWS);

	// MAC アドレス
	Add("ethernet0-macaddr", "auto");
	Add("ethernet1-macaddr", "auto", VMCap::X68030);
	AddAlias("ethernet-macaddr", "ethernet0-macaddr");

	// 実行ファイル(-X)
	Add("exec-file");
	Add("exec-arg", "", VMCap::LUNA | VMCap::X68030);
	// RAMDISK (--initrd)
	Add("exec-initrd", "", VMCap::VIRT68K);

	// virt-m68k のブートパラメータ文字列。BootInfo の BI_COMMANDLINE で、
	// NetBSD/virt68k ではルートデバイスの決定に使っている。
	Add("exec-bootparam", "", VMCap::VIRT68K);

	// X68030 の拡張メモリサイズ
	Add("extram-size", "0", VMCap::X68030);

	// フロッピーディスク
	Add("fd-drive", 2, VMCap::X68030);
	for (int id = 0; id < FDCDevice::MAX_DRIVE; id++) {
		// 種別とイメージパス
		Add(string_format("fd%u-image", id), VMCap::X68030);
		// エミュレータ的に書き込みを禁止するか
		Add(string_format("fd%u-writeignore", id), 0, VMCap::X68030);
	}

	// 68030 の FPU の有無。
	Add("fpu-type", "68881", VMCap::VIRT68K | VMCap::X68030);

	// 高速モード。
	Add("fast-mode", "0");

	// ホスト CPU アクセラレーション。
	// host-* の方は少しの間残しておくがそのうち消す。
	Add("hostcpu-avx2", "auto");
	Add("hostcpu-neon", "auto");
	AddObsolete("host-avx2",	"hostcpu-avx2");
	AddObsolete("host-neon",	"hostcpu-neon");

	// ホスト CPU の高性能コア指定。
	Add("hostcpu-fastcore", "auto");
	// 古い隠しオプションのほうは使われたらエラーを出す。
	// こっちは単なる読み替えではないため AddObsolete() ではない。
	Add(".host-cpu-affinity",	"obsolete");

	// シリアルポートのホスト側設定。
	Add("hostcom0-driver", "none");
	Add("hostcom0-tcp-port", 9998);

	Add("hostcom1-driver", "none",	VMCap::LUNA);
	Add("hostcom1-tcp-port", 9997,	VMCap::LUNA);

	Add("hostcom2-driver", "none",	VMCap::LUNA);
	Add("hostcom2-tcp-port", 9996,	VMCap::LUNA);

	AddAlias("hostcom-driver",		"hostcom0-driver");
	AddAlias("hostcom-tcp-port",	"hostcom0-tcp-port");

	// キーボードの入力モード
	Add("hostkbd-input", "char", VMCap::LUNA | VMCap::X68030);

	// ネットワークのホスト側設定。
	// (0 と 1 で -driver の初期値が違う)
	Add("hostnet0-driver",			"auto");
	Add("hostnet0-afpacket-ifname",	"auto");
	Add("hostnet0-bpf-ifname",		"auto");
	Add("hostnet0-tap-devpath",		"auto");
	Add("hostnet0-usermode-hostfwd","");
	Add("hostnet0-usermode-net",	"10.8.0.0/24");
	Add("hostnet0-usermode-net6",	"fd08::/64");

	Add("hostnet1-driver",			"none",	VMCap::X68030);
	Add("hostnet1-afpacket-ifname",	"auto",	VMCap::X68030);
	Add("hostnet1-bpf-ifname",		"auto",	VMCap::X68030);
	Add("hostnet1-tap-devpath",		"auto",	VMCap::X68030);
	Add("hostnet1-usermode-hostfwd","",		VMCap::X68030);
	Add("hostnet1-usermode-net",	"10.8.0.0/24",	VMCap::X68030);
	Add("hostnet1-usermode-net6",	"fd08::/64",	VMCap::X68030);

	AddAlias("hostnet-driver",			"hostnet0-driver");
	AddAlias("hostnet-afpacket-ifname",	"hostnet0-afpacket-ifname");
	AddAlias("hostnet-bpf-ifname",		"hostnet0-bpf-ifname");
	AddAlias("hostnet-tap-devpath",		"hostnet0-tap-devpath");
	AddAlias("hostnet-usermode-hostfwd","hostnet0-usermode-hostfwd");
	AddAlias("hostnet-usermode-net",	"hostnet0-usermode-net");
	AddAlias("hostnet-usermode-net6",	"hostnet0-usermode-net6");

	Add("hostsound-driver",				"auto");
	Add("hostsound-alsa-device",		"default");
	Add("hostsound-netbsd-device",		"/dev/sound");

	// IPLROM1/2 ファイルパス。なければ内蔵 ROM。
	Add("iplrom1-image", "", VMCap::X68030);
	Add("iplrom2-image", "", VMCap::X68030);

	// キーボードの接続
	Add("keyboard-connect", 1, VMCap::LUNA | VMCap::X68030);

	// LUNA の RTC(MK48T02) がうるう年でない 1970 をエポックとして
	// 設定しているので、そちらに合わせてうるう年を補正する
	Add("luna-adjust-misused-epoch", 1, VMCap::LUNA);

	// LUNA の DIPSW (1 が UP、0 が DOWN)。
	// 初期値は機種によって異なるため pio.cpp で設定している。
	Add("luna-dipsw1", VMCap::LUNA);
	Add("luna-dipsw2", VMCap::LUNA);

	// LUNA ビデオボードプレーン数
	Add("luna-video-plane", 4, VMCap::LUNA);

	// m88100 の別名ニーモニックを併記する。
	Add(".m88k-altname", 0, VMCap::M88K);

	// m88100/88200 のバージョン。
	// 初期値はコンストラクタ側で用意している。
	Add("m88100-version", 0, VMCap::M88K);
	Add("m88200-version", 0, VMCap::M88K);

	// 画面サイズ。
	Add("mainview-scale", "1");

	// モニタウィンドウ(等)のフォントサイズ
	Add("monitor-fontsize", 12);

	// モニタウィンドウの更新頻度 [Hz]
	Add("monitor-rate", 20);

	// MPU クロック (MHz 単位の実数、小数点以下は3桁まで)。
	// 初期値は各 VM クラスで設定している。
	Add("mpu-clock");

	// MPU のキャッシュをオンに出来なくする。テスト用。
	Add("mpu-force-disable-dcache", 0);
	Add("mpu-force-disable-icache", 0);

	// M88100 で疑似 STOP 状態をサポートする
	Add("mpu-pseudo-stop", 1, VMCap::M88K);

	// MPU 種別。現状 m68k のみ。初期値は機種依存。
	Add("mpu-type", "", VMCap::M68K);

	// Nereid
	Add("nereid0-enable", 0, VMCap::X68030);
	Add("nereid1-enable", 0, VMCap::X68030);

	// Nereid イーサネット
	Add("nereid0-net", 1, VMCap::X68030);
	Add("nereid1-net", 1, VMCap::X68030);

	// Nereid バンクメモリ容量 (0, 4, 16 もしくは -4, -16)
	Add("nereid0-ram-size", 16, VMCap::X68030);
	Add("nereid1-ram-size", 16, VMCap::X68030);

	// NEWS の DIPSW (1 がオン、0 がオフ)。
	Add("news-dipsw", "00001000", VMCap::NEWS);

	// NEWS の SIC を無視する。(暫定)
	// これは新旧で処置が変わるのでエイリアスにしていない。
	Add("xxx-news-sic-ignore", "0", VMCap::NEWS);
	Add("xxx-news-sci-ignore", "", VMCap::NEWS);

	// PROM ファイルパス。なければ内蔵 ROM 起動。
	Add("prom-image", "", VMCap::LUNA);

	// PROM のデバッグオプション。
	// PROM 中の SCSI デバッグビットを立てる。
	Add(".prom-scsi-debug", 0, VMCap::LUNA1);

	// RAM サイズ (MB 単位の整数)。
	// 初期値は機種ごとの VM クラスで設定している。
	Add("ram-size", 0);

	// RTC のデバッグ用オプション。
	Add(".rtc-force-fixed", 0);
	Add(".rtc-date");
	Add(".rtc-time");

	// RTC の epoch。初期値は各 VM クラスで設定している。
	Add("rtc-epoch-year", VMCap::LUNA | VMCap::NEWS);

	// ステータスパネルを表示する。
	Add("show-statuspanel", 1);

	// SCSI 設定。
	for (int id = 0; id < 8; id++) {
		auto scsicap = VMCap::LUNA | VMCap::X68030;
		// 種別とイメージパス
		Add(string_format("spc0-id%u-image", id), scsicap);
		// エミュレータ的に書き込みを禁止するか
		Add(string_format("spc0-id%u-writeignore", id), 0, scsicap);
		// シークタイム [msec]
		Add(string_format("spc0-id%u-seektime", id), 0, scsicap);
	}

	// SRAM の RAM 容量を同期するか。
	Add("sram-sync-ramsize", 1, VMCap::X68030);

	// virtio 設定。
	auto viocap = VMCap::VIRT68K;
	// デバイス。
	Add("virtio-net", 1, viocap);
	Add("virtio-entropy", 1, viocap);
	Add("virtio-scsi", 1, viocap);
	// virtio-block
	for (int id = 0; id < 8; id++) {
		// イメージパス
		Add(string_format("virtio-block%u-image", id), viocap);
		// エミュレータ的に書き込みを禁止するか
		Add(string_format("virtio-block%u-writeignore", id), 0, viocap);
	}
	// virtio-scsi
	for (int id = 0; id < 8; id++) {
		// 種別とイメージパス
		Add(string_format("virtio-scsi-id%u-image", id), viocap);
		// エミュレータ的に書き込みを禁止するか
		Add(string_format("virtio-scsi-id%u-writeignore", id), 0, viocap);
	}

	// Windrv パス。
	Add("windrv-path", "", VMCap::X68030);

	// X68030 で FC2 ピンを切るか。
	Add("x68k-cut-fc2", 1, VMCap::X68030);

	// XP プロセッサのクロック (MHz 単位の実数、小数点以下は3桁まで)。
	Add("xp-clock", "6.144", VMCap::LUNA);
}

// デストラクタ
Config::~Config()
{
	gConfig = NULL;
}

// 項目を追加 (初期状態用)。値は空文字列。
bool
Config::Add(const std::string& key, VMCap vmcap)
{
	return Add(key, "", vmcap);
}

// 項目を追加 (初期状態用)。値は整数値を文字列にしたもの。
bool
Config::Add(const std::string& key, int value, VMCap vmcap)
{
	return Add(key, std::to_string(value), vmcap);
}

// 項目を追加 (初期状態用)
bool
Config::Add(const std::string& key, const std::string& value, VMCap vmcap)
{
	assert(FindItem(key) == NULL);
	assert(fixed == false);

	list.emplace_back(key, value, vmcap, ConfigItem::FromInitial, "");
	return true;
}

// エイリアスを追加
bool
Config::AddAlias(const std::string& alias, const std::string& key)
{
	// すでに同名のエイリアスがあれば、プログラムミス
	for (const auto& p : aliases) {
		if (alias == p.first) {
			PANIC("aliases already has %s !", alias.c_str());
		}
	}

	aliases.emplace_back(alias, key);
	return true;
}

// 廃止項目を追加
void
Config::AddObsolete(const std::string& old, const std::string& alter)
{
	// すでに追加済みなら、プログラムミス。
	if (IsObsolete(old)) {
		PANIC("obsolete already has %s !", old.c_str());
	}
	obsolete[old] = alter;
}

// 項目を削除
// key が見付からなくても何もしない。key は複数登録されていることはないはず。
void
Config::Delete(const std::string& key)
{
	for (auto it = list.begin(); it != list.end(); ++it) {
		auto item = *it;
		if (key == item.key) {
			list.erase(it);
			return;
		}
	}
	assert(FindItem(key) == NULL);
}

// 設定ファイルの内容で上書きする
bool
Config::Update(const ConfigFile& file)
{
	assert(fixed == false);

	for (const auto& pair : file.lines) {
		const std::string& line = pair.first;
		uint lineno = pair.second;

		auto where = string_format("%s:%u", file.filepath.c_str(), lineno);
		if (UpdateLine(line, file.from, where) == false) {
			return false;
		}
	}

	return true;
}

#define UpdateLinePerf(str)	do {	\
	if (UpdateLine(str, ConfigItem::FromPerf, where) == false) \
		return false;	\
} while (0)

// コマンドラインオプションで更新
bool
Config::Update(const std::string& line, const std::string& optname)
{
	assert(fixed == false);

	// パフォーマンス測定用オプション --perf が指定されたら
	// line="--perf" という非正規の書式で呼ばれるので、ここで展開する。
	if (line == "--perf") {
		// -V prom-image=PROM.DAT はパスの問題があるので、各自で追加指定
		// する必要がある。
		std::string where = "--perf -V";
		UpdateLinePerf(".rtc-force-fixed=1");
		UpdateLinePerf("clock-sync=virtual");
		UpdateLinePerf("fast-mode=1");
		UpdateLinePerf("fd0-writeignore=1");
		UpdateLinePerf("fd1-writeignore=1");
		UpdateLinePerf("fd2-writeignore=1");
		UpdateLinePerf("fd3-writeignore=1");
		UpdateLinePerf("ethernet-macaddr=02:00:00:00:00:01");
		// hostcom0-driver は機種依存があるので後で mainapp.cpp で変更する。
		UpdateLinePerf("hostnet-driver=none");
		UpdateLinePerf("luna-dipsw1=11110111");
		UpdateLinePerf("luna-video-plane=4");
		UpdateLinePerf("spc0-id0-writeignore=1");
		UpdateLinePerf("spc0-id1-writeignore=1");
		UpdateLinePerf("spc0-id2-writeignore=1");
		UpdateLinePerf("spc0-id3-writeignore=1");
		UpdateLinePerf("spc0-id4-writeignore=1");
		UpdateLinePerf("spc0-id5-writeignore=1");
		UpdateLinePerf("spc0-id6-writeignore=1");
		UpdateLinePerf("virtio-block0-writeignore=1");
		UpdateLinePerf("virtio-block1-writeignore=1");
		UpdateLinePerf("virtio-block2-writeignore=1");
		UpdateLinePerf("virtio-block3-writeignore=1");
		UpdateLinePerf("virtio-block4-writeignore=1");
		UpdateLinePerf("virtio-block5-writeignore=1");
		UpdateLinePerf("virtio-block6-writeignore=1");
		UpdateLinePerf("virtio-block7-writeignore=1");
		UpdateLinePerf("virtio-scsi-id0-writeignore=1");
		UpdateLinePerf("virtio-scsi-id1-writeignore=1");
		UpdateLinePerf("virtio-scsi-id2-writeignore=1");
		UpdateLinePerf("virtio-scsi-id3-writeignore=1");
		UpdateLinePerf("virtio-scsi-id4-writeignore=1");
		UpdateLinePerf("virtio-scsi-id5-writeignore=1");
		UpdateLinePerf("virtio-scsi-id6-writeignore=1");
		UpdateLinePerf("virtio-scsi-id7-writeignore=1");
		return true;
	} else {
		return UpdateLine(line, ConfigItem::FromOption, optname);
	}
}

// 実行中に動的に変更する。
bool
Config::UpdateRunning(const std::string& line)
{
	return UpdateLine(line, ConfigItem::FromRunning, "");
}

// 更新1行分の共通処理。
// キーが見付からない場合は from によって動作が異なる。
// o 共通設定ファイルでなら、何もせず true を返す。
// o VM 設定ファイルでなら、エラーメッセージを表示するが true を返す。
// o コマンドラインでなら、エラーメッセージを表示して false を返す。
// 起動前のこれらでは、false は実行中止を示す。
// また
// o 実行中の変更なら、何もせず false を返す。
// この場合の false はキーが見付からなかったことを意味する。
// (文法エラーの場合にも false が返るが、それはプログラムエラー)
bool
Config::UpdateLine(const std::string& line, ConfigItem::from_t from,
	const std::string& where)
{
	std::string key;
	std::string val;
	std::string head;

	// キーと値に分解
	if (SplitKeyVal(&key, &val, line) == false) {
		head = ConfigItem::ErrFrom(from, where, "", line);
		warnx("%s: Syntax error", head.c_str());
		return false;
	}

	head = ConfigItem::ErrFrom(from, where, key, val);

	// キーが別名なら正規名に置換
	key = GetCanonKey(key);

	// このキーが廃止予定なら警告は出すがそのまま使う。
	if (IsObsolete(key)) {
		warnx("Warning: %s: '%s' is obsolete. Please use '%s' instead.",
			head.c_str(), key.c_str(), obsolete[key].c_str());
		key = obsolete[key];
	}

	// キーで検索
	ConfigItem *itemptr = FindItem(key);
	if (itemptr == NULL) {
		switch (from) {
		 default:
		 case ConfigItem::FromHome:
		 case ConfigItem::FromPerf:
			// 共通設定ファイルなら黙って無視する。
			// このファイルには他機種向けの設定も並んでいる可能性があるため
			// 無視してくれないと困る。
			return true;

		 case ConfigItem::FromConfig:
			// VM 設定ファイルなら警告のみで無視する。
			// このファイルに他機種向けの設定があるのはおかしいが
			// 止めるほどではない。
			warnx("%s: Unknown key, ignored", head.c_str());
			return true;

		 case ConfigItem::FromOption:
			// コマンドラインからの場合はエラーにする。
			warnx("%s: Unknown key", head.c_str());
			return false;

		 case ConfigItem::FromRunning:
			// 実行中なら、黙って無視する。
			return true;
		}
	}

	// 見付かったので更新
	ConfigItem& item = *itemptr;
	item.value = val;
	item.from  = from;
	item.where = where;

	return true;
}

// "key=val" 形式の行 line を分解するして *keyp, *valp に代入する。
// その際 key, val 前後の空白は取り除く。
// 失敗すれば false を返す。
/*static*/ bool
Config::SplitKeyVal(std::string *keyp, std::string *valp,
	const std::string& line)
{
	assert(keyp);
	assert(valp);

	// キーと値に分解
	auto pos = line.find('=');
	if (pos == std::string::npos) {
		return false;
	}
	auto key = line.substr(0, pos++);
	auto val = line.substr(pos, line.size() - pos);

	*keyp = string_trim(key);
	*valp = string_trim(val);

	return true;
}

// キーがエイリアスなら正規名を返す。
// エイリアスでなければキーをそのまま返す。
const std::string&
Config::GetCanonKey(const std::string& key) const
{
	for (const auto& p : aliases) {
		const auto& alias = p.first;
		const auto& canon = p.second;

		if (key == alias) {
			return canon;
		}
	}

	return key;
}

// 内容を表示する。
// all なら隠し設定も含めて表示。
void
Config::Show(bool all) const
{
	assert(fixed);

	for (const auto& item : list) {
		// '!' から始まるのは内部用変数なので隠す
		if (!all && item.key[0] == '!') {
			continue;
		}
		// ドットから始まるのは隠し設定で、初期値のままなら隠す
		if (!all &&
		    item.key[0] == '.' && item.from == ConfigItem::FromInitial) {
			continue;
		}

		std::string buf = string_format("%s=\"%s\"",
			item.key.c_str(), item.value.c_str());
		if (buf.size() < 40) {
			uint n = ((40 - buf.size()) + 7) / 8;
			buf.append(n, '\t');
		} else {
			buf.push_back('\t');
		}

		std::string fromstr;
		switch (item.from) {
		 case ConfigItem::FromInitial:
			fromstr = "initial value";
			break;
		 case ConfigItem::FromHome:
		 case ConfigItem::FromConfig:
			fromstr = item.where;
			break;
		 case ConfigItem::FromOption:
			fromstr = "option " + item.where;
			break;
		 case ConfigItem::FromPerf:
			fromstr = "option --perf";
			break;
		 default:
			fromstr = string_format("corrupted item.from=%u for key=%s",
				(uint)item.from, item.key.c_str());
			break;
		}

		printf("%s(%s)\n", buf.c_str(), fromstr.c_str());
	}
}

// key から ConfigItem を取得する。見付からなければ assert する。
const ConfigItem&
Config::Find(const std::string& key) const
{
	for (const auto& item : list) {
		if (key == item.key) {
			return item;
		}
	}
	PANIC("Config::Find() \"%s\" not found", key.c_str());
}

// key のデフォルト値を差し替える。key が見付からなければ assert する。
void
Config::SetDefault(const std::string& key, int val)
{
	std::string valstr = std::to_string(val);
	SetDefault(key, valstr);
}

// key のデフォルト値を差し替える。key が見付からなければ assert する。
void
Config::SetDefault(const std::string& key, const std::string& val)
{
	assert(fixed == false);

	for (auto& item : list) {
		if (key == item.key) {
			item.SetDefault(val);
			return;
		}
	}
	assertmsg(0, "Config::SetDefault() \"%s\" not found\n", key.c_str());
}

// key から ConfigItem を返す。見付からなければ NULL を返す。
// (こっちは内部で変更するために使う)
ConfigItem *
Config::FindItem(const std::string& key)
{
	for (auto& item : list) {
		if (key == item.key) {
			return &item;
		}
	}
	return NULL;
}

// これ以降は書き込み禁止。
bool
Config::Fix()
{
	assert(fixed == false);

	// ここで、この機種に不要な変数を削除する。
	// その変数がユーザから指定されていればエラーにする。
	for (auto it = list.begin(); it != list.end(); ) {
		ConfigItem& item = *it;
		VMCap vmcap = item.GetVMCap();
		if (gMainApp.Has(vmcap) == false) {
			// この機種用ではない
			switch (item.from) {
			 case ConfigItem::FromConfig:
			 case ConfigItem::FromOption:
				// 明示的に更新されていればエラー
				item.Err("Not supported in this vmtype");
				return false;
			 default:
				break;
			}

			it = list.erase(it);
		} else {
			++it;
		}
	}

	fixed = true;
	return true;
}


//
// 設定項目
//

// 値を整数値として取得してみる。
bool
ConfigItem::TryInt(int *val) const
{
	char *endp;

	if (value.empty()) {
		return false;
	}

	errno = 0;
	int r = strtol(value.c_str(), &endp, 10);
	if (errno == ERANGE || *endp != '\0') {
		return false;
	}
	*val = r;
	return true;
}

// 値を double 値として取得してみる。
bool
ConfigItem::TryDouble(double *val) const
{
	char *endp;

	if (value.empty()) {
		return false;
	}

	errno = 0;
	double res = strtod(value.c_str(), &endp);
	if (errno) {
		return false;
	}
	*val = res;
	return true;
}

// b を e 乗した整数を返す。
static inline int
ipow(int b, int e)
{
	int rv = 1;

	for (int i = 0; i < e; i++) {
		rv *= b;
	}
	return rv;
}

// 値を固定小数点値として取得してみる。
// digit は小数点以下の桁数で、それ以下は四捨五入する。
// 値が "1.2345" で digit=3 なら *val = 1235 を返す。
bool
ConfigItem::TryFixedDecimal(int *val, int digit) const
{
	char *endp;
	bool neg = false;

	if (value.empty()) {
		return false;
	}

	errno = 0;
	int r = strtol(value.c_str(), &endp, 10);
	if (errno == ERANGE || !(*endp == '\0' || *endp == '.')) {
		return false;
	}
	if (r < 0) {
		r = -r;
		neg = true;
	}
	r *= ipow(10, digit);
	if (*endp == '.') {
		char *p = endp + 1;
		errno = 0;
		int f = strtol(p, &endp, 10);
		if (errno == ERANGE || *endp != '\0') {
			return false;
		}
		int n = endp - p;
		if (n <= digit) {
			r += f * ipow(10, digit - n);
		} else {
			r += f / ipow(10, n - digit) + (p[digit] >= '5');
		}
	}
	*val = neg ? -r : r;
	return true;
}

// デフォルト値を差し替える
void
ConfigItem::SetDefault(const std::string& val)
{
	if (from == FromInitial) {
		value = val;
	}
}

std::string
ConfigItem::ErrMsg() const
{
	// 定型文
	return ErrMsg("Invalid argument");
}

std::string
ConfigItem::ErrMsg(const char *fmt, ...) const
{
	char buf[1024];
	va_list ap;

	va_start(ap, fmt);
	vsnprintf(buf, sizeof(buf), fmt, ap);
	va_end(ap);

	std::string msg(buf);
	return ErrMsg(msg);
}

std::string
ConfigItem::ErrMsg(const std::string& msg) const
{
	return ErrFrom(from, where, key, value) + ": " + msg;
}

void
ConfigItem::Err() const
{
	// 定型文
	Err("Invalid argument");
}

void
ConfigItem::Err(const char *fmt, ...) const
{
	char buf[1024];
	va_list ap;

	va_start(ap, fmt);
	vsnprintf(buf, sizeof(buf), fmt, ap);
	va_end(ap);

	std::string msg(buf);
	Err(msg);
}

void
ConfigItem::Err(const std::string& msg) const
{
	warnx("%s", ErrMsg(msg).c_str());
}

// この item のエラーメッセージのヘッダ部分を返す。
// key=val の '=' がない場合のみ key を空、value を行全体として呼ぶ。
/*static*/ std::string
ConfigItem::ErrFrom(ConfigItem::from_t from, const std::string& where,
	const std::string& key, const std::string& value)
{
	std::string fromstr;

	switch (from) {
	 case FromInitial:
		// 初期値の場合 key が空なことは起きないはず。
		// というか初期値でエラーが起きることも普通はないはず。
		fromstr = "initial value: " + key + "=" + value;
		break;

	 case FromHome:
	 case FromConfig:
		// 設定ファイルでのエラー。where はファイル名と行番号。
		fromstr = where + ": ";
		if (key.empty() == false) {
			fromstr += key;
			fromstr += "=";
		}
		fromstr += value;
		break;

	 case FromOption:
	 case FromPerf:
		// コマンドラインオプションの場合は where は
		// "--fontsize" とか "-V" なので、
		// "-V" なら (key =) value を追加し、
		// それ以外なら value のみ追加がいいか。
		fromstr = "option " + where + " ";
		if (where.find("-V") != std::string::npos) {
			if (key.empty() == false) {
				fromstr += key;
				fromstr += "=";
			}
		}
		fromstr += value;
		break;

	 default:
		fromstr = string_format("corrupted item.from=%u for key=%s",
			(uint)from, key.c_str());
		break;
	}

	return fromstr;
}


//
// 設定ファイル
//

// コンストラクタ
ConfigFile::ConfigFile(const std::string& filepath_, ConfigItem::from_t from_)
{
	filepath = filepath_;
	from = from_;
}

// デストラクタ
ConfigFile::~ConfigFile()
{
}

// ファイルの読み込み。
// 成功すれば true を、失敗すればメッセージを出力して false を返す。
// ファイルがないのは正常とする (なければスルーするため)。
// ファイルがあって読めなければ失敗 (エラー終了したいので)。
bool
ConfigFile::Load()
{
	char buf[1024];
	FILE *fp;

	if (filepath.empty()) {
		return true;
	}

	fp = fopen(filepath.c_str(), "r");
	if (fp == NULL) {
		if (errno == ENOENT) {
			return true;
		}
		warn("%s", filepath.c_str());
		return false;
	}

	for (int line = 1; fgets(buf, sizeof(buf), fp) != NULL; line++) {
		// 空行とコメント行を無視
		rtrim(buf);
		if (buf[0] == '#') {
			buf[0] = '\0';
		}
		if (buf[0] == '\0') {
			continue;
		}

		// ここでは記憶するだけ
		lines.emplace_back(buf, line);
	}

	fclose(fp);
	return true;
}

unix.superglobalmegacorp.com

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