]>
Commit | Line | Data |
---|---|---|
aff192e6 DM |
1 | package PVE::Utils; |
2 | ||
3 | use strict; | |
4 | use POSIX qw (:sys_wait_h strftime); | |
5 | use PVE::pvecfg; | |
6 | use IPC::Open3; | |
7 | use IO::File; | |
8 | use IO::Select; | |
9 | use PVE::SafeSyslog; | |
10 | use Authen::PAM qw(:constants); | |
11 | use Time::HiRes qw (gettimeofday); | |
12 | use Digest::SHA1; | |
13 | use Encode; | |
14 | ||
15 | my $clock_ticks = POSIX::sysconf(&POSIX::_SC_CLK_TCK); | |
16 | ||
17 | # access control | |
18 | ||
19 | my $accmode = { | |
20 | root => [[ '/', 'w' ]], | |
21 | audit => [[ '/', 'r' ]], | |
22 | }; | |
23 | ||
24 | my $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 | ||
34 | sub 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 | ||
55 | sub 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 | ||
63 | sub 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 | ||
73 | sub 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 | ||
83 | sub 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 | ||
102 | sub 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 | |
122 | sub 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 | ||
160 | sub 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 | ||
198 | sub 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 | ||
236 | sub 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 | ||
253 | my $WORKER_PIDS; | |
254 | ||
255 | sub 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 | ||
265 | sub 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 | ||
280 | sub 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 | ||
291 | sub 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 | ||
308 | sub 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 | ||
320 | sub 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 | ||
332 | sub 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 | ||
349 | sub 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 | ||
370 | sub 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 | ||
390 | sub 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 | ||
400 | my $last_proc_stat; | |
401 | ||
402 | sub 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 | ||
453 | sub 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 | |
493 | sub 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 | ||
512 | sub 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 | ||
547 | sub 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 | ||
573 | my $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 | ||
581 | sub 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 | ||
627 | sub 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 | ||
648 | sub 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 | ||
725 | sub _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 | ||
732 | sub 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 | ||
753 | sub 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 | ||
790 | sub 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 | ||
835 | sub 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 | ||
857 | sub 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 | ||
919 | sub 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 | ||
959 | sub 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 | ||
987 | sub 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 | ||
1023 | 1; | |
1024 |