]> Dogcows Code - chaz/p5-File-KDBX/commitdiff
Save key files atomically
authorCharles McGarvey <ccm@cpan.org>
Tue, 19 Apr 2022 01:53:14 +0000 (19:53 -0600)
committerCharles McGarvey <ccm@cpan.org>
Sun, 1 May 2022 00:29:00 +0000 (18:29 -0600)
lib/File/KDBX/Key/File.pm
t/keys.t

index 5c7cb12645d2f3bdd7f763924eb004f8f5d4bef7..5949d4c8afa2ea3147de65bd3a869cd1fbfd909e 100644 (file)
@@ -163,9 +163,18 @@ sub save {
     my $filepath    = $args{filepath} // $self->filepath;
     my $fh          = $args{fh};
 
+    my $filepath_temp;
     if (!openhandle($fh)) {
         $filepath or throw 'Must specify where to safe the key file to';
-        open($fh, '>:raw', $filepath) or throw "Failed to open key file for writing: $!";
+
+        require File::Temp;
+        ($fh, $filepath_temp) = eval { File::Temp::tempfile("${filepath}-XXXXXX", CLEANUP => 1) };
+        if (!$fh or my $err = $@) {
+            $err //= 'Unknown error';
+            throw sprintf('Open file failed (%s): %s', $filepath_temp, $err),
+                error       => $err,
+                filepath    => $filepath_temp;
+        }
     }
 
     if ($type == KEY_FILE_TYPE_XML) {
@@ -182,6 +191,20 @@ sub save {
     else {
         throw "Cannot save $type key file (invalid type)", type => $type;
     }
+
+    close($fh);
+
+    if ($filepath_temp) {
+        my ($file_mode, $file_uid, $file_gid) = (stat($filepath))[2, 4, 5];
+
+        my $mode = $args{mode} // $file_mode // do { my $m = umask; defined $m ? oct(666) &~ $m : undef };
+        my $uid  = $args{uid}  // $file_uid  // -1;
+        my $gid  = $args{gid}  // $file_gid  // -1;
+        chmod($mode, $filepath_temp) if defined $mode;
+        chown($uid, $gid, $filepath_temp);
+        rename($filepath_temp, $filepath)
+            or throw "Failed to write file ($filepath): $!", filepath => $filepath;
+    }
 }
 
 ##############################################################################
index 62d2a1a35ab5be2f196427113345ff142831133d..65658e5a7391af4fb10ff2ca199ac22e5c2aa036 100644 (file)
--- a/t/keys.t
+++ b/t/keys.t
@@ -89,7 +89,8 @@ subtest 'IO handle key files' => sub {
     is $key->type, 'hashed', 'file type is detected as hashed';
 
     my ($fh_save, $filepath) = tempfile('keyfile-XXXXXX', TMPDIR => 1, UNLINK => 1, SUFFIX => '.key');
-    ok $key->save(fh => $fh_save, type => KEY_FILE_TYPE_XML), 'Save key file using IO handle';
+    is exception { $key->save(fh => $fh_save, type => KEY_FILE_TYPE_XML) }, undef,
+        'Save key file using IO handle';
     close($fh_save);
 
     my $key2 = File::KDBX::Key::File->new($filepath);
This page took 0.024706 seconds and 4 git commands to generate.