1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166
|
#!/usr/bin/perl
use strict;
use warnings;
use 5.020; # needs at least Debian 8 Jessie
# No modules, must work with just the essential package perl-base
# Check commandline options
my $work_on_backup = 0;
my $usage_text = <<"EOT";
Usage: $0 [-b]
$0 has no parameters and only these commandline flags:
-b work on backup database "records.old" instead of "records".
-h, --help show this help.
See https://github.com/rpodgorny/uptimed/issues/2,
https://github.com/rpodgorny/uptimed/pull/22 and
https://bugs.debian.org/654830 for details on the issue this script
has been written for.
EOT
if (@ARGV) {
if ($ARGV[0] eq '--help' or $ARGV[0] eq '-h') {
print "$0 -- Fix uptimed records db wrt. duplicate entries due to leap seconds\n\n".
$usage_text;
exit 0;
}
if ($#ARGV >= 1) {
die "Error: too many arguments.\n\n".
$usage_text;
}
if ($ARGV[0] ne '-b') {
die "Error: unknown commandline option: $ARGV[0]\n\n".
$usage_text;
}
if ($ARGV[0] eq '-b') {
$work_on_backup = 1;
}
}
# Check if we're running as root
if ($> != 0) {
die "$0 needs to be run with super-user (root) permissions.\n";
}
# Constants taken from the 0.4.6 upstream release
my $boot_fuzz = 30; # seconds
# Cache / lookup variables
my @records = ();
my %record_by_boot_time = ();
my @ch_cmd;
my $last_boot_time = 0;
my $last_boot_record = '0:0:none';
my @last_boot_record = (0,0,'none');
# Filehandle variables
my $infh = undef;
my $outfh = undef;
# Spool directory and file
my $spooldir = '/var/spool/uptimed';
my $records = "$spooldir/records".($work_on_backup ? '.old' : '');
my $records_backup = "$records.fixup-duplicates-backup";
# Temporary file
my $mktemp_cmd = "mktemp $spooldir/uptime-records-fixup-XXXXXXXXXX";
my $tempfile = `$mktemp_cmd`;
# Check exit code of mktemp command
if ($? == -1) { die "\"$mktemp_cmd\" failed to execute: $!"; }
elsif ($? & 127) { die "\"$mktemp_cmd\" died with signal ".($? & 127); }
elsif (($? >> 8) != 0) { die "\"$mktemp_cmd\" exited with value ".($? >> 8); }
chomp($tempfile);
# Then fixup the permissions of the temporary file.
@ch_cmd = ('chown', "--reference=$records", $tempfile);
system(@ch_cmd) == 0 or die '"'.join(' ', @ch_cmd)."\" failed: $?";
@ch_cmd = ('chmod', "--reference=$records", $tempfile);
system(@ch_cmd) == 0 or die '"'.join(' ', @ch_cmd)."\" failed: $?";
# Telling the use that we're going to fixup that records file
say "$0: Will fixup $records for https://bugs.debian.org/654830".
"\nrespectively https://github.com/rpodgorny/uptimed/issues/2".
"\nleftovers.";
# Read the original records file
open($infh, '<', $records) or die "Can't open $records: $!";
while (my $line = <$infh>) {
# Store original line minus the trailing line break;
chomp($line);
push(@records, $line);
# Extract the boot time and save
my @line = split(/:/, $line);
$record_by_boot_time{$line[1]} = $line;
}
close($infh);
# Sort boot times and check if there's any difference less than
# $boot_fuzz, but with an uprecord higher than $boot_fuzz (and the
# same kernel).
foreach my $boot_time (sort { $a <=> $b } keys %record_by_boot_time) {
my $boot_record = $record_by_boot_time{$boot_time};
my @boot_record = split(/:/, $boot_record);
# Check for boot times close behind each other. Can only be
# consecutive boot times, as we sorted the whole list.
if ($last_boot_time + $boot_fuzz > $boot_time) {
# Now cross-check if the uptime is bigger than $boot_fuzz
if ($boot_record[0] > $boot_fuzz) {
# Now compare the kernels
if ($boot_record[2] eq $last_boot_record[2]) {
say "Dropping near-duplicate uptimed entry \"$last_boot_record\".\n".
" (more recent entry: \"$boot_record\")";
@records = grep { $_ ne $last_boot_record } @records;
# Check for higher uptime record
if ($last_boot_record[0] > $boot_record[0]) {
my $merged_boot_record = $boot_record;
# Re-add leap-seconds
my $fixed_uptime =
$last_boot_record[0] + ($boot_time - $last_boot_time);
$merged_boot_record =~
s/^$boot_record[0]:/$fixed_uptime:/;
$boot_record = $merged_boot_record;
@records =
map { s/^$boot_record[0]:/$fixed_uptime:/r }
@records;
}
} else {
warn "Kernel differs between \"$last_boot_record\" and \"$boot_record\", not removed.\n";
}
} else {
warn "Two close boots, but with very low uptime: $last_boot_record and $boot_record\", not removed.\n";
}
}
$last_boot_time = $boot_time;
$last_boot_record = $boot_record;
@last_boot_record = @boot_record;
}
# Save the result as temporary file
#say $tempfile;
open($outfh, '>', $tempfile) or die "Can't write to $tempfile: $!";
foreach my $record (@records) {
say $outfh $record or die "Couldn't write \"$record\" to $tempfile: $!";
}
close($outfh);
# Finally rename the original file and replace it with the new file.
if (! -e $records_backup) {
rename($records, $records_backup)
or die "Couldn't rename $records to $records_backup: $!";
}
rename($tempfile, $records)
or die "Couldn't rename $tempfile to $records: $!";
say "$records backed up at $records_backup and replaced with fixed version.\n";
|