768 lines
24 KiB
C++
768 lines
24 KiB
C++
/************************************************************************
|
|
*
|
|
* cmdopt.cpp - Definitions of the option parsing subsystem
|
|
*
|
|
* $Id: cmdopt.cpp 588734 2007-10-26 18:17:55Z sebor $
|
|
*
|
|
************************************************************************
|
|
*
|
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
|
* contributor license agreements. See the NOTICE file distributed
|
|
* with this work for additional information regarding copyright
|
|
* ownership. The ASF licenses this file to you under the Apache
|
|
* License, Version 2.0 (the "License"); you may not use this file
|
|
* except in compliance with the License. You may obtain a copy of
|
|
* the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
* implied. See the License for the specific language governing
|
|
* permissions and limitations under the License.
|
|
*
|
|
**************************************************************************/
|
|
|
|
/* disable Compaq/HP C++ pure libc headers to allow POSIX symbols
|
|
such as SIGALRM or SIGKILL to be defined.*/
|
|
#undef __PURE_CNAME
|
|
|
|
#include <assert.h> /* for assert() */
|
|
#include <ctype.h> /* for isspace */
|
|
#include <errno.h> /* for errno */
|
|
#include <signal.h> /* for raise, signal, SIG_IGN */
|
|
#include <stdio.h> /* for *printf, fputs */
|
|
#include <stdlib.h> /* for exit */
|
|
#include <string.h> /* for str* */
|
|
#if !defined (_WIN32) && !defined (_WIN64)
|
|
# include <unistd.h> /* for sleep */
|
|
|
|
# if defined (_XOPEN_UNIX)
|
|
# include <sys/resource.h> /* for struct rlimit, RLIMIT_CORE, ... */
|
|
# endif
|
|
|
|
#else
|
|
# include <windows.h> /* for Sleep */
|
|
#endif /* _WIN{32,64} */
|
|
|
|
#include "exec.h"
|
|
#include "target.h"
|
|
#include "util.h"
|
|
|
|
#include "cmdopt.h"
|
|
|
|
const char* exe_name; /**< Alias for process argv [0]. */
|
|
#if !defined (_WIN32) && !defined (_WIN64)
|
|
const char escape_code = '\\';
|
|
const char default_path_sep = '/';
|
|
const char suffix_sep = '.';
|
|
const size_t exe_suffix_len = 0;
|
|
#if defined (_SC_CLK_TCK)
|
|
const float TICKS_PER_SEC = sysconf (_SC_CLK_TCK);
|
|
#elif defined (CLK_TCK)
|
|
const float TICKS_PER_SEC = CLK_TCK;
|
|
#elif defined (CLOCKS_PER_SEC)
|
|
const float TICKS_PER_SEC = CLOCKS_PER_SEC;
|
|
#else
|
|
# error Unable to determine number of clock ticks in a second.
|
|
#endif
|
|
#else
|
|
const char escape_code = '^';
|
|
const char default_path_sep = '\\';
|
|
const char suffix_sep = '.';
|
|
const size_t exe_suffix_len = 4; /* strlen(".exe") == 4 */
|
|
const float TICKS_PER_SEC = CLOCKS_PER_SEC;
|
|
|
|
# ifndef _WIN32_WINNT
|
|
# define _WIN32_WINNT 0x0500
|
|
# endif
|
|
|
|
# if _WIN32_WINNT >= 0x0500
|
|
# define RLIMIT_AS
|
|
# endif
|
|
#endif
|
|
|
|
static const char
|
|
usage_text[] = {
|
|
"Usage: %s [OPTIONS] [targets]\n"
|
|
"\n"
|
|
" Treats each token in targets as the path to an executable. Each target\n"
|
|
" enumerated is executed, and the output is processed after termination.\n"
|
|
" If target prepended by '@' character, target is treated as text file\n"
|
|
" with list of targets (one target per line).\n"
|
|
" If the execution takes longer than a certain (configurable) amount of\n"
|
|
" time, the process is killed.\n"
|
|
"\n"
|
|
" -d dir Specify root directory for output reference files.\n"
|
|
" -h, -? Display usage information and exit.\n"
|
|
" -t seconds Set timeout before killing target (default is 10\n"
|
|
" seconds).\n"
|
|
" -x opts Specify command line options to pass to targets.\n"
|
|
" -- Terminate option processing and treat all arguments\n"
|
|
" that follow as targets.\n"
|
|
" --compat Use compatability mode test output parsing.\n"
|
|
" --nocompat Use standard test output parsing (default).\n"
|
|
" --help Display usage information and exit.\n"
|
|
" --exit=val Exit immediately with the specified return code.\n"
|
|
" --sleep=sec Sleep for the specified number of seconds.\n"
|
|
" --signal=sig Send itself the specified signal.\n"
|
|
" --ignore=sig Ignore the specified signal.\n"
|
|
" --ulimit=lim Set child process usage limits (see below).\n"
|
|
" --warn=alias Set compiler log warning pattern (see below).\n"
|
|
"\n"
|
|
" All short (single dash) options must be specified seperately.\n"
|
|
" If a short option takes a value, it may either be provided like\n"
|
|
" '-sval' or '-s val'.\n"
|
|
" If a long option take a value, it may either be provided like\n"
|
|
" '--option=value' or '--option value'.\n"
|
|
"\n"
|
|
" --ulimit sets limits on how much of a given resource or resorces\n"
|
|
" child processes are allowed to utilize. These limits take on two\n"
|
|
" forms, 'soft' and 'hard' limits. Options are specified in the form\n"
|
|
" 'resource:limit', where resource is a resource named below, and and\n"
|
|
" limit is a number, with value specifing the limit for the named\n"
|
|
" resource. If multiple limits are to be set, they can be specified\n"
|
|
" either in multiple instances of the --ulimit switch, or by specifying\n"
|
|
" additional limits in the same call by seperating the pairs with\n"
|
|
" commas. 'Soft' limits are specified by providing the resource name\n"
|
|
" in lowercase letters, while 'hard' limits are specified by providing\n"
|
|
" the resource name in uppercase letters. To set both limits, specify\n"
|
|
" the resource name in title case.\n"
|
|
"\n"
|
|
" --ulimit modes:\n"
|
|
" core Maximum size of core file, in bytes.\n"
|
|
" cpu Maximum CPU time, in seconds.\n"
|
|
" data Maximum data segment size, in bytes.\n"
|
|
" fsize Maximum size of generated files, in bytes.\n"
|
|
" nofile Maximum number of open file descriptors.\n"
|
|
" stack Maximum size of initial thread's stack, in bytes.\n"
|
|
" as Maximum size of available memory, in bytes.\n"
|
|
"\n"
|
|
" Note: Some operating systems lack support for some or all of the\n"
|
|
" ulimit modes. If a system is unable to limit a given property, a\n"
|
|
" warning message will be produced.\n"
|
|
"\n"
|
|
" --warn set the string used to parse compile and link logs. Rather\n"
|
|
" than specifying a search string, an alias code is provided,\n"
|
|
" coresponding to the output of a compiler and linker. Alias codes\n"
|
|
" are case sensitive.\n"
|
|
"\n"
|
|
" --warn modes:\n"
|
|
" acc HP aCC\n"
|
|
" cxx Compaq C++\n"
|
|
" eccp EDG eccp\n"
|
|
" gcc GNU gcc\n"
|
|
" icc Intel icc for Linux\n"
|
|
" mipspro SGI MIPSpro\n"
|
|
" sunpro Sun C++\n"
|
|
" vacpp IBM VisualAge C++\n"
|
|
" xlc IBM XLC++\n"
|
|
};
|
|
|
|
|
|
/**
|
|
Display command line switches for program and terminate.
|
|
|
|
@param status status code to exit with.
|
|
*/
|
|
static void
|
|
show_usage (int status)
|
|
{
|
|
FILE* const where = status ? stderr : stdout;
|
|
|
|
assert (0 != exe_name);
|
|
|
|
fprintf (where, usage_text, exe_name);
|
|
|
|
exit (status);
|
|
}
|
|
|
|
/**
|
|
Helper function to read the value for a short option
|
|
|
|
@param argv argument array
|
|
@param idx reference to index for option
|
|
*/
|
|
static char*
|
|
get_short_val (char* const* argv, int* idx)
|
|
{
|
|
assert (0 != argv);
|
|
assert (0 != idx);
|
|
|
|
if ('\0' == argv [*idx][2])
|
|
return argv [++(*idx)];
|
|
else
|
|
return argv [*idx] + 2;
|
|
}
|
|
|
|
/**
|
|
Helper function to read the value for a long option
|
|
|
|
@param argv argument array
|
|
@param idx reference to index for option
|
|
@param offset length of option name (including leading --)
|
|
*/
|
|
static char*
|
|
get_long_val (char* const* argv, int* idx, unsigned offset)
|
|
{
|
|
assert (0 != argv);
|
|
assert (0 != idx);
|
|
|
|
if ('\0' == argv [*idx][offset])
|
|
return argv [++(*idx)];
|
|
else if ('=' == argv [*idx][offset])
|
|
return argv [*idx] + offset + 1;
|
|
else
|
|
return (char*)0;
|
|
}
|
|
|
|
/**
|
|
Helper function to parse a ulimit value string
|
|
|
|
@param opts ulimit value string to pares
|
|
@see child_limits
|
|
*/
|
|
static bool
|
|
parse_limit_opts (const char* opts, struct target_opts* defaults)
|
|
{
|
|
static const struct {
|
|
rw_rlimit** limit;
|
|
const char* name;
|
|
const char* caps;
|
|
const char* mixd;
|
|
size_t len;
|
|
} limits[] = {
|
|
{
|
|
#ifdef RLIMIT_CORE
|
|
&defaults->core,
|
|
#else
|
|
0,
|
|
#endif /* RLIMIT_CORE */
|
|
"core", "CORE", "Core", 4 },
|
|
{
|
|
#ifdef RLIMIT_CPU
|
|
&defaults->cpu,
|
|
#else
|
|
0,
|
|
#endif /* RLIMIT_CPU */
|
|
"cpu", "CPU", "Cpu", 3 },
|
|
{
|
|
#ifdef RLIMIT_DATA
|
|
&defaults->data,
|
|
#else
|
|
0,
|
|
#endif /* RLIMIT_DATA */
|
|
"data", "DATA", "Data", 4 },
|
|
{
|
|
#ifdef RLIMIT_FSIZE
|
|
&defaults->fsize,
|
|
#else
|
|
0,
|
|
#endif /* RLIMIT_FSIZE */
|
|
"fsize", "FSIZE", "Fsize", 5 },
|
|
{
|
|
#ifdef RLIMIT_NOFILE
|
|
&defaults->nofile,
|
|
#else
|
|
0,
|
|
#endif /* RLIMIT_NOFILE */
|
|
"nofile", "NOFILE", "Nofile", 6 },
|
|
{
|
|
#ifdef RLIMIT_STACK
|
|
&defaults->stack,
|
|
#else
|
|
0,
|
|
#endif /* RLIMIT_STACK */
|
|
"stack", "STACK", "Stack", 5 },
|
|
{
|
|
#ifdef RLIMIT_AS
|
|
&defaults->as,
|
|
#else
|
|
0,
|
|
#endif /* RLIMIT_AS */
|
|
"as", "AS", "As", 2 },
|
|
{ 0, 0, 0, 0, 0 }
|
|
};
|
|
|
|
const char* arg = opts;
|
|
|
|
assert (0 != opts);
|
|
|
|
while (arg && *arg) {
|
|
|
|
const size_t arglen = strlen (arg);
|
|
|
|
for (size_t i = 0; limits [i].name; ++i) {
|
|
if ( limits [i].len < arglen
|
|
&& ( 0 == memcmp (limits [i].name, arg, limits [i].len)
|
|
|| 0 == memcmp (limits [i].caps, arg, limits [i].len)
|
|
|| 0 == memcmp (limits [i].mixd, arg, limits [i].len))
|
|
&& ':' == arg [limits [i].len]) {
|
|
|
|
/* determine whether the hard limit and/or the soft limit
|
|
should be set. */
|
|
const bool hard = 0 != isupper (arg [0]);
|
|
const bool soft = 0 != islower (arg [1]);
|
|
|
|
arg += limits [i].len + 1;
|
|
|
|
if (!isdigit (*arg)) {
|
|
return 1;
|
|
}
|
|
|
|
char *end;
|
|
const long lim = strtol (arg, &end, 10);
|
|
|
|
arg = end;
|
|
|
|
if ('\0' != *arg && ',' != *arg)
|
|
break;
|
|
|
|
if (limits [i].limit) {
|
|
if (!*limits [i].limit) {
|
|
(*limits [i].limit) =
|
|
(rw_rlimit*)RW_MALLOC (sizeof (rw_rlimit));
|
|
(*limits [i].limit)->rlim_cur = RLIM_SAVED_CUR;
|
|
(*limits [i].limit)->rlim_max = RLIM_SAVED_MAX;
|
|
}
|
|
if (soft)
|
|
(*limits [i].limit)->rlim_cur = lim;
|
|
|
|
if (hard)
|
|
(*limits [i].limit)->rlim_max = lim;
|
|
}
|
|
else
|
|
warn ("Unable to process %s limit: Not supported\n",
|
|
limits [i].name);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (',' == *arg) {
|
|
++arg;
|
|
}
|
|
else if ('\0' != *arg) {
|
|
return 1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
Helper function to parse a warning value string
|
|
|
|
@param opts ulimit value string to pares
|
|
@see child_limits
|
|
*/
|
|
static bool
|
|
parse_warn_opts (const char* arg, struct target_opts* defaults)
|
|
{
|
|
static const struct {
|
|
const char* name;
|
|
const char* pat;
|
|
} warn_set [] = {
|
|
{ "acc", "Warning " },
|
|
/*
|
|
{ "cds", "UNKNOWN"},
|
|
{ "como", "UNKNOWN"},
|
|
*/
|
|
{ "cxx", "Warning:"},
|
|
{ "eccp", "warning:"},
|
|
{ "gcc", "warning:"},
|
|
{ "icc", "warning #"},
|
|
{ "mipspro", "CC: WARNING"},
|
|
{ "sunpro", "Warning:"},
|
|
{ "vacpp", ": (W) "},
|
|
{ "xlc", ": (W) "}, /* xlc and vacpp are synonyms. */
|
|
{ 0, 0 }
|
|
};
|
|
|
|
assert (0 != arg);
|
|
assert (0 != defaults);
|
|
|
|
for (size_t i = 0; warn_set [i].name; ++i) {
|
|
if (0 == strcmp (warn_set [i].name, arg)) {
|
|
|
|
/* Set both compiler and linker warning string. */
|
|
defaults->c_warn = warn_set [i].pat;
|
|
defaults->l_warn = warn_set [i].pat;
|
|
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/**
|
|
Helper function to produce 'Bad argument' error message.
|
|
|
|
@param opt name of option encountered
|
|
@param val invalid value found
|
|
*/
|
|
static void
|
|
bad_value (const char* opt, const char* val)
|
|
{
|
|
assert (0 != opt);
|
|
|
|
terminate (1, "Bad argument for %s: %s\n", opt, val);
|
|
}
|
|
|
|
/**
|
|
Helper function to produce 'Missing argument' error message.
|
|
|
|
@param opt name of option missing argument
|
|
*/
|
|
static void
|
|
missing_value (const char* opt)
|
|
{
|
|
assert (0 != opt);
|
|
|
|
terminate (1, "Missing argument for %s\n", opt);
|
|
}
|
|
|
|
/**
|
|
Helper function to produce 'Unknown option' error message.
|
|
|
|
@param opt name of option encountered
|
|
*/
|
|
static void
|
|
bad_option (const char* opt)
|
|
{
|
|
assert (0 != opt);
|
|
|
|
warn ("Unknown option: %s\n", opt);
|
|
|
|
show_usage (1);
|
|
}
|
|
|
|
int
|
|
eval_options (int argc, char **argv, struct target_opts* defaults,
|
|
const char** exe_opts)
|
|
{
|
|
const char opt_timeout[] = "-t";
|
|
const char opt_data_dir[] = "-d";
|
|
const char opt_t_flags[] = "-x";
|
|
const char opt_compat[] = "--compat";
|
|
const char opt_exit[] = "--exit";
|
|
const char opt_help[] = "--help";
|
|
const char opt_ignore[] = "--ignore";
|
|
const char opt_nocompat[] = "--nocompat";
|
|
const char opt_signal[] = "--signal";
|
|
const char opt_sleep[] = "--sleep";
|
|
const char opt_ulimit[] = "--ulimit";
|
|
const char opt_verbose[] = "--verbose";
|
|
const char opt_warn[] = "--warn";
|
|
|
|
int i;
|
|
|
|
assert (0 != argv);
|
|
assert (0 != defaults);
|
|
|
|
memset (defaults, 0, sizeof (target_opts));
|
|
|
|
/* The chain of preprocesor logic below initializes the defaults->c_warn
|
|
and defaults->l_warn values.
|
|
*/
|
|
#ifdef __GNUG__
|
|
parse_warn_opts ("Gcc", defaults);
|
|
#elif defined (__HP_aCC)
|
|
parse_warn_opts ("Acc", defaults);
|
|
#elif defined (__IBMCPP__)
|
|
parse_warn_opts ("Xlc", defaults);
|
|
#elif defined (__SUNPRO_CC)
|
|
parse_warn_opts ("Sunpro", defaults);
|
|
#elif defined (SNI)
|
|
parse_warn_opts ("Cds", defaults);
|
|
#elif defined (__APOGEE__) /* EDG variant that doesn't define __EDG__. */
|
|
parse_warn_opts ("Como", defaults);
|
|
|
|
/* The following are EDG variants, that define __EDG__ */
|
|
#elif defined (__DECCXX)
|
|
parse_warn_opts ("Cxx", defaults);
|
|
#elif defined (_SGI_COMPILER_VERSION)
|
|
parse_warn_opts ("Mipspro", defaults);
|
|
#elif defined (__INTEL_COMPILER)
|
|
parse_warn_opts ("Icc", defaults);
|
|
|
|
/* So we need to check for __EDG__ after we check for them. */
|
|
#elif defined (__EDG__)
|
|
parse_warn_opts ("Eccp", defaults);
|
|
#endif
|
|
|
|
if (1 == argc || '-' != argv [1][0])
|
|
return 1;
|
|
|
|
for (i = 1; i < argc && '-' == argv [i][0]; ++i) {
|
|
|
|
/* the name of the option being processed */
|
|
const char* optname = argv [i];
|
|
|
|
/* the option's argument, if any */
|
|
const char* optarg = 0;
|
|
|
|
char* end = 0;
|
|
|
|
switch (argv [i][1]) {
|
|
case '?': /* display help and exit with status of 0 */
|
|
case 'h':
|
|
show_usage (0);
|
|
|
|
case 'r':
|
|
++i; /* Ignore -r option (makefile compat) */
|
|
break;
|
|
|
|
case 't': /* executable timeout in seconds */
|
|
optname = opt_timeout;
|
|
optarg = get_short_val (argv, &i);
|
|
if (optarg) {
|
|
if (!isdigit (*optarg))
|
|
bad_value (optname, optarg);
|
|
|
|
errno = 0;
|
|
defaults->timeout = strtol (optarg, &end, 10);
|
|
if (*end || errno)
|
|
bad_value (optname, optarg);
|
|
}
|
|
else
|
|
missing_value (optname);
|
|
|
|
break;
|
|
|
|
case 'd': /* directory containing example reference files */
|
|
optname = opt_data_dir;
|
|
defaults->data_dir = get_short_val (argv, &i);
|
|
if (!defaults->data_dir)
|
|
missing_value (optname);
|
|
break;
|
|
|
|
case 'v': /* enable verbose mode */
|
|
optname = opt_verbose;
|
|
++defaults->verbose;
|
|
break;
|
|
|
|
case 'x': /* command line options to pass to targets */
|
|
optname = opt_t_flags;
|
|
*exe_opts = get_short_val (argv, &i);
|
|
if (!*exe_opts)
|
|
missing_value (optname);
|
|
break;
|
|
|
|
case '-': /* long options */
|
|
{
|
|
const size_t arglen = strlen (argv [i]);
|
|
|
|
/* abort processing on --, eat token */
|
|
if ('\0' == argv [i][2])
|
|
return i+1;
|
|
|
|
if ( sizeof opt_compat - 1 == arglen
|
|
&& !memcmp (opt_compat, argv [i], sizeof opt_compat)) {
|
|
/* enter compatibility mode */
|
|
defaults->compat = 1;
|
|
break;
|
|
}
|
|
else if ( sizeof opt_nocompat - 1 == arglen
|
|
&& !memcmp (opt_nocompat, argv [i], sizeof opt_nocompat)) {
|
|
/* exit compatibility mode */
|
|
defaults->compat = 0;
|
|
break;
|
|
}
|
|
else if ( sizeof opt_exit - 1 <= arglen
|
|
&& !memcmp (opt_exit, argv [i], sizeof opt_exit - 1)) {
|
|
/* exit immediately with the specified status */
|
|
optname = opt_exit;
|
|
optarg = get_long_val (argv, &i, sizeof opt_exit - 1);
|
|
if (optarg && *optarg) {
|
|
if (!isdigit (*optarg))
|
|
bad_value (optname, optarg);
|
|
|
|
errno = 0;
|
|
const long code = strtol (optarg, &end, 10);
|
|
if ('\0' == *end && !errno)
|
|
exit (code);
|
|
}
|
|
}
|
|
else if ( sizeof opt_help - 1 == arglen
|
|
&& !memcmp (opt_help, argv [i], sizeof opt_help - 1)) {
|
|
/* display help and exit with status of 0 */
|
|
optname = opt_help;
|
|
show_usage (0);
|
|
break;
|
|
}
|
|
else if ( sizeof opt_sleep - 1 <= arglen
|
|
&& !memcmp (opt_sleep, argv [i], sizeof opt_sleep - 1)) {
|
|
/* sleep for the specified number of seconds */
|
|
optname = opt_sleep;
|
|
optarg = get_long_val (argv, &i, sizeof opt_sleep - 1);
|
|
if (optarg && *optarg) {
|
|
if (!isdigit (*optarg))
|
|
bad_value (optname, optarg);
|
|
|
|
errno = 0;
|
|
const long nsec = strtol (optarg, &end, 10);
|
|
if ('\0' == *end && 0 <= nsec && !errno) {
|
|
rw_sleep (nsec);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else if ( sizeof opt_signal - 1 <= arglen
|
|
&& !memcmp (opt_signal, argv [i], sizeof opt_signal - 1)) {
|
|
/* send ourselves the specified signal */
|
|
optname = opt_signal;
|
|
optarg = get_long_val (argv, &i, sizeof opt_signal - 1);
|
|
if (optarg && *optarg) {
|
|
const int signo = get_signo (optarg);
|
|
if (0 <= signo) {
|
|
if (0 > raise (signo))
|
|
terminate (1, "raise(%s) failed: %s\n",
|
|
get_signame (signo), strerror (errno));
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else if ( sizeof opt_ignore - 1 <= arglen
|
|
&& !memcmp (opt_ignore, argv [i], sizeof opt_ignore - 1)) {
|
|
/* ignore the specified signal */
|
|
optname = opt_ignore;
|
|
optarg = get_long_val (argv, &i, sizeof opt_ignore - 1);
|
|
if (optarg && *optarg) {
|
|
const int signo = get_signo (optarg);
|
|
if (0 <= signo) {
|
|
if (rw_signal (signo, 0 /* SIG_IGN */))
|
|
terminate (1, "rw_signal(%s, ...) failed: %s\n",
|
|
get_signame (signo), strerror (errno));
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else if ( sizeof opt_ulimit - 1 <= arglen
|
|
&& !memcmp (opt_ulimit, argv [i], sizeof opt_ulimit - 1)) {
|
|
/* set child process resource utilization limits */
|
|
optname = opt_ulimit;
|
|
optarg = get_long_val (argv, &i, sizeof opt_ulimit - 1);
|
|
if (optarg && *optarg) {
|
|
if (!parse_limit_opts (optarg, defaults)) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else if ( sizeof opt_warn - 1 <= arglen
|
|
&& !memcmp (opt_warn, argv [i], sizeof opt_warn - 1)) {
|
|
/* set compiler warning mode */
|
|
optname = opt_warn;
|
|
optarg = get_long_val (argv, &i, sizeof opt_warn - 1);
|
|
if (optarg && *optarg) {
|
|
if (!parse_warn_opts (optarg, defaults)) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
/* fall through */
|
|
}
|
|
default:
|
|
if (optarg) {
|
|
if (*optarg)
|
|
bad_value (optname, optarg);
|
|
else
|
|
missing_value (optname);
|
|
}
|
|
|
|
if (argv [i])
|
|
bad_option (argv [i]);
|
|
else
|
|
missing_value (optname);
|
|
}
|
|
}
|
|
|
|
return i;
|
|
}
|
|
|
|
char**
|
|
split_opt_string (const char* opts)
|
|
{
|
|
char in_quote = 0;
|
|
int in_escape = 0;
|
|
int in_token = 0;
|
|
const char *pos;
|
|
char *target, *last;
|
|
char **table_pos, **argv;
|
|
size_t optlen;
|
|
|
|
assert (0 != opts);
|
|
|
|
optlen = strlen (opts);
|
|
|
|
if (0 == optlen) {
|
|
/* Alloc a an index array to hold the program name */
|
|
argv = (char**)RW_MALLOC (sizeof (char*));
|
|
|
|
/* And tie the two together */
|
|
argv [0] = (char*)0;
|
|
return argv;
|
|
}
|
|
|
|
table_pos = argv = (char**)RW_MALLOC ((optlen + 3) * sizeof (char*) / 2);
|
|
/* (strlen (opts)+3)/2 is overkill for the most situations, but it is just
|
|
large enough to handle the worst case scenario. The worst case is a
|
|
string similar to 'x y' or 'x y z', requiring array lengths of 4 and 5
|
|
respectively.
|
|
*/
|
|
|
|
last = target = argv [0] = (char*)RW_MALLOC (optlen + 1);
|
|
|
|
/* Transcribe the contents, handling escaping and splitting */
|
|
for (pos = opts; *pos; ++pos) {
|
|
if (in_escape) {
|
|
*(target++) = *pos;
|
|
in_escape = 0;
|
|
continue;
|
|
}
|
|
if (isspace (*pos)) {
|
|
if (in_quote) {
|
|
*(target++) = *pos;
|
|
}
|
|
else {
|
|
if (in_token) {
|
|
*(target++) = '\0';
|
|
*(table_pos++) = last;
|
|
in_token = 0;
|
|
}
|
|
last = target;
|
|
}
|
|
continue;
|
|
}
|
|
in_token = 1;
|
|
switch (*pos) {
|
|
case escape_code:
|
|
in_escape = 1;
|
|
break;
|
|
case '"':
|
|
case '\'':
|
|
if (*pos == in_quote) {
|
|
in_quote = 0;
|
|
break;
|
|
}
|
|
else if (0 == in_quote) {
|
|
in_quote = *pos;
|
|
break;
|
|
}
|
|
/* intentionally falling through (in a quote and quote didn't
|
|
match opening quote.
|
|
*/
|
|
default:
|
|
*(target++) = *pos;
|
|
}
|
|
}
|
|
|
|
if (in_token) { /* close and record the final token */
|
|
*(target++) = '\0';
|
|
*(table_pos++) = last;
|
|
}
|
|
*table_pos = (char*)0;/*And terminate the array*/
|
|
|
|
return argv;
|
|
}
|