Commit | Line | Data |
---|---|---|
86530b38 AT |
1 | package Psh::OS::Unix; |
2 | ||
3 | use strict; | |
4 | require POSIX; | |
5 | require Psh::Locale; | |
6 | ||
7 | $Psh::OS::PATH_SEPARATOR=':'; | |
8 | $Psh::OS::FILE_SEPARATOR='/'; | |
9 | ||
10 | $Psh::history_file = ".psh_history"; | |
11 | ||
12 | # Sets the title of the current window | |
13 | sub set_window_title { | |
14 | my $title= shift; | |
15 | my $term= $ENV{TERM}; | |
16 | if( $term=~ /^(rxvt.*)|(xterm.*)|(.*xterm)|(kterm)|(aixterm)|(dtterm)/) { | |
17 | print "\017\033]2;$title\007"; | |
18 | } | |
19 | } | |
20 | ||
21 | # | |
22 | # Returns the hostname of the machine psh is running on, preferrably | |
23 | # the full version | |
24 | # | |
25 | ||
26 | sub get_hostname { | |
27 | require Sys::Hostname; | |
28 | return Sys::Hostname::hostname(); | |
29 | } | |
30 | ||
31 | sub getcwd_psh { | |
32 | my $cwd; | |
33 | chomp($cwd = `pwd`); | |
34 | $cwd; | |
35 | } | |
36 | ||
37 | # | |
38 | # Returns a list of well-known hosts (from /etc/hosts) | |
39 | # | |
40 | sub get_known_hosts { | |
41 | my $hosts_file = "/etc/hosts"; # TODO: shouldn't be hard-coded? | |
42 | my @result=(); | |
43 | local *F_KNOWNHOST; | |
44 | if (open(F_KNOWNHOST,"< $hosts_file")) { | |
45 | my $hosts_text = join ('', <F_KNOWNHOST>); | |
46 | close(F_KNOWNHOST); | |
47 | push @result,Psh::Util::parse_hosts_file($hosts_text); | |
48 | } | |
49 | my $tmp= catfile(Psh::OS::get_home_dir(), | |
50 | '.ssh','known_hosts'); | |
51 | if (-r $tmp) { | |
52 | if (open(F_KNOWNHOST, "< $tmp")) { | |
53 | while (<F_KNOWNHOST>) { | |
54 | chomp; | |
55 | next unless $_; | |
56 | if (/^([a-zA-Z].*?)\,/) { | |
57 | push @result, $1; | |
58 | } | |
59 | } | |
60 | } | |
61 | } | |
62 | if (!@result) { | |
63 | push @result,'localhost'; | |
64 | } | |
65 | return @result; | |
66 | } | |
67 | ||
68 | # | |
69 | # Returns a list of all users on the system, prepended with ~ | |
70 | # | |
71 | { | |
72 | my @user_cache; | |
73 | sub get_all_users { | |
74 | unless (@user_cache) { | |
75 | CORE::setpwent; | |
76 | while (my ($name) = CORE::getpwent) { | |
77 | push(@user_cache,'~'.$name); | |
78 | } | |
79 | CORE::endpwent; | |
80 | } | |
81 | return @user_cache; | |
82 | } | |
83 | } | |
84 | ||
85 | # | |
86 | # void display_pod(text) | |
87 | # | |
88 | sub display_pod { | |
89 | my $tmp= Psh::OS::tmpnam(); | |
90 | my $text= shift; | |
91 | ||
92 | local *TMP; | |
93 | open( TMP,">$tmp"); | |
94 | print TMP $text; | |
95 | close(TMP); | |
96 | ||
97 | eval { | |
98 | require Pod::Text; | |
99 | Pod::Text::pod2text($tmp,*STDOUT); | |
100 | }; | |
101 | Psh::Util::print_debug_class('e',"Error: $@") if $@; | |
102 | print $text if $@; | |
103 | ||
104 | unlink($tmp); | |
105 | } | |
106 | ||
107 | sub get_home_dir { | |
108 | my $user = shift || $ENV{USER}; | |
109 | return $ENV{HOME} if ((! $user) && (-d $ENV{HOME})); | |
110 | return (CORE::getpwnam($user))[7]||''; | |
111 | } | |
112 | ||
113 | sub get_rc_files { | |
114 | my @rc=(); | |
115 | ||
116 | if (-r '/etc/pshrc') { | |
117 | push @rc, '/etc/pshrc'; | |
118 | } | |
119 | my $home= Psh::OS::get_home_dir(); | |
120 | if ($home) { push @rc, catfile($home,'.pshrc') }; | |
121 | return @rc; | |
122 | } | |
123 | ||
124 | sub get_path_extension { return (''); } | |
125 | ||
126 | # | |
127 | # int inc_shlvl () | |
128 | # | |
129 | # Increments $ENV{SHLVL}. Also checks for login shell status and does | |
130 | # appropriate OS-specific tasks depending on it. | |
131 | # | |
132 | sub inc_shlvl { | |
133 | my @pwent = CORE::getpwuid($<); | |
134 | if ((! $ENV{SHLVL}) && ($pwent[8] eq $0)) { # would use $Psh::bin, but login shells are guaranteed full paths | |
135 | $Psh::login_shell = 1; | |
136 | $ENV{SHLVL} = 1; | |
137 | } else { | |
138 | $Psh::login_shell = 0; | |
139 | $ENV{SHLVL}++; | |
140 | } | |
141 | } | |
142 | ||
143 | ||
144 | ################################################################### | |
145 | # JOB CONTROL | |
146 | ################################################################### | |
147 | ||
148 | ||
149 | # | |
150 | # void _give_terminal_to (int PID) | |
151 | # | |
152 | # Make pid the foreground process of the terminal controlling STDIN. | |
153 | # | |
154 | ||
155 | { | |
156 | my $terminal_owner=0; | |
157 | ||
158 | sub _give_terminal_to | |
159 | { | |
160 | # If a fork of a psh fork tries to call this then exit | |
161 | # as it would probably mess up the shell | |
162 | # This hack is necessary as e.g. | |
163 | # alias ls=/bin/ls | |
164 | # ls & | |
165 | # call fork_process from within a fork | |
166 | ||
167 | return if $Psh::OS::Unix::forked_already; | |
168 | return if $terminal_owner==$_[0]; | |
169 | $terminal_owner=$_[0]; | |
170 | ||
171 | local $SIG{TSTP} = 'IGNORE'; | |
172 | local $SIG{TTIN} = 'IGNORE'; | |
173 | local $SIG{TTOU} = 'IGNORE'; | |
174 | local $SIG{CHLD} = 'IGNORE'; | |
175 | ||
176 | my ($pkg,$file,$line,$sub)= caller(1); | |
177 | my $status= POSIX::tcsetpgrp(fileno STDIN,$_[0]); | |
178 | } | |
179 | ||
180 | sub _get_terminal_owner | |
181 | { | |
182 | return $terminal_owner; | |
183 | } | |
184 | } | |
185 | ||
186 | ||
187 | ||
188 | # | |
189 | # void _wait_for_system(int PID, [bool QUIET_EXIT], [bool NO_TERMINAL]) | |
190 | # | |
191 | # Waits for a program to be stopped/ended, prints no message on normal | |
192 | # termination if QUIET_EXIT is specified and true. | |
193 | # | |
194 | # If NO_TERMINAL is specified and true it won't try to transfer | |
195 | # terminal ownership | |
196 | # | |
197 | ||
198 | sub _wait_for_system | |
199 | { | |
200 | my($pid, $quiet) = @_; | |
201 | if (!defined($quiet)) { $quiet = 0; } | |
202 | ||
203 | my $psh_pgrp = CORE::getpgrp(); | |
204 | ||
205 | my $pid_status = -1; | |
206 | ||
207 | my $job= Psh::Joblist::get_job($pid); | |
208 | ||
209 | return if ! $job; | |
210 | ||
211 | my $term_pid= $job->{pgrp_leader}||$pid; | |
212 | ||
213 | _give_terminal_to($term_pid); | |
214 | ||
215 | my $output=''; | |
216 | my $status=1; | |
217 | my $returnpid; | |
218 | while (1) { | |
219 | if (!$job->{running}) { $job->continue; } | |
220 | { | |
221 | local $Psh::currently_active = $pid; | |
222 | $returnpid = CORE::waitpid($pid,POSIX::WUNTRACED()); | |
223 | $pid_status = $?; | |
224 | } | |
225 | last if $returnpid<1; | |
226 | ||
227 | # Very ugly work around for the problem that | |
228 | # processes occasionally get SIGTTOUed without reason | |
229 | # We can do this here because we know the process has | |
230 | # to run and could not have been stopped by TTOU | |
231 | if ($returnpid== $pid && | |
232 | POSIX::WIFSTOPPED($pid_status) && | |
233 | Psh::OS::signal_name(POSIX::WSTOPSIG($pid_status)) eq 'TTOU') { | |
234 | $job->continue; | |
235 | next; | |
236 | } | |
237 | # Collect output here - we cannot print it while another | |
238 | # process might possibly be in the foreground; | |
239 | $output.=_handle_wait_status($returnpid, $pid_status, $quiet, 1); | |
240 | if ($returnpid == $pid) { | |
241 | $status=POSIX::WEXITSTATUS($pid_status); | |
242 | last; | |
243 | } | |
244 | } | |
245 | _give_terminal_to($psh_pgrp); | |
246 | Psh::Util::print_out($output) if length($output); | |
247 | return $status==0; | |
248 | } | |
249 | ||
250 | # | |
251 | # void _handle_wait_status(int PID, int STATUS, bool QUIET_EXIT) | |
252 | # | |
253 | # Take the appropriate action given that waiting on PID returned | |
254 | # STATUS. Normal termination is not reported if QUIET_EXIT is true. | |
255 | # | |
256 | ||
257 | sub _handle_wait_status { | |
258 | my ($pid, $pid_status, $quiet, $collect) = @_; | |
259 | # Have to obtain these before we potentially delete the job | |
260 | my $job= Psh::Joblist::get_job($pid); | |
261 | my $command = $job->{call}; | |
262 | my $visindex= Psh::Joblist::get_job_number($pid); | |
263 | my $verb=''; | |
264 | ||
265 | if (POSIX::WIFEXITED($pid_status)) { | |
266 | my $status= POSIX::WEXITSTATUS($pid_status); | |
267 | if ($status==0) { | |
268 | $verb= ucfirst(Psh::Locale::get_text('done')) unless $quiet; | |
269 | } else { | |
270 | $verb= ucfirst(Psh::Locale::get_text('error')); | |
271 | } | |
272 | Psh::Joblist::delete_job($pid); | |
273 | } elsif (POSIX::WIFSIGNALED($pid_status)) { | |
274 | my $tmp= Psh::Locale::get_text('terminated'); | |
275 | $verb = "\u$tmp (" . | |
276 | Psh::OS::signal_description(POSIX::WTERMSIG($pid_status)) . ')'; | |
277 | Psh::Joblist::delete_job($pid); | |
278 | } elsif (POSIX::WIFSTOPPED($pid_status)) { | |
279 | my $tmp= Psh::Locale::get_text('stopped'); | |
280 | $verb = "\u$tmp (" . | |
281 | Psh::OS::signal_description(POSIX::WSTOPSIG($pid_status)) . ')'; | |
282 | $job->{running}= 0; | |
283 | } | |
284 | if ($verb && $visindex>0) { | |
285 | my $line="[$visindex] $verb $pid $command\n"; | |
286 | return $line if $collect; | |
287 | ||
288 | Psh::Util::print_out($line ); | |
289 | } | |
290 | return ''; | |
291 | } | |
292 | ||
293 | ||
294 | # | |
295 | # void reap_children() | |
296 | # | |
297 | # Checks wether any children we spawned died | |
298 | # | |
299 | ||
300 | sub reap_children | |
301 | { | |
302 | my $returnpid=0; | |
303 | while (($returnpid = CORE::waitpid(-1, POSIX::WNOHANG() | | |
304 | POSIX::WUNTRACED())) > 0) { | |
305 | _handle_wait_status($returnpid, $?); | |
306 | } | |
307 | } | |
308 | ||
309 | sub execute_complex_command { | |
310 | my @array= @{shift()}; | |
311 | my $fgflag= shift @array; | |
312 | my @return_val; | |
313 | my $success= 0; | |
314 | my $eval_thingie; | |
315 | my $pgrp_leader= 0; | |
316 | my $pid; | |
317 | my $string=''; | |
318 | my @tmp; | |
319 | ||
320 | my ($read,$chainout,$chainin); | |
321 | ||
322 | for( my $i=0; $i<@array; $i++) { | |
323 | # ([ $strat, $how, \@options, \@words, $line]); | |
324 | my ($strategy, $how, $options, $words, $text, $opt)= @{$array[$i]}; | |
325 | local $Psh::current_options= $opt; | |
326 | $text||=''; | |
327 | ||
328 | my $line= join(' ',@$words); | |
329 | my $forcefork; | |
330 | ($success, $eval_thingie,$words,$forcefork, @return_val)= $strategy->execute( \$line, $words, $how, $i>0); | |
331 | ||
332 | $forcefork||=$i<$#array; | |
333 | ||
334 | if( defined($eval_thingie)) { | |
335 | if( $#array) { | |
336 | ($read,$chainout)= POSIX::pipe(); | |
337 | } | |
338 | foreach (@$options) { | |
339 | if ($_->[0]==Psh::Parser::T_REDIRECT() and | |
340 | ($_->[1] eq '<&' or $_->[1] eq '>&')) { | |
341 | if ($_->[3] eq 'chainin') { | |
342 | $_->[3]= $chainin; | |
343 | } elsif ($_->[3] eq 'chainout') { | |
344 | $_->[3]= $chainout; | |
345 | } | |
346 | } | |
347 | } | |
348 | my $termflag=!($i==$#array); | |
349 | ||
350 | ($pid,$success,@tmp)= _fork_process($eval_thingie,$words, | |
351 | $fgflag,$text,$options, | |
352 | $pgrp_leader,$termflag, | |
353 | $forcefork); | |
354 | ||
355 | if( !$i && !$pgrp_leader) { | |
356 | $pgrp_leader=$pid; | |
357 | } | |
358 | ||
359 | if( $i<$#array && $#array) { | |
360 | POSIX::close($chainout); | |
361 | $chainin= $read; | |
362 | } | |
363 | if( @return_val < 1 || | |
364 | !defined($return_val[0])) { | |
365 | @return_val= @tmp; | |
366 | } | |
367 | } | |
368 | $string.='|' if $i>0; | |
369 | $string.=$text; | |
370 | } | |
371 | ||
372 | if( $pid) { | |
373 | my $job= Psh::Joblist::create_job($pid,$string); | |
374 | $job->{pgrp_leader}=$pgrp_leader; | |
375 | if( $fgflag) { | |
376 | $success=_wait_for_system($pid, 1); | |
377 | } else { | |
378 | my $visindex= Psh::Joblist::get_job_number($job->{pid}); | |
379 | Psh::Util::print_out("[$visindex] Background $pgrp_leader $string\n"); | |
380 | } | |
381 | } | |
382 | return ($success,\@return_val); | |
383 | } | |
384 | ||
385 | sub _setup_redirects { | |
386 | my $options= shift; | |
387 | my $save= shift; | |
388 | ||
389 | return [] if ref $options ne 'ARRAY'; | |
390 | ||
391 | my @cache=(); | |
392 | foreach my $option (@$options) { | |
393 | if( $option->[0] == Psh::Parser::T_REDIRECT()) { | |
394 | my $type= $option->[2]; | |
395 | my $cachefileno; | |
396 | ||
397 | if ($option->[1] eq '<&') { | |
398 | POSIX::dup2($option->[3], $type); | |
399 | } elsif ($option->[1] eq '>&') { | |
400 | POSIX::dup2($option->[3], $type); | |
401 | } elsif ($option->[1] eq '<') { | |
402 | my $tmpfd= POSIX::open( $option->[3], &POSIX::O_RDONLY); | |
403 | POSIX::dup2($tmpfd, $type); | |
404 | POSIX::close($tmpfd); | |
405 | } elsif ($option->[1] eq '>') { | |
406 | my $tmpfd= POSIX::open( $option->[3], &POSIX::O_WRONLY | | |
407 | &POSIX::O_TRUNC | &POSIX::O_CREAT ); | |
408 | POSIX::dup2($tmpfd, $type); | |
409 | POSIX::close($tmpfd); | |
410 | } elsif ($option->[1] eq '>>') { | |
411 | my $tmpfd= POSIX::open( $option->[3], &POSIX::O_WRONLY | | |
412 | &POSIX::O_CREAT); | |
413 | POSIX::lseek($tmpfd,0, &POSIX::SEEK_END); | |
414 | POSIX::dup2($tmpfd, $type); | |
415 | POSIX::close($tmpfd); | |
416 | } | |
417 | if ($^F<$type) { # preserve filedescriptors higher than 2 | |
418 | $^F=$type; | |
419 | } | |
420 | } | |
421 | } | |
422 | select(STDOUT); | |
423 | return \@cache; | |
424 | } | |
425 | ||
426 | sub _has_redirects { | |
427 | my $options= shift; | |
428 | return 0 if ref $options ne 'ARRAY'; | |
429 | ||
430 | foreach my $option (@$options) { | |
431 | return 1 if( $option->[0] == Psh::Parser::T_REDIRECT()); | |
432 | } | |
433 | return 0; | |
434 | } | |
435 | ||
436 | # | |
437 | # void fork_process( code|program, words, | |
438 | # int fgflag, text to display in jobs, | |
439 | # redirection options, | |
440 | # pid of pgroupleader, do not set terminal flag, | |
441 | # force a fork?) | |
442 | # | |
443 | ||
444 | sub _fork_process { | |
445 | my( $code, $words, $fgflag, $string, $options, | |
446 | $pgrp_leader, $termflag, $forcefork) = @_; | |
447 | my($pid); | |
448 | ||
449 | # HACK - if it's foreground code AND perl code AND | |
450 | # there are no redirects | |
451 | # we do not fork, otherwise we'll never get | |
452 | # the result value, changed variables etc. | |
453 | if( $fgflag and !$forcefork and ref($code) eq 'CODE' | |
454 | and !_has_redirects($options) | |
455 | ) { | |
456 | my @result= eval { &$code }; | |
457 | Psh::Util::print_error($@) if $@ && $@ !~/^SECRET/; | |
458 | return (0,@result); | |
459 | } | |
460 | ||
461 | unless ($pid = fork) { #child | |
462 | unless (defined $pid) { | |
463 | Psh::Util::print_error_i18n('fork_failed'); | |
464 | return (-1,0,undef); | |
465 | } | |
466 | ||
467 | $Psh::OS::Unix::forked_already=1; | |
468 | close(READ) if( $pgrp_leader); | |
469 | _setup_redirects($options,0); | |
470 | POSIX::setpgid(0,$pgrp_leader||$$); | |
471 | _give_terminal_to($pgrp_leader||$$) if $fgflag && !$termflag; | |
472 | remove_signal_handlers(); | |
473 | ||
474 | if( ref($code) eq 'CODE') { | |
475 | my @tmp=&{$code}; | |
476 | if (!@tmp or $tmp[0]) { | |
477 | CORE::exit(0); | |
478 | } | |
479 | CORE::exit(1); | |
480 | } else { | |
481 | { | |
482 | if( ! ref $options) { | |
483 | exec $code; | |
484 | } else { | |
485 | $code= shift @$words; | |
486 | exec { $code } @$words; | |
487 | } | |
488 | } # Avoid unreachable warning | |
489 | Psh::Util::print_error_i18n('exec_failed',$code); | |
490 | CORE::exit(-1); | |
491 | } | |
492 | } | |
493 | POSIX::setpgid($pid,$pgrp_leader||$pid); | |
494 | _give_terminal_to($pgrp_leader||$pid) if $fgflag && !$termflag; | |
495 | return ($pid,0,undef); | |
496 | } | |
497 | ||
498 | sub fork_process { | |
499 | my( $code, $fgflag, $string, $options) = @_; | |
500 | my ($pid,$sucess,@result)= _fork_process($code,undef,$fgflag,$string,$options); | |
501 | return @result if !$pid; | |
502 | my $job= Psh::Joblist::create_job($pid,$string); | |
503 | if( !$fgflag) { | |
504 | my $visindex= Psh::Joblist::get_job_number($job->{pid}); | |
505 | Psh::Util::print_out("[$visindex] Background $pid $string\n"); | |
506 | } | |
507 | _wait_for_system($pid, 1) if $fgflag; | |
508 | return undef; | |
509 | } | |
510 | ||
511 | # | |
512 | # Returns true if the system has job_control abilities | |
513 | # | |
514 | sub has_job_control { return 1; } | |
515 | ||
516 | # | |
517 | # void restart_job(bool FOREGROUND, int JOB_INDEX) | |
518 | # | |
519 | sub restart_job | |
520 | { | |
521 | my ($fg_flag, $job_to_start) = @_; | |
522 | ||
523 | my $job= Psh::Joblist::find_job($job_to_start); | |
524 | ||
525 | if(defined($job)) { | |
526 | my $pid = $job->{pid}; | |
527 | my $command = $job->{call}; | |
528 | ||
529 | if ($command) { | |
530 | my $verb; | |
531 | my $qRunning = $job->{running}; | |
532 | ||
533 | if ($fg_flag) { | |
534 | $verb= ucfirst(Psh::Locale::get_text('foreground')); | |
535 | } elsif ($qRunning) { | |
536 | # bg request, and it's already running: | |
537 | return; | |
538 | } else { | |
539 | $verb= ucfirst(Psh::Locale::get_text('restart')); | |
540 | } | |
541 | my $visindex = Psh::Joblist::get_job_number($pid); | |
542 | Psh::Util::print_out("[$visindex] $verb $pid $command\n"); | |
543 | ||
544 | if($fg_flag) { | |
545 | eval { _wait_for_system($pid, 0); }; | |
546 | Psh::Util::print_debug_class('e',"Error: $@") if $@; | |
547 | } elsif( !$qRunning) { | |
548 | $job->continue; | |
549 | } | |
550 | } | |
551 | } | |
552 | } | |
553 | ||
554 | sub resume_job { | |
555 | my $job= shift; | |
556 | ||
557 | kill 'CONT', -$job->{pid}; | |
558 | kill 'CONT', -$job->{pgrp_leader} if $job->{pgrp_leader}; | |
559 | } | |
560 | ||
561 | # Simply doing backtick eval - mainly for Prompt evaluation | |
562 | sub backtick { | |
563 | my $com=join ' ',@_; | |
564 | local $^F=50; | |
565 | my ($read,$write)= POSIX::pipe(); | |
566 | ||
567 | unless(my $pid=fork) { | |
568 | POSIX::close($read); | |
569 | POSIX::dup2($write,fileno(*STDOUT)); | |
570 | $^F=$write if ($write>$^F); | |
571 | my ($success)= Psh::evl($com); | |
572 | CORE::exit(!$success); | |
573 | } | |
574 | POSIX::close($write); | |
575 | my $result=''; | |
576 | local(*READ); | |
577 | open(READ,"<&=$read"); | |
578 | while(<READ>) { | |
579 | $result.=$_; | |
580 | } | |
581 | close(READ); | |
582 | return $result; | |
583 | } | |
584 | ||
585 | ################################################################### | |
586 | # SIGNALS | |
587 | ################################################################### | |
588 | ||
589 | # Setup special treatment of certain signals | |
590 | # Having a value of 0 means to ignore the signal completely in | |
591 | # the loops while a code ref installs a different default | |
592 | # handler. Note that calling _ignore_handler is different than | |
593 | # setting the signal action to ignore - if you set the signal | |
594 | # action to ignore, the signal might be passed on to parent processes | |
595 | # which could decide to handle them for us | |
596 | ||
597 | my %special_handlers= ( | |
598 | 'CHLD' => \&_ignore_handler, | |
599 | 'CLD' => \&_ignore_handler, | |
600 | 'TTOU' => \&_ttou_handler, | |
601 | 'TTIN' => \&_ttou_handler, | |
602 | 'TERM' => \&Psh::OS::fb_exit_psh, | |
603 | 'HUP' => \&Psh::OS::fb_exit_psh, | |
604 | 'SEGV' => 0, | |
605 | 'WINCH'=> 0, | |
606 | 'ZERO' => 0, | |
607 | ); | |
608 | ||
609 | my @signals= grep { substr($_,0,1) ne '_' } keys %SIG; | |
610 | ||
611 | # | |
612 | # void remove_signal_handlers() | |
613 | # | |
614 | # This used to manually set INT, QUIT, CONT, STOP, TSTP, TTIN, | |
615 | # TTOU, and CHLD. | |
616 | # | |
617 | # The new technique changes the settings of *all* signals. It is | |
618 | # from Recipe 16.13 of The Perl Cookbook (Page 582). It should be | |
619 | # compatible with Perl 5.004 and later. | |
620 | # | |
621 | ||
622 | sub remove_signal_handlers | |
623 | { | |
624 | foreach my $sig (@signals) { | |
625 | next if exists($special_handlers{$sig}) && | |
626 | ! ref($special_handlers{$sig}); | |
627 | $SIG{$sig} = 'DEFAULT'; | |
628 | } | |
629 | } | |
630 | ||
631 | # | |
632 | # void setup_signal_handlers | |
633 | # | |
634 | # This used to manually set INT, QUIT, CONT, STOP, TSTP, TTIN, | |
635 | # TTOU, and CHLD. | |
636 | # | |
637 | # See comment for remove_signal_handlers() for more information. | |
638 | # | |
639 | ||
640 | sub setup_signal_handlers | |
641 | { | |
642 | foreach my $sig (@signals) { | |
643 | if( exists($special_handlers{$sig})) { | |
644 | if( ref($special_handlers{$sig})) { | |
645 | $SIG{$sig}= $special_handlers{$sig}; | |
646 | } | |
647 | next; | |
648 | } | |
649 | $SIG{$sig} = \&_signal_handler; | |
650 | } | |
651 | ||
652 | reinstall_resize_handler(); | |
653 | } | |
654 | ||
655 | ||
656 | # | |
657 | # Setup the SIGSEGV handler | |
658 | # | |
659 | sub setup_sigsegv_handler | |
660 | { | |
661 | $SIG{SEGV} = \&_error_handler; | |
662 | } | |
663 | ||
664 | # | |
665 | # Setup SIGINT handler for readline | |
666 | # | |
667 | sub setup_readline_handler | |
668 | { | |
669 | $SIG{INT}= \&_readline_handler; | |
670 | } | |
671 | ||
672 | sub remove_readline_handler | |
673 | { | |
674 | $SIG{INT}= \&_signal_handler; | |
675 | } | |
676 | ||
677 | sub reinstall_resize_handler | |
678 | { | |
679 | Psh::OS::fb_reinstall_resize_handler(); | |
680 | &_resize_handler('WINCH'); | |
681 | } | |
682 | ||
683 | ||
684 | # | |
685 | # readline_handler() | |
686 | # | |
687 | # Readline ^C handler. | |
688 | # | |
689 | ||
690 | sub _readline_handler | |
691 | { | |
692 | my $sig= shift; | |
693 | setup_readline_handler(); | |
694 | print "\n"; # Clean up the display | |
695 | die "SECRET $Psh::bin: Signal $sig\n"; # changed to SECRET... just in case | |
696 | } | |
697 | ||
698 | sub _ttou_handler | |
699 | { | |
700 | _give_terminal_to($$); | |
701 | } | |
702 | ||
703 | # | |
704 | # void _signal_handler( string SIGNAL ) | |
705 | # | |
706 | ||
707 | sub _signal_handler | |
708 | { | |
709 | my ($sig) = @_; | |
710 | ||
711 | if ($Psh::currently_active > 0) { | |
712 | Psh::Util::print_debug("Received signal SIG$sig, sending to $Psh::currently_active\n"); | |
713 | ||
714 | kill $sig, -$Psh::currently_active; | |
715 | } elsif ($Psh::currently_active < 0) { | |
716 | Psh::Util::print_debug("Received signal SIG$sig, sending to Perl code\n"); | |
717 | die "SECRET ${Psh::bin}: Signal $sig\n"; | |
718 | } else { | |
719 | _give_terminal_to($$); | |
720 | Psh::Util::print_debug("Received signal SIG$sig, die-ing\n"); | |
721 | die "SECRET ${Psh::bin}: Signal $sig\n" if $sig eq 'INT'; | |
722 | } | |
723 | ||
724 | $SIG{$sig} = \&_signal_handler; | |
725 | } | |
726 | ||
727 | ||
728 | # | |
729 | # ignore_handler() | |
730 | # | |
731 | ||
732 | sub _ignore_handler | |
733 | { | |
734 | } | |
735 | ||
736 | ||
737 | sub _error_handler | |
738 | { | |
739 | my ($sig) = @_; | |
740 | Psh::Util::print_error_i18n('unix_received_strange_sig',$sig); | |
741 | kill 'INT', $$; # HACK to stop a possible endless loop! | |
742 | } | |
743 | ||
744 | # | |
745 | # _resize_handler() | |
746 | # | |
747 | ||
748 | sub _resize_handler | |
749 | { | |
750 | my ($sig) = @_; | |
751 | ||
752 | Psh::OS::check_terminal_size(); | |
753 | ||
754 | $SIG{$sig} = \&_resize_handler; | |
755 | } | |
756 | ||
757 | { | |
758 | my $debian=-1; | |
759 | sub _check_debian { | |
760 | if ($debian==-1) { | |
761 | if (-r '/etc/debian-version') { | |
762 | $debian=1; | |
763 | } else { | |
764 | $debian=0; | |
765 | } | |
766 | } | |
767 | return $debian; | |
768 | } | |
769 | } | |
770 | ||
771 | sub get_editor { | |
772 | my $file= shift; | |
773 | my $suggestion= shift; | |
774 | my $editor= $suggestion||$ENV{VISUAL}||$ENV{EDITOR}; | |
775 | if (_check_debian()) { | |
776 | $editor ||='editor'; | |
777 | } else { | |
778 | $editor ||='vi'; | |
779 | } | |
780 | return $editor; | |
781 | } | |
782 | ||
783 | # File::Spec | |
784 | ||
785 | sub canonpath { | |
786 | my ($path) = @_; | |
787 | $path =~ s|/+|/|g unless($^O eq 'cygwin'); # xx////xx -> xx/xx | |
788 | $path =~ s|(/\.)+/|/|g; # xx/././xx -> xx/xx | |
789 | $path =~ s|^(\./)+||s unless $path eq "./"; # ./xx -> xx | |
790 | $path =~ s|^/(\.\./)+|/|s; # /../../xx -> xx | |
791 | $path =~ s|/\Z(?!\n)|| unless $path eq "/"; # xx/ -> xx | |
792 | return $path; | |
793 | } | |
794 | ||
795 | sub catfile { | |
796 | my $file = pop @_; | |
797 | return $file unless @_; | |
798 | my $dir = catdir(@_); | |
799 | $dir .= "/" unless substr($dir,-1) eq "/"; | |
800 | return $dir.$file; | |
801 | } | |
802 | ||
803 | sub catdir { | |
804 | my @args = @_; | |
805 | foreach (@args) { | |
806 | # append a slash to each argument unless it has one there | |
807 | $_ .= "/" if $_ eq '' || substr($_,-1) ne "/"; | |
808 | } | |
809 | return canonpath(join('', @args)); | |
810 | } | |
811 | ||
812 | sub file_name_is_absolute { | |
813 | my $file= shift; | |
814 | return scalar($file =~ m:^/:s); | |
815 | } | |
816 | ||
817 | sub rootdir { | |
818 | '/'; | |
819 | } | |
820 | ||
821 | sub splitdir { | |
822 | my ($directories) = @_ ; | |
823 | ||
824 | if ( $directories !~ m|/\Z(?!\n)| ) { | |
825 | return split( m|/|, $directories ); | |
826 | } | |
827 | else { | |
828 | my( @directories )= split( m|/|, "${directories}dummy" ) ; | |
829 | $directories[ $#directories ]= '' ; | |
830 | return @directories ; | |
831 | } | |
832 | } | |
833 | ||
834 | sub rel2abs { | |
835 | my ($path,$base ) = @_; | |
836 | ||
837 | # Clean up $path | |
838 | if ( ! file_name_is_absolute( $path ) ) { | |
839 | # Figure out the effective $base and clean it up. | |
840 | if ( !defined( $base ) || $base eq '' ) { | |
841 | $base = Psh::getcwd_psh() ; | |
842 | } | |
843 | elsif ( ! file_name_is_absolute( $base ) ) { | |
844 | $base = rel2abs( $base ) ; | |
845 | } | |
846 | else { | |
847 | $base = canonpath( $base ) ; | |
848 | } | |
849 | ||
850 | # Glom them together | |
851 | $path = catdir( $base, $path ) ; | |
852 | } | |
853 | ||
854 | return canonpath( $path ) ; | |
855 | } | |
856 | ||
857 | 1; | |
858 | ||
859 | __END__ | |
860 | ||
861 |