From a739e5dacb1b6701e9aee0ffae04e2d047231f38 Mon Sep 17 00:00:00 2001 From: Andreas Bachlechner <62039342+bachandi@users.noreply.github.com> Date: Wed, 23 Feb 2022 11:55:15 +0100 Subject: [PATCH 1/4] Add rrd locking. - Add an enable_rrd_lock config option to enable it. - It creates a lock file and uses it via flock when reading and writing rrd files. --- lib/Monitorix.pm | 25 ++++++++++++++++++++++++- man/man5/monitorix.conf.5 | 7 +++++++ monitorix | 6 ++++++ monitorix.cgi | 4 ++++ monitorix.conf | 1 + 5 files changed, 42 insertions(+), 1 deletion(-) diff --git a/lib/Monitorix.pm b/lib/Monitorix.pm index 1aa37af..82e7c68 100644 --- a/lib/Monitorix.pm +++ b/lib/Monitorix.pm @@ -24,8 +24,9 @@ use strict; use warnings; use Exporter 'import'; use POSIX qw(setuid setgid setsid getgid getuid ceil); +use Fcntl qw(:flock); use Socket; -our @EXPORT = qw(logger trim min max celsius_to img_element picz_a_element picz_js_a_element uptime2str setup_riglim httpd_setup get_nvidia_data get_ati_data flush_accounting_rules); +our @EXPORT = qw(logger trim min max celsius_to img_element picz_a_element picz_js_a_element uptime2str setup_riglim httpd_setup get_nvidia_data get_ati_data flush_accounting_rules lockfile_handler global_flock); sub logger { my ($msg) = @_; @@ -511,4 +512,26 @@ sub flush_accounting_rules { } } +sub lockfile_handler { + my ($config) = @_; + if (lc($config->{enable_rrd_lock} || "") eq "y") { + my $lock_file = "/tmp/monitorix.flock"; + + my $lockfile_was_available = (-e $lock_file); + open(my $fh, ">>", $lock_file) or die "Can't open $lock_file: $!"; + if (!$lockfile_was_available) { + chmod(0666, $lock_file); + } + return $fh; + } + return undef; +} + +sub global_flock { + my ($fh, $option) = @_; + if (defined($fh)) { + flock($fh, $option) or die "flock error: $!\n"; + } +} + 1; diff --git a/man/man5/monitorix.conf.5 b/man/man5/monitorix.conf.5 index ab195e6..cb59864 100644 --- a/man/man5/monitorix.conf.5 +++ b/man/man5/monitorix.conf.5 @@ -169,6 +169,13 @@ This option will fork an independent process for each graph in order to speed up Default value: \fIy\fP .RE .P +.BI enable_rrd_lock +.RS +This option will synchronise the rrd file access by creating the file \fI/tmp/monitorix.lock\fP and use it via flock. +.P +Default value: \fIn\fP +.RE +.P .BI include_dir .RS The main configuration file is usually called \fImonitorix.conf\fP and its location is provided as part of the command line arguments. In addition, other configuration files may be loaded placing them in the directory pointed by this option. The names must end with .conf to be included. diff --git a/monitorix b/monitorix index 39c41e3..4b80f8c 100755 --- a/monitorix +++ b/monitorix @@ -31,6 +31,7 @@ use POSIX qw(WNOHANG LC_TIME setlocale uname pause setsid); use Config::General; use Getopt::Std; use Cwd qw(abs_path); +use Fcntl qw(:flock); # Force a standard locale $ENV{LANG} = ""; @@ -767,6 +768,8 @@ while(1) { # call to all enabled graphs on every minute if($sec == 0) { + my $lockfile_handler = lockfile_handler(\%config); + global_flock($lockfile_handler, LOCK_SH); foreach my $f (@{$config{func_update}}) { my $update = $f . "_update"; my $d = $f; @@ -785,6 +788,7 @@ while(1) { } } } + global_flock($lockfile_handler, LOCK_UN); # TRAFFACCT graph daily reports if(lc($config{traffacct}->{enabled} || "") eq "y") { @@ -828,6 +832,7 @@ while(1) { next; } + global_flock($lockfile_handler, LOCK_SH); # daily if(lc($emailreports->{daily}->{enabled} || "") eq "y") { eval { emailreports::emailreports_send(\%config, "daily", "1day", $d); }; @@ -865,6 +870,7 @@ while(1) { } } } + global_flock($lockfile_handler, LOCK_UN); } } diff --git a/monitorix.cgi b/monitorix.cgi index 48133d6..16e7749 100755 --- a/monitorix.cgi +++ b/monitorix.cgi @@ -31,6 +31,7 @@ use Config::General; use POSIX; use RRDs; use Encode; +use Fcntl qw(:flock); my %config; my %cgi; @@ -577,6 +578,8 @@ if($mode eq "localhost") { my @writers; # array of file descriptors my $children = 0; + my $lockfile_handler = lockfile_handler(\%config); + global_flock($lockfile_handler, LOCK_SH); foreach (split(',', $config{graph_name})) { my $gn = trim($_); my $g = ""; @@ -652,6 +655,7 @@ if($mode eq "localhost") { print @{$outputs{$n}} if $outputs{$n}; } } + global_flock($lockfile_handler, LOCK_UN); } elsif($mode eq "multihost") { multihost(\%config, \%colors, \%cgi); diff --git a/monitorix.conf b/monitorix.conf index be68d08..2e89fcb 100644 --- a/monitorix.conf +++ b/monitorix.conf @@ -19,6 +19,7 @@ max_historic_years = 1 accept_selfsigned_certs = y image_format = PNG enable_parallelizing = y +enable_rrd_lock = n include_dir = /etc/monitorix/conf.d base_dir = /var/lib/monitorix/www/ From 97669bfe6544ff0d70bdb5c9f32d44abd5840726 Mon Sep 17 00:00:00 2001 From: Andreas Bachlechner <62039342+bachandi@users.noreply.github.com> Date: Wed, 23 Feb 2022 13:18:33 +0100 Subject: [PATCH 2/4] Change lockfile suffix to .lock. --- lib/Monitorix.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Monitorix.pm b/lib/Monitorix.pm index 82e7c68..988775c 100644 --- a/lib/Monitorix.pm +++ b/lib/Monitorix.pm @@ -515,7 +515,7 @@ sub flush_accounting_rules { sub lockfile_handler { my ($config) = @_; if (lc($config->{enable_rrd_lock} || "") eq "y") { - my $lock_file = "/tmp/monitorix.flock"; + my $lock_file = "/tmp/monitorix.lock"; my $lockfile_was_available = (-e $lock_file); open(my $fh, ">>", $lock_file) or die "Can't open $lock_file: $!"; From 8f7deef03f716181d0ab937b31ba4b7391db31df Mon Sep 17 00:00:00 2001 From: Andreas Bachlechner <62039342+bachandi@users.noreply.github.com> Date: Wed, 23 Feb 2022 13:23:43 +0100 Subject: [PATCH 3/4] Updating the rrd needs to have the exculsive lock and emailreports do not need to have a lock as it just creates an cgi request which has its own lock. --- monitorix | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/monitorix b/monitorix index 4b80f8c..beda540 100755 --- a/monitorix +++ b/monitorix @@ -769,7 +769,7 @@ while(1) { # call to all enabled graphs on every minute if($sec == 0) { my $lockfile_handler = lockfile_handler(\%config); - global_flock($lockfile_handler, LOCK_SH); + global_flock($lockfile_handler, LOCK_EX); foreach my $f (@{$config{func_update}}) { my $update = $f . "_update"; my $d = $f; @@ -832,7 +832,6 @@ while(1) { next; } - global_flock($lockfile_handler, LOCK_SH); # daily if(lc($emailreports->{daily}->{enabled} || "") eq "y") { eval { emailreports::emailreports_send(\%config, "daily", "1day", $d); }; @@ -870,7 +869,6 @@ while(1) { } } } - global_flock($lockfile_handler, LOCK_UN); } } From d1f029c694aac0636fa00fdee503258397358c52 Mon Sep 17 00:00:00 2001 From: Andreas Bachlechner <62039342+bachandi@users.noreply.github.com> Date: Fri, 25 Feb 2022 09:49:26 +0100 Subject: [PATCH 4/4] Fix the lock file access if the accessing user is not the owner of the lockfile in a world readable directory. --- lib/Monitorix.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Monitorix.pm b/lib/Monitorix.pm index 988775c..d3b3ff3 100644 --- a/lib/Monitorix.pm +++ b/lib/Monitorix.pm @@ -518,7 +518,7 @@ sub lockfile_handler { my $lock_file = "/tmp/monitorix.lock"; my $lockfile_was_available = (-e $lock_file); - open(my $fh, ">>", $lock_file) or die "Can't open $lock_file: $!"; + open(my $fh, ($lockfile_was_available ? "+<" : ">>"), $lock_file) or die "Can't open $lock_file: $!"; # If the file already exists we open it without the O_CREATE flag due to the limitations introduced by fs.protected_regular. if (!$lockfile_was_available) { chmod(0666, $lock_file); }