From 0959fa2774b8cf9e1a428996c824118475ebe050 Mon Sep 17 00:00:00 2001 From: Matt Mascarenhas Date: Sat, 30 May 2020 18:12:54 +0100 Subject: [PATCH] cinera.c: Fix segfault and event handling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit • Segfault was due to a read access violation on an unset entry pointer, which in turn was due to stale neighbourhood data. To fix it we simply reset the neighbourhood when starting to delete an entry. Additionally we now check that those entry pointers are set before accessing them. • Event handling of the trio of events triggered when vim saves a file. We now read in a second set of events while processing the first if we were on the verge of processing a deletion. If we get any more events, we continue to squash those ones if possible, to always end up having seen the entire trio of events associated with a file save, and then process it as an insertion / reinsertion, not a deletion. • Sort the asset landmarks by their offset. • Change GenerateTopicColours() to initially open cinera_topics.css as "r" to search it for the incoming topic, and only if that topic is absent reopen it as "a+", thus triggering an IN_CLOSE_WRITE event. --- cinera/cinera.c | 540 +++++++++++++++++++++++++++-------------- cinera/cinera_config.c | 14 +- 2 files changed, 367 insertions(+), 187 deletions(-) diff --git a/cinera/cinera.c b/cinera/cinera.c index a40d9d0..f86587e 100644 --- a/cinera/cinera.c +++ b/cinera/cinera.c @@ -23,7 +23,7 @@ typedef struct version CINERA_APP_VERSION = { .Major = 0, .Minor = 7, - .Patch = 13 + .Patch = 14 }; #include // NOTE(matt): varargs @@ -4238,14 +4238,20 @@ BinarySearchForMetadataLandmark(db_asset *Asset, landmark_range ProjectRange, in void PrintEntryIndex(db_project_index Project, int64_t EntryIndex) { - fprintf(stderr, "%s%i:%i%s %s%3li%s", + fprintf(stderr, "%s%2i:%2i%s %s%3li%s", ColourStrings[CS_MAGENTA], Project.Generation, Project.Index, ColourStrings[CS_END], ColourStrings[CS_BLUE_BOLD], EntryIndex, ColourStrings[CS_END]); } void -PrintLandmark(db_landmark *L) +PrintLandmark(db_landmark *L, uint16_t *Index) { + if(Index) + { + Colourise(CS_BLACK_BOLD); + fprintf(stderr, "[%4u] ", *Index); + Colourise(CS_END); + } PrintEntryIndex(L->Project, L->EntryIndex); fprintf(stderr, " %6u", L->Position); } @@ -4256,7 +4262,7 @@ PrintAsset(db_asset *A, uint16_t *Index) if(Index) { Colourise(CS_BLACK_BOLD); - fprintf(stderr, "[%i]", *Index); + fprintf(stderr, "[%4u]", *Index); Colourise(CS_END); } string FilenameL = Wrap0i(A->Filename, sizeof(A->Filename)); @@ -4301,7 +4307,7 @@ PrintAssetAndLandmarks(db_asset *A, uint16_t *Index) db_landmark *FirstLandmark = LocateFirstLandmark(A); //Colourise(CS_BLACK_BOLD); fprintf(stderr, "%4u ", 0); Colourise(CS_END); //PrintLandmark(FirstLandmark); - for(int i = 0; i < A->LandmarkCount; ++i) + for(uint16_t i = 0; i < A->LandmarkCount; ++i) { db_landmark *This = FirstLandmark + i; if((i % 8) == 0) @@ -4312,8 +4318,8 @@ PrintAssetAndLandmarks(db_asset *A, uint16_t *Index) { PrintC(CS_BLACK_BOLD, " │ "); } - Colourise(CS_BLACK_BOLD); fprintf(stderr, "%4u ", i); Colourise(CS_END); - PrintLandmark(This); + //Colourise(CS_BLACK_BOLD); fprintf(stderr, "%4u ", i); Colourise(CS_END); + PrintLandmark(This, &i); } fprintf(stderr, "\n"); } @@ -4789,7 +4795,7 @@ SnipeChecksumIntoHTML(db_asset *Asset, buffer *Checksum) { PrintC(CS_ERROR, "\nInvalid landmark (aborting update): "); PrintAsset(Asset, 0); - PrintLandmark(Landmark); + PrintLandmark(Landmark, 0); Result = RC_FAILURE; break; } @@ -6904,9 +6910,10 @@ BuildQuote(quote_info *Info, string Speaker, int ID, bool ShouldFetchQuotes) return Result; } -int +rc GenerateTopicColours(neighbourhood *N, string Topic) { + // TODO(matt): Maybe straighten out the return code situation? // NOTE(matt): Stack-string char SanitisedTopic[Topic.Length + 1]; CopyString(SanitisedTopic, sizeof(SanitisedTopic), "%.*s", (int)Topic.Length, Topic.Base); @@ -6949,43 +6956,27 @@ GenerateTopicColours(neighbourhood *N, string Topic) closedir(CSSDirHandle); *Ptr = '/'; - if((Topics.Handle = fopen(Topics.Path, "a+"))) + ReadFileIntoBuffer(&Topics); + + bool Exists = FALSE; + while(Topics.Buffer.Ptr - Topics.Buffer.Location < Topics.Buffer.Size) { - fseek(Topics.Handle, 0, SEEK_END); - Topics.Buffer.Size = ftell(Topics.Handle); - fseek(Topics.Handle, 0, SEEK_SET); - - if(!(Topics.Buffer.Location = malloc(Topics.Buffer.Size))) + Topics.Buffer.Ptr += StringLength(".category."); + if(!StringsDifferT(SanitisedTopic, Topics.Buffer.Ptr, ' ')) { - return RC_ERROR_MEMORY; + Exists = TRUE; + break; } - -#if DEBUG_MEM - FILE *MemLog = fopen("/home/matt/cinera_mem", "a+"); - fprintf(MemLog, " Allocated Topics (%ld)\n", Topics.Buffer.Size); - fclose(MemLog); - printf(" Allocated Topics (%ld)\n", Topics.Buffer.Size); -#endif - - Topics.Buffer.Ptr = Topics.Buffer.Location; - fread(Topics.Buffer.Location, Topics.Buffer.Size, 1, Topics.Handle); - - while(Topics.Buffer.Ptr - Topics.Buffer.Location < Topics.Buffer.Size) + while(Topics.Buffer.Ptr - Topics.Buffer.Location < Topics.Buffer.Size && *Topics.Buffer.Ptr != '\n') { - Topics.Buffer.Ptr += StringLength(".category."); - if(!StringsDifferT(SanitisedTopic, Topics.Buffer.Ptr, ' ')) - { - FreeBuffer(&Topics.Buffer); - fclose(Topics.Handle); - return RC_NOOP; - } - while(Topics.Buffer.Ptr - Topics.Buffer.Location < Topics.Buffer.Size && *Topics.Buffer.Ptr != '\n') - { - ++Topics.Buffer.Ptr; - } ++Topics.Buffer.Ptr; } + ++Topics.Buffer.Ptr; + } + if(!Exists) + { + Topics.Handle = fopen(Topics.Path, "a+"); if(!StringsDifferLv0(Topic, "nullTopic")) { fprintf(Topics.Handle, ".category.%s { border: 1px solid transparent; background: transparent; }\n", @@ -6999,14 +6990,15 @@ GenerateTopicColours(neighbourhood *N, string Topic) SanitisedTopic, Colour.Hue, Colour.Saturation, Colour.Lightness, Colour.Hue, Colour.Saturation, Colour.Lightness); } - fclose(Topics.Handle); #if DEBUG_MEM MemLog = fopen("/home/matt/cinera_mem", "a+"); fprintf(MemLog, " Freed Topics (%ld)\n", Topics.Buffer.Size); fclose(MemLog); printf(" Freed Topics (%ld)\n", Topics.Buffer.Size); #endif - FreeBuffer(&Topics.Buffer); + + fclose(Topics.Handle); + FreeFile(&Topics); asset *Asset = GetPlaceInBook(&Assets, ASSET_CSS_TOPICS); if(Asset->Known) @@ -7020,14 +7012,8 @@ GenerateTopicColours(neighbourhood *N, string Topic) asset *CSSTopics = BuiltinAssets + ASSET_CSS_TOPICS; PlaceAsset(Wrap0(CSSTopics->Filename), CSSTopics->Type, CSSTopics->Variants, CSSTopics->Associated, ASSET_CSS_TOPICS); } - return RC_SUCCESS; - } - else - { - // NOTE(matt): Maybe it shouldn't be possible to hit this case now that we MakeDir the actual dir... - perror(Topics.Path); - return RC_ERROR_FILE; } + return RC_SUCCESS; } void @@ -7166,8 +7152,8 @@ enable syntax highlighting:")); // Defaults // NewSection("Defaults", &IndentationLevel); - scope_tree *ScopeTree = calloc(1, sizeof(scope_tree)); - InitScopeBooks(ScopeTree); + + scope_tree *ScopeTree = InitRootScopeTree(); SetTypeSpec(ScopeTree, &TypeSpecs); SetDefaults(ScopeTree, &TypeSpecs); PrintScopeTree(ScopeTree, IndentationLevel); @@ -9303,6 +9289,7 @@ HMMLToBuffers(buffers *CollationBuffers, template *BespokeTemplate, string BaseF case RC_ERROR_MEMORY: HMMLCleanup(); return RC_ERROR_FATAL; + default: break; }; if(!HasFilterMenu) { @@ -9587,6 +9574,7 @@ AppendedIdentifier: case RC_ERROR_MEMORY: HMMLCleanup(); return RC_ERROR_FATAL; + default: break; } if(!HasFilterMenu) { @@ -9607,6 +9595,7 @@ AppendedIdentifier: case RC_ERROR_MEMORY: HMMLCleanup(); return RC_ERROR_FATAL; + default: break; }; InsertCategory(&Topics, &LocalTopics, &Media, &LocalMedia, Wrap0("nullTopic")); } @@ -10424,9 +10413,48 @@ GenerateNavigation(config *C, project *Target, navigation_buffer *NavBuffer) } } -int +void +SortLandmarks(memory_book *A) +{ + for(int AssetIndex = 0; AssetIndex < A->ItemCount; ++AssetIndex) + { + asset *This = GetPlaceInBook(A, AssetIndex); + for(int SearchLandmarkIndex = 0; SearchLandmarkIndex < This->SearchLandmarkCount; ++SearchLandmarkIndex) + { + landmark *A = This->Search + SearchLandmarkIndex; + for(int TestIndex = SearchLandmarkIndex + 1; TestIndex < This->SearchLandmarkCount; ++TestIndex) + { + landmark *B = This->Search + TestIndex; + if(A->Offset > B->Offset) + { + landmark Temp = *A; + *A = *B; + *B = Temp; + } + } + } + + for(int PlayerLandmarkIndex = 0; PlayerLandmarkIndex < This->PlayerLandmarkCount; ++PlayerLandmarkIndex) + { + landmark *A = This->Player + PlayerLandmarkIndex; + for(int TestIndex = PlayerLandmarkIndex + 1; TestIndex < This->PlayerLandmarkCount; ++TestIndex) + { + landmark *B = This->Player + TestIndex; + if(A->Offset > B->Offset) + { + landmark Temp = *A; + *A = *B; + *B = Temp; + } + } + } + } +} + +rc BuffersToHTML(config *C, project *Project, buffers *CollationBuffers, template *Template, char *OutputPath, page_type PageType, unsigned int *PlayerOffset) { + rc Result = RC_SUCCESS; MEM_TEST_INITIAL(); #if DEBUG printf("\n\n --- Buffer Collation ---\n" @@ -10465,7 +10493,8 @@ BuffersToHTML(config *C, project *Project, buffers *CollationBuffers, template * LogError(LOG_ERROR, "BuffersToHTML(): %s", strerror(errno)); MEM_TEST_AFTER("BuffersToHTML"); - return RC_ERROR_MEMORY; + Result = RC_ERROR_MEMORY; + goto End; } #if DEBUG_MEM @@ -10619,7 +10648,8 @@ BuffersToHTML(config *C, project *Project, buffers *CollationBuffers, template * #endif MEM_TEST_AFTER("BuffersToHTML"); - return RC_ERROR_FILE; + Result = RC_ERROR_FILE; + goto End; } fwrite(Master.Location, Master.Ptr - Master.Location, 1, OutFile); fclose(OutFile); @@ -10634,16 +10664,14 @@ BuffersToHTML(config *C, project *Project, buffers *CollationBuffers, template * #endif MEM_TEST_AFTER("BuffersToHTML"); - return RC_SUCCESS; } else { MEM_TEST_AFTER("BuffersToHTML"); - return RC_INVALID_TEMPLATE; + Result = RC_INVALID_TEMPLATE; } #endif // AFE MEM_TEST_AFTER("BuffersToHTML"); - return RC_SUCCESS; // NOTE(matt): We added this simply to squash the "end of non-void function" warning } else { @@ -10657,7 +10685,8 @@ BuffersToHTML(config *C, project *Project, buffers *CollationBuffers, template * LogError(LOG_ERROR, "BuffersToHTML(): %s", strerror(errno)); MEM_TEST_AFTER("BuffersToHTML"); - return RC_ERROR_MEMORY; + Result = RC_ERROR_MEMORY; + goto End; } MEM_TEST_MID("BuffersToHTML3"); Master.Ptr = Master.Location; @@ -10700,7 +10729,8 @@ BuffersToHTML(config *C, project *Project, buffers *CollationBuffers, template * LogError(LOG_ERROR, "Unable to open output file %s: %s", OutputPath, strerror(errno)); DeclaimBuffer(&Master); MEM_TEST_AFTER("BuffersToHTML"); - return RC_ERROR_FILE; + Result = RC_ERROR_FILE; + goto End; } MEM_TEST_MID("BuffersToHTML13"); fwrite(Master.Location, Master.Ptr - Master.Location, 1, OutFile); @@ -10710,8 +10740,11 @@ BuffersToHTML(config *C, project *Project, buffers *CollationBuffers, template * OutFile = 0; FreeBuffer(&Master); MEM_TEST_AFTER("BuffersToHTML"); - return RC_SUCCESS; + Result = RC_SUCCESS; } +End: + SortLandmarks(&Assets); + return Result; } int @@ -11042,14 +11075,11 @@ GetNeighbourhoodForAddition(neighbourhood *N, edit_type_id EditType) void GetNeighbourhoodForDeletion(neighbourhood *N) { - char *Ptr = (char *)N->Project; - Ptr += sizeof(*N->Project); - db_entry *FirstEntry = (db_entry *)Ptr; - db_entry *Entry = FirstEntry + N->ThisIndex; + db_entry *FirstEntry = LocateFirstEntry(N->Project); N->PreDeletionThisIndex = N->ThisIndex; - N->This = Entry; + db_entry *Entry; int EntryIndex; N->DeletedEntryWasFirst = TRUE; @@ -11608,7 +11638,6 @@ AddLandmarks(neighbourhood *N, project *P, edit_type_id EditType) for(int j = 0; j < AssetInMemory->PlayerLandmarkCount; ++j) { db_landmark Landmark = {}; - // TODO(matt): Actually make sure that the correct project_index is set! Landmark.Project = P->Index; Landmark.EntryIndex = N->ThisIndex; Landmark.Position = AssetInMemory->Player[j].Offset; @@ -11718,31 +11747,45 @@ VerifyLandmarks_(neighbourhood *N, int LineNumber) db_block_assets *Block = LocateBlock(B_ASET); db_asset *Asset = LocateFirstAsset(Block); - for(int i = 0; i < Block->Count; ++i) + bool Malformed = FALSE; + for(uint16_t i = 0; i < Block->Count; ++i) { + bool Titled = FALSE; db_landmark *Landmark = LocateFirstLandmark(Asset); - for(int j = 0; j < Asset->LandmarkCount; ++j, ++Landmark) + for(uint16_t j = 0; j < Asset->LandmarkCount; ++j, ++Landmark) { if(j + 1 < Asset->LandmarkCount) { db_landmark *Next = Landmark + 1; if((ProjectIndicesDiffer(Next->Project, Landmark->Project) < 0) || - (ProjectIndicesMatch(Next->Project, Landmark->Project) && Next->EntryIndex < Landmark->EntryIndex)) + (ProjectIndicesMatch(Next->Project, Landmark->Project) && Next->EntryIndex < Landmark->EntryIndex) || + (ProjectIndicesMatch(Next->Project, Landmark->Project) && Next->EntryIndex == Landmark->EntryIndex && Next->Position < Landmark->Position)) { - PrintC(CS_ERROR, "We fail, sadly\n"); - PrintAssetsBlock(0); - PrintLandmark(Landmark); - fprintf(stderr, " vs "); - PrintLandmark(Next); + if(!Titled) + { + fprintf(stderr, "\nOut-of-order landmarks\n"); + PrintAsset(Asset, &i); + Titled = TRUE; + } + //PrintAssetsBlock(0); + PrintLandmark(Landmark, &j); + PrintC(CS_YELLOW, " vs "); + PrintLandmark(Next, 0); fprintf(stderr, "\n"); - PrintNeighbourhood(N); - _exit(1); + //PrintNeighbourhood(N); + //_exit(1); + Malformed = TRUE; } } } Asset = SkipAsset(Asset); } + if(Malformed) + { + _exit(1); + } + #if 0 DB.Metadata.Buffer.Ptr = DB.Metadata.Buffer.Location; DB.Header = *(db_header *)DB.Metadata.Buffer.Ptr; @@ -12263,57 +12306,97 @@ LinkOverDeletedEntry(neighbourhood *N) { if(N->DeletedEntryWasFirst) { - N->PreLinkNextOffsetTotal = N->Next->LinkOffsets.PrevEnd - + N->Next->LinkOffsets.NextStart - + N->Next->LinkOffsets.NextEnd; + if(N->Next) // NOTE(matt): Should be impossible to fail this test + { + N->PreLinkNextOffsetTotal = N->Next->LinkOffsets.PrevEnd + + N->Next->LinkOffsets.NextStart + + N->Next->LinkOffsets.NextEnd; - MarkNextAsFirst(N); + MarkNextAsFirst(N); - N->NextOffsetModifier = N->Next->LinkOffsets.PrevEnd - + N->Next->LinkOffsets.NextStart - + N->Next->LinkOffsets.NextEnd - - N->PreLinkNextOffsetTotal; + N->NextOffsetModifier = N->Next->LinkOffsets.PrevEnd + + N->Next->LinkOffsets.NextStart + + N->Next->LinkOffsets.NextEnd + - N->PreLinkNextOffsetTotal; + } + else + { + PrintC(CS_ERROR, "Error: Malformed neighbourhood"); + PrintNeighbourhood(N); + } return; } else if(N->DeletedEntryWasFinal) { - N->PreLinkPrevOffsetTotal = N->Prev->LinkOffsets.PrevEnd - + N->Prev->LinkOffsets.NextStart - + N->Prev->LinkOffsets.NextEnd; + if(N->Prev) // NOTE(matt): Should be impossible to fail this test + { + N->PreLinkPrevOffsetTotal = N->Prev->LinkOffsets.PrevEnd + + N->Prev->LinkOffsets.NextStart + + N->Prev->LinkOffsets.NextEnd; - MarkPrevAsFinal(N); + MarkPrevAsFinal(N); - N->PrevOffsetModifier = N->Prev->LinkOffsets.PrevEnd - + N->Prev->LinkOffsets.NextStart - + N->Prev->LinkOffsets.NextEnd - - N->PreLinkPrevOffsetTotal; + N->PrevOffsetModifier = N->Prev->LinkOffsets.PrevEnd + + N->Prev->LinkOffsets.NextStart + + N->Prev->LinkOffsets.NextEnd + - N->PreLinkPrevOffsetTotal; + } + else + { + PrintC(CS_ERROR, "Error: Malformed neighbourhood"); + PrintNeighbourhood(N); + } return; } else { // Assert(N->PrevIndex >= 0 && N->NextIndex >= 0) - N->PreLinkPrevOffsetTotal = N->Prev->LinkOffsets.PrevEnd - + N->Prev->LinkOffsets.NextStart - + N->Prev->LinkOffsets.NextEnd; + if(N->Prev) + { + N->PreLinkPrevOffsetTotal = N->Prev->LinkOffsets.PrevEnd + + N->Prev->LinkOffsets.NextStart + + N->Prev->LinkOffsets.NextEnd; + } + else + { + PrintC(CS_ERROR, "Error: Malformed neighbourhood"); + PrintNeighbourhood(N); + } - N->PreLinkNextOffsetTotal = N->Next->LinkOffsets.PrevEnd - + N->Next->LinkOffsets.NextStart - + N->Next->LinkOffsets.NextEnd; + if(N->Next) + { + N->PreLinkNextOffsetTotal = N->Next->LinkOffsets.PrevEnd + + N->Next->LinkOffsets.NextStart + + N->Next->LinkOffsets.NextEnd; + } + else + { + PrintC(CS_ERROR, "Error: Malformed neighbourhood"); + PrintNeighbourhood(N); + } } - InsertNeighbourLink(N->Project, N->Prev, N->Next, LINK_FORWARDS, N->FormerIsFirst); - InsertNeighbourLink(N->Project, N->Next, N->Prev, LINK_BACKWARDS, N->LatterIsFinal); + if(N->Prev && N->Next) + { + InsertNeighbourLink(N->Project, N->Prev, N->Next, LINK_FORWARDS, N->FormerIsFirst); + InsertNeighbourLink(N->Project, N->Next, N->Prev, LINK_BACKWARDS, N->LatterIsFinal); - N->PrevOffsetModifier = N->Prev->LinkOffsets.PrevEnd - + N->Prev->LinkOffsets.NextStart - + N->Prev->LinkOffsets.NextEnd - - N->PreLinkPrevOffsetTotal; + N->PrevOffsetModifier = N->Prev->LinkOffsets.PrevEnd + + N->Prev->LinkOffsets.NextStart + + N->Prev->LinkOffsets.NextEnd + - N->PreLinkPrevOffsetTotal; - N->NextOffsetModifier = N->Next->LinkOffsets.PrevEnd - + N->Next->LinkOffsets.NextStart - + N->Next->LinkOffsets.NextEnd - - N->PreLinkNextOffsetTotal; + N->NextOffsetModifier = N->Next->LinkOffsets.PrevEnd + + N->Next->LinkOffsets.NextStart + + N->Next->LinkOffsets.NextEnd + - N->PreLinkNextOffsetTotal; + } + else + { + PrintC(CS_ERROR, "Error: Malformed neighbourhood"); + PrintNeighbourhood(N); + } } } } @@ -12334,6 +12417,7 @@ LinkNeighbours(neighbourhood *N, enum8(link_types) LinkType) rc DeleteFromDB(neighbourhood *N, string BaseFilename) { + ResetNeighbourhood(N); // TODO(matt): LogError() db_entry *Entry = 0; int EntryIndex = BinarySearchForMetadataEntry(N->Project, &Entry, BaseFilename); @@ -14813,49 +14897,76 @@ SyncProject(project *P, neighbourhood *N, buffers *CollationBuffers, template *B PushWatchHandle(P->HMMLDir, EXT_HMML, WT_HMML, P, 0); } +char *inotifyEventStrings[] = +{ + /* 0x00000001 */ "IN_ACCESS", + /* 0x00000002 */ "IN_MODIFY", + /* 0x00000004 */ "IN_ATTRIB", + /* 0x00000008 */ "IN_CLOSE_WRITE", + /* 0x00000010 */ "IN_CLOSE_NOWRITE", + /* 0x00000020 */ "IN_OPEN", + /* 0x00000040 */ "IN_MOVED_FROM", + /* 0x00000080 */ "IN_MOVED_TO", + /* 0x00000100 */ "IN_CREATE", + /* 0x00000200 */ "IN_DELETE", + /* 0x00000400 */ "IN_DELETE_SELF", + /* 0x00000800 */ "IN_MOVE_SELF", + /* 0x00001000 */ "", // NOTE(matt): Apparently doesn't exist + /* 0x00002000 */ "IN_UNMOUNT", + /* 0x00004000 */ "IN_Q_OVERFLOW", + /* 0x00008000 */ "IN_IGNORED", + /* 0x00010000 */ "", + /* 0x00020000 */ "", + /* 0x00040000 */ "", + /* 0x00080000 */ "", + /* 0x00100000 */ "", + /* 0x00200000 */ "", + /* 0x00400000 */ "", + /* 0x00800000 */ "", + /* 0x01000000 */ "IN_ONLYDIR", + /* 0x02000000 */ "IN_DONT_FOLLOW", + /* 0x04000000 */ "IN_EXCL_UNLINK", + /* 0x08000000 */ "", // NOTE(matt): Apparently doesn't exist + /* 0x10000000 */ "IN_MASK_CREATE", + /* 0x20000000 */ "IN_MASK_ADD", + /* 0x40000000 */ "IN_ISDIR", + /* 0x80000000 */ "IN_ONESHOT", +}; + #define DEBUG_EVENTS 0 #if DEBUG_EVENTS void -PrintEvent(struct inotify_event *Event, int EventIndex) +PrintEvent(struct inotify_event *Event, int EventIndex, int Indentation) { + fprintf(stderr, "\n\n"); + Indent(Indentation); + fprintf(stderr, "Event[%d]\n", EventIndex); + Indent(Indentation); + fprintf(stderr, " wd: %d\n", Event->wd); + Indent(Indentation); + fprintf(stderr, " mask: 0x%08X", Event->mask); - printf("\nEvent[%d]\n" - " wd: %d\n" - " mask: %d\n", - EventIndex, - Event->wd, - Event->mask); + for(int i = 0; i < ArrayCount(inotifyEventStrings); ++i) + { + if(Event->mask & (1 << i)) + { + fprintf(stderr, "\n"); + Indent(Indentation); + fprintf(stderr, " %s", inotifyEventStrings[i]); + } + } - if(Event->mask & IN_ACCESS) { printf(" IN_ACCESS\n"); } - if(Event->mask & IN_ATTRIB) { printf(" IN_ATTRIB\n"); } - if(Event->mask & IN_CLOSE_WRITE) { printf(" IN_CLOSE_WRITE\n"); } - if(Event->mask & IN_CLOSE_NOWRITE) { printf(" IN_CLOSE_NOWRITE\n"); } - if(Event->mask & IN_CREATE) { printf(" IN_CREATE\n"); } - if(Event->mask & IN_DELETE) { printf(" IN_DELETE\n"); } - if(Event->mask & IN_DELETE_SELF) { printf(" IN_DELETE_SELF\n"); } - if(Event->mask & IN_MODIFY) { printf(" IN_MODIFY\n"); } - if(Event->mask & IN_MOVE_SELF) { printf(" IN_MOVE_SELF\n"); } - if(Event->mask & IN_MOVED_FROM) { printf(" IN_MOVED_FROM\n"); } - if(Event->mask & IN_MOVED_TO) { printf(" IN_MOVED_TO\n"); } - if(Event->mask & IN_OPEN) { printf(" IN_OPEN\n"); } - if(Event->mask & IN_MOVE) { printf(" IN_MOVE\n"); } - if(Event->mask & IN_CLOSE) { printf(" IN_CLOSE\n"); } - if(Event->mask & IN_DONT_FOLLOW) { printf(" IN_DONT_FOLLOW\n"); } - if(Event->mask & IN_EXCL_UNLINK) { printf(" IN_EXCL_UNLINK\n"); } - if(Event->mask & IN_MASK_ADD) { printf(" IN_MASK_ADD\n"); } - if(Event->mask & IN_ONESHOT) { printf(" IN_ONESHOT\n"); } - if(Event->mask & IN_ONLYDIR) { printf(" IN_ONLYDIR\n"); } - if(Event->mask & IN_IGNORED) { printf(" IN_IGNORED\n"); } - if(Event->mask & IN_ISDIR) { printf(" IN_ISDIR\n"); } - if(Event->mask & IN_Q_OVERFLOW) { printf(" IN_Q_OVERFLOW\n"); } - if(Event->mask & IN_UNMOUNT) { printf(" IN_UNMOUNT\n"); } + fprintf(stderr, "\n"); + Indent(Indentation); + fprintf(stderr, " cookie: %d", Event->cookie); - printf( " cookie: %d\n" - " len: %d\n" - " name: %s\n", - Event->cookie, - Event->len, - Event->name); + fprintf(stderr, "\n"); + Indent(Indentation); + fprintf(stderr, " len: %d", Event->len); + + fprintf(stderr, "\n"); + Indent(Indentation); + fprintf(stderr, " name: %s", Event->name); } #endif @@ -14995,6 +15106,56 @@ ParseAndEitherPrintConfigOrInitAll(string ConfigPath, memory_book *TokensList, n } } +#if DEBUG_EVENTS +void +SquashEventsD(buffer *Events, int BytesRead, struct inotify_event **Event, int *DebugEventIndex) +{ + char *PeekPtr = Events->Ptr + sizeof(struct inotify_event) + (*Event)->len; + struct inotify_event *Peek = (struct inotify_event *)PeekPtr; + bool Squashed = FALSE; + while(PeekPtr - Events->Location < BytesRead && (*Event)->wd == Peek->wd && StringsMatch(Wrap0((*Event)->name), Wrap0(Peek->name))) + { + if(!Squashed) + { + fprintf(stderr, "\n\n" + " Squashing events:"); + + } + + PrintEvent(Peek, ++*DebugEventIndex, 1); + + Squashed = TRUE; + + *Event = Peek; + Events->Ptr = PeekPtr; + + PeekPtr = Events->Ptr + sizeof(struct inotify_event) + (*Event)->len; + Peek = (struct inotify_event *)PeekPtr; + } + + if(Squashed) + { + fprintf(stderr, "\n\n" + " Finished squashing\n"); + } +} +#endif + +void +SquashEvents(buffer *Events, int BytesRead, struct inotify_event **Event) +{ + char *PeekPtr = Events->Ptr + sizeof(struct inotify_event) + (*Event)->len; + struct inotify_event *Peek = (struct inotify_event *)PeekPtr; + while(PeekPtr - Events->Location < BytesRead && (*Event)->wd == Peek->wd && StringsMatch(Wrap0((*Event)->name), Wrap0(Peek->name))) + { + *Event = Peek; + Events->Ptr = PeekPtr; + + PeekPtr = Events->Ptr + sizeof(struct inotify_event) + (*Event)->len; + Peek = (struct inotify_event *)PeekPtr; + } +} + int MonitorFilesystem(neighbourhood *N, buffers *CollationBuffers, template *BespokeTemplate, string ConfigPath, memory_book *TokensList) { @@ -15004,9 +15165,6 @@ MonitorFilesystem(neighbourhood *N, buffers *CollationBuffers, template *Bespoke struct inotify_event *Event; int BytesRead = read(inotifyInstance, Events.Location, Events.Size); // TODO(matt): Handle error EINVAL - if(inotifyInstance < 0) { perror("MonitorFilesystem()"); } - - // TODO(matt): Test this with longer update intervals, and combinations of events... #if DEBUG_EVENTS if(BytesRead > 0) { @@ -15031,46 +15189,56 @@ MonitorFilesystem(neighbourhood *N, buffers *CollationBuffers, template *Bespoke Event = (struct inotify_event *)Events.Ptr; #if DEBUG_EVENTS - PrintEvent(Event, DebugEventIndex); + PrintEvent(Event, DebugEventIndex, 0); //PrintWatchHandles(); #endif watch_file *WatchFile = GetWatchFileForEvent(Event); if(WatchFile) { - char *PeekPtr = Events.Ptr + sizeof(struct inotify_event) + Event->len; - struct inotify_event *Peek = (struct inotify_event *)PeekPtr; - while(PeekPtr - Events.Location < BytesRead && Event->wd == Peek->wd && StringsMatch(Wrap0i(Event->name, Event->len), Wrap0i(Peek->name, Peek->len))) - { #if DEBUG_EVENTS - fprintf(stderr, "Squashing events:\n" - " "); - PrintEvent(Event, DebugEventIndex++); - fprintf(stderr, "\n" - " "); - PrintEvent(Peek, DebugEventIndex); - fprintf(stderr, "\n"); + SquashEventsD(&Events, BytesRead, &Event, &DebugEventIndex); +#else + SquashEvents(&Events, BytesRead, &Event); #endif + bool WouldHaveDeleted = (Event->mask & (IN_DELETE | IN_MOVED_FROM)) ? TRUE : FALSE; - Event = Peek; - Events.Ptr = PeekPtr; + if(WouldHaveDeleted && (Events.Ptr + sizeof(struct inotify_event) + Event->len) - Events.Location == BytesRead) + { + fprintf(stderr, "Sleeping before polling for more filesystem events"); + sleep(1); - PeekPtr = Events.Ptr + sizeof(struct inotify_event) + Event->len; - Peek = (struct inotify_event *)PeekPtr; + struct inotify_event EventN = *Event; + char EventNName[Event->len + 1]; + ClearCopyStringNoFormat(EventNName, sizeof(EventNName), Wrap0(Event->name)); + + char *EventSlot1 = Events.Location + sizeof(struct inotify_event) + Event->len; + + int NewBytesRead = read(inotifyInstance, EventSlot1, Events.Size - (EventSlot1 - Events.Location)); // TODO(matt): Handle error EINVAL + if(NewBytesRead >= 0) + { + BytesRead = sizeof(struct inotify_event) + EventN.len + NewBytesRead; + struct inotify_event *Event0 = (struct inotify_event *)Events.Location; + *Event0 = EventN; + CopyStringNoFormat(Event0->name, Event0->len, Wrap0(EventNName)); + Event = Event0; + Events.Ptr = Events.Location; +#if DEBUG_EVENTS + SquashEventsD(&Events, BytesRead, &Event, &DebugEventIndex); +#else + SquashEvents(&Events, BytesRead, &Event); +#endif + } } switch(WatchFile->Type) { - // TODO(matt): We're probably watching for too many events when the target directory exists case WT_HMML: { SetCurrentProject(WatchFile->Project, N); - //PrintLineage(CurrentProject->Lineage, TRUE); - string BaseFilename = GetBaseFilename(Wrap0(Event->name), WatchFile->Extension); if(Event->mask & (IN_DELETE | IN_MOVED_FROM)) { - // TODO(matt): Why are we getting here after editing a .hmml file? We should just reinsert Deleted |= (DeleteEntry(N, BaseFilename) == RC_SUCCESS); } else if(Event->mask & (IN_CLOSE_WRITE | IN_MOVED_TO)) @@ -15080,8 +15248,6 @@ MonitorFilesystem(neighbourhood *N, buffers *CollationBuffers, template *Bespoke } break; case WT_ASSET: { - // TODO(matt): Why are we getting here after editing a .hmml file? - //PrintAssetsBlock(0); UpdateAsset(WatchFile->Asset, FALSE); UpdateNeighbourhoodPointers(N, &DB.Metadata.Signposts); #if DEBUG_LANDMARKS @@ -15210,6 +15376,7 @@ main(int ArgC, char **Args) 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; string ConfigPathL = Wrap0(ConfigPath); PushWatchHandle(ConfigPathL, EXT_NULL, WT_CONFIG, 0, 0); @@ -15250,24 +15417,31 @@ main(int ArgC, char **Args) } } - //PrintWatchHandles(); - while(MonitorFilesystem(&Neighbourhood, &CollationBuffers, &BespokeTemplate, ConfigPathL, &TokensList) != RC_ARENA_FULL) + if(inotifyInstance) { - // 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) + while(MonitorFilesystem(&Neighbourhood, &CollationBuffers, &BespokeTemplate, ConfigPathL, &TokensList) != RC_ARENA_FULL) { - RecheckPrivacy(&Neighbourhood, &CollationBuffers, &BespokeTemplate); + // 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); } - sleep(GLOBAL_UPDATE_INTERVAL); + } + else + { + fprintf(stderr, "Error: Quitting because inotify initialisation failed with message\n" + " %s\n", strerror(inotifyError)); } DiscardAllAndFreeConfig(); diff --git a/cinera/cinera_config.c b/cinera/cinera_config.c index 2b89285..e7bed48 100644 --- a/cinera/cinera_config.c +++ b/cinera/cinera_config.c @@ -1543,6 +1543,14 @@ InitScopeBooks(scope_tree *Scope) InitBook(&Scope->BoolPairs, sizeof(config_bool_pair), 4, MBT_CONFIG_BOOL_PAIR); } +scope_tree * +InitRootScopeTree(void) +{ + scope_tree *Result = calloc(1, sizeof(scope_tree)); + InitScopeBooks(Result); + return Result; +} + scope_tree * PushScope(memory_book *TypeSpecs, scope_tree *Parent, config_pair *ID) { @@ -4583,8 +4591,7 @@ ParseConfig(string Path, memory_book *TokensList) if(T) { - scope_tree *ScopeTree = calloc(1, sizeof(scope_tree)); - InitScopeBooks(ScopeTree); + scope_tree *ScopeTree = InitRootScopeTree(); SetTypeSpec(ScopeTree, &TypeSpecs); SetDefaults(ScopeTree, &TypeSpecs); ScopeTree = ScopeTokens(ScopeTree, TokensList, T, &TypeSpecs, 0); @@ -4597,8 +4604,7 @@ ParseConfig(string Path, memory_book *TokensList) FreeTokensList(TokensList); MEM_LOOP_PRE_WORK() T = Tokenise(TokensList, Path); - ScopeTree = calloc(1, sizeof(scope_tree)); - InitScopeBooks(ScopeTree); + ScopeTree = InitRootScopeTree(); SetTypeSpec(ScopeTree, &TypeSpecs); SetDefaults(ScopeTree, &TypeSpecs); ScopeTree = ScopeTokens(ScopeTree, TokensList, T, &TypeSpecs, 0);