Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make config file saving safe against write failures #34009

Merged
merged 1 commit into from
Sep 15, 2022
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 29 additions & 25 deletions lib/private/Config.php
Original file line number Diff line number Diff line change
Expand Up @@ -224,11 +224,6 @@ private function readData() {
continue;
}

// Try to acquire a file lock
if (!flock($filePointer, LOCK_SH)) {
throw new \Exception(sprintf('Could not acquire a shared lock on the config file %s', $file));
}

unset($CONFIG);
include $file;
if (!defined('PHPUNIT_RUN') && headers_sent()) {
Expand All @@ -244,7 +239,6 @@ private function readData() {
}

// Close the file pointer and release the lock
flock($filePointer, LOCK_UN);
fclose($filePointer);
}

Expand All @@ -256,8 +250,7 @@ private function readData() {
*
* Saves the config to the config file.
*
* @throws HintException If the config file cannot be written to
* @throws \Exception If no file lock can be acquired
* @throws HintException If the config file cannot be written to, etc
*/
private function writeData() {
$this->checkReadOnly();
Expand All @@ -268,30 +261,41 @@ private function writeData() {
$content .= var_export($this->cache, true);
$content .= ";\n";

touch($this->configFilePath);
$filePointer = fopen($this->configFilePath, 'r+');

// Prevent others not to read the config
chmod($this->configFilePath, 0640);
// tmpfile must be in the same filesystem for the rename() to be atomic
$tmpfile = tempnam(dirname($this->configFilePath), 'config.php.tmp.');
// dirname check is for PHP's fallback quirk
if (!$tmpfile || dirname($tmpfile) != dirname($this->configFilePath)) {
come-nc marked this conversation as resolved.
Show resolved Hide resolved
if ($tmpfile) {
unlink($tmpfile);
}
throw new HintException(
"Can't create temporary file in config directory!",
'This can usually be fixed by giving the webserver write access to the config directory.');
}

// File does not exist, this can happen when doing a fresh install
chmod($tmpfile, 0640);
$filePointer = fopen($tmpfile, 'w');
if (!is_resource($filePointer)) {
throw new HintException(
"Can't write into config directory!",
'This can usually be fixed by giving the webserver write access to the config directory.');
"Failed to open temporary file in config directory for writing",
'Please report this to Nextcloud developers.');
}

// Try to acquire a file lock
if (!flock($filePointer, LOCK_EX)) {
throw new \Exception(sprintf('Could not acquire an exclusive lock on the config file %s', $this->configFilePath));
$write_ok = fwrite($filePointer, $content);
$close_ok = fclose($filePointer);
if (!$write_ok || !$close_ok) {
unlink($tmpfile);
throw new HintException(
"Failed to save temporary file in config directory",
'Please report this to Nextcloud developers.');
}

// Write the config and release the lock
ftruncate($filePointer, 0);
fwrite($filePointer, $content);
fflush($filePointer);
flock($filePointer, LOCK_UN);
fclose($filePointer);
if (!rename($tmpfile, $this->configFilePath)) {
unlink($tmpfile);
throw new HintException(
"Failed to replace the config file with the new copy",
'Please report this to Nextcloud developers.');
}

if (function_exists('opcache_invalidate')) {
@opcache_invalidate($this->configFilePath, true);
Expand Down