]> git.proxmox.com Git - pve-common.git/blobdiff - src/PVE/Tools.pm
sort keymap array
[pve-common.git] / src / PVE / Tools.pm
index ae06c12b224b873dc7a27d5965078f54a8d9a851..8c7f3733f80aa0c0338a09c6d9d314122138a342 100644 (file)
@@ -75,6 +75,9 @@ use constant {CLONE_NEWNS   => 0x00020000,
               CLONE_NEWPID  => 0x20000000,
               CLONE_NEWNET  => 0x40000000};
 
+use constant {O_PATH    => 0x00200000,
+              O_TMPFILE => 0x00410000}; # This includes O_DIRECTORY
+
 sub run_with_timeout {
     my ($timeout, $code, @param) = @_;
 
@@ -126,15 +129,16 @@ sub lock_file_full {
 
     my $lock_func = sub {
         if (!$lock_handles->{$$}->{$filename}) {
-            $lock_handles->{$$}->{$filename} = new IO::File (">>$filename") ||
-                die "can't open file - $!\n";
+           my $fh = new IO::File(">>$filename") ||
+               die "can't open file - $!\n";
+           $lock_handles->{$$}->{$filename} = { fh => $fh, refcount => 0};
         }
 
-        if (!flock ($lock_handles->{$$}->{$filename}, $mode|LOCK_NB)) {
-            print STDERR "trying to aquire lock...";
+        if (!flock($lock_handles->{$$}->{$filename}->{fh}, $mode|LOCK_NB)) {
+            print STDERR "trying to acquire lock...";
            my $success;
            while(1) {
-               $success = flock($lock_handles->{$$}->{$filename}, $mode);
+               $success = flock($lock_handles->{$$}->{$filename}->{fh}, $mode);
                # try again on EINTR (see bug #273)
                if ($success || ($! != EINTR)) {
                    last;
@@ -142,10 +146,11 @@ sub lock_file_full {
            }
             if (!$success) {
                 print STDERR " failed\n";
-                die "can't aquire lock - $!\n";
+                die "can't acquire lock '$filename' - $!\n";
             }
             print STDERR " OK\n";
         }
+       $lock_handles->{$$}->{$filename}->{refcount}++;
     };
 
     my $res;
@@ -159,9 +164,12 @@ sub lock_file_full {
        $err = $@;
     }
 
-    if (my $fh = $lock_handles->{$$}->{$filename}) {
-        $lock_handles->{$$}->{$filename} = undef;
-        close ($fh);
+    if (my $fh = $lock_handles->{$$}->{$filename}->{fh}) {
+       my $refcount = --$lock_handles->{$$}->{$filename}->{refcount};
+       if ($refcount <= 0) {
+           $lock_handles->{$$}->{$filename} = undef;
+           close ($fh);
+       }
     }
 
     if ($err) {
@@ -670,7 +678,7 @@ my $keymaphash =  {
 };
 
 my $kvmkeymaparray = [];
-foreach my $lc (keys %$keymaphash) {
+foreach my $lc (sort keys %$keymaphash) {
     push @$kvmkeymaparray, $keymaphash->{$lc}->[1];
 }
 
@@ -1054,7 +1062,7 @@ sub dump_logfile {
 }
 
 sub dump_journal {
-    my ($start, $limit, $filter) = @_;
+    my ($start, $limit, $since, $until) = @_;
 
     my $lines = [];
     my $count = 0;
@@ -1072,6 +1080,9 @@ sub dump_journal {
     };
 
     my $cmd = ['journalctl', '-o', 'short', '--no-pager'];
+
+    push @$cmd, '--since', $since if $since;
+    push @$cmd, '--until', $until if $until;
     run_command($cmd, outfunc => $parser);
 
     # HACK: ExtJS store.guaranteeRange() does not like empty array
@@ -1185,4 +1196,130 @@ sub unshare($) {
     return 0 == syscall(272, $flags);
 }
 
+sub setns($$) {
+    my ($fileno, $nstype) = @_;
+    return 0 == syscall(308, $fileno, $nstype);
+}
+
+sub syncfs($) {
+    my ($fileno) = @_;
+    return 0 == syscall(306, $fileno);
+}
+
+sub sync_mountpoint {
+    my ($path) = @_;
+    sysopen my $fd, $path, O_PATH or die "failed to open $path: $!\n";
+    my $result = syncfs(fileno($fd));
+    close($fd);
+    return $result;
+}
+
+# support sending multi-part mail messages with a text and or a HTML part
+# mailto may be a single email string or an array of receivers
+sub sendmail {
+    my ($mailto, $subject, $text, $html, $mailfrom, $author) = @_;
+
+    $mailto = [ $mailto ] if !ref($mailto);
+
+    my $rcvrarg = '';
+    foreach my $r (@$mailto) {
+       $rcvrarg .= " '$r'";
+    }
+    my $rcvrtxt = join (', ', @$mailto);
+
+    $mailfrom = $mailfrom || "root";
+    $author = $author || 'Proxmox VE';
+
+    open (MAIL,"|sendmail -B 8BITMIME -f $mailfrom $rcvrarg") ||
+       die "unable to open 'sendmail' - $!";
+
+    # multipart spec see https://www.ietf.org/rfc/rfc1521.txt
+    my $boundary = "----_=_NextPart_001_".int(time).$$;
+
+    print MAIL "Content-Type: multipart/alternative;\n";
+    print MAIL "\tboundary=\"$boundary\"\n";
+    print MAIL "MIME-Version: 1.0\n";
+
+    print MAIL "FROM: $author <$mailfrom>\n";
+    print MAIL "TO: $rcvrtxt\n";
+    print MAIL "SUBJECT: $subject\n";
+    print MAIL "\n";
+    print MAIL "This is a multi-part message in MIME format.\n\n";
+    print MAIL "--$boundary\n";
+
+    if (defined($text)) {
+       print MAIL "Content-Type: text/plain;\n";
+       print MAIL "\tcharset=\"UTF8\"\n";
+       print MAIL "Content-Transfer-Encoding: 8bit\n";
+       print MAIL "\n";
+
+       # avoid 'remove extra line breaks' issue (MS Outlook)
+       my $fill = '  ';
+       $text =~ s/^/$fill/gm;
+
+       print MAIL $text;
+
+       print MAIL "\n--$boundary\n";
+    }
+
+    if (defined($html)) {
+       print MAIL "Content-Type: text/html;\n";
+       print MAIL "\tcharset=\"UTF8\"\n";
+       print MAIL "Content-Transfer-Encoding: 8bit\n";
+       print MAIL "\n";
+
+       print MAIL $html;
+
+       print MAIL "\n--$boundary--\n";
+    }
+
+    close(MAIL);
+}
+
+sub tempfile {
+    my ($perm, %opts) = @_;
+
+    # default permissions are stricter than with file_set_contents
+    $perm = 0600 if !defined($perm);
+
+    my $dir = $opts{dir} // '/tmp';
+    my $mode = $opts{mode} // O_RDWR;
+    $mode |= O_EXCL if !$opts{allow_links};
+
+    my $fh = IO::File->new($dir, $mode | O_TMPFILE, $perm)
+       or die "failed to create tempfile: $!\n";
+    return $fh;
+}
+
+sub tempfile_contents {
+    my ($data, $perm, %opts) = @_;
+
+    my $fh = tempfile($perm, %opts);
+    eval {
+       die "unable to write to tempfile: $!\n" if !print {$fh} $data;
+       die "unable to flush to tempfile: $!\n" if !defined($fh->flush());
+    };
+    if (my $err = $@) {
+       close $fh;
+       die $err;
+    }
+
+    return ("/proc/$$/fd/".$fh->fileno, $fh);
+}
+
+sub validate_ssh_public_keys {
+    my ($raw) = @_;
+    my @lines = split(/\n/, $raw);
+
+    foreach my $line (@lines) {
+       next if $line =~ m/^\s*$/;
+       eval {
+           my ($filename, $handle) = tempfile_contents($line);
+           run_command(["ssh-keygen", "-l", "-f", $filename],
+                       outfunc => sub {}, errfunc => sub {});
+       };
+       die "SSH public key validation error\n" if $@;
+    }
+}
+
 1;