From 910ae5a99d7aeee8c8feac92d9a08e55717fa2c3 Mon Sep 17 00:00:00 2001 From: Ilia Ross Date: Thu, 8 Aug 2024 15:44:52 +0300 Subject: [PATCH] Add better support in case of a race condition https://github.com/webmin/authentic-theme/commit/c3098fe01a8cd4c9060c6af5c514245f5904a377#r145157572 --- stats-lib-funcs.pl | 83 +++++++++++++++++++++++++++++----------------- 1 file changed, 52 insertions(+), 31 deletions(-) diff --git a/stats-lib-funcs.pl b/stats-lib-funcs.pl index 1488af9b6..bfd508b2b 100644 --- a/stats-lib-funcs.pl +++ b/stats-lib-funcs.pl @@ -5,7 +5,7 @@ # use strict; use feature 'state'; -use Fcntl ':flock'; +use Fcntl qw(:flock O_RDWR O_CREAT); our $json; eval "use JSON::XS"; @@ -78,38 +78,59 @@ sub stats_read_file_contents { # Write file contents sub stats_write_file_contents { my ($filename, $contents) = @_; - # Open file for writing - my $fh; - if (!open($fh, '>', $filename)) { - error_stderr("Failed to open file '$filename' for writing: $!"); - return 0; - } - # Acquire lock the file for writing - if (!flock($fh, LOCK_EX)) { - error_stderr("Failed to acquire exclusive lock on file '$filename': $!"); - close($fh); + my $cleanup = sub { + my ($fh, $unlock, $success) = @_; + # Unlock the file. Not strictly necessary as Perl + # automatically releases locks on file closure + flock($fh, LOCK_UN) if ($unlock); + # Close file handle + close($fh) + or do { + error_stderr("Failed to close file '$filename': $!"); + undef($fh); + return 0; + }; + # Release memory. Generally not necessary as Perl's garbage collection + # should always handle this. However, it doesn't hurt and can be + # useful in certain scenarios, like long-running processes undef($fh); - return 0; - } + # Return result + return $success ? 1 : 0; + }; + # Open file for reading and writing without truncating it, or + # create it if it doesn't exist + sysopen(my $fh, $filename, O_RDWR | O_CREAT) + or do { + error_stderr("Failed to open file '$filename' for reading and writing: $!"); + return 0; + }; + # Acquire exclusive lock on the file for writing + flock($fh, LOCK_EX) + or do { + error_stderr("Failed to acquire exclusive lock on file '$filename': $!"); + return $cleanup->($fh, 0); + }; + # Truncate the file after acquiring the lock + truncate($fh, 0) + or do { + error_stderr("Failed to truncate file '$filename': $!"); + return $cleanup->($fh, 1); + }; + # Reset the file pointer to the beginning of the file + # preventing potential race conditions + seek($fh, 0, 0) + or do { + error_stderr("Failed to seek to the beginning of file '$filename': $!"); + return $cleanup->($fh, 1); + }; # Write to file - if (!print($fh $contents)) { - error_stderr("Failed to write to file '$filename': $!"); - close($fh); - undef($fh); - return 0; - } - # Unlock the file - flock($fh, LOCK_UN); - # Close file - if (!close($fh)) { - error_stderr("Failed to close file '$filename': $!"); - undef($fh); - return 0; - } - # Release memory - undef($fh); - # Return success - return 1; + print($fh $contents) + or do { + error_stderr("Failed to write to file '$filename': $!"); + return $cleanup->($fh, 1); + }; + # Clean up and return success + return $cleanup->($fh, 1, 1); } # Check if feature is enabled