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