|
|
1.1 root 1: //
2: // nono
1.1.1.3 root 3: // Copyright (C) 2020 nono project
4: // Licensed under nono-license.txt
1.1 root 5: //
6:
7: #include "mainapp.h"
1.1.1.2 root 8: #include "mystring.h"
1.1.1.8 root 9: #include "netdriver_selector.h"
1.1.1.2 root 10: #include <getopt.h>
11: #include <sys/stat.h>
1.1.1.8 root 12: #include <algorithm>
1.1.1.2 root 13:
14: // グローバルインスタンス
15: MainApp gMainApp;
16:
17: // ヘルプメッセージ
18: void
19: MainApp::ShowHelp(bool all) const
20: {
21: ShowVersion();
22:
23: #define p(msg) printf(" " msg "\n")
24:
25: printf("usage: %s [<options>]\n", getprogname());
1.1.1.6 root 26: p("-c <path> vm directory or configuration file");
1.1.1.2 root 27: p("-f fast mode");
28: if (IsGUI())
1.1.1.4 root 29: p("--fontsize <height> fontsize in monitors {12,16} (default:12)");
1.1.1.2 root 30: p("-h show brief help message");
31: p("--help show all help message including for developers");
32: if (IsGUI())
33: p("-s,--scale <scale> screen scale, default 1.0");
34: p("--show-config show configuration variables");
1.1.1.8 root 35: p("--show-hostnet show list of hostnet drivers");
1.1.1.2 root 36: p("-v show version");
37: p("-V <key>=<val> overwrite config option");
1.1.1.9 root 38: p("-X <file> [arg..] load and execute host binary (a.out or ELF)");
1.1.1.2 root 39:
40: if (all) {
41: printf("\n(options for developers)\n");
1.1.1.4 root 42: p("-b <addr>[,<skip>] set breakpoint");
1.1.1.2 root 43: p("-B <rom#> benchmark mode");
44: p("-C output log to console");
45: p("-d debugger prompt on startup");
46: p("-D debugger on console");
1.1.1.10 root 47: p("-H human68k console emulation");
1.1.1.2 root 48: p("-L <name>=<lv>[,..] set loglevel (-Lhelp displays names)");
1.1.1.11! root 49: p("--load-only <file> load host binary (a.out or ELF) but not execute");
1.1.1.2 root 50: p("-M <name>[,..] monitors to display at startup (-Mhelp displays names)");
51: }
52: }
53:
54: // long option は enum と struct option をアルファベット順に並べる。
55: // enum は getopt() の1文字のオプションと衝突しなければいいので適当に
56: // 0x80 から始めておく。
57: enum {
58: OPT_fontsize = 0x80,
1.1.1.11! root 59: OPT_load_only,
1.1.1.2 root 60: OPT_help,
61: OPT_show_config,
1.1.1.3 root 62: OPT_show_config_all,
1.1.1.8 root 63: OPT_show_hostnet,
1.1.1.2 root 64: };
65: static struct option longopts[] = {
66: { "fontsize", required_argument, NULL, OPT_fontsize },
1.1.1.11! root 67: { "load-only", required_argument, NULL, OPT_load_only },
1.1.1.2 root 68: { "help", no_argument, NULL, OPT_help },
69: { "show-config", no_argument, NULL, OPT_show_config },
1.1.1.3 root 70: // --show-config-all は開発用なのでヘルプには載せない
71: { "show-config-all",no_argument, NULL, OPT_show_config_all },
1.1.1.8 root 72: { "show-hostnet", no_argument, NULL, OPT_show_hostnet },
1.1.1.2 root 73: { NULL, 0, NULL, 0 },
74: };
75:
1.1.1.7 root 76: // VM の初期化、ステージ1。
77: // VM 作成と設定確定あたりまで。スレッド生成を伴わないもの。
1.1.1.2 root 78: // 所々 CLI と GUI で処理が違う。
1.1.1.5 root 79: // 戻り値は以下のいずれか。
80: // MainApp::PASS .. 実行を継続(通常パス)
81: // EXIT_SUCCESS / EXIT_FAILURE .. この終了コードでアプリケーションを終了
82: int
1.1.1.7 root 83: MainApp::Init1(bool is_cli_, int ac, char *av[])
1.1.1.2 root 84: {
85: is_cli = is_cli_;
86:
87: if (!ParseOpt(ac, av)) {
1.1.1.5 root 88: return EXIT_FAILURE;
1.1.1.2 root 89: }
1.1 root 90:
1.1.1.2 root 91: // ログ機構は引数処理後なるはや
92: // CLI ならログは常に標準出力へ(も)出力。
93: // GUI なら -C で指定。
1.1.1.3 root 94: gLogger.UseStdout(IsCLI() ? true : log_to_console);
1.1.1.2 root 95:
96: // 設定を作成。コンストラクタで初期値を用意
97: gConfig.reset(new Config());
98:
1.1.1.6 root 99: // 引数で指定されたディレクトリの設定ファイルがあれば読み込んで設定を更新
100: ConfigFile file(vmfile);
1.1.1.2 root 101: if (file.Load() == false) {
1.1.1.5 root 102: return EXIT_FAILURE;
1.1.1.2 root 103: }
104: if (gConfig->Update(file) == false) {
1.1.1.5 root 105: return EXIT_FAILURE;
1.1.1.2 root 106: }
107: // 最後にコマンドライン引数で更新
108: if (gConfig->Update(config_options) == false) {
1.1.1.5 root 109: return EXIT_FAILURE;
1.1.1.2 root 110: }
1.1 root 111:
1.1.1.8 root 112: // 1. VM 種別を決定
1.1.1.9 root 113: // VM 種別文字列から vmtype を決定
114: const ConfigItem& item = gConfig->Find("vmtype");
115: std::string vmstr = string_tolower(item.AsString());
116: if (vmstr == "x68030") {
117: vmtype = VMTYPE_X68030;
118: } else if (vmstr == "luna") {
119: vmtype = VMTYPE_LUNA1;
120: } else if (vmstr == "luna88k") {
121: vmtype = VMTYPE_LUNA88K;
1.1.1.2 root 122: } else {
1.1.1.9 root 123: if (item.GetFrom() == ConfigItem::FromInitial) {
124: // 未指定の時
125: warnx("vmtype must be specified");
1.1.1.2 root 126: } else {
1.1.1.9 root 127: // ユーザ由来の時
128: item.Err("invalid vmtype");
1.1.1.2 root 129: }
1.1.1.9 root 130: return EXIT_FAILURE;
1.1.1.2 root 131: }
1.1 root 132:
1.1.1.8 root 133: // 2. VM 作成
1.1.1.2 root 134: switch (vmtype) {
135: case VMTYPE_X68030:
136: gVM.reset(new VM_X68030());
137: break;
138:
1.1.1.3 root 139: case VMTYPE_LUNA1:
1.1.1.6 root 140: gVM.reset(new VM_LUNA1());
1.1.1.2 root 141: break;
142:
143: case VMTYPE_LUNA88K:
144: gVM.reset(new VM_LUNA88K());
145: break;
1.1 root 146:
1.1.1.2 root 147: default:
148: __unreachable();
149: }
150:
1.1.1.8 root 151: // 3. 動的なコンストラクション
1.1.1.5 root 152: if (!gVM->Create()) {
1.1.1.7 root 153: gVM.reset();
1.1.1.5 root 154: return EXIT_FAILURE;
155: }
156:
1.1.1.8 root 157: // デバッガ (ここでもモニタを登録する)
158: debugger_create();
159:
1.1.1.5 root 160: // 設定内容を表示して終了
161: // (VM コンストラクタで変数の増減があるのでそれより後、
162: // Create() でも SCSI パラメータを減らすので、それより後)
1.1.1.3 root 163: gConfig->Fix();
1.1.1.2 root 164: if (show_config) {
1.1.1.3 root 165: gConfig->Show(show_config - 1);
1.1.1.7 root 166: gVM.reset();
1.1.1.5 root 167: return EXIT_SUCCESS;
1.1.1.2 root 168: }
169:
1.1.1.8 root 170: // 4. ログレベルを設定。コンストラクト後すぐに行う。
1.1.1.2 root 171: // -L help もここで処理。
172: if (!ParseLogopt()) {
1.1.1.7 root 173: gVM.reset();
1.1.1.5 root 174: return EXIT_FAILURE;
1.1.1.2 root 175: }
176:
1.1.1.8 root 177: // 5. ホストネットワークドライバの作成。
178: //
179: // ホストネットワークドライバを作成する時にはログ表示が欲しいが、通常の
180: // フローではオブジェクトが出揃った後でログレベルを割り当てるので、
181: // にわたま問題になってしまう。そこでこれだけ特別対応する。
182: // 手順 3. のコンストラクションで EthernetDevice が NetDriverSelector を
183: // 作成し、手順 4. で通常通りにログレベルの割り当てを受ける。このログを
184: // 使ってここ(手順5)で実際の NetDriver を作成し、同時にログレベルも引き
185: // 継ぐことにする。
186: if ((bool)gNetDriverSelector) {
187: // 実際の NetDriver を作成
188: NetDriver *newdriver = gNetDriverSelector->InitDriver();
189: if (newdriver == NULL) {
190: gVM.reset();
191: return EXIT_FAILURE;
192: }
193: // 成功すればセット
194: gNetDriver.reset(newdriver);
195:
196: // NetDriverSelector はこれ以降まったく使わないがとりあえず放置。
197: }
198:
1.1.1.5 root 199: return PASS;
1.1.1.2 root 200: }
201:
1.1.1.7 root 202: // VM の初期化、ステージ2。スレッド生成を伴う。
203: // Init1() で引数を受け付けてから Init2() でデバッガスレッドなどを起動するが、
1.1.1.2 root 204: // -Mhelp とかが指定された場合はここでスレッド開始する前にプロセスを終了する
1.1.1.7 root 205: // 必要があるので分けてある。wxapp.cpp も参照。
1.1.1.2 root 206: bool
1.1.1.7 root 207: MainApp::Init2()
1.1.1.2 root 208: {
209: // VM 初期化。
210: // これ以降はスレッドを開始したかもしれないので false を返す際には
211: // VM をデストラクトすること。
212: if (!gVM->Init()) {
213: gVM.reset();
214: return false;
215: }
216: // デバッガは VM オブジェクトリストに入っていない
217: debugger_init();
218:
219: // 起動時設定の適用
220: if (!gVM->Apply()) {
221: gVM.reset();
222: return false;
223: }
224:
225: return true;
226: }
227:
228: // コマンドライン引数を処理する。
229: // 知らない引数とかがあればこちらで usage を表示して false を返す。
230: bool
231: MainApp::ParseOpt(int ac, char *av[])
232: {
1.1.1.6 root 233: struct stat st;
234: const char *cpath;
1.1.1.2 root 235: int b;
236: int c;
237:
238: fontsize = 12;
239: screen_scale = 1.0;
1.1.1.6 root 240: cpath = ".";
1.1.1.2 root 241:
1.1.1.9 root 242: while ((c = getopt_long(ac, av, "b:B:c:CdDfhHL:M:s:vV:X:",
1.1.1.2 root 243: longopts, NULL)) != -1) {
244: switch (c) {
245: case 'b':
1.1.1.4 root 246: debug_breakaddr.push_back(optarg);
1.1.1.2 root 247: break;
248:
249: case 'B':
250: b = atoi(optarg);
251: if (b < 2 || b > 6) {
252: fprintf(stderr, "-B <benchmark_mode>: 2..6\n");
253: return false;
254: }
255: benchmark_mode = b;
256: break;
257:
258: case 'c':
1.1.1.6 root 259: cpath = optarg;
260: // 空文字列なら再び初期値に
261: if (cpath[0] == '\0') {
262: cpath = ".";
263: }
1.1.1.2 root 264: break;
265:
266: case 'C':
267: log_to_console = true;
268: break;
269:
270: case 'd':
271: debug_on_start = true;
272: break;
273:
274: case 'D':
275: debug_on_console = true;
276: break;
277:
278: case 'f':
279: fast_mode = true;
280: break;
281:
1.1.1.9 root 282: case 'H':
283: human_mode = true;
284: break;
285:
1.1.1.2 root 286: case 'L':
287: if (logopt[0] != '\0') {
288: strlcat(logopt, ",", sizeof(logopt));
289: }
290: strlcat(logopt, optarg, sizeof(logopt));
291: break;
292:
293: case 'M':
294: if (!monitor_opt.empty()) {
295: monitor_opt += ",";
296: }
297: monitor_opt += optarg;
298: break;
299:
300: case 's':
301: if (IsGUI()) {
302: char *end;
303: errno = 0;
304: screen_scale = strtod(optarg, &end);
305: if (end == optarg || end[0] != '\0' || errno == ERANGE) {
306: warnx("-s: invalid argument");
307: return false;
308: }
309: // 上限は適当
310: if (screen_scale <= 0.0 || screen_scale >= 10.0) {
311: warnx("-s: invalid scale");
312: return false;
313: }
314: }
315: break;
316:
317: case 'X':
1.1.1.11! root 318: load_and_exec = true;
! 319: FALLTHROUGH;
! 320: case OPT_load_only:
1.1.1.9 root 321: exec_file = optarg;
1.1.1.2 root 322: for (int i = optind; i < ac; i++) {
323: if (i != optind) {
1.1.1.9 root 324: exec_arg += " ";
1.1.1.2 root 325: }
1.1.1.9 root 326: exec_arg += av[i];
1.1.1.2 root 327: }
328: optind = ac;
329: break;
330:
331: case 'v':
332: ShowVersion();
333: exit(0);
334:
335: case 'V':
336: config_options.push_back(optarg);
337: break;
338:
339: case OPT_fontsize:
340: fontsize = atoi(optarg);
341: break;
342:
343: case OPT_help:
344: ShowHelp(true);
345: return false;
346:
347: case OPT_show_config:
1.1.1.3 root 348: show_config = 1;
349: break;
350:
351: case OPT_show_config_all:
352: show_config = 2;
1.1.1.2 root 353: break;
354:
1.1.1.8 root 355: case OPT_show_hostnet:
356: ShowHostnet();
357: return false;
358:
1.1.1.2 root 359: case 'h':
360: default:
361: ShowHelp(false);
362: return false;
363: }
364: }
365:
1.1.1.6 root 366: // 引数がディレクトリなら、それを VM ディレクトリとし、その中の
367: // nono.cfg を設定ファイルとする。
368: // 引数がファイルなら、それを設定ファイルとし、そのファイルがある
369: // ディレクトリを VM ディレクトリとする。
370: // -c DIR => vmdir = DIR, vmfile = DIR/nono.cfg
371: // -c DIR/FILE => vmdir = DIR, vmfile = FILE
372: if (stat(cpath, &st) < 0) {
373: warn("stat %s", cpath);
374: return false;
1.1.1.2 root 375: }
1.1.1.6 root 376: if (S_ISDIR(st.st_mode)) {
377: vmdir = std::string(cpath);
378: if (vmdir.back() != '/') {
379: vmdir += '/';
380: }
381: vmfile = vmdir + "nono.cfg";
382: } else if (S_ISREG(st.st_mode)) {
383: vmfile = std::string(cpath);
384: auto pos = vmfile.rfind('/');
385: if (pos != std::string::npos) {
386: vmdir = vmfile.substr(0, pos + 1);
387: } else {
388: vmdir = "./";
389: }
390: } else {
391: warnx("-c %s: path must be file or directory", cpath);
392: return false;
1.1.1.2 root 393: }
394:
395: // CLI 版で -Mhelp つけても黙って起動するのはさすがにどうかと思う。
396: // とは言え GUI 版のコマンドラインを CLI 版用に書き換えた時に -M を
397: // 消して回らないと起動できないのも面倒なので -M オプション自体は
398: // 無意味だけど許容する。うーん。
399: if (IsCLI()) {
400: if (monitor_opt == "help") {
401: warnx("-Mhelp is not available on CLI");
402: return false;
403: }
404: if (!monitor_opt.empty()) {
405: warnx("-M option is ignored on CLI");
406: return true;
407: }
408: }
409:
1.1.1.9 root 410: // Human モードでは実行ファイル名が必要
411: if (human_mode) {
412: if (exec_file == NULL) {
413: warnx("-H option needs -X");
414: return false;
415: }
416: }
417:
1.1.1.2 root 418: return true;
419: }
420:
421: // バージョンを表示
422: void
423: MainApp::ShowVersion() const
424: {
425: // ここは実行ファイル名によらず nono にする
426: fprintf(stderr, "nono version %d.%d.%d (%s)\n",
427: NONO_MAJOR_VER, NONO_MINOR_VER, NONO_PATCH_VER, NONO_DATE);
428: }
429:
1.1.1.8 root 430: // ログレベル指定文字列を処理する。
431: // str はログレベル指定文字列を ',' で連結した "foo=1,bar=2" 形式の文字列で、
432: // これを分解してそれぞれ担当するオブジェクトのログレベルにセットする。
433: // "help" があれば設定は行わず一覧を表示。
1.1 root 434: bool
1.1.1.2 root 435: MainApp::ParseLogopt()
1.1 root 436: {
1.1.1.8 root 437: std::vector<std::string> items;
438:
439: // 分解して..
440: items = string_split(logopt, ',');
441:
442: // "help" があればヘルプを表示して終了
443: for (const auto& item : items) {
444: if (item == "help") {
445: std::vector<std::string> list = GetLogNames();
446: // less したいだろうから stderr ではなく stdout に出力する
447: for (const auto& name : list) {
448: printf(" %s\n", name.c_str());
449: }
450: return false;
1.1 root 451: }
1.1.1.8 root 452: }
453:
454: // ログレベルを設定
455: std::string errmsg;
456: if (SetLogopt(items, &errmsg) == false) {
457: warnx("%s", errmsg.c_str());
1.1 root 458: return false;
459: }
460:
1.1.1.8 root 461: return true;
462: }
1.1 root 463:
1.1.1.8 root 464: // ログレベルを設定する。
465: // ログレベル指定文字列を 1つずつに分解したリスト items を処理する。
466: // "help" があるケースはここに来るまでに処理してあるので、ここには来ない。
467: // 成功なら何も表示せず true を返す。失敗なら *errmsg にエラーメッセージを
468: // 格納して false を返す。
469: // MainApp 内と Debugger からも呼ばれる。
470: /*static*/ bool
471: MainApp::SetLogopt(const std::vector<std::string>& items, std::string *errmsg)
472: {
473: for (const auto& item : items) {
474: if (SetLogopt1(item.c_str(), errmsg) == false) {
475: return false;
476: }
477: }
478: if (0) { // デバッグ用
479: for (const auto o : gObjects) {
480: printf("%-16s %d\n", o->GetName().c_str(), o->loglevel);
1.1 root 481: }
1.1.1.8 root 482: }
483: return true;
484: }
485:
486: // "logname[=loglevel]" 形式をパースしてオブジェクトにログレベルを設定する。
487: // loglevel は省略なら 1 とする。
488: // logname が "all" なら全オブジェクトにセットする。
489: // そうでない場合は case ignore で完全一致するか前方一致で1つに確定すれば、
490: // そのオブジェクトにログレベルを設定して true を返す。
491: // 見付からないか候補が複数ある場合はエラーメッセージを *errmsg に出力して
492: // false を返す。
493: // この関数は (このすぐ上の SetLogopt() を経由して)
494: // MainApp と Debugger から呼ばれることに注意。
495: /*static*/ bool
496: MainApp::SetLogopt1(const std::string& item, std::string *errmsg)
497: {
498: std::string name;
499: const char *v;
500: int level;
501:
502: v = strchr(item.c_str(), '=');
503: if (v) {
504: name = std::string(item.c_str(), v - item.c_str());
505: level = atoi(++v);
506: } else {
507: name = item;
508: level = 1;
509: }
510: // ここで name は変数名、level は値(省略されたら1)
511:
512: if (name.empty()) {
513: *errmsg = "logname must be specified";
514: return false;
515: }
1.1 root 516:
1.1.1.8 root 517: // "all" なら全部にセット
518: if (name == "all") {
519: for (auto obj : gObjects) {
520: if (obj->GetName().empty() == false) {
521: obj->loglevel = level;
522: }
1.1 root 523: }
1.1.1.8 root 524: return true;
525: }
526:
527: std::vector<Object *> found; // 候補オブジェクト
528: std::vector<std::string> candidates; // 候補名
1.1 root 529:
1.1.1.8 root 530: // 前方一致する名前(とそのオブジェクト)を列挙する
531: for (auto *obj : gObjects) {
532: const std::vector<std::string>& aliases = obj->GetAliases();
533:
534: for (const auto& a : aliases) {
535: // 完全一致したら即採用
536: // (前方一致しつつ完全一致したらこの時点で採用して打ち切るため)
537: if (strcasecmp(a.c_str(), name.c_str()) == 0) {
538: obj->loglevel = level;
539: return true;
1.1 root 540: }
1.1.1.8 root 541:
542: // 前方一致したら覚えておく
543: if (strncasecmp(a.c_str(), name.c_str(), name.size()) == 0) {
544: candidates.emplace_back(a);
545: found.push_back(obj);
1.1 root 546: }
1.1.1.8 root 547: }
548: }
549:
550: // 見付からない場合はエラー
551: if (found.empty()) {
552: *errmsg = string_format("Unknown logname \"%s\"", name.c_str());
553: return false;
554: }
555:
556: // 1つだけなら確定
557: if (found.size() == 1) {
558: Object *obj = found[0];
559: obj->loglevel = level;
560: return true;
561: }
562:
563: // 複数あれば候補文字列を作成
564: *errmsg = string_format("Ambiguous logname \"%s\": candidates are",
565: name.c_str());
566: for (const auto& cand : candidates) {
567: *errmsg += string_format(" \"%s\"", cand.c_str());
568: }
569: return false;
570: }
571:
572: // ログ名の一覧を取得する。
573: // MainApp と Debugger から呼ばれる。
574: /*static*/ std::vector<std::string>
575: MainApp::GetLogNames()
576: {
577: std::vector<Object *> sortobj;
578:
579: // エイリアスを持つオブジェクトだけ抜き出す
580: for (const auto& obj : gObjects) {
581: if (obj->GetAliases().empty() == false) {
582: sortobj.emplace_back(obj);
583: }
584: }
585:
586: // aliases の一語目で sortobj をソートする
587: std::sort(sortobj.begin(), sortobj.end(),
588: [](const auto& a, const auto *b) {
589: const auto& sa = a->GetAliases()[0];
590: const auto& sb = b->GetAliases()[0];
591: return strcmp(sa.c_str(), sb.c_str()) < 0;
592: }
593: );
594:
595: std::vector<std::string> list;
596: for (const auto *obj : sortobj) {
597: const auto& aliases = obj->GetAliases();
598: std::string str;
599:
600: str += aliases[0];
601: if (aliases.size() > 1) {
602: str += " (alias:";
603: for (int i = 1, sz = aliases.size(); i < sz; i++) {
604: str += ' ';
605: str += aliases[i];
1.1 root 606: }
1.1.1.8 root 607: str += ')';
1.1.1.6 root 608: }
1.1.1.8 root 609: list.emplace_back(str);
1.1 root 610: }
1.1.1.8 root 611: list.emplace_back("all");
1.1 root 612:
1.1.1.8 root 613: return list;
1.1 root 614: }
1.1.1.2 root 615:
616: // 関連するファイルのパスを取得。
1.1.1.3 root 617: // 1. name がファイル名のみなら、VM ディレクトリとその親ディレクトリを検索。
618: // ファイルが存在すればその時点で戻り値とする。ファイルが存在したけど
619: // 何らか不都合があったとしても (例えばファイルサイズが 0 だとか
620: // パーミッションが足りないとか) 次を試すとかはしない。
621: // 2. name が '~' から始まっていればホームディレクトリに展開。
622: // 3. name が相対パスなら VM ディレクトリからの相対パスとする。
623: // 4. name は絶対パスのはずなので、そのまま使用する。
1.1.1.2 root 624: std::string
625: MainApp::SearchFile(const std::string& name) const
626: {
627: std::string path;
628: struct stat st;
629:
1.1.1.3 root 630: // 1. パス区切りを含んでなければ、ファイル名のみ
631: if (name.find('/') == std::string::npos) {
632: // 1a. VM ディレクトリ
633: path = GetVMDir() + name;
634: if (stat(path.c_str(), &st) == 0) {
635: return path;
636: }
637:
638: // 2. 親ディレクトリ
639: path = GetVMDir() + "../" + name;
640: if (stat(path.c_str(), &st) == 0) {
641: return path;
642: }
643:
644: // どちらもなければエラー
645: return "";
646: }
1.1.1.2 root 647:
1.1.1.3 root 648: // 2. 先頭の '~' を $HOME に展開
649: path = name;
650: if (path[0] == '~' && path[1] == '/') {
651: const char *home = getenv("HOME");
652: if (home == NULL) {
653: home = "";
654: }
655: path = string_format("%s%s", home, path.c_str() + 1);
1.1.1.2 root 656: }
657:
1.1.1.3 root 658: // 3. 相対パスなら、VM ディレクトリからの相対
659: if (path[0] != '/') {
660: path = GetVMDir() + path;
1.1.1.2 root 661: }
662:
1.1.1.3 root 663: return path;
664: }
665:
666: // 現在の VM が指定の feature を持っているか?
667: bool
668: MainApp::HasVMFeature(vmf_t feature) const
669: {
670: uint32 vmf = 1 << GetVMType();
671: return (vmf & feature) != 0;
1.1.1.2 root 672: }
1.1.1.8 root 673:
674: // コンパイルされている host netdriver の一覧を表示。
675: void
676: MainApp::ShowHostnet() const
677: {
678: auto list = NetDriverSelector::GetDrivers();
679: for (const auto& name : list) {
680: printf(" %s\n", name.c_str());
681: }
682: }
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.