blob: 7b8f27fd03bcf4d71bf8dc156cc09b61f8da0431 [file] [log] [blame]
/*
* 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 <stdarg.h>
#include <string.h>
#include <errno.h>
#include <sys/stat.h>
#include "log.h"
#include "strfmt.h"
#include "file.h"
#include "queue.h"
#include "datafile.h"
#include "variables.h"
#include "utf8.h"
#include "unicode.h"
#include "brl_dots.h"
struct DataFileStruct {
const char *const name;
const DataFileParameters *const parameters;
DataFile *const includer;
int line;
struct {
dev_t device;
ino_t file;
} identity;
Queue *conditions;
VariableNestingLevel *variables;
const wchar_t *start;
const wchar_t *end;
};
const wchar_t brlDotNumbers[BRL_DOT_COUNT] = {
WC_C('1'), WC_C('2'), WC_C('3'), WC_C('4'),
WC_C('5'), WC_C('6'), WC_C('7'), WC_C('8')
};
const unsigned char brlDotBits[BRL_DOT_COUNT] = {
BRL_DOT_1, BRL_DOT_2, BRL_DOT_3, BRL_DOT_4,
BRL_DOT_5, BRL_DOT_6, BRL_DOT_7, BRL_DOT_8
};
int
brlDotNumberToIndex (wchar_t number, int *index) {
const wchar_t *character = wmemchr(brlDotNumbers, number, ARRAY_COUNT(brlDotNumbers));
if (!character) return 0;
*index = character - brlDotNumbers;
return 1;
}
int
brlDotBitToIndex (unsigned char bit, int *index) {
const unsigned char *cell = memchr(brlDotBits, bit, ARRAY_COUNT(brlDotBits));
if (!cell) return 0;
*index = cell - brlDotBits;
return 1;
}
void
reportDataError (DataFile *file, const char *format, ...) {
char message[0X200];
{
const char *name = NULL;
const int *line = NULL;
va_list args;
if (file) {
name = file->name;
if (file->line) line = &file->line;
}
va_start(args, format);
formatInputError(message, sizeof(message), name, line, format, args);
va_end(args);
}
logMessage(LOG_WARNING, "%s", message);
}
int
compareKeyword (const wchar_t *keyword, const wchar_t *characters, size_t count) {
while (count > 0) {
wchar_t character1;
wchar_t character2;
if (!(character1 = *keyword)) return -1;
keyword += 1;
character1 = towlower(character1);
character2 = towlower(*characters++);
if (character1 < character2) return -1;
if (character1 > character2) return 1;
count -= 1;
}
return *keyword? 1: 0;
}
int
compareKeywords (const wchar_t *keyword1, const wchar_t *keyword2) {
return compareKeyword(keyword1, keyword2, wcslen(keyword2));
}
int
isKeyword (const wchar_t *keyword, const wchar_t *characters, size_t count) {
return compareKeyword(keyword, characters, count) == 0;
}
int
isHexadecimalDigit (wchar_t character, int *value, int *shift) {
if ((character >= WC_C('0')) && (character <= WC_C('9'))) {
*value = character - WC_C('0');
} else if ((character >= WC_C('a')) && (character <= WC_C('f'))) {
*value = character - WC_C('a') + 10;
} else if ((character >= WC_C('A')) && (character <= WC_C('F'))) {
*value = character - WC_C('A') + 10;
} else {
return 0;
}
*shift = 4;
return 1;
}
int
isOctalDigit (wchar_t character, int *value, int *shift) {
if ((character < WC_C('0')) || (character > WC_C('7'))) return 0;
*value = character - WC_C('0');
*shift = 3;
return 1;
}
int
isNumber (int *number, const wchar_t *characters, int length) {
if (length > 0) {
char string[length + 1];
string[length] = 0;
{
int index;
for (index=0; index<length; index+=1) {
wchar_t wc = characters[index];
if (!iswLatin1(wc)) return 0;
string[index] = wc;
}
}
{
char *end;
long value = strtol(string, &end, 0);
if (!*end) {
*number = value;
return 1;
}
}
}
return 0;
}
static VariableNestingLevel *baseDataVariables = NULL;
static VariableNestingLevel *currentDataVariables = NULL;
static VariableNestingLevel *
getBaseDataVariables (void) {
if (baseDataVariables) {
releaseVariableNestingLevel(currentDataVariables);
deleteVariables(baseDataVariables);
} else {
VariableNestingLevel *globalVariables = getGlobalVariables(1);
if (!globalVariables) return NULL;
VariableNestingLevel *baseVariables = newVariableNestingLevel(globalVariables, "base");
if (!baseVariables) return NULL;
baseDataVariables = claimVariableNestingLevel(baseVariables);
}
currentDataVariables = claimVariableNestingLevel(baseDataVariables);
return baseDataVariables;
}
int
setBaseDataVariables (const VariableInitializer *initializers) {
VariableNestingLevel *variables = getBaseDataVariables();
if (!variables) return 0;
return setStringVariables(variables, initializers);
}
int
setTableDataVariables (const char *tableExtension, const char *subtableExtension) {
const VariableInitializer initializers[] = {
{ .name = "tableExtension", .value = tableExtension },
{ .name = "subtableExtension", .value = subtableExtension },
{ .name = NULL }
};
return setBaseDataVariables(initializers);
}
static int
pushDataVariableNestingLevel (void) {
VariableNestingLevel *variables = newVariableNestingLevel(currentDataVariables, NULL);
if (!variables) return 0;
releaseVariableNestingLevel(currentDataVariables);
currentDataVariables = claimVariableNestingLevel(variables);
return 1;
}
int
findDataOperand (DataFile *file, const char *description) {
file->start = file->end;
while (iswspace(file->start[0])) file->start += 1;
if (*(file->end = file->start)) return 1;
if (description) reportDataError(file, "%s not specified", description);
return 0;
}
int
getDataCharacter (DataFile *file, wchar_t *character) {
if (!*file->end) return 0;
*character = *file->end++;
return 1;
}
int
ungetDataCharacters (DataFile *file, unsigned int count) {
unsigned int maximum = file->end - file->start;
if (count > maximum) {
reportDataError(file, "unget character count out of range: %u > %u",
count, maximum);
return 0;
}
file->end -= count;
return 1;
}
void
getTextRemaining (DataFile *file, DataOperand *text) {
file->end = file->start + wcslen(file->start);
text->characters = file->start;
text->length = file->end - file->start;
}
int
getTextOperand (DataFile *file, DataOperand *text, const char *description) {
if (!findDataOperand(file, description)) return 0;
getTextRemaining(file, text);
while (text->length) {
unsigned int newLength = text->length - 1;
if (!iswspace(text->characters[newLength])) break;
text->length = newLength;
}
return 1;
}
int
getDataOperand (DataFile *file, DataOperand *operand, const char *description) {
if (!findDataOperand(file, description)) return 0;
do {
file->end += 1;
} while (file->end[0] && !iswspace(file->end[0]));
operand->characters = file->start;
operand->length = file->end - file->start;
return 1;
}
int
parseDataString (DataFile *file, DataString *string, const wchar_t *characters, int length, int noUnicode) {
int index = 0;
string->length = 0;
while (index < length) {
wchar_t character = characters[index];
DataOperand substitution = {
.characters = &character,
.length = 1
};
if (character == WC_C('\\')) {
int start = index;
const char *problem = strtext("invalid escape sequence");
int ok = 0;
if (++index < length) {
switch ((character = characters[index])) {
case WC_C('#'):
case WC_C('\\'):
ok = 1;
break;
case WC_C('b'):
character = WC_C('\b');
ok = 1;
break;
case WC_C('f'):
character = WC_C('\f');
ok = 1;
break;
case WC_C('n'):
character = WC_C('\n');
ok = 1;
break;
case WC_C('r'):
character = WC_C('\r');
ok = 1;
break;
case WC_C('R'):
character = UNICODE_REPLACEMENT_CHARACTER;
ok = 1;
break;
case WC_C('s'):
character = WC_C(' ');
ok = 1;
break;
case WC_C('t'):
character = WC_C('\t');
ok = 1;
break;
case WC_C('v'):
character = WC_C('\v');
ok = 1;
break;
{
uint32_t result;
int count;
int (*isDigit) (wchar_t character, int *value, int *shift);
case WC_C('o'):
count = 3;
isDigit = isOctalDigit;
goto doNumber;
case WC_C('U'):
if (noUnicode) break;
count = 8;
goto doHexadecimal;
case WC_C('u'):
if (noUnicode) break;
count = 4;
goto doHexadecimal;
case WC_C('X'):
case WC_C('x'):
count = 2;
goto doHexadecimal;
doHexadecimal:
isDigit = isHexadecimalDigit;
goto doNumber;
doNumber:
result = 0;
while (++index < length) {
{
int value;
int shift;
if (!isDigit(characters[index], &value, &shift)) break;
result = (result << shift) | value;
}
if (!--count) {
if (result > WCHAR_MAX) {
problem = NULL;
} else {
character = result;
ok = 1;
}
break;
}
}
break;
}
case WC_C('{'): {
const wchar_t *first = &characters[++index];
const wchar_t *end = wmemchr(first, WC_C('}'), length-index);
if (end) {
int count = end - first;
index += count;
const Variable *variable = findReadableVariable(currentDataVariables, first, count);
if (variable) {
getVariableValue(variable, &substitution.characters, &substitution.length);
ok = 1;
}
} else {
index = length - 1;
}
break;
}
case WC_C('<'): {
const wchar_t *first = &characters[++index];
const wchar_t *end = wmemchr(first, WC_C('>'), length-index);
if (noUnicode) break;
if (end) {
int count = end - first;
index += count;
{
char name[count+1];
{
unsigned int i;
for (i=0; i<count; i+=1) {
wchar_t wc = first[i];
if (wc == WC_C('_')) wc = WC_C(' ');
if (!iswLatin1(wc)) break;
name[i] = wc;
}
if (i < count) break;
name[i] = 0;
}
if (getCharacterByName(&character, name)) ok = 1;
}
} else {
index = length - 1;
}
break;
}
}
}
if (!ok) {
if (index < length) index += 1;
if (problem) {
reportDataError(file, "%s: %.*" PRIws,
gettext(problem),
index-start, &characters[start]);
}
return 0;
}
}
index += 1;
{
unsigned int newLength = string->length + substitution.length;
/* there needs to be room for a trailing NUL */
if (newLength >= ARRAY_COUNT(string->characters)) {
reportDataError(file, "string operand too long");
return 0;
}
wmemcpy(&string->characters[string->length], substitution.characters, substitution.length);
string->length = newLength;
}
}
string->characters[string->length] = 0;
return 1;
}
int
getDataString (DataFile *file, DataString *string, int noUnicode, const char *description) {
DataOperand operand;
if (getDataOperand(file, &operand, description))
if (parseDataString(file, string, operand.characters, operand.length, noUnicode))
return 1;
return 0;
}
int
writeHexadecimalCharacter (FILE *stream, wchar_t character) {
uint32_t value = character;
if (value < 0X100) {
return fprintf(stream, "\\x%02" PRIX32, value) != EOF;
} else if (value < 0X10000) {
return fprintf(stream, "\\u%04" PRIX32, value) != EOF;
} else {
return fprintf(stream, "\\U%08" PRIX32, value) != EOF;
}
}
int
writeEscapedCharacter (FILE *stream, wchar_t character) {
{
static const char escapes[] = {
[' '] = 's',
['\\'] = '\\'
};
if (character < ARRAY_COUNT(escapes)) {
char escape = escapes[character];
if (escape) {
return fprintf(stream, "\\%c", escape) != EOF;
}
}
}
if (iswspace(character) || iswcntrl(character)) return writeHexadecimalCharacter(stream, character);
return writeUtf8Character(stream, character);
}
int
writeEscapedCharacters (FILE *stream, const wchar_t *characters, size_t count) {
const wchar_t *end = characters + count;
while (characters < end)
if (!writeEscapedCharacter(stream, *characters++))
return 0;
return 1;
}
static int
parseDotOperand (DataFile *file, int *index, const wchar_t *characters, int length) {
if (length == 1)
if (brlDotNumberToIndex(characters[0], index))
return 1;
reportDataError(file, "invalid braille dot number: %.*" PRIws, length, characters);
return 0;
}
int
getDotOperand (DataFile *file, int *index) {
DataOperand number;
if (getDataOperand(file, &number, "dot number"))
if (parseDotOperand(file, index, number.characters, number.length))
return 1;
return 0;
}
int
parseCellsOperand (DataFile *file, ByteOperand *cells, const wchar_t *characters, int length) {
unsigned char cell = 0;
int start = 0;
int index;
cells->length = 0;
for (index=0; index<length; index+=1) {
int started = index != start;
wchar_t character = characters[index];
switch (character) {
{
int dot;
case WC_C('1'):
dot = BRL_DOT_1;
goto doDot;
case WC_C('2'):
dot = BRL_DOT_2;
goto doDot;
case WC_C('3'):
dot = BRL_DOT_3;
goto doDot;
case WC_C('4'):
dot = BRL_DOT_4;
goto doDot;
case WC_C('5'):
dot = BRL_DOT_5;
goto doDot;
case WC_C('6'):
dot = BRL_DOT_6;
goto doDot;
case WC_C('7'):
dot = BRL_DOT_7;
goto doDot;
case WC_C('8'):
dot = BRL_DOT_8;
goto doDot;
doDot:
if (started && !cell) goto invalid;
if (cell & dot) {
reportDataError(file, "dot specified more than once: %.1" PRIws, &character);
return 0;
}
cell |= dot;
break;
}
case CELLS_OPERAND_SPACE:
if (started) goto invalid;
break;
case CELLS_OPERAND_DELIMITER:
if (!started) {
reportDataError(file, "missing cell specification: %.*" PRIws,
length-index, &characters[index]);
return 0;
}
cells->bytes[cells->length++] = cell;
if (cells->length == ARRAY_COUNT(cells->bytes)) {
reportDataError(file, "cells operand too long");
return 0;
}
cell = 0;
start = index + 1;
break;
default:
invalid:
reportDataError(file, "invalid dot number: %.1" PRIws, &character);
return 0;
}
}
if (index == start) {
reportDataError(file, "missing cell specification");
return 0;
}
cells->bytes[cells->length++] = cell; /*last cell */
return 1;
}
int
getCellsOperand (DataFile *file, ByteOperand *cells, const char *description) {
DataOperand operand;
if (getDataOperand(file, &operand, description))
if (parseCellsOperand(file, cells, operand.characters, operand.length))
return 1;
return 0;
}
int
writeDots (FILE *stream, unsigned char cell) {
unsigned int dot;
for (dot=1; dot<=BRL_DOT_COUNT; dot+=1) {
if (cell & BRL_DOT(dot)) {
if (fprintf(stream, "%u", dot) == EOF) return 0;
}
}
return 1;
}
int
writeDotsCell (FILE *stream, unsigned char cell) {
if (!cell) return fputc('0', stream) != EOF;
return writeDots(stream, cell);
}
int
writeDotsCells (FILE *stream, const unsigned char *cells, size_t count) {
const unsigned char *cell = cells;
const unsigned char *end = cells + count;
while (cell < end) {
if (cell != cells)
if (fputc('-', stream) == EOF)
return 0;
if (!writeDotsCell(stream, *cell++)) return 0;
}
return 1;
}
int
writeUtf8Cell (FILE *stream, unsigned char cell) {
return writeUtf8Character(stream, (UNICODE_BRAILLE_ROW | cell));
}
int
writeUtf8Cells (FILE *stream, const unsigned char *cells, size_t count) {
const unsigned char *end = cells + count;
while (cells < end)
if (!writeUtf8Cell(stream, *cells++))
return 0;
return 1;
}
typedef struct {
unsigned canInclude:1;
unsigned isIncluding:1;
unsigned inElse:1;
} DataCondition;
static inline int
shallInclude (const DataCondition *condition) {
return condition->canInclude && condition->isIncluding;
}
static void
deallocateDataCondition (void *item, void *data UNUSED) {
DataCondition *condition = item;
free(condition);
}
static Element *
getInnermostDataCondition (DataFile *file) {
return getStackHead(file->conditions);
}
static Element *
getCurrentDataCondition (DataFile *file) {
{
Element *element = getInnermostDataCondition(file);
if (element) return element;
}
reportDataError(file, "no outstanding condition");
return NULL;
}
static int
removeDataCondition (DataFile *file, Element *element, int identifier) {
if (!(element && (identifier == getElementIdentifier(element)))) return 0;
deleteElement(element);
return 1;
}
static Element *
pushDataCondition (
DataFile *file, const DataString *name,
DataConditionTester *testCondition, int negateCondition
) {
DataCondition *condition;
if ((condition = malloc(sizeof(*condition)))) {
memset(condition, 0, sizeof(*condition));
condition->inElse = 0;
{
const DataOperand identifier = {
.characters = name->characters,
.length = name->length
};
condition->isIncluding = testCondition(file, &identifier, file->parameters->data);
if (negateCondition) condition->isIncluding = !condition->isIncluding;
}
{
Element *element = getInnermostDataCondition(file);
if (element) {
const DataCondition *parent = getElementItem(element);
condition->canInclude = shallInclude(parent);
} else {
condition->canInclude = 1;
}
}
{
Element *element = enqueueItem(file->conditions, condition);
if (element) return element;
}
free(condition);
} else {
logMallocError();
}
return NULL;
}
static int
testDataCondition (DataFile *file) {
Element *element = getInnermostDataCondition(file);
if (element) {
const DataCondition *condition = getElementItem(element);
if (!shallInclude(condition)) return 0;
}
return 1;
}
static int
compareDataDirectiveNames (const wchar_t *name1, const wchar_t *name2) {
return compareKeywords(name1, name2);
}
static int
sortDataDirectivesByName (const void *element1, const void *element2) {
const DataDirective *const *directive1 = element1;
const DataDirective *const *directive2 = element2;
return compareDataDirectiveNames((*directive1)->name, (*directive2)->name);
}
static int
searchDataDirectiveByName (const void *target, const void *element) {
const wchar_t *name = target;
const DataDirective *const *directive = element;
return compareDataDirectiveNames(name, (*directive)->name);
}
static const DataDirective *
findDataDirectiveByName (const DataDirectives *directives, const wchar_t *name) {
DataDirective **directive = bsearch(
name, directives->sorted.table, directives->sorted.count,
sizeof(*directives->sorted.table), searchDataDirectiveByName
);
return directive? *directive: NULL;
}
static int
prepareDataDirectives (DataDirectives *directives) {
if (!directives->sorted.table) {
static const DataDirective unnamed = {
.name = NULL,
.processor = NULL
};
directives->unnamed = &unnamed;
directives->sorted.count = 0;
if (!(directives->sorted.table = malloc(ARRAY_SIZE(directives->sorted.table, directives->unsorted.count)))) {
logMallocError();
return 0;
}
{
const DataDirective *directive = directives->unsorted.table;
const DataDirective *end = directive + directives->unsorted.count;
while (directive < end) {
if (directive->name) {
directives->sorted.table[directives->sorted.count++] = directive;
} else {
directives->unnamed = directive;
}
directive += 1;
}
}
qsort(directives->sorted.table, directives->sorted.count,
sizeof(*directives->sorted.table), sortDataDirectivesByName);
}
return 1;
}
int
processDirectiveOperand (DataFile *file, DataDirectives *directives, const char *description, void *data) {
DataOperand name;
if (getDataOperand(file, &name, description)) {
const DataDirective *directive;
if (!prepareDataDirectives(directives)) return 0;
{
wchar_t string[name.length + 1];
wmemcpy(string, name.characters, name.length);
string[name.length] = 0;
if (!(directive = findDataDirectiveByName(directives, string))) {
directive = directives->unnamed;
ungetDataCharacters(file, name.length);
}
}
if (!(directive->unconditional || testDataCondition(file))) return 1;
if (directive->processor) return directive->processor(file, data);
reportDataError(file, "unknown %s: %.*" PRIws,
description, name.length, name.characters);
}
return 1;
}
static int
processDataOperands (DataFile *file) {
return file->parameters->processOperands(file, file->parameters->data);
}
static int
processDataCharacters (DataFile *file, const wchar_t *line) {
file->end = file->start = line;
if (!(file->parameters->options & DFO_NO_COMMENTS)) {
if (!findDataOperand(file, NULL)) return 1;
if (file->start[0] == WC_C('#')) return 1;
}
return processDataOperands(file);
}
static int
processConditionSubdirective (DataFile *file, Element *element) {
int identifier = getElementIdentifier(element);
if (findDataOperand(file, NULL)) {
int result = processDataOperands(file);
removeDataCondition(file, element, identifier);
return result;
}
return 1;
}
int
processConditionOperands (
DataFile *file,
DataConditionTester *testCondition, int negateCondition,
const char *description, void *data
) {
DataString name;
if (getDataString(file, &name, 1, description)) {
Element *element = pushDataCondition(file, &name, testCondition, negateCondition);
if (!element) return 0;
if (!processConditionSubdirective(file, element)) return 0;
}
return 1;
}
static DATA_CONDITION_TESTER(testVariableDefined) {
return !!findReadableVariable(currentDataVariables, identifier->characters, identifier->length);
}
static int
processVariableTestOperands (DataFile *file, int not, void *data) {
return processConditionOperands(file, testVariableDefined, not, "variable name", data);
}
DATA_OPERANDS_PROCESSOR(processIfVarOperands) {
return processVariableTestOperands(file, 0, data);
}
DATA_OPERANDS_PROCESSOR(processIfNotVarOperands) {
return processVariableTestOperands(file, 1, data);
}
DATA_OPERANDS_PROCESSOR(processBeginVariablesOperands) {
return pushDataVariableNestingLevel();
}
DATA_OPERANDS_PROCESSOR(processEndVariablesOperands) {
if (currentDataVariables == file->variables) {
reportDataError(file, "no nested variables");
} else {
currentDataVariables = removeVariableNestingLevel(currentDataVariables);
}
return 1;
}
DATA_OPERANDS_PROCESSOR(processListVariablesOperands) {
listVariables(currentDataVariables);
return 1;
}
static int
processVariableAssignmentOperands (DataFile *file, int ifNotSet, void *data) {
DataOperand name;
if (getDataOperand(file, &name, "variable name")) {
DataString value;
if (!getDataString(file, &value, 0, NULL)) {
value.length = 0;
}
if (ifNotSet) {
const Variable *variable = findReadableVariable(currentDataVariables, name.characters, name.length);
if (variable) return 1;
}
{
Variable *variable = findWritableVariable(currentDataVariables, name.characters, name.length);
if (variable) {
if (setVariable(variable, value.characters, value.length)) return 1;
}
}
}
return 1;
}
DATA_OPERANDS_PROCESSOR(processAssignDefaultOperands) {
return processVariableAssignmentOperands(file, 1, data);
}
DATA_OPERANDS_PROCESSOR(processAssignOperands) {
return processVariableAssignmentOperands(file, 0, data);
}
DATA_OPERANDS_PROCESSOR(processElseOperands) {
Element *element = getCurrentDataCondition(file);
if (element) {
DataCondition *condition = getElementItem(element);
if (condition->inElse) {
reportDataError(file, "already in else");
} else {
condition->inElse = 1;
condition->isIncluding = !condition->isIncluding;
if (!processConditionSubdirective(file, element)) return 0;
}
}
return 1;
}
DATA_OPERANDS_PROCESSOR(processEndIfOperands) {
Element *element = getCurrentDataCondition(file);
if (element) {
removeDataCondition(file, element, getElementIdentifier(element));
}
return 1;
}
static int
isDataFileIncluded (DataFile *file, const char *path) {
struct stat info;
if (stat(path, &info) != -1) {
while (file) {
if ((file->identity.device == info.st_dev) && (file->identity.file == info.st_ino)) return 1;
file = file->includer;
}
}
return 0;
}
static FILE *
openIncludedDataFile (DataFile *includer, const char *path, const char *mode, int optional) {
const char *const *overrideDirectories = getAllOverrideDirectories();
const char *overrideDirectory = NULL;
char *overridePath = NULL;
int writable = (*mode == 'w') || (*mode == 'a');
const char *name = locatePathName(path);
FILE *file;
if (overrideDirectories) {
const char *const *directory = overrideDirectories;
while (*directory) {
if (**directory) {
char *path = makePath(*directory, name);
if (path) {
if (!isDataFileIncluded(includer, path)) {
if (testFilePath(path)) {
file = openFile(path, mode, optional);
overrideDirectory = *directory;
overridePath = path;
goto done;
}
}
free(path);
}
}
directory += 1;
}
}
if (isDataFileIncluded(includer, path)) {
logMessage(LOG_WARNING, "data file include loop: %s", path);
file = NULL;
errno = ENOENT;
} else if (!(file = openFile(path, mode, optional))) {
if (writable) {
if (errno == ENOENT) {
char *directory = getPathDirectory(path);
if (directory) {
int exists = ensureDirectory(directory, 0);
free(directory);
if (exists) {
file = openFile(path, mode, optional);
goto done;
}
}
}
if ((errno == EACCES) || (errno == EROFS)) {
if ((overrideDirectory = getPrimaryOverrideDirectory())) {
if ((overridePath = makePath(overrideDirectory, name))) {
if (ensureDirectory(overrideDirectory, 0)) {
file = openFile(overridePath, mode, optional);
goto done;
}
}
}
}
}
}
done:
if (overridePath) free(overridePath);
return file;
}
FILE *
openDataFile (const char *path, const char *mode, int optional) {
return openIncludedDataFile(NULL, path, mode, optional);
}
int
includeDataFile (DataFile *file, const wchar_t *name, int length) {
int ok = 0;
const char *prefixAddress = file->name;
size_t prefixLength = 0;
size_t suffixLength;
char *suffixAddress = getUtf8FromWchars(name, length, &suffixLength);
if (suffixAddress) {
if (!isAbsolutePath(suffixAddress)) {
const char *prefixEnd = strrchr(prefixAddress, '/');
if (prefixEnd) prefixLength = prefixEnd - prefixAddress + 1;
}
{
char path[prefixLength + suffixLength + 1];
FILE *stream;
snprintf(path, sizeof(path), "%.*s%.*s",
(int)prefixLength, prefixAddress,
(int)suffixLength, suffixAddress);
if ((stream = openIncludedDataFile(file, path, "r", 0))) {
if (processDataStream(file, stream, path, file->parameters)) ok = 1;
fclose(stream);
}
}
free(suffixAddress);
} else {
logMallocError();
}
return ok;
}
DATA_OPERANDS_PROCESSOR(processIncludeOperands) {
DataString path;
if (getDataString(file, &path, 0, "include file path"))
if (!includeDataFile(file, path.characters, path.length))
return 0;
return 1;
}
static int
processDataLine (const LineHandlerParameters *parameters) {
DataFile *file = parameters->data;
file->line += 1;
const char *byte = parameters->line.text;
size_t size = parameters->line.length + 1;
wchar_t characters[size];
wchar_t *character = characters;
convertUtf8ToWchars(&byte, &character, size);
character = characters;
if (*byte) {
unsigned int offset = byte - parameters->line.text;
reportDataError(file, "illegal UTF-8 character at offset %u", offset);
return 1;
}
if (file->line == 1) {
if (*character == UNICODE_BYTE_ORDER_MARK) {
character += 1;
}
}
return processDataCharacters(file, character);
}
int
processDataStream (
DataFile *includer,
FILE *stream, const char *name,
const DataFileParameters *parameters
) {
int ok = 0;
if (parameters->logFileName) {
parameters->logFileName(name, parameters->data);
} else {
logMessage(LOG_DEBUG, "including data file: %s", name);
}
DataFile file = {
.name = name,
.parameters = parameters,
.includer = includer,
.line = 0,
};
{
struct stat info;
if (fstat(fileno(stream), &info) != -1) {
file.identity.device = info.st_dev;
file.identity.file = info.st_ino;
}
}
VariableNestingLevel *oldVariables = currentDataVariables;
if ((file.variables = newVariableNestingLevel(oldVariables, name))) {
currentDataVariables = claimVariableNestingLevel(file.variables);
if ((file.conditions = newQueue(deallocateDataCondition, NULL))) {
if (processLines(stream, processDataLine, &file)) ok = 1;
if (getInnermostDataCondition(&file)) {
reportDataError(&file, "outstanding condition at end of file");
}
deallocateQueue(file.conditions);
}
releaseVariableNestingLevel(currentDataVariables);
currentDataVariables = oldVariables;
}
return ok;
}
int
processDataFile (const char *name, const DataFileParameters *parameters) {
int ok = 0;
FILE *stream;
if ((stream = openDataFile(name, "r", 0))) {
if (processDataStream(NULL, stream, name, parameters)) ok = 1;
fclose(stream);
}
return ok;
}