mirror of https://github.com/acidanthera/audk.git
656 lines
18 KiB
C
656 lines
18 KiB
C
|
/** @file
|
||
|
A simple "fuzzer" application for OrderedCollectionLib, reading commands from
|
||
|
the standard input, and writing results to the standard output.
|
||
|
|
||
|
Make sure you configure your platform so that the console stderr device is
|
||
|
visible to the user (or else run the program from wherever stderr is visible
|
||
|
per default, eg. serial line).
|
||
|
|
||
|
Copyright (C) 2014, Red Hat, Inc.
|
||
|
Copyright (c) 2010 - 2011, Intel Corporation. All rights reserved.<BR>
|
||
|
|
||
|
This program and the accompanying materials are licensed and made available
|
||
|
under the terms and conditions of the BSD License which accompanies this
|
||
|
distribution. The full text of the license may be found at
|
||
|
http://opensource.org/licenses/bsd-license.
|
||
|
|
||
|
THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS, WITHOUT
|
||
|
WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
|
||
|
**/
|
||
|
|
||
|
#include <assert.h> // assert()
|
||
|
#include <errno.h> // errno
|
||
|
#include <limits.h> // INT_MIN
|
||
|
#include <stdio.h> // fgets()
|
||
|
#include <stdlib.h> // EXIT_FAILURE
|
||
|
#include <string.h> // strerror()
|
||
|
#include <unistd.h> // getopt()
|
||
|
|
||
|
#include <Library/OrderedCollectionLib.h>
|
||
|
|
||
|
//
|
||
|
// We allow the user to select between stdin+stdout and regular input+output
|
||
|
// files via command line options. We don't rely on shell redirection for two
|
||
|
// reasons:
|
||
|
//
|
||
|
// - The "old shell" doesn't support input redirection (<a, <);
|
||
|
//
|
||
|
// - The "new shell" supports input redirection (<a, <), but those redirections
|
||
|
// break fgets(stdin). Both when redirecting stdin from an ASCII file (<a),
|
||
|
// and when redirecting stdin from a UCS-2 file (<), the very first fgets()
|
||
|
// spirals into an infinite loop, spewing ^@ on the serial console.
|
||
|
//
|
||
|
// Performing fopen() manually (made available to the user with the -i option),
|
||
|
// and reading from that stream with fgets() work under both old and new shell.
|
||
|
// Only ASCII encoded files are supported.
|
||
|
//
|
||
|
static FILE *Input, *Output;
|
||
|
|
||
|
|
||
|
//
|
||
|
// Define a (potentially aggregate) key type.
|
||
|
//
|
||
|
typedef struct {
|
||
|
int Value;
|
||
|
} USER_KEY;
|
||
|
|
||
|
//
|
||
|
// The user structure includes the key as one of its fields. (There can be
|
||
|
// several, optionally differently typed, keys, if we link user structures into
|
||
|
// several collections, with different comparators.)
|
||
|
//
|
||
|
typedef struct {
|
||
|
unsigned char Supplementary1[4];
|
||
|
USER_KEY Key;
|
||
|
unsigned short Supplementary2[2];
|
||
|
} USER_STRUCT;
|
||
|
|
||
|
|
||
|
/**
|
||
|
Compare a standalone key against a user structure containing an embedded key.
|
||
|
|
||
|
@param[in] StandaloneKey Pointer to the bare key.
|
||
|
|
||
|
@param[in] UserStruct Pointer to the user structure with the embedded
|
||
|
key.
|
||
|
|
||
|
@retval <0 If StandaloneKey compares less than UserStruct's key.
|
||
|
|
||
|
@retval 0 If StandaloneKey compares equal to UserStruct's key.
|
||
|
|
||
|
@retval >0 If StandaloneKey compares greater than UserStruct's key.
|
||
|
**/
|
||
|
static
|
||
|
INTN
|
||
|
EFIAPI
|
||
|
KeyCompare (
|
||
|
IN CONST VOID *StandaloneKey,
|
||
|
IN CONST VOID *UserStruct
|
||
|
)
|
||
|
{
|
||
|
const USER_KEY *CmpKey;
|
||
|
const USER_STRUCT *CmpStruct;
|
||
|
|
||
|
CmpKey = StandaloneKey;
|
||
|
CmpStruct = UserStruct;
|
||
|
|
||
|
return CmpKey->Value < CmpStruct->Key.Value ? -1 :
|
||
|
CmpKey->Value > CmpStruct->Key.Value ? 1 :
|
||
|
0;
|
||
|
}
|
||
|
|
||
|
|
||
|
/**
|
||
|
Comparator function type for two user structures.
|
||
|
|
||
|
@param[in] UserStruct1 Pointer to the first user structure.
|
||
|
|
||
|
@param[in] UserStruct2 Pointer to the second user structure.
|
||
|
|
||
|
@retval <0 If UserStruct1 compares less than UserStruct2.
|
||
|
|
||
|
@retval 0 If UserStruct1 compares equal to UserStruct2.
|
||
|
|
||
|
@retval >0 If UserStruct1 compares greater than UserStruct2.
|
||
|
**/
|
||
|
static
|
||
|
INTN
|
||
|
EFIAPI
|
||
|
UserStructCompare (
|
||
|
IN CONST VOID *UserStruct1,
|
||
|
IN CONST VOID *UserStruct2
|
||
|
)
|
||
|
{
|
||
|
const USER_STRUCT *CmpStruct1;
|
||
|
|
||
|
CmpStruct1 = UserStruct1;
|
||
|
return KeyCompare (&CmpStruct1->Key, UserStruct2);
|
||
|
}
|
||
|
|
||
|
|
||
|
/**
|
||
|
Empty the collection by iterating forward through its entries.
|
||
|
|
||
|
This function demonstrates that iterators different from the one being
|
||
|
removed remain valid.
|
||
|
|
||
|
@param[in,out] Collection The collection to empty.
|
||
|
**/
|
||
|
static void
|
||
|
CmdForwardEmpty (
|
||
|
IN OUT ORDERED_COLLECTION *Collection
|
||
|
)
|
||
|
{
|
||
|
ORDERED_COLLECTION_ENTRY *Entry;
|
||
|
|
||
|
Entry = OrderedCollectionMin (Collection);
|
||
|
while (Entry != NULL) {
|
||
|
ORDERED_COLLECTION_ENTRY *Next;
|
||
|
void *Ptr;
|
||
|
USER_STRUCT *UserStruct;
|
||
|
|
||
|
Next = OrderedCollectionNext (Entry);
|
||
|
OrderedCollectionDelete (Collection, Entry, &Ptr);
|
||
|
|
||
|
UserStruct = Ptr;
|
||
|
fprintf (Output, "%s: %d: removed\n", __FUNCTION__, UserStruct->Key.Value);
|
||
|
free (UserStruct);
|
||
|
|
||
|
Entry = Next;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/**
|
||
|
Empty the collection by iterating backward through its entries.
|
||
|
|
||
|
This function demonstrates that iterators different from the one being
|
||
|
removed remain valid.
|
||
|
|
||
|
@param[in,out] Collection The collection to empty.
|
||
|
**/
|
||
|
static void
|
||
|
CmdBackwardEmpty (
|
||
|
IN OUT ORDERED_COLLECTION *Collection
|
||
|
)
|
||
|
{
|
||
|
ORDERED_COLLECTION_ENTRY *Entry;
|
||
|
|
||
|
Entry = OrderedCollectionMax (Collection);
|
||
|
while (Entry != NULL) {
|
||
|
ORDERED_COLLECTION_ENTRY *Prev;
|
||
|
void *Ptr;
|
||
|
USER_STRUCT *UserStruct;
|
||
|
|
||
|
Prev = OrderedCollectionPrev (Entry);
|
||
|
OrderedCollectionDelete (Collection, Entry, &Ptr);
|
||
|
|
||
|
UserStruct = Ptr;
|
||
|
fprintf (Output, "%s: %d: removed\n", __FUNCTION__, UserStruct->Key.Value);
|
||
|
free (UserStruct);
|
||
|
|
||
|
Entry = Prev;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/**
|
||
|
List the user structures linked into the collection, in increasing order.
|
||
|
|
||
|
@param[in] Collection The collection to list.
|
||
|
**/
|
||
|
static void
|
||
|
CmdForwardList (
|
||
|
IN ORDERED_COLLECTION *Collection
|
||
|
)
|
||
|
{
|
||
|
ORDERED_COLLECTION_ENTRY *Entry;
|
||
|
|
||
|
for (Entry = OrderedCollectionMin (Collection); Entry != NULL;
|
||
|
Entry = OrderedCollectionNext (Entry)) {
|
||
|
USER_STRUCT *UserStruct;
|
||
|
|
||
|
UserStruct = OrderedCollectionUserStruct (Entry);
|
||
|
fprintf (Output, "%s: %d\n", __FUNCTION__, UserStruct->Key.Value);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/**
|
||
|
List the user structures linked into the collection, in decreasing order.
|
||
|
|
||
|
@param[in] Collection The collection to list.
|
||
|
**/
|
||
|
static void
|
||
|
CmdBackwardList (
|
||
|
IN ORDERED_COLLECTION *Collection
|
||
|
)
|
||
|
{
|
||
|
ORDERED_COLLECTION_ENTRY *Entry;
|
||
|
|
||
|
for (Entry = OrderedCollectionMax (Collection); Entry != NULL;
|
||
|
Entry = OrderedCollectionPrev (Entry)) {
|
||
|
USER_STRUCT *UserStruct;
|
||
|
|
||
|
UserStruct = OrderedCollectionUserStruct (Entry);
|
||
|
fprintf (Output, "%s: %d\n", __FUNCTION__, UserStruct->Key.Value);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/**
|
||
|
Create a new user structure and attempt to insert it into the collection.
|
||
|
|
||
|
@param[in] Value The key value of the user structure to create.
|
||
|
|
||
|
@param[in,out] Collection The collection to insert the new user structure
|
||
|
into.
|
||
|
**/
|
||
|
static void
|
||
|
CmdInsert (
|
||
|
IN int Value,
|
||
|
IN OUT ORDERED_COLLECTION *Collection
|
||
|
)
|
||
|
{
|
||
|
USER_STRUCT *UserStruct, *UserStruct2;
|
||
|
RETURN_STATUS Status;
|
||
|
ORDERED_COLLECTION_ENTRY *Entry;
|
||
|
|
||
|
UserStruct = calloc (1, sizeof *UserStruct);
|
||
|
if (UserStruct == NULL) {
|
||
|
fprintf (Output, "%s: %d: calloc(): out of memory\n", __FUNCTION__, Value);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
UserStruct->Key.Value = Value;
|
||
|
Status = OrderedCollectionInsert (Collection, &Entry, UserStruct);
|
||
|
|
||
|
switch (Status) {
|
||
|
case RETURN_OUT_OF_RESOURCES:
|
||
|
fprintf (Output, "%s: %d: OrderedCollectionInsert(): out of memory\n",
|
||
|
__FUNCTION__, Value);
|
||
|
goto ReleaseUserStruct;
|
||
|
|
||
|
case RETURN_ALREADY_STARTED:
|
||
|
UserStruct2 = OrderedCollectionUserStruct (Entry);
|
||
|
assert (UserStruct != UserStruct2);
|
||
|
assert (UserStruct2->Key.Value == Value);
|
||
|
fprintf (Output, "%s: %d: already exists\n", __FUNCTION__,
|
||
|
UserStruct2->Key.Value);
|
||
|
goto ReleaseUserStruct;
|
||
|
|
||
|
default:
|
||
|
assert (Status == RETURN_SUCCESS);
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
assert (OrderedCollectionUserStruct (Entry) == UserStruct);
|
||
|
fprintf (Output, "%s: %d: inserted\n", __FUNCTION__, Value);
|
||
|
return;
|
||
|
|
||
|
ReleaseUserStruct:
|
||
|
free (UserStruct);
|
||
|
}
|
||
|
|
||
|
|
||
|
/**
|
||
|
Look up a user structure by key in the collection and print it.
|
||
|
|
||
|
@param[in] Value The key of the user structure to find.
|
||
|
|
||
|
@param[in] Collection The collection to search for the user structure with
|
||
|
the key.
|
||
|
**/
|
||
|
static void
|
||
|
CmdFind (
|
||
|
IN int Value,
|
||
|
IN ORDERED_COLLECTION *Collection
|
||
|
)
|
||
|
{
|
||
|
USER_KEY StandaloneKey;
|
||
|
ORDERED_COLLECTION_ENTRY *Entry;
|
||
|
USER_STRUCT *UserStruct;
|
||
|
|
||
|
StandaloneKey.Value = Value;
|
||
|
Entry = OrderedCollectionFind (Collection, &StandaloneKey);
|
||
|
|
||
|
if (Entry == NULL) {
|
||
|
fprintf (Output, "%s: %d: not found\n", __FUNCTION__, Value);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
UserStruct = OrderedCollectionUserStruct (Entry);
|
||
|
assert (UserStruct->Key.Value == StandaloneKey.Value);
|
||
|
fprintf (Output, "%s: %d: found\n", __FUNCTION__, UserStruct->Key.Value);
|
||
|
}
|
||
|
|
||
|
|
||
|
/**
|
||
|
Look up a user structure by key in the collection and delete it.
|
||
|
|
||
|
@param[in] Value The key of the user structure to find.
|
||
|
|
||
|
@param[in] Collection The collection to search for the user structure with
|
||
|
the key.
|
||
|
**/
|
||
|
static void
|
||
|
CmdDelete (
|
||
|
IN int Value,
|
||
|
IN ORDERED_COLLECTION *Collection
|
||
|
)
|
||
|
{
|
||
|
USER_KEY StandaloneKey;
|
||
|
ORDERED_COLLECTION_ENTRY *Entry;
|
||
|
void *Ptr;
|
||
|
USER_STRUCT *UserStruct;
|
||
|
|
||
|
StandaloneKey.Value = Value;
|
||
|
Entry = OrderedCollectionFind (Collection, &StandaloneKey);
|
||
|
|
||
|
if (Entry == NULL) {
|
||
|
fprintf (Output, "%s: %d: not found\n", __FUNCTION__, Value);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
OrderedCollectionDelete (Collection, Entry, &Ptr);
|
||
|
|
||
|
UserStruct = Ptr;
|
||
|
assert (UserStruct->Key.Value == StandaloneKey.Value);
|
||
|
fprintf (Output, "%s: %d: removed\n", __FUNCTION__, UserStruct->Key.Value);
|
||
|
free (UserStruct);
|
||
|
}
|
||
|
|
||
|
|
||
|
typedef struct {
|
||
|
const char *Command;
|
||
|
void (*Function) (ORDERED_COLLECTION *Collection);
|
||
|
const char *Description;
|
||
|
} KEYLESS_COMMAND;
|
||
|
|
||
|
typedef struct {
|
||
|
const char *Command;
|
||
|
void (*Function) (int Value, ORDERED_COLLECTION *Collection);
|
||
|
const char *Description;
|
||
|
} KEYED_COMMAND;
|
||
|
|
||
|
static const KEYLESS_COMMAND KeylessCommands[] = {
|
||
|
{ "forward-empty", CmdForwardEmpty,
|
||
|
"empty the collection iterating forward" },
|
||
|
{ "fe", CmdForwardEmpty,
|
||
|
"shorthand for forward-empty" },
|
||
|
{ "backward-empty", CmdBackwardEmpty,
|
||
|
"empty the collection iterating backward" },
|
||
|
{ "be", CmdBackwardEmpty,
|
||
|
"shorthand for backward-empty" },
|
||
|
{ "forward-list", CmdForwardList,
|
||
|
"list contents, iterating forward" },
|
||
|
{ "fl", CmdForwardList,
|
||
|
"shorthand for forward-list" },
|
||
|
{ "backward-list", CmdBackwardList,
|
||
|
"list contents, iterating backward" },
|
||
|
{ "bl", CmdBackwardList,
|
||
|
"shorthand for backward-list" },
|
||
|
{ NULL }
|
||
|
};
|
||
|
|
||
|
static const KEYED_COMMAND KeyedCommands[] = {
|
||
|
{ "insert ", CmdInsert, "insert value into collection" },
|
||
|
{ "i ", CmdInsert, "shorthand for insert" },
|
||
|
{ "find ", CmdFind, "find value in collection" },
|
||
|
{ "f ", CmdFind, "shorthand for find" },
|
||
|
{ "delete ", CmdDelete, "delete value from collection" },
|
||
|
{ "d ", CmdDelete, "shorthand for delete" },
|
||
|
{ NULL }
|
||
|
};
|
||
|
|
||
|
|
||
|
/**
|
||
|
List the supported commands on stderr.
|
||
|
**/
|
||
|
static void
|
||
|
ListCommands (
|
||
|
void
|
||
|
)
|
||
|
{
|
||
|
const KEYLESS_COMMAND *KeylessCmd;
|
||
|
const KEYED_COMMAND *KeyedCmd;
|
||
|
|
||
|
fprintf (stderr, "Supported commands:\n\n");
|
||
|
for (KeylessCmd = KeylessCommands; KeylessCmd->Command != NULL;
|
||
|
++KeylessCmd) {
|
||
|
fprintf (stderr, "%-14s: %s\n", KeylessCmd->Command,
|
||
|
KeylessCmd->Description);
|
||
|
}
|
||
|
for (KeyedCmd = KeyedCommands; KeyedCmd->Command != NULL; ++KeyedCmd) {
|
||
|
fprintf (stderr, "%-9s<int>: %s\n", KeyedCmd->Command,
|
||
|
KeyedCmd->Description);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/**
|
||
|
Configure stdio FILEs that we'll use for input and output.
|
||
|
|
||
|
@param[in] ArgC The number of elements in ArgV, from main(). The environment
|
||
|
is required to ensure ArgC >= 1 (ie. that the program name,
|
||
|
ArgV[0], is available).
|
||
|
|
||
|
@param[in] ArgV Command line argument list, from main().
|
||
|
**/
|
||
|
static void
|
||
|
SetupInputOutput (
|
||
|
IN int ArgC,
|
||
|
IN char **ArgV
|
||
|
)
|
||
|
{
|
||
|
char *InputName, *OutputName;
|
||
|
int Loop;
|
||
|
|
||
|
assert (ArgC >= 1);
|
||
|
|
||
|
InputName = NULL;
|
||
|
OutputName = NULL;
|
||
|
Loop = 1;
|
||
|
|
||
|
while (Loop) {
|
||
|
switch (getopt (ArgC, ArgV, ":i:o:h")) {
|
||
|
case 'i':
|
||
|
InputName = optarg;
|
||
|
break;
|
||
|
|
||
|
case 'o':
|
||
|
OutputName = optarg;
|
||
|
break;
|
||
|
|
||
|
case 'h':
|
||
|
fprintf (stderr,
|
||
|
"%1$s: simple OrderedCollectionLib tester\n"
|
||
|
"\n"
|
||
|
"Usage: 1. %1$s [-i InputFile] [-o OutputFile]\n"
|
||
|
" 2. %1$s -h\n"
|
||
|
"\n"
|
||
|
"Options:\n"
|
||
|
" -i InputFile : read commands from InputFile\n"
|
||
|
" (will read from stdin if absent)\n"
|
||
|
" -o OutputFile: write command responses to OutputFile\n"
|
||
|
" (will write to stdout if absent)\n"
|
||
|
" -h : print this help and exit\n"
|
||
|
"\n", ArgV[0]);
|
||
|
ListCommands ();
|
||
|
exit (EXIT_SUCCESS);
|
||
|
|
||
|
//
|
||
|
// The current "compatibility" getopt() implementation doesn't support optopt,
|
||
|
// but it gracefully degrades these branches to the others (one of the optarg
|
||
|
// ones or the excess operands one).
|
||
|
//
|
||
|
#if 0
|
||
|
case ':':
|
||
|
fprintf (stderr, "%s: option -%c requires an argument; pass -h for "
|
||
|
"help\n", ArgV[0], optopt);
|
||
|
exit (EXIT_FAILURE);
|
||
|
|
||
|
case '?':
|
||
|
fprintf (stderr, "%s: unknown option -%c; pass -h for help\n", ArgV[0],
|
||
|
optopt);
|
||
|
exit (EXIT_FAILURE);
|
||
|
#endif
|
||
|
|
||
|
case -1:
|
||
|
if (optind != ArgC) {
|
||
|
fprintf (stderr, "%s: excess operands on command line; pass -h for "
|
||
|
"help\n", ArgV[0]);
|
||
|
exit (EXIT_FAILURE);
|
||
|
}
|
||
|
Loop = 0;
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
assert (0);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (InputName == NULL) {
|
||
|
Input = stdin;
|
||
|
} else {
|
||
|
Input = fopen (InputName, "r");
|
||
|
if (Input == NULL) {
|
||
|
fprintf (stderr, "%s: fopen(\"%s\", \"r\"): %s\n", ArgV[0], InputName,
|
||
|
strerror (errno));
|
||
|
exit (EXIT_FAILURE);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (OutputName == NULL) {
|
||
|
Output = stdout;
|
||
|
} else {
|
||
|
Output = fopen (OutputName, "w");
|
||
|
if (Output == NULL) {
|
||
|
fprintf (stderr, "%s: fopen(\"%s\", \"w\"): %s\n", ArgV[0], OutputName,
|
||
|
strerror (errno));
|
||
|
exit (EXIT_FAILURE);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// When reading commands from the standard input, assume interactive mode,
|
||
|
// and list the supported commands. However, delay this until both streams
|
||
|
// are set up.
|
||
|
//
|
||
|
if (InputName == NULL) {
|
||
|
ListCommands ();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
int
|
||
|
main (
|
||
|
IN int ArgC,
|
||
|
IN char **ArgV
|
||
|
)
|
||
|
{
|
||
|
int RetVal;
|
||
|
ORDERED_COLLECTION *Collection;
|
||
|
char Line[256];
|
||
|
|
||
|
SetupInputOutput (ArgC, ArgV);
|
||
|
|
||
|
Collection = OrderedCollectionInit (UserStructCompare, KeyCompare);
|
||
|
if (Collection == NULL) {
|
||
|
fprintf (stderr, "%s: OrderedCollectionInit(): out of memory\n",
|
||
|
__FUNCTION__);
|
||
|
return EXIT_FAILURE;
|
||
|
}
|
||
|
|
||
|
RetVal = EXIT_SUCCESS;
|
||
|
while (fgets (Line, sizeof Line, Input) != NULL) {
|
||
|
size_t Length;
|
||
|
const KEYLESS_COMMAND *KeylessCmd;
|
||
|
const KEYED_COMMAND *KeyedCmd;
|
||
|
|
||
|
Length = strlen (Line);
|
||
|
assert (Length > 0);
|
||
|
if (Line[Length - 1] != '\n') {
|
||
|
fprintf (stderr, "%s: overlong line\n", __FUNCTION__);
|
||
|
RetVal = EXIT_FAILURE;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Strip [\r]\n.
|
||
|
//
|
||
|
Line[Length - 1] = '\0';
|
||
|
if (Length >= 2 && Line[Length - 2] == '\r') {
|
||
|
Line[Length - 2] = '\0';
|
||
|
}
|
||
|
//
|
||
|
// Ignore empty lines and comments.
|
||
|
//
|
||
|
if (Line[0] == '\0' || Line[0] == '#') {
|
||
|
if (Input != stdin) {
|
||
|
//
|
||
|
// ... but echo them back in non-interactive mode.
|
||
|
//
|
||
|
fprintf (Output, "%s\n", Line);
|
||
|
}
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Ironically, this is the kind of loop that should be replaced with an
|
||
|
// ORDERED_COLLECTION.
|
||
|
//
|
||
|
for (KeylessCmd = KeylessCommands; KeylessCmd->Command != NULL;
|
||
|
++KeylessCmd) {
|
||
|
if (strcmp (KeylessCmd->Command, Line) == 0) {
|
||
|
KeylessCmd->Function (Collection);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
if (KeylessCmd->Command != NULL) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
for (KeyedCmd = KeyedCommands; KeyedCmd->Command != NULL; ++KeyedCmd) {
|
||
|
size_t CmdLength;
|
||
|
|
||
|
CmdLength = strlen (KeyedCmd->Command);
|
||
|
assert (CmdLength >= 2);
|
||
|
if (strncmp (KeyedCmd->Command, Line, CmdLength) == 0) {
|
||
|
char *CommandArg, *EndPtr;
|
||
|
long Value;
|
||
|
|
||
|
CommandArg = Line + CmdLength;
|
||
|
errno = 0;
|
||
|
Value = strtol (CommandArg, &EndPtr, 10);
|
||
|
if (EndPtr == CommandArg || // no conversion performed
|
||
|
errno != 0 || // not in long's range, etc
|
||
|
*EndPtr != '\0' || // final string not empty
|
||
|
Value < INT_MIN || Value > INT_MAX // parsed long not in int range
|
||
|
) {
|
||
|
fprintf (stderr, "%s: %.*s: \"%s\": not an int\n", __FUNCTION__,
|
||
|
(int)(CmdLength - 1), Line, CommandArg);
|
||
|
} else {
|
||
|
KeyedCmd->Function (Value, Collection);
|
||
|
}
|
||
|
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
if (KeyedCmd->Command != NULL) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
fprintf (stderr, "%s: \"%s\": unknown command\n", __FUNCTION__, Line);
|
||
|
}
|
||
|
|
||
|
if (RetVal == EXIT_SUCCESS && ferror (Input)) {
|
||
|
fprintf (stderr, "%s: fgets(): %s\n", __FUNCTION__, strerror (errno));
|
||
|
RetVal = EXIT_FAILURE;
|
||
|
}
|
||
|
|
||
|
CmdForwardEmpty (Collection);
|
||
|
OrderedCollectionUninit (Collection);
|
||
|
return RetVal;
|
||
|
}
|