blob: 11833a1185b9f4698ff2c277789e068266347a9d [file] [log] [blame] [edit]
/*
* BRLTTY - A background process providing access to the console screen (when in
* text mode) for a blind person using a refreshable braille display.
*
* Copyright (C) 1995-2023 by The BRLTTY Developers.
*
* BRLTTY comes with ABSOLUTELY NO WARRANTY.
*
* This is free software, placed under the terms of the
* GNU Lesser General Public License, as published by the Free Software
* Foundation; either version 2.1 of the License, or (at your option) any
* later version. Please see the file LICENSE-LGPL for details.
*
* Web Page: http://brltty.app/
*
* This software is maintained by Dave Mielke <dave@mielke.cc>.
*/
#include "prologue.h"
#include <stdio.h>
#include <string.h>
#include <strings.h>
#include <ctype.h>
#include <errno.h>
#include "program.h"
#include "cmdline.h"
#include "params.h"
#include "log.h"
#include "strfmt.h"
#include "file.h"
#include "datafile.h"
#include "utf8.h"
#include "parse.h"
#undef ALLOW_DOS_OPTION_SYNTAX
#if defined(__MINGW32__) || defined(__MSDOS__)
#define ALLOW_DOS_OPTION_SYNTAX
#endif /* allow DOS syntax */
#ifdef HAVE_GETOPT_H
#include <getopt.h>
#endif /* HAVE_GETOPT_H */
typedef struct {
const CommandLineOptions *const options;
uint8_t *const ensuredSettings;
uint8_t exitImmediately:1;
uint8_t warning:1;
uint8_t syntaxError:1;
} OptionProcessingInformation;
static int
hasExtendableArgument (const CommandLineOption *option) {
return option->argument && (option->flags & OPT_Extend);
}
static uint8_t *
getEnsuredSetting (
const OptionProcessingInformation *info,
const CommandLineOption *option
) {
return &info->ensuredSettings[option - info->options->table];
}
static void
setEnsuredSetting (
const OptionProcessingInformation *info,
const CommandLineOption *option,
uint8_t yes
) {
*getEnsuredSetting(info, option) = yes;
}
static int
ensureSetting (
OptionProcessingInformation *info,
const CommandLineOption *option,
const char *value
) {
uint8_t *ensured = getEnsuredSetting(info, option);
if (!*ensured || hasExtendableArgument(option)) {
*ensured = 1;
if (option->argument) {
if (option->setting.string) {
if (option->flags & OPT_Extend) {
if (!extendStringSetting(option->setting.string, value, 1)) return 0;
} else if (!changeStringSetting(option->setting.string, value)) {
return 0;
}
}
} else {
if (option->setting.flag) {
if (option->flags & OPT_Extend) {
int count;
if (isInteger(&count, value) && (count >= 0)) {
*option->setting.flag = count;
} else {
logMessage(LOG_ERR, "%s: %s", gettext("invalid counter setting"), value);
info->warning = 1;
}
} else {
unsigned int on;
if (validateFlagKeyword(&on, value)) {
*option->setting.flag = on;
} else {
logMessage(LOG_ERR, "%s: %s", gettext("invalid flag setting"), value);
info->warning = 1;
}
}
}
}
}
return 1;
}
static void
showWrappedText (
FILE *stream, const char *text, char *line,
unsigned int offset, unsigned int width
) {
unsigned int limit = width - offset;
unsigned int charsLeft = strlen(text);
while (1) {
unsigned int charCount = charsLeft;
if (charCount > limit) {
charCount = limit;
while (charCount > 0) {
if (isspace(text[charCount])) break;
charCount -= 1;
}
while (charCount > 0) {
if (!isspace(text[--charCount])) {
charCount += 1;
break;
}
}
}
if (charCount > 0) {
memcpy(line+offset, text, charCount);
unsigned int length = offset + charCount;
writeWithConsoleEncoding(stream, line, length);
fputc('\n', stream);
}
while (charCount < charsLeft) {
if (!isspace(text[charCount])) break;
charCount += 1;
}
if (!(charsLeft -= charCount)) break;
text += charCount;
memset(line, ' ', offset);
}
}
static void
showFormattedLines (
FILE *stream, const char *const *const *blocks,
char *line, int width
) {
const char *const *const *block = blocks;
char *paragraphText = NULL;
size_t paragraphSize = 0;
size_t paragraphLength = 0;
while (*block) {
const char *const *chunk = *block++;
if (!*chunk) continue;
while (1) {
const char *text = *chunk;
if (!text) break;
text = gettext(text);
if (*text && !iswspace(*text)) {
size_t textLength = strlen(text);
size_t newLength = paragraphLength + textLength + 1;
int extending = !!paragraphLength;
if (extending) newLength += 1;
if (newLength > paragraphSize) {
size_t newSize = (newLength | 0XFF) + 1;
char *newText = realloc(paragraphText, newSize);
if (!newText) {
logMallocError();
goto done;
}
paragraphText = newText;
paragraphSize = newSize;
}
if (extending) paragraphText[paragraphLength++] = ' ';
memcpy(&paragraphText[paragraphLength], text, textLength);
paragraphText[paragraphLength += textLength] = 0;
} else {
if (paragraphLength) {
showWrappedText(stream, paragraphText, line, 0, width);
paragraphLength = 0;
}
fprintf(stream, "%s\n", text);
}
chunk += 1;
}
if (paragraphLength) {
showWrappedText(stream, paragraphText, line, 0, width);
paragraphLength = 0;
}
if (*block) fputc('\n', stream);
}
done:
if (paragraphText) free(paragraphText);
}
static void
showSyntax (
FILE *stream,
int haveOptions,
const char *parameters
) {
fprintf(stream, "%s: %s", gettext("Syntax"), programName);
if (haveOptions) {
fprintf(stream, " [-%s ...]", gettext("option"));
}
if (parameters && *parameters) {
fprintf(stream, " %s", parameters);
}
fprintf(stream, "\n");
}
static void
showOptions (
FILE *stream, char *line, unsigned int lineWidth,
OptionProcessingInformation *info
) {
size_t optionCount = info->options->count;
if (info->options->count > 0) {
unsigned int letterWidth = 0;
unsigned int wordWidth = 0;
unsigned int argumentWidth = 0;
for (unsigned int optionIndex=0; optionIndex<optionCount; optionIndex+=1) {
const CommandLineOption *option = &info->options->table[optionIndex];
if (option->word) {
unsigned int length = strlen(option->word);
if (option->argument) length += 1;
wordWidth = MAX(wordWidth, length);
}
if (option->letter) letterWidth = 2;
if (option->argument) argumentWidth = MAX(argumentWidth, strlen(gettext(option->argument)));
}
fprintf(stream, "\n%s:\n", gettext("Options"));
for (unsigned int optionIndex=0; optionIndex<optionCount; optionIndex+=1) {
const CommandLineOption *option = &info->options->table[optionIndex];
unsigned int lineLength = 0;
while (lineLength < 2) line[lineLength++] = ' ';
{
unsigned int end = lineLength + letterWidth;
if (option->letter) {
line[lineLength++] = '-';
line[lineLength++] = option->letter;
}
while (lineLength < letterWidth) {
line[lineLength++] = ' ';
}
while (lineLength < end) line[lineLength++] = ' ';
}
line[lineLength++] = ' ';
{
unsigned int end = lineLength + 2 + wordWidth;
if (option->word) {
size_t wordLength = strlen(option->word);
line[lineLength++] = '-';
line[lineLength++] = '-';
memcpy(line+lineLength, option->word, wordLength);
lineLength += wordLength;
if (option->argument) line[lineLength++] = '=';
}
while (lineLength < end) line[lineLength++] = ' ';
}
line[lineLength++] = ' ';
{
unsigned int end = lineLength + argumentWidth;
if (option->argument) {
const char *argument = gettext(option->argument);
size_t argumentLength = strlen(argument);
memcpy(line+lineLength, argument, argumentLength);
lineLength += argumentLength;
}
while (lineLength < end) line[lineLength++] = ' ';
}
line[lineLength++] = ' ';
line[lineLength++] = ' ';
{
const int formatStrings = !!(option->flags & OPT_Format);
const char *description = option->description? gettext(option->description): "";
char buffer[0X400];
char *from = buffer;
const char *const to = from + sizeof(buffer);
if (formatStrings? !!option->strings.format: !!option->strings.array) {
unsigned int index = 0;
const unsigned int limit = 4;
const char *strings[limit];
while (index < limit) {
const char *string;
if (formatStrings) {
size_t length = option->strings.format(from, (to - from), index);
if (length) {
string = from;
from += length + 1;
} else {
string = NULL;
}
} else {
string = option->strings.array[index];
}
if (!string) break;
strings[index++] = string;
}
while (index < limit) strings[index++] = "";
snprintf(from, (to - from),
description, strings[0], strings[1], strings[2], strings[3]
);
description = from;
}
showWrappedText(stream, description, line, lineLength, lineWidth);
}
}
}
}
static void
processCommandLine (
OptionProcessingInformation *info,
int *argumentCount,
char ***argumentVector,
const CommandLineUsage *usage
) {
const char *reset = NULL;
const char resetPrefix = '+';
int resetLetter;
int dosSyntax = 0;
const int firstNonLetter = 0X80;
const CommandLineOption *letterToOption[firstNonLetter + info->options->count];
for (unsigned int index=0; index<ARRAY_COUNT(letterToOption); index+=1) {
letterToOption[index] = NULL;
}
int indexToLetter[info->options->count];
char shortOptions[2 + (info->options->count * 2) + 1];
{
int nextNonLetter = firstNonLetter;
char *opt = shortOptions;
*opt++ = '+'; // stop parsing options as soon as a non-option argument is encountered
*opt++ = ':'; // Don't write any error messages
for (unsigned int index=0; index<info->options->count; index+=1) {
const CommandLineOption *option = &info->options->table[index];
int letter = option->letter;
if (letter) {
if (letterToOption[letter]) {
logMessage(LOG_WARNING, "duplicate short option: -%c", letter);
letter = 0;
} else {
*opt++ = letter;
if (option->argument) *opt++ = ':';
}
}
if (!letter) letter = nextNonLetter++;
indexToLetter[index] = letter;
letterToOption[letter] = option;
if (option->argument) {
if (option->setting.string) *option->setting.string = NULL;
} else {
if (option->setting.flag) *option->setting.flag = 0;
}
}
*opt = 0;
}
#ifdef HAVE_GETOPT_LONG
struct option longOptions[(info->options->count * 2) + 1];
{
struct option *opt = longOptions;
for (unsigned int index=0; index<info->options->count; index+=1) {
const CommandLineOption *option = &info->options->table[index];
const char *word = option->word;
if (!word) continue;
int letter = indexToLetter[index];
opt->name = word;
opt->has_arg = option->argument? required_argument: no_argument;
opt->flag = NULL;
opt->val = letter;
opt += 1;
if (!option->argument && option->setting.flag) {
char *name;
const char *noPrefix = "no-";
size_t noLength = strlen(noPrefix);
if (strncasecmp(noPrefix, word, noLength) == 0) {
name = strdup(&word[noLength]);
} else {
size_t size = noLength + strlen(word) + 1;
if ((name = malloc(size))) {
snprintf(name, size, "%s%s", noPrefix, word);
}
}
if (name) {
opt->name = name;
opt->has_arg = no_argument;
opt->flag = &resetLetter;
opt->val = letter;
opt += 1;
} else {
logMallocError();
}
}
}
memset(opt, 0, sizeof(*opt));
}
#endif /* HAVE_GETOPT_LONG */
#ifdef ALLOW_DOS_OPTION_SYNTAX
const char dosPrefix = '/';
if (*argumentCount > 1) {
if (*(*argumentVector)[1] == dosPrefix) {
dosSyntax = 1;
}
}
#endif /* ALLOW_DOS_OPTION_SYNTAX */
opterr = 0;
optind = 1;
int lastOptInd = -1;
int optHelp = 0;
while (1) {
int letter;
char prefix = '-';
resetLetter = 0;
if (optind == *argumentCount) {
letter = -1;
} else {
char *argument = (*argumentVector)[optind];
#ifdef ALLOW_DOS_OPTION_SYNTAX
if (dosSyntax) {
prefix = dosPrefix;
optind += 1;
if (*argument != dosPrefix) {
letter = -1;
} else {
char *name = argument + 1;
size_t nameLength = strcspn(name, ":");
char *value = name[nameLength]? (name + nameLength + 1): NULL;
const CommandLineOption *option;
if (nameLength == 1) {
option = letterToOption[letter = *name];
} else {
letter = -1;
for (unsigned int index=0; index<info->options->count; index+=1) {
option = &info->options->table[index];
const char *word = option->word;
if (word) {
if ((nameLength == strlen(word)) &&
(strncasecmp(word, name, nameLength) == 0)) {
letter = indexToLetter[index];
break;
}
}
}
if (letter < 0) {
option = NULL;
letter = 0;
}
}
optopt = letter;
optarg = value;
if (!option) {
letter = '?';
} else if (option->argument) {
if (!optarg) letter = ':';
} else if (value) {
unsigned int on;
if (!validateFlagKeyword(&on, value)) {
letter = '-';
} else if (!on) {
resetLetter = letter;
letter = 0;
}
}
}
} else
#endif /* ALLOW_DOS_OPTION_SYNTAX */
if (reset) {
prefix = resetPrefix;
if (!(letter = *reset++)) {
reset = NULL;
optind += 1;
continue;
}
{
const CommandLineOption *option = letterToOption[letter];
if (option && !option->argument && option->setting.flag) {
resetLetter = letter;
letter = 0;
} else {
optopt = letter;
letter = '?';
}
}
} else {
if (optind != lastOptInd) {
lastOptInd = optind;
if ((reset = (*argument == resetPrefix)? argument+1: NULL)) continue;
}
#ifdef HAVE_GETOPT_LONG
letter = getopt_long(*argumentCount, *argumentVector, shortOptions, longOptions, NULL);
#else /* HAVE_GETOPT_LONG */
letter = getopt(*argumentCount, *argumentVector, shortOptions);
#endif /* HAVE_GETOPT_LONG */
}
}
if (letter == -1) break;
/* continue on error as much as possible, as often we are typing blind
* and won't even see the error message unless the display comes up.
*/
switch (letter) {
default: {
const CommandLineOption *option = letterToOption[letter];
if (option->argument) {
if (!*optarg) {
setEnsuredSetting(info, option, 0);
break;
}
if (option->setting.string) {
if (option->flags & OPT_Extend) {
extendStringSetting(option->setting.string, optarg, 0);
} else {
changeStringSetting(option->setting.string, optarg);
}
}
} else {
if (option->setting.flag) {
if (option->flags & OPT_Extend) {
*option->setting.flag += 1;
} else {
*option->setting.flag = 1;
}
}
}
setEnsuredSetting(info, option, 1);
break;
}
case 0: { // reset a flag
const CommandLineOption *option = letterToOption[resetLetter];
*option->setting.flag = 0;
setEnsuredSetting(info, option, 1);
break;
}
{
const char *problem;
char message[0X100];
case '?': // an unknown option has been specified
info->syntaxError = 1;
problem = gettext("unknown option");
goto logOptionProblem;
case ':': // the operand for a string option hasn't been specified
info->syntaxError = 1;
problem = gettext("missing operand");
goto logOptionProblem;
case '-': // the operand for an option is invalid
info->warning = 1;
problem = gettext("invalid operand");
goto logOptionProblem;
logOptionProblem:
STR_BEGIN(message, sizeof(message));
STR_PRINTF("%s: ", problem);
size_t optionStart = STR_LENGTH;
if (optopt) {
const CommandLineOption *option = letterToOption[optopt];
if (option) {
const char *beforeLetter = "";
const char *afterLetter = "";
if (option->word) {
if (!dosSyntax) STR_PRINTF("%c", prefix);
STR_PRINTF("%c%s", prefix, option->word);
beforeLetter = " (";
afterLetter = ")";
}
if (option->letter) {
STR_PRINTF("%s%c%c%s", beforeLetter, prefix, option->letter, afterLetter);
}
} else if (optopt < firstNonLetter) {
STR_PRINTF("%c%c", prefix, optopt);
}
}
if (STR_LENGTH == optionStart) {
STR_PRINTF("%s", (*argumentVector)[optind-1]);
}
STR_END;
logMessage(LOG_WARNING, "%s", message);
break;
}
case 'h': // help - show usage summary and then exit
optHelp = 1;
break;
}
}
*argumentVector += optind;
*argumentCount -= optind;
if (optHelp) {
FILE *usageStream = stdout;
size_t width = UINT16_MAX;
getConsoleSize(&width, NULL);
char line[width+1];
{
const char *purpose = gettext(usage->purpose);
if (purpose && *purpose) {
showWrappedText(usageStream, purpose, line, 0, width);
fputc('\n', usageStream);
}
}
showSyntax(usageStream, !!info->options->count, usage->parameters);
showOptions(usageStream, line, width, info);
{
const char *const *const *notes = usage->notes;
if (notes && *notes) {
fputc('\n', usageStream);
showFormattedLines(usageStream, notes, line, width);
}
}
info->exitImmediately = 1;
}
#ifdef HAVE_GETOPT_LONG
{
struct option *opt = longOptions;
while (opt->name) {
if (opt->flag) free((char *)opt->name);
opt += 1;
}
}
#endif /* HAVE_GETOPT_LONG */
}
static void
processBootParameters (
OptionProcessingInformation *info,
const char *parameter
) {
const char *value;
char *allocated = NULL;
if (!(value = allocated = getBootParameters(parameter))) {
if (!(value = getenv(parameter))) {
return;
}
}
{
int parameterCount = 0;
char **parameters = splitString(value, ',', &parameterCount);
for (unsigned int optionIndex=0; optionIndex<info->options->count; optionIndex+=1) {
const CommandLineOption *option = &info->options->table[optionIndex];
if ((option->bootParameter) && (option->bootParameter <= parameterCount)) {
char *parameter = parameters[option->bootParameter-1];
if (*parameter) {
{
char *byte = parameter;
do {
if (*byte == '+') *byte = ',';
} while (*++byte);
}
ensureSetting(info, option, parameter);
}
}
}
deallocateStrings(parameters);
}
if (allocated) free(allocated);
}
static int
processEnvironmentVariable (
OptionProcessingInformation *info,
const CommandLineOption *option,
const char *prefix
) {
size_t prefixLength = strlen(prefix);
if ((option->flags & OPT_EnvVar) && option->word) {
size_t nameSize = prefixLength + 1 + strlen(option->word) + 1;
char name[nameSize];
snprintf(name, nameSize, "%s_%s", prefix, option->word);
{
char *character = name;
while (*character) {
if (*character == '-') {
*character = '_';
} else if (islower((unsigned char)*character)) {
*character = toupper((unsigned char)*character);
}
character += 1;
}
}
{
const char *setting = getenv(name);
if (setting && *setting) {
if (!ensureSetting(info, option, setting)) {
return 0;
}
}
}
}
return 1;
}
static int
processEnvironmentVariables (
OptionProcessingInformation *info,
const char *prefix
) {
for (unsigned int optionIndex=0; optionIndex<info->options->count; optionIndex+=1) {
const CommandLineOption *option = &info->options->table[optionIndex];
if (!processEnvironmentVariable(info, option, prefix)) return 0;
}
return 1;
}
static void
processInternalSettings (
OptionProcessingInformation *info,
int config
) {
for (unsigned int optionIndex=0; optionIndex<info->options->count; optionIndex+=1) {
const CommandLineOption *option = &info->options->table[optionIndex];
if (!(option->flags & OPT_Config) == !config) {
const char *setting = option->internal.setting;
char *newSetting = NULL;
if (!setting) setting = option->argument? "": OPT_WORD_FALSE;
if (option->internal.adjust) {
if (*setting) {
if ((newSetting = strdup(setting))) {
if (option->internal.adjust(&newSetting)) {
setting = newSetting;
}
} else {
logMallocError();
}
}
}
ensureSetting(info, option, setting);
if (newSetting) free(newSetting);
}
}
}
typedef struct {
unsigned int option;
wchar_t keyword[0];
} ConfigurationDirective;
static int
sortConfigurationDirectives (const void *element1, const void *element2) {
const ConfigurationDirective *const *directive1 = element1;
const ConfigurationDirective *const *directive2 = element2;
return compareKeywords((*directive1)->keyword, (*directive2)->keyword);
}
static int
searchConfigurationDirective (const void *target, const void *element) {
const wchar_t *keyword = target;
const ConfigurationDirective *const *directive = element;
return compareKeywords(keyword, (*directive)->keyword);
}
typedef struct {
OptionProcessingInformation *info;
char **settings;
struct {
ConfigurationDirective **table;
unsigned int count;
} directive;
} ConfigurationFileProcessingData;
static const ConfigurationDirective *
findConfigurationDirective (const wchar_t *keyword, const ConfigurationFileProcessingData *conf) {
const ConfigurationDirective *const *directive = bsearch(keyword, conf->directive.table, conf->directive.count, sizeof(*conf->directive.table), searchConfigurationDirective);
if (directive) return *directive;
return NULL;
}
static int
processConfigurationDirective (
const wchar_t *keyword,
const char *value,
const ConfigurationFileProcessingData *conf
) {
const ConfigurationDirective *directive = findConfigurationDirective(keyword, conf);
if (directive) {
const CommandLineOption *option = &conf->info->options->table[directive->option];
char **setting = &conf->settings[directive->option];
if (*setting && !hasExtendableArgument(option)) {
logMessage(LOG_ERR, "%s: %" PRIws, gettext("configuration directive specified more than once"), keyword);
conf->info->warning = 1;
free(*setting);
*setting = NULL;
}
if (*setting) {
if (!extendStringSetting(setting, value, 0)) return 0;
} else {
if (!(*setting = strdup(value))) {
logMallocError();
return 0;
}
}
} else {
logMessage(LOG_ERR, "%s: %" PRIws, gettext("unknown configuration directive"), keyword);
conf->info->warning = 1;
}
return 1;
}
static DATA_OPERANDS_PROCESSOR(processConfigurationOperands) {
const ConfigurationFileProcessingData *conf = data;
int ok = 1;
DataString keyword;
if (getDataString(file, &keyword, 0, "configuration directive")) {
DataString value;
if (getDataString(file, &value, 0, "configuration value")) {
char *v = getUtf8FromWchars(value.characters, value.length, NULL);
if (v) {
if (!processConfigurationDirective(keyword.characters, v, conf)) ok = 0;
free(v);
} else {
ok = 0;
}
} else {
conf->info->warning = 1;
}
} else {
conf->info->warning = 1;
}
return ok;
}
static DATA_CONDITION_TESTER(testConfigurationDirectiveSet) {
const ConfigurationFileProcessingData *conf = data;
wchar_t keyword[identifier->length + 1];
wmemcpy(keyword, identifier->characters, identifier->length);
keyword[identifier->length] = 0;
{
const ConfigurationDirective *directive = findConfigurationDirective(keyword, conf);
if (directive) {
if (conf->settings[directive->option]) {
return 1;
}
}
}
return 0;
}
static int
processConfigurationDirectiveTestOperands (DataFile *file, int not, void *data) {
return processConditionOperands(file, testConfigurationDirectiveSet, not, "configuration directive", data);
}
static DATA_OPERANDS_PROCESSOR(processIfSetOperands) {
return processConfigurationDirectiveTestOperands(file,00, data);
}
static DATA_OPERANDS_PROCESSOR(processIfNotSetOperands) {
return processConfigurationDirectiveTestOperands(file, 1, data);
}
static DATA_OPERANDS_PROCESSOR(processConfigurationLine) {
BEGIN_DATA_DIRECTIVE_TABLE
DATA_NESTING_DIRECTIVES,
DATA_VARIABLE_DIRECTIVES,
DATA_CONDITION_DIRECTIVES,
{.name=WS_C("ifset"), .processor=processIfSetOperands, .unconditional=1},
{.name=WS_C("ifnotset"), .processor=processIfNotSetOperands, .unconditional=1},
{.name=NULL, .processor=processConfigurationOperands},
END_DATA_DIRECTIVE_TABLE
return processDirectiveOperand(file, &directives, "configuration file directive", data);
}
static void
freeConfigurationDirectives (ConfigurationFileProcessingData *conf) {
while (conf->directive.count > 0) free(conf->directive.table[--conf->directive.count]);
}
static int
addConfigurationDirectives (ConfigurationFileProcessingData *conf) {
for (unsigned int optionIndex=0; optionIndex<conf->info->options->count; optionIndex+=1) {
const CommandLineOption *option = &conf->info->options->table[optionIndex];
if ((option->flags & OPT_Config) && option->word) {
ConfigurationDirective *directive;
const char *keyword = option->word;
size_t length = countUtf8Characters(keyword);
size_t size = sizeof(*directive) + ((length + 1) * sizeof(wchar_t));
if (!(directive = malloc(size))) {
logMallocError();
freeConfigurationDirectives(conf);
return 0;
}
directive->option = optionIndex;
{
const char *utf8 = keyword;
wchar_t *wc = directive->keyword;
convertUtf8ToWchars(&utf8, &wc, length+1);
}
conf->directive.table[conf->directive.count++] = directive;
}
}
qsort(conf->directive.table, conf->directive.count,
sizeof(*conf->directive.table), sortConfigurationDirectives);
return 1;
}
static void
processConfigurationFile (
OptionProcessingInformation *info,
const char *path,
int optional
) {
if (setBaseDataVariables(NULL)) {
FILE *file = openDataFile(path, "r", optional);
if (file) {
char *settings[info->options->count];
ConfigurationDirective *directives[info->options->count];
ConfigurationFileProcessingData conf = {
.info = info,
.settings = settings,
.directive = {
.table = directives,
.count = 0
}
};
if (addConfigurationDirectives(&conf)) {
int processed;
for (unsigned int index=0; index<info->options->count; index+=1) {
conf.settings[index] = NULL;
}
{
const DataFileParameters dataFileParameters = {
.processOperands = processConfigurationLine,
.data = &conf
};
processed = processDataStream(NULL, file, path, &dataFileParameters);
}
for (unsigned int index=0; index<info->options->count; index+=1) {
char *setting = conf.settings[index];
if (setting) {
ensureSetting(info, &info->options->table[index], setting);
free(setting);
}
}
if (!processed) {
logMessage(LOG_ERR, gettext("file '%s' processing error."), path);
info->warning = 1;
}
freeConfigurationDirectives(&conf);
}
fclose(file);
} else if (!optional || (errno != ENOENT)) {
info->warning = 1;
}
}
}
void
resetOptions (const CommandLineOptions *options) {
for (unsigned int index=0; index<options->count; index+=1) {
const CommandLineOption *option = &options->table[index];
if (option->argument) {
char **string = option->setting.string;
if (string) changeStringSetting(string, NULL);
} else {
int *flag = option->setting.flag;
if (flag) *flag = 0;
}
}
}
static void
exitOptions (void *data) {
const CommandLineOptions *options = data;
resetOptions(options);
}
ProgramExitStatus
processOptions (const CommandLineDescriptor *descriptor, int *argumentCount, char ***argumentVector) {
uint8_t ensuredSettings[descriptor->options->count];
memset(ensuredSettings, 0, sizeof(ensuredSettings));
OptionProcessingInformation info = {
.options = descriptor->options,
.ensuredSettings = ensuredSettings,
.exitImmediately = 0,
.warning = 0,
.syntaxError = 0
};
onProgramExit("options", exitOptions, (void *)descriptor->options);
beginProgram(*argumentCount, *argumentVector);
processCommandLine(&info, argumentCount, argumentVector, &descriptor->usage);
if (descriptor->doBootParameters && *descriptor->doBootParameters) {
processBootParameters(&info, descriptor->applicationName);
}
if (descriptor->doEnvironmentVariables && *descriptor->doEnvironmentVariables) {
processEnvironmentVariables(&info, descriptor->applicationName);
}
processInternalSettings(&info, 0);
{
int configurationFileSpecified = descriptor->configurationFile && *descriptor->configurationFile;
if (configurationFileSpecified) {
processConfigurationFile(&info, *descriptor->configurationFile, !configurationFileSpecified);
}
}
processInternalSettings(&info, 1);
if (info.exitImmediately) return PROG_EXIT_FORCE;
if (info.syntaxError) return PROG_EXIT_SYNTAX;
return PROG_EXIT_SUCCESS;
}
static ProgramExitStatus
processInputStream (
FILE *stream, const char *name,
const InputFilesProcessingParameters *parameters
) {
int ok = 0;
if (parameters->beginStream) {
parameters->beginStream(name, parameters->dataFileParameters.data);
}
if (setBaseDataVariables(NULL)) {
if (processDataStream(NULL, stream, name, &parameters->dataFileParameters)) {
ok = 1;
}
}
if (parameters->endStream) {
parameters->endStream(!ok, parameters->dataFileParameters.data);
}
return ok? PROG_EXIT_SUCCESS: PROG_EXIT_FATAL;
}
static ProgramExitStatus
processStandardInput (const InputFilesProcessingParameters *parameters) {
return processInputStream(stdin, standardInputName, parameters);
}
static ProgramExitStatus
processInputFile (const char *path, const InputFilesProcessingParameters *parameters) {
if (strcmp(path, standardStreamArgument) == 0) {
return processStandardInput(parameters);
}
{
FILE *stream = fopen(path, "r");
if (!stream) {
logMessage(LOG_ERR, "input file open error: %s: %s", path, strerror(errno));
return PROG_EXIT_FATAL;
}
ProgramExitStatus status = processInputStream(stream, path, parameters);
fclose(stream);
return status;
}
}
ProgramExitStatus
processInputFiles (
char **paths, int count,
const InputFilesProcessingParameters *parameters
) {
if (!count) return processStandardInput(parameters);
do {
ProgramExitStatus status = processInputFile(*paths++, parameters);
if (status != PROG_EXIT_SUCCESS) return status;
} while (count -= 1);
return PROG_EXIT_SUCCESS;
}