$Psh::OS
::PATH_SEPARATOR
=':';
$Psh::OS
::FILE_SEPARATOR
='/';
$Psh::history_file
= ".psh_history";
# Sets the title of the current window
if( $term=~ /^(rxvt.*)|(xterm.*)|(.*xterm)|(kterm)|(aixterm)|(dtterm)/) {
print "\017\033]2;$title\007";
# Returns the hostname of the machine psh is running on, preferrably
return Sys
::Hostname
::hostname
();
# Returns a list of well-known hosts (from /etc/hosts)
my $hosts_file = "/etc/hosts"; # TODO: shouldn't be hard-coded?
if (open(F_KNOWNHOST
,"< $hosts_file")) {
my $hosts_text = join ('', <F_KNOWNHOST
>);
push @result,Psh
::Util
::parse_hosts_file
($hosts_text);
my $tmp= catfile
(Psh
::OS
::get_home_dir
(),
if (open(F_KNOWNHOST
, "< $tmp")) {
if (/^([a-zA-Z].*?)\,/) {
push @result,'localhost';
# Returns a list of all users on the system, prepended with ~
while (my ($name) = CORE
::getpwent) {
push(@user_cache,'~'.$name);
my $tmp= Psh
::OS
::tmpnam
();
Pod
::Text
::pod2text
($tmp,*STDOUT
);
Psh
::Util
::print_debug_class
('e',"Error: $@") if $@
;
my $user = shift || $ENV{USER
};
return $ENV{HOME
} if ((! $user) && (-d
$ENV{HOME
}));
return (CORE
::getpwnam($user))[7]||'';
my $home= Psh
::OS
::get_home_dir
();
if ($home) { push @rc, catfile
($home,'.pshrc') };
sub get_path_extension
{ return (''); }
# Increments $ENV{SHLVL}. Also checks for login shell status and does
# appropriate OS-specific tasks depending on it.
my @pwent = CORE
::getpwuid($<);
if ((! $ENV{SHLVL
}) && ($pwent[8] eq $0)) { # would use $Psh::bin, but login shells are guaranteed full paths
###################################################################
###################################################################
# void _give_terminal_to (int PID)
# Make pid the foreground process of the terminal controlling STDIN.
# If a fork of a psh fork tries to call this then exit
# as it would probably mess up the shell
# This hack is necessary as e.g.
# call fork_process from within a fork
return if $Psh::OS
::Unix
::forked_already
;
return if $terminal_owner==$_[0];
local $SIG{TSTP
} = 'IGNORE';
local $SIG{TTIN
} = 'IGNORE';
local $SIG{TTOU
} = 'IGNORE';
local $SIG{CHLD
} = 'IGNORE';
my ($pkg,$file,$line,$sub)= caller(1);
my $status= POSIX
::tcsetpgrp
(fileno STDIN
,$_[0]);
# void _wait_for_system(int PID, [bool QUIET_EXIT], [bool NO_TERMINAL])
# Waits for a program to be stopped/ended, prints no message on normal
# termination if QUIET_EXIT is specified and true.
# If NO_TERMINAL is specified and true it won't try to transfer
if (!defined($quiet)) { $quiet = 0; }
my $psh_pgrp = CORE
::getpgrp();
my $job= Psh
::Joblist
::get_job
($pid);
my $term_pid= $job->{pgrp_leader
}||$pid;
_give_terminal_to
($term_pid);
if (!$job->{running
}) { $job->continue; }
local $Psh::currently_active
= $pid;
$returnpid = CORE
::waitpid($pid,POSIX
::WUNTRACED
());
# Very ugly work around for the problem that
# processes occasionally get SIGTTOUed without reason
# We can do this here because we know the process has
# to run and could not have been stopped by TTOU
POSIX
::WIFSTOPPED
($pid_status) &&
Psh
::OS
::signal_name
(POSIX
::WSTOPSIG
($pid_status)) eq 'TTOU') {
# Collect output here - we cannot print it while another
# process might possibly be in the foreground;
$output.=_handle_wait_status
($returnpid, $pid_status, $quiet, 1);
if ($returnpid == $pid) {
$status=POSIX
::WEXITSTATUS
($pid_status);
_give_terminal_to
($psh_pgrp);
Psh
::Util
::print_out
($output) if length($output);
# void _handle_wait_status(int PID, int STATUS, bool QUIET_EXIT)
# Take the appropriate action given that waiting on PID returned
# STATUS. Normal termination is not reported if QUIET_EXIT is true.
sub _handle_wait_status
{
my ($pid, $pid_status, $quiet, $collect) = @_;
# Have to obtain these before we potentially delete the job
my $job= Psh
::Joblist
::get_job
($pid);
my $command = $job->{call
};
my $visindex= Psh
::Joblist
::get_job_number
($pid);
if (POSIX
::WIFEXITED
($pid_status)) {
my $status= POSIX
::WEXITSTATUS
($pid_status);
$verb= ucfirst(Psh
::Locale
::get_text
('done')) unless $quiet;
$verb= ucfirst(Psh
::Locale
::get_text
('error'));
Psh
::Joblist
::delete_job
($pid);
} elsif (POSIX
::WIFSIGNALED
($pid_status)) {
my $tmp= Psh
::Locale
::get_text
('terminated');
Psh
::OS
::signal_description
(POSIX
::WTERMSIG
($pid_status)) . ')';
Psh
::Joblist
::delete_job
($pid);
} elsif (POSIX
::WIFSTOPPED
($pid_status)) {
my $tmp= Psh
::Locale
::get_text
('stopped');
Psh
::OS
::signal_description
(POSIX
::WSTOPSIG
($pid_status)) . ')';
if ($verb && $visindex>0) {
my $line="[$visindex] $verb $pid $command\n";
return $line if $collect;
Psh
::Util
::print_out
($line );
# Checks wether any children we spawned died
while (($returnpid = CORE
::waitpid(-1, POSIX
::WNOHANG
() |
POSIX
::WUNTRACED
())) > 0) {
_handle_wait_status
($returnpid, $?
);
sub execute_complex_command
{
my $fgflag= shift @array;
my ($read,$chainout,$chainin);
for( my $i=0; $i<@array; $i++) {
# ([ $strat, $how, \@options, \@words, $line]);
my ($strategy, $how, $options, $words, $text, $opt)= @
{$array[$i]};
local $Psh::current_options
= $opt;
my $line= join(' ',@
$words);
($success, $eval_thingie,$words,$forcefork, @return_val)= $strategy->execute( \
$line, $words, $how, $i>0);
if( defined($eval_thingie)) {
($read,$chainout)= POSIX
::pipe();
if ($_->[0]==Psh
::Parser
::T_REDIRECT
() and
($_->[1] eq '<&' or $_->[1] eq '>&')) {
if ($_->[3] eq 'chainin') {
} elsif ($_->[3] eq 'chainout') {
my $termflag=!($i==$#array);
($pid,$success,@tmp)= _fork_process
($eval_thingie,$words,
if( !$i && !$pgrp_leader) {
if( $i<$#array && $#array) {
!defined($return_val[0])) {
my $job= Psh
::Joblist
::create_job
($pid,$string);
$job->{pgrp_leader
}=$pgrp_leader;
$success=_wait_for_system
($pid, 1);
my $visindex= Psh
::Joblist
::get_job_number
($job->{pid
});
Psh
::Util
::print_out
("[$visindex] Background $pgrp_leader $string\n");
return ($success,\
@return_val);
return [] if ref $options ne 'ARRAY';
foreach my $option (@
$options) {
if( $option->[0] == Psh
::Parser
::T_REDIRECT
()) {
if ($option->[1] eq '<&') {
POSIX
::dup2
($option->[3], $type);
} elsif ($option->[1] eq '>&') {
POSIX
::dup2
($option->[3], $type);
} elsif ($option->[1] eq '<') {
my $tmpfd= POSIX
::open( $option->[3], &POSIX
::O_RDONLY
);
POSIX
::dup2
($tmpfd, $type);
} elsif ($option->[1] eq '>') {
my $tmpfd= POSIX
::open( $option->[3], &POSIX
::O_WRONLY
|
&POSIX
::O_TRUNC
| &POSIX
::O_CREAT
);
POSIX
::dup2
($tmpfd, $type);
} elsif ($option->[1] eq '>>') {
my $tmpfd= POSIX
::open( $option->[3], &POSIX
::O_WRONLY
|
POSIX
::lseek
($tmpfd,0, &POSIX
::SEEK_END
);
POSIX
::dup2
($tmpfd, $type);
if ($^F
<$type) { # preserve filedescriptors higher than 2
return 0 if ref $options ne 'ARRAY';
foreach my $option (@
$options) {
return 1 if( $option->[0] == Psh
::Parser
::T_REDIRECT
());
# void fork_process( code|program, words,
# int fgflag, text to display in jobs,
# pid of pgroupleader, do not set terminal flag,
my( $code, $words, $fgflag, $string, $options,
$pgrp_leader, $termflag, $forcefork) = @_;
# HACK - if it's foreground code AND perl code AND
# we do not fork, otherwise we'll never get
# the result value, changed variables etc.
if( $fgflag and !$forcefork and ref($code) eq 'CODE'
and !_has_redirects
($options)
my @result= eval { &$code };
Psh
::Util
::print_error
($@
) if $@
&& $@
!~/^SECRET/;
unless ($pid = fork) { #child
Psh
::Util
::print_error_i18n
('fork_failed');
$Psh::OS
::Unix
::forked_already
=1;
close(READ
) if( $pgrp_leader);
_setup_redirects
($options,0);
POSIX
::setpgid
(0,$pgrp_leader||$$);
_give_terminal_to
($pgrp_leader||$$) if $fgflag && !$termflag;
remove_signal_handlers
();
if( ref($code) eq 'CODE') {
} # Avoid unreachable warning
Psh
::Util
::print_error_i18n
('exec_failed',$code);
POSIX
::setpgid
($pid,$pgrp_leader||$pid);
_give_terminal_to
($pgrp_leader||$pid) if $fgflag && !$termflag;
my( $code, $fgflag, $string, $options) = @_;
my ($pid,$sucess,@result)= _fork_process
($code,undef,$fgflag,$string,$options);
my $job= Psh
::Joblist
::create_job
($pid,$string);
my $visindex= Psh
::Joblist
::get_job_number
($job->{pid
});
Psh
::Util
::print_out
("[$visindex] Background $pid $string\n");
_wait_for_system
($pid, 1) if $fgflag;
# Returns true if the system has job_control abilities
sub has_job_control
{ return 1; }
# void restart_job(bool FOREGROUND, int JOB_INDEX)
my ($fg_flag, $job_to_start) = @_;
my $job= Psh
::Joblist
::find_job
($job_to_start);
my $command = $job->{call
};
my $qRunning = $job->{running
};
$verb= ucfirst(Psh
::Locale
::get_text
('foreground'));
# bg request, and it's already running:
$verb= ucfirst(Psh
::Locale
::get_text
('restart'));
my $visindex = Psh
::Joblist
::get_job_number
($pid);
Psh
::Util
::print_out
("[$visindex] $verb $pid $command\n");
eval { _wait_for_system
($pid, 0); };
Psh
::Util
::print_debug_class
('e',"Error: $@") if $@
;
kill 'CONT', -$job->{pid
};
kill 'CONT', -$job->{pgrp_leader
} if $job->{pgrp_leader
};
# Simply doing backtick eval - mainly for Prompt evaluation
my ($read,$write)= POSIX
::pipe();
POSIX
::dup2
($write,fileno(*STDOUT
));
$^F
=$write if ($write>$^F
);
my ($success)= Psh
::evl
($com);
###################################################################
###################################################################
# Setup special treatment of certain signals
# Having a value of 0 means to ignore the signal completely in
# the loops while a code ref installs a different default
# handler. Note that calling _ignore_handler is different than
# setting the signal action to ignore - if you set the signal
# action to ignore, the signal might be passed on to parent processes
# which could decide to handle them for us
'CHLD' => \
&_ignore_handler
,
'CLD' => \
&_ignore_handler
,
'TTOU' => \
&_ttou_handler
,
'TTIN' => \
&_ttou_handler
,
'TERM' => \
&Psh
::OS
::fb_exit_psh
,
'HUP' => \
&Psh
::OS
::fb_exit_psh
,
my @signals= grep { substr($_,0,1) ne '_' } keys %SIG;
# void remove_signal_handlers()
# This used to manually set INT, QUIT, CONT, STOP, TSTP, TTIN,
# The new technique changes the settings of *all* signals. It is
# from Recipe 16.13 of The Perl Cookbook (Page 582). It should be
# compatible with Perl 5.004 and later.
sub remove_signal_handlers
foreach my $sig (@signals) {
next if exists($special_handlers{$sig}) &&
! ref($special_handlers{$sig});
# void setup_signal_handlers
# This used to manually set INT, QUIT, CONT, STOP, TSTP, TTIN,
# See comment for remove_signal_handlers() for more information.
sub setup_signal_handlers
foreach my $sig (@signals) {
if( exists($special_handlers{$sig})) {
if( ref($special_handlers{$sig})) {
$SIG{$sig}= $special_handlers{$sig};
$SIG{$sig} = \
&_signal_handler
;
reinstall_resize_handler
();
# Setup the SIGSEGV handler
sub setup_sigsegv_handler
$SIG{SEGV
} = \
&_error_handler
;
# Setup SIGINT handler for readline
sub setup_readline_handler
$SIG{INT
}= \
&_readline_handler
;
sub remove_readline_handler
$SIG{INT
}= \
&_signal_handler
;
sub reinstall_resize_handler
Psh
::OS
::fb_reinstall_resize_handler
();
&_resize_handler
('WINCH');
setup_readline_handler
();
print "\n"; # Clean up the display
die "SECRET $Psh::bin: Signal $sig\n"; # changed to SECRET... just in case
# void _signal_handler( string SIGNAL )
if ($Psh::currently_active
> 0) {
Psh
::Util
::print_debug
("Received signal SIG$sig, sending to $Psh::currently_active\n");
kill $sig, -$Psh::currently_active
;
} elsif ($Psh::currently_active
< 0) {
Psh
::Util
::print_debug
("Received signal SIG$sig, sending to Perl code\n");
die "SECRET ${Psh::bin}: Signal $sig\n";
Psh
::Util
::print_debug
("Received signal SIG$sig, die-ing\n");
die "SECRET ${Psh::bin}: Signal $sig\n" if $sig eq 'INT';
$SIG{$sig} = \
&_signal_handler
;
Psh
::Util
::print_error_i18n
('unix_received_strange_sig',$sig);
kill 'INT', $$; # HACK to stop a possible endless loop!
Psh
::OS
::check_terminal_size
();
$SIG{$sig} = \
&_resize_handler
;
if (-r
'/etc/debian-version') {
my $editor= $suggestion||$ENV{VISUAL
}||$ENV{EDITOR
};
$path =~ s
|/+|/|g
unless($^O
eq 'cygwin'); # xx////xx -> xx/xx
$path =~ s
|(/\.)+/|/|g; # xx/././xx -> xx
/xx
$path =~ s
|^(\
./)+||s unless $path eq "./"; # ./xx -> xx
$path =~ s|^/(\.\./)+|/|s; # /../../xx -> xx
$path =~ s|/\Z(?!\n)|| unless $path eq "/"; # xx/ -> xx
$dir .= "/" unless substr($dir,-1) eq "/";
# append a slash to each argument unless it has one there
$_ .= "/" if $_ eq '' || substr($_,-1) ne "/";
return canonpath(join('', @args));
sub file_name_is_absolute {
return scalar($file =~ m:^/:s);
if ( $directories !~ m|/\Z(?!\n)| ) {
return split( m|/|, $directories );
my( @directories )= split( m|/|, "${directories
}dummy
" ) ;
$directories[ $#directories ]= '' ;
if ( ! file_name_is_absolute( $path ) ) {
# Figure out the effective $base and clean it up.
if ( !defined( $base ) || $base eq '' ) {
$base = Psh::getcwd_psh() ;
elsif ( ! file_name_is_absolute( $base ) ) {
$base = rel2abs( $base ) ;
$base = canonpath( $base ) ;
$path = catdir( $base, $path ) ;
return canonpath( $path ) ;