+sub _open_keys ($) {
+ my ($r) = @_;
+ my $spath = $r->{S}{secrets_path};
+ for (;;) {
+ my $keys = new IO::File $spath, 'r+';
+ if ($keys) {
+ stat $keys or die $!; # NB must not disturb stat _
+ my $size = (stat _)[7];
+ my $age = time - (stat _)[9];
+ return $keys if $size && $age <= $r->{S}{key_rollover} / 2;
+ }
+ # file doesn't exist, or is empty or too old
+ if (!$keys) {
+ die "$spath $!" unless $!==&ENOENT;
+ # doesn't exist, so create it just so we can lock it
+ $keys = new IO::File $spath, 'a+';
+ die "$keys $!" unless $keys;
+ stat $keys or die $!; # NB must not disturb stat _
+ my $size = (stat _)[7];
+ next if $size; # oh someone else has done it, reopen and read it
+ }
+ # file now exists is empty or too old, we must try to replace it
+ my $our_inum = (stat _)[1]; # last use of that stat _
+ flock $keys, LOCK_EX or die "$spath $!";
+ stat $spath or die "$spath $!";
+ my $path_inum = (stat _)[1];
+ next if $our_inum != $path_inum; # someone else has done it
+ # We now hold the lock!
+ my $newkeys = new IO::Handle;
+ sysopen $newkeys, "$spath.new", O_CREAT|O_TRUNC|O_WRONLY, 0600
+ or die "$spath.new $!";
+ # we add the new key to the front which means it's always sorted
+ print $newkeys, time, ' ', $r->_random_key(), "\n" or die $!;
+ while (my ($gen,$key,$line) = $r->_read_key($keys)) {
+ print $newkeys, $line or die $!;
+ }
+ $keys->error and die $!;
+ close $newkeys or die "$spath.new $!";
+ rename "$spath.new", "$spath" or die "$spath: $!";
+ # that rename effective unlocks, since it makes the name refer
+ # to the new file which we haven't locked
+ # we go round again opening the file at the beginning
+ # so that our caller gets a fresh handle onto the existing key file