diff --git a/cinera/cinera.c b/cinera/cinera.c index 008d1d3..9198e91 100644 --- a/cinera/cinera.c +++ b/cinera/cinera.c @@ -23,7 +23,7 @@ typedef struct version CINERA_APP_VERSION = { .Major = 0, .Minor = 7, - .Patch = 16 + .Patch = 17 }; #include // NOTE(matt): varargs @@ -1825,12 +1825,14 @@ char *ErrorDomainStrings[] = { "Config", "Indexing", + "System", }; typedef enum { ED_CONFIG, ED_INDEXING, + ED_SYSTEM, } error_domain; void @@ -2014,6 +2016,20 @@ IndexingErrorCustomSizing(string *Filename, uint64_t LineNumber, int CustomIndex } } +void +SystemError(string *Filename, uint64_t LineNumber, severity Severity, char *Message, string *Received) +{ + ErrorFilenameAndLineNumber(Filename, LineNumber, Severity, ED_SYSTEM); + // TODO(matt): Typeset the Message? + fprintf(stderr, + "%s", Message); + if(Received) + { + PrintStringC(CS_MAGENTA_BOLD, *Received); + } + fprintf(stderr, "\n"); +} + typedef enum { // NOTE(matt): https://tools.ietf.org/html/rfc5424#section-6.2.1 @@ -3877,7 +3893,35 @@ ExpandPath(string Path, string *RelativeToFile) wordexp_t Expansions = {}; char *WorkingPath = MakeString0("l", &Path); - wordexp(WorkingPath, &Expansions, Flags); + int wordexpResult = wordexp(WorkingPath, &Expansions, Flags); + if(wordexpResult) + { + switch(wordexpResult) + { + case WRDE_BADCHAR: + { + SystemError(0, 0, S_ERROR, "wordexp: Illegal occurrence of newline or one of |, &, ;, <, >, (, ), {, } in: ", &Path); + } break; + case WRDE_BADVAL: + { + SystemError(0, 0, S_ERROR, "wordexp: An undefined shell variable was referenced, and the WRDE_UNDEF flag told us to consider this an error, in: ", &Path); + } break; + case WRDE_CMDSUB: + { + SystemError(0, 0, S_ERROR, "wordexp: Command substitution requested, but the WRDE_NOCMD flag told us to consider this an error, in: ", &Path); + } break; + case WRDE_NOSPACE: + { + SystemError(0, 0, S_ERROR, "wordexp: Out of memory", 0); + } break; + + case WRDE_SYNTAX: + { + SystemError(0, 0, S_ERROR, "wordexp: Shell syntax error, such as unbalanced parentheses or unmatched quotes, in: ", &Path); + } break; + + } + } Free(WorkingPath); char *Result = 0; @@ -3903,7 +3947,10 @@ ExpandPath(string Path, string *RelativeToFile) wordfree(&Expansions); - ResolvePath(&Result); + if(Result) + { + ResolvePath(&Result); + } return Result; } @@ -15719,112 +15766,114 @@ main(int ArgC, char **Args) MEM_TEST_MID("main()"); ConfigPath = ExpandPath(Wrap0(ConfigPath), 0); - - PrintVersions(); + if(ConfigPath) + { + PrintVersions(); #if DEBUG_MEM - FILE *MemLog = fopen("/home/matt/cinera_mem", "a+"); - fprintf(MemLog, " Allocated MemoryArena (%d)\n", MemoryArena.Size); - fclose(MemLog); - printf(" Allocated MemoryArena (%d)\n", MemoryArena.Size); + FILE *MemLog = fopen("/home/matt/cinera_mem", "a+"); + fprintf(MemLog, " Allocated MemoryArena (%d)\n", MemoryArena.Size); + fclose(MemLog); + printf(" Allocated MemoryArena (%d)\n", MemoryArena.Size); #endif #if DEBUG - printf("Allocated MemoryArena: %d\n\n", MemoryArena.Size); + printf("Allocated MemoryArena: %d\n\n", MemoryArena.Size); #endif - // NOTE(matt): Tree structure of buffer dependencies - // IncludesPlayer - // Menus - // Player - // ScriptPlayer - // - // IncludesSearch - // Search + // NOTE(matt): Tree structure of buffer dependencies + // IncludesPlayer + // Menus + // Player + // ScriptPlayer + // + // IncludesSearch + // Search - buffers CollationBuffers = {}; - if(ClaimBuffer(&CollationBuffers.IncludesPlayer, BID_COLLATION_BUFFERS_INCLUDES_PLAYER, Kilobytes(2)) == RC_ARENA_FULL) { Exit(); }; - if(ClaimBuffer(&CollationBuffers.Player, BID_COLLATION_BUFFERS_PLAYER, Kilobytes(552)) == RC_ARENA_FULL) { Exit(); }; + buffers CollationBuffers = {}; + if(ClaimBuffer(&CollationBuffers.IncludesPlayer, BID_COLLATION_BUFFERS_INCLUDES_PLAYER, Kilobytes(2)) == RC_ARENA_FULL) { Exit(); }; + if(ClaimBuffer(&CollationBuffers.Player, BID_COLLATION_BUFFERS_PLAYER, Kilobytes(552)) == RC_ARENA_FULL) { Exit(); }; - if(ClaimBuffer(&CollationBuffers.IncludesSearch, BID_COLLATION_BUFFERS_INCLUDES_SEARCH, Kilobytes(2)) == RC_ARENA_FULL) { Exit(); }; - if(ClaimBuffer(&CollationBuffers.SearchEntry, BID_COLLATION_BUFFERS_SEARCH_ENTRY, Kilobytes(32)) == RC_ARENA_FULL) { Exit(); }; + if(ClaimBuffer(&CollationBuffers.IncludesSearch, BID_COLLATION_BUFFERS_INCLUDES_SEARCH, Kilobytes(2)) == RC_ARENA_FULL) { Exit(); }; + if(ClaimBuffer(&CollationBuffers.SearchEntry, BID_COLLATION_BUFFERS_SEARCH_ENTRY, Kilobytes(32)) == RC_ARENA_FULL) { Exit(); }; - CollationBuffers.Search.ID = BID_COLLATION_BUFFERS_SEARCH; // NOTE(matt): Allocated by SearchToBuffer() + CollationBuffers.Search.ID = BID_COLLATION_BUFFERS_SEARCH; // NOTE(matt): Allocated by SearchToBuffer() - memory_book TokensList = InitBook(MBT_TOKENS, 8); - template BespokeTemplate = {}; - neighbourhood Neighbourhood = {}; + memory_book TokensList = InitBook(MBT_TOKENS, 8); + template BespokeTemplate = {}; + neighbourhood Neighbourhood = {}; - InitWatchHandles((IN_CREATE | IN_MOVED_FROM | IN_MOVED_TO | IN_CLOSE_WRITE | IN_DELETE | IN_DELETE_SELF | IN_MOVE_SELF), Kilobytes(4)); + InitWatchHandles((IN_CREATE | IN_MOVED_FROM | IN_MOVED_TO | IN_CLOSE_WRITE | IN_DELETE | IN_DELETE_SELF | IN_MOVE_SELF), Kilobytes(4)); - inotifyInstance = inotify_init1(IN_NONBLOCK); - int inotifyError = errno; + inotifyInstance = inotify_init1(IN_NONBLOCK); + int inotifyError = errno; - string ConfigPathL = Wrap0(ConfigPath); - PushWatchHandle(ConfigPathL, EXT_NULL, WT_CONFIG, 0, 0); + string ConfigPathL = Wrap0(ConfigPath); + PushWatchHandle(ConfigPathL, EXT_NULL, WT_CONFIG, 0, 0); - Config = ParseConfig(ConfigPathL, &TokensList); - if(Config) - { - if(Mode & MODE_EXAMINE) + Config = ParseConfig(ConfigPathL, &TokensList); + if(Config) { - // TODO(matt): Allow optionally passing a .db file as an argument? - ExamineDB(); - _exit(RC_SUCCESS); - } - - if(Mode & MODE_DRYRUN) - { - PrintConfig(Config, FALSE); - } - else - { - /* */ MEM_TEST_MID("main()"); - /* +MEM */ InitAll(&Neighbourhood, &CollationBuffers, &BespokeTemplate); - /* */ MEM_TEST_MID("main()"); - } - } - else if(SeekConfirmation("Print config help?", TRUE)) - { - if(ArgC < 2) - { - PrintHelp(Args[0], ConfigPath); - } - else - { - PrintHelpConfig(); - } - } - - if(inotifyInstance) - { - while(MonitorFilesystem(&Neighbourhood, &CollationBuffers, &BespokeTemplate, ConfigPathL, &TokensList) != RC_ARENA_FULL) - { - // TODO(matt): Refetch the quotes and rebuild player pages if needed - // - // Every sixty mins, redownload the quotes and, I suppose, SyncDBWithInput(). But here we still don't even know - // who the speaker is. To know, we'll probably have to store all quoted speakers in the project's .metadata. Maybe - // postpone this for now, but we will certainly need this to happen - // - // The most ideal solution is possibly that we store quote numbers in the Metadata->Entry, listen for and handle a - // REST PUT request from insobot when a quote changes (unless we're supposed to poll insobot for them?), and rebuild - // the player page(s) accordingly. - // - if(!(Mode & MODE_DRYRUN) && Config && Config->RespectingPrivacy && time(0) - LastPrivacyCheck > Config->PrivacyCheckInterval) + if(Mode & MODE_EXAMINE) { - RecheckPrivacy(&Neighbourhood, &CollationBuffers, &BespokeTemplate); + // TODO(matt): Allow optionally passing a .db file as an argument? + ExamineDB(); + _exit(RC_SUCCESS); } - sleep(GLOBAL_UPDATE_INTERVAL); - } - } - else - { - fprintf(stderr, "Error: Quitting because inotify initialisation failed with message\n" - " %s\n", strerror(inotifyError)); - } - DiscardAllAndFreeConfig(); - RemoveAndFreeWatchHandles(&WatchHandles); - MEM_TEST_END("main()"); + if(Mode & MODE_DRYRUN) + { + PrintConfig(Config, FALSE); + } + else + { + /* */ MEM_TEST_MID("main()"); + /* +MEM */ InitAll(&Neighbourhood, &CollationBuffers, &BespokeTemplate); + /* */ MEM_TEST_MID("main()"); + } + } + else if(SeekConfirmation("Print config help?", TRUE)) + { + if(ArgC < 2) + { + PrintHelp(Args[0], ConfigPath); + } + else + { + PrintHelpConfig(); + } + } + + if(inotifyInstance) + { + while(MonitorFilesystem(&Neighbourhood, &CollationBuffers, &BespokeTemplate, ConfigPathL, &TokensList) != RC_ARENA_FULL) + { + // TODO(matt): Refetch the quotes and rebuild player pages if needed + // + // Every sixty mins, redownload the quotes and, I suppose, SyncDBWithInput(). But here we still don't even know + // who the speaker is. To know, we'll probably have to store all quoted speakers in the project's .metadata. Maybe + // postpone this for now, but we will certainly need this to happen + // + // The most ideal solution is possibly that we store quote numbers in the Metadata->Entry, listen for and handle a + // REST PUT request from insobot when a quote changes (unless we're supposed to poll insobot for them?), and rebuild + // the player page(s) accordingly. + // + if(!(Mode & MODE_DRYRUN) && Config && Config->RespectingPrivacy && time(0) - LastPrivacyCheck > Config->PrivacyCheckInterval) + { + RecheckPrivacy(&Neighbourhood, &CollationBuffers, &BespokeTemplate); + } + sleep(GLOBAL_UPDATE_INTERVAL); + } + } + else + { + fprintf(stderr, "Error: Quitting because inotify initialisation failed with message\n" + " %s\n", strerror(inotifyError)); + } + + DiscardAllAndFreeConfig(); + RemoveAndFreeWatchHandles(&WatchHandles); + MEM_TEST_END("main()"); + } Exit(); } diff --git a/cinera/cinera_config.c b/cinera/cinera_config.c index 3b34127..c22a4d9 100644 --- a/cinera/cinera_config.c +++ b/cinera/cinera_config.c @@ -2280,38 +2280,45 @@ ScopeTokens(scope_tree *Tree, memory_book *TokensList, tokens *T, memory_book *T { string IncluderFilePath = Wrap0(T->File.Path); char *IncludePath = ExpandPath(Pair.Value, &IncluderFilePath); - string IncludePathL = Wrap0(IncludePath); + if(IncludePath) + { + string IncludePathL = Wrap0(IncludePath); - tokens *I = 0; - for(int i = 0; i < TokensList->ItemCount; ++i) - { - tokens *This = GetPlaceInBook(TokensList, i); - if(!StringsDifferLv0(Value.Content, This->File.Path)) + tokens *I = 0; + for(int i = 0; i < TokensList->ItemCount; ++i) { - I = This; - I->CurrentIndex = 0; - I->CurrentLine = 0; + tokens *This = GetPlaceInBook(TokensList, i); + if(!StringsDifferLv0(Value.Content, This->File.Path)) + { + I = This; + I->CurrentIndex = 0; + I->CurrentLine = 0; + } } - } - if(!I) - { - I = Tokenise(TokensList, IncludePathL); - } - if(I) - { - Parent = ScopeTokens(Parent, TokensList, I, TypeSpecs, &Rules); - FreeBook(&Rules.ID); - if(!Parent) + if(!I) { - return 0; + I = Tokenise(TokensList, IncludePathL); } + if(I) + { + Parent = ScopeTokens(Parent, TokensList, I, TypeSpecs, &Rules); + FreeBook(&Rules.ID); + if(!Parent) + { + return 0; + } + } + else + { + token *This = GetPlaceInBook(&T->Token, IncludePathTokenIndex); + ConfigFileIncludeError(&Filepath, This->LineNumber, Wrap0(IncludePath)); + } + Free(IncludePath); } else { - token *This = GetPlaceInBook(&T->Token, IncludePathTokenIndex); - ConfigFileIncludeError(&Filepath, This->LineNumber, Wrap0(IncludePath)); + ConfigError(&IncluderFilePath, Pair.Position.LineNumber, S_WARNING, "Unable to include file: ", &Pair.Value); } - Free(IncludePath); } break; case RC_SCHEME_MIXTURE: {