$HEADER = <<'EOF'; ------------------------------------ Comm.pl ----------------------------------- This is a free library of IPC goodies. There is no warrenty, but I'd be happy to get ideas for improvements. - Eric.Arnold@Sun.com. It's been tested with Perl4/Perl5 and SunOS4.x and Solaris2.3 - 2.5. Work is being done on AIX3.2.5, IRIX5.3, HP-UX(9), Linux A lot was borrowed from "chat2.pl"(Randal L. Schwartz), and then diverged as its goals became generalized client/server IPC, support for SVR4/Solaris, and to facilitate my "shelltalk" program. Since then, I/we've been using it for all sorts of stuff. Per the notes on creating new modules, here is some boilerplate: Copyright (c) 1995 Eric Arnold. All rights reserved. This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself. See the end of this file for example programs demonstrating usage. It's normally put into a file and "require"d, but can also be simply concatinated to the end of some other perl script. If you do that, use: require "Comm.pl" unless defined &Comm'init; Function summary: (Remember to use prefixes (i.e. "&Comm'init") for anything not exported.) (All file handles passed up from these functions are exported into the caller's package.) init : ---- &Comm'init(); # Required after "require". It sets up all # internal symbols, and exports functions to # caller's package. &Comm'init(1.5); # If first arg is numeric, it specifies a # desired version for compatibility. &Comm'init(1.5, "func",...);# Tell it to export specified function(s), # otherwise, init() will export all documented # functions. open_port : --------- # Open a STREAM socket connection to a host: $handle = &open_port($host, $port, $timeout); open_listen : ----------- # Open a STREAM listen socket on your host: $handle = &open_listen( $port ); # Or you can specify the $host if you need to listen on an address # other than: `uname -n` (E.g. if you have a second ethernet) $handle = &open_listen( $host, $port ); select_it : --------- # Give it a timeout and a list of handles, and it tells you which ones # have data ready (or some condition, like EOF). It's called "select_it" # so it won't clash with "select". @ready_handles = &select_it( $timeout, $handle1, $handle2, ..... ); accept_it : --------- # Complement to "open_listen": ( $new_handle, $rem_host ) = &accept_it( $handle ); open_proc : --------- # Set up a pseudo-tty, and start "$Command" running in it. ( $Proc_pty_handle, $Proc_tty_handle, $Proc_pid ) = &open_proc($Command); wait_nohang : ----------- # Does a portable wait4/waitpid. Used mostly internally. Not exported. &Comm'wait_nohang; expect : ------ # This function scans an input stream for a pattern, without blocking, # a la "sysread()". # # Patterns are scanned in the order given, so later patterns can contain # general defaults that won't be examined unless the earlier patterns # have failed. Be careful of timing problems, however. If you specify # a very general pattern later in the list, it might match undesireably # if a partial packet of data is received. # "$err" can contain "TIMEOUT" or "EOF". # "$before" and "$after" are intended to help you debug your process. # "$before" will contain anything before "$match", or everything # accumulated if "$err" is set. "$after" contains everything after # "$match" (assuming the pattern succeeds). # Each file handle has an associated internal accumulator containing # any data read but not discarded: # - A successful match will discard "$before" and "$match" from # the accumulator. # - A TIMEOUT will return "$before", but not clear the accumulator. # - An EOF will return "$before", and clear the accumulator. # Each call to "expect()" will try to match in the accumulator first. ( $match, $err, $before, $after ) = &expect( $fh, $timeout, 'regexp1', 'regexp2' ); # or $match = &expect( $fh, $timeout, 'regexp1', 'regexp2', ... ); # You can give it any file handle, but remember to pass the type glob, # so it can be used in a different package namespace: open(RDR, "somecommand|"); # or &open3(WRT, RDR, ERR, 'somecommand' ); ( $match, $err, $before, $after ) = expect( *RDR, 1, $pattern ); # $timeout can be an absolute time (i.e. $timeout = time + 10 ) # or just a relative time (i.e. $timeout = 10 ) # If you need to pass in regex options, you can use the Perl5 syntax: &expect( $fh, $timeout, qq{(?i)(?m)what} ); interact : -------- # This connects a process opened with "open_proc()" to the user via # STDIN, and allows them to "interact". # # You specify patterns to trigger return of control to your script, which # can be matched either in STDIN or the process file handle. The # $Proc_pty_handle serves as a delimeter between string patterns for STDIN, # and regex patterns for $Proc_pty_handle. # # Any pattern matched for STDIN isn't sent to the process. Therefore, # patterns for STDIN are treated only as strings (it's too hard to # figure out partial matches on a regex). # You must set terminal modes for programs which don't handle that # themselves (like "telnet"): &stty_sane($Proc_tty_handle); # use $Proc_pty_handle for HP &stty_raw(STDIN); ( $match, $err ) = &interact( "optional string patterns for STDIN", ..., $Proc_pty_handle, "optional regex patterns", ... ); &stty_sane(STDIN); open_udp_port : ------------- # Open a UDP port. There are more variations possible for UDP ports, # so the arguments you can give are more variable: # Just open a UDP socket on your host. You'll have to use "send_to" # if you want to do more than read from it. If you don't specify # a host (i.e. ""), it uses `uname -n`. If you don't specify a port # (i.e. 0 ) it will assign a port for you. $handle = &open_udp_port( "", 0 ) # Set up a connected UPD port. You can "print" to this handle: $handle = &open_udp_port( "local_addr", 5050, "remotehost", 5050 ) etc. send_to : ------- # This is a convenience interface function to "send()". It packs up # the appropriate binary structure from the remote address and port. &send_to( $handle, $buf, $flags, $remote_addr, $remote_port ); sockaddr_struct : --------------- # If you're really pressed for performance, you can save a packed struct, # and use "send()", which saves some overhead with each call: $remote_sockaddr = &sockaddr_struct( $remote_addr, $remote_port ); send( $handle, $buf, 0, $remote_sockaddr ) || die "send $!"; recv_from : --------- # This is another convenience function, which unpacks the returned struct # from "recv()", and tells you what address and port the data came from. # You have to pass it a glob (i.e. *buf) so it can fill that variable with # the data. ( $addr, $port ) = &recv_from($handle, *buf, 10000, 0); close_it : -------- # This will either call "shutdown()" if the handle is a socket type, # or kill the child process if the handle is a pty type. &close_it( $handle ) close_noshutdown : ---------------- # Use this when a parent forks a child to handle a request on a socket # file handle. The parent would like to close the file handle, but # leave the socket alive so the child can continue to read/write it # (the child inherited the file handle and therefore the socket). &close_noshutdown( $handle ); stty_sane, stty_raw, stty_ioctl : -------------------------------- # These use "stty" to set the terminal modes the first time through, # because "stty" is easy and portable. The binary ioctl struct # containing the modes is then cached for subsequent calls to # "ioctl()", which is much faster for switching between modes, but is # a pain to make portable. "$Proc_tty_handle" can be "STDIN". # Use $Proc_pty_handle for HP. &stty_sane( $Proc_tty_handle ); &stty_raw( $Proc_tty_handle ); # "stty_raw/sane" use "stty_ioctl". See the header for # "get_ioctl_from_stty" for more information about getting and saving # binary ioctl structs. &stty_ioctl( $Proc_tty_handle, "stty intr '^c'" ); open_dupsockethandle, open_dupprochandle : ---------------------------------------- # I don't know if anybody will ever use these. They dup file # handles, which will fool the utilities here into thinking that # your file handle (created from some other package) was actually # created by a routine in here. &open_dupsockethandle($handle); &open_dupprochandle($handle); Misc: $Debug is "inherited" from $main'Debug Portability bug-a-boos: - There are two versions of getpty(). getpty_svr4() tries to do the right SVR4 thing, although without direct access to the right function calls :-(. getpty() also works for SVR4/Solaris, using some partial BSD backward compatibility. Neither is all too clean. If you do have "grantpt()" and "ptsname()", etc., but not "/usr/lib/pt_chmod" or the bit hack for "ptsname()" doesn't work for you, try compiling the "pt_chmod.c" and "ptsname.c" programs I've supplied with this package in the tar file. (Remember to give "pt_chmod" setuid perms.) - Once I decide to bite the bullet, and give up support for perl4, it should use "use Socket" for all the socket defines like SOCK_STREAM. There's no getting around putting some of the other defines directly in here, I think (i.e. I_STR). Bugs: - There used to be some odd problems with the value for SOCK_STREAM, depending on whether it was perl4 or perl5 and whether it was compiled under SunOS or Solaris, but it seems to be better now. History: 09/11/94 07:03:04 PM; eric: fixed for Solaris and /dev/tty 09/14/94 02:11:19 AM; eric: close correct file handle in open_listen 09/15/94 03:33:31 AM; eric: added system() 09/19/94 10:48:11 AM; eric: added cheapo/easy ioctl dump/do 10/11/94 11:07:14 AM; eric: added I_POP to clear stream on pty 11/08/94 03:03:19 PM; eric: changed to first try SOCK_STREAM=1, then =2 02/28/95 12:53:22 PM; eric: found the right place to set SO_LINGER! 03/18/95 08:19:46 PM; eric: added timeout arg to open_port 05/07/95 10:56:25 PM; eric: fixed shutdown/close order bug in close() added close_noshutdown 06/08/95 01:06:03 PM; eric: fixed Sol2.4 problem with string literal as last arg to syscall($SYS_ioctl 09/13/95 10:29:26 AM; eric: added emport_FH() function 09/17/95 06:05:35 PM; eric: &open_udp_port(), plus examples at the end 09/19/95 07:10:03 PM; eric: revamped &open_udp_port(), put all sockaddr stuff into &sockaddr_struct(), also added &send_to() and &recv_from() 10/03/95 10:23:00 AM; eric: added expect(); $Version, more portable stty_raw/sane() 10/05/95 04:12:57 PM; eric: added interact(), getpty_svr4(), exported funcs 10/07/95 02:14:57 PM; eric: added stty_ioctl(), version 1.2, now Comm.pl 10/09/95 04:51:10 PM; eric: expect() now keeps accum. data per FH, v1.3 10/12/95 07:02:31 PM; eric: added support for user supplied "pt_chmod" and "ptsname" programs 10/16/95 19:20:13 PM; eric: fixes for AIX2.3 11/02/95 05:42:51 PM; eric: partial fixes for HP-UX9, Linux, v1.4 11/21/95 03:21:51 PM; eric: *lots* of hacking for HP-UX, v1.5 EOF package Comm; #&init; # nah, force them to call it, proper. sub init{ local( $version ); if ( $_[0] =~ /^[\d.-]+$/ ) { $version = shift; } local( @args) = @_; $Version = 1.5; if ( $version ) { if ( $Version ne $version ) { warn "Package version, $Version, does not match requested, $version"; } } local( $pkg ) = caller; $My_pkg = "Comm"; *Debug = *main'Debug; # set this before export_sym if ( !@args ) { # For some reason, exporting to myself causes later export to main to fail if ( $pkg ne $My_pkg ) { &export_sym( $pkg, ( "open_port", "open_listen", "open_udp_port", "open_proc", "send_to", "recv_from", "accept_it", "select_it", "expect", "interact", "close_noshutdown", "close_it", "stty_sane", "stty_raw", "stty_ioctl", ) ); } } else { &export_sym( $pkg, @args ); } return if $Inited; $Inited = 1; $OS_name = `uname`; chop $OS_name; if( $OS_name eq "SunOS" && ( ! -f "/vmunix" ) ) { $OS_name = "Solaris"; } # First, try to divide the world into two camps. It works in somewhat, # but there will be many overrides :-( if ( -f "/vmunix" ) { $OS_type = "BSD"; } else { $OS_type = "SVR4"; } if ( $OS_name eq "HP-UX" || $OS_name eq "AIX" ) { $OS_type = "BSD"; } elsif( $OS_name eq "Linux" ) { require 'sys/syscall.ph'; $OS_type = "SVR4"; $WNOHANG = 1; } print STDERR "OS_type=$OS_type\n" if $Debug; chop( $My_host = `uname -n ` ); $Next_handle="commutils000000"; $Sockaddr_t = 'S n a4 x8'; # actually should be named $Sockaddr_in_t $SYS_ioctl = 54; $AF_INET = 2; if ( $OS_type eq "SVR4" ) { $SOCK_STREAM=2; # the weenies just had to reverse it! $SOCK_DGRAM=1; # from /usr/include/sys/termios.h $tIOC =( unpack("C", 't') << 8); $TIOCGETP =($tIOC|8); $TIOCSETP =($tIOC|9); $TIOC =( unpack("C", 'T' ) <<8); $TCGETS =($TIOC|13); $TCSETS =($TIOC|14); $TCSANOW =(( unpack("C",'T')<<8)|14); #/* same as TCSETS */ $TCGETA =($TIOC|1); $TCSETA =($TIOC|2); # From /usr/include/sys/stropts.h $STR = ( unpack("C", "S") <<8 ); $I_PUSH = ($STR|02); #$I_PUSH = 21250; $I_POP = ($STR|03); $I_LOOK = ($STR|04); $I_STR = ($STR|010); #define I_FLUSH (STR|05) # from /usr/include/sys/ptms.h: $ISPTM = ((ord('P')<<8)|1); #/* query for master */ $UNLKPT = ((ord('P')<<8)|2); #/* unlock master/slave pair */ if( $OS_name eq "Linux" ) { $SOCK_STREAM=1; $SOCK_DGRAM=2; $TCGETA = 0x5405; $TCSETA = 0x5406; } } else { $SOCK_STREAM=1; $SOCK_DGRAM=2; if ( $OS_name eq "HP-UX" ) { # Note: "use POSIX" has the nasty habit of causing re-open of STDIN # not to use file descriptor 0, if done between closing and re-opening. eval "use POSIX"; # quote for perl5.000 (otherwis,e causes # abort during compilation even if not on HP). $WNOHANG = 1; $TIOCGETP=0x40087408; $TIOCSETP=0x80087409; $TCGETA=0x40125401; $TCSETA=0x80125402; $TIOCSCTTY=0x20005421; $TIOCTTY=0x80047468; $TIOCTRAP=0x80047467; $TIOCMONITOR=0x8004745f; $TIOCREQSET=0x80187464; $TIOCREQCHECK=0x40187471; $TIOCCLOSE=0x20007462; # not defined: $TIOCNOTTY } else { $TIOCGETP=0x40067408; #d(1074164744) $TIOCSETP=0x80067409; #d(-2147060727) $TIOCNOTTY=0x20007471; } } local($ioctl); for $ioctl ( TIOCGETP, TIOCSETP, TIOCSCTTY, TIOCTTY, TIOCTRAP, TIOCMONITOR, TIOCGETP, TIOCSETP, TIOCNOTTY, TIOCGETP, TIOCSETP, TCGETS, TCSETS, TCSANOW, TCGETA, TCSETA, TIOCREQSET, TIOCREQCHECK, TIOCCLOSE ) { eval qq,\$Ioctl_names{\$$ioctl} .= "$ioctl " ,; } # stuff common to both OS types: $SOL_SOCKET =0xffff ;#/* options for socket level */ $SO_DEBUG =0x0001 ;#* turn on debugging info recording */ $SO_ACCEPTCONN =0x0002 ;#* socket has had listen() */ $SO_REUSEADDR =0x0004 ;#* allow local address reuse */ $SO_KEEPALIVE =0x0008 ;#* keep connections alive */ $SO_DONTROUTE =0x0010 ;#* just use interface addresses */ $SO_BROADCAST =0x0020 ;#* permit sending of broadcast msgs */ $SO_USELOOPBACK =0x0040 ;#* bypass hardware when possible */ $SO_LINGER =0x0080 ;#* linger on close if data present */ $SO_OOBINLINE =0x0100 ;#* leave received OOB data in line */ } sub open_port{ die "$My_pkg'init not called, aborting" unless $Inited; local( $remote_addr, $remote_port, $timeout ) = @_; local( $new_handle ) = "socket" . ++$Next_handle; local( %saveSIG, $ret ); local( $local_sockaddr ) = &sockaddr_struct( $My_host, 0 ); local( $remote_sockaddr ) = &sockaddr_struct( $remote_addr, $remote_port ); unless (socket( $new_handle, $AF_INET, $SOCK_STREAM, 6)) { ($!) = ($!, close( $new_handle)); # close new_handle while saving $! print STDERR "Socket error $!\n" if $Debug; return undef; } unless (bind( $new_handle, $local_sockaddr)) { ($!) = ($!, close( $new_handle)); # close new_handle while saving $! print STDERR "bind error $!\n" if $Debug; return undef; } %saveSIG=%SIG; if ( $timeout ) { $SIG{ALRM} = "timedout"; alarm($timeout); } eval { $ret = connect( $new_handle, $remote_sockaddr) }; if ( !$ret || ($@ =~ /^timedout/) ) { ($!) = ($!, close( $new_handle)); # close new_handle while saving $! #die "connect failed, $!"; print STDERR "connect error eval=($@)$!\n" if $Debug; if ( $@ =~ /^timedout/ ) { $! .= ", timeout after $timeout seconds";} return undef; } if ( $timeout ) { %SIG = %saveSIG; alarm(0); } select((select( $new_handle), $| = 1)[0]); &export_FH( (caller)[0], $new_handle ); return $new_handle; } sub timedout { die "timedout"; } # Usage: # open_udp_port( $local_addr, $local_port, $remote_addr, $remote_port ); # or # open_udp_port( $local_addr, $local_port ); # # To create a port to read on, unconnected, which will create a port based # on "uname -n": # # open_udp_port( "", 5050 ) # # A "connected" socket where we don't care what port we're reading on looks # like: # # open_udp_port( "", 0, "remotehost", 5050 ) # # This will read on the broadcast address: # # open_udp_port( "129.145.43.255", 5050 ) # # Note: reading on broadcast only works for Solaris, haven't found SunOS fix sub open_udp_port{ die "$My_pkg'init not called, aborting" unless $Inited; local( $local_addr, $local_port, $remote_addr, $remote_port, $proto ); local( $local_sockaddr, $new_handle ); if ( @_ == 2 ) { ( $local_addr, $local_port ) = @_; } elsif ( @_ == 4 ) { ( $local_addr, $local_port, $remote_addr, $remote_port ) = @_; } else { warn "open_udp_port: too few args"; return undef; } if ( ! $local_addr ) # specified as "", 0, or undef { $local_sockaddr = &sockaddr_struct( $My_host, $local_port ); } else { $local_sockaddr = &sockaddr_struct( $local_addr, $local_port ); } $new_handle = "socket" . ++$Next_handle; $proto = getprotobyname("udp"); unless (socket( $new_handle, $AF_INET, $SOCK_DGRAM, $proto)) { print STDERR "socket failed: $!\n" if $Debug; ($!) = ($!, close($new_handle)); # close S while saving $! return undef; } # /usr/demo/SOUND/src/radio/libradio/netbroadcast.c says that SO_BROADCAST # not required for Suns # # Don't know if you can just pass a 1 or need to pass a struct: #$val = pack("I", 1 ); #setsockopt( $new_handle, $SOL_SOCKET, $SO_BROADCAST, $val ) || die "setsockopt: $!"; #setsockopt( $new_handle, $SOL_SOCKET, $SO_BROADCAST, 1 ) || die "setsockopt: $!"; select( (select( $new_handle ), $| = 1 )[0] ); unless ( bind( $new_handle, $local_sockaddr )) { #die "bind failed: $!"; print STDERR "bind failed: $!\n" if $Debug; ($!) = ($!, close($new_handle)); # close S while saving $! return undef; } if ( $remote_addr ) { $remote_sockaddr = &sockaddr_struct( $remote_addr, $remote_port ); connect( $new_handle, $remote_sockaddr) || die "connect $!"; print STDERR "connected to $remote_addr $remote_port\n" if $Debug; } else { print STDERR "binding unconnected\n" if $Debug; } # find out what we actually did: local( $family, $port, @myaddr ) = unpack( "S n C4 x8", # $Sockaddr_t is wrong for unpacking getsockname( $new_handle )); if ( $Debug ) { print "after bind [connect], ( family, port, myaddr ) =", "( $family, $port, @myaddr) \n" } &export_FH( (caller)[0], $new_handle ); return $new_handle; } sub sockaddr_struct{ local( $addr, $port ) = @_; local( $addr_struct, $sockaddr_struct, @addr_info ); if ($addr =~ /^(\d+)+\.(\d+)\.(\d+)\.(\d+)$/) { $addr_struct = pack('C4', $1, $2, $3, $4); } else { return undef unless ( @addr_info = gethostbyname( $addr ) ); $addr_struct = $addr_info[4]; } $sockaddr_struct = pack( $Sockaddr_t, 2, $port, $addr_struct); if ( $Debug ) { print STDERR "\$sockaddr_struct = pack($Sockaddr_t, 2, $port, ip=(", join(".", unpack("C*", $addr_struct ) ), "),addr=($addr))\n"; } return $sockaddr_struct; } # Note: it would be faster to save a copy of the sockaddr, # and use "send()", if you really need performance: sub send_to{ local( $handle, $buf, $flags, $remote_addr, $remote_port ) = @_; local( $remote_sockaddr ); $remote_sockaddr = &sockaddr_struct( $remote_addr, $remote_port ); send( $handle, $buf, $flags, $remote_sockaddr ) || die "send_to $!"; } # ($addr, $port ) = recv_from($handle, *buf, 10000, 0); sub recv_from{ local( $handle, *buf, $len, $flags ) = @_; local( $remote_info ); return undef unless ( $remote_info = recv($handle, $buf, 10000, 0) ); local( $family, $port, @addr ) = unpack( "S n C4 x8", $remote_info ); local($name, $aliases, $type, $len, $acceptaddr) = gethostbyaddr( pack( 'C4', @addr ), 2 ); return ( $name, $port ); } sub open_listen{ die "$My_pkg'init not called, aborting" unless $Inited; local( $local_addr, $local_port ); local( $new_handle ); if ( @_ == 2 ) { ( $local_addr, $local_port ) = @_; } elsif ( @_ == 1 ) { ( $local_port ) = @_; $local_addr = $My_host; } else { warn "open_listen: too few args"; return undef; } local( $local_sockaddr ) = &sockaddr_struct( $local_addr, $local_port ); $new_handle = "socket" . ++$Next_handle; unless (socket( $new_handle, $AF_INET, $SOCK_STREAM, 6)) { print STDERR "socket failed: $!\n" if $Debug; ($!) = ($!, close($new_handle)); # close S while saving $! return undef; } # We want it it release the socket for immediate reuse if the server is # shutdown/restarted. It seems that SO_LINGER and SO_REUSEADDR are most # pertinant, but SO_KEEPALIVE seems like it might be nice too, for # notification of peer disappearance. $linger = pack("II", 0, 0 ); # linger is a C struct in socket.h setsockopt( $new_handle, $SOL_SOCKET, $SO_LINGER, $linger); setsockopt( $new_handle, $SOL_SOCKET, $SO_KEEPALIVE, 1); setsockopt( $new_handle, $SOL_SOCKET, $SO_REUSEADDR, 1); # contributed by somebody: #setsockopt(S, "0xffff", "0x0004", 1); unless ( bind( $new_handle, $local_sockaddr )) { #die "bind failed: $!"; print STDERR "bind failed: $!\n" if $Debug; ($!) = ($!, close($new_handle)); # close S while saving $! return undef; } unless ( listen( $new_handle, 1 )) { #die "listen failed: $!"; print STDERR "listen failed: $!\n" if $Debug; ($!) = ($!, close($new_handle)); # close S while saving $! return undef; } select( (select( $new_handle ), $| = 1 )[0] ); local( $family, $port, @myaddr ) = unpack( "S n C C C C x8", getsockname( $new_handle )); &export_FH( (caller)[0], $new_handle ); return $new_handle; } sub accept_it{ local( $handle ) = @_; local( $addr, $af, $port, $inetaddr, $acceptaddr ) = (); $new_handle = "socket" . ++$Next_handle; unless( ( $addr = accept( $new_handle, $handle ) ) ) { print STDERR "accept failed: $!"; } ( $af, $port, $inetaddr ) = unpack( $Sockaddr_t, $addr ); @inetaddr = unpack( 'C4', $inetaddr ); ($name, $aliases, $type, $len, $acceptaddr) = gethostbyaddr( pack( 'C4', @inetaddr ), 2 ); select( ( select( $new_handle ), $| = 1 )[0] ); $name = join(".", @inetaddr ) unless $name; &export_FH( (caller)[0], $new_handle ); return ($new_handle,$name); } sub select_it { local( $timeout, @handles ) = @_; # Init these to make -w happy: local( @ready ) = (); local( $rout, $rmask, $handle, $eout, $emask ) = ( '', '', '', '', '' ); for $handle ( @handles ) { vec( $rmask, fileno( $handle ), 1 ) = 1; vec( $emask, fileno( $handle ), 1 ) = 1; } ( $nfound, $timeleft ) = select( $rout=$rmask, undef, $eout=$emask, $timeout ); print "nfound=$nfound\n" if $DEBUG; if ( $nfound < 1 ) { if ( $nfound < 0 ) { print "error=$!\n" if $DEBUG; } return @ready; } # You could also do: # @bit = split(//,unpack('b*',$rout)); # if ($bit[fileno(STDIN)] == 1){ ... }; for $handle ( @handles ) { if ( vec( $rout, fileno( $handle ), 1 ) == 1 ) { print "fh=$handle is ready\n" if $DEBUG; push( @ready, $handle ); } if ( vec( $eout, fileno( $handle ), 1 ) == 1 ) { if ( $OS_name eq "HP-UX" ) { &pty_clear_trap($handle); } print "Exception on read_handle=$handle\n" if $DEBUG; } } return @ready; } sub open_proc { #eval "use Pty_spawn"; #if ( ! $@ ) #{ #return &open_proc_Pty_spawn( @_ ); #} die "$My_pkg'init not called, aborting" unless $Inited; local(@cmd) = @_; #local(*TTY,*PTY); # PTY must not die when sub returns local( $pty_handle, $tty_handle ); local($pty,$tty); $pty_handle = "proc" . ++$Next_handle; *PTY = $pty_handle; # glob magic needed, apparently :-( $tty_handle = "proc" . ++$Next_handle; *TTY = $tty_handle; ($pty,$tty) = &getpty(PTY,TTY); die "Cannot find a new pty" unless defined $pty; local($pid) = fork; die "Cannot fork: $!" unless defined $pid; print STDERR "open_proc: mypid=$$, \$PIDS{$pty_handle} = $pid\n" if $Debug; $PIDS{$pty_handle} = $pid; $PIDS{$tty_handle} = $pid; $TTYS{$tty_handle} = $tty; $TTYS{$pty_handle} = $tty; $PTY_for_TTY{$tty_handle} = $pty_handle; $TTY_for_PTY{$pty_handle} = $tty_handle; unless ($pid) { &do_tty_child( $tty_handle, $tty, @cmd ); } &export_FH( (caller)[0], $pty_handle,$tty_handle ); if ( wantarray ) { print STDERR "open_proc returning: ($pty_handle,$tty_handle,$pid) \n" if $Debug; return ($pty_handle,$tty_handle,$pid); } else { print STDERR "open_proc returning: pty_handle=$pty_handle \n" if $Debug; return $pty_handle; } } <pid; $pty_handle = "proc" . ++$Next_handle; $tty_handle = "proc" . ++$Next_handle; *{$pty_handle} = *{$pty->master}; *{$tty_handle} = *{$pty->slave}; print "slave=", $pty->slave, "\n"; # what does this do?? #sub gensym #{ # my ($what) = @_; # local *{"Pty_spawn::$what"}; # \delete $Pty_spawn::{$what}; #} $PIDS{$pty_handle} = $pid; $TTYS{$tty_handle} = $pty->tty; $PTY_for_TTY{$tty_handle} = $pty_handle; if ( wantarray ) { print STDERR "open_proc returning: ($pty_handle,$tty_handle,$pid) \n" if $Debug; return ($pty_handle,$tty_handle,$pid); } else { print STDERR "open_proc returning: pty_handle=$pty_handle \n" if $Debug; return $pty_handle; } } EOF sub do_tty_child{ local( $tty_fh, $tty_name, @cmd ) = @_; local( *TTY ) = $tty_fh; print STDERR "do_tty_child: ( $tty_fh, $tty_name, @cmd )\n" if $Debug; # Since we have to close STDOUT/STDERR in order to get a new controlling # tty, we have to find some other place to put the debug data: local(*DEBUG_FH); if( $Debug ) { open(DEBUG_FH, ">Comm.pl.debug" ); select DEBUG_FH ; $|=1; select STDOUT; } close STDIN; close STDOUT; close STDERR; # Try to do setsid for systems that have it: if ( $OS_name eq "Solaris" ) { &syscall_safe(39,3); #* setsid():: syscall(39,3) } elsif ( $OS_name eq "Linux" ) { syscall($SYS_setsid); } elsif ( $OS_name eq "HP-UX" ) { # I hope they have Perl5, cause there's no other access to setsid(), # and without it, a new controlling terminal group is not set, and # certain things like ^c interrupt signals don't get sent. eval " POSIX::setsid()"; # quote it for perl4 compat # TIOCSCTTY doesn't seem to be necessary: #ioctl( STDIN, $TIOCSCTTY, 0 ); } else { #??? } # Try to do setpgrp for systems that use it: if( $OS_name eq "SunOS" ) # Solaris has setsid, so do that instead { # Check to see if POSIX is/can be set, which will affect which form of # setpgrp() to use. # perl5.000 "use POSIX" has the nasty habit of opening some file descriptors # which causes subsequent reopens of STDIN/OUT/ERR to open on the wrong # numbers (i.e. not 0, 1, 2 ) if ( $] >= 5.001 ) { # Note: "use POSIX" has the nasty habit of causing re-open of STDIN # not to use file descriptor 0, if done between closing and re-opening. eval "use POSIX"; # eval for perl4 print DEBUG_FH "do_tty_child: use POSIX returned ($@)\n" if $Debug; } else { eval "somejunktoset$@"; } if ( $@ ) { print DEBUG_FH "do_tty_child: trying to setpgrp(0,$$) \n" if $Debug; setpgrp(0,$$); } else { print DEBUG_FH "do_tty_child: trying to POSIX setpgrp() \n" if $Debug; eval "setpgrp()"; # perl4 thinks this is a syntax error if ( $@ ) { print DEBUG_FH "do_tty_child: POSIX setpgrp() failed\n" if $Debug; } } } elsif ( $OS_name eq "HP-UX" ) { #print DEBUG_FH "do_tty_child: trying to setpgrp(0,0) \n" if $Debug; #setpgrp(0, 0); # HP dies with setpgrp(0,$$); } # note, setpgrp kills AIX process. if ( $OS_name eq "SunOS" ) { # (TIOCNOTTY not defined on HP-UX) # this ioctl is necessary for "isig" to work right, # and otherwise "csh" freaks out and hangs: if (open( DEVTTY, "/dev/tty")) { &ioctl_syscall( DEVTTY, $TIOCNOTTY, undef ); close DEVTTY; } else { } } print DEBUG_FH "do_tty_child: reopening STDIN \n" if $Debug; open(STDIN,"<$tty_name"); #open(STDIN,"<&TTY"); # fails to assign controlling tty! (Sun) open(STDOUT,">&TTY"); #open(STDOUT,">$tty_name"); # This causes weirdo problems with AIX open(STDERR,">&STDOUT"); # Wait until STDERR is open to send error message :-) die "Should be 0: fileno(STDIN) = " . fileno(STDIN) unless fileno(STDIN) == 0; # sanity die "Should be 1: fileno(STDOUT) = " . fileno(STDOUT) unless fileno(STDOUT) == 1; # sanity die "Should be 2: fileno(STDERR) = " . fileno(STDERR) unless fileno(STDERR) == 2; # sanity close(PTY) || print "error closing master handle:$!\n"; print DEBUG_FH "do_tty_child: mypid=$$, execing @cmd, STDIN=$tty_name STDOUT=$tty_name \n" if $Debug; if ( scalar(@cmd) == 1 ) { exec $cmd[0] || die "Cannot exec @cmd: $!"; } elsif ( scalar(@cmd) > 1 ) { exec @cmd || die "Cannot exec @cmd: $!"; } else { # Oh no! } } sub getpty { ## private local( $_PTY, $_TTY ) = @_; local( $pty, $tty ); local( @ptys ); # Force given filehandle names explicitly into caller's package: $_PTY =~ s/^([^']+)$/(caller)[$[]."'".$1/e; $_TTY =~ s/^([^']+)$/(caller)[$[]."'".$1/e; if ( -e "/dev/ptmx" || -e "/dev/ptc" ) { return &getpty_svr4($_PTY,$_TTY); } @ptys = `ls /dev/pty* 2>/dev/null`; chop @ptys; if ( @ptys && ! -d "/dev/ptym" ) { $Have_pty = 1; } else { @ptys = `ls /dev/ptym/* 2>/dev/null`; chop @ptys; if ( @ptys ) { # HP-UX uses ptym: $Have_ptym = 1; } else { die "Don't know how to allocate a pseudo-tty on your system"; } } for $pty ( @ptys ) { open($_PTY,"+>$pty") || next; select((select($_PTY), $| = 1)[0]); if ( $Have_pty ) { ($tty = $pty) =~ s/pty/tty/; } elsif ( $Have_ptym ) { ($tty = $pty) =~ s:/dev/ptym/pty:/dev/pty/tty:; } print STDERR "getpty: trying pty=$pty, tty=$tty\n" if $Debug; open($_TTY,"+>$tty") || next; select((select($_TTY), $| = 1)[0]); system "stty nl > $tty < $tty"; # might cause AIX timing problems?? print STDERR "getpty: returning ($pty,$tty)\n" if $Debug; return ($pty,$tty); } return undef; } # I don't know if this is any more portable than the OS_type switches # in getpty(). It has that scarey bit thing it does with $rdev. # The basic code (thanks!) is from: casper@fwi.uva.nl (Casper H.S. Dik) sub getpty_svr4{ local( $MASTER, $SLAVE ) = @_; local( $master, $master_fd, $slave, $rdev, @attrib ); local( $i ); $master = "/dev/ptmx"; $master = "/dev/ptc" if ( -e "/dev/ptc" ); # Try a few times, in case we're competing with another process for ( $i = 0 ; ; $i++ ) { if ( open($MASTER, "+>$master") ) { last; } elsif ( $i >= 5 ) { warn "Could not open $master, $!, after $i attempts"; return undef; } sleep 1; } select((select($MASTER), $| = 1)[0]); $master_fd = fileno( $MASTER ); # Perl sets close-on-exec. stupid.[Casper] fcntl($MASTER, 2, 0); #@attrib = stat($MASTER); @attrib = eval " stat($MASTER ) "; # otherwise, it thinks $MASTER is filename $rdev = $attrib[6]; print STDERR "getpty_svr4: stat($MASTER)=(",join(",",@attrib),"), 6=$rdev\n" if $Debug; # The user might have an executable "ptsname" program: eval "$slave = `ptsname $master_fd 2>/dev/null`"; # trap error messages chop $slave; if ( !$slave ) { print STDERR "ptsname not found, using Solaris minor numbers\n" if $Debug; # Solaris: # ptsname - not portable probably: assumes 14 bit minor numbers. # only a problem if it's less than 14bits, I think. [Casper] print STDERR "rdev=$rdev\n" if $Debug; $rdev &= (1<<14) - 1; $slave = "/dev/pts/$rdev"; } print STDERR "slave=$slave, ptsname($master_fd)=$slave\n" if $Debug; # Try to find "pt_chmod". It *might* be in "/usr/lib". $ENV{PATH} .= ":/usr/lib" unless $ENV{PATH} =~ m!/usr/lib[^/]*!; # grantpt() function emulation, apparently it calls pt_chmod: local($cmd) = "pt_chmod $master_fd"; print STDERR "system ($cmd)\n" if $Debug; system $cmd || die "pt_chmod failed"; # unlockpt (send STREAMs message UNLKPT) [Casper] $p = pack("i3p", $UNLKPT, 0, 0, $ret); ioctl($MASTER, $I_STR, $p ); # open slave open($SLAVE,"+>$slave") || die "could not open slave, errno=$!"; if ( $OS_name eq "Solaris" ) { # push streams modules ptem and ldterm, # but first remove any modules that might have been hanging around. local( $pop ) = pack( "p", $pop ); ioctl( $SLAVE, $I_POP, 0 ); ioctl( $SLAVE, $I_POP, 0 ); ioctl( $SLAVE, $I_POP, $pop ); #print "looked: len=", length($pop),"($pop)\n"; #syscall($SYS_ioctl, fileno($_TTY), $I_LOOK, $pop ); #print "looked: len=", length($pop),"($pop)\n"; #$pop = pack( "p", $pop ); #syscall($SYS_ioctl, fileno($_TTY), $I_LOOK, $pop ); #print "looked: len=", length($pop),"($pop)\n"; # $tmp needed because Solaris2.4,2.5 complains: # Modification of a read-only value attempted at ... # if you use a string literal instead, E.g.: ###syscall($SYS_ioctl, fileno($_TTY), $I_PUSH, "ptem" ); local($module) = "ptem"; ioctl($SLAVE, $I_PUSH, $module ) || die "ioctl $module failed, errno=$!"; $module = "ldterm"; ioctl($SLAVE, $I_PUSH, $module ) || die "ioctl $module failed, errno=$!"; $module = "ttcompat"; ioctl($SLAVE, $I_PUSH, $module ) || die "ioctl $module failed, errno=$!"; } #system "stty nl < $slave > $slave"; # not sure this does anything useful print STDERR "getpty_svr4 returning ($master,$slave)\n" if $Debug; return ($master,$slave); } # This function scans an input stream for a pattern, # without blocking, a la "sysread()". # # $timeout_time is the time (either relative to the current time, or # absolute, ala time(2)) at which a timeout event occurs. # # Each pat is a regular-expression (probably enclosed in single-quotes # in the invocation). # # Patterns are scanned in the order given, so later patterns can contain # general defaults that won't be examined unless the earlier patterns # have failed. Be careful of timing problems, however. If you specify # a very general pattern later in the list, it might match undesireably # if a partial packet of data is received. E.g.: # expect( 10, 'login:', '.+' ); # will probably match # Trying 129.145.... # prematurely, since the stuff about "login:" is received in a separate # packet about second before the rest of the stuff: # Connected to myhost. # Escape character is '^]'. # # UNIX(r) System V Release 4.0 (myhost) # # login: # # # ^ and $ should work, respecting the current value of $*. %Accum = (); # shut up -w sub expect { local( $fh, $endtime, @patterns ) = @_; local( $pattern, $accum, $match, $before, $after, $err ); local( $rmask, $nfound, $nread, $buf ); local( $pkg ) = caller; $endtime += time if $endtime < 600_000_000; #print STDERR "expect: fh=$fh, time=",time,", endtime=$endtime\n" if $Debug; # try to speed things up when the child dies if ( $PIDS{$fh} ) { &wait_nohang; if ( !kill( 0, $PIDS{$fh} ) ) { $endtime = 0; } } LOOP: { if ( $Accum{$fh} ne "" ) { for $pattern ( @patterns ) { if ( $Accum{$fh} =~ /$pattern/ ) { ( $match, $before, $after ) = ( $&, $`, $' ); $Accum{$fh} = $after; last LOOP; } } } $rmask = ""; vec($rmask,fileno( $fh ),1) = 1; ($nfound, $rmask) = select($rmask, undef, undef, $endtime - time); if ($nfound) { #print STDERR "expect: nfound=$nfound, reading fh=$fh\n" if $Debug; # Oddly enough, 1000 seems to be about optimal. 10,000 is actually # slower, since the bottleneck seems to be the above regex match, # which takes much more time on longer strings, even if it's just # ^.*\n $nread = sysread($fh, $buf, 1000); if ($nread > 0) { $Accum{$fh} .= $buf; } else { print STDERR "expect: sysread returned null, returning EOF\n" if $Debug; $before = $Accum{$fh}; $Accum{$fh} = ""; $err = "EOF"; last LOOP; } } else { $before = $Accum{$fh}; $err = "TIMEOUT"; last LOOP; } redo LOOP; } if ( $err eq "TIMEOUT" ) { # only do this bit when we get a timeout, otherwise, I suppose there # is the potential of having data in the buffer after the child dies, # and we wouldn't want to return EOF yet. if ( $PIDS{$fh} ) { print STDERR "expect: checking pid $PIDS{$fh} \n" if $Debug; &wait_nohang; if ( !kill( 0, $PIDS{$fh} ) ) { $before = $Accum{$fh}; $Accum{$fh} = ""; $err = "EOF"; print STDERR "expect: pid $PIDS{$fh} gone, returning EOF\n" if $Debug; } } } if ( wantarray ) { return ( $match, $err, $before, $after ); } else { if ( $err eq "TIMEOUT" ) { #$err = "error:$err, errno:($!), after($Accum{$fh})"; # rats! I can't set $! to any value: it only accepts valid errno's #$r = eval qq{ package main ; \$! = "$err" ; die "error=(\$!)" }; #print "set err, r=$r, \@ = ($@), err=($err)\n"; # Still doesn't work: eval qq{ package $pkg ; \$! = 4 }; # EINTR } elsif ( $err eq "EOF" ) { eval qq{ package $pkg ; \$! = 5 }; # EIO } return $match; } } # I only seem to receive traps when I set TIOCTRAP to 0, oddly. sub pty_select_clear_trap { local( $handle ) = @_; return if $handle eq "STDIN"; if ( $PTY_for_TTY{$handle} ) { $handle = $PTY_for_TTY{$handle}; } # Init these to make -w happy: local( @ready ) = (); local( $rout, $rmask, $eout, $emask ) = ( '', '', '', '', '' ); local( $request, $junk, $ioctl_info ); LOOP:{ vec( $emask, fileno( $handle ), 1 ) = 1; ( $nfound, $timeleft ) = select( undef, undef, $eout=$emask, 0 ); print STDERR "pty_select_clear_trap: after select fh=$handle, nfound=$nfound\n" if $Debug; if ( vec( $eout, fileno( $handle ), 1 ) == 1 ) { print STDERR "pty_select_clear_trap: exception on fh=$handle\n" if $Debug; } if ( $nfound < 1 ) { if ( $nfound < 0 ) { print STDERR "pty_select_clear_trap: error=$!\n" if $Debug; } return ; } &pty_clear_trap($handle); redo LOOP; } } sub pty_clear_trap{ local($handle) = @_; return if $handle eq "STDIN"; if ( $PTY_for_TTY{$handle} ) { $handle = $PTY_for_TTY{$handle}; } print STDERR "pty_clear_trap: before ioctl TIOCREQCHECK \n" if $Debug; ioctl( $handle, $TIOCREQCHECK, $ioctl_info) || die "$!"; local($request_info_t) = "IIISSII"; ( $request, $argget, $argset, $pgrp, $pid, $errno_error, $return_value ) = unpack( $request_info_t, $ioctl_info ); print STDERR "pty_clear_trap: request=$request (TIOCCLOSE=$TIOCCLOSE) \n" if $Debug; if ( $request == $TIOCCLOSE ) { } else { $errno_error = $return_value = 0; $ioctl_info = pack( $request_info_t, $request, $argget, $argset, $pgrp, $pid, $errno_error, $return_value ); ioctl( $handle, $TIOCREQSET, $ioctl_info)|| die "$!"; #/* presumably, we trapped an open here */ } } # From /usr/include/sys/ptyio.h on HP-UX: #struct request_info { # int request; /* ioctl command received (read only) */ # int argget; /* request to get argument trapped on # on slave side (read only) */ # int argset; /* request to set argument to be returned # to slave side (read only) */ # short pgrp; /* process group number of slave side process # doing the operation (read only) */ # short pid; /* process id of slave side process # doing the operation (read only) */ # int errno_error; /* errno(2) error returned to be # returned to slave side (read/write) */ # int return_value; /* return value for slave side (read/write) */ #}; # The pattern matched in STDIN isnt' sent to the proc., # therefore, patterns for STDIN are treated only as strings. # # Usage: $match = &interact( "optional string patterns for STDIN", # $Proc_pty_handle, "optional regex patterns" ); sub interact { local( @args ) = @_; local( $caller ) = caller; local( $pattern, @stdin_patterns, @handle_patterns ); local( $regex_accum, $string_accum ); local( $match, $err ); local( $handle, @ready_handles, $ready_handle ); local( $c, $s, $waiting ); for $arg ( @args ) { if ( $arg =~ /commutils\d+$/ ) { $handle = $arg; next; } if ( $handle ) { push( @handle_patterns, $arg ); } else { push( @stdin_patterns, $arg ); } } die "No appropriate file handle passed to interact" unless $handle; #&system_proc( $handle, "stty sane" ); # not my job! $| = 1; # STDOUT better be selected, # or nothin's gunna work anyway if ( $Accum{$handle} ne "" ) { print $Accum{$handle} ; $Accum{$handle} = ""; } LOOP: { @ready_handles = &select_it(1, STDIN,$handle); for $ready_handle ( @ready_handles ) { if ( $ready_handle eq $handle ) { last unless sysread( $handle, $buf, 100000 ); print $buf; #($buf,$ret) = Pty_spawn::Pty_read(fileno($handle)); #last if $ret; #print "($buf)"; $regex_accum .= $buf; for $pattern ( @handle_patterns ) { if ( $regex_accum =~ /$pattern/ ) { $match = $&; last LOOP; } } $regex_accum =~ s/^.*[\r\n]//; } if ( $ready_handle eq "STDIN" ) { last unless sysread( STDIN, $buf, 1024 ); $string_accum .= $buf; $saw_something = 0; for $pattern ( @stdin_patterns ) { if ( $string_accum eq $pattern ) { $match = $pattern; last LOOP; } # if it's a string pattern, don't send to proc until we know if # it's not a match: $s = ""; for $c ( split(//, $pattern ) ) { $s .= $c; if ( $string_accum eq $s ) { $waiting = 1; $saw_something = 1; last; } } } if ( $waiting && ! $saw_something ) { $waiting = 0; print $handle $string_accum; } $string_accum = "" unless $saw_something; print $handle $buf unless $waiting; } } # try to speed things up when the child dies &wait_nohang; if ( !kill( 0, $PIDS{$handle} ) ) { print STDERR "expect: pid $PIDS{$handle} gone, returning EOF\n" if $Debug; #system "ps -lp $PIDS{$handle}" if $Debug; $err = "EOF"; last LOOP; } else { #print STDERR "interact: handle=$handle, pid=$PIDS{$handle} still alive\n" if $Debug; } redo LOOP; } if ( wantarray ) { return ( $match, $err ); } else { return $match; } } # duplicates an file handle to conform to internal format sub open_dupsockethandle { local( $handle ) = @_; local( $new_handle ) = "socket" . ++$Next_handle; open($new_handle,"<&$handle"); return $new_handle; } sub open_dupprochandle { local( $handle ) = @_; local( $new_handle ) = "proc" . ++$Next_handle; open($new_handle,"<&$handle"); return $new_handle; } # "Bring out your deeeeeeead" sub wait_nohang{ #if ( $OS_type eq "SVR4" ) if ( $OS_name eq "Solaris" ) { # syscall 107 == waitsys for Solaris, which seems to be waitid? # int waitid(idtype_t idtype, id_t id, siginfo_t *infop, int options); #define WNOHANG 0100/* non blocking form of wait */ #define WEXITED 0001/* wait for processes that have exited */ # See: and # Arguments: 7=P_ALL=idtype_t, 64=\100=WNOHANG | 1=W &syscall_safe(107,7,0,0,64|1); } elsif ( $OS_name eq "SunOS" ) { # Maybe unnecessary, since the SunOS4.x version of Perl does an implicit # wait4, apparently. 7==SYS_wait4, 1==WNOHANG &syscall_safe(7,0,0,1,0); } elsif ( $OS_name eq "Linux" ) { $pg = getpgrp; waitpid(-$pg, $WNOHANG); } elsif ( $OS_name eq "HP-UX" ) { # 84 is syscall wait3 for HP-UX # 200 is syscall waitpid for HP-UX #&syscall_safe(200,0,1,0) ; # I don't know why the native Perl waitpid() doesn't work with pgrp waitpid(-1, $WNOHANG ); } else { # make "waitpid" the default? # maybe just better not to do anything, since guesses will probably # cause a blocking/hanging "wait". } } # Ideally, you probably want to keep the file handle name space # encapsulated in this package. On the other hand, it is # also really nice not to have to provide a "Comm'whatever()" function for # every Perl function which uses a file handle. sub import_FH{ local( @fh ) = @_; local( $pkg ) = caller; &export_FH( $pkg, @fh ); } sub export_FH{ &export_sym; } sub export_sym{ local( $pkg, @syms ) = @_; local( $eval ); return undef unless @syms; $pkg = "main" if ( $pkg eq "$My_pkg" ); for $sym ( @syms ) { $eval = qq{ *$pkg'$sym = *$My_pkg'$sym }; print STDERR "$eval\n" if $Debug; eval $eval; } } # "print", "sysread", etc. are no longer needed, but kept around for # backward compatibility. sub print{ local($fh)=shift; local($ret); $ret = print $fh @_; unless ( $ret ){ print STDERR "Error printing to fh($fh),$!\n"; } return $ret; } # Don't use syscall for AIX, it kills the process sub syscall_safe{ return 1 if $OS_name eq "AIX"; #print "syscall( $_[0], $_[1], $_[2], $_[3], $_[4] ) \n" if $Debug; syscall( $_[0], $_[1], $_[2], $_[3], $_[4] ) ; } # *val must be a glob, because some ioctl() functions return a structure # into the given variable. sub ioctl_syscall{ local($fh,$func,*val)=@_; local( $pty ); # First try using the native "ioctl()" call. Then if that doesn't work # (and it doesn't in some situations, i.e. IRIX5.3), use a "syscall()" # equivalent: print STDERR "ioctl_syscall($fh, $Ioctl_names{$func}, $val) \n" if $Debug; if ( !ioctl($fh, $func, $val ) ) { print STDERR "ioctl_syscall: ioctl failed,resorting to syscall\n" if $Debug; if ( &syscall_safe( $SYS_ioctl, fileno($fh), $func, $val ) != 0 ) { warn "ioctl failed, args=(@_), errno=$!"; } } print STDERR "ioctl_syscall returning \n" if $Debug; return 1; } #sub sysread{ # local(*FH)=shift; # sysread(FH, $_[0], $_[1]); #} # Use this when a parent forks a child to handle a request on a socket # file handle. The parent would like to close the file handle, but # leave the socket alive so the child can continue to read/write it # (the child inherited the file handle and therefore the socket) sub close_noshutdown{ for (@_){ next unless $_; close( $_ ); } } # For backward compatibility: sub close{ &close_it; } # "close_it" exists so it won't clash with "close" sub close_it{ local( $fh ); for $fh (@_) { next unless $fh; if ( $fh =~ /^socket/ ) { print STDERR "Doing shutdown on $fh\n" if $DEBUG; shutdown($fh,2) ; # must happen before close } #local( *fh ) = $fh; # some god-aweful magic, #close( $fh ); # left around in case it's ever needed again close( $fh ); if ( $fh =~ /^proc/ && $PIDS{$fh} ) # try not to kill the wrong thing { kill( 15, $PIDS{$fh} ); # thump it for ( 1 .. 5 ) { last unless kill( 0, $PIDS{$fh} ); print STDERR "Waiting for $PIDS{$fh} to die\n" if $Debug; select( undef, undef, undef, .1); # sleep } kill( 9, $PIDS{$fh} ); # drill it! } } } sub system_proc{ local( $handle, @args ) = @_; print STDERR "system_proc: handle($handle), args(@args)\n" if $Debug; unless ( $handle =~ /^proc/ || $handle eq "STDIN" ) { warn "Handle($handle) passed &${My_pkg}'system is not a proc/pty handle"; } if ( $handle eq "STDIN" ) { system @args; } else { unless ( fork() ) { &do_tty_child( $handle, $TTYS{$handle}, @args ); if(0) { local($tty); close(STDIN);close(STDOUT); # AIX can't seem to handle this idea: open(STDIN,"<&$handle" ); open(STDOUT,">&$handle" ); exec ( @args ); } exit; } print STDERR "system_proc: waiting\n" if $Debug; wait; print STDERR "system_proc: done waiting\n" if $Debug; } } # "stty_sane" and "stty_raw" use "stty" to set the terminal modes the # first time through, because "stty" is nice and portable. It then caches # the modes for subsequent calls to "ioctl()", which is nice and faster # for switching between modes, but is a pain to make portable. sub stty_sane{ local( $handle ) = @_; local( $tmp ) = (); if ( $OS_name eq "HP-UX" ) { $handle = $PTY_for_TTY{$handle} if $PTY_for_TTY{$handle}; } if ( $OS_name eq "AIX" ) # AIX stty hangs in weird places { $tmp = pack("C*", 13,13,8,21,0,216 ); return &ioctl_syscall( $handle, $TIOCSETP, *tmp ); } &stty_ioctl( $handle, "stty sane" ); print STDERR "Done, stty_sane\n" if $Debug; } sub stty_raw{ local( $handle ) = @_; local( $tmp); if ( $OS_name eq "HP-UX" ) { $handle = $PTY_for_TTY{$handle} if $PTY_for_TTY{$handle}; } if ( $OS_name eq "AIX" ) { $tmp = pack("C*", 13,13,8,21,0,224 ); return &ioctl_syscall( $handle, $TIOCSETP, *tmp ); } if ( $OS_type eq "SVR4" ) { &stty_ioctl( $handle, "stty raw -echo" ); } else { &stty_ioctl( $handle, "stty raw -echo -icanon eol '^a'" ); } print STDERR "Done, stty_raw\n" if $Debug; } sub stty_ioctl{ local( $handle, $stty_cmd ) = @_; local( $tmp, $ret ); if ( $OS_name eq "HP-UX" ) { $handle = $PTY_for_TTY{$handle} if $PTY_for_TTY{$handle}; } if ( ! $Stty_struct{$stty_cmd} ){ $Stty_struct{$stty_cmd} = &get_ioctl_from_stty( $handle, $stty_cmd ) } local($tmp) = $Stty_struct{$stty_cmd}; if ( $OS_type eq "SVR4" || $OS_name eq "HP-UX" ) { $ret = &ioctl_syscall( $handle, $TCSETA, *tmp ); } else { $ret = &ioctl_syscall( $handle, $TIOCSETP, *tmp ); } warn "stty_ioctl, ioctl failed for handle($handle), command($stty_cmd), errno=$!\n" unless $ret; print STDERR "Done, stty_ioctl\n" if $Debug; } sub get_ioctl_from_stty{ local( $handle, $stty_cmd ) = @_; local( $ioctl_struct, $get_cmd, $set_cmd, $out, $ret ) = (); local( $pty_handle, $pid ); # This seems to be recommended, but I don't see it doing much: if ( $OS_name eq "HP-UX" && $handle ne "STDIN" ) { $pty_handle = $handle; $pty_handle = $PTY_for_TTY{$handle} if $PTY_for_TTY{$handle}; ioctl($pty_handle, $TIOCTRAP, 0 ) || die "$!"; #ioctl( $pty_handle, $TIOCTTY, 0 ); # causes hang of open_proc child # shell regardless of value??? } local( $tty ) = $TTYS{$handle}; $tty = "/dev/tty" unless $tty; print STDERR "get_ioctl_from_stty($handle): $stty_cmd <$tty >$tty\n" if $Debug; if ( $OS_name eq "HP-UX" && $handle ne "STDIN" ) { # if I set TIOCTRAP to 0, I will subsequently receive traps once # interact() starts, and for every "stty sane" command run in it, # which is backwards, as far as I understand the doc.s (which is poorly). # Since it seems to work either way, I'm leaving it alone. #&ioctl_syscall($handle, $TIOCTRAP, 0 ); print STDERR "get_ioctl_from_stty: forking \n" if $Debug; local($pid); if ( $pid = fork ) { { # For some reason, if we don't clear the data from the PTY, # anything else will hang until we do. Also, if we then try # reading again. # I'm hoping that "expect()" will clear the pending data, and # do any PTY trapping necessary. print STDERR "get_ioctl_from_stty: waiting for pid=$pid \n" if $Debug; local( @r ) = &expect( $handle, 1, '' ); print STDERR "get_ioctl_from_stty: cleared (",join(",",@r), ") from $handle \n" if $Debug; if ( kill( 0, $pid) ) { # be sure that the child has set the new TTY modes, and exited # before we proceed to read those modes. select( undef, undef, undef, .1); # sleep redo; } } print STDERR "get_ioctl_from_stty: done waiting\n" if $Debug; } else { print STDERR "get_ioctl_from_stty: before stty\n" if $Debug; system "$stty_cmd <$tty >$tty"; print STDERR "get_ioctl_from_stty: stty done\n" if $Debug; exit 0; } } else { system "$stty_cmd <$tty >$tty"; } # These only return 4 bytes. Why? # $p = pack("p", $ioctl_struct ); #$ret = syscall($SYS_ioctl, fileno($handle), $TIOCGETP, $p); #$ret = syscall($SYS_ioctl, fileno($handle), $TCGETA, $p); if ( $OS_type eq "SVR4" || $OS_name eq "HP-UX" ) { $get_cmd = $TCGETA; } else { # If you use TIOCGETP/SETP on HP, it will cause a hang on a PTY/TTY !!! # It is defined, but not longer fully supported, I guess. $get_cmd = $TIOCGETP; } $!=0; $ioctl_struct = "\0"x256; # for perl4 $ret = &ioctl_syscall($handle, $get_cmd, *ioctl_struct ); warn "get_ioctl_from_stty: ioctl failed, errno=$!" unless $ret; return $ioctl_struct; #return ( $ioctl_struct, $ret ); # blows up $ioctl_struct on the stack } # # $ioctl_struct = &get_ioctl_from_stty( $handle, $stty_cmd ); # ... # ioctl( $handle, $TCSETA, $ioctl_struct ); # note: $TCSETA isn't exported # # # "dump_ioctl" is not normally used. "sane" and "raw" modes usually # # suffice. You can use this to print out the tty modes ioctl_struct # # in a list format that you can squirrel away in your script: # # $ioctl_struct = &dump_ioctl( $stty_cmd ); # # E.g.: # # &dump_ioctl( "stty sane" ); # # would print out something like # # stty sane = 37,38,0,5,5,173,138,59,0,3,28,127,21,4,0,0,0,0,..... # # which you could then recreate in your script as: # # $ioctl_struct = pack("C*", 37,38,0,5,5,173,138,59,0,3,28,127,21,4 ); # ioctl( $handle, $TCSETA, $ioctl_struct ); # sub dump_ioctl{ local( $handle, $stty_cmd ) = @_; local( $ioctl_struct, $c, $out ) = (); $ioctl_struct = &get_ioctl_from_stty( $handle, $stty_cmd ); for $c ( unpack("C*", $ioctl_struct) ){ #$out .= sprintf("0x%2.2x,", $c ); $out .= sprintf("%d,", $c ); } print "$stty_cmd = $out \n"; # I don't know off hand how much of the returned buffer is actually # significant; certainly less than the full 256 bytes. return $ioctl_struct; } 1; __END__ #--------------------------------Example server--------------------------------- # # Allows multiple client connections, and rebroadcasts data between them. eval '(exit $?0)' && eval 'exec perl -S $0 ${1+"$@"}' & eval 'exec perl -S $0 $argv:q' if 0; require "Comm.pl" unless defined &Comm'init; $Listen_port = 5050; $Listen_port = $ARGV[0] if $ARGV[0]; $SIG{'HUP'} = "my_exit"; $SIG{'INT'} = "my_exit"; $SIG{'QUIT'} = "my_exit"; $DEBUG = 1; $|=1; &Comm'init; if(1) { $Listen_handle = &open_listen( $Listen_port ); die "open_listen failed on port $Listen_port" unless $Listen_handle; } else { # This is optional; it can be useful to use a range of ports # if your sockets don't always release a port right away when you kill # a process. However, the "setsockopt()" calls should release the ports # for you, so this should no longer be necessary. $start_port = $Listen_port; { if ( ! ( $Listen_handle = &open_listen( $Listen_port ) ) ) { redo unless ( ++$Listen_port <= $start_port + 10 ); die "open_listen failed on port $Listen_port"; } } } print "Listening on port $Listen_port\n" if $DEBUG; while (1) { @ready_handles = &select_it(1, keys(%Client_handles), $Listen_handle ); print "Handles ready: @ready_handles\n" if $DEBUG && @ready_handles; foreach $handle (@ready_handles) { if ($handle eq $Listen_handle) { ($new_handle, $rem_host) = &accept_it($handle); $Client_handles{$new_handle} = $rem_host; print "New connection from $rem_host\n" if $DEBUG; } else { if ( sysread($handle, $buf, 10000) ) { $buf = $Client_handles{$handle} . ": $buf"; $buf =~ s/[\n]*$/\n/; print $buf; # rebroadcast data to all clients: for $client_handle ( keys %Client_handles ) { &Comm'print( $client_handle, $buf ); } } else { print "Closing handle $handle, host $rem_host\n"; &Comm'close( $handle ); delete $Client_handles{ $handle }; } } } } sub my_exit { &Comm'close( $Listen_handle ); print "Closing listen port\n" if $DEBUG; exit; } #--------------------------------Example client--------------------------------- # Connect to a server, and send STDIN data to it. # Usage: tstclient eval '(exit $?0)' && eval 'exec perl -S $0 ${1+"$@"}' & eval 'exec perl -S $0 $argv:q' if 0; require "Comm.pl" unless defined &Comm'init; $Server_port = 5050; $Server_host = "serverhost.domain"; ( $Server_host, $Server_port ) = @ARGV if @ARGV; $SIG{'HUP'} = "my_exit"; $SIG{'INT'} = "my_exit"; $SIG{'QUIT'} = "my_exit"; $|=1; $DEBUG = 1; &Comm'init; if ( ! ( $Server_handle = &open_port($Server_host, $Server_port, 5) ) ) { die "open_port failed on host $Server_host, port $Server_port"; } print "Connected to host $Server_host, port $Server_port\n" if $DEBUG; while (1) { @ready_handles = &select_it(1, $Server_handle, STDIN); foreach $handle (@ready_handles) { if ($handle eq "STDIN") { $buf = ; print $Server_handle $buf || die; } else # server { unless ( sysread($handle, $buf, 1000) ) { print "Server connection broken\n"; &my_exit; } print $buf; } } } sub my_exit { &Comm'close($HANDLE); exit; } #--------------------------------Example udp send ------------------------------ eval '(exit $?0)' && eval 'exec perl -S $0 ${1+"$@"}' & eval 'exec perl -S $0 $argv:q' if 0; require "Comm.pl" unless defined &Comm'init; #$remote_addr = "129.145.43.255"; # broadcast chop( $remote_addr = `uname -n` ); $remote_port = 5050; ( $remote_port ) = @ARGV if @ARGV == 1; ( $remote_addr, $remote_port ) = @ARGV if @ARGV == 2; #$Debug = 1; ( $sock = &open_udp_port( "", 0, $remote_addr, $remote_port ) ) || die "open_udp_port: $!"; print "\nsending\n"; print $sock "testing with print\n" || die "send $!"; # send and send_to won't work with connected sockets under SunOS4.x $remote_sockaddr = &sockaddr_struct( $remote_addr, $remote_port ); send( $sock, "testing with send\n", 0, $remote_sockaddr ) || die "send $!"; &send_to( $sock, "testing with send_to\n", 0, $remote_addr, $remote_port ) || die "send $!"; #--------------------------------Example udp recv------------------------------- eval '(exit $?0)' && eval 'exec perl -S $0 ${1+"$@"}' & eval 'exec perl -S $0 $argv:q' if 0; require "Comm.pl" unless defined &Comm'init; &Comm'init; chop( $My_addr = `uname -n` ); $My_port = 5050; ( $My_port ) = @ARGV if @ARGV == 1; ( $My_addr, $My_port ) = @ARGV if @ARGV == 2; $SIG{'HUP'} = "my_exit"; $SIG{'INT'} = "my_exit"; $SIG{'QUIT'} = "my_exit"; #$Debug = $DEBUG = 1; $|=1; $Udp_handle = &open_udp_port( $My_addr, $My_port ); die "open_udp_port failed on port $My_port" unless $Udp_handle; while (1) { @ready_handles = &select_it(1, $Udp_handle ); print "Handles ready: @ready_handles\n" if $DEBUG && @ready_handles; foreach $handle (@ready_handles) { if ($handle eq $Udp_handle) { if ( ( $addr, $port ) = &recv_from($handle, *buf, 10000, 0) ) { print "From port=$port, addr=$addr\n"; print $buf; } else { print "Closing handle $handle, host $rem_host\n"; &Comm'close( $handle ); } } } } sub my_exit { &Comm'close( $Udp_handle ); print "Closing udp port\n" if $DEBUG; exit; } #-------------------- Example telnet expect, short version --------------------- # # This will give an idea of the usage, without becoming overwhelming. See # the next example for better error checking and more interesting operations. eval '(exit $?0)' && eval 'exec perl -S $0 ${1+"$@"}' & eval 'exec perl -S $0 $argv:q' if 0; require "Comm.pl"; &Comm'init( 1.5 ); $Host = "somehost"; $User = "someuser"; $Password = "somepassword"; $PS1 = '(\$|\%|#|Z\|) $'; # shell prompt, Z| is my weird prompt $|=1; $proc_handle = &open_proc( "telnet $Host" ) || die "open_proc failed"; ( $match, $err, $before ) = &expect( $proc_handle, 3, 'login:' ); die "failed looking for login: err($err), before($before)" unless $match; print $proc_handle "$User\n"; &expect( $proc_handle, 3, 'word:' ) || die "Didn't get a password prompt"; print $proc_handle "$Password\n"; &expect( $proc_handle, 10, $PS1 ) || die "no shell prompt"; print $proc_handle "who\n"; # do something, anything { # Now, show the results of the above command: ( $match, $err, $before, $after ) = &expect( $proc_handle, 5, $PS1 ); redo unless $match; print $before; die "err=$err, quitting\n" if ( $err eq "EOF" ); } print $proc_handle "\n"; # give us another shell prompt, please &stty_raw(STDIN); &interact( $proc_handle ); print "Exited interact()\n"; &stty_sane(STDIN); &close_it( $proc_handle ); #--------------------------- Example telnet expect ----------------------------- eval '(exit $?0)' && eval 'exec perl -S $0 ${1+"$@"}' & eval 'exec perl -S $0 $argv:q' if 0; require "Comm.pl"; &Comm'init( 1.5 ); $Program = "telnet"; #$Program = "/usr/ucb/rlogin -l qwerty"; # try this to test login recovery $Host = "somehost"; $User = "someuser"; $Password = "somepassword"; $Shell_prompt = '(\$|\%|#|Z\|) $'; # Z| is my weird prompt $|=1; ( $Proc_pty_handle, $Proc_tty_handle, $pid ) = &open_proc( "$Program $Host"); die "open_proc failed" unless $Proc_pty_handle; { ( $match, $err, $before, $after ) = &expect( $Proc_pty_handle, 3, 'login:', 'word:' ); &print_clean( "err=($err), match=($match), before=($before), after=($after)"); if ( defined $match ) # Remember, "if($match)" fails if "$match = '0'" . { if ( $match eq 'login:' ) { print "got a login: $match\n"; print $Proc_pty_handle "$User\n"; } else { print "Oops, got a password prompt or something instead of a login\n"; print $Proc_pty_handle "\n"; # try to get a login prompt sleep 5; redo; } } else { print "exiting on err($err)\n"; exit; } } ( $match, $err, $before ) = &expect( $Proc_pty_handle, 3, 'word:' ); die "failed looking for password:$err, before=$before" unless $match; print $Proc_pty_handle "$Password\n"; print "waiting for a shell prompt\n"; &expect( $Proc_pty_handle, 10, '[\0-\377]+' . $Shell_prompt ) || die "no shell prompt"; print "got it\n"; print $Proc_pty_handle "ps\n"; { # A little tricky regex note: if you want to a line at a time, and # not miss any newlines, use: # ( $m, $err ) = &expect($Proc_pty_handle, 5, '.*\n' ); # Note: a pattern of '.+' will fail finally, because the last shell prompt # won't be terminated with a newline. Use '[\s\S]+' instead: ( $match, $err, $before, $after ) = &expect( $Proc_pty_handle, 5, '[\s\S]+' ); # Another way to do this would be to expect on the $Shell_prompt, and # keep printing out $before until $match hits. &print_clean( "getting ps info, ($err)($before)($after)($match)" ); die "err=$err, quitting\n" if ( $err eq "EOF" ); redo unless $match =~ /$Shell_prompt/; } print "You are now connected to the telnet process\n", "Enter ESC-1 for 'pwd' or ^C to break out\n", "type 'date' to trigger the date scanner\n"; print $Proc_pty_handle "\n"; # give us another shell prompt, please &stty_raw(STDIN); LOOP: { ( $match, $err ) = &interact( "\003", "\0331", # don't use '\003' or string match will see "\ 0 0 3" $Proc_pty_handle, ".*199\d", ); if ( $err ) { print "Aborting, err($err)\n"; last; } if ( $match eq "\003" ) { print "Got control-C\n"; last; } if ( $match eq "\0331" ) { #print "Got F1, sending 'pwd'\n"; print $Proc_pty_handle "pwd\n"; } if ( $match =~ /199\d/ ) { # Suck the time info from the output from "date" $match =~ /\d+:\d+:\d+/; select( undef, undef, undef, .3 ); # let active shells like zsh catch up print $Proc_pty_handle "banner $&\n"; } redo LOOP; } &stty_sane(STDIN); print "sending ^] to telnet...\n"; print $Proc_pty_handle "\035"; ( $match, $err, $before, $after ) = &expect( $Proc_pty_handle, 5, 'telnet>' ) ; die "didn't get a telnet> prompt, err($err) before($before)" unless $match; print $Proc_pty_handle "quit\n"; { ( $match, $err ) = &expect( $Proc_pty_handle, 5, '.+' ); &print_clean( "waiting for child death, err($err), match($match)\n" ); die "got EOF, quitting\n" if ( $err eq "EOF" ); redo; } exit 0; sub print_clean{ local( $s ) = @_; $s =~ s/\n/\\n/g; # replace real \n with fake \n to clean up the output $s =~ s/\r/\\r/g; $s =~ s/[\0-\037]/sprintf('\%3.3o', ord($&) )/ge; print "$s\n"; } #--------------------------- Example /bin/sh expect ----------------------------- # This is a useful test in addition to the telnet test because having the # "telnet" process between you and the remote shell can mask terminal modes # problems. eval '(exit $?0)' && eval 'exec perl -S $0 ${1+"$@"}' & eval 'exec perl -S $0 $argv:q' if 0; require "Comm.pl"; &Comm'init( 1.5 ); #$Debug=1; $|=1; ( $Proc_pty_handle, $Proc_tty_handle, $pid ) = &open_proc( "/bin/sh" ); die "open_proc failed" unless $Proc_pty_handle; &stty_sane($Proc_tty_handle); # use $Proc_pty_handle for HP &stty_raw(STDIN); print "You are now connected to the shell process, ^C to break out\n"; LOOP: { ( $match, $err ) = &interact( "\003", $Proc_pty_handle ); if ( $err ) { print "Aborting, err($err)\n"; last; } if ( $match eq "\003" ) { print "Got control-C\n"; last; } redo LOOP; } &stty_sane(STDIN); print "Disconnected\n";