X-Git-Url: https://git.proxmox.com/?p=pve-common.git;a=blobdiff_plain;f=src%2FPVE%2FTools.pm;h=3ad37940fdbb1fcfc298957bd3f5784e3baa82aa;hp=60ba8aa5f62d01892ed2fd9189c77141d34ca74e;hb=21c56a963f6bdcb8cd62aaf40fda4d5d26cb146b;hpb=f127adaba550ef1fd5ccdbff258efdfc5f48a096 diff --git a/src/PVE/Tools.pm b/src/PVE/Tools.pm index 60ba8aa..3ad3794 100644 --- a/src/PVE/Tools.pm +++ b/src/PVE/Tools.pm @@ -12,6 +12,7 @@ use Filesys::Df (); # don't overwrite our df() use IO::Pipe; use IO::File; use IO::Dir; +use IO::Handle; use IPC::Open3; use Fcntl qw(:DEFAULT :flock); use base 'Exporter'; @@ -75,6 +76,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) = @_; @@ -675,7 +679,7 @@ my $keymaphash = { }; my $kvmkeymaparray = []; -foreach my $lc (keys %$keymaphash) { +foreach my $lc (sort keys %$keymaphash) { push @$kvmkeymaparray, $keymaphash->{$lc}->[1]; } @@ -1059,7 +1063,7 @@ sub dump_logfile { } sub dump_journal { - my ($start, $limit, $filter) = @_; + my ($start, $limit, $since, $until) = @_; my $lines = []; my $count = 0; @@ -1077,6 +1081,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 @@ -1195,4 +1202,145 @@ sub setns($$) { 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 $@; + } +} + +sub openat($$$;$) { + my ($dirfd, $pathname, $flags, $mode) = @_; + my $fd = syscall(257, $dirfd, $pathname, $flags, $mode//0); + return undef if $fd < 0; + # sysopen() doesn't deal with numeric file descriptors apparently + # so we need to convert to a mode string for IO::Handle->new_from_fd + my $flagstr = ($flags & O_RDWR) ? 'rw' : ($flags & O_WRONLY) ? 'w' : 'r'; + my $handle = IO::Handle->new_from_fd($fd, $flagstr); + return $handle if $handle; + my $err = $!; # save error before closing the raw fd + syscall(3, $fd); # close + $! = $err; + return undef; +} + +sub mkdirat($$$) { + my ($dirfd, $name, $mode) = @_; + return syscall(258, $dirfd, $name, $mode) == 0; +} + 1;