Config, add more pessimistic locking option.

We usually only lock on write, but when data has a high posibility of changing in between reads, we should have an option to lock for writes exclusively.

needed for https://github.com/opnsense/core/issues/3062
This commit is contained in:
Ad Schellevis 2018-12-21 12:09:26 +01:00
parent d27cc83642
commit 5d0d4e832e

View File

@ -44,6 +44,12 @@ class Config extends Singleton
*/
private $config_file = "";
/**
* config file handle
* @var null|file
*/
private $config_file_handle = null;
/**
* SimpleXML type reference to config
* @var SimpleXML
@ -157,7 +163,9 @@ class Config extends Singleton
*/
public function toArrayFromFile($filename, $forceList = null)
{
$xml = $this->loadFromFile($filename);
$fp = fopen($filename, "r");
$xml = $this->loadFromStream($fp);
fclose($fp);
return $this->toArray($forceList, $xml);
}
@ -298,18 +306,15 @@ class Config extends Singleton
}
/**
* load xml config from file
* @param string $filename config xml source
* load xml config from file handle
* @param file $fp config xml source
* @return \SimpleXMLElement root node
* @throws ConfigException when config could not be parsed
*/
private function loadFromFile($filename)
private function loadFromStream($fp)
{
// exception handling
if (!file_exists($filename)) {
throw new ConfigException('file not found');
}
$xml = file_get_contents($filename);
fseek($fp, 0);
$xml = stream_get_contents($fp);
if (trim($xml) == '') {
throw new ConfigException('empty file');
}
@ -339,7 +344,17 @@ class Config extends Singleton
{
$this->simplexml = null;
$this->statusIsValid = false;
$this->simplexml = $this->loadFromFile($this->config_file);
// exception handling
if (!file_exists($this->config_file)) {
throw new ConfigException('file not found');
}
if (!is_resource($this->config_file_handle)) {
$this->config_file_handle = fopen($this->config_file, "r+");
}
$this->simplexml = $this->loadFromStream($this->config_file_handle);
$this->statusIsValid = true;
}
@ -488,6 +503,7 @@ class Config extends Singleton
if ($this->isValid()) {
// if current config is valid,
$simplexml = $this->simplexml;
$config_file_handle = $this->config_file_handle;
try {
// try to restore config
copy($filename, $this->config_file);
@ -496,6 +512,7 @@ class Config extends Singleton
} catch (ConfigException $e) {
// copy / load failed, restore previous version
$this->simplexml = $simplexml;
$this->config_file_handle = $config_file_handle;
$this->statusIsValid = true;
$this->save(null, true);
return false;
@ -528,25 +545,54 @@ class Config extends Singleton
// serialize to text
$xml_text = $this->__toString();
// save configuration, try to obtain a lock before doing so.
$target_filename = $this->config_file;
if (file_exists($target_filename)) {
$fp = fopen($target_filename, "r+");
} else {
// apparently we are missing the config, not expected but open a new one.
$fp = fopen($target_filename, "w+");
}
if (flock($fp, LOCK_EX)) {
// lock aquired, truncate and write new data
ftruncate($fp, 0);
fwrite($fp, $xml_text);
// flush, unlock and close file handler
fflush($fp);
flock($fp, LOCK_UN);
fclose($fp);
} else {
throw new ConfigException("Unable to lock config");
if ($this->config_file_handle !== null) {
if (flock($this->config_file_handle, LOCK_EX)) {
fseek($this->config_file_handle, 0);
ftruncate($this->config_file_handle, 0);
fwrite($this->config_file_handle, $xml_text);
// flush, unlock, but keep the handle open
fflush($this->config_file_handle);
flock($this->config_file_handle, LOCK_UN);
} else {
throw new ConfigException("Unable to lock config");
}
}
}
/**
* cleanup, close file handle
*/
public function __destruct ()
{
if ($this->config_file_handle !== null) {
fclose($this->config_file_handle);
}
}
/**
* lock configuration
* @param boolean $reload reload config from open file handle to enforce synchronicity
*/
public function lock($reload=true)
{
if ($this->config_file_handle !== null) {
flock($this->config_file_handle, LOCK_EX);
if ($reload) {
$this->load();
}
}
return $this;
}
/**
* unlock configuration
*/
public function unlock()
{
if (is_resource($this->config_file_handle)) {
flock($this->config_file_handle, LOCK_UN);
}
return $this;
}
}