]> git.proxmox.com Git - pve-manager.git/blame - lib/PVE.old/Utils.pm
imported from svn 'pve-manager/pve2'
[pve-manager.git] / lib / PVE.old / Utils.pm
CommitLineData
aff192e6
DM
1package PVE::Utils;
2
3use strict;
4use POSIX qw (:sys_wait_h strftime);
5use PVE::pvecfg;
6use IPC::Open3;
7use IO::File;
8use IO::Select;
9use PVE::SafeSyslog;
10use Authen::PAM qw(:constants);
11use Time::HiRes qw (gettimeofday);
12use Digest::SHA1;
13use Encode;
14
15my $clock_ticks = POSIX::sysconf(&POSIX::_SC_CLK_TCK);
16
17# access control
18
19my $accmode = {
20 root => [[ '/', 'w' ]],
21 audit => [[ '/', 'r' ]],
22};
23
24my $accmode_cnode = {
25 root => [[ '/server/' , 'w' ],
26 [ '/logs/', 'w' ],
27 [ '/system/options.htm', 'r' ],
28 [ '/system/', 'w' ],
29 [ '/', 'r' ],
30 ],
31 audit => [[ '/', 'r' ]],
32};
33
34sub get_access_mode {
35 my ($username, $group, $uri, $role) = @_;
36
37 my $alist;
38 if ($role eq 'N') {
39 $alist = $accmode_cnode->{$group};
40 } else {
41 $alist = $accmode->{$group};
42 }
43 return undef if !$alist;
44
45 foreach my $am (@$alist) {
46 my ($d, $m) = @$am;
47 return $m if $uri =~ m/^$d/;
48 }
49
50 return undef;
51}
52
53# authentication tickets
54
55sub load_auth_secret {
56 my $secret = (split (/\s/, `md5sum /etc/pve/pve-root-ca.key`))[0];
57
58 die "unable to load authentication secret\n" if !$secret;
59
60 return $secret;
61}
62
63sub create_auth_ticket {
64 my ($secret, $username, $group) = @_;
65
66 my $timestamp = time();
67 my $ticket = $username . '::' . $group . '::' . $timestamp . '::' .
68 Digest::SHA1::sha1_hex($username, $group, $timestamp, $secret);
69
70 return $ticket;
71}
72
73sub verify_username {
74 my $username = shift;
75
76 # we only allow a limited set of characters (colon is not allowed,
77 # because we store usernames in colon separated lists)!
78 return $username if $username =~ m/^[A-Za-z0-9\.\-_]+(\@[A-Za-z0-9\.\-_]+)?$/;
79
80 return undef;
81}
82
83sub verify_ticket {
84 my ($secret, $ticket) = @_;
85
86 my $cookie_timeout = 2400; # seconds
87
88 my ($username, $group, $time, $mac) = split /::/, $ticket;
89
90 return undef if !verify_username($username);
91
92 my $age = time() - $time;
93
94 if (($age > -300) && ($age < $cookie_timeout) &&
95 (Digest::SHA1::sha1_hex($username, $group, $time, $secret) eq $mac)) {
96 return wantarray ? ($username, $group, $age) : $username;
97 }
98
99 return undef;
100}
101
102sub verify_web_ticket {
103 my ($secret, $ticket) = @_;
104
105 my $cookie_timeout = 2400; # seconds
106
107 my ($username, $group, $time, $mac, $webmac) = split /::/, $ticket;
108
109 return undef if !verify_username($username);
110
111 my $age = time() - $time;
112
113 if (($age > -300) && ($age < $cookie_timeout) &&
114 (Digest::SHA1::sha1_hex($username, $group, $time, $mac, $secret) eq $webmac)) {
115 return wantarray ? ($username, $group, $age) : $username;
116 }
117
118 return undef;
119}
120
121# password should be utf8 encoded
122sub pam_is_valid_user {
123 my ($username, $password) = @_;
124
125 # user (www-data) need to be able to read /etc/passwd /etc/shadow
126
127 my $pamh = new Authen::PAM ('common-auth', $username, sub {
128 my @res;
129 while(@_) {
130 my $msg_type = shift;
131 my $msg = shift;
132 push @res, (0, $password);
133 }
134 push @res, 0;
135 return @res;
136 });
137
138 if (!ref ($pamh)) {
139 my $err = $pamh->pam_strerror($pamh);
140 die "Error during PAM init: $err";
141 }
142
143 my $res;
144
145 if (($res = $pamh->pam_authenticate(0)) != PAM_SUCCESS) {
146 my $err = $pamh->pam_strerror($res);
147 die "PAM auth failed: $err\n";
148 }
149
150 if (($res = $pamh->pam_acct_mgmt (0)) != PAM_SUCCESS) {
151 my $err = $pamh->pam_strerror($res);
152 die "PAM auth failed: $err\n";
153 }
154
155 $pamh = 0; # call destructor
156
157 return 1;
158}
159
160sub is_valid_user {
161 my ($username, $password) = @_;
162
163 if (!verify_username ($username)) {
164 syslog ('info', "auth failed: invalid characters in username '$username'");
165 return undef;
166 }
167
168 my $valid = 0;
169
170 eval {
171 $valid = pam_is_valid_user ($username, $password);
172 };
173 my $err = $@;
174
175 if ($err) {
176 syslog ('info', $err);
177 return undef;
178 }
179
180 return undef if !$valid;
181
182 my ($name, $passwd, $uid, $gid) = getpwnam ($username);
183 my $groupname = getgrgid($gid) || 'nogroup';
184
185 # fixme: what groups are allowed?
186 if ($groupname ne 'root') {
187 syslog ('info', "auth failed: group '$groupname' is not in the list of allowed groups");
188 return undef;
189 }
190
191 return $groupname;
192}
193
194# UPID helper
195# WARN: $res->{filename} must not depend on PID, because we
196# use it before we know the PID
197
198sub upid_decode {
199 my $upid = shift;
200
201 my $res;
202
203 # "UPID:$pid:$start:$type:$data"
204 if ($upid =~ m/^UPID:(\d+)(-(\d+))?:(\d+):([^:\s]+):(.*)$/) {
205 $res->{pid} = $1;
206 $res->{pstart} = $3 || 0;
207 $res->{starttime} = $4;
208 $res->{type} = $5;
209 $res->{data} = $6;
210
211 if ($res->{type} eq 'vmops') {
212 if ($res->{data} =~ m/^([^:\s]+):(\d+):(\d+):(\S+)$/) {
213 $res->{command} = $1;
214 $res->{cid} = $2;
215 $res->{veid} = $3;
216 $res->{user} = $4;
217
218 $res->{filename} = "/tmp/vmops-$res->{veid}.out";
219 } else {
220 return undef;
221 }
222 } elsif ($res->{type} eq 'apldownload') {
223 if ($res->{data} =~ m/^([^:\s]+):(.+)$/) {
224 $res->{user} = $1;
225 $res->{apl} = $2;
226 $res->{filename} = "/tmp/apldownload-$res->{user}.out";
227 } else {
228 return undef;
229 }
230 }
231 }
232
233 return $res;
234}
235
236sub upid_encode {
237 my $uip_hash = shift;
238
239 my $d = $uip_hash; # shortcut
240
241 return "UPID:$d->{pid}-$d->{pstart}:$d->{starttime}:$d->{type}:$d->{data}";
242}
243
244
245# save $SIG{CHLD} handler implementation.
246# simply set $SIG{CHLD} = &PVE::Utils::worker_reaper;
247# and register forked processes with PVE::Utils::register_worker(pid)
248# Note: using $SIG{CHLD} = 'IGNORE' or $SIG{CHLD} = sub { wait (); } or ...
249# has serious side effects, because perls built in system() and open()
250# functions can't get the correct exit status of a child. So we cant use
251# that (also see perlipc)
252
253my $WORKER_PIDS;
254
255sub worker_reaper {
256 local $!; local $?;
257 foreach my $pid (keys %$WORKER_PIDS) {
258 my $waitpid = waitpid ($pid, WNOHANG);
259 if (defined($waitpid) && ($waitpid == $pid)) {
260 delete ($WORKER_PIDS->{$pid});
261 }
262 }
263}
264
265sub register_worker {
266 my $pid = shift;
267
268 return if !$pid;
269
270 # do not register if already finished
271 my $waitpid = waitpid ($pid, WNOHANG);
272 if (defined($waitpid) && ($waitpid == $pid)) {
273 delete ($WORKER_PIDS->{$pid});
274 return;
275 }
276
277 $WORKER_PIDS->{$pid} = 1;
278}
279
280sub trim {
281 my $s = shift;
282
283 return $s if !$s;
284
285 $s =~ s/^\s*//;
286 $s =~ s/\s*$//;
287
288 return $s;
289}
290
291sub foreach_vmrec {
292 my ($vmhash, $func) = @_;
293
294 foreach my $ckey (keys %$vmhash) {
295 next if $ckey !~ m/^CID_(\d+)$/;
296 my $cid = $1;
297 if (my $vmlist = $vmhash->{$ckey}) {
298 foreach my $vmkey (sort keys %$vmlist) {
299 next if $vmkey !~ m/^VEID_(\d+)$/;
300 my $vmid = $1;
301 my $d = $vmlist->{$vmkey};
302 &$func ($cid, $vmid, $d, $ckey, $vmkey);
303 }
304 }
305 }
306}
307
308sub foreach_cid {
309 my ($vmhash, $func) = @_;
310
311 foreach my $ckey (keys %$vmhash) {
312 next if $ckey !~ m/^CID_(\d+)$/;
313 my $cid = $1;
314 if (my $vmlist = $vmhash->{$ckey}) {
315 &$func ($cid, $vmlist, $ckey);
316 }
317 }
318}
319
320sub foreach_veid {
321 my ($vmlist, $func) = @_;
322
323 foreach my $vmkey (keys %$vmlist) {
324 next if $vmkey !~ m/^VEID_(\d+)$/;
325 my $veid = $1;
326 if (my $d = $vmlist->{$vmkey}) {
327 &$func ($veid, $d, $vmkey);
328 }
329 }
330}
331
332sub foreach_veid_sorted {
333 my ($vmlist, $func) = @_;
334
335 my @vma = ();
336 foreach my $vmkey (keys %$vmlist) {
337 next if $vmkey !~ m/^VEID_(\d+)$/;
338 push @vma, $1;
339 }
340
341 foreach my $vmid (sort @vma) {
342 my $vmkey = "VEID_$vmid";
343 if (my $d = $vmlist->{$vmkey}) {
344 &$func ($vmid, $d, $vmkey);
345 }
346 }
347}
348
349sub read_proc_uptime {
350 my $ticks = shift;
351
352 my $uptime;
353 my $fh = IO::File->new ("/proc/uptime", "r");
354 if (defined ($fh)) {
355 my $line = <$fh>;
356 $fh->close;
357
358 if ($line =~ m|^(\d+\.\d+)\s+(\d+\.\d+)\s*$|) {
359 if ($ticks) {
360 return (int($1*100), int($2*100));
361 } else {
362 return (int($1), int($2));
363 }
364 }
365 }
366
367 return (0, 0);
368}
369
370sub read_proc_starttime {
371 my $pid = shift;
372
373 my $statstr;
374 my $fh = IO::File->new ("/proc/$pid/stat", "r");
375 if (defined ($fh)) {
376 $statstr = <$fh>;
377 $fh->close;
378 }
379
380 if ($statstr =~ m/^$pid \(.*\) \S (-?\d+) -?\d+ -?\d+ -?\d+ -?\d+ \d+ \d+ \d+ \d+ \d+ (\d+) (\d+) (-?\d+) (-?\d+) -?\d+ -?\d+ -?\d+ 0 (\d+) (\d+) (-?\d+) \d+ \d+ \d+ \d+ \d+ \d+ \d+ \d+ \d+ \d+ \d+ \d+ \d+ -?\d+ -?\d+ \d+ \d+ \d+/) {
381 my $ppid = $1;
382 my $starttime = $6;
383
384 return $starttime;
385 }
386
387 return 0;
388}
389
390sub check_process {
391 my ($pid, $pstart) = @_;
392
393 my $st = read_proc_starttime ($pid);
394
395 return 0 if !$st;
396
397 return $st == $pstart;
398}
399
400my $last_proc_stat;
401
402sub read_proc_stat {
403 my $uptime;
404
405 my $res = { user => 0, nice => 0, system => 0, idle => 0 , sum => 0};
406
407 my $cpucount = 0;
408
409 if (my $fh = IO::File->new ("/proc/stat", "r")) {
410 while (defined (my $line = <$fh>)) {
411 if ($line =~ m|^cpu\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s|) {
412 $res->{user} = $1;
413 $res->{nice} = $2;
414 $res->{system} = $3;
415 $res->{idle} = $4;
416 $res->{used} = $1+$2+$3;
417 $res->{iowait} = $5;
418 } elsif ($line =~ m|^cpu\d+\s|) {
419 $cpucount++;
420 }
421 }
422 $fh->close;
423 }
424
425 $cpucount = 1 if !$cpucount;
426
427 my $ctime = gettimeofday; # floating point time in seconds
428
429 $res->{ctime} = $ctime;
430 $res->{cpu} = 0;
431 $res->{wait} = 0;
432
433 $last_proc_stat = $res if !$last_proc_stat;
434
435 my $diff = ($ctime - $last_proc_stat->{ctime}) * $clock_ticks * $cpucount;
436
437 if ($diff > 1000) { # don't update too often
438 my $useddiff = $res->{used} - $last_proc_stat->{used};
439 $useddiff = $diff if $useddiff > $diff;
440 $res->{cpu} = $useddiff/$diff;
441 my $waitdiff = $res->{iowait} - $last_proc_stat->{iowait};
442 $waitdiff = $diff if $waitdiff > $diff;
443 $res->{wait} = $waitdiff/$diff;
444 $last_proc_stat = $res;
445 } else {
446 $res->{cpu} = $last_proc_stat->{cpu};
447 $res->{wait} = $last_proc_stat->{wait};
448 }
449
450 return $res;
451}
452
453sub get_uptime {
454
455 my $res = { uptime => 0, idle => 0, avg1 => 0, avg5 => 0, avg15 => 0 };
456
457 my $fh = IO::File->new ('/proc/loadavg', "r");
458 my $line = <$fh>;
459 $fh->close;
460
461 if ($line =~ m|^(\d+\.\d+)\s+(\d+\.\d+)\s+(\d+\.\d+)\s+\d+/\d+\s+\d+\s*$|) {
462 $res->{avg1} = $1;
463 $res->{avg5} = $2;
464 $res->{avg15} = $3;
465 }
466
467 ($res->{uptime}, $res->{idle}) = read_proc_uptime();
468
469 my $ut = $res->{uptime};
470 my $days = int ($ut / 86400);
471 $ut -= $days*86400;
472 my $hours = int ($ut / 3600);
473 $ut -= $hours*3600;
474 my $mins = $ut /60;
475
476 my $utstr = strftime ("%H:%M:%S up ", localtime);
477 if ($days) {
478 my $ds = $days > 1 ? 'days' : 'day';
479 $res->{uptimestrshort} = sprintf "%d $ds %02d:%02d", $days, $hours, $mins;
480 } else {
481 $res->{uptimestrshort} = sprintf "%02d:%02d", $hours, $mins;
482 }
483
484 $utstr .= "$res->{uptimestrshort}, ";
485 $utstr .= "load average: $res->{avg1}, $res->{avg5}, $res->{avg15}";
486 $res->{uptimestr} = $utstr;
487
488 return $res;
489}
490
491
492# memory usage of current process
493sub get_mem_usage {
494
495 my $res = { size => 0, resident => 0, shared => 0 };
496
497 my $ps = 4096;
498
499 open (MEMINFO, "</proc/$$/statm");
500 my $line = <MEMINFO>;
501 close (MEMINFO);
502
503 if ($line =~ m/^(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+/) {
504 $res->{size} = $1*$ps;
505 $res->{resident} = $2*$ps;
506 $res->{shared} = $3*$ps;
507 }
508
509 return $res;
510}
511
512sub get_memory_info {
513
514 my $res = {
515 memtotal => 0,
516 memfree => 0,
517 memused => 0,
518 swaptotal => 0,
519 swapfree => 0,
520 swapused => 0,
521 };
522
523 open (MEMINFO, "/proc/meminfo");
524
525 while (my $line = <MEMINFO>) {
526 if ($line =~ m/^(\S+):\s+(\d+)\s*kB/i) {
527 $res->{lc ($1)} = $2;
528 }
529 }
530
531 close (MEMINFO);
532
533 $res->{memused} = $res->{memtotal} - $res->{memfree};
534 $res->{swapused} = $res->{swaptotal} - $res->{swapfree};
535
536 $res->{mbmemtotal} = int ($res->{memtotal}/1024);
537 $res->{mbmemfree} = int (($res->{memfree} + $res->{buffers} + $res->{cached})/1024);
538 $res->{mbmemused} = $res->{mbmemtotal} - $res->{mbmemfree};
539
540 $res->{mbswaptotal} = int ($res->{swaptotal}/1024);
541 $res->{mbswapfree} = int ($res->{swapfree}/1024);
542 $res->{mbswapused} = $res->{mbswaptotal} - $res->{mbswapfree};
543
544 return $res;
545}
546
547sub get_hd_info {
548 my ($dir) = @_;
549
550 $dir = '/' if !$dir;
551
552 my $hd = `df -P '$dir'`;
553
554 # simfs ... openvz
555 # vzfs ... virtuozzo
556
557 my ($rootfs, $hdo_total, $hdo_used, $hdo_avail) = $hd =~
558 m/^(simfs|vzfs|\/dev\/\S+)\s+(\d+)\s+(\d+)\s+(\d+)\s+\d+%\s.*$/mg;
559
560 my $real_hd_used = int ($hdo_used/1024);
561 my $real_hd_total = int ($hdo_total/1024);
562
563 # available memory = total memory - reserved memory
564 my $real_hd_avail = int (($hdo_used+$hdo_avail)/1024);
565
566 return { total => $real_hd_total,
567 avail => $real_hd_avail,
568 used => $real_hd_used,
569 free => $real_hd_avail - $real_hd_used
570 };
571}
572
573my $cpuinfo;
574
575# cycles_per_jiffy = frequency_of_your_cpu/jiffies_per_second
576# jiffies_per_second = 1000
577
578# frequency_of_your_cpu can be read from /proc/cpuinfo, as:
579# cpu MHz : <frequency_of_your_cpu>
580
581sub get_cpu_info {
582 my $fn = '/proc/cpuinfo';
583
584 return $cpuinfo if $cpuinfo;
585
586 open (CPUINFO, "<$fn");
587
588 my $res;
589
590 $res->{model} = 'unknown';
591 $res->{mhz} = 0;
592 $res->{cpus} = 0;
593 $res->{cpu_cycles_per_jiffy} = 0; # just to be not 0
594
595 #$cpu_total = 0;
596
597 my $count = 0;
598 while (my $line = <CPUINFO>) {
599 if ($line =~ m/^processor\s*:\s*\d+\s*$/i) {
600 $count++;
601 } elsif ($line =~ m/^model\s+name\s*:\s*(.*)\s*$/i) {
602 $res->{model} = $1 if $res->{model} eq 'unknown';
603 } elsif ($line =~ m/^cpu\s+MHz\s*:\s*(\d+\.\d+)\s*$/i) {
604 #$cpu_total += $1;
605 $res->{mhz} = $1 if !$res->{mhz};
606 $res->{cpu_cycles_per_jiffy} += $1 * 1000;
607 } elsif ($line =~ m/^flags\s*:.*(vmx|svm)/) {
608 $res->{hvm} = 1; # Hardware Virtual Machine (Intel VT / AMD-V)
609 }
610 }
611
612 $res->{cpus} = $count;
613
614 close (CPUINFO);
615
616 $res->{kversion} = `uname -srv`;
617
618 $res->{proxversion} = PVE::pvecfg::package() . "/" .
619 PVE::pvecfg::version() . "/" .
620 PVE::pvecfg::repoid();
621
622 $cpuinfo = $res;
623
624 return $res;
625}
626
627sub get_bridges {
628
629 my $res = [];
630
631 my $line;
632 my $fd2;
633
634 if ($fd2 = IO::File->new ("/proc/net/dev", "r")) {
635 while (defined ($line = <$fd2>)) {
636 chomp ($line);
637 if ($line =~ m/^\s*(vmbr([0-9]{1,3})):.*/) {
638 my ($name, $num) = ($1, $2);
639 push @$res, $name if int($num) eq $num; # no leading zero
640 }
641 }
642 close ($fd2);
643 }
644
645 return $res;
646}
647
648sub run_command {
649 my ($cmd, $input, $timeout) = @_;
650
651 my $reader = IO::File->new();
652 my $writer = IO::File->new();
653 my $error = IO::File->new();
654
655 my $cmdstr = join (' ', @$cmd);
656
657 my $orig_pid = $$;
658
659 my $pid;
660 eval {
661 $pid = open3 ($writer, $reader, $error, @$cmd) || die $!;
662 };
663
664 my $err = $@;
665
666 # catch exec errors
667 if ($orig_pid != $$) {
668 syslog ('err', "ERROR: $err");
669 POSIX::_exit (1);
670 kill ('KILL', $$);
671 }
672
673 die $err if $err;
674
675 print $writer $input if defined $input;
676 close $writer;
677
678 my $select = new IO::Select;
679 $select->add ($reader);
680 $select->add ($error);
681
682 my ($ostream, $estream) = ('', '');
683
684 while ($select->count) {
685 my @handles = $select->can_read ($timeout);
686
687 if (defined ($timeout) && (scalar (@handles) == 0)) {
688 kill (9, $pid);
689 waitpid ($pid, 0);
690 die "command '$cmdstr' failed: timeout";
691 }
692
693 foreach my $h (@handles) {
694 my $buf = '';
695 my $count = sysread ($h, $buf, 4096);
696 if (!defined ($count)) {
697 my $err = $!;
698 kill (9, $pid);
699 waitpid ($pid, 0);
700 die "command '$cmdstr' failed: $err";
701 }
702 $select->remove ($h) if !$count;
703 if ($h eq $reader) {
704 $ostream .= $buf;
705 } elsif ($h eq $error) {
706 $ostream .= $buf;
707 $estream .= $buf;
708 }
709 }
710 }
711
712 my $rv = waitpid ($pid, 0);
713 my $ec = ($? >> 8);
714
715 if ($ec) {
716 if ($estream) {
717 die "command '$cmdstr' failed with exit code $ec:\n$estream";
718 }
719 die "command '$cmdstr' failed with exit code $ec";
720 }
721
722 return $ostream;
723}
724
725sub _encrypt_pw {
726 my ($pw) = @_;
727
728 my $time = substr (Digest::SHA1::sha1_base64 (time), 0, 8);
729 return crypt (encode("utf8", $pw), "\$1\$$time\$");
730}
731
732sub modify_user {
733 my ($username, $group, $pw, $comment, $rawpw) = @_;
734
735 my $cmd = ['/usr/sbin/usermod'];
736
737 push @$cmd, '-c', $comment if defined ($comment);
738
739 if ($pw) {
740 my $epw = $rawpw ? $pw :_encrypt_pw ($pw);
741 push @$cmd, '-p', $epw;
742 }
743
744 push @$cmd, '-g', $group if $group && $username ne 'root';
745
746 return if scalar (@$cmd) == 1 ; # no flags given
747
748 push @$cmd, $username;
749
750 run_command ($cmd);
751}
752
753sub kvmkeymaps {
754 return {
755 'dk' => ['Danish', 'da', 'qwerty/dk-latin1.kmap.gz', 'dk', 'nodeadkeys'],
756 'de' => ['German', 'de', 'qwertz/de-latin1-nodeadkeys.kmap.gz', 'de', 'nodeadkeys' ],
757 'de-ch' => ['Swiss-German', 'de-ch', 'qwertz/sg-latin1.kmap.gz', 'ch', 'de_nodeadkeys' ],
758 'en-gb' => ['United Kingdom', 'en-gb', 'qwerty/uk.kmap.gz' , 'gb', 'intl' ],
759 'en-us' => ['U.S. English', 'en-us', 'qwerty/us-latin1.kmap.gz', 'us', 'intl' ],
760 'es' => ['Spanish', 'es', 'qwerty/es.kmap.gz', 'es', 'nodeadkeys'],
761 #'et' => [], # Ethopia or Estonia ??
762 'fi' => ['Finnish', 'fi', 'qwerty/fi-latin1.kmap.gz', 'fi', 'nodeadkeys'],
763 #'fo' => ['Faroe Islands', 'fo', ???, 'fo', 'nodeadkeys'],
764 'fr' => ['French', 'fr', 'azerty/fr-latin1.kmap.gz', 'fr', 'nodeadkeys'],
765 'fr-be' => ['Belgium-French', 'fr-be', 'azerty/be2-latin1.kmap.gz', 'be', 'nodeadkeys'],
766 'fr-ca' => ['Canada-French', 'fr-ca', 'qwerty/cf.kmap.gz', 'ca', 'fr-legacy'],
767 'fr-ch' => ['Swiss-French', 'fr-ch', 'qwertz/fr_CH-latin1.kmap.gz', 'ch', 'fr_nodeadkeys'],
768 #'hr' => ['Croatia', 'hr', 'qwertz/croat.kmap.gz', 'hr', ??], # latin2?
769 'hu' => ['Hungarian', 'hu', 'qwertz/hu.kmap.gz', 'hu', undef],
770 'is' => ['Icelandic', 'is', 'qwerty/is-latin1.kmap.gz', 'is', 'nodeadkeys'],
771 'it' => ['Italian', 'it', 'qwerty/it2.kmap.gz', 'it', 'nodeadkeys'],
772 'jp' => ['Japanese', 'ja', 'qwerty/jp106.kmap.gz', 'jp', undef],
773 'lt' => ['Lithuanian', 'lt', 'qwerty/lt.kmap.gz', 'lt', 'std'],
774 #'lv' => ['Latvian', 'lv', 'qwerty/lv-latin4.kmap.gz', 'lv', ??], # latin4 or latin7?
775 'mk' => ['Macedonian', 'mk', 'qwerty/mk.kmap.gz', 'mk', 'nodeadkeys'],
776 'nl' => ['Dutch', 'nl', 'qwerty/nl.kmap.gz', 'nl', undef],
777 #'nl-be' => ['Belgium-Dutch', 'nl-be', ?, ?, ?],
778 'no' => ['Norwegian', 'no', 'qwerty/no-latin1.kmap.gz', 'no', 'nodeadkeys'],
779 'pl' => ['Polish', 'pl', 'qwerty/pl.kmap.gz', 'pl', undef],
780 'pt' => ['Portuguese', 'pt', 'qwerty/pt-latin1.kmap.gz', 'pt', 'nodeadkeys'],
781 'pt-br' => ['Brazil-Portuguese', 'pt-br', 'qwerty/br-latin1.kmap.gz', 'br', 'nodeadkeys'],
782 #'ru' => ['Russian', 'ru', 'qwerty/ru.kmap.gz', 'ru', undef], # dont know?
783 'si' => ['Slovenian', 'sl', 'qwertz/slovene.kmap.gz', 'si', undef],
784 #'sv' => [], Swedish ?
785 #'th' => [],
786 #'tr' => [],
787 };
788}
789
790sub debmirrors {
791
792 return {
793 'at' => 'ftp.at.debian.org',
794 'au' => 'ftp.au.debian.org',
795 'be' => 'ftp.be.debian.org',
796 'bg' => 'ftp.bg.debian.org',
797 'br' => 'ftp.br.debian.org',
798 'ca' => 'ftp.ca.debian.org',
799 'ch' => 'ftp.ch.debian.org',
800 'cl' => 'ftp.cl.debian.org',
801 'cz' => 'ftp.cz.debian.org',
802 'de' => 'ftp.de.debian.org',
803 'dk' => 'ftp.dk.debian.org',
804 'ee' => 'ftp.ee.debian.org',
805 'es' => 'ftp.es.debian.org',
806 'fi' => 'ftp.fi.debian.org',
807 'fr' => 'ftp.fr.debian.org',
808 'gr' => 'ftp.gr.debian.org',
809 'hk' => 'ftp.hk.debian.org',
810 'hr' => 'ftp.hr.debian.org',
811 'hu' => 'ftp.hu.debian.org',
812 'ie' => 'ftp.ie.debian.org',
813 'is' => 'ftp.is.debian.org',
814 'it' => 'ftp.it.debian.org',
815 'jp' => 'ftp.jp.debian.org',
816 'kr' => 'ftp.kr.debian.org',
817 'mx' => 'ftp.mx.debian.org',
818 'nl' => 'ftp.nl.debian.org',
819 'no' => 'ftp.no.debian.org',
820 'nz' => 'ftp.nz.debian.org',
821 'pl' => 'ftp.pl.debian.org',
822 'pt' => 'ftp.pt.debian.org',
823 'ro' => 'ftp.ro.debian.org',
824 'ru' => 'ftp.ru.debian.org',
825 'se' => 'ftp.se.debian.org',
826 'si' => 'ftp.si.debian.org',
827 'sk' => 'ftp.sk.debian.org',
828 'tr' => 'ftp.tr.debian.org',
829 'tw' => 'ftp.tw.debian.org',
830 'gb' => 'ftp.uk.debian.org',
831 'us' => 'ftp.us.debian.org',
832 };
833}
834
835sub shellquote {
836 my $str = shift;
837
838 return "''" if !defined ($str) || ($str eq '');
839
840 die "unable to quote string containing null (\\000) bytes"
841 if $str =~ m/\x00/;
842
843 # from String::ShellQuote
844 if ($str =~ m|[^\w!%+,\-./:@^]|) {
845
846 # ' -> '\''
847 $str =~ s/'/'\\''/g;
848
849 $str = "'$str'";
850 $str =~ s/^''//;
851 $str =~ s/''$//;
852 }
853
854 return $str;
855}
856
857sub service_cmd {
858 my ($service, $cmd) = @_;
859
860 my $initd_cmd;
861
862 ($cmd eq 'start' || $cmd eq 'stop' || $cmd eq 'restart'
863 || $cmd eq 'reload' || $cmd eq 'awaken') ||
864 die "unknown service command '$cmd': ERROR";
865
866 if ($service eq 'postfix') {
867 $initd_cmd = '/etc/init.d/postfix';
868 } elsif ($service eq 'pvemirror') {
869 $initd_cmd = '/etc/init.d/pvemirror';
870 } elsif ($service eq 'pvetunnel') {
871 $initd_cmd = '/etc/init.d/pvetunnel';
872 } elsif ($service eq 'pvedaemon') {
873 $initd_cmd = '/etc/init.d/pvedaemon';
874 } elsif ($service eq 'apache') {
875 if ($cmd eq 'restart') {
876 $initd_cmd = '/usr/sbin/apache2ctl';
877 $cmd = 'graceful';
878 } else {
879 die "invalid service cmd 'apache $cmd': ERROR";
880 }
881 } elsif ($service eq 'network') {
882 if ($cmd eq 'restart') {
883 return system ('(sleep 1; /etc/init.d/networking restart; /etc/init.d/postfix restart; /usr/sbin/apache2ctl graceful)&');
884 }
885 die "invalid service cmd 'network $cmd': ERROR";
886 } elsif ($service eq 'ntpd') {
887 # debian start/stop scripts does not work for us
888 if ($cmd eq 'stop') {
889 system ('/etc/init.d/ntp stop');
890 #system ('/usr/bin/killall /usr/sbin/ntpd');
891 } elsif ($cmd eq 'start') {
892 system ('/etc/init.d/ntp start');
893 system ('/sbin/hwclock --systohc');
894 } elsif ($cmd eq 'restart') {
895 system ('/etc/init.d/ntp restart');
896 system ('/sbin/hwclock --systohc');
897 # restart cron/syslog to get right schedules and log time/dates
898 system ('/etc/init.d/sysklogd restart');
899 system ('/etc/init.d/cron restart');
900 }
901 return 0;
902 } elsif ($service eq 'syslog') {
903 $initd_cmd = '/etc/init.d/sysklogd';
904 } elsif ($service eq 'cron') {
905 $initd_cmd = '/etc/init.d/cron';
906 } elsif ($service eq 'sshd') {
907 $initd_cmd = '/etc/init.d/ssh';
908 } else {
909 die "unknown service '$service': ERROR";
910 }
911
912 my $servicecmd = "$initd_cmd $cmd";
913
914 my $res = run_command ([$initd_cmd, $cmd]);
915
916 return $res;
917}
918
919sub service_state {
920 my ($service) = @_;
921
922 my $pid_file;
923
924 if ($service eq 'postfix') {
925 $pid_file = '/var/spool/postfix/pid/master.pid';
926 } elsif ($service eq 'apache') {
927 $pid_file = '/var/run/apache2.pid';
928 } elsif ($service eq 'bind') {
929 $pid_file = '/var/run/bind/run/named.pid';
930 } elsif ($service eq 'pvemirror') {
931 $pid_file = '/var/run/pvemirror.pid';
932 } elsif ($service eq 'pvetunnel') {
933 $pid_file = '/var/run/pvetunnel.pid';
934 } elsif ($service eq 'pvedaemon') {
935 $pid_file = '/var/run/pvedaemon.pid';
936 } elsif ($service eq 'ntpd') {
937 $pid_file = '/var/run/ntpd.pid';
938 } elsif ($service eq 'sshd') {
939 $pid_file = '/var/run/sshd.pid';
940 } else {
941 die "unknown service '$service': ERROR";
942 }
943
944 my $pid;
945 if (my $fh = IO::File->new ($pid_file, "r")) {
946 my $line = <$fh>;
947 chomp $line;
948
949 if ($line && ($line =~ m/^\s*(\d+)\s*$/)) {
950 $pid = $1;
951 }
952 }
953
954 return 'running' if ($pid && kill (0, $pid));
955
956 return 'stopped';
957};
958
959sub service_wait_stopped {
960 my ($timeout, @services) = @_;
961
962 my $starttime = time();
963
964 while (1) {
965 my $wait = 0;
966
967 foreach my $s (@services) {
968 if (service_state ($s) eq 'running') {
969
970 if ((time() - $starttime) > $timeout) {
971 die "unable to stop services (got timeout)\n";
972 }
973
974 service_cmd ($s, 'stop');
975 $wait = 1;
976 }
977 }
978
979 if ($wait) {
980 sleep (1);
981 } else {
982 last;
983 }
984 }
985}
986
987sub check_vm_settings {
988 my ($settings) = @_;
989
990 if (defined ($settings->{mem})) {
991
992 my $max = 65536;
993 my $min = 64;
994
995 if ($settings->{mem} < $min) {
996 die __("Memory needs to be at least $min MB") . "\n";
997 }
998 if ($settings->{mem} > $max) {
999 die __("Memory needs to be less than $max MB") . "\n";
1000 }
1001 }
1002
1003 if (defined ($settings->{swap})) {
1004
1005 my $max = 65536;
1006
1007 if ($settings->{swap} > $max) {
1008 die __("Swap needs to be less than $max MB") . "\n";
1009 }
1010 }
1011
1012 if (defined ($settings->{cpuunits}) &&
1013 ($settings->{cpuunits} < 8 || $settings->{cpuunits} > 500000)) {
1014 die "parameter cpuunits out of range\n";
1015 }
1016
1017 if (defined ($settings->{cpus}) &&
1018 ($settings->{cpus} < 1 || $settings->{cpus} > 16)) {
1019 die "parameter cpus out of range\n";
1020 }
1021}
1022
10231;
1024