|
|
nono 1.7.0
//
// nono
// Copyright (C) 2020 nono project
// Licensed under nono-license.txt
//
//
// CLI/GUI 共通のメイン部分
//
#include "mainapp.h"
#include "accel_avx2.h"
#include "config.h"
#include "hostnet.h"
#include "logger.h"
#include "monitor.h"
#include "mystring.h"
#include "mythread.h"
#include "uimessage.h"
#include "vm_luna.h"
#include "vm_news.h"
#include "vm_virt68k.h"
#include "vm_x68k.h"
#include <getopt.h>
#include <pwd.h>
#include <sys/ioctl.h>
#include <sys/param.h>
#include <sys/stat.h>
#include <algorithm>
#include <thread>
#if defined(__linux__)
#include <sys/prctl.h>
#endif
static std::string maptocsv(const std::vector<bool>& list);
// グローバルインスタンス
MainApp gMainApp;
// コンストラクタ
MainApp::MainApp()
{
}
// デストラクタ
MainApp::~MainApp()
{
// 逆順に解放
pVM.reset();
pConfig.reset();
gConfig = NULL;
pMonitorManager.reset();
gMonitorManager = NULL;
hostcpu.reset();
#if 0 // 確認用
if (objects.empty() == false) {
printf("~MainApp: undead objects are:");
for (const auto *obj : objects) {
printf(" %s", obj->GetIdStr());
}
printf("\n");
}
#endif
logger.reset();
}
// VM を解放。
void
MainApp::Dispose()
{
if ((bool)pVM) {
pVM->Dispose();
}
}
// ヘルプメッセージ
void
MainApp::ShowHelp(bool all) const
{
ShowVersion();
#define p(msg) printf(" " msg "\n")
printf("usage: %s [<options>]\n", getprogname());
p("-c <path> vm directory or configuration file");
p("-f fast mode (same as '-V fast-mode=1')");
p("--fd[01] <path> specify floppy image (same as '-V fdN-image=<path>' except");
p(" base directory)");
if (IsGUI())
p("--fontsize <height> fontsize (same as '-V monitor-fontsize=<height>')");
p("-h show brief help message");
p("--help show all help message including for developers");
if (IsGUI())
p("-s <scale> mainview scale (same as '-V mainview-scale=<scale>')");
p("--show-config show configuration variables");
p("--show-hostnet show list of hostnet drivers");
p("-v show version");
p("-V <key>=<val> overwrite config option");
p("-X <file> load and execute host binary (a.out or ELF)");
p("--initrd <img> initial ramdisk (only for virt68k)");
if (all) {
printf("\n(options for developers)\n");
p("--b [<cpu>,]<addr>[,<skip>] (or -b)");
p(" set breakpoint. <cpu> is either 'main'(default) or 'xp'");
p("--bi [<cpu>,]<inst>[:<mask>][,<skip>]");
p(" set instruction breakpoint.");
p("--bi-exg set instruction breakpoint on 'exg sp,sp'");
p("--bv [<cpu>,]<vec1>[-<vec2>][,<skip>]");
p(" set vector breakpoint.");
p("-C output log to console");
p("--console-log <file> specify the console output log");
p("-d debugger prompt on startup");
p("-D same as '-V debugger-driver=stdio'");
p("-H human68k console emulation");
p("-L <name>=<lv>[,..] set loglevel (-Lhelp displays names)");
p("-M <name>[,..] monitors to display at startup (-Mhelp displays names)");
p(" memdump<N>[=<hex-addr>[.<fmt>]] fmt := B/W/L/M(MMU)/I(Disasm)/Z(XPDisasm)");
p("-S MSX-DOS console emulation");
p("--perf performance measure mode");
}
}
// long option は enum と struct option をアルファベット順に並べる。
// enum は getopt() の1文字のオプションと衝突しなければいいので適当に
// 0x80 から始めておく。
enum {
OPTstart = 0x80 - 1,
OPT_bi,
OPT_bi_exg,
OPT_bv,
OPT_console_log,
OPT_fd0,
OPT_fd1,
OPT_fontsize,
OPT_help,
OPT_initrd,
OPT_perf,
OPT_show_config,
OPT_show_config_all,
OPT_show_hostnet,
};
static struct option longopts[] = {
{ "b", required_argument, NULL, 'b' },
{ "bi", required_argument, NULL, OPT_bi },
{ "bi-exg", no_argument, NULL, OPT_bi_exg },
{ "bv", required_argument, NULL, OPT_bv },
{ "console-log", required_argument, NULL, OPT_console_log },
{ "fd0", required_argument, NULL, OPT_fd0 },
{ "fd1", required_argument, NULL, OPT_fd1 },
{ "fontsize", required_argument, NULL, OPT_fontsize },
{ "help", no_argument, NULL, OPT_help },
{ "initrd", required_argument, NULL, OPT_initrd },
{ "perf", no_argument, NULL, OPT_perf },
{ "show-config", no_argument, NULL, OPT_show_config },
// --show-config-all は開発用なのでヘルプには載せない
{ "show-config-all",no_argument, NULL, OPT_show_config_all },
{ "show-hostnet", no_argument, NULL, OPT_show_hostnet },
{ NULL, 0, NULL, 0 },
};
// VM の初期化、ステージ1。
// VM 作成と設定確定あたりまで。スレッド生成を伴わないもの。
// 所々 CLI と GUI で処理が違う。
// 戻り値は以下のいずれか。
// MainApp::PASS .. 実行を継続(通常パス)
// EXIT_SUCCESS / EXIT_FAILURE .. この終了コードでアプリケーションを終了
int
MainApp::Init1(int ac, char *av[], std::string (*conv)(const std::string&))
{
int rv;
is_cli = (conv == NULL);
rv = Init1a(ac, av, conv);
if (rv != MainApp::PASS) {
return rv;
}
rv = Init1b();
return rv;
}
// VM の初期化、ステージ1 の前半。
// Config.Fix まで。
int
MainApp::Init1a(int ac, char *av[], std::string (*conv)(const std::string&))
{
int rv;
#if defined(__linux__)
// Linux ではケーパビリティが設定されていると coredump しないので
// 明示的に許可する必要がある
prctl(PR_SET_DUMPABLE, 1, 0, 0, 0);
#endif
rv = ParseOpt(ac, av);
if (rv != MainApp::PASS) {
return rv;
}
// スレッドマネージャ (ログより前)
threadmanager.reset(new ThreadManager());
// ここでメインスレッド関連の設定。
PTHREAD_SETNAME("Main");
threadmanager->RegistThread(NULL, "Main");
// モニタマネージャ (VM 作成より前、もしかしたら Config より前?)
pMonitorManager.reset(new MonitorManager());
gMonitorManager = pMonitorManager.get();
// ログ機構は引数処理後なるはや…にしたいがモニタマネージャは必要。
// CLI ならログは常に標準出力へ(も)出力。
// GUI なら -C で指定。
logger.reset(new Logger());
if (IsCLI()) {
logger->UseStdout(true);
} else {
logger->UseStdout(log_to_console);
logger->SetConverter(conv);
}
logger->StartThread();
// UIMessage は VM 起動前ならどこでもいいはず。
uimessage.reset(new UIMessage());
// 設定を作成。
// c0. コンストラクタで初期値を用意。
pConfig.reset(new Config());
gConfig = pConfig.get();
// c1. ホームディレクトリに設定ファイルがあれば読み込んで設定を更新。
struct passwd *passwd = getpwuid(getuid());
// 見付からないことはないはずだが、一応。
if (passwd != NULL) {
std::string filename = std::string(passwd->pw_dir) + "/.nono.cfg";
ConfigFile dotfile(filename, ConfigItem::FromHome);
if (dotfile.Load()) {
if (gConfig->Update(dotfile) == false) {
return EXIT_FAILURE;
}
}
}
// c2. VM ディレクトリに設定ファイルがあれば読み込んで設定を更新。
// vmfile が存在するかどうかはここで調べるまで分からない。
ConfigFile cfgfile(vmfile, ConfigItem::FromConfig);
if (cfgfile.Load() == false) {
return EXIT_FAILURE;
}
if (gConfig->Update(cfgfile) == false) {
return EXIT_FAILURE;
}
// c3. 最後にコマンドライン引数で更新
for (const auto& pair : config_options) {
if (gConfig->Update(pair.first, pair.second) == false) {
return EXIT_FAILURE;
}
}
// c4. exec-file, exec-arg をここでメンバ変数 exec_file, exec_arg に代入。
// (c3 までに確定していて、1. VM コンストラクト時には必要)
const ConfigItem& itemexec = gConfig->Find("exec-file");
const std::string& execstr = itemexec.AsString();
if (execstr.empty() == false) {
// 歴史的経緯で exec_file は const char *。
exec_file = strdup(NormalizePath(execstr).c_str());
}
const ConfigItem& itemarg = gConfig->Find("exec-arg");
exec_arg = itemarg.AsString();
// Human68k/MSX-DOS モードならここでいくつかパラメータを強制的に変更。
if (human_mode) {
// X68030 最小構成でいい。
gConfig->Update("vmtype=x68030", "-H");
}
if (msxdos_mode) {
// LUNA-I 最小構成でいい。
gConfig->Update("vmtype=luna", "-S");
gConfig->Update("ram-size=16", "-S");
gConfig->Update("luna-video-plane=1", "-S");
// ホスト側には干渉しない。
gConfig->Update("hostcom0-driver=none", "-S");
gConfig->Update("hostcom1-driver=none", "-S");
gConfig->Update("hostcom2-driver=none", "-S");
gConfig->Update("hostnet-driver=none", "-S");
}
// ログ表示のためだけのオブジェクト
hostcpu.reset(new Object(OBJ_HOSTCPU));
// この後の CheckCPU() で hostcpu のログを出したいので、
// 先にログレベルを分かる分だけ処理する。
ParseLogoptEarly();
// ホスト CPU の機能チェック。
if (CheckCPU() == false) {
return EXIT_FAILURE;
}
// 0. VM 種別を決定
// VM 種別文字列から vmtype を決定
const ConfigItem& item = gConfig->Find("vmtype");
vmstr = string_tolower(item.AsString());
if (vmstr == "x68030") {
vmtype = VMType::X68030;
} else if (vmstr == "luna") {
vmtype = VMType::LUNA1;
} else if (vmstr == "luna88k") {
vmtype = VMType::LUNA88K;
} else if (vmstr == "news") {
vmtype = VMType::NEWS;
} else if (vmstr == "virt-m68k" || vmstr == "virt68k") {
vmtype = VMType::VIRT68K;
} else {
if (item.GetFrom() == ConfigItem::FromInitial) {
// 未指定の時
warnx("vmtype must be specified");
} else {
// ユーザ由来の時
item.Err("Invalid vmtype");
}
return EXIT_FAILURE;
}
// 0.1 機種が確定したので、機種依存の設定値変更。
if (opt_perf) {
// --perf なら余計な I/O を避けるため HostCOM をオフにしておきたい。
// ただし virt-* はデフォルトが console なのでそれはオフにしたくない。
if (gMainApp.IsVIRT68K() == false) {
gConfig->Update("hostcom0-driver=none", "--perf");
}
}
// 1. VM 作成
switch (vmtype) {
case VMType::X68030:
pVM.reset(new VM_X68030());
break;
case VMType::LUNA1:
pVM.reset(new VM_LUNA1());
break;
case VMType::LUNA88K:
pVM.reset(new VM_LUNA88K());
break;
case VMType::NEWS:
pVM.reset(new VM_NEWS());
break;
case VMType::VIRT68K:
pVM.reset(new VM_Virt68k());
break;
default:
PANIC("vmtype=%s not configured", vmstr.c_str());
}
// 2. 動的なコンストラクションその1
// 3. ログ出力用の EarlyInit
if (!pVM->Create(VM::CreationPhase::First)) {
pVM.reset();
return EXIT_FAILURE;
}
// 4. 設定を確定。
// (VM コンストラクタで変数の増減があるのでそれより後、
// Create() でも SCSI パラメータを減らすので、それより後)
if (gConfig->Fix() == false) {
pVM.reset();
return EXIT_SUCCESS;
}
// Fix() は配列から削除を行うため、これより前に取得していた ConfigItem
// のポインタや参照は無効になっている可能性がある。Fix() をまたいで
// ポインタや参照を使ってしまわないようここで一旦フォーカスを抜ける。
// なお Fix() 後は要素の削除は出来ないため、以降はこの問題は発生しない。
return PASS;
}
// VM の初期化、ステージ1 の後半。
// Config.Fix 以降。
int
MainApp::Init1b()
{
// 4.1 設定が確定したので --show-config なら設定内容を表示して終了。
if (show_config) {
gConfig->Show(show_config - 1);
pVM.reset();
return EXIT_SUCCESS;
}
// 古い隠しオプションは書式に互換性がないので捨てる。
// --show-config の後にしないといけない。
const ConfigItem& item_cpu_aff = gConfig->Find(".host-cpu-affinity");
if (item_cpu_aff.GetFrom() != ConfigItem::FromInitial) {
item_cpu_aff.Err(
"Obsolete. Replace it with new syntax 'hostcpu-fastcore'. "
"(Please see document)");
return false;
}
// 4.2 ログレベルを設定。コンストラクト後すぐに行う。
// -L help もここで処理。
if (!ParseLogopt()) {
pVM.reset();
return EXIT_FAILURE;
}
// 4.3 -X のチェック。
// -L help より後にしないとヘルプが表示できない。
const ConfigItem& itemexec = gConfig->Find("exec-file");
if (exec_file == NULL) {
// -X が必要なケースで指定されてなければエラー。
// 実行ファイル指定が必須の VM。
if (vmtype == VMType::NEWS || vmtype == VMType::VIRT68K) {
if (itemexec.GetFrom() == ConfigItem::FromInitial) {
warnx("vmtype=%s requires -X option (or exec-file).",
vmstr.c_str());
} else {
itemexec.Err("filename must be specified.");
}
return EXIT_FAILURE;
}
// Human モード、MSX-DOS モードでも実行ファイル名が必要。
if (human_mode) {
warnx("-H option needs -X");
return EXIT_FAILURE;
}
if (msxdos_mode) {
warnx("-S option needs -X");
return EXIT_FAILURE;
}
} else {
// 実行ファイルのファイル名を間違えたくらいならここでエラーに出来る。
int r = access(exec_file, R_OK);
if (r != 0) {
itemexec.Err("%s", strerror(errno));
return EXIT_FAILURE;
}
}
// 5. 動的コンストラクションその2
// ログ出力が出来る代わりにログクラスはもう増やせない。
if (!pVM->Create(VM::CreationPhase::Second)) {
pVM.reset();
return EXIT_FAILURE;
}
return PASS;
}
// VM の初期化、ステージ2。スレッド生成を伴う。
// Init1() で引数を受け付けてから Init2() でデバッガスレッドなどを起動するが、
// -Mhelp とかが指定された場合はここでスレッド開始する前にプロセスを終了する
// 必要があるので分けてある。wxapp.cpp も参照。
bool
MainApp::Init2()
{
// 7. VM 初期化。
if (!pVM->Init()) {
return false;
}
// 8. スレッド開始
if (!pVM->StartThread()) {
return false;
}
return true;
}
// コマンドライン引数を処理する。
// 知らない引数とかがあればこちらで usage を表示して false を返す。
// 戻り値は、MainApp::PASS なら実行を継続、
// EXIT_SUCCESS/EXIT_FAILURE ならこのコードで終了。
int
MainApp::ParseOpt(int ac, char *av[])
{
struct stat st;
const char *cpath;
int c;
cpath = NULL;
while ((c = getopt_long(ac, av, "b:c:CdDfhHL:M:s:SvV:X:",
longopts, NULL)) != -1) {
switch (c) {
case 'b':
debug_breakaddr.push_back(optarg);
break;
case 'c':
cpath = optarg;
// 空文字列なら再び初期値に
if (cpath[0] == '\0') {
cpath = NULL;
}
break;
case 'C':
log_to_console = true;
break;
case 'd':
debug_on_start = true;
break;
case 'D':
// -D は -V debugger-driver=stdio と等価。
config_options.emplace_back("debugger-driver=stdio", "-D");
break;
case 'f':
// -f は -V fast-mode=1 と等価。
config_options.emplace_back("fast-mode=1", "-f");
break;
case OPT_fd0:
case OPT_fd1:
{
int fd = c - OPT_fd0;
// 相対パスはカレントディレクトリを起点とする。
std::string line = string_format("fd%u-image=%s", fd,
AbsPath(optarg).c_str());
std::string from = string_format("--fd%u", fd);
config_options.emplace_back(line, from);
break;
}
case 'H':
human_mode = true;
break;
case 'L':
AddLogopt(optarg);
break;
case 'M':
if (!monitor_opt.empty()) {
monitor_opt += ",";
}
monitor_opt += optarg;
break;
case 's':
{
auto line = string_format("mainview-scale=%s", optarg);
config_options.emplace_back(line, "-s");
break;
}
case 'S':
msxdos_mode = true;
break;
case 'X':
{
// 相対パスはカレントディレクトリ起点とする。
std::string line = "exec-file=" + AbsPath(optarg);
config_options.emplace_back(line, "-X");
// Human68k モードなら後続を引数にする。
// UNIX モードでは親和性が悪いのでしない。
if (human_mode) {
std::string arg;
arg = "exec-arg=";
for (int i = optind; i < ac; i++) {
if (i != optind) {
arg += " ";
}
arg += av[i];
}
optind = ac;
config_options.emplace_back(arg, "-X");
}
break;
}
case 'v':
ShowVersion();
exit(0);
case 'V':
config_options.emplace_back(optarg, "-V");
break;
case OPT_bi:
debug_breakinst.push_back(optarg);
break;
case OPT_bi_exg:
debug_breakinst_exg = true;
break;
case OPT_bv:
debug_breakvec.push_back(optarg);
break;
case OPT_console_log:
// この時点ではコンソールを持つ VM かどうかが分からないので
// コンソールのない設定でもエラーを出さない(出せない)。
console_logfile = optarg;
break;
case OPT_fontsize:
{
auto line = string_format("monitor-fontsize=%s", optarg);
config_options.emplace_back(line, "--fontsize");
break;
}
case OPT_help:
ShowHelp(true);
return EXIT_SUCCESS;
case OPT_initrd:
{
// 相対パスはカレントディレクトリを起点とする。
std::string line = "exec-initrd=" + AbsPath(optarg);
config_options.emplace_back(line, "--initrd");
break;
}
case OPT_perf:
// パフォーマンス確認用。(Config::Update() 側で展開する)
opt_perf = true;
config_options.emplace_back("--perf", "");
log_to_console = true;
AddLogopt("all=-1,syncer=0");
break;
case OPT_show_config:
show_config = 1;
break;
case OPT_show_config_all:
show_config = 2;
break;
case OPT_show_hostnet:
ShowHostnet();
return EXIT_SUCCESS;
case 'h':
ShowHelp(false);
return EXIT_SUCCESS;
default:
ShowHelp(false);
return EXIT_FAILURE;
}
}
if (human_mode) {
// Human モードで -c ありなら、VM ディレクトリ指定。
// Human モードで -c 省略なら、VM ディレクトリなし (cpath==NULL)。
} else {
// Human モードでなければ、-c 省略は -c . と同義。
if (cpath == NULL) {
cpath = ".";
}
}
if (cpath != NULL) {
// 引数がディレクトリなら、それを VM ディレクトリとし、その中の
// nono.cfg を設定ファイルとする。
// 引数がファイルなら、それを設定ファイルとし、そのファイルがある
// ディレクトリを VM ディレクトリとする。
// -c DIR => vmdir = DIR, vmfile = DIR/nono.cfg
// -c DIR/FILE => vmdir = DIR, vmfile = FILE
if (stat(cpath, &st) < 0) {
warn("stat %s", cpath);
return EXIT_FAILURE;
}
if (S_ISDIR(st.st_mode)) {
vmdir = std::string(cpath);
if (vmdir.back() != '/') {
vmdir += '/';
}
vmfile = vmdir + "nono.cfg";
} else if (S_ISREG(st.st_mode)) {
vmfile = std::string(cpath);
auto pos = vmfile.rfind('/');
if (pos != std::string::npos) {
vmdir = vmfile.substr(0, pos + 1);
} else {
vmdir = "./";
}
} else {
warnx("-c %s: path must be file or directory", cpath);
return EXIT_FAILURE;
}
}
// CLI 版で -Mhelp つけても黙って起動するのはさすがにどうかと思う。
// とは言え GUI 版のコマンドラインを CLI 版用に書き換えた時に -M を
// 消して回らないと起動できないのも面倒なので -M オプション自体は
// 無意味だけど許容する。うーん。
if (IsCLI()) {
if (monitor_opt == "help") {
warnx("-Mhelp is not available on CLI");
return EXIT_FAILURE;
}
if (!monitor_opt.empty()) {
warnx("-M option is ignored on CLI");
return EXIT_FAILURE;
}
}
return MainApp::PASS;
}
// path を絶対パスにして返す。
// 相対パスならカレントディレクトリからのパスとする。
/*static*/ std::string
MainApp::AbsPath(const char *path)
{
char cwd[PATH_MAX];
getcwd(cwd, sizeof(cwd));
return NormalizePath(optarg, std::string(cwd));
}
// CPU の機能とかをチェック。
bool
MainApp::CheckCPU()
{
// ヘテロ構成とか。
if (CheckCPUAffinity() == false) {
return false;
}
#if defined(HAVE_AVX2)
// AVX2
detect_avx2 = ::DetectAVX2();
const ConfigItem& item_avx2 = gConfig->Find("hostcpu-avx2");
std::string cfg_avx2 = item_avx2.AsString();
if (cfg_avx2 == "auto") {
enable_avx2 = detect_avx2;
} else if (cfg_avx2 == "no") {
enable_avx2 = false;
} else {
item_avx2.Err("must be either \"auto\" or \"no\".");
return false;
}
#endif
#if defined(HAVE_NEON)
// NEON は aarch64 なら必ずある?
detect_neon = true;
const ConfigItem& item_neon = gConfig->Find("hostcpu-neon");
std::string cfg_neon = item_neon.AsString();
if (cfg_neon == "auto") {
enable_neon = detect_neon;
} else if (cfg_neon == "no") {
enable_neon = false;
} else {
item_neon.Err("must be either \"auto\" or \"no\".");
return false;
}
#endif
// 何もしない。設定のチェックもしないほうがいい。
return true;
}
// CPU アフィニティを調べる。
// 成功なら affinity_policy (と fastcore) をセットして true を返す。
// 失敗ならエラーを表示して false を返す。
bool
MainApp::CheckCPUAffinity()
{
affinity_policy = AffinityPolicy::Neutral;
fastcore.resize(std::thread::hardware_concurrency());
const ConfigItem& item_aff = gConfig->Find("hostcpu-fastcore");
std::string cfg_aff = item_aff.AsString();
if (cfg_aff == "none") {
// 何もしない。
} else if (cfg_aff == "auto") {
#if defined(__x86_64__)
if (ThreadManager::DetectCPUAffinity_x86(fastcore) == false) {
// 自動認識に失敗しただけなので、なかったことにして進む。
std::fill(fastcore.begin(), fastcore.end(), false);
}
// 区別出来た時だけ Binding にする。
uint num = std::count(fastcore.begin(), fastcore.end(), true);
if (num != 0 && num != fastcore.size()) {
affinity_policy = AffinityPolicy::Binding;
}
#else
// none と同じ。
#endif
} else {
// 手動設定。
// "0,3-5,7" みたいな感じで高性能側 CPU の番号を列挙。
affinity_policy = AffinityPolicy::Binding;
auto errmsg = ParseCPUList(cfg_aff);
if (errmsg.empty() == false) {
item_aff.Err(errmsg);
return false;
}
uint num = std::count(fastcore.begin(), fastcore.end(), true);
if (num == 0 || num == fastcore.size()) {
// 区別がないとたぶん割り当て出来ないスレッドが出来てしまうので
// エラーにする?
item_aff.Err("All cores have the same affinity?");
return false;
}
}
if (hostcpu->loglevel >= 1) {
if (affinity_policy == AffinityPolicy::Neutral) {
hostcpu->putmsgn("%s: affinity_policy=neutral", __func__);
} else {
hostcpu->putmsgn("%s: affinity_policy=binding", __func__);
hostcpu->putmsgn("%s: fast_cores=%s", __func__,
maptocsv(fastcore).c_str());
}
}
return true;
}
// CPU 番号リストの文字列からビット配列を作って fastcore にセットする。
// fastcore は事前にコア数分確保してあること。
// 成功すれば string.empty を返す。
// 失敗すればエラーメッセージを返す。
std::string
MainApp::ParseCPUList(const std::string& input)
{
// 設定は "0,3-5,7,10-" のような書式
std::vector<std::string> csv = string_split(input, ',');
for (auto& r : csv) {
const char *str;
char *endp;
uint start, last;
if (r.empty()) {
// 空なら無視する?
continue;
} else if (r.find('-') == std::string::npos) {
// '-' がなければ単独
str = &r[0];
errno = 0;
start = strtoul(str, &endp, 10);
if (endp == str || *endp != '\0' || errno == ERANGE) {
return "syntax error";
}
last = start;
} else if (r[0] == '-') {
// "-a" なら範囲 [0, a]
str = &r[1];
errno = 0;
last = strtoul(str, &endp, 10);
if (endp == str || *endp != '\0' || errno == ERANGE) {
return "syntax error";
}
start = 0;
} else if (r.back() == '-') {
// "a-" なら範囲 [a, MAX]
str = &r[0];
errno = 0;
start = strtoul(str, &endp, 10);
if (endp == str || endp != &r[r.size() - 1] || errno == ERANGE) {
return "syntax error";
}
last = fastcore.size() - 1;
} else {
// "a-b" なら範囲 [a, b]
str = &r[0];
errno = 0;
start = strtoul(str, &endp, 10);
if (endp == str || *endp != '-' || errno == ERANGE) {
return "syntax error";
}
str = endp + 1;
last = strtoul(str, &endp, 10);
if (endp == str || *endp != '\0' || errno == ERANGE) {
return "syntax error";
}
if (start > last) {
uint tmp = start;
start = last;
last = tmp;
}
}
if (start >= fastcore.size()) {
return string_format("cpu number %u exceeds number of cores(%zu)",
start, fastcore.size());
}
if (last >= fastcore.size()) {
return string_format("cpu number %u exceeds number of cores(%zu)",
last, fastcore.size());
}
for (uint n = start; n <= last; n++) {
fastcore[n] = true;
}
}
return "";
}
// srclist を "0-8,10" みたいな可読文字列にする。
static std::string
maptocsv(const std::vector<bool>& srclist)
{
std::string rv;
int start = -1;
// 最後が true で終わっている場合の処理分けを減らすため、
// 末尾に false を足しておく。
std::vector<bool> list = srclist;
list.push_back(false);
for (uint i = 0; i < list.size(); i++) {
if (start == -1) {
if (list[i] == true) {
if (rv.size() != 0) {
rv += ',';
}
rv += string_format("%u", i);
start = i;
}
} else {
if (list[i] == false) {
int end = i - 1;
if (start < end) {
rv += string_format("-%u", end);
}
start = -1;
}
}
}
return rv;
}
// バージョンを表示
void
MainApp::ShowVersion() const
{
// ここは実行ファイル名によらず nono にする
fprintf(stderr, "nono version %d.%d.%d (%s)\n",
NONO_MAJOR_VER, NONO_MINOR_VER, NONO_PATCH_VER, NONO_DATE);
}
// ログレベル指定文字列を logopt に追加する。
void
MainApp::AddLogopt(const char *opt)
{
if (logopt.empty() == false) {
logopt += ',';
}
logopt += opt;
}
// ログレベル指定文字列を処理する。
// str はログレベル指定文字列を ',' で連結した "foo=1,bar=2" 形式の文字列で、
// これを分解してそれぞれ担当するオブジェクトのログレベルにセットする。
// "help" があれば設定は行わず一覧を表示。
bool
MainApp::ParseLogopt()
{
std::vector<std::string> items;
// 分解して..
items = string_split(logopt.c_str(), ',');
// "help" があればヘルプを表示して終了
for (const auto& item : items) {
if (item == "help") {
std::vector<std::string> list = GetLogNames();
ShowHelpList(stdout, list);
return false;
}
}
// ログレベルを設定
std::string errmsg;
if (SetLogopt(items, &errmsg) == false) {
warnx("%s", errmsg.c_str());
return false;
}
return true;
}
// list を一覧表示をする。-Lhelp, -Mhelp 用。
/*static*/ void
MainApp::ShowHelpList(FILE *fp, const std::vector<std::string>& list)
{
int width = 0;
// 端末なら桁数を取得。
int fd = fileno(fp);
if (isatty(fd)) {
struct winsize ws;
if (ioctl(fd, TIOCGWINSZ, &ws) == 0) {
width = ws.ws_col;
}
}
if (width == 0) {
// パイプかリダイレクトなので垂れ流す。
for (const auto& name : list) {
fprintf(fp, " %s\n", name.c_str());
}
} else {
// 桁数が取得できたので ls(1) みたいに表示する。
uint maxlen = 0;
for (const auto& name : list) {
maxlen = std::max(maxlen, (uint)name.size());
}
// 余白分。
maxlen += 2;
const int LeftPadding = 1;
int ncol = (width - LeftPadding) / maxlen;
int nrow = (list.size() + ncol - 1) / ncol;
for (int row = 0; row < nrow; row++) {
fprintf(fp, " ");
for (int col = 0; col < ncol; col++) {
int idx = col * nrow + row;
if (idx < list.size()) {
fprintf(fp, "%-*s", maxlen, list[idx].c_str());
}
}
fprintf(fp, "\n");
}
}
}
// ログレベル指定文字列を処理する。早期処理用。
// この上の ParseLogopt() と概ね同じだが、"help" を含めて知らないものは
// スルーし、エラーが起きてもエラーメッセージも出力しない。
void
MainApp::ParseLogoptEarly()
{
std::vector<std::string> items;
items = string_split(logopt.c_str(), ',');
std::string errmsg;
SetLogopt(items, &errmsg);
}
// ログレベルを設定する。
// ログレベル指定文字列を 1つずつに分解したリスト items を処理する。
// "help" があるケースはここに来るまでに処理してあるので、ここには来ない。
// 成功なら何も表示せず true を返す。失敗なら *errmsg にエラーメッセージを
// 格納して false を返す。
// MainApp 内と Debugger からも呼ばれる。
/*static*/ bool
MainApp::SetLogopt(const std::vector<std::string>& items, std::string *errmsg)
{
for (const auto& item : items) {
if (SetLogopt1(item.c_str(), errmsg) == false) {
return false;
}
}
if (0) { // デバッグ用
for (const auto o : gMainApp.GetObjects()) {
printf("%-16s %d\n", o->GetName().c_str(), o->loglevel);
}
}
return true;
}
// "logname[=loglevel]" 形式をパースしてオブジェクトにログレベルを設定する。
// loglevel は省略なら 1 とする。
// logname が "all" なら全オブジェクトにセットする。
// そうでない場合は case ignore で完全一致するか前方一致で1つに確定すれば、
// そのオブジェクトにログレベルを設定して true を返す。
// 見付からないか候補が複数ある場合はエラーメッセージを *errmsg に出力して
// false を返す。
// この関数は (このすぐ上の SetLogopt() を経由して)
// MainApp と Debugger から呼ばれることに注意。
/*static*/ bool
MainApp::SetLogopt1(const std::string& item, std::string *errmsg)
{
std::string name;
const char *v;
int level;
v = strchr(item.c_str(), '=');
if (v) {
name = std::string(item.c_str(), v - item.c_str());
level = atoi(++v);
} else {
name = item;
level = 1;
}
// ここで name は変数名、level は値(省略されたら1)
if (name.empty()) {
*errmsg = "logname must be specified";
return false;
}
// 今の所、値域は -1 〜 9 ということにしておく。
if (level < -1) {
level = -1;
} else if (level > 9) {
level = 9;
}
// "all" なら全部にセット
if (name == "all") {
for (auto obj : gMainApp.GetObjects()) {
if (obj->GetName().empty() == false) {
obj->SetLogLevel(level);
}
}
return true;
}
// "<key>" なら"<key>*" 全部に展開する。
static const std::vector<const char *> expandkeys {
"bankram",
"fdd",
"gfpic",
"hostcom",
"hostnet",
};
for (const auto key : expandkeys) {
if (name == key) {
for (auto obj : gMainApp.GetObjects()) {
const auto& objname = obj->GetName();
if (strncasecmp(objname.c_str(), key, strlen(key)) == 0) {
obj->SetLogLevel(level);
}
}
return true;
}
}
// エイリアスリストを作る
using aliaslist_t = std::vector<std::pair<std::string, Object *>>;
aliaslist_t alias_list;
for (const auto& obj : gMainApp.GetObjects()) {
const std::vector<std::string>& aliases = obj->GetAliases();
for (const auto& a : aliases) {
alias_list.emplace_back(a, obj);
}
}
// エイリアスを完全一致のみのものと部分一致も許容するものに分ける
aliaslist_t exact_alias;
aliaslist_t partial_alias;
for (const auto& a0 : alias_list) {
bool exact = false;
for (const auto& a1 : alias_list) {
if (a0.first == a1.first) {
continue;
}
if (starts_with_ignorecase(a0.first, a1.first)) {
exact = true;
break;
}
}
if (exact) {
exact_alias.push_back(a0);
} else {
partial_alias.push_back(a0);
}
}
// 完全一致をまず調べる
for (const auto& a : exact_alias) {
if (strcasecmp(a.first.c_str(), name.c_str()) == 0) {
a.second->SetLogLevel(level);
return true;
}
}
aliaslist_t found;
for (const auto& a : partial_alias) {
// 前方一致したら覚えておく
if (starts_with_ignorecase(a.first, name)) {
found.push_back(a);
}
}
// 見付からない場合はエラー
if (found.empty()) {
*errmsg = string_format("Unknown logname \"%s\"", name.c_str());
return false;
}
// 1つだけなら確定
if (found.size() == 1) {
found[0].second->SetLogLevel(level);
return true;
}
// 複数あれば候補文字列を作成
*errmsg = string_format("Ambiguous logname \"%s\": candidates are",
name.c_str());
for (const auto& cand : found) {
*errmsg += string_format(" \"%s\"", cand.first.c_str());
}
return false;
}
// ログ名の一覧を取得する。
// MainApp と Debugger から呼ばれる。
/*static*/ std::vector<std::string>
MainApp::GetLogNames()
{
std::vector<Object *> sortobj;
// エイリアスを持つオブジェクトだけ抜き出す
for (const auto& obj : gMainApp.GetObjects()) {
if (obj->GetAliases().empty() == false) {
sortobj.emplace_back(obj);
}
}
// aliases の一語目で sortobj をソートする
std::sort(sortobj.begin(), sortobj.end(),
[](const auto a, const auto b) {
const auto& sa = a->GetAliases()[0];
const auto& sb = b->GetAliases()[0];
return sa < sb;
}
);
std::vector<std::string> list;
for (const auto *obj : sortobj) {
const auto& aliases = obj->GetAliases();
std::string str;
str = aliases[0];
if (aliases.size() > 1) {
str += " (alias:";
for (int i = 1, sz = aliases.size(); i < sz; i++) {
str += ' ';
str += aliases[i];
}
str += ')';
}
list.emplace_back(str);
}
list.emplace_back("all");
return list;
}
// 関連するファイルのパスを取得。
// 1. name がファイル名のみなら、VM ディレクトリとその親ディレクトリを検索。
// ファイルが存在すればその時点で戻り値とする。ファイルが存在したけど
// 何らか不都合があったとしても (例えばファイルサイズが 0 だとか
// パーミッションが足りないとか) 次を試すとかはしない。
// 2. 通常手順(相対パスは VM ディレクトリ起点) でパスを展開。
// ファイルの有無は不問。
std::string
MainApp::SearchFile(const std::string& name) const
{
// 1. パス区切りを含んでなければ、ファイル名のみ
if (name.find('/') == std::string::npos) {
std::string path;
struct stat st;
// 1a. VM ディレクトリ
path = GetVMDir() + name;
if (stat(path.c_str(), &st) == 0) {
return path;
}
// 1b. 親ディレクトリ
path = GetVMDir() + "../" + name;
if (stat(path.c_str(), &st) == 0) {
return path;
}
// どちらもなければエラー
return "";
}
// 2. 通常手順でパスを展開。
return NormalizePath(name);
}
// path を必要なら絶対パスに展開して返す。
// 相対パスは VM ディレクトリを起点とする。
std::string
MainApp::NormalizePath(const std::string& path) const
{
return NormalizePath(path, GetVMDir());
}
// path を必要なら絶対パスに展開して返す。
// 相対パスは basedir を起点とする。
/*static*/ std::string
MainApp::NormalizePath(const std::string& path, const std::string& basedir)
{
// 絶対パスならそのまま返す。
if (path[0] == '/') {
return path;
}
std::string newpath;
if (path[0] == '~') {
// 先頭が '~' ならホームディレクトリに展開。
struct passwd *passwd = getpwuid(getuid());
// 見付からないことはないはずだが、一応。
if (passwd != NULL) {
newpath = std::string(passwd->pw_dir);
}
newpath += &path[1];
} else {
// basedir からの相対パスとして展開。
newpath = basedir;
if (newpath.empty() || newpath.back() != '/') {
newpath += '/';
}
newpath += path;
}
return newpath;
}
// 現在の VM が指定のケーパビリティを持っているか?
bool
MainApp::Has(VMCap cap) const
{
uint32 vmcap = 1U << (int)GetVMType();
return (vmcap & (uint32)cap) != 0;
}
// VM 機種名を返す。
const char *
MainApp::GetVMName() const
{
if ((bool)pVM) {
return pVM->GetVMName();
} else {
return NULL; // not configured
}
}
// コンパイルされている host netdriver の一覧を表示。
void
MainApp::ShowHostnet() const
{
auto list = HostNetDevice::GetDrivers();
for (const auto& name : list) {
printf(" %s\n", name.c_str());
}
}
// オブジェクト登録
void
MainApp::RegistObject(Object *obj)
{
// ID が重複していないかチェック (NONE なら重複可)
auto id = obj->GetId();
if (id != OBJ_NONE) {
if (FindObject(id)) {
PANIC("%s already exists", Object::GetIdStr(id));
}
}
obj->logger = gMainApp.GetLogger();
objects.push_back(obj);
}
// オブジェクト削除
void
MainApp::UnregistObject(Object *obj)
{
// 最初に見付かった一つを削除するだけでいい
for (auto it = objects.begin(); it != objects.end(); ++it) {
if (*it == obj) {
objects.erase(it);
break;
}
}
}
// 指定された id を持つオブジェクトを返す。なければ NULL を返す。
Object *
MainApp::FindObject(uint id) const
{
// NONE はここで検索しない (SCSI デバイスなど、そっちで検索する)
if (id == OBJ_NONE) {
return NULL;
}
for (auto obj : objects) {
if (obj->GetId() == id) {
return obj;
}
}
return NULL;
}
// 指定された id を持つオブジェクトを探す。なければ assert する。
Object *
MainApp::GetObject(uint id) const
{
Object *obj = FindObject(id);
if (__predict_false(obj == NULL)) {
PANIC("objid=%s not found", Object::GetIdStr(id));
}
return obj;
}
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.