From f2753994a8eb54c002cb7537b3bbbada2ba6c841 Mon Sep 17 00:00:00 2001 From: jessefaler Date: Fri, 9 Jan 2026 22:32:29 -0600 Subject: [PATCH] Fix race condition in config file parsing Add file locking and syncing to prevent server processes from reading config files while they're being updated. Also document that json.Marshal(config.Get()) is used to resolve {{config.*}} placeholders. --- parser/parser.go | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/parser/parser.go b/parser/parser.go index e7c98b3b2..8f4814490 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -14,6 +14,7 @@ import ( "github.com/buger/jsonparser" "github.com/icza/dyno" "github.com/magiconair/properties" + "golang.org/x/sys/unix" "gopkg.in/ini.v1" "gopkg.in/yaml.v3" @@ -191,9 +192,23 @@ func (cfr *ConfigurationFileReplacement) UnmarshalJSON(data []byte) error { // Parse parses a given configuration file and updates all the values within // as defined in the API response from the Panel. func (f *ConfigurationFile) Parse(file ufs.File) error { - // log.WithField("path", path).WithField("parser", f.Parser.String()).Debug("parsing server configuration file") + // Acquire an exclusive lock on the file to prevent race conditions + // where the server process might read the file while we're updating it. + fd := file.Fd() + if err := unix.Flock(int(fd), unix.LOCK_EX); err != nil { + return errors.Wrap(err, "parser: failed to acquire exclusive lock on configuration file") + } + // Ensure we unlock the file when we're done + defer func() { + if unlockErr := unix.Flock(int(fd), unix.LOCK_UN); unlockErr != nil { + log.WithError(unlockErr).WithField("file_name", f.FileName).Warn("failed to unlock configuration file") + } + }() - // What the fuck is going on here? + // Marshal the daemon configuration to JSON for resolving {{config.*}} placeholders. + // This JSON representation is used by LookupConfigurationValue to look up nested + // configuration values (e.g., {{config.docker.interface}}) when processing config + // file replacements. if mb, err := json.Marshal(config.Get()); err != nil { return err } else { @@ -216,6 +231,7 @@ func (f *ConfigurationFile) Parse(file ufs.File) error { case Xml: err = f.parseXmlFile(file) } + return err }