--- nono/lib/mainapp.cpp 2026/04/29 17:04:40 1.1.1.5 +++ nono/lib/mainapp.cpp 2026/04/29 17:05:56 1.1.1.24 @@ -4,14 +4,81 @@ // 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 +#include +#include +#include #include +#include +#include +#if defined(__linux__) +#include +#endif + +static std::string maptocsv(const std::vector& 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 @@ -21,29 +88,42 @@ MainApp::ShowHelp(bool all) const #define p(msg) printf(" " msg "\n") printf("usage: %s []\n", getprogname()); - p("-A load and execute host binary (a.out or ELF)"); - p("-c vm directory"); - p("-f fast mode"); + p("-c vm directory or configuration file"); + p("-f fast mode (same as '-V fast-mode=1')"); + p("--fd[01] specify floppy image (same as '-V fdN-image=' except"); + p(" base directory)"); if (IsGUI()) - p("--fontsize fontsize in monitors {12,16} (default:12)"); + p("--fontsize fontsize (same as '-V monitor-fontsize=')"); p("-h show brief help message"); p("--help show all help message including for developers"); if (IsGUI()) - p("-s,--scale screen scale, default 1.0"); + p("-s mainview scale (same as '-V mainview-scale=')"); p("--show-config show configuration variables"); + p("--show-hostnet show list of hostnet drivers"); p("-v show version"); p("-V = overwrite config option"); + p("-X load and execute host binary (a.out or ELF)"); + p("--initrd initial ramdisk (only for virt68k)"); if (all) { printf("\n(options for developers)\n"); - p("-b [,] set breakpoint"); - p("-B benchmark mode"); + p("--b [,][,] (or -b)"); + p(" set breakpoint. is either 'main'(default) or 'xp'"); + p("--bi [,][:][,]"); + p(" set instruction breakpoint."); + p("--bi-exg set instruction breakpoint on 'exg sp,sp'"); + p("--bv [,][-][,]"); + p(" set vector breakpoint."); p("-C output log to console"); + p("--console-log specify the console output log"); p("-d debugger prompt on startup"); - p("-D debugger on console"); + p("-D same as '-V debugger-driver=stdio'"); + p("-H human68k console emulation"); p("-L =[,..] set loglevel (-Lhelp displays names)"); p("-M [,..] monitors to display at startup (-Mhelp displays names)"); - p("-X human68k console emulation"); + p(" memdump[=[.]] fmt := B/W/L/M(MMU)/I(Disasm)/Z(XPDisasm)"); + p("-S MSX-DOS console emulation"); + p("--perf performance measure mode"); } } @@ -51,145 +131,351 @@ MainApp::ShowHelp(bool all) const // enum は getopt() の1文字のオプションと衝突しなければいいので適当に // 0x80 から始めておく。 enum { - OPT_fontsize = 0x80, + 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 の実行開始前まで。 +// VM の初期化、ステージ1。 +// VM 作成と設定確定あたりまで。スレッド生成を伴わないもの。 // 所々 CLI と GUI で処理が違う。 // 戻り値は以下のいずれか。 // MainApp::PASS .. 実行を継続(通常パス) // EXIT_SUCCESS / EXIT_FAILURE .. この終了コードでアプリケーションを終了 int -MainApp::Init(bool is_cli_, int ac, char *av[]) +MainApp::Init1(int ac, char *av[], std::string (*conv)(const std::string&)) { - is_cli = is_cli_; + int rv; - if (!ParseOpt(ac, av)) { - return EXIT_FAILURE; + 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 で指定。 - gLogger.UseStdout(IsCLI() ? true : log_to_console); + 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()); - // 設定を作成。コンストラクタで初期値を用意 - gConfig.reset(new Config()); + // 設定を作成。 + // 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; + } + } + } - // 引数で指定されたディレクトリの設定ファイルを読み込んで設定を更新 - ConfigFile file(vmdir + "nono.cfg"); - if (file.Load() == false) { + // c2. VM ディレクトリに設定ファイルがあれば読み込んで設定を更新。 + // vmfile が存在するかどうかはここで調べるまで分からない。 + ConfigFile cfgfile(vmfile, ConfigItem::FromConfig); + if (cfgfile.Load() == false) { return EXIT_FAILURE; } - if (gConfig->Update(file) == false) { + if (gConfig->Update(cfgfile) == false) { return EXIT_FAILURE; } - // 最後にコマンドライン引数で更新 - if (gConfig->Update(config_options) == false) { + + // 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; } - // VM 種別を決定 - if (IsCLI() && human68k_file) { - vmtype = VMTYPE_RXZ; - } else { - // VM 種別文字列から vmtype を決定 - const ConfigItem& item = gConfig->Find("vmtype"); - std::string 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; + // 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 { - if (item.GetFrom() == ConfigItem::FromInitial) { - // 未指定の時 - warnx("vmtype must be specified"); - } else { - // ユーザ由来の時 - item.Err("invalid vmtype"); - } - return EXIT_FAILURE; + // ユーザ由来の時 + item.Err("Invalid vmtype"); } + return EXIT_FAILURE; } - // VM 作成 + // 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: - gVM.reset(new VM_X68030()); + case VMType::X68030: + pVM.reset(new VM_X68030()); break; - case VMTYPE_LUNA1: - gVM.reset(new VM_LUNA()); + case VMType::LUNA1: + pVM.reset(new VM_LUNA1()); break; - case VMTYPE_RXZ: - gVM.reset(new VM_RXZ()); + case VMType::LUNA88K: + pVM.reset(new VM_LUNA88K()); break; - case VMTYPE_LUNA88K: - gVM.reset(new VM_LUNA88K()); + case VMType::NEWS: + pVM.reset(new VM_NEWS()); + break; + + case VMType::VIRT68K: + pVM.reset(new VM_Virt68k()); break; default: - __unreachable(); + PANIC("vmtype=%s not configured", vmstr.c_str()); } - // 動的なコンストラクション - if (!gVM->Create()) { + // 2. 動的なコンストラクションその1 + // 3. ログ出力用の EarlyInit + if (!pVM->Create(VM::CreationPhase::First)) { + pVM.reset(); return EXIT_FAILURE; } - // 設定内容を表示して終了 + // 4. 設定を確定。 // (VM コンストラクタで変数の増減があるのでそれより後、 // Create() でも SCSI パラメータを減らすので、それより後) - gConfig->Fix(); + 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 の実行部分。 -// Init() で引数を受け付けてから Start() でデバッガスレッドなどを起動するが、 +// VM の初期化、ステージ2。スレッド生成を伴う。 +// Init1() で引数を受け付けてから Init2() でデバッガスレッドなどを起動するが、 // -Mhelp とかが指定された場合はここでスレッド開始する前にプロセスを終了する -// 必要があるので、分けてある。 +// 必要があるので分けてある。wxapp.cpp も参照。 bool -MainApp::Start() +MainApp::Init2() { - // VM 初期化。 - // これ以降はスレッドを開始したかもしれないので false を返す際には - // VM をデストラクトすること。 - if (!gVM->Init()) { - gVM.reset(); + // 7. VM 初期化。 + if (!pVM->Init()) { return false; } - // デバッガは VM オブジェクトリストに入っていない - debugger_init(); - // 起動時設定の適用 - if (!gVM->Apply()) { - gVM.reset(); + // 8. スレッド開始 + if (!pVM->StartThread()) { return false; } @@ -198,37 +484,30 @@ MainApp::Start() // コマンドライン引数を処理する。 // 知らない引数とかがあればこちらで usage を表示して false を返す。 -bool +// 戻り値は、MainApp::PASS なら実行を継続、 +// EXIT_SUCCESS/EXIT_FAILURE ならこのコードで終了。 +int MainApp::ParseOpt(int ac, char *av[]) { - int b; + struct stat st; + const char *cpath; int c; - fontsize = 12; - screen_scale = 1.0; + cpath = NULL; - while ((c = getopt_long(ac, av, "A:b:B:c:CdDfhL:M:s:vV:X:", + while ((c = getopt_long(ac, av, "b:c:CdDfhHL:M:s:SvV:X:", longopts, NULL)) != -1) { switch (c) { - case 'A': - host_file = optarg; - break; - case 'b': debug_breakaddr.push_back(optarg); break; - case 'B': - b = atoi(optarg); - if (b < 2 || b > 6) { - fprintf(stderr, "-B : 2..6\n"); - return false; - } - benchmark_mode = b; - break; - case 'c': - vmdir = std::string(optarg); + cpath = optarg; + // 空文字列なら再び初期値に + if (cpath[0] == '\0') { + cpath = NULL; + } break; case 'C': @@ -240,18 +519,33 @@ MainApp::ParseOpt(int ac, char *av[]) break; case 'D': - debug_on_console = true; + // -D は -V debugger-driver=stdio と等価。 + config_options.emplace_back("debugger-driver=stdio", "-D"); break; case 'f': - fast_mode = true; + // -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': - if (logopt[0] != '\0') { - strlcat(logopt, ",", sizeof(logopt)); - } - strlcat(logopt, optarg, sizeof(logopt)); + AddLogopt(optarg); break; case 'M': @@ -262,48 +556,91 @@ MainApp::ParseOpt(int ac, char *av[]) break; case 's': - if (IsGUI()) { - char *end; - errno = 0; - screen_scale = strtod(optarg, &end); - if (end == optarg || end[0] != '\0' || errno == ERANGE) { - warnx("-s: invalid argument"); - return false; - } - // 上限は適当 - if (screen_scale <= 0.0 || screen_scale >= 10.0) { - warnx("-s: invalid scale"); - return false; - } - } + { + auto line = string_format("mainview-scale=%s", optarg); + config_options.emplace_back(line, "-s"); + break; + } + + case 'S': + msxdos_mode = true; break; case 'X': - human68k_file = optarg; - for (int i = optind; i < ac; i++) { - if (i != optind) { - strlcat(human68k_arg, " ", sizeof(human68k_arg)); + { + // 相対パスはカレントディレクトリ起点とする。 + 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]; } - strlcat(human68k_arg, av[i], sizeof(human68k_arg)); + optind = ac; + config_options.emplace_back(arg, "-X"); } - optind = ac; break; + } case 'v': ShowVersion(); exit(0); case 'V': - config_options.push_back(optarg); + 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: - fontsize = atoi(optarg); + { + auto line = string_format("monitor-fontsize=%s", optarg); + config_options.emplace_back(line, "--fontsize"); break; + } case OPT_help: ShowHelp(true); - return false; + 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; @@ -313,19 +650,59 @@ MainApp::ParseOpt(int ac, char *av[]) show_config = 2; break; + case OPT_show_hostnet: + ShowHostnet(); + return EXIT_SUCCESS; + case 'h': + ShowHelp(false); + return EXIT_SUCCESS; + default: ShowHelp(false); - return false; + return EXIT_FAILURE; } } - // vmdir に '/' を付けておく - if (vmdir.empty()) { - vmdir = "."; + if (human_mode) { + // Human モードで -c ありなら、VM ディレクトリ指定。 + // Human モードで -c 省略なら、VM ディレクトリなし (cpath==NULL)。 + } else { + // Human モードでなければ、-c 省略は -c . と同義。 + if (cpath == NULL) { + cpath = "."; + } } - if (vmdir.back() != '/') { - vmdir += '/'; + + 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 つけても黙って起動するのはさすがにどうかと思う。 @@ -335,17 +712,255 @@ MainApp::ParseOpt(int ac, char *av[]) if (IsCLI()) { if (monitor_opt == "help") { warnx("-Mhelp is not available on CLI"); - return false; + return EXIT_FAILURE; } if (!monitor_opt.empty()) { warnx("-M option is ignored on CLI"); - return true; + 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 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& srclist) +{ + std::string rv; + int start = -1; + + // 最後が true で終わっている場合の処理分けを減らすため、 + // 末尾に false を足しておく。 + std::vector 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 @@ -355,97 +970,307 @@ MainApp::ShowVersion() const 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; +} -// 引数 logopt のログ指定文字列をパースする。 -// arg は "foo=1,bar=2" 形式の文字列で、これを分解して -// それぞれ担当するオブジェクトのログレベルにセットする。 +// ログレベル指定文字列を処理する。 +// str はログレベル指定文字列を ',' で連結した "foo=1,bar=2" 形式の文字列で、 +// これを分解してそれぞれ担当するオブジェクトのログレベルにセットする。 +// "help" があれば設定は行わず一覧を表示。 bool MainApp::ParseLogopt() { - char *buf; - char *last; - char *p; - bool rv; - - // "help" (完全一致) なら識別子一覧を表示。 - if (strcmp(logopt, "help") == 0) { - for (auto& obj : gObjects) { - if (obj->logname != "?") { - printf("%s\n", obj->logname.c_str()); - } - } - // エイリアスとか - printf("sch -> scheduler\n"); - if (gMainApp.HasVMFeature(VMF_LUNA)) { - printf("scc -> sio\n"); + std::vector items; + + // 分解して.. + items = string_split(logopt.c_str(), ','); + + // "help" があればヘルプを表示して終了 + for (const auto& item : items) { + if (item == "help") { + std::vector list = GetLogNames(); + ShowHelpList(stdout, list); + return false; } - printf("all\n"); + } + + // ログレベルを設定 + std::string errmsg; + if (SetLogopt(items, &errmsg) == false) { + warnx("%s", errmsg.c_str()); return false; } - rv = false; - buf = strdup(logopt); - for (p = strtok_r(buf, ",", &last); - p; - p = strtok_r(NULL, ",", &last)) - { - const char *name; - char *v; - int val; - - name = p; - - v = strchr(p, '='); - if (v) { - *v++ = '\0'; - val = atoi(v); - } else { - val = 1; + return true; +} + +// list を一覧表示をする。-Lhelp, -Mhelp 用。 +/*static*/ void +MainApp::ShowHelpList(FILE *fp, const std::vector& list) +{ + int width = 0; + + // 端末なら桁数を取得。 + int fd = fileno(fp); + if (isatty(fd)) { + struct winsize ws; + if (ioctl(fd, TIOCGWINSZ, &ws) == 0) { + width = ws.ws_col; } - // ここで name は変数名、val は値(省略されたら1) + } - if (strlen(name) < 1) { - printf("invalid logname '%s'\n", name); - goto abort; + if (width == 0) { + // パイプかリダイレクトなので垂れ流す。 + for (const auto& name : list) { + fprintf(fp, " %s\n", name.c_str()); } + } else { + // 桁数が取得できたので ls(1) みたいに表示する。 - // 短縮形とかエイリアスとか - if (strcmp(name, "sch") == 0) - name = "scheduler"; - if (gMainApp.HasVMFeature(VMF_LUNA)) { - if (strcmp(name, "scc") == 0) - name = "sio"; + uint maxlen = 0; + for (const auto& name : list) { + maxlen = std::max(maxlen, (uint)name.size()); } + // 余白分。 + maxlen += 2; - // 比較 - if (strcmp(name, "all") == 0) { - // "all" なら none 以外の全部にセット - for (auto& obj : gObjects) { - obj->loglevel = val; + 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()); + } } - } else { - // それ以外は一致するキーを探してセット。 - // 複数のオブジェクトが同じ logname を持ってもよい (CMMUとか)。 - std::string sname = name; - bool found = false; - for (auto& obj : gObjects) { - if (obj->logname == sname) { - obj->loglevel = val; - found = true; + fprintf(fp, "\n"); + } + } +} + +// ログレベル指定文字列を処理する。早期処理用。 +// この上の ParseLogopt() と概ね同じだが、"help" を含めて知らないものは +// スルーし、エラーが起きてもエラーメッセージも出力しない。 +void +MainApp::ParseLogoptEarly() +{ + std::vector 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& 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; + } + + // "" なら"*" 全部に展開する。 + static const std::vector 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); } } - // 見つからない場合はエラー - if (!found) { - printf("invalid logname '%s'\n", name); - goto abort; + return true; + } + } + + // エイリアスリストを作る + using aliaslist_t = std::vector>; + aliaslist_t alias_list; + for (const auto& obj : gMainApp.GetObjects()) { + const std::vector& 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); + } } - rv = true; - abort: - free(buf); - return rv; + // 完全一致をまず調べる + 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 +MainApp::GetLogNames() +{ + std::vector 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 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; } // 関連するファイルのパスを取得。 @@ -453,24 +1278,23 @@ MainApp::ParseLogopt() // ファイルが存在すればその時点で戻り値とする。ファイルが存在したけど // 何らか不都合があったとしても (例えばファイルサイズが 0 だとか // パーミッションが足りないとか) 次を試すとかはしない。 -// 2. name が '~' から始まっていればホームディレクトリに展開。 -// 3. name が相対パスなら VM ディレクトリからの相対パスとする。 -// 4. name は絶対パスのはずなので、そのまま使用する。 +// 2. 通常手順(相対パスは VM ディレクトリ起点) でパスを展開。 +// ファイルの有無は不問。 std::string MainApp::SearchFile(const std::string& name) const { - std::string path; - struct stat st; - // 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; } - // 2. 親ディレクトリ + // 1b. 親ディレクトリ path = GetVMDir() + "../" + name; if (stat(path.c_str(), &st) == 0) { return path; @@ -480,28 +1304,134 @@ MainApp::SearchFile(const std::string& n return ""; } - // 2. 先頭の '~' を $HOME に展開 - path = name; - if (path[0] == '~' && path[1] == '/') { - const char *home = getenv("HOME"); - if (home == NULL) { - home = ""; - } - path = string_format("%s%s", home, path.c_str() + 1); + // 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; } - // 3. 相対パスなら、VM ディレクトリからの相対 - if (path[0] != '/') { - path = GetVMDir() + 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 path; + return newpath; } -// 現在の VM が指定の feature を持っているか? +// 現在の VM が指定のケーパビリティを持っているか? bool -MainApp::HasVMFeature(vmf_t feature) const +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 { - uint32 vmf = 1 << GetVMType(); - return (vmf & feature) != 0; + // 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; }