// 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 // Call parse_validate_options to perform some validation on mfm_write options // // Copyright 2024 David Gesswein. // This file is part of MFM disk utilities. // // 04/29/24 DJG Made separate file for mfm_write since options different than // mfm_util/mfm_read // // 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 #include #include "msg.h" #include "crc_ecc.h" #include "emu_tran_file.h" #include "mfm_decoder.h" #include "drive.h" #include "parse_cmdline.h" #include "version.h" #define ARRAYSIZE(x) (sizeof(x) / sizeof(x[0])) // 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); } // Delete bit n from v shifting higher bits down #define DELETE_BIT(v, n) (v & ((1 << n)-1)) | (((v & ~((1 << (n+1))-1)) >> 1)) // If you change this fix parse_print_cmdline and check if ext2emu should // delete new option static struct option long_options[] = { {"drive", 1, NULL, 'd'}, {"unbuffered_seek", 0, NULL, 'u'}, {"quiet", 1, NULL, 'q'}, {"emulation_file", 1, NULL, 'm'}, {"precomp_ns", 1, NULL, 'p'}, {"precomp_cyl", 1, NULL, 'c'}, {"version", 0, NULL, 'v'}, {NULL, 0, NULL, 0} }; static char short_options[] = "d:uq:m:p:c:v"; // Main routine for parsing command lines // // argc, argv: Main argc, argv // drive_parameters: Drive parameters where most of the parsed values are stored // delete_options: Options to delete from list of valid options (short option) // initialize: 1 if drive_params should be initialized with defaults // only_deleted: 1 if we only want to process options specified in // delete_options. Other options are ignored, not error // ignore_invalid_options: Don't exit if option is not known void parse_cmdline(int argc, char *argv[], DRIVE_PARAMS *drive_params, char *delete_options, int initialize, int only_deleted, int ignore_invalid_options, int track_layout_format_only) { int rc; // Loop counters int i,j; int options_index; // Bit vector of which options were specified char *tok; char delete_list[sizeof(short_options)]; // If only deleted then copy all options to delete list that aren't // in delete_options if (only_deleted) { j = 0; for (i = 0; i < sizeof(short_options); i++) { if (short_options[i] != ':' && strchr(delete_options, short_options[i]) == 0) { delete_list[j++] = short_options[i]; } } delete_list[j] = 0; delete_options = delete_list; } // Options above are superset for all the programs to ensure options stay consistent if (initialize) { // Enable all errors other than debug msg_set_err_mask(~0 ^ (MSG_DEBUG | MSG_DEBUG_DATA)); memset(drive_params, 0, sizeof(*drive_params)); // Set defaults. A lot of this isn't used by mfm_write drive_params->emu_fd = -1; drive_params->tran_fd = -1; drive_params->ext_fd = -1; drive_params->step_speed = DRIVE_STEP_FAST; drive_params->retries = 50; drive_params->no_seek_retries = 4; drive_params->sector_size = 512; drive_params->emulation_output = 0; drive_params->analyze = 0; drive_params->start_time_ns = 0; drive_params->header_crc.length = -1; // 0 is valid drive_params->first_logical_sector = -1; // Default no precompensation drive_params->early_precomp_ns = 0; drive_params->late_precomp_ns = 0; drive_params->write_precomp_cyl = 9999; } // Handle the options. The long options are converted to the short // option name for the switch by getopt_long. options_index = -1; optind = 1; // Start with first element. We may call again with same argv 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) { // If only deleted specified don't print error. Option will // be ignored below. The valid options would be printed with // only the few options selected which would confuse the user if (!ignore_invalid_options) { //msg(MSG_FATAL, "Error parsing option %c\n",rc); msg(MSG_FATAL,"Valid options:\n"); for (i = 0; long_options[i].name != NULL; i++) { if (strchr(delete_options, long_options[i].val) == 0) { msg(MSG_FATAL, "%c %s\n", long_options[i].val, long_options[i].name); } } exit(1); } } } // If option is deleted or not found either error or ignore if (strchr(delete_options, rc) != 0) { if (!ignore_invalid_options) { msg(MSG_FATAL,"Option '%c' %s not valid for this program\n", rc, long_options[options_index].name); exit(1); } } else { drive_params->opt_mask |= 1 << options_index; switch(rc) { case 'p': tok = strtok(optarg,","); drive_params->early_precomp_ns = atoi(tok); tok = strtok(NULL,","); if (tok != NULL) { drive_params->late_precomp_ns = atoi(tok); } else { drive_params->late_precomp_ns = drive_params->early_precomp_ns; } if (drive_params->late_precomp_ns < 0 || drive_params->late_precomp_ns > 30 || drive_params->early_precomp_ns < 0 || drive_params->early_precomp_ns > 30) { msg(MSG_FATAL,"Precompensation ns must be 0 to 30\n"); if (!ignore_invalid_options) { exit(1); } } drive_params->early_precomp_ns = round(drive_params->early_precomp_ns / 5.0) * 5; drive_params->late_precomp_ns = round(drive_params->late_precomp_ns / 5.0) * 5; msg(MSG_INFO,"Using early ns %d, late ns %d for precompensation\n", drive_params->early_precomp_ns, drive_params->late_precomp_ns); break; case 'c': drive_params->write_precomp_cyl = atoi(optarg); break; case 'u': drive_params->step_speed = DRIVE_STEP_SLOW; break; case 'm': drive_params->emulation_filename = optarg; // Caller will correct if file is actually input. drive_params->emulation_output = 1; break; case 'd': drive_params->drive = atoi(optarg); break; case 'q': msg_set_err_mask(~strtoul(optarg, NULL, 0)); break; case 'v': msg(MSG_INFO_SUMMARY,"Version %s\n",VERSION); break; case '?': if (!ignore_invalid_options) { exit(1); } break; default: msg(MSG_FATAL, "Didn't process argument %c\n", rc); if (!ignore_invalid_options) { exit(1); } } } options_index = -1; } if (optind < argc && !ignore_invalid_options) { msg(MSG_FATAL, "Uknown option %s specified\n",argv[optind]); exit(1); } } // This validates options where we need the options list for messages // // drive_params: Drive parameters // mfm_read: Not used. void parse_validate_options(DRIVE_PARAMS *drive_params, int mfm_read) { // For mfm_util drive doesn't need to be specified. This // option error handling is getting messy. // Drive 1-4 valid if specified. If analyze specified drive will be 0 if (drive_params->drive < 1 || drive_params->drive > 4) { msg(MSG_FATAL, "Drive must be between 1 and 4\n"); exit(1); } }