// Parse the command line. // // Call parse_cmdline to parse the command line // Call parse_print_cmdline to print drive parameter information in command // line format // // 02/23/24 DJG Changed default buffers to match autostart script values // 09/12/23 JST Changes to support 5.10 kernel and --sync option // 05/17/21 DJG removed --fill and added optional argument after --initialize // with controller type // 05/13/21 DJG Add --fill to set value used to fill emulator data for // --initialize // 04/15/19 DJG Added RPM option // 01/04/15 DJG Changed buffer to pool, added begin_time and rate options. // 11/09/14 DJG Added command option to set number of buffers and max delay // added note and options command line options. // // Copyright 2021 David Gesswein. // This file is part of MFM disk utilities. // // MFM disk utilities is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // MFM disk utilities is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with MFM disk utilities. If not, see . #include #include #include #include #include #include #include "msg.h" #include "emu_tran_file.h" #define DEF_DATA #include "parse_cmdline.h" #undef DEF_DATA #include "version.h" #define ARRAYSIZE(x) (sizeof(x) / sizeof(x[0])) static void parse_drive_list(char *arg, DRIVE_PARAMS *drive_params); static void parse_buffer_list(char *arg, DRIVE_PARAMS *drive_params); static void parse_filename_list(char *arg, DRIVE_PARAMS *drive_params); static int parse_controller(char *arg); // Main routine for parsing command lines // // argc, argv: Main argc, argv // drive_parameters: Drive parameters where most of the parsed values are stored void parse_cmdline(int argc, char *argv[], DRIVE_PARAMS *drive_params) { #define MIN_OPTS (0x03) #define MIN_INITIALIZE_OPTS (0x0f) // If you change this fix parse_print_cmdline struct option long_options[] = { {"file", 1, NULL, 'f'}, {"drive", 1, NULL, 'd'}, {"heads", 1, NULL, 'h'}, {"cylinders", 1, NULL, 'c'}, {"rate", 1, NULL, 'r'}, {"begin_time", 1, NULL, 'b'}, {"initialize", 2, NULL, 'i'}, {"pool", 1, NULL, 'p'}, {"quiet", 1, NULL, 'q'}, {"version", 0, NULL, 'v'}, {"note", 1, NULL, 'n'}, {"options", 1, NULL, 'o'}, {"rpm", 1, NULL, 'R'}, {"sync", 0, NULL, 's'}, {NULL, 0, NULL, 0} }; char short_options[] = "f:d:h:c:r:b:i::p:q:vn:o:R:s"; int rc; // Loop counters int i; int options_index; // Bit vector of which options were specified uint32_t opt_mask = 0; // Enable all errors other than debug msg_set_err_mask(~0 ^ MSG_DEBUG); // Set defaults memset(drive_params, 0, sizeof(*drive_params)); // Default number of buffers and maximum seek delay time drive_params->buffer_count = 200; drive_params->buffer_max_time = .6; drive_params->sample_rate_hz = 10000000; drive_params->sync = 0; //drive_params->initialize and ->num_drives need to be zero // Handle the options. The long options are converted to the short // option name for the switch by getopt_long. options_index = -1; while ((rc = getopt_long(argc, argv, short_options, long_options, &options_index)) != -1) { // Short options don't set options_index so look it up if (options_index == -1) { for (i = 0; i < ARRAYSIZE(long_options); i++) { if (rc == long_options[i].val) { options_index = i; break; } } if (options_index == -1) { msg(MSG_FATAL,"Valid options:\n"); for (i = 0; long_options[i].name != NULL; i++) { msg(MSG_FATAL, "%c %s\n", long_options[i].val, long_options[i].name); } exit(1); } } opt_mask |= 1 << options_index; switch(rc) { case 'h': drive_params->num_head = atoi(optarg); if (drive_params->num_head <= 0 || drive_params->num_head > MAX_HEAD) { msg(MSG_FATAL,"Number of heads must be 1 to %d\n", MAX_HEAD); exit(1); } break; case 'c': drive_params->num_cyl = atoi(optarg); if (drive_params->num_cyl <= 0) { msg(MSG_FATAL,"Number of cylinders must be greater than 0 %d\n"); exit(1); } break; case 'f': parse_filename_list(optarg, drive_params); break; case 'd': parse_drive_list(optarg, drive_params); break; case 'i': if (optarg != NULL) { drive_params->initialize = parse_controller(optarg); } else { drive_params->initialize = CONTROLLER_DEFAULT; } break; case 'b': drive_params->start_time_ns = strtoul(optarg, NULL, 0); break; case 'p': parse_buffer_list(optarg, drive_params); break; case 'q': msg_set_err_mask(~strtol(optarg, NULL, 0)); break; case 'v': msg(MSG_INFO_SUMMARY,"Version %s\n",VERSION); break; case 'n': drive_params->note = optarg; break; case 'o': drive_params->options = optarg; break; case 'r': drive_params->sample_rate_hz = strtoul(optarg, NULL, 0); break; case 'R': drive_params->rpm = strtoul(optarg, NULL, 0); break; case 's': drive_params->sync = 1; break; case '?': exit(1); break; default: msg(MSG_FATAL, "Didn't process argument %c\n", rc); exit(1); } options_index = -1; } if (optind < argc) { msg(MSG_FATAL, "Uknown option %s specified\n",argv[optind]); exit(1); } if ((opt_mask & MIN_OPTS) != MIN_OPTS) { msg(MSG_FATAL, "Program requires options:"); for (i = 0; i < 32; i++) { int bit = (1 << i); if (!(opt_mask & bit) && (MIN_OPTS & bit)) { msg(MSG_FATAL, " %s", long_options[i].name); } } msg(MSG_FATAL, "\n"); exit(1); } if (drive_params->initialize && (opt_mask & MIN_INITIALIZE_OPTS) != MIN_INITIALIZE_OPTS) { msg(MSG_FATAL, "Initialize requires options:"); for (i = 0; i < 32; i++) { int bit = (1 << i); if (!(opt_mask & bit) && (MIN_INITIALIZE_OPTS & bit)) { msg(MSG_FATAL, " %s", long_options[i].name); } } msg(MSG_FATAL, "\n"); exit(1); } drive_params->buffer_time = drive_params->buffer_max_time / drive_params->buffer_count; } // Routine for parsing comma separated buffer information // // arg: Argument string // drive_params: Drive parameters to store buffer information in static void parse_buffer_list(char *arg, DRIVE_PARAMS *drive_params) { int i; char *str, *tok; str = arg; i = 0; while (1) { tok = strtok(str,","); if (tok == NULL) { break; } switch(i) { case 0: drive_params->buffer_count = atoi(tok); break; case 1: drive_params->buffer_max_time = atof(tok); break; default: msg(MSG_FATAL, "Maximum of %d buffer parameters may be specified\n", i); exit(1); } str = NULL; // For next strtok call i++; } } // Routine for parsing comma separated drive number list // // arg: Argument string // drive_params: Drive parameters to store drive numbers in static void parse_drive_list(char *arg, DRIVE_PARAMS *drive_params) { int i; char *str, *tok; str = arg; i = 0; while (1) { tok = strtok(str,","); if (tok == NULL) { break; } if (i >= ARRAYSIZE(drive_params->drive)) { msg(MSG_FATAL, "Maximum of %d drives may be specified\n", ARRAYSIZE(drive_params->drive)); exit(1); } str = NULL; // For next strtok call drive_params->drive[i] = strtoull(tok, NULL, 0); i++; } if (drive_params->num_drives == 0) { drive_params->num_drives = i; } else { if (drive_params->num_drives != i) { msg(MSG_FATAL,"Number of drive selects specified must match number of filenames\n"); exit(1); } } } // Routine for parsing comma separated file name list // // arg: Argument string // drive_params: Drive parameters to store file names to static void parse_filename_list(char *arg, DRIVE_PARAMS *drive_params) { int i; char *str, *tok; str = arg; i = 0; while (1) { tok = strtok(str,","); if (tok == NULL) { break; } if (i >= ARRAYSIZE(drive_params->drive)) { msg(MSG_FATAL, "Maximum of %d drives may be specified\n", ARRAYSIZE(drive_params->drive)); exit(1); } str = NULL; // For next strtok call drive_params->filename[i] = tok; i++; } if (drive_params->num_drives == 0) { drive_params->num_drives = i; } else { if (drive_params->num_drives != i) { msg(MSG_FATAL,"Number of drive selects specified must match number of filenames\n"); exit(1); } } } // Print to buffer and exit if no space left. // ptr: buffer to write to // left: how many characters left // format: format string // ...: arguments to print void safe_print(char **ptr, int *left, char *format, ...) { va_list va; int rc; va_start(va, format); rc = vsnprintf(*ptr, *left, format, va); *left -= rc; if (left <= 0) { msg(MSG_FATAL, "Command line exceeded buffer\n"); exit(1); } *ptr += rc; va_end(va); } // Print command line to give current drive parameter settings // // drive_params: Drive parameters char *parse_print_cmdline(DRIVE_PARAMS *drive_params, int print) { static char cmdline[2048]; int cmdleft = sizeof(cmdline)-1; char *cmdptr = cmdline; safe_print(&cmdptr, &cmdleft, "--heads %d --cylinders %d ", drive_params->num_head, drive_params->num_cyl); if (drive_params->start_time_ns) { safe_print(&cmdptr, &cmdleft, " --begin_time %u", drive_params->start_time_ns); } safe_print(&cmdptr, &cmdleft, " --rate %u", drive_params->sample_rate_hz); if (drive_params->options != NULL) { safe_print(&cmdptr, &cmdleft, "%s ",drive_params->options); } #if 0 if (drive_params->note != NULL) { char *p; safe_print(&cmdptr, &cmdleft, " --note \""); for (p = drive_params->note; *p != 0; p++) { if (*p == '"') { safe_print(&cmdptr, &cmdleft, "\\%c", *p); } else { safe_print(&cmdptr, &cmdleft, "%c", *p); } } safe_print(&cmdptr, &cmdleft, "\""); } msg(MSG_INFO_SUMMARY," --transitions_file %s --extracted_data_file %s", drive_params->transitions_filename, drive_params->extract_filename); msg(MSG_INFO_SUMMARY," --emulation_file %s", drive_params->emulation_file); #endif if (print) { msg(MSG_INFO_SUMMARY, "Command line to generate file:\n%s\n", cmdline); } return cmdline; } // Parse controller value (track/sector header format). The formats are named // after the controller that wrote the format. Multiple controllers may use // the same format. // // arg: Controller string // return: Controller number static int parse_controller(char *arg) { int i; int controller = -1; for (i = 0; mfm_controller_info[i].name != NULL; i++) { if (strcasecmp(mfm_controller_info[i].name, arg) == 0) { controller = mfm_controller_info[i].value; } } if (controller == -1) { msg(MSG_FATAL, "Unknown controller for initialize %s. Choices are\n",arg); for (i = 0; mfm_controller_info[i].name != NULL; i++) { msg(MSG_FATAL,"%s\n",mfm_controller_info[i].name); } exit(1); } return controller; }