Annotation of nono/lib/mainapp.cpp, revision 1.1.1.6

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: }

unix.superglobalmegacorp.com

This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.