X-Git-Url: https://git.proxmox.com/?p=pve-common.git;a=blobdiff_plain;f=data%2FPVE%2FTools.pm;h=622f8ad02cd430763b5b577e6722436b8cc7489a;hp=3212595fdd8306021d0fa30ceb6b87d72ff93413;hb=46a11c0030d41394aa10c876758abd9cd5a1b16a;hpb=b5d12b08455e316e42843171abf8c054d33dd5bf diff --git a/data/PVE/Tools.pm b/data/PVE/Tools.pm index 3212595..622f8ad 100644 --- a/data/PVE/Tools.pm +++ b/data/PVE/Tools.pm @@ -1,6 +1,7 @@ package PVE::Tools; use strict; +use warnings; use POSIX qw(EINTR); use IO::Socket::INET; use IO::Select; @@ -16,9 +17,13 @@ use Encode; use Digest::SHA; use Text::ParseWords; use String::ShellQuote; +use Time::HiRes qw(usleep gettimeofday tv_interval); our @EXPORT_OK = qw( +$IPV6RE +$IPV4RE lock_file +lock_file_full run_command file_set_contents file_get_contents @@ -38,6 +43,22 @@ my $pvetaskdir = "$pvelogdir/tasks"; 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) = @_; @@ -79,22 +100,24 @@ sub run_with_timeout { 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; @@ -135,6 +158,13 @@ sub lock_file { 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) = @_; @@ -236,6 +266,7 @@ sub run_command { my $logfunc; my $input; my $output; + my $afterfork; eval { @@ -256,6 +287,8 @@ sub run_command { $errfunc = $param{$p}; } elsif ($p eq 'logfunc') { $logfunc = $param{$p}; + } elsif ($p eq 'afterfork') { + $afterfork = $param{$p}; } else { die "got unknown parameter '$p' for run_command\n"; } @@ -316,6 +349,8 @@ sub run_command { local $SIG{ALRM} = sub { die "got timeout\n"; } if $timeout; $oldtimeout = alarm($timeout) if $timeout; + &$afterfork() if $afterfork; + if (ref($writer)) { print $writer $input if defined $input; close $writer; @@ -540,8 +575,8 @@ my $keymaphash = { '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'], @@ -593,13 +628,16 @@ sub extract_param { return $res; } -# Note: we use this to wait until vncterm is ready +# Note: we use this to wait until vncterm/spiceterm is ready sub wait_for_vnc_port { my ($port, $timeout) = @_; $timeout = 5 if !$timeout; + my $sleeptime = 0; + my $starttime = [gettimeofday]; + my $elapsed; - for (my $i = 0; $i < $timeout; $i++) { + while (($elapsed = tv_interval($starttime)) < $timeout) { if (my $fh = IO::File->new ("/proc/net/tcp", "r")) { while (defined (my $line = <$fh>)) { if ($line =~ m/^\s*\d+:\s+([0-9A-Fa-f]{8}):([0-9A-Fa-f]{4})\s/) { @@ -611,30 +649,89 @@ sub wait_for_vnc_port { } close($fh); } - sleep(1); + $sleeptime += 100000 if $sleeptime < 1000000; + usleep($sleeptime); } 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"; + + my $code = sub { - for (my $p = 5900; $p < 6000; $p++) { + my $expiretime = 5; + my $ctime = time(); - my $sock = IO::Socket::INET->new (Listen => 5, - LocalAddr => 'localhost', - LocalPort => $p, - ReuseAddr => 1, - Proto => 0); + my $ports = {}; - if ($sock) { - close ($sock); - return $p; + 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); +} + +sub next_spice_port { + return next_unused_port(61000, 61099); +} # NOTE: NFS syscall can't be interrupted, so alarm does # not work to provide timeouts. @@ -677,6 +774,8 @@ sub df { 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}); @@ -689,7 +788,8 @@ sub upid_decode { 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); @@ -735,7 +835,7 @@ sub upid_read_status { 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); @@ -782,7 +882,9 @@ sub decode_utf8_parameters { sub random_ether_addr { - my $rand = Digest::SHA::sha1_hex(rand(), time()); + my ($seconds, $microseconds) = gettimeofday; + + my $rand = Digest::SHA::sha1_hex($$, rand(), $seconds, $microseconds); my $mac = ''; for (my $i = 0; $i < 6; $i++) {