2 # Functions for managing processes
4 BEGIN { push(@INC, ".."); };
10 if ($module_info{'usermin'} && !$ENV{'FOREIGN_MODULE_NAME'}) {
11 &switch_to_remote_user();
13 do "$config{ps_style}-lib.pl";
14 if ($module_info{'usermin'}) {
15 %access = ( 'edit' => 1,
18 $no_module_config = 1;
19 $user_processes_only = 1;
20 $index_file = "$user_module_config_directory/index";
23 %access = &get_module_acl();
24 map { $hide{$_}++ } split(/\s+/, $access{'hide'});
25 $index_file = "$module_config_directory/index";
26 $user_processes_only = $access{'only'};
27 if (!defined($access{'users'})) {
28 $access{'users'} = $access{'uid'} < 0 ? "x" :
29 $access{'uid'} == 0 ? "*" :
30 getpwuid($access{'uid'});
34 if ($access{'users'} eq "*") {
35 $default_run_user = "root";
37 elsif (&can_edit_process($remote_user)) {
38 $default_run_user = $remote_user;
41 local @canu = split(/\s+/, $access{'users'});
42 if ($canu[0] =~ /^\@(.*)$/) {
43 $default_run_user = undef;
45 elsif ($can[0] =~ /^(\d+)\-(\d+)$/) {
46 $default_run_user = getpwuid($1);
49 $default_run_user = $canu[0];
56 local @plist = &list_processes($_[0]);
57 return @plist ? %{$plist[0]} : ();
60 # index_links(current)
64 print "<b>$text{'index_display'} : </b>\n";
66 foreach $l ("tree", "user", "size", "cpu", ($has_zone ? ("zone") : ()),
68 next if ($l eq "run" && !$access{'run'});
70 if ($l ne $_[0]) { $link .= "<a href=index_$l.cgi>"; }
71 else { $link .= "<b>"; }
72 $link .= $text{"index_$l"};
73 if ($l ne $_[0]) { $link .= "</a>"; }
74 else { $link .= "</b>"; }
77 print &ui_links_row(\@links);
79 &create_user_config_dirs();
80 open(INDEX, ">$index_file");
82 print INDEX "$1?$in\n";
86 # cut_string(string, [length])
89 local $len = $_[1] || $config{'cut_length'};
90 if ($len && length($_[0]) > $len) {
91 return substr($_[0], 0, $len)." ...";
99 return if ($module_info{'usermin'}); # already switched!
100 if ($access{'uid'} < 0) {
101 local @u = getpwnam($remote_user);
102 @u || &error("Failed to find user $remote_user");
103 &switch_to_unix_user(\@u);
105 elsif ($access{'uid'}) {
106 local @u = getpwuid($access{'uid'});
107 &switch_to_unix_user(\@u);
111 # safe_process_exec(command, uid, gid, handle, [input], [fixtags], [bsmode],
113 # Executes the given command as the given user/group and writes all output
114 # to the given file handle. Finishes when there is no more output or the
115 # process stops running. Returns the number of bytes read.
116 sub safe_process_exec
118 if (&is_readonly_mode() && !$_[8]) {
119 # Veto command in readonly mode
122 &webmin_debug_log('CMD', "cmd=$_[0] uid=$_[1] gid=$_[2]")
123 if ($gconfig{'debug_what_cmd'});
125 if ($gconfig{'os_type'} eq 'windows') {
126 # For Windows, just run the command and read output
127 local $temp = &transname();
128 open(TEMP, ">$temp");
131 &open_execute_command(OUT, "$_[0] <$temp 2>&1", 1);
135 print $fh &html_escape($_);
145 # setup pipes and fork the process
146 local $chld = $SIG{'CHLD'};
147 $SIG{'CHLD'} = \&safe_exec_reaper;
156 open(STDIN, "<&INr");
157 open(STDOUT, ">&OUTw");
158 open(STDERR, ">&OUTw");
160 close(OUTr); close(INw);
163 if (defined($_[2])) {
164 # switch to given UID and GID
165 &switch_to_unix_user(
166 [ undef, undef, $_[1], $_[2] ]);
169 # switch to UID and all GIDs
170 local @u = getpwuid($_[1]);
171 &switch_to_unix_user(\@u);
176 delete($ENV{'FOREIGN_MODULE_NAME'});
177 delete($ENV{'SCRIPT_NAME'});
178 exec("/bin/sh", "-c", $_[0]);
179 print "Exec failed : $!\n";
182 close(OUTw); close(INr);
184 # Feed input (if any)
188 # Read and show output
189 local $fn = fileno(OUTr);
193 local $start = time();
194 $safe_process_exec_timeout = 0;
196 local ($rmask, $buf);
197 vec($rmask, $fn, 1) = 1;
198 local $sel = select($rmask, undef, undef, 1);
199 if ($sel > 0 && vec($rmask, $fn, 1)) {
200 # got something to read.. print it
201 sysread(OUTr, $buf, 1024) || last;
202 $got += length($buf);
204 $buf = &html_escape($buf);
207 # Convert backspaces and returns and escapes
209 while($line =~ s/^([^\n]*\n)//) {
211 while($one =~ s/.\010//) { }
212 $one =~ s/\033[^m]+m//g;
221 # nothing to read. maybe the process is done, and a
222 # subprocess is hanging things up
223 last if (!kill(0, $pid));
225 if ($_[7] && time() - $start > $_[7]) {
226 # Timeout exceeded - kill the process
228 $safe_process_exec_timeout = 1;
233 $SIG{'CHLD'} = $chld;
238 # safe_process_exec_logged(..)
239 # Like safe_process_exec, but also logs the command
240 sub safe_process_exec_logged
242 &additional_log('exec', undef, $_[0]);
243 return &safe_process_exec(@_);
249 do { local $oldexit = $?;
250 $xp = waitpid(-1, WNOHANG);
251 $? = $oldexit if ($? < 0);
255 # pty_process_exec(command, [uid, gid])
256 # Starts the given command in a new pty and returns the pty filehandle and PID
259 local ($cmd, $uid, $gid) = @_;
260 if (&is_readonly_mode()) {
261 # When in readonly mode, don't run the command
264 &webmin_debug_log('CMD', "cmd=$cmd uid=$uid gid=$gid")
265 if ($gconfig{'debug_what_cmd'});
269 # Use the IO::Pty perl module if installed
270 local $ptyfh = new IO::Pty;
272 &error("Failed to create new PTY with IO::Pty");
276 local $ttyfh = $ptyfh->slave();
277 local $tty = $ptyfh->ttyname();
278 if (defined(&close_controlling_pty)) {
279 &close_controlling_pty();
281 setsid(); # create a new session group
282 $ptyfh->make_slave_controlling_terminal();
284 # Turn off echoing, if we can
287 IO::Stty::stty($ttyfh, 'raw', '-echo');
290 close(STDIN); close(STDOUT); close(STDERR);
291 untie(*STDIN); untie(*STDOUT); untie(*STDERR);
293 &switch_to_unix_user([ undef, undef, $_[1], $_[2] ]);
296 close($ptyfh); # Used by other side only
297 open(STDIN, "<&".fileno($ttyfh));
298 open(STDOUT, ">&".fileno($ttyfh));
299 open(STDERR, ">&".fileno($ttyfh));
300 close($ttyfh); # Already dup'd
302 print "Exec failed : $!\n";
305 $ptyfh->close_slave();
306 return ($ptyfh, $pid);
309 # Need to create a PTY using built-in Webmin code
310 local ($ptyfh, $ttyfh, $pty, $tty) = &get_new_pty();
311 $tty || &error("Failed to create new PTY - try installing the IO::Tty Perl module");
314 if (defined(&close_controlling_pty)) {
315 &close_controlling_pty();
318 setsid(); # create a new session group
321 # Needs to be opened, as get_new_pty on linux cannot do
325 chown($_[1], $_[2], $tty);
327 open($ttyfh, "+<$tty") || &error("Failed to open $tty : $!");
330 # Turn off echoing, if we can
333 IO::Stty::stty($ttyfh, 'raw', '-echo');
336 if (defined(&open_controlling_pty)) {
337 &open_controlling_pty($ptyfh, $ttyfh, $pty, $tty);
340 close(STDIN); close(STDOUT); close(STDERR);
341 untie(*STDIN); untie(*STDOUT); untie(*STDERR);
344 &switch_to_unix_user([ undef, undef, $_[1], $_[2] ]);
347 open(STDIN, "<$tty");
348 open(STDOUT, ">&$ttyfh");
349 open(STDERR, ">&STDOUT");
352 print "Exec failed : $!\n";
356 return ($ptyfh, $pid);
360 # pty_process_exec_logged(..)
361 # Like pty_process_exec, but logs the command as well
362 sub pty_process_exec_logged
364 &additional_log('exec', undef, $_[0]);
365 return &pty_process_exec(@_);
369 # Returns an array of all processes matching some name
373 local @rv = grep { $_->{'args'} =~ /$name/ } &list_processes();
374 return wantarray ? @rv : $rv[0];
377 $has_lsof_command = &has_command("lsof");
379 # find_socket_processes(protocol, port)
380 # Returns all processes using some port and protocol
381 sub find_socket_processes
384 open(LSOF, "lsof -i '$_[0]:$_[1]' |");
386 if (/^(\S+)\s+(\d+)/) {
394 # find_ip_processes(ip)
395 # Returns all processes using some IP address
396 sub find_ip_processes
399 open(LSOF, "lsof -i '\@$_[0]' |");
401 if (/^(\S+)\s+(\d+)/) {
409 # find_process_sockets(pid)
410 # Returns all network connections made by some process
411 sub find_process_sockets
414 open(LSOF, "lsof -i tcp -i udp -n |");
416 if (/^(\S+)\s+(\d+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+).*(TCP|UDP)\s+(.*)/
418 local $n = { 'fd' => $4,
422 if ($m =~ /^([^:\s]+):([^:\s]+)\s+\(listen\)/i) {
427 elsif ($m =~ /^([^:\s]+):([^:\s]+)->([^:\s]+):([^:\s]+)\s+\((\S+)\)/) {
434 elsif ($m =~ /^([^:\s]+):([^:\s]+)/) {
445 # find_process_files(pid)
446 # Returns all files currently held open by some process
447 sub find_process_files
450 open(LSOF, "lsof -p '$_[0]' |");
452 if (/^(\S+)\s+(\d+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\d+),(\d+)\s+(\d+)\s+(\d+)\s+(.*)/) {
453 push(@rv, { 'fd' => lc($4),
455 'device' => [ $6, $7 ],
460 elsif (/^(\S+)\s+(\d+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\d+),(\d+)\s+(\d+)\s+(.*)/) {
461 push(@rv, { 'fd' => lc($4),
463 'device' => [ $6, $7 ],
472 # pty_backquote(cmd, uid, gid)
473 # Like the normal Perl backquote operator, but executes the command in a PTY
477 local ($fh, $pid) = &pty_process_exec(@_);
482 waitpid($pid, WNOHANG);
486 # pty_backquote_logged(cmd, uid, gid)
487 # Like pty_backquote, but logs the command as well
488 sub pty_backquote_logged
490 &additional_log('exec', undef, $_[0]);
491 return &pty_backquote(@_);
495 # Returns a list containing the 5, 10 and 15 minute load averages, and possibly
496 # the CPU mhz, model, vendor, cache and count
499 if (defined(&os_get_cpu_info)) {
500 return &os_get_cpu_info();
503 local $out = &backquote_command("uptime 2>&1");
504 &reset_environment();
505 return $out =~ /average(s)?:\s+([0-9\.]+),?\s+([0-9\.]+),?\s+([0-9\.]+)/i ?
506 ( $2, $3, $4 ) : ( );
509 # find_subprocesses(&proc, [&plist])
510 # Returns a list of all processes under the one given
511 sub find_subprocesses
514 local @plist = $_[1] ? @{$_[1]} : &list_processes();
515 local @sp = grep { $_->{'ppid'} &&
516 $_->{'ppid'} == $proc->{'pid'} } @plist;
519 push(@rv, $sp, &find_subprocesses($sp, \@plist));
524 # supported_signals()
525 # Returns signal names known to Perl for the kill function
526 sub supported_signals
528 if (defined(&os_supported_signals)) {
529 return &os_supported_signals();
532 return split(/\s+/, $Config{'sig_name'});
536 # can_view_process(user)
537 # Returns 1 if processes belong to this user can be seen
544 elsif ($user_processes_only) {
545 return &can_edit_process($user);
552 # can_edit_process(user)
553 # Returns 1 if processes belong to this user can be edited. The 'manage as'
554 # user will still apply though.
558 if (!$access{'edit'}) {
561 elsif ($hide{$user}) {
564 elsif ($access{'users'} eq '*') {
567 elsif ($access{'users'} eq 'x') {
568 return $user eq $remote_user;
571 local @uinfo = getpwnam($user);
572 foreach my $u (split(/\s+/, $access{'users'})) {
573 if ($u =~ /^\@(.*)$/) {
574 # Is he in this group?
575 local @ginfo = getgrnam($1);
576 return 1 if ($uinfo[3] == $ginfo[2]);
577 return 1 if (&indexof($ginfo[0],
578 &other_groups($user)) >= 0);
580 elsif ($u =~ /^(\d+)\-(\d+)$/) {
582 return 1 if ($uinfo[2] >= $1 && $uinfo[2] <= $2);
585 return 1 if ($u eq $user);
592 # nice_selector(name, value)
593 # Returns a menu for selecting a nice level
596 local ($name, $value) = @_;
597 local $l = scalar(@nice_range);
598 return &ui_select($name, $value,
599 [ map { [ $_, $_.($_ == $nice_range[0] ? " ($text{'edit_prihigh'})" :
600 $_ == 0 ? " ($text{'edit_pridef'})" :
601 $_ == $nice_range[$l-1] ? " ($text{'edit_prilow'})" :
602 "") ] } @nice_range ]);
606 # Returns the system's kernel version, architecture and OS
609 if (defined(&os_get_kernel_info)) {
610 return &os_get_kernel_info();
613 my $uname = &has_command("uptrack-uname") || &has_command("uname");
614 my $out = &backquote_command("$uname -r 2>/dev/null ; ".
615 "$uname -m 2>/dev/null ; ".
616 "$uname -s 2>/dev/null");
617 return split(/\r?\n/, $out);
621 # get_system_uptime()
622 # Returns uptime in days, minutes and hours
623 sub get_system_uptime
625 my $out = &backquote_command("LC_ALL='' LANG='' uptime");
626 if ($out =~ /up\s+(\d+)\s+(day|days),?\s+(\d+):(\d+)/) {
628 return ( $1, $3, $4 );
630 elsif ($out =~ /up\s+(\d+)\s+(day|days),?\s+(\d+)\s+min/) {
631 # up 198 days, 10 mins
632 return ( $1, 0, $3 );
634 elsif ($out =~ /up\s+(\d+):(\d+)/) {
636 return ( 0, $1, $2 );
638 elsif ($out =~ /up\s+(\d+)\s+min/) {