// 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 options that
// both mfm_util and mfm_read need
//
// Copyright 2024 David Gesswein.
// This file is part of MFM disk utilities.
//
// 02/20/24 DJG Set controller to CONTROLLER_NONE if not valid in file
// 10/09/23 DJG Only support ext2emu interleave parameters. Drop old mfm_read,
// mfm_util format.
// 03/11/23 DJG Fix for EC1841 decoding
// 12/19/21 DJG crc_length now allowed to be 0 so unset length is -1.
// 01/18/21 DJG Only print valid formats for ext2emu
// 03/15/20 DJG Fix fix for emulation_output set when file not specified
// 03/09/20 DJG Fix emulation_output set when file not specified
// 10/25/19 DJG Set drive_params->metadata_bytes
// 10/05/19 DJG Print format first in command line. When controller defines
// all parameters all options set before it will be overwritten
// 07/05/2019 DJG Added support for using recovery signal
// 02/10/19 DJG Added missing space
// 11/03/18 DJG Renamed variable
// 09/28/18 DJG Allow drive 0 when analyze specified
// 08/05/18 DJG Don't allow drive 0 to be specified for mfm_read
// 04/01/18 DJG Fixed handling of unknown format in stored command line
// 01/18/17 DJG Added --ignore_seek_errors and fixed missing track_words when
// printing command line options
// 11/17/16 DJG Added emulator file track length option
// 11/14/16 DJG Changes for Vector4 format
// 10/31/16 DJG Change default analyze cylinder and head to detect
// formats better
// 10/16/16 DJG Added parameter to control seeks to --retry
// 01/13/16 DJG Changes for ext2emu related changes on how drive formats will
// be handled. If controller defines other parameters such as polynomial
// set them
// 01/06/16 DJG Rename structure
// 01/02/16 DJG Add --mark_bad support
// 12/31/15 DJG Changes for ext2emu
// 11/01/15 DJG Validate options required when format is specified.
// 05/17/15 DJG Made drive -d and data_crc -j so -d would be drive in all
// of the MFM programs.
// 01/04/15 DJG Suppressed printing command line options that weren't set
// Added begin_time. Made failure to decode options non fatal when
// reading options stored in file headers.
// 11/09/14 DJG Modified option parsing to allow mfm_util to reparse
// options stored in transitions file
// 10/01/14 DJG Incremented version number
// 09/06/14 DJG Made new class of info errors not print by default.
//
// 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 "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);
}
// Print command line to give current drive parameter settings
//
// drive_params: Drive parameters
char *parse_print_cmdline(DRIVE_PARAMS *drive_params, int print,
int no_retries_drive_interleave) {
static char cmdline[2048];
int cmdleft = sizeof(cmdline)-1;
char *cmdptr = cmdline;
if (drive_params->controller != CONTROLLER_NONE) {
safe_print(&cmdptr, &cmdleft, "--format %s ",
mfm_controller_info[drive_params->controller].name);
}
if (drive_params->num_sectors != 0) {
safe_print(&cmdptr, &cmdleft, "--sectors %d,%d ",
drive_params->num_sectors, drive_params->first_sector_number);
}
safe_print(&cmdptr, &cmdleft, "--heads %d --cylinders %d ",
drive_params->num_head, drive_params->num_cyl);
if (drive_params->header_crc.length != -1) {
safe_print(&cmdptr, &cmdleft,
"--header_crc 0x%llx,0x%llx,%d,%d ",
drive_params->header_crc.init_value, drive_params->header_crc.poly,
drive_params->header_crc.length, drive_params->header_crc.ecc_max_span);
}
if (drive_params->data_crc.length != 0) {
safe_print(&cmdptr, &cmdleft,
"--data_crc 0x%llx,0x%llx,%d,%d ",
drive_params->data_crc.init_value, drive_params->data_crc.poly,
drive_params->data_crc.length, drive_params->data_crc.ecc_max_span);
}
safe_print(&cmdptr, &cmdleft,
"--sector_length %d ", drive_params->sector_size);
if (!no_retries_drive_interleave) {
safe_print(&cmdptr, &cmdleft, "--retries %d,%d --drive %d ",
drive_params->retries, drive_params->no_seek_retries,
drive_params->drive);
}
if (drive_params->step_speed == DRIVE_STEP_SLOW) {
safe_print(&cmdptr, &cmdleft, "--unbuffered_seek ");
}
if (drive_params->head_3bit) {
safe_print(&cmdptr, &cmdleft, "--head_3bit ");
}
if (!no_retries_drive_interleave && drive_params->sector_numbers != NULL) {
int i;
safe_print(&cmdptr, &cmdleft, " --interleave ");
for (i = 0; i < drive_params->num_sectors; i++) {
if (i == drive_params->num_sectors - 1) {
safe_print(&cmdptr, &cmdleft, "%d", drive_params->sector_numbers[i]);
} else {
safe_print(&cmdptr, &cmdleft, "%d,", drive_params->sector_numbers[i]);
}
}
}
if (drive_params->start_time_ns) {
safe_print(&cmdptr, &cmdleft, " --begin_time %u ",
drive_params->start_time_ns);
}
if (drive_params->emu_track_data_bytes != 0) {
safe_print(&cmdptr, &cmdleft, "--track_words %d ",
drive_params->emu_track_data_bytes/4);
}
if (drive_params->ignore_seek_errors) {
safe_print(&cmdptr, &cmdleft, "--ignore_seek_errors ");
}
#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 read disk:\n%s\n", cmdline);
}
return cmdline;
}
// Parse CRC values
//
// arg: CRC value string
// return: CRC information
static CRC_INFO parse_crc(char *arg) {
CRC_INFO info;
int i;
char *str, *tok;
uint64_t val;
str = arg;
memset(&info, 0, sizeof(info));
for (i = 0; i < 4; i++) {
tok = strtok(str,",");
if (tok == NULL) {
break;
}
str = NULL;
val = strtoull(tok, NULL, 0);
switch(i) {
case 0:
info.init_value = val;
break;
case 1:
info.poly = val;
break;
case 2:
info.length = val;
break;
case 3:
info.ecc_max_span = val;
break;
}
}
if (i < 3) {
msg(MSG_FATAL,"Minimum for CRC is initial value, polynomial, and polynomial size\n");
exit(1);
}
return info;
}
// Set the drive parameter data by controller number
//
// drive_params: Drive parameters structure
// cont: Controller number
//
void parse_set_drive_params_from_controller(DRIVE_PARAMS *drive_params,
int controller) {
CONTROLLER *contp = &mfm_controller_info[controller];
drive_params->num_sectors = contp->write_num_sectors;
drive_params->first_sector_number = contp->write_first_sector_number;
drive_params->controller = controller;
drive_params->sector_size = contp->write_sector_size;
drive_params->metadata_bytes = contp->metadata_bytes;
if (!drive_params->dont_change_start_time) {
drive_params->start_time_ns = contp->start_time_ns;
}
drive_params->header_crc = contp->write_header_crc;
drive_params->data_crc = contp->write_data_crc;
}
// 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
// ignore_invalid_option: Don't print if controller is invalid
// drive_params: Drive parameters structure
// params_set: Returns if parameters set from CONT_MODEL
// track_layout_format_only: True if we only allow formats that have
// track_layout set
// return: Controller number
static int parse_controller(char *arg, int ignore_invalid_options,
DRIVE_PARAMS *drive_params, int *params_set, int track_layout_format_only) {
int i;
int controller = -1;
#define VALID_CONTROLLER(i) (!track_layout_format_only || mfm_controller_info[i].track_layout != NULL)
*params_set = 0;
for (i = 0; mfm_controller_info[i].name != NULL; i++) {
if (strcasecmp(mfm_controller_info[i].name, arg) == 0 && VALID_CONTROLLER(i)) {
controller = i;
}
}
if (controller == -1) {
if (ignore_invalid_options) {
msg(MSG_INFO, "Unknown controller %s.\n",arg);
controller = CONTROLLER_NONE;
} else {
msg(MSG_FATAL, "Unknown controller %s. Choices are\n",arg);
for (i = 0; mfm_controller_info[i].name != NULL; i++) {
if (VALID_CONTROLLER(i)) {
msg(MSG_FATAL,"%s\n",mfm_controller_info[i].name);
}
}
exit(1);
}
} else {
if (mfm_controller_info[controller].analyze_search == CONT_MODEL) {
// TODO, this can be confusing since it overrides what is specified on the
// command line. May be better way.
parse_set_drive_params_from_controller(drive_params, controller);
*params_set = 1;
}
}
return controller;
}
// Parse the interleave information. It may either be an interleave number
// or a comma separated list of sector number.
//
// arg: Interleave information string
// drive_params: Drive parameters
// return: Pointer to list of sector number
static uint8_t *parse_interleave(char *arg, DRIVE_PARAMS *drive_params) {
// Must be static since we return pointer. Sector numbers for interleave
static uint8_t sectors[MAX_SECTORS];
int i;
char *str, *tok;
str = arg;
// Only first 2 now used for ext2emu
for (i = 0; i < MAX_SECTORS; i++) {
tok = strtok(str,",");
if (tok == NULL) {
break;
}
str = NULL;
sectors[i] = atoi(tok);
}
if (i == 1) {
sectors[1] = 0; // Default cylinder to cylinder interleave
} else {
// allow 2 values for ext2emu
if (i != 2) {
msg(MSG_FATAL, "Interleave takes one or two comma separated values\n");
exit(1);
}
}
// If "" specified then return null for no sector list
if (i == 0)
return NULL;
else
return sectors;
}
// Parse analyze optional arguments
//
// arg: Interleave information string
// drive_params: Drive parameters
static void parse_analyze(char *arg, DRIVE_PARAMS *drive_params) {
char *tok;
// Best for separating formats
drive_params->analyze_cyl = 2;
drive_params->analyze_head = 1;
if (arg == NULL) {
return;
}
tok = strtok(arg,",");
if (tok == NULL) {
return;
}
drive_params->analyze_cyl = atoi(tok);
tok = strtok(NULL,",");
if (tok == NULL) {
return;
}
drive_params->analyze_head = atoi(tok);
}
static int mark_bad_compare(const void *a, const void *b) {
const MARK_BAD_INFO *mba, *mbb;
mba = a;
mbb = b;
if (mba->cyl > mbb->cyl || (mba->cyl == mbb->cyl &&
((mba->head > mbb->head) || (mba->head == mbb->head &&
mba->sector > mbb->sector)))) {
return 1;
} else if (mba->cyl == mbb->cyl && mba->head == mbb->head &&
mba->sector == mbb->sector) {
return 0;
} else {
return -1;
}
}
// Parse the mark bad sector information. Format is cyl,head,sect:cyl,head,sect
//
// arg: Bad sector information string
// drive_params: Drive parameters
// return: Pointer to list of bad sector data sorted ascending
static MARK_BAD_INFO *parse_mark_bad(char *arg, DRIVE_PARAMS *drive_params) {
int i;
char *str, *tok;
int num_bad;
MARK_BAD_INFO *mark_bad_list;
str = arg;
num_bad = 1;
while (*str != 0) {
if (*str++ == ':') {
num_bad++;
}
}
mark_bad_list = msg_malloc(num_bad * sizeof(MARK_BAD_INFO),
"Mark bad list");
str = arg;
for (i = 0; i < num_bad; i++) {
tok = strtok(str,":");
if (sscanf(tok, "%d,%d,%d", &mark_bad_list[i].cyl, &mark_bad_list[i].head,
&mark_bad_list[i].sector) != 3) {
msg(MSG_FATAL,"Error parsing mark bad list %s\n",tok);
exit(1);
}
mark_bad_list[i].last = 0;
str = NULL;
}
qsort(mark_bad_list, num_bad, sizeof(MARK_BAD_INFO), mark_bad_compare);
mark_bad_list[num_bad-1].last = 1;
return mark_bad_list;
}
// Delete bit n from v shifting higher bits down
#define DELETE_BIT(v, n) (v & ((1 << n)-1)) | (((v & ~((1 << (n+1))-1)) >> 1))
// Minimum to generate extract file (sector data)
static int min_read_opts = 0x7f;
// Minimum to generation emulation file (MFM clock & data)
static int min_read_transitions_opts = 0x46;
// The options set when a CONT_MODEL controller used
static int controller_model_params = 0x99;
// Drive option bitmask
static int drive_opt = 0x40;
// data_crc option bitmask
static int data_crc_opt = 0x10;
// If you change this fix parse_print_cmdline and check if ext2emu should
// delete new option
static struct option long_options[] = {
{"sectors", 1, NULL, 's'},
{"heads", 1, NULL, 'h'},
{"cylinders", 1, NULL, 'c'},
{"header_crc", 1, NULL, 'g'},
{"data_crc", 1, NULL, 'j'},
{"format", 1, NULL, 'f'},
{"drive", 1, NULL, 'd'},
{"sector_length", 1, NULL, 'l'},
{"unbuffered_seek", 0, NULL, 'u'},
{"interleave", 1, NULL, 'i'},
{"head_3bit", 0, NULL, '3'},
{"retries", 1, NULL, 'r'},
{"recovery", 0, NULL, 'R'},
{"analyze", 2, NULL, 'a'},
{"quiet", 1, NULL, 'q'},
{"begin_time", 1, NULL, 'b'},
{"transitions_file", 1, NULL, 't'},
{"extracted_data_file", 1, NULL, 'e'},
{"emulation_file", 1, NULL, 'm'},
{"version", 0, NULL, 'v'},
{"note", 1, NULL, 'n'},
{"mark_bad", 1, NULL, 'M'},
{"track_words", 1, NULL, 'w'},
{"ignore_seek_errors", 0, NULL, 'I'},
{NULL, 0, NULL, 0}
};
static char short_options[] = "s:h:c:g:d:f:j:l:ui:3r:a::q:b:t:e:m:vn:M:w:I";
// 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)];
int params_set;
// 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
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;
}
// 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 's':
tok = strtok(optarg,",");
drive_params->num_sectors = atoi(tok);
tok = strtok(NULL,",");
if (tok != NULL) {
drive_params->first_sector_number = atoi(tok);
}
if (drive_params->num_sectors <= 0 ||
drive_params->num_sectors > MAX_SECTORS) {
msg(MSG_FATAL,"Sectors must be 1 to %d\n", MAX_SECTORS);
if (!ignore_invalid_options) {
exit(1);
}
}
break;
case 'h':
drive_params->num_head = atoi(optarg);
if (drive_params->num_head <= 0 ||
drive_params->num_head > MAX_HEAD) {
msg(MSG_FATAL,"Heads must be 1 to %d\n", MAX_HEAD);
if (!ignore_invalid_options) {
exit(1);
}
}
break;
case 'c':
drive_params->num_cyl = atoi(optarg);
if (drive_params->num_cyl <= 0) {
msg(MSG_FATAL,"Cylinders must be greater than 0\n");
if (!ignore_invalid_options) {
exit(1);
}
}
break;
case 'g':
drive_params->header_crc = parse_crc(optarg);
break;
case 'j':
drive_params->data_crc = parse_crc(optarg);
break;
case 'u':
drive_params->step_speed = DRIVE_STEP_SLOW;
break;
case 'i':
drive_params->sector_numbers = parse_interleave(optarg, drive_params);
break;
case '3':
drive_params->head_3bit = 1;
break;
case 'f':
drive_params->controller = parse_controller(optarg,
ignore_invalid_options, drive_params, ¶ms_set, track_layout_format_only);
// If not valid don't clear option set bit
if (drive_params->controller == -1) {
drive_params->opt_mask &= ~(1 << options_index);
}
if (params_set) {
drive_params->opt_mask |= controller_model_params;
}
break;
case 'l':
drive_params->sector_size = atoi(optarg);
if (drive_params->sector_size <= 0 ||
drive_params->sector_size > MAX_SECTOR_SIZE) {
msg(MSG_FATAL,"Sector size must be 1 to %d\n", MAX_SECTOR_SIZE);
if (!ignore_invalid_options) {
exit(1);
}
}
break;
case 'r':
drive_params->retries = atoi(optarg);
tok = strstr(optarg,",");
if (tok != NULL) {
drive_params->no_seek_retries = atoi(tok+1);
}
break;
case 'R':
drive_params->recovery = 1;
break;
case 'a':
drive_params->analyze = 1;
parse_analyze(optarg, drive_params);
break;
case 'b':
drive_params->start_time_ns = atoi(optarg);
drive_params->dont_change_start_time = 1;
break;
case 't':
drive_params->transitions_filename = optarg;
break;
case 'e':
drive_params->extract_filename = optarg;
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 'n':
drive_params->note = optarg;
break;
case '?':
if (!ignore_invalid_options) {
exit(1);
}
break;
case 'M':
drive_params->mark_bad_list = parse_mark_bad(optarg, drive_params);
break;
case 'w':
drive_params->emu_track_data_bytes = atoi(optarg) * 4;
break;
case 'I':
drive_params->ignore_seek_errors = 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: 1 if mfm_read, 0 if mfm_util
void parse_validate_options(DRIVE_PARAMS *drive_params, int mfm_read) {
int i;
// For mfm_util drive doesn't need to be specified. This
// option error handling is getting messy.
if (mfm_read) {
// Drive 1-4 valid if specified. If analyze specified drive will be 0
if ((drive_params->drive < 1 || drive_params->drive > 4) &&
!(drive_params->analyze && drive_params->drive == 0)) {
msg(MSG_FATAL, "Drive must be between 1 and 4\n");
exit(1);
}
} else {
min_read_opts &= ~drive_opt;
}
// Corvus H and Cromemco drive doesn't have separate header and data
// CRC. We use header for both
if (drive_params->controller == CONTROLLER_CORVUS_H ||
drive_params->controller == CONTROLLER_CROMEMCO ||
drive_params->controller == CONTROLLER_VECTOR4_ST506 ||
drive_params->controller == CONTROLLER_VECTOR4) {
min_read_opts &= ~data_crc_opt;
}
if ((drive_params->extract_filename != NULL)
&& !drive_params->analyze && (drive_params->opt_mask & min_read_opts)
!= min_read_opts) {
msg(MSG_FATAL, "Generating extract file without analyze requires options:");
for (i = 0; i < 32; i++) {
int bit = (1 << i);
if (!(drive_params->opt_mask & bit) && (min_read_opts & bit)) {
msg(MSG_FATAL, " %s", long_options[i].name);
}
}
msg(MSG_FATAL, "\n");
exit(1);
}
if (drive_params->controller != CONTROLLER_NONE
&& !drive_params->analyze && (drive_params->opt_mask & min_read_opts)
!= min_read_opts) {
msg(MSG_FATAL, "Format specified without analyze requires options:");
for (i = 0; i < 32; i++) {
int bit = (1 << i);
if (!(drive_params->opt_mask & bit) && (min_read_opts & bit)) {
msg(MSG_FATAL, " %s", long_options[i].name);
}
}
msg(MSG_FATAL, "\n");
exit(1);
}
if (mfm_read & (drive_params->transitions_filename != NULL ||
drive_params->emulation_filename != NULL) &&
!drive_params->analyze &&
(drive_params->opt_mask & min_read_transitions_opts) !=
min_read_transitions_opts) {
msg(MSG_FATAL, "Generation of transition or emulation file without analyze requires options:\n");
for (i = 0; i < 32; i++) {
int bit = (1 << i);
if (!(drive_params->opt_mask & bit) &&
(min_read_transitions_opts & bit)) {
msg(MSG_FATAL, " %s", long_options[i].name);
}
}
msg(MSG_FATAL, "\n");
exit(1);
}
}
void parse_validate_options_listed(DRIVE_PARAMS *drive_params, char *opt) {
int i;
int fatal = 0;
while (*opt != 0) {
for (i = 0; i < ARRAYSIZE(long_options); i++) {
if ( (*opt == long_options[i].val) &&
!(drive_params->opt_mask & (1 << i)) ) {
msg(MSG_FATAL, "Option %s must be specified\n", long_options[i].name);
fatal = 1;
}
}
opt++;
}
if (fatal) {
exit(1);
}
}