cinera: Lock database and config files

This commit aims to help Cinera instances coordinate themselves by
throwing an error upon being instructed to use a database or config
file currently in use by another instance.
This commit is contained in:
Matt Mascarenhas 2022-09-16 16:10:19 +01:00
parent 1cf703e346
commit f60cf3087f
2 changed files with 570 additions and 472 deletions

View File

@ -23,15 +23,22 @@ typedef struct
version CINERA_APP_VERSION = {
.Major = 0,
.Minor = 10,
.Patch = 12
.Patch = 13
};
#define __USE_XOPEN2K8 // NOTE(matt): O_NOFOLLOW
#include <fcntl.h> // NOTE(matt): open()
#undef __USE_XOPEN2K8
#include <stdarg.h> // NOTE(matt): varargs
#define __USE_POSIX // NOTE(matt): fileno()
#include <stdio.h> // NOTE(matt): printf, sprintf, vsprintf, fprintf, perror
#undef __USE_POSIX
#include <stdlib.h> // NOTE(matt): calloc, malloc, free
#include <getopt.h> // NOTE(matt): getopts
#include <curl/curl.h>
#include <time.h>
#include <sys/file.h> // NOTE(matt): flock
#include <sys/stat.h>
#include <sys/types.h>
#include <dirent.h>
@ -41,10 +48,6 @@ version CINERA_APP_VERSION = {
#include <sys/ioctl.h> // NOTE(matt): ioctl and TIOCGWINSZ
#include <wordexp.h>
#define __USE_XOPEN2K8 // NOTE(matt): O_NOFOLLOW
#include <fcntl.h> // NOTE(matt): open()
#undef __USE_XOPEN2K8
#define __USE_XOPEN2K // NOTE(matt): readlink()
#include <unistd.h> // NOTE(matt): sleep()
#undef __USE_XOPEN2K
@ -67,6 +70,7 @@ version CINERA_APP_VERSION = {
typedef uint64_t bool;
#define TRUE 1
#define FALSE 0
#define NA 0
#define SECONDS_PER_HOUR 3600
#define SECONDS_PER_MINUTE 60
@ -279,6 +283,7 @@ typedef enum
RC_ERROR_DIRECTORY,
RC_ERROR_FATAL,
RC_ERROR_FILE,
RC_ERROR_FILE_LOCKED,
RC_ERROR_HMML,
RC_ERROR_MAX_REFS,
RC_ERROR_MEMORY,
@ -464,6 +469,7 @@ typedef struct
buffer Buffer;
char *Path;
FILE *Handle;
bool Locking;
} file;
typedef struct
@ -1064,7 +1070,7 @@ ExtendString0(char **Dest, string Src)
}
file
InitFile(string *Directory, string *Filename, extension_id Extension)
InitFile(string *Directory, string *Filename, extension_id Extension, bool Locking)
{
file Result = {};
if(Directory)
@ -1079,6 +1085,36 @@ InitFile(string *Directory, string *Filename, extension_id Extension)
{
ExtendString0(&Result.Path, ExtensionStrings[Extension]);
}
Result.Locking = Locking;
return Result;
}
void
CloseFile(file *F, bool CloseLocking)
{
if(F->Handle)
{
fclose(F->Handle);
F->Handle = 0;
if(F->Locking && !CloseLocking)
{
F->Handle = fopen(F->Path, "r");
flock(fileno(F->Handle), LOCK_EX | LOCK_NB);
}
}
}
bool
TryLock(file *F) // USAGE: File shall already be open, if possible
{
bool Result = TRUE;
if(F->Locking && F->Handle)
{
if(flock(fileno(F->Handle), LOCK_EX | LOCK_NB) == -1)
{
Result = FALSE;
}
}
return Result;
}
@ -1088,7 +1124,9 @@ ReadFileIntoBuffer(file *F)
rc Result = RC_ERROR_FILE;
if(F->Path)
{
if((F->Handle = fopen(F->Path, "r")))
if(F->Handle || (F->Handle = fopen(F->Path, "r")))
{
if(TryLock(F))
{
fseek(F->Handle, 0, SEEK_END);
F->Buffer.Size = ftell(F->Handle);
@ -1096,13 +1134,13 @@ ReadFileIntoBuffer(file *F)
F->Buffer.Ptr = F->Buffer.Location;
fseek(F->Handle, 0, SEEK_SET);
fread(F->Buffer.Location, F->Buffer.Size, 1, F->Handle);
fclose(F->Handle);
F->Handle = 0;
CloseFile(F, FALSE);
Result = RC_SUCCESS;
}
else
{
perror(F->Path);
Result = RC_ERROR_FILE_LOCKED;
}
}
}
return Result;
@ -1914,16 +1952,6 @@ ConfigErrorUnset(config_identifier_id FieldID)
"Unset %s\n", ConfigIdentifiers[FieldID].String);
}
void
ConfigFileIncludeError(string *Filename, uint64_t LineNumber, string Path)
{
ErrorFilenameAndLineNumber(Filename, LineNumber, S_WARNING, ED_CONFIG);
fprintf(stderr,
"Included file could not be opened (%s): ", strerror(errno));
PrintStringC(CS_MAGENTA_BOLD, Path);
fprintf(stderr, "\n");
}
void
ConfigErrorSizing(string *Filename, uint64_t LineNumber, config_identifier_id FieldID, string *Received, uint64_t MaxSize)
{
@ -1933,6 +1961,27 @@ ConfigErrorSizing(string *Filename, uint64_t LineNumber, config_identifier_id Fi
fprintf(stderr, "\n");
}
void
ConfigErrorLockedConfigLocation(string *Filename, uint64_t LineNumber, string *Received)
{
ErrorFilenameAndLineNumber(Filename, LineNumber, Filename ? S_WARNING : S_ERROR, ED_CONFIG);
fprintf(stderr, "%s file %s%.*s%s is in use by another Cinera instance\n", Filename ? "Included" : "Config", ColourStrings[CS_MAGENTA_BOLD], (int)Received->Length, Received->Base, ColourStrings[CS_END]);
}
void
ConfigErrorUnopenableConfigLocation(string *Filename, uint64_t LineNumber, string *Received)
{
ErrorFilenameAndLineNumber(Filename, LineNumber, Filename ? S_WARNING : S_ERROR, ED_CONFIG);
fprintf(stderr, "%s file %s%.*s%s could not be opened: %s\n", Filename ? "Included" : "Config", ColourStrings[CS_MAGENTA_BOLD], (int)Received->Length, Received->Base, ColourStrings[CS_END], strerror(errno));
}
void
ConfigErrorLockedDBLocation(string *Filename, uint64_t LineNumber, string *Received)
{
ErrorFilenameAndLineNumber(Filename, LineNumber, S_ERROR, ED_CONFIG);
fprintf(stderr, "File at db_location %s%.*s%s is in use by another Cinera instance\n", ColourStrings[CS_MAGENTA_BOLD], (int)Received->Length, Received->Base, ColourStrings[CS_END]);
}
void
ConfigErrorInt(string *Filename, uint64_t LineNumber, severity Severity, char *Message, uint64_t Number)
{
@ -2740,6 +2789,7 @@ typedef struct
{
file File;
file_signposted Metadata;
bool Ready;
db_header5 Header;
db_block_projects5 ProjectsBlock;
@ -3024,17 +3074,23 @@ ClearTemplateMetadata(template *Template)
}
void
FreeFile(file *F)
FreeFileBufferAndPath(file *F)
{
FreeBuffer(&F->Buffer);
Free(F->Path);
F->Handle = 0;
}
void
FreeSignpostedFile(file_signposted *F)
FreeFile(file *F, bool CloseLocking)
{
FreeFile(&F->File);
CloseFile(F, CloseLocking);
FreeFileBufferAndPath(F);
}
void
FreeSignpostedFile(file_signposted *F, bool CloseLocking)
{
FreeFile(&F->File, CloseLocking);
file_signposts Zero = {};
F->Signposts = Zero;
}
@ -3042,7 +3098,7 @@ FreeSignpostedFile(file_signposted *F)
void
FreeTemplate(template *Template)
{
FreeFile(&Template->File);
FreeFile(&Template->File, NA);
ClearTemplateMetadata(Template);
}
@ -3152,8 +3208,6 @@ typedef enum
WT_CONFIG,
} watch_type;
#include "cinera_config.c"
typedef struct
{
watch_type Type;
@ -3161,6 +3215,7 @@ typedef struct
extension_id Extension;
project *Project;
asset *Asset;
FILE *Handle;
} watch_file;
typedef struct
@ -3178,6 +3233,8 @@ typedef struct
uint32_t DefaultEventsMask;
} watch_handles;
#include "cinera_config.c"
#define AFD 1
#define AFE 0
@ -4281,17 +4338,17 @@ InitTemplate(template *Template, string Location, template_type Type)
Template->Metadata.Type = Type;
if(Type == TEMPLATE_GLOBAL_SEARCH)
{
Template->File = InitFile(&Config->GlobalTemplatesDir, &Config->GlobalSearchTemplatePath, EXT_NULL);
Template->File = InitFile(&Config->GlobalTemplatesDir, &Config->GlobalSearchTemplatePath, EXT_NULL, FALSE);
}
else
{
if(Location.Base[0] == '/')
{
Template->File = InitFile(0, &Location, EXT_NULL);
Template->File = InitFile(0, &Location, EXT_NULL, FALSE);
}
else
{
Template->File = InitFile(Type == TEMPLATE_BESPOKE ? &CurrentProject->HMMLDir : &CurrentProject->TemplatesDir, &Location, EXT_NULL);
Template->File = InitFile(Type == TEMPLATE_BESPOKE ? &CurrentProject->HMMLDir : &CurrentProject->TemplatesDir, &Location, EXT_NULL, FALSE);
}
}
fprintf(stderr, "%sPacking%s template: %s\n", ColourStrings[CS_ONGOING], ColourStrings[CS_END], Template->File.Path);
@ -4516,7 +4573,7 @@ ConstructAssetPath(file *AssetFile, string Filename, asset_type Type)
void
CycleFile(file *File)
{
fclose(File->Handle);
CloseFile(File, FALSE);
// TODO(matt): Rather than freeing the buffer, why not just realloc it to fit the new file? Couldn't that work easily?
// The reason we Free / Reread is to save us having to shuffle the buffer contents around, basically
// If we switch the whole database over to use linked lists - the database being the only file we actually cycle
@ -4541,7 +4598,7 @@ CycleSignpostedFile(file_signposted *File)
// NOTE(matt) This file_signposted struct is totally hardcoded to the Metadata file, but may suffice for now
//
// TODO(matt): This is probably insufficient. We likely need to offset the pointers any time we add stuff to the database
fclose(File->File.Handle);
CloseFile(&File->File, FALSE);
// TODO(matt): Rather than freeing the buffer, why not just realloc it to fit the new file? Couldn't that work easily?
// The reason we Free / Reread is to save us having to shuffle the buffer contents around, basically
// If we switch the whole database over to use linked lists - the database being the only file we actually cycle
@ -4834,9 +4891,7 @@ SnipeChecksumAndCloseFile(file *HTMLFile, db_asset *Asset, int LandmarksInFile,
HTMLFile->Handle = fopen(HTMLFile->Path, "w");
fwrite(HTMLFile->Buffer.Location, HTMLFile->Buffer.Size, 1, HTMLFile->Handle);
fclose(HTMLFile->Handle);
HTMLFile->Handle = 0;
FreeFile(HTMLFile);
FreeFile(HTMLFile, NA);
}
// TODO(matt): Bounds-check the Metadata.File.Buffer
@ -5136,13 +5191,25 @@ AccumulateFileEditSize(file_signposted *File, int64_t Bytes)
File->Signposts.Edit.Size += Bytes;
}
FILE *
OpenFileForWriting(file *F)
{
if(F->Locking && F->Handle)
{
fclose(F->Handle);
}
F->Handle = fopen(F->Path, "w");
TryLock(F);
return F->Handle;
}
// TODO(matt): Consider enforcing an order of these blocks, basically putting the easy-to-skip ones first...
void *
InitBlock(block_id ID)
{
db_header *Header = (db_header *)DB.Metadata.File.Buffer.Location;
++Header->BlockCount;
DB.Metadata.File.Handle = fopen(DB.Metadata.File.Path, "w");
OpenFileForWriting(&DB.Metadata.File);
fwrite(DB.Metadata.File.Buffer.Location, DB.Metadata.File.Buffer.Size, 1, DB.Metadata.File.Handle);
SetFileEditPosition(&DB.Metadata);
@ -5419,9 +5486,10 @@ IsSymlink(char *Filepath)
return Result;
}
void
watch_file *
PushWatchFileUniquely(watch_handle *Handle, string Filepath, extension_id Extension, watch_type Type, project *Project, asset *Asset)
{
watch_file *Result = 0;
bool Required = TRUE;
for(int i = 0; i < Handle->Files.ItemCount; ++i)
{
@ -5432,14 +5500,16 @@ PushWatchFileUniquely(watch_handle *Handle, string Filepath, extension_id Extens
{
if(This->Extension == Extension)
{
Result = This;
Required = FALSE;
break;
}
}
else if(StringsMatch(This->Path, Filepath))
{
Required = FALSE;
Result = This;
EraseCurrentStringFromBook(&WatchHandles.Paths);
Required = FALSE;
break;
}
}
@ -5459,7 +5529,9 @@ PushWatchFileUniquely(watch_handle *Handle, string Filepath, extension_id Extens
New->Type = Type;
New->Project = Project;
New->Asset = Asset;
Result = New;
}
return Result;
}
bool
@ -5535,9 +5607,10 @@ GetNearestExistingPath(string Path)
return Result;
}
void
watch_file *
PushWatchHandle(string Path, extension_id Extension, watch_type Type, project *Project, asset *Asset)
{
watch_file *Result = 0;
// NOTE(matt): This function influences RemoveAndFreeAllButFirstWatchFile(). If we change to write different strings into
// the W->Paths memory_book, we must reflect that change over to RemoveAndFreeAllButFirstWatchFile()
@ -5675,7 +5748,8 @@ PushWatchHandle(string Path, extension_id Extension, watch_type Type, project *P
//PrintWatchHandles();
}
PushWatchFileUniquely(Watch, Filename, Extension, Type, Project, Asset);
Result = PushWatchFileUniquely(Watch, Filename, Extension, Type, Project, Asset);
return Result;
}
bool
@ -5795,6 +5869,17 @@ UpdateNeighbourhoodPointers(neighbourhood *N, file_signposts *S)
N->Next = S->Next.Ptr;
}
void
WriteEntireDatabase(neighbourhood *N)
{
// NOTE(matt): This may suffice when the only changes are to existing fixed-size variables,
// e.g. ProjectsBlock->GlobalSearchDir
OpenFileForWriting(&DB.Metadata.File);
fwrite(DB.Metadata.File.Buffer.Location, DB.Metadata.File.Buffer.Size, 1, DB.Metadata.File.Handle);
CycleSignpostedFile(&DB.Metadata);
UpdateNeighbourhoodPointers(N, &DB.Metadata.Signposts);
}
int
UpdateAssetInDB(asset *Asset)
{
@ -5854,7 +5939,7 @@ UpdateAssetInDB(asset *Asset)
FreeString(&Message);
DB.Metadata.File.Handle = fopen(DB.Metadata.File.Path, "w");
OpenFileForWriting(&DB.Metadata.File);
fwrite(DB.Metadata.File.Buffer.Location, DB.Metadata.File.Buffer.Size, 1, DB.Metadata.File.Handle);
SetFileEditPosition(&DB.Metadata);
@ -5865,7 +5950,7 @@ UpdateAssetInDB(asset *Asset)
else
{
// Append new asset, not bothering to insertion sort because there likely won't be many
DB.Metadata.File.Handle = fopen(DB.Metadata.File.Path, "w");
OpenFileForWriting(&DB.Metadata.File);
char *InsertionPoint = LocateEndOfAssetsBlock(AssetsBlock);
AssetIndexInDB = AssetsBlock->Count;
@ -5994,7 +6079,7 @@ UpdateAsset(asset *Asset, bool Defer)
{
AssetIndexInDB = UpdateAssetInDB(Asset);
}
FreeFile(&File);
FreeFile(&File, NA);
return AssetIndexInDB;
}
@ -6068,7 +6153,7 @@ PlaceAsset(string Filename, asset_type Type, uint64_t Variants, bool Associated,
printf("%sNonexistent%s %s asset: %s\n", ColourStrings[CS_WARNING], ColourStrings[CS_END], AssetTypeNames[Type], File.Path);
}
FreeFile(&File);
FreeFile(&File, NA);
return This;
}
@ -7601,7 +7686,7 @@ GenerateTopicColours(neighbourhood *N, string Topic)
printf(" Freed Topics (%ld)\n", Topics.Buffer.Size);
#endif
fclose(Topics.Handle);
CloseFile(&Topics, NA);
asset *Asset = GetPlaceInBook(&Assets, ASSET_CSS_TOPICS);
if(Asset->Known)
@ -7623,7 +7708,7 @@ GenerateTopicColours(neighbourhood *N, string Topic)
}
}
}
FreeFile(&Topics);
FreeFile(&Topics, NA);
}
return Result;
}
@ -9115,7 +9200,7 @@ void
ExamineDB(void)
{
DB.Metadata.File.Buffer.ID = BID_DATABASE;
DB.Metadata.File = InitFile(0, &Config->DatabaseLocation, EXT_NULL);
DB.Metadata.File = InitFile(0, &Config->DatabaseLocation, EXT_NULL, TRUE);
ReadFileIntoBuffer(&DB.Metadata.File); // NOTE(matt): Could we actually catch errors (permissions?) here and bail?
if(DB.Metadata.File.Buffer.Location)
@ -9146,7 +9231,7 @@ ExamineDB(void)
{
fprintf(stderr, "Unable to open database file %s: %s\n", DB.Metadata.File.Path, strerror(errno));
}
FreeFile(&DB.Metadata.File);
FreeFile(&DB.Metadata.File, TRUE);
}
#define HMMLCleanup() \
@ -9323,7 +9408,7 @@ DeleteSearchPageFromFilesystem(string BaseDir, string SearchLocation, string Pro
remove(DB.File.Path);
// TODO(matt): Consider the correctness of this.
FreeFile(&DB.File);
FreeFile(&DB.File, NA);
char *SearchDirectory = ConstructDirectoryPath(&BaseDir, &SearchLocation, 0);
remove(SearchDirectory);
@ -11866,7 +11951,7 @@ InitIndexFile(project *P)
DB.File.Handle = fopen(DB.File.Path, "w");
fprintf(DB.File.Handle, "---\n");
fclose(DB.File.Handle);
CloseFile(&DB.File, NA);
ReadFileIntoBuffer(&DB.File);
}
}
@ -12243,10 +12328,7 @@ RenumberEntries(neighbourhood *N, db_header_project *P, int64_t IndexOfFirstEntr
Cursor += fwrite(Cursor, 1, DB.File.Buffer.Size - (Cursor - DB.File.Buffer.Location), DB.File.Handle);
CycleFile(&DB.File);
if(!(DB.Metadata.File.Handle = fopen(DB.Metadata.File.Path, "w"))) { return; }
fwrite(DB.Metadata.File.Buffer.Location, 1, DB.Metadata.File.Buffer.Size, DB.Metadata.File.Handle);
CycleSignpostedFile(&DB.Metadata);
UpdateNeighbourhoodPointers(N, &DB.Metadata.Signposts);
WriteEntireDatabase(N);
}
db_entry *
@ -12328,7 +12410,7 @@ InsertIntoDB(neighbourhood *N, buffers *CollationBuffers, template *BespokeTempl
fwrite(DB.File.Buffer.Location, EntryInsertionStart, 1, DB.File.Handle);
fwrite(CollationBuffers->SearchEntry.Location, N->This->Size, 1, DB.File.Handle);
fwrite(DB.File.Buffer.Location + EntryInsertionEnd, DB.File.Buffer.Size - EntryInsertionEnd, 1, DB.File.Handle);
fclose(DB.File.Handle);
CloseFile(&DB.File, NA);
if(N->This->Size == EntryInsertionEnd - EntryInsertionStart)
{
@ -12350,7 +12432,7 @@ InsertIntoDB(neighbourhood *N, buffers *CollationBuffers, template *BespokeTempl
Ptr += sizeof(*N->Project) + sizeof(db_entry) * N->ThisIndex;
uint64_t BytesIntoFile = Ptr - DB.Metadata.File.Buffer.Location;
if(!(DB.Metadata.File.Handle = fopen(DB.Metadata.File.Path, "w"))) { return 0; }
if(!(OpenFileForWriting(&DB.Metadata.File))) { return 0; }
fwrite(DB.Metadata.File.Buffer.Location, BytesIntoFile, 1, DB.Metadata.File.Handle);
SetFileEditPosition(&DB.Metadata);
@ -12401,7 +12483,7 @@ WritePastAssetsHeader(void)
DB.Metadata.File.Buffer.Ptr = DB.Metadata.Signposts.AssetsBlock.Ptr;
DB.Metadata.File.Buffer.Ptr += sizeof(db_block_assets);
DB.Metadata.File.Handle = fopen(DB.Metadata.File.Path, "w");
OpenFileForWriting(&DB.Metadata.File);
fwrite(DB.Metadata.File.Buffer.Location, DB.Metadata.File.Buffer.Ptr - DB.Metadata.File.Buffer.Location, 1, DB.Metadata.File.Handle);
}
@ -13197,7 +13279,7 @@ InsertNeighbourLink(db_header_project *P, db_entry *From, db_entry *To, enum8(li
if(To) { DeclaimBuffer(&ToPlayerURL); }
DeclaimBuffer(&Link);
fclose(HTML.Handle);
CloseFile(&HTML, NA);
HTML.Handle = 0;
}
}
@ -13205,7 +13287,7 @@ InsertNeighbourLink(db_header_project *P, db_entry *From, db_entry *To, enum8(li
{
Result = RC_ERROR_FILE;
}
FreeFile(&HTML);
FreeFile(&HTML, NA);
MEM_TEST_END("InsertNeighbourLink()");
return Result;
}
@ -13236,14 +13318,14 @@ DeleteNeighbourLinks(neighbourhood *N)
ReadPlayerPageIntoBuffer(&HTML, Wrap0i(N->Project->BaseDir), Wrap0i(N->Project->PlayerLocation), Wrap0i(Entry->OutputLocation));
if(HTML.Buffer.Location)
{
if(!(HTML.Handle = fopen(HTML.Path, "w"))) { FreeFile(&HTML); return RC_ERROR_FILE; };
if(!(HTML.Handle = fopen(HTML.Path, "w"))) { FreeFile(&HTML, NA); return RC_ERROR_FILE; };
fwrite(HTML.Buffer.Location, Entry->LinkOffsets.PrevStart, 1, HTML.Handle);
fwrite(HTML.Buffer.Location + Entry->LinkOffsets.PrevStart + Entry->LinkOffsets.PrevEnd, Entry->LinkOffsets.NextStart, 1, HTML.Handle);
fwrite(HTML.Buffer.Location + Entry->LinkOffsets.PrevStart + Entry->LinkOffsets.PrevEnd + Entry->LinkOffsets.NextStart + Entry->LinkOffsets.NextEnd,
HTML.Buffer.Size - (Entry->LinkOffsets.PrevStart + Entry->LinkOffsets.PrevEnd + Entry->LinkOffsets.NextStart + Entry->LinkOffsets.NextEnd),
1,
HTML.Handle);
fclose(HTML.Handle);
CloseFile(&HTML, NA);
Entry->LinkOffsets.PrevEnd = 0;
Entry->LinkOffsets.NextEnd = 0;
if(N->Prev && N->PrevIndex >= 0)
@ -13262,7 +13344,7 @@ DeleteNeighbourLinks(neighbourhood *N)
}
}
FreeFile(&HTML);
FreeFile(&HTML, NA);
return RC_SUCCESS;
}
@ -13331,7 +13413,7 @@ MarkNextAsFirst(neighbourhood *N)
N->Next->LinkOffsets.PrevEnd = Link.Ptr - Link.Location;
DeclaimBuffer(&Link);
fclose(HTML.Handle);
CloseFile(&HTML, NA);
}
else
{
@ -13339,7 +13421,7 @@ MarkNextAsFirst(neighbourhood *N)
N->Next->LinkOffsets = Blank;
}
FreeFile(&HTML);
FreeFile(&HTML, NA);
}
void
@ -13369,7 +13451,7 @@ MarkPrevAsFinal(neighbourhood *N)
N->Prev->LinkOffsets.NextEnd = Link.Ptr - Link.Location;
DeclaimBuffer(&Link);
fclose(HTML.Handle);
CloseFile(&HTML, NA);
}
else
{
@ -13377,7 +13459,7 @@ MarkPrevAsFinal(neighbourhood *N)
N->Prev->LinkOffsets = Blank;
}
FreeFile(&HTML);
FreeFile(&HTML, NA);
}
void
@ -13534,7 +13616,7 @@ DeleteFromDB(neighbourhood *N, string BaseFilename, db_entry *Deceased)
}
N->Project->EntryCount = NewEntryCount;
if(!(DB.Metadata.File.Handle = fopen(DB.Metadata.File.Path, "w"))) { FreeBuffer(&DB.Metadata.File.Buffer); return RC_ERROR_FILE; }
if(!(OpenFileForWriting(&DB.Metadata.File))) { FreeBuffer(&DB.Metadata.File.Buffer); return RC_ERROR_FILE; }
char *Ptr = (char *)N->This;
uint64_t BytesIntoFile = Ptr - DB.Metadata.File.Buffer.Location;
@ -14560,7 +14642,7 @@ SetCurrentProject(project *P, neighbourhood *N)
{
if(CurrentProject != P)
{
FreeFile(&DB.File);
FreeFile(&DB.File, NA);
if(CurrentProject)
{
@ -14608,7 +14690,6 @@ RecheckPrivacyRecursively(project *P, neighbourhood *N, buffers *CollationBuffer
DeleteStaleAssets();
UpdateNeighbourhoodPointers(N, &DB.Metadata.Signposts);
}
}
for(int i = 0; i < P->Child.ItemCount; ++i)
@ -14723,7 +14804,7 @@ UpgradeDB(int OriginalDBVersion)
ClearCopyStringNoFormat(DB.AssetsBlock.JSDir, sizeof(DB.AssetsBlock.JSDir), Config->JSDir);
++DB.Header.BlockCount;
DB.Metadata.File.Handle = fopen(DB.Metadata.File.Path, "w");
OpenFileForWriting(&DB.Metadata.File);
fwrite(&DB.Header, sizeof(DB.Header), 1, DB.Metadata.File.Handle);
DB.Metadata.Signposts.ProjectsBlock.Byte = ftell(DB.Metadata.File.Handle);
@ -14829,7 +14910,7 @@ DeleteDeadDBEntries(neighbourhood *N, bool *Modified)
Free(NewSearchDirectory);
FreeFile(&DB.File);
FreeFile(&DB.File, NA);
DB.File.Path = ConstructIndexFilePath(CurrentProject->BaseDir, CurrentProject->SearchLocation, CurrentProject->ID);
ReadFileIntoBuffer(&DB.File); // NOTE(matt): Could we actually catch errors (permissions?) here and bail?
@ -14847,7 +14928,7 @@ DeleteDeadDBEntries(neighbourhood *N, bool *Modified)
if(StringsDiffer(CurrentProject->BaseDir, Wrap0i(N->Project->BaseDir)))
{
ClearCopyStringNoFormat(N->Project->BaseDir, sizeof(N->Project->BaseDir), CurrentProject->BaseDir);
DB.Metadata.File.Handle = fopen(DB.Metadata.File.Path, "w");
OpenFileForWriting(&DB.Metadata.File);
WriteFromByteToEnd(&DB.Metadata.File, 0);
CycleSignpostedFile(&DB.Metadata);
UpdateNeighbourhoodPointers(N, &DB.Metadata.Signposts);
@ -14857,7 +14938,7 @@ DeleteDeadDBEntries(neighbourhood *N, bool *Modified)
if(StringsDiffer(CurrentProject->BaseURL, Wrap0i(N->Project->BaseURL)))
{
ClearCopyStringNoFormat(N->Project->BaseURL, sizeof(N->Project->BaseURL), CurrentProject->BaseURL);
DB.Metadata.File.Handle = fopen(DB.Metadata.File.Path, "w");
OpenFileForWriting(&DB.Metadata.File);
WriteFromByteToEnd(&DB.Metadata.File, 0);
CycleSignpostedFile(&DB.Metadata);
UpdateNeighbourhoodPointers(N, &DB.Metadata.Signposts);
@ -14866,7 +14947,7 @@ DeleteDeadDBEntries(neighbourhood *N, bool *Modified)
if(HasNewPlayerLocation || HasNewSearchLocation)
{
if(!(DB.Metadata.File.Handle = fopen(DB.Metadata.File.Path, "w"))) { FreeBuffer(&DB.Metadata.File.Buffer); return RC_ERROR_FILE; }
if(!(OpenFileForWriting(&DB.Metadata.File))) { FreeBuffer(&DB.Metadata.File.Buffer); return RC_ERROR_FILE; }
ClearCopyStringNoFormat(N->Project->BaseDir, sizeof(N->Project->BaseDir), CurrentProject->BaseDir);
ClearCopyStringNoFormat(N->Project->BaseURL, sizeof(N->Project->BaseURL), CurrentProject->BaseURL);
fwrite(DB.Metadata.File.Buffer.Location, DB.Metadata.File.Buffer.Size, 1, DB.Metadata.File.Handle);
@ -14942,7 +15023,7 @@ SyncDBWithInput(neighbourhood *N, buffers *CollationBuffers, template *BespokeTe
if(StringsDiffer(CurrentProject->Title, Wrap0i(N->Project->Title)))
{
ClearCopyStringNoFormat(N->Project->Title, sizeof(N->Project->Title), CurrentProject->Title);
DB.Metadata.File.Handle = fopen(DB.Metadata.File.Path, "w");
OpenFileForWriting(&DB.Metadata.File);
WriteFromByteToEnd(&DB.Metadata.File, 0);
CycleSignpostedFile(&DB.Metadata);
UpdateNeighbourhoodPointers(N, &DB.Metadata.Signposts);
@ -14952,7 +15033,7 @@ SyncDBWithInput(neighbourhood *N, buffers *CollationBuffers, template *BespokeTe
if(StringsDiffer(CurrentProject->Theme, Wrap0i(N->Project->Theme)))
{
ClearCopyStringNoFormat(N->Project->Theme, sizeof(N->Project->Theme), CurrentProject->Theme);
DB.Metadata.File.Handle = fopen(DB.Metadata.File.Path, "w");
OpenFileForWriting(&DB.Metadata.File);
WriteFromByteToEnd(&DB.Metadata.File, 0);
CycleSignpostedFile(&DB.Metadata);
UpdateNeighbourhoodPointers(N, &DB.Metadata.Signposts);
@ -14962,7 +15043,7 @@ SyncDBWithInput(neighbourhood *N, buffers *CollationBuffers, template *BespokeTe
if(StringsDiffer(CurrentProject->Numbering.Unit, Wrap0i(N->Project->Unit)))
{
ClearCopyStringNoFormat(N->Project->Unit, sizeof(N->Project->Unit), CurrentProject->Numbering.Unit);
DB.Metadata.File.Handle = fopen(DB.Metadata.File.Path, "w");
OpenFileForWriting(&DB.Metadata.File);
WriteFromByteToEnd(&DB.Metadata.File, 0);
CycleSignpostedFile(&DB.Metadata);
UpdateNeighbourhoodPointers(N, &DB.Metadata.Signposts);
@ -15082,16 +15163,6 @@ InitAccumulator(project_generations *G)
return Result;
}
void
WriteEntireDatabase()
{
// NOTE(matt): This may suffice when the only changes are to existing fixed-size variables,
// e.g. ProjectsBlock->GlobalSearchDir
DB.Metadata.File.Handle = fopen(DB.Metadata.File.Path, "w");
fwrite(DB.Metadata.File.Buffer.Location, DB.Metadata.File.Buffer.Size, 1, DB.Metadata.File.Handle);
fclose(DB.Metadata.File.Handle);
}
rc
InitDB(void)
{
@ -15101,9 +15172,15 @@ InitDB(void)
// need a separate InitIndex() function that we can call when looping over the projects after ParseConfig()
DB.Metadata.File.Buffer.ID = BID_DATABASE;
DB.Metadata.File = InitFile(0, &Config->DatabaseLocation, EXT_NULL);
ReadFileIntoBuffer(&DB.Metadata.File); // NOTE(matt): Could we actually catch errors (permissions?) here and bail?
DB.Metadata.File = InitFile(0, &Config->DatabaseLocation, EXT_NULL, TRUE);
if(ReadFileIntoBuffer(&DB.Metadata.File) == RC_ERROR_FILE_LOCKED) // NOTE(matt): Could we actually catch errors (permissions?) here and bail?
{
ConfigErrorLockedDBLocation(0, 0, &Config->DatabaseLocation);
CloseFile(&DB.Metadata.File, TRUE);
Result = RC_ERROR_FILE_LOCKED;
}
else
{
if(DB.Metadata.File.Buffer.Location)
{
// TODO(matt): Handle this gracefully (it'll be an invalid file)
@ -15169,12 +15246,12 @@ InitDB(void)
}
}
DB.Metadata.File.Handle = fopen(DB.Metadata.File.Path, "w");
if(DB.Metadata.File.Handle)
if(OpenFileForWriting(&DB.Metadata.File))
{
fwrite(B->Location, B->Size, 1, DB.Metadata.File.Handle);
SetFileEditPosition(&DB.Metadata);
CycleSignpostedFile(&DB.Metadata);
DB.Ready = TRUE;
}
else
{
@ -15233,9 +15310,7 @@ InitDB(void)
if(Result == RC_SUCCESS)
{
DB.Metadata.File.Handle = fopen(DB.Metadata.File.Path, "w");
if(DB.Metadata.File.Handle)
if(OpenFileForWriting(&DB.Metadata.File))
{
fwrite(&DB.Header, sizeof(DB.Header), 1, DB.Metadata.File.Handle);
@ -15245,10 +15320,11 @@ InitDB(void)
DB.Metadata.Signposts.AssetsBlock.Byte = ftell(DB.Metadata.File.Handle);
fwrite(&DB.AssetsBlock, sizeof(DB.AssetsBlock), 1, DB.Metadata.File.Handle);
fclose(DB.Metadata.File.Handle);
CloseFile(&DB.Metadata.File, FALSE);
ReadFileIntoBuffer(&DB.Metadata.File);
DB.Metadata.Signposts.ProjectsBlock.Ptr = DB.Metadata.File.Buffer.Location + DB.Metadata.Signposts.ProjectsBlock.Byte;
DB.Metadata.Signposts.AssetsBlock.Ptr = DB.Metadata.File.Buffer.Location + DB.Metadata.Signposts.AssetsBlock.Byte;
DB.Ready = TRUE;
}
else
{
@ -15262,11 +15338,12 @@ InitDB(void)
#if 0
DB.File.Handle = fopen(DB.File.Path, "w");
fprintf(DB.File.Handle, "---\n");
fclose(DB.File.Handle);
CloseFile(&DB.File, NA);
ReadFileIntoBuffer(&DB.File);
#endif
}
}
}
return Result;
}
@ -15407,7 +15484,7 @@ InsertProjectIntoDB_TopLevel(project_generations *G, db_block_projects **Block,
{
uint64_t Byte = 0;
DB.Metadata.File.Handle = fopen(DB.Metadata.File.Path, "w");
OpenFileForWriting(&DB.Metadata.File);
WriteFromByteToPointer(&DB.Metadata.File, &Byte, *Block);
db_block_projects NewBlock = **Block;
@ -15431,7 +15508,7 @@ InsertProjectIntoDB(project_generations *G, db_header_project **Parent, db_heade
{
uint64_t Byte = 0;
DB.Metadata.File.Handle = fopen(DB.Metadata.File.Path, "w");
OpenFileForWriting(&DB.Metadata.File);
WriteFromByteToPointer(&DB.Metadata.File, &Byte, *Parent);
db_header_project NewParent = **Parent;
@ -15633,7 +15710,7 @@ DeleteProject_TopLevel(db_block_projects **Parent, db_header_project **Child, pr
uint64_t PPos = (char *)*Parent - DB.Metadata.File.Buffer.Location;
uint64_t CPos = (char *)*Child - DB.Metadata.File.Buffer.Location;
DB.Metadata.File.Handle = fopen(DB.Metadata.File.Path, "w");
OpenFileForWriting(&DB.Metadata.File);
uint64_t Byte = 0;
WriteFromByteToPointer(&DB.Metadata.File, &Byte, *Parent);
@ -15663,7 +15740,7 @@ DeleteProject(db_header_project **Parent, db_header_project **Child, project_gen
uint64_t CPos = (char *)*Child - DB.Metadata.File.Buffer.Location;
//db_project_index Index = GetCurrentProjectIndex(G);
DB.Metadata.File.Handle = fopen(DB.Metadata.File.Path, "w");
OpenFileForWriting(&DB.Metadata.File);
uint64_t Byte = 0;
WriteFromByteToPointer(&DB.Metadata.File, &Byte, *Parent);
@ -15723,7 +15800,7 @@ ReorganiseProjectsInterior(project_generations *G, project *CChild, uint64_t Chi
project_generations ThisAcc = InitAccumulator(G);
AccumulateProjectIndices(&ThisAcc, This);
DB.Metadata.File.Handle = fopen(DB.Metadata.File.Path, "w");
OpenFileForWriting(&DB.Metadata.File);
uint64_t Byte = 0;
WriteFromByteToPointer(&DB.Metadata.File, &Byte, SChild);
@ -16036,13 +16113,13 @@ SyncGlobalPagesWithInput(neighbourhood *N, buffers *CollationBuffers)
if(StringsDiffer(StoredGlobalSearchDir, Config->GlobalSearchDir))
{
ClearCopyStringNoFormat(ProjectsBlock->GlobalSearchDir, sizeof(ProjectsBlock->GlobalSearchDir), Config->GlobalSearchDir);
WriteEntireDatabase();
WriteEntireDatabase(N);
}
if(StringsDiffer(StoredGlobalSearchURL, Config->GlobalSearchURL))
{
ClearCopyStringNoFormat(ProjectsBlock->GlobalSearchURL, sizeof(ProjectsBlock->GlobalSearchURL), Config->GlobalSearchURL);
WriteEntireDatabase();
WriteEntireDatabase(N);
}
if(!ProjectsBlock->GlobalSearchDir[0])
@ -16350,6 +16427,15 @@ RemoveAndFreeWatchHandles(watch_handles *W)
{
watch_handle *This = GetPlaceInBook(&W->Handles, i);
inotify_rm_watch(inotifyInstance, This->Descriptor);
for(int j = 0; j < This->Files.ItemCount; ++j)
{
watch_file *File = GetPlaceInBook(&This->Files, j);
if(File->Handle)
{
fclose(File->Handle);
File->Handle = 0;
}
}
FreeBook(&This->Files);
}
FreeAndReinitialiseBook(&W->Handles);
@ -16359,8 +16445,9 @@ RemoveAndFreeWatchHandles(watch_handles *W)
void
DiscardAllAndFreeConfig(void)
{
FreeSignpostedFile(&DB.Metadata); // NOTE(matt): This seems fine
FreeFile(&DB.File); // NOTE(matt): This seems fine
FreeSignpostedFile(&DB.Metadata, TRUE); // NOTE(matt): This seems fine
FreeFile(&DB.File, NA); // NOTE(matt): This seems fine
DB.Ready = FALSE;
FreeAssets(&Assets); // NOTE(matt): This seems fine
RemoveAndFreeWatchHandles(&WatchHandles);
CurrentProject = 0; // NOTE(matt): This is fine
@ -16572,6 +16659,8 @@ MonitorFilesystem(neighbourhood *N, buffers *CollationBuffers, template *Bespoke
switch(WatchFile->Type)
{
case WT_HMML:
{
if(DB.Ready)
{
SetCurrentProject(WatchFile->Project, N);
string BaseFilename = GetBaseFilename(Wrap0(Event->name), WatchFile->Extension);
@ -16583,14 +16672,18 @@ MonitorFilesystem(neighbourhood *N, buffers *CollationBuffers, template *Bespoke
{
Inserted |= (InsertEntry(N, CollationBuffers, BespokeTemplate, BaseFilename, 0) == RC_SUCCESS);
}
}
} break;
case WT_ASSET:
{
if(DB.Ready)
{
UpdateAsset(WatchFile->Asset, FALSE);
UpdateNeighbourhoodPointers(N, &DB.Metadata.Signposts);
#if DEBUG_LANDMARKS
UpdatedAsset = TRUE;
#endif
}
} break;
case WT_CONFIG:
{
@ -16749,7 +16842,6 @@ main(int ArgC, char **Args)
PushWatchHandle(ConfigPathL, EXT_NULL, WT_CONFIG, 0, 0);
Config = ParseConfig(ConfigPathL, &TokensList);
rc Succeeding = RC_SUCCESS;
if(Config)
{
if(Mode & MODE_EXAMINE)
@ -16766,7 +16858,7 @@ main(int ArgC, char **Args)
else
{
/* */ MEM_TEST_MID("main()");
/* +MEM */ Succeeding = InitAll(&Neighbourhood, &CollationBuffers, &BespokeTemplate);
/* +MEM */ InitAll(&Neighbourhood, &CollationBuffers, &BespokeTemplate);
/* */ MEM_TEST_MID("main()");
}
}
@ -16782,8 +16874,6 @@ main(int ArgC, char **Args)
}
}
if(Succeeding == RC_SUCCESS)
{
if(inotifyInstance != -1)
{
while(GlobalRunning && MonitorFilesystem(&Neighbourhood, &CollationBuffers, &BespokeTemplate, ConfigPathL, &TokensList) != RC_ARENA_FULL)
@ -16798,7 +16888,7 @@ main(int ArgC, char **Args)
// 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_DRYRUN) && DB.Ready && Config && Config->RespectingPrivacy && time(0) - LastPrivacyCheck > Config->PrivacyCheckInterval)
{
RecheckPrivacy(&Neighbourhood, &CollationBuffers, &BespokeTemplate);
}
@ -16812,9 +16902,7 @@ main(int ArgC, char **Args)
}
DiscardAllAndFreeConfig();
RemoveAndFreeWatchHandles(&WatchHandles);
MEM_TEST_END("main()");
}
}
Exit();
}

View File

@ -8,12 +8,6 @@ exit
// config
//
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdarg.h>
#define ArgCountPointer(...) (sizeof((void *[]){__VA_ARGS__})/sizeof(void *))
#define DigitsInInt(I) DigitsInInt_(I, sizeof(*I))
@ -348,6 +342,15 @@ PushTokens(memory_book *TokensList)
return This;
}
void
PopTokens(memory_book *TokensList)
{
--TokensList->ItemCount;
tokens *This = GetPlaceInBook(TokensList, TokensList->ItemCount);
FreeBook(&This->Token);
This->CurrentLine = 0;
}
void
FreeTokensList(memory_book *TokensList)
{
@ -357,7 +360,8 @@ FreeTokensList(memory_book *TokensList)
This->CurrentIndex = 0;
This->CurrentLine = 0;
FreeBook(&This->Token);
FreeFile(&This->File);
FreeFileBufferAndPath(&This->File); // NOTE(matt): Not closing file because the next time we touch this handle is in
// RemoveAndFreeWatchHandles() where we will close it
}
TokensList->ItemCount = 0;
}
@ -588,22 +592,36 @@ typedef struct
} config;
char *ExpandPath(string Path, string *RelativeToFile); // NOTE(matt): Forward declared. Consider reorganising the code?
void PushWatchHandle(string Path, extension_id Extension, watch_type Type, project *Project, asset *Asset); // NOTE(matt): Forward declared. Consider reorganising the code?
watch_file *PushWatchHandle(string Path, extension_id Extension, watch_type Type, project *Project, asset *Asset); // NOTE(matt): Forward declared. Consider reorganising the code?
tokens *
Tokenise(memory_book *TokensList, string Path)
Tokenise(memory_book *TokensList, string Path, string *Filename, uint64_t LineNumber)
{
tokens *Result = 0;
char *Path0 = MakeString0("l", &Path);
FILE *Handle = 0;
PushWatchHandle(Path, EXT_NULL, WT_CONFIG, 0, 0);
if((Handle = fopen(Path0, "r")))
{
fclose(Handle);
watch_file *WatchFile = PushWatchHandle(Path, EXT_NULL, WT_CONFIG, 0, 0);
Result = PushTokens(TokensList);
Result->File = InitFile(0, &Path, EXT_NULL);
ReadFileIntoBuffer(&Result->File);
Result->File = InitFile(0, &Path, EXT_NULL, TRUE);
switch(ReadFileIntoBuffer(&Result->File))
{
case RC_ERROR_FILE_LOCKED:
{
ConfigErrorLockedConfigLocation(Filename, LineNumber, &Path);
FreeFile(&Result->File, TRUE);
PopTokens(TokensList);
Result = 0;
} break;
case RC_ERROR_FILE:
{
ConfigErrorUnopenableConfigLocation(Filename, LineNumber, &Path);
FreeFile(&Result->File, TRUE);
PopTokens(TokensList);
Result = 0;
} break;
default:
{
WatchFile->Handle = Result->File.Handle;
buffer *B = &Result->File.Buffer;
SkipWhitespace(Result, B);
while(B->Ptr - B->Location < B->Size)
@ -770,12 +788,8 @@ Tokenise(memory_book *TokensList, string Path)
Advance(B, Advancement);
SkipWhitespace(Result, B);
}
} break;
}
else
{
ConfigError(0, 0, S_WARNING, "Unable to open config file: ", &Path);
}
Free(Path0);
return Result;
}
@ -2341,7 +2355,8 @@ ScopeTokens(scope_tree *Tree, memory_book *TokensList, tokens *T, memory_book *T
}
if(!I)
{
I = Tokenise(TokensList, IncludePathL);
token *This = GetPlaceInBook(&T->Token, IncludePathTokenIndex);
I = Tokenise(TokensList, IncludePathL, &Filepath, This->LineNumber);
}
if(I)
{
@ -2352,11 +2367,6 @@ ScopeTokens(scope_tree *Tree, memory_book *TokensList, tokens *T, memory_book *T
return 0;
}
}
else
{
token *This = GetPlaceInBook(&T->Token, IncludePathTokenIndex);
ConfigFileIncludeError(&Filepath, This->LineNumber, Wrap0(IncludePath));
}
Free(IncludePath);
}
else
@ -4977,7 +4987,7 @@ ParseConfig(string Path, memory_book *TokensList)
MEM_LOOP_POST("InitTypeSpecs")
#endif
config *Result = 0;
tokens *T = Tokenise(TokensList, Path);
tokens *T = Tokenise(TokensList, Path, NA, NA);
#if 0
MEM_LOOP_PRE_FREE("Tokenise")
@ -4993,7 +5003,7 @@ ParseConfig(string Path, memory_book *TokensList)
scope_tree *ScopeTree = InitRootScopeTree();
SetTypeSpec(ScopeTree, &TypeSpecs);
SetDefaults(ScopeTree, &TypeSpecs);
ScopeTree = ScopeTokens(ScopeTree, TokensList, T, &TypeSpecs, 0);
ScopeTree = ScopeTokens(ScopeTree, TokensList, T, &TypeSpecs, NA);
// TODO(matt): Mem testing
//