|
|
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"
9: #include <getopt.h>
10: #include <sys/stat.h>
11:
12: // グローバルインスタンス
13: MainApp gMainApp;
14:
15: // ヘルプメッセージ
16: void
17: MainApp::ShowHelp(bool all) const
18: {
19: ShowVersion();
20:
21: #define p(msg) printf(" " msg "\n")
22:
23: printf("usage: %s [<options>]\n", getprogname());
24: p("-A <file> load and execute host binary (a.out or ELF)");
1.1.1.6 ! root 25: p("-c <path> vm directory or configuration file");
1.1.1.2 root 26: p("-f fast mode");
27: if (IsGUI())
1.1.1.4 root 28: p("--fontsize <height> fontsize in monitors {12,16} (default:12)");
1.1.1.2 root 29: p("-h show brief help message");
30: p("--help show all help message including for developers");
31: if (IsGUI())
32: p("-s,--scale <scale> screen scale, default 1.0");
33: p("--show-config show configuration variables");
34: p("-v show version");
35: p("-V <key>=<val> overwrite config option");
36:
37: if (all) {
38: printf("\n(options for developers)\n");
1.1.1.4 root 39: p("-b <addr>[,<skip>] set breakpoint");
1.1.1.2 root 40: p("-B <rom#> benchmark mode");
41: p("-C output log to console");
42: p("-d debugger prompt on startup");
43: p("-D debugger on console");
44: p("-L <name>=<lv>[,..] set loglevel (-Lhelp displays names)");
45: p("-M <name>[,..] monitors to display at startup (-Mhelp displays names)");
46: p("-X <human68k executable> human68k console emulation");
47: }
48: }
49:
50: // long option は enum と struct option をアルファベット順に並べる。
51: // enum は getopt() の1文字のオプションと衝突しなければいいので適当に
52: // 0x80 から始めておく。
53: enum {
54: OPT_fontsize = 0x80,
55: OPT_help,
56: OPT_show_config,
1.1.1.3 root 57: OPT_show_config_all,
1.1.1.2 root 58: };
59: static struct option longopts[] = {
60: { "fontsize", required_argument, NULL, OPT_fontsize },
61: { "help", no_argument, NULL, OPT_help },
62: { "show-config", no_argument, NULL, OPT_show_config },
1.1.1.3 root 63: // --show-config-all は開発用なのでヘルプには載せない
64: { "show-config-all",no_argument, NULL, OPT_show_config_all },
1.1.1.2 root 65: { NULL, 0, NULL, 0 },
66: };
67:
68: // 起動時の処理、VM の実行開始前まで。
69: // 所々 CLI と GUI で処理が違う。
1.1.1.5 root 70: // 戻り値は以下のいずれか。
71: // MainApp::PASS .. 実行を継続(通常パス)
72: // EXIT_SUCCESS / EXIT_FAILURE .. この終了コードでアプリケーションを終了
73: int
1.1.1.2 root 74: MainApp::Init(bool is_cli_, int ac, char *av[])
75: {
76: is_cli = is_cli_;
77:
78: if (!ParseOpt(ac, av)) {
1.1.1.5 root 79: return EXIT_FAILURE;
1.1.1.2 root 80: }
1.1 root 81:
1.1.1.2 root 82: // ログ機構は引数処理後なるはや
83: // CLI ならログは常に標準出力へ(も)出力。
84: // GUI なら -C で指定。
1.1.1.3 root 85: gLogger.UseStdout(IsCLI() ? true : log_to_console);
1.1.1.2 root 86:
87: // 設定を作成。コンストラクタで初期値を用意
88: gConfig.reset(new Config());
89:
1.1.1.6 ! root 90: // 引数で指定されたディレクトリの設定ファイルがあれば読み込んで設定を更新
! 91: ConfigFile file(vmfile);
1.1.1.2 root 92: if (file.Load() == false) {
1.1.1.5 root 93: return EXIT_FAILURE;
1.1.1.2 root 94: }
95: if (gConfig->Update(file) == false) {
1.1.1.5 root 96: return EXIT_FAILURE;
1.1.1.2 root 97: }
98: // 最後にコマンドライン引数で更新
99: if (gConfig->Update(config_options) == false) {
1.1.1.5 root 100: return EXIT_FAILURE;
1.1.1.2 root 101: }
1.1 root 102:
1.1.1.2 root 103: // VM 種別を決定
104: if (IsCLI() && human68k_file) {
105: vmtype = VMTYPE_RXZ;
106: } else {
107: // VM 種別文字列から vmtype を決定
1.1.1.3 root 108: const ConfigItem& item = gConfig->Find("vmtype");
1.1.1.2 root 109: std::string vmstr = string_tolower(item.AsString());
110: if (vmstr == "x68030") {
111: vmtype = VMTYPE_X68030;
112: } else if (vmstr == "luna") {
1.1.1.3 root 113: vmtype = VMTYPE_LUNA1;
1.1.1.2 root 114: } else if (vmstr == "luna88k") {
115: vmtype = VMTYPE_LUNA88K;
116: } else {
117: if (item.GetFrom() == ConfigItem::FromInitial) {
118: // 未指定の時
119: warnx("vmtype must be specified");
120: } else {
121: // ユーザ由来の時
122: item.Err("invalid vmtype");
123: }
1.1.1.5 root 124: return EXIT_FAILURE;
1.1.1.2 root 125: }
126: }
1.1 root 127:
1.1.1.2 root 128: // VM 作成
129: switch (vmtype) {
130: case VMTYPE_X68030:
131: gVM.reset(new VM_X68030());
132: break;
133:
1.1.1.3 root 134: case VMTYPE_LUNA1:
1.1.1.6 ! root 135: gVM.reset(new VM_LUNA1());
1.1.1.2 root 136: break;
137:
138: case VMTYPE_RXZ:
139: gVM.reset(new VM_RXZ());
140: break;
141:
142: case VMTYPE_LUNA88K:
143: gVM.reset(new VM_LUNA88K());
144: break;
1.1 root 145:
1.1.1.2 root 146: default:
147: __unreachable();
148: }
149:
1.1.1.5 root 150: // 動的なコンストラクション
151: if (!gVM->Create()) {
152: return EXIT_FAILURE;
153: }
154:
155: // 設定内容を表示して終了
156: // (VM コンストラクタで変数の増減があるのでそれより後、
157: // Create() でも SCSI パラメータを減らすので、それより後)
1.1.1.3 root 158: gConfig->Fix();
1.1.1.2 root 159: if (show_config) {
1.1.1.3 root 160: gConfig->Show(show_config - 1);
1.1.1.5 root 161: return EXIT_SUCCESS;
1.1.1.2 root 162: }
163:
164: // ログレベルを設定。コンストラクト後すぐに行う。
165: // -L help もここで処理。
166: if (!ParseLogopt()) {
1.1.1.5 root 167: return EXIT_FAILURE;
1.1.1.2 root 168: }
169:
1.1.1.5 root 170: return PASS;
1.1.1.2 root 171: }
172:
173: // VM の実行部分。
174: // Init() で引数を受け付けてから Start() でデバッガスレッドなどを起動するが、
175: // -Mhelp とかが指定された場合はここでスレッド開始する前にプロセスを終了する
176: // 必要があるので、分けてある。
177: bool
178: MainApp::Start()
179: {
180: // VM 初期化。
181: // これ以降はスレッドを開始したかもしれないので false を返す際には
182: // VM をデストラクトすること。
183: if (!gVM->Init()) {
184: gVM.reset();
185: return false;
186: }
187: // デバッガは VM オブジェクトリストに入っていない
188: debugger_init();
189:
190: // 起動時設定の適用
191: if (!gVM->Apply()) {
192: gVM.reset();
193: return false;
194: }
195:
196: return true;
197: }
198:
199: // コマンドライン引数を処理する。
200: // 知らない引数とかがあればこちらで usage を表示して false を返す。
201: bool
202: MainApp::ParseOpt(int ac, char *av[])
203: {
1.1.1.6 ! root 204: struct stat st;
! 205: const char *cpath;
1.1.1.2 root 206: int b;
207: int c;
208:
209: fontsize = 12;
210: screen_scale = 1.0;
1.1.1.6 ! root 211: cpath = ".";
1.1.1.2 root 212:
213: while ((c = getopt_long(ac, av, "A:b:B:c:CdDfhL:M:s:vV:X:",
214: longopts, NULL)) != -1) {
215: switch (c) {
216: case 'A':
217: host_file = optarg;
218: break;
219:
220: case 'b':
1.1.1.4 root 221: debug_breakaddr.push_back(optarg);
1.1.1.2 root 222: break;
223:
224: case 'B':
225: b = atoi(optarg);
226: if (b < 2 || b > 6) {
227: fprintf(stderr, "-B <benchmark_mode>: 2..6\n");
228: return false;
229: }
230: benchmark_mode = b;
231: break;
232:
233: case 'c':
1.1.1.6 ! root 234: cpath = optarg;
! 235: // 空文字列なら再び初期値に
! 236: if (cpath[0] == '\0') {
! 237: cpath = ".";
! 238: }
1.1.1.2 root 239: break;
240:
241: case 'C':
242: log_to_console = true;
243: break;
244:
245: case 'd':
246: debug_on_start = true;
247: break;
248:
249: case 'D':
250: debug_on_console = true;
251: break;
252:
253: case 'f':
254: fast_mode = true;
255: break;
256:
257: case 'L':
258: if (logopt[0] != '\0') {
259: strlcat(logopt, ",", sizeof(logopt));
260: }
261: strlcat(logopt, optarg, sizeof(logopt));
262: break;
263:
264: case 'M':
265: if (!monitor_opt.empty()) {
266: monitor_opt += ",";
267: }
268: monitor_opt += optarg;
269: break;
270:
271: case 's':
272: if (IsGUI()) {
273: char *end;
274: errno = 0;
275: screen_scale = strtod(optarg, &end);
276: if (end == optarg || end[0] != '\0' || errno == ERANGE) {
277: warnx("-s: invalid argument");
278: return false;
279: }
280: // 上限は適当
281: if (screen_scale <= 0.0 || screen_scale >= 10.0) {
282: warnx("-s: invalid scale");
283: return false;
284: }
285: }
286: break;
287:
288: case 'X':
289: human68k_file = optarg;
290: for (int i = optind; i < ac; i++) {
291: if (i != optind) {
292: strlcat(human68k_arg, " ", sizeof(human68k_arg));
293: }
294: strlcat(human68k_arg, av[i], sizeof(human68k_arg));
295: }
296: optind = ac;
297: break;
298:
299: case 'v':
300: ShowVersion();
301: exit(0);
302:
303: case 'V':
304: config_options.push_back(optarg);
305: break;
306:
307: case OPT_fontsize:
308: fontsize = atoi(optarg);
309: break;
310:
311: case OPT_help:
312: ShowHelp(true);
313: return false;
314:
315: case OPT_show_config:
1.1.1.3 root 316: show_config = 1;
317: break;
318:
319: case OPT_show_config_all:
320: show_config = 2;
1.1.1.2 root 321: break;
322:
323: case 'h':
324: default:
325: ShowHelp(false);
326: return false;
327: }
328: }
329:
1.1.1.6 ! root 330: // 引数がディレクトリなら、それを VM ディレクトリとし、その中の
! 331: // nono.cfg を設定ファイルとする。
! 332: // 引数がファイルなら、それを設定ファイルとし、そのファイルがある
! 333: // ディレクトリを VM ディレクトリとする。
! 334: // -c DIR => vmdir = DIR, vmfile = DIR/nono.cfg
! 335: // -c DIR/FILE => vmdir = DIR, vmfile = FILE
! 336: if (stat(cpath, &st) < 0) {
! 337: warn("stat %s", cpath);
! 338: return false;
1.1.1.2 root 339: }
1.1.1.6 ! root 340: if (S_ISDIR(st.st_mode)) {
! 341: vmdir = std::string(cpath);
! 342: if (vmdir.back() != '/') {
! 343: vmdir += '/';
! 344: }
! 345: vmfile = vmdir + "nono.cfg";
! 346: } else if (S_ISREG(st.st_mode)) {
! 347: vmfile = std::string(cpath);
! 348: auto pos = vmfile.rfind('/');
! 349: if (pos != std::string::npos) {
! 350: vmdir = vmfile.substr(0, pos + 1);
! 351: } else {
! 352: vmdir = "./";
! 353: }
! 354: } else {
! 355: warnx("-c %s: path must be file or directory", cpath);
! 356: return false;
1.1.1.2 root 357: }
358:
359: // CLI 版で -Mhelp つけても黙って起動するのはさすがにどうかと思う。
360: // とは言え GUI 版のコマンドラインを CLI 版用に書き換えた時に -M を
361: // 消して回らないと起動できないのも面倒なので -M オプション自体は
362: // 無意味だけど許容する。うーん。
363: if (IsCLI()) {
364: if (monitor_opt == "help") {
365: warnx("-Mhelp is not available on CLI");
366: return false;
367: }
368: if (!monitor_opt.empty()) {
369: warnx("-M option is ignored on CLI");
370: return true;
371: }
372: }
373:
374: return true;
375: }
376:
377: // バージョンを表示
378: void
379: MainApp::ShowVersion() const
380: {
381: // ここは実行ファイル名によらず nono にする
382: fprintf(stderr, "nono version %d.%d.%d (%s)\n",
383: NONO_MAJOR_VER, NONO_MINOR_VER, NONO_PATCH_VER, NONO_DATE);
384: }
385:
386:
387: // 引数 logopt のログ指定文字列をパースする。
1.1 root 388: // arg は "foo=1,bar=2" 形式の文字列で、これを分解して
389: // それぞれ担当するオブジェクトのログレベルにセットする。
390: bool
1.1.1.2 root 391: MainApp::ParseLogopt()
1.1 root 392: {
393: char *buf;
394: char *last;
395: char *p;
396: bool rv;
397:
398: // "help" (完全一致) なら識別子一覧を表示。
1.1.1.2 root 399: if (strcmp(logopt, "help") == 0) {
1.1 root 400: for (auto& obj : gObjects) {
401: if (obj->logname != "?") {
402: printf("%s\n", obj->logname.c_str());
403: }
404: }
405: // エイリアスとか
406: printf("sch -> scheduler\n");
1.1.1.3 root 407: if (gMainApp.HasVMFeature(VMF_LUNA)) {
1.1 root 408: printf("scc -> sio\n");
409: }
410: printf("all\n");
411: return false;
412: }
413:
414: rv = false;
1.1.1.2 root 415: buf = strdup(logopt);
1.1 root 416: for (p = strtok_r(buf, ",", &last);
417: p;
418: p = strtok_r(NULL, ",", &last))
419: {
420: const char *name;
421: char *v;
422: int val;
423:
424: name = p;
425:
426: v = strchr(p, '=');
427: if (v) {
428: *v++ = '\0';
429: val = atoi(v);
430: } else {
431: val = 1;
432: }
433: // ここで name は変数名、val は値(省略されたら1)
434:
435: if (strlen(name) < 1) {
436: printf("invalid logname '%s'\n", name);
437: goto abort;
438: }
439:
440: // 短縮形とかエイリアスとか
441: if (strcmp(name, "sch") == 0)
442: name = "scheduler";
1.1.1.3 root 443: if (gMainApp.HasVMFeature(VMF_LUNA)) {
1.1 root 444: if (strcmp(name, "scc") == 0)
445: name = "sio";
446: }
447:
448: // 比較
449: if (strcmp(name, "all") == 0) {
450: // "all" なら none 以外の全部にセット
451: for (auto& obj : gObjects) {
452: obj->loglevel = val;
453: }
454: } else {
1.1.1.3 root 455: // それ以外は一致するキーを探してセット。
456: // 複数のオブジェクトが同じ logname を持ってもよい (CMMUとか)。
1.1 root 457: std::string sname = name;
458: bool found = false;
459: for (auto& obj : gObjects) {
460: if (obj->logname == sname) {
461: obj->loglevel = val;
462: found = true;
463: }
464: }
465: // 見つからない場合はエラー
466: if (!found) {
467: printf("invalid logname '%s'\n", name);
468: goto abort;
469: }
1.1.1.6 ! root 470: }
1.1 root 471: }
472:
473: rv = true;
474: abort:
475: free(buf);
476: return rv;
477: }
1.1.1.2 root 478:
479: // 関連するファイルのパスを取得。
1.1.1.3 root 480: // 1. name がファイル名のみなら、VM ディレクトリとその親ディレクトリを検索。
481: // ファイルが存在すればその時点で戻り値とする。ファイルが存在したけど
482: // 何らか不都合があったとしても (例えばファイルサイズが 0 だとか
483: // パーミッションが足りないとか) 次を試すとかはしない。
484: // 2. name が '~' から始まっていればホームディレクトリに展開。
485: // 3. name が相対パスなら VM ディレクトリからの相対パスとする。
486: // 4. name は絶対パスのはずなので、そのまま使用する。
1.1.1.2 root 487: std::string
488: MainApp::SearchFile(const std::string& name) const
489: {
490: std::string path;
491: struct stat st;
492:
1.1.1.3 root 493: // 1. パス区切りを含んでなければ、ファイル名のみ
494: if (name.find('/') == std::string::npos) {
495: // 1a. VM ディレクトリ
496: path = GetVMDir() + name;
497: if (stat(path.c_str(), &st) == 0) {
498: return path;
499: }
500:
501: // 2. 親ディレクトリ
502: path = GetVMDir() + "../" + name;
503: if (stat(path.c_str(), &st) == 0) {
504: return path;
505: }
506:
507: // どちらもなければエラー
508: return "";
509: }
1.1.1.2 root 510:
1.1.1.3 root 511: // 2. 先頭の '~' を $HOME に展開
512: path = name;
513: if (path[0] == '~' && path[1] == '/') {
514: const char *home = getenv("HOME");
515: if (home == NULL) {
516: home = "";
517: }
518: path = string_format("%s%s", home, path.c_str() + 1);
1.1.1.2 root 519: }
520:
1.1.1.3 root 521: // 3. 相対パスなら、VM ディレクトリからの相対
522: if (path[0] != '/') {
523: path = GetVMDir() + path;
1.1.1.2 root 524: }
525:
1.1.1.3 root 526: return path;
527: }
528:
529: // 現在の VM が指定の feature を持っているか?
530: bool
531: MainApp::HasVMFeature(vmf_t feature) const
532: {
533: uint32 vmf = 1 << GetVMType();
534: return (vmf & feature) != 0;
1.1.1.2 root 535: }
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.