package PVE::Tools;
use strict;
+use warnings;
use POSIX qw(EINTR);
use IO::Socket::INET;
use IO::Select;
use String::ShellQuote;
our @EXPORT_OK = qw(
+$IPV6RE
+$IPV4RE
lock_file
+lock_file_full
run_command
file_set_contents
file_get_contents
mkdir $pvelogdir;
mkdir $pvetaskdir;
+my $IPV4OCTET = "(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])";
+our $IPV4RE = "(?:(?:$IPV4OCTET\\.){3}$IPV4OCTET)";
+my $IPV6H16 = "(?:[0-9a-fA-F]{1,4})";
+my $IPV6LS32 = "(?:(?:$IPV4RE|$IPV6H16:$IPV6H16))";
+
+our $IPV6RE = "(?:" .
+ "(?:(?:" . "(?:$IPV6H16:){6})$IPV6LS32)|" .
+ "(?:(?:" . "::(?:$IPV6H16:){5})$IPV6LS32)|" .
+ "(?:(?:(?:" . "$IPV6H16)?::(?:$IPV6H16:){4})$IPV6LS32)|" .
+ "(?:(?:(?:(?:$IPV6H16:){0,1}$IPV6H16)?::(?:$IPV6H16:){3})$IPV6LS32)|" .
+ "(?:(?:(?:(?:$IPV6H16:){0,2}$IPV6H16)?::(?:$IPV6H16:){2})$IPV6LS32)|" .
+ "(?:(?:(?:(?:$IPV6H16:){0,3}$IPV6H16)?::(?:$IPV6H16:){1})$IPV6LS32)|" .
+ "(?:(?:(?:(?:$IPV6H16:){0,4}$IPV6H16)?::" . ")$IPV6LS32)|" .
+ "(?:(?:(?:(?:$IPV6H16:){0,5}$IPV6H16)?::" . ")$IPV6H16)|" .
+ "(?:(?:(?:(?:$IPV6H16:){0,6}$IPV6H16)?::" . ")))";
+
sub run_with_timeout {
my ($timeout, $code, @param) = @_;
my $lock_handles = {};
-sub lock_file {
- my ($filename, $timeout, $code, @param) = @_;
+sub lock_file_full {
+ my ($filename, $timeout, $shared, $code, @param) = @_;
$timeout = 10 if !$timeout;
+ my $mode = $shared ? LOCK_SH : LOCK_EX;
+
my $lock_func = sub {
if (!$lock_handles->{$$}->{$filename}) {
$lock_handles->{$$}->{$filename} = new IO::File (">>$filename") ||
die "can't open file - $!\n";
}
- if (!flock ($lock_handles->{$$}->{$filename}, LOCK_EX|LOCK_NB)) {
+ if (!flock ($lock_handles->{$$}->{$filename}, $mode|LOCK_NB)) {
print STDERR "trying to aquire lock...";
my $success;
while(1) {
- $success = flock($lock_handles->{$$}->{$filename}, LOCK_EX);
+ $success = flock($lock_handles->{$$}->{$filename}, $mode);
# try again on EINTR (see bug #273)
if ($success || ($! != EINTR)) {
last;
return $res;
}
+
+sub lock_file {
+ my ($filename, $timeout, $code, @param) = @_;
+
+ return lock_file_full($filename, $timeout, 0, $code, @param);
+}
+
sub file_set_contents {
my ($filename, $data, $perm) = @_;
'dk' => ['Danish', 'da', 'qwerty/dk-latin1.kmap.gz', 'dk', 'nodeadkeys'],
'de' => ['German', 'de', 'qwertz/de-latin1-nodeadkeys.kmap.gz', 'de', 'nodeadkeys' ],
'de-ch' => ['Swiss-German', 'de-ch', 'qwertz/sg-latin1.kmap.gz', 'ch', 'de_nodeadkeys' ],
- 'en-gb' => ['United Kingdom', 'en-gb', 'qwerty/uk.kmap.gz' , 'gb', 'intl' ],
- 'en-us' => ['U.S. English', 'en-us', 'qwerty/us-latin1.kmap.gz', 'us', 'intl' ],
+ 'en-gb' => ['United Kingdom', 'en-gb', 'qwerty/uk.kmap.gz' , 'gb', undef],
+ 'en-us' => ['U.S. English', 'en-us', 'qwerty/us-latin1.kmap.gz', 'us', undef ],
'es' => ['Spanish', 'es', 'qwerty/es.kmap.gz', 'es', 'nodeadkeys'],
#'et' => [], # Ethopia or Estonia ??
'fi' => ['Finnish', 'fi', 'qwerty/fi-latin1.kmap.gz', 'fi', 'nodeadkeys'],
return undef;
}
-sub next_vnc_port {
+sub next_unused_port {
+ my ($range_start, $range_end) = @_;
+
+ # We use a file to register allocated ports.
+ # Those registrations expires after $expiretime.
+ # We use this to avoid race conditions between
+ # allocation and use of ports.
+
+ my $filename = "/var/tmp/pve-reserved-ports";
- for (my $p = 5900; $p < 6000; $p++) {
+ my $code = sub {
- my $sock = IO::Socket::INET->new (Listen => 5,
- LocalAddr => 'localhost',
- LocalPort => $p,
- ReuseAddr => 1,
- Proto => 0);
+ my $expiretime = 5;
+ my $ctime = time();
- if ($sock) {
- close ($sock);
- return $p;
+ my $ports = {};
+
+ if (my $fh = IO::File->new ($filename, "r")) {
+ while (my $line = <$fh>) {
+ if ($line =~ m/^(\d+)\s(\d+)$/) {
+ my ($port, $timestamp) = ($1, $2);
+ if (($timestamp + $expiretime) > $ctime) {
+ $ports->{$port} = $timestamp; # not expired
+ }
+ }
+ }
}
- }
+
+ my $newport;
+
+ for (my $p = $range_start; $p < $range_end; $p++) {
+ next if $ports->{$p}; # reserved
+
+ my $sock = IO::Socket::INET->new(Listen => 5,
+ LocalAddr => 'localhost',
+ LocalPort => $p,
+ ReuseAddr => 1,
+ Proto => 0);
+
+ if ($sock) {
+ close($sock);
+ $newport = $p;
+ $ports->{$p} = $ctime;
+ last;
+ }
+ }
+
+ my $data = "";
+ foreach my $p (keys %$ports) {
+ $data .= "$p $ports->{$p}\n";
+ }
+
+ file_set_contents($filename, $data);
- die "unable to find free vnc port";
-};
+ return $newport;
+ };
+
+ my $p = lock_file($filename, 10, $code);
+ die $@ if $@;
+
+ die "unable to find free port (${range_start}-${range_end})\n" if !$p;
+
+ return $p;
+}
+
+sub next_migrate_port {
+ return next_unused_port(60000, 60010);
+}
+
+sub next_vnc_port {
+ return next_unused_port(5900, 6000);
+}
# NOTE: NFS syscall can't be interrupted, so alarm does
# not work to provide timeouts.
sub upid_encode {
my $d = shift;
+ # Note: pstart can be > 32bit if uptime > 497 days, so this can result in
+ # more that 8 characters for pstart
return sprintf("UPID:%s:%08X:%08X:%08X:%s:%s:%s:", $d->{node}, $d->{pid},
$d->{pstart}, $d->{starttime}, $d->{type}, $d->{id},
$d->{user});
my $filename;
# "UPID:$node:$pid:$pstart:$startime:$dtype:$id:$user"
- if ($upid =~ m/^UPID:([a-zA-Z0-9]([a-zA-Z0-9\-]*[a-zA-Z0-9])?):([0-9A-Fa-f]{8}):([0-9A-Fa-f]{8}):([0-9A-Fa-f]{8}):([^:\s]+):([^:\s]*):([^:\s]+):$/) {
+ # Note: allow up to 9 characters for pstart (work until 20 years uptime)
+ if ($upid =~ m/^UPID:([a-zA-Z0-9]([a-zA-Z0-9\-]*[a-zA-Z0-9])?):([0-9A-Fa-f]{8}):([0-9A-Fa-f]{8,9}):([0-9A-Fa-f]{8}):([^:\s]+):([^:\s]*):([^:\s]+):$/) {
$res->{node} = $1;
$res->{pid} = hex($3);
$res->{pstart} = hex($4);
my ($task, $filename) = upid_decode($upid);
my $fh = IO::File->new($filename, "r");
return "unable to open file - $!" if !$fh;
- my $maxlen = 1024;
+ my $maxlen = 4096;
sysseek($fh, -$maxlen, 2);
my $readbuf = '';
my $br = sysread($fh, $readbuf, $maxlen);