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