X-Git-Url: https://git.proxmox.com/?p=pve-common.git;a=blobdiff_plain;f=data%2FPVE%2FTools.pm;h=f8e9c4773dbe94ed7e57d1c9de5102d96410b85e;hp=6eaf68c7ac3422bd74db51f0f8899ffecf6e147b;hb=2f13cbb5a6557da77c644e112c09d797066496ea;hpb=e143e9d86b489c57166b5c192bf41dcc3452471e diff --git a/data/PVE/Tools.pm b/data/PVE/Tools.pm index 6eaf68c..f8e9c47 100644 --- a/data/PVE/Tools.pm +++ b/data/PVE/Tools.pm @@ -1,24 +1,35 @@ package PVE::Tools; use strict; -use POSIX; +use warnings; +use POSIX qw(EINTR); use IO::Socket::INET; use IO::Select; use File::Basename; use File::Path qw(make_path); use IO::File; +use IO::Dir; use IPC::Open3; use Fcntl qw(:DEFAULT :flock); use base 'Exporter'; use URI::Escape; 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 file_read_firstline +dir_glob_regex +dir_glob_foreach split_list template_replace safe_print @@ -32,45 +43,104 @@ my $pvetaskdir = "$pvelogdir/tasks"; mkdir $pvelogdir; mkdir $pvetaskdir; -# flock: we use one file handle per process, so lock file -# can be called multiple times and succeeds for the same process. +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))"; -my $lock_handles = {}; +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 lock_file { - my ($filename, $timeout, $code, @param) = @_; +sub run_with_timeout { + my ($timeout, $code, @param) = @_; + + die "got timeout\n" if $timeout <= 0; + + my $prev_alarm; + + my $sigcount = 0; my $res; - $timeout = 10 if !$timeout; + local $SIG{ALRM} = sub { $sigcount++; }; # catch alarm outside eval eval { + local $SIG{ALRM} = sub { $sigcount++; die "got timeout\n"; }; + local $SIG{PIPE} = sub { $sigcount++; die "broken pipe\n" }; + local $SIG{__DIE__}; # see SA bug 4631 - local $SIG{ALRM} = sub { die "got timeout (can't lock '$filename')\n"; }; + $prev_alarm = alarm($timeout); - alarm ($timeout); + $res = &$code(@param); + + alarm(0); # avoid race conditions + }; + my $err = $@; + + alarm($prev_alarm) if defined($prev_alarm); + + die "unknown error" if $sigcount && !$err; # seems to happen sometimes + + die $err if $err; + + return $res; +} + +# flock: we use one file handle per process, so lock file +# can be called multiple times and succeeds for the same process. + +my $lock_handles = {}; + +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 lock file '$filename' - $!\n"; + 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..."; - if (!flock ($lock_handles->{$$}->{$filename}, LOCK_EX)) { + my $success; + while(1) { + $success = flock($lock_handles->{$$}->{$filename}, $mode); + # try again on EINTR (see bug #273) + if ($success || ($! != EINTR)) { + last; + } + } + if (!$success) { print STDERR " failed\n"; - die "can't aquire lock for '$filename' - $!\n"; + die "can't aquire lock - $!\n"; } print STDERR " OK\n"; } - alarm (0); - - $res = &$code(@param); }; - my $err = $@; + my $res; - alarm (0); + eval { run_with_timeout($timeout, $lock_func); }; + my $err = $@; + if ($err) { + $err = "can't lock file '$filename' - $err"; + } else { + eval { $res = &$code(@param) }; + $err = $@; + } if ($lock_handles->{$$}->{$filename}) { my $fh = $lock_handles->{$$}->{$filename}; @@ -88,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) = @_; @@ -134,7 +211,7 @@ sub file_read_firstline { my $fh = IO::File->new ($filename, "r"); return undef if !$fh; my $res = <$fh>; - chomp $res; + chomp $res if $res; $fh->close; return $res; } @@ -164,10 +241,19 @@ sub run_command { my ($cmd, %param) = @_; my $old_umask; + my $cmdstr; - $cmd = [ $cmd ] if !ref($cmd); - - my $cmdstr = join (' ', @$cmd); + if (!ref($cmd)) { + $cmdstr = $cmd; + if ($cmd =~ m/|/) { + # see 'man bash' for option pipefail + $cmd = [ '/bin/bash', '-c', "set -o pipefail && $cmd" ]; + } else { + $cmd = [ $cmd ]; + } + } else { + $cmdstr = cmd2string($cmd); + } my $errmsg; my $laststderr; @@ -175,49 +261,78 @@ sub run_command { my $oldtimeout; my $pid; - eval { - my $reader = IO::File->new(); - my $writer = IO::File->new(); - my $error = IO::File->new(); + my $outfunc; + my $errfunc; + my $logfunc; + my $input; + my $output; + my $afterfork; - my $input; - my $outfunc; - my $errfunc; + eval { foreach my $p (keys %param) { if ($p eq 'timeout') { $timeout = $param{$p}; } elsif ($p eq 'umask') { - umask($param{$p}); + $old_umask = umask($param{$p}); } elsif ($p eq 'errmsg') { $errmsg = $param{$p}; - $errfunc = sub { - print STDERR "$laststderr\n" if $laststderr; - $laststderr = shift; - }; } elsif ($p eq 'input') { $input = $param{$p}; + } elsif ($p eq 'output') { + $output = $param{$p}; } elsif ($p eq 'outfunc') { $outfunc = $param{$p}; } elsif ($p eq 'errfunc') { $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"; } } + if ($errmsg) { + my $origerrfunc = $errfunc; + $errfunc = sub { + if ($laststderr) { + if ($origerrfunc) { + &$origerrfunc("$laststderr\n"); + } else { + print STDERR "$laststderr\n" if $laststderr; + } + } + $laststderr = shift; + }; + } + + my $reader = $output && $output =~ m/^>&/ ? $output : IO::File->new(); + my $writer = $input && $input =~ m/^<&/ ? $input : IO::File->new(); + my $error = IO::File->new(); + # try to avoid locale related issues/warnings my $lang = $param{lang} || 'C'; my $orig_pid = $$; eval { - local $ENV{LANG} = $lang; + local $ENV{LC_ALL} = $lang; # suppress LVM warnings like: "File descriptor 3 left open"; local $ENV{LVM_SUPPRESS_FD_WARNINGS} = "1"; $pid = open3($writer, $reader, $error, @$cmd) || die $!; + + # if we pipe fron STDIN, open3 closes STDIN, so we we + # a perl warning "Filehandle STDIN reopened as GENXYZ .. " + # as soon as we open a new file. + # to avoid that we open /dev/null + if (!ref($writer) && !defined(fileno(STDIN))) { + POSIX::close(0); + open(STDIN, "add($reader); + $select->add($reader) if ref($reader); $select->add($error); my $outlog = ''; @@ -260,12 +379,13 @@ sub run_command { } $select->remove ($h) if !$count; if ($h eq $reader) { - if ($outfunc) { + if ($outfunc || $logfunc) { eval { $outlog .= $buf; while ($outlog =~ s/^([^\010\r\n]*)(\r|\n|(\010)+|\r\n)//s) { my $line = $1; - &$outfunc($line); + &$outfunc($line) if $outfunc; + &$logfunc($line) if $logfunc; } }; my $err = $@; @@ -279,12 +399,13 @@ sub run_command { *STDOUT->flush(); } } elsif ($h eq $error) { - if ($errfunc) { + if ($errfunc || $logfunc) { eval { $errlog .= $buf; while ($errlog =~ s/^([^\010\r\n]*)(\r|\n|(\010)+|\r\n)//s) { my $line = $1; - &$errfunc($line); + &$errfunc($line) if $errfunc; + &$logfunc($line) if $logfunc; } }; my $err = $@; @@ -302,7 +423,10 @@ sub run_command { } &$outfunc($outlog) if $outfunc && $outlog; + &$logfunc($outlog) if $logfunc && $outlog; + &$errfunc($errlog) if $errfunc && $errlog; + &$logfunc($errlog) if $logfunc && $errlog; waitpid ($pid, 0); @@ -311,12 +435,14 @@ sub run_command { } elsif (my $sig = ($? & 127)) { die "got signal $sig\n"; } elsif (my $ec = ($? >> 8)) { - if ($errmsg && $laststderr) { - my $lerr = $laststderr; - $laststderr = undef; - die "$lerr\n"; + if (!($ec == 24 && ($cmdstr =~ m|^(\S+/)?rsync\s|))) { + if ($errmsg && $laststderr) { + my $lerr = $laststderr; + $laststderr = undef; + die "$lerr\n"; + } + die "exit code $ec\n"; } - die "exit code $ec\n"; } alarm(0); @@ -326,7 +452,9 @@ sub run_command { alarm(0); - print STDERR "$laststderr\n" if $laststderr; + if ($errmsg && $laststderr) { + &$errfunc(undef); # flush laststderr + } umask ($old_umask) if defined($old_umask); @@ -340,17 +468,22 @@ sub run_command { } if ($errmsg) { + $err =~ s/^usermod:\s*// if $cmdstr =~ m|^(\S+/)?usermod\s|; die "$errmsg: $err"; } else { die "command '$cmdstr' failed: $err"; } } + + return undef; } sub split_list { my $listtxt = shift || ''; - $listtxt =~ s/[,;\0]/ /g; + return split (/\0/, $listtxt) if $listtxt =~ m/\0/; + + $listtxt =~ s/[,;]/ /g; $listtxt =~ s/^\s+//; my @data = split (/\s+/, $listtxt); @@ -373,6 +506,8 @@ sub trim { sub template_replace { my ($tmpl, $data) = @_; + return $tmpl if !$tmpl; + my $res = ''; while ($tmpl =~ m/([^{]+)?({([^}]+)})?/g) { $res .= $1 if $1; @@ -436,41 +571,52 @@ sub debmirrors { }; } +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', 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'], + #'fo' => ['Faroe Islands', 'fo', ???, 'fo', 'nodeadkeys'], + 'fr' => ['French', 'fr', 'azerty/fr-latin1.kmap.gz', 'fr', 'nodeadkeys'], + 'fr-be' => ['Belgium-French', 'fr-be', 'azerty/be2-latin1.kmap.gz', 'be', 'nodeadkeys'], + 'fr-ca' => ['Canada-French', 'fr-ca', 'qwerty/cf.kmap.gz', 'ca', 'fr-legacy'], + 'fr-ch' => ['Swiss-French', 'fr-ch', 'qwertz/fr_CH-latin1.kmap.gz', 'ch', 'fr_nodeadkeys'], + #'hr' => ['Croatia', 'hr', 'qwertz/croat.kmap.gz', 'hr', ??], # latin2? + 'hu' => ['Hungarian', 'hu', 'qwertz/hu.kmap.gz', 'hu', undef], + 'is' => ['Icelandic', 'is', 'qwerty/is-latin1.kmap.gz', 'is', 'nodeadkeys'], + 'it' => ['Italian', 'it', 'qwerty/it2.kmap.gz', 'it', 'nodeadkeys'], + 'jp' => ['Japanese', 'ja', 'qwerty/jp106.kmap.gz', 'jp', undef], + 'lt' => ['Lithuanian', 'lt', 'qwerty/lt.kmap.gz', 'lt', 'std'], + #'lv' => ['Latvian', 'lv', 'qwerty/lv-latin4.kmap.gz', 'lv', ??], # latin4 or latin7? + 'mk' => ['Macedonian', 'mk', 'qwerty/mk.kmap.gz', 'mk', 'nodeadkeys'], + 'nl' => ['Dutch', 'nl', 'qwerty/nl.kmap.gz', 'nl', undef], + #'nl-be' => ['Belgium-Dutch', 'nl-be', ?, ?, ?], + 'no' => ['Norwegian', 'no', 'qwerty/no-latin1.kmap.gz', 'no', 'nodeadkeys'], + 'pl' => ['Polish', 'pl', 'qwerty/pl.kmap.gz', 'pl', undef], + 'pt' => ['Portuguese', 'pt', 'qwerty/pt-latin1.kmap.gz', 'pt', 'nodeadkeys'], + 'pt-br' => ['Brazil-Portuguese', 'pt-br', 'qwerty/br-latin1.kmap.gz', 'br', 'nodeadkeys'], + #'ru' => ['Russian', 'ru', 'qwerty/ru.kmap.gz', 'ru', undef], # dont know? + 'si' => ['Slovenian', 'sl', 'qwertz/slovene.kmap.gz', 'si', undef], + 'se' => ['Swedish', 'sv', 'qwerty/se-latin1.kmap.gz', 'se', 'nodeadkeys'], + #'th' => [], + 'tr' => ['Turkish', 'tr', 'qwerty/trq.kmap.gz', 'tr', undef], +}; + +my $kvmkeymaparray = []; +foreach my $lc (keys %$keymaphash) { + push @$kvmkeymaparray, $keymaphash->{$lc}->[1]; +} + sub kvmkeymaps { - return { - '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' ], - 'es' => ['Spanish', 'es', 'qwerty/es.kmap.gz', 'es', 'nodeadkeys'], - #'et' => [], # Ethopia or Estonia ?? - 'fi' => ['Finnish', 'fi', 'qwerty/fi-latin1.kmap.gz', 'fi', 'nodeadkeys'], - #'fo' => ['Faroe Islands', 'fo', ???, 'fo', 'nodeadkeys'], - 'fr' => ['French', 'fr', 'azerty/fr-latin1.kmap.gz', 'fr', 'nodeadkeys'], - 'fr-be' => ['Belgium-French', 'fr-be', 'azerty/be2-latin1.kmap.gz', 'be', 'nodeadkeys'], - 'fr-ca' => ['Canada-French', 'fr-ca', 'qwerty/cf.kmap.gz', 'ca', 'fr-legacy'], - 'fr-ch' => ['Swiss-French', 'fr-ch', 'qwertz/fr_CH-latin1.kmap.gz', 'ch', 'fr_nodeadkeys'], - #'hr' => ['Croatia', 'hr', 'qwertz/croat.kmap.gz', 'hr', ??], # latin2? - 'hu' => ['Hungarian', 'hu', 'qwertz/hu.kmap.gz', 'hu', undef], - 'is' => ['Icelandic', 'is', 'qwerty/is-latin1.kmap.gz', 'is', 'nodeadkeys'], - 'it' => ['Italian', 'it', 'qwerty/it2.kmap.gz', 'it', 'nodeadkeys'], - 'jp' => ['Japanese', 'ja', 'qwerty/jp106.kmap.gz', 'jp', undef], - 'lt' => ['Lithuanian', 'lt', 'qwerty/lt.kmap.gz', 'lt', 'std'], - #'lv' => ['Latvian', 'lv', 'qwerty/lv-latin4.kmap.gz', 'lv', ??], # latin4 or latin7? - 'mk' => ['Macedonian', 'mk', 'qwerty/mk.kmap.gz', 'mk', 'nodeadkeys'], - 'nl' => ['Dutch', 'nl', 'qwerty/nl.kmap.gz', 'nl', undef], - #'nl-be' => ['Belgium-Dutch', 'nl-be', ?, ?, ?], - 'no' => ['Norwegian', 'no', 'qwerty/no-latin1.kmap.gz', 'no', 'nodeadkeys'], - 'pl' => ['Polish', 'pl', 'qwerty/pl.kmap.gz', 'pl', undef], - 'pt' => ['Portuguese', 'pt', 'qwerty/pt-latin1.kmap.gz', 'pt', 'nodeadkeys'], - 'pt-br' => ['Brazil-Portuguese', 'pt-br', 'qwerty/br-latin1.kmap.gz', 'br', 'nodeadkeys'], - #'ru' => ['Russian', 'ru', 'qwerty/ru.kmap.gz', 'ru', undef], # dont know? - 'si' => ['Slovenian', 'sl', 'qwertz/slovene.kmap.gz', 'si', undef], - #'sv' => [], Swedish ? - #'th' => [], - #'tr' => [], - }; + return $keymaphash; +} + +sub kvmkeymaplist { + return $kvmkeymaparray; } sub extract_param { @@ -482,24 +628,110 @@ sub extract_param { return $res; } -sub next_vnc_port { +# 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; + + 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/) { + if ($port == hex($2)) { + close($fh); + return 1; + } + } + } + close($fh); + } + $sleeptime += 100000 if $sleeptime < 1000000; + usleep($sleeptime); + } + + return undef; +} + +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. - for (my $p = 5900; $p < 6000; $p++) { + my $filename = "/var/tmp/pve-reserved-ports"; - my $sock = IO::Socket::INET->new (Listen => 5, - LocalAddr => 'localhost', - LocalPort => $p, - ReuseAddr => 1, - Proto => 0); + my $code = sub { - if ($sock) { - close ($sock); - return $p; + my $expiretime = 5; + my $ctime = time(); + + 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); +} + +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. @@ -542,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}); @@ -554,16 +788,17 @@ sub upid_decode { my $filename; # "UPID:$node:$pid:$pstart:$startime:$dtype:$id:$user" - if ($upid =~ m/^UPID:([A-Za-z][[:alnum:]\-]*[[:alnum:]]+):([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($2); - $res->{pstart} = hex($3); - $res->{starttime} = hex($4); - $res->{type} = $5; - $res->{id} = $6; - $res->{user} = $7; - - my $subdir = substr($4, 7, 8); + $res->{pid} = hex($3); + $res->{pstart} = hex($4); + $res->{starttime} = hex($5); + $res->{type} = $6; + $res->{id} = $7; + $res->{user} = $8; + + my $subdir = substr($5, 7, 8); $filename = "$pvetaskdir/$subdir/$upid"; } else { @@ -589,7 +824,7 @@ sub upid_open { my $outfh = IO::File->new ($filename, O_WRONLY|O_CREAT|O_EXCL, $perm) || die "unable to create output file '$filename' - $!\n"; - chown $wwwid, $outfh; + chown $wwwid, -1, $outfh; return $outfh; }; @@ -600,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); @@ -635,5 +870,130 @@ sub decode_text { return Encode::decode("utf8", uri_unescape($data)); } +sub decode_utf8_parameters { + my ($param) = @_; + + foreach my $p (qw(comment description firstname lastname)) { + $param->{$p} = decode('utf8', $param->{$p}) if $param->{$p}; + } + + return $param; +} + +sub random_ether_addr { + + my $rand = Digest::SHA::sha1_hex(rand(), time()); + + my $mac = ''; + for (my $i = 0; $i < 6; $i++) { + my $ss = hex(substr($rand, $i*2, 2)); + if (!$i) { + $ss &= 0xfe; # clear multicast + $ss |= 2; # set local id + } + $ss = sprintf("%02X", $ss); + + if (!$i) { + $mac .= "$ss"; + } else { + $mac .= ":$ss"; + } + } + + return $mac; +} + +sub shellquote { + my $str = shift; + + return String::ShellQuote::shell_quote($str); +} + +sub cmd2string { + my ($cmd) = @_; + + die "no arguments" if !$cmd; + + return $cmd if !ref($cmd); + + my @qa = (); + foreach my $arg (@$cmd) { push @qa, shellquote($arg); } + + return join (' ', @qa); +} + +# split an shell argument string into an array, +sub split_args { + my ($str) = @_; + + return $str ? [ Text::ParseWords::shellwords($str) ] : []; +} + +sub dump_logfile { + my ($filename, $start, $limit) = @_; + + my $lines = []; + my $count = 0; + + my $fh = IO::File->new($filename, "r"); + if (!$fh) { + $count++; + push @$lines, { n => $count, t => "unable to open file - $!"}; + return ($count, $lines); + } + + $start = 0 if !$start; + $limit = 50 if !$limit; + + my $line; + while (defined($line = <$fh>)) { + next if $count++ < $start; + next if $limit <= 0; + chomp $line; + push @$lines, { n => $count, t => $line}; + $limit--; + } + + close($fh); + + # HACK: ExtJS store.guaranteeRange() does not like empty array + # so we add a line + if (!$count) { + $count++; + push @$lines, { n => $count, t => "no content"}; + } + + return ($count, $lines); +} + +sub dir_glob_regex { + my ($dir, $regex) = @_; + + my $dh = IO::Dir->new ($dir); + return wantarray ? () : undef if !$dh; + + while (defined(my $tmp = $dh->read)) { + if (my @res = $tmp =~ m/^($regex)$/) { + $dh->close; + return wantarray ? @res : $tmp; + } + } + $dh->close; + + return wantarray ? () : undef; +} + +sub dir_glob_foreach { + my ($dir, $regex, $func) = @_; + + my $dh = IO::Dir->new ($dir); + if (defined $dh) { + while (defined(my $tmp = $dh->read)) { + if (my @res = $tmp =~ m/^($regex)$/) { + &$func (@res); + } + } + } +} 1;