#!/usr/bin/perl -w # # build-opt-env.pl # November 6, 1998 clh # used by Perl scripts in JGOFS OtherOptions server # to establish an operating environment # NB: this file belongs in the directory configured as ScriptAlias /jg/ # on this HTTPD server. (typically htmlbin or optbin) # # OPTHOME == top of tree # OPTRUNDIR == to be appended to OPTHOME to find this directory # MYADDR == how to refer to host running these scripts # DEFAULT_PORT == which port on this host to use # JGTEMP == top level temporary space # USETEMPDIR == temporary space to use as tree for current object # USETEMPADDR == URL for USETEMPDIR # LOCAL_SERVER_ROOT == file spec of object's JGOFSroot. Non-emptiness depends # on defn of config param LOCAL_SERVER_ROOTS # *** # and then, a set of commonly used strings referring to the object # *** # given a JGOFS object: http://usjgofs.whoi.edu/test(press>100) # # OBJECT == a jgofs object string (no jg/serv/, no extension) # //usjgofs.whoi.edu/test (or, for locally-accessed objects, /test) # OBJEXT == same as above, with extension (not used often) # //usjgofs.whoi.edu/test.html0 # URLOBJX == object spec as usable from a browser (jg/serv and ext.) # //usjgofs.whoi.edu/jg/serv/test.html0 # URLOBJ == object spec as above without the ext. (calling scripts) # //usjgofs.whoi.edu/jg/serv/test # PROTOLEV == just the extension # html0 # SUBSELS == subselections list used to pass to scripts/programs # press>100 ( or for string compare: press%20gt%20100) # DISPSS == un-trigram'ed subselections for displaying in browser # press>100 ( or for string compare: "press gt 100") # # # Also, the extended URL is "put into the environment", as a whole and # as its pieces. The env var PATH_INFO_PREFIX (currently "PATH_INFO_") # is put in front of all env vars in this section. Given an extended # URL of the form {dir=dirserver,info=infoserver/obj,new_field=whatever}, # we provide env vars # PREFIXdir == dirserver # PREFIXinfo == infoserver/obj # PREFIXnew_field == whatever # if PATH_INFO is non-empty (a PATH_INFO w/o extended URL will result # in defined, empty env vars). For convenience, we also define # PREFIXoption_string as the extended URL w/o the curly brackets. (Note: # nothing in the path_info_routines architecture requires that "new" # fields be part of the extended URL. But that's how it is now (Aug 04). # Code for PATH_INFO_PREFIX section just looks at all non-object/protocol/ # level stuff that comes out of parse_path_info) # 1 Aug 16. WJS # LOCAL_SERVER_ROOT env var, via a reworking of LOCAL_SERVER_ROOTS logic # 9 Apr 15. WJS # When doing a search of a list, it's not a good idea to unconditionally return # after processing the first element. List in question this time was # LOCAL_SERVER_ROOTS # 7 Aug 15. WJS # Use REMOTE_ADDR for a temp directory name instead of REMOTE_HOST # Latter is only available conditionally and ip addr more useful for ID # purposes (dns name goes away more frequently than ip addr changes) # Motivation for change was neither. OOserver run on dmoserv2 "got" REMOTE_HOST # for most boxes while production server on gb11 didn't, implying some difference # in Apache setup that we didn't want to have to track down # 26 Mar 15. WJS # Set INCLUDE_ATTRIBUTES_SWITCH env var (for list) # 22 Mar 15. WJS # Bug fix to 17 Jan work, which did not handle local object spec format as input # 17 Jan 15. WJS # Make OBJECT point to a local object if LOCAL_SERVER_ROOTS says so # 25 Jul 12. WJS # Lost quotation marks around obj spec in call to fixurl. Been # that way since 2008 - wonder if it was even in the conversion # of build-opt-env (has it right) to perl. # 6 Jan 11. WJS # Define variables if in error environment # Jul 21, 2009. WJS # Trigram shell chars in SUBSELS # May 22, 2009. WJS # Remove htmlunesc going from QS -> $subsels # December 27, 2008. WJS # Replace backtick operators w/call to backtick function. While at # it, submit to perl's doc for its error treatment vs my ideas # about what perl "had to do" # May 22, 2007. WJS # Replace copy_file code w/escape to OS "cp" command. Not as # good (consider system independence) but file copying stuff # still not right (see comments near sysopen call) # March 28, 2007. WJS # Bug fix: the good news re the 29 Nov 06 mod - it got the permissions # correct. The bad news: it failed to copy the file contents. # November 29, 2006. WJS # Bug fix: copying setup directory did not also copy file permissions # March 16, 2005. WJS # Bug fix: URLOBJ & URLOBJX defns were reversed # 10 Dec 04 mod dropped protocol/level if level=0. Some day I # may learn that 0 is considered "False" by perl, along with # "undefined" being considered "False" # December 10, 2004 wjs # Bug fix: don't add . to object spec if no protocol/level # Define a few more env vars to "" rather than leaving them undefined # December 2, 2004 wjs # USETEMPADDR # August 5, 2004 wjs # Replace template/make technique w/run-time invocation of # opt-build-env.pl # Hardcode perl location to /usr/bin/perl; add -w # Add a "normal exit" value of 1 # Reorder comments to most-recent-first # Some work w/ ENV{"PATH_INFO"}: # 1) check if it exists. This routine can be used in an # environment where we don't use/care about PATH_INFO # parse_path_info not graceful in this situation, and # resulted in this routine issuing a warning # 2) Check parse_path_info return and ALERT to STDERR if necessary # 3) Put pieces of extended URL into environment # December 7, 1999 clh # add another variable DISPOBJ, the display-style object, # complete with subselections if present # October 25, 1999 clh # attempt to provide a bit more error handling # list of environment variables established: # April 15, 1999 clh # add env var USETEMPDIR # add make_common_object_env_vars # December 15, 1998 clh # conditional use of REMOTE_HOST # #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # # Get rid of args, since opt-build-env.pl will read them if present # $build_env_file_name is used in opt-build-env.pl @ARGV = (); # To avoid duplicate defns because of duplicate inclusion of wjs*.pl, file names # in all references must match; eg, can't say ./wjs*.pl here and wjs*.pl there. # Feh. Next step is to just do it myself via a WJS_INCLUDED var defined in wjs*.pl $utils_file_name = "wjs_web_perl_utilities.pl"; require $utils_file_name; $build_env_file_name = "../opt-build-env.pl"; require $build_env_file_name; $trigram_file_name = "./trigram.pl"; require $trigram_file_name; # Control list behavior. OOserver needs "new" behavior. With # use of list from data server (for local access), we might get a # list compiled "the old way" (highly likely since Bob didn't want # to change) $ENV{"INCLUDE_ATTRIBUTES_SWITCH"} = "TRUE"; $cp_program = $ENV{"CPPROG"}; $cp_program || die "Did not get file spec for cp program from $build_env_file_name"; (-e $cp_program) || die "$cp_program (set up in $build_env_file_name) does not exist"; (-x $cp_program) || die "$cp_program (set up in $build_env_file_name) not executable"; # $topdir = $ENV{"JGOFSDIR"}; $ENV{'OPTHOME'} = $topdir; # "optbin" originally hard-coded in make file that made this from a # template version of this. Don't know why optbin not defined in # something like opt-build-env $rundir = "optbin"; $ENV{'OPTRUNDIR'} = $rundir; # $ENV{'DEFAULT_PORT'} = $ENV{"PORT"}; $tempdir = $ENV{"TEMPDIR"}; # # we need this directory to exist so that a subdir can be created # if (!-e $tempdir) { $success = mkdir($tempdir,493); if (not $success) { print STDERR "*** ALERT: $tempdir could not be created ***\n"; die "*** reason from mkdir: $!\n"; } } # # Env var REMOTE_HOST might be set, but HTTP guarantees REMOTE_ADDR # Used to use _HOST if available; decided (Aug 15) to unconditionally use _ADDR) $ENV{'JGTEMP'} = $tempdir; $remotehost = $ENV{'REMOTE_ADDR'}; # # check that the temporary directory exists and that it has all setup # info/data in it that any of the menu items needs # mod November 2, 1998 - clh - move here so all items need not dup code # $realtempdir = "$tempdir"."/"."$remotehost"; if (!-e $realtempdir) { # # MODE arg for mkdir is in decimal, max (o777) is 511 # we want to turn off group and other write bits (umask 022) # and, 022 is 18 decimal, so: 511 - 18 == 493 # $success = mkdir($realtempdir,493); if (not $success) { print STDERR "*** ALERT: $realtempdir could not be created ***\n"; die "*** reason from mkdir: $!\n"; } } $success = opendir(DIR,"$topdir/setup"); if ($success) { @setup_files = readdir(DIR); closedir(DIR); # # if any of the setup files do not exist in tempdir, copy 'em # Seems to me that we would want to do more than just test existence. If # we changed a setup file, presumably we'd want the new one copied in. # However, "on the ground", the reason the copies of the setup file get # changed is for debugging purposes, etc, and resetting them would be quite # an annoyance. Hence, letting the traditional behavior continue WJS Mar 07 # foreach $i (@setup_files) { $setupfile = "$realtempdir"."/"."$i"; if (! -e $setupfile) { ©_file("$topdir/setup/$i", $setupfile); } } } else { print STDERR "*** ALERT: cannot access $topdir/setup directory ***\n"; print STDERR "*** reason: $!\n"; } $ENV{'USETEMPDIR'} = $realtempdir; $ENV{'USETEMPADDR'} = $ENV{'TEMPADDR'} . "/$remotehost"; &make_common_object_env_vars; #&print_to_stderr_env; #&dump_environment_variables; 1; # #*********** subroutines # sub copy_file() { my ($from,$to) = @_; my ($result,$exit_status,@status,$command); # If you wish to indulge in self-mortification, try to get a space # between the > and the &. Purpose of that is to get cp's error info, # which goes to stderr, to go to stdout so we can get it here. $command = "$cp_program $from $to 2>&1"; ($result,$exit_status,@status) = &backtick($command); ($exit_status == 0) || die &format_backtick_return_status($command,$exit_status,@status,$result); return 1; } sub make_common_object_env_vars { # Next line a copy of one in *perl_utilities.pl. Some day figure out # how to do .h files in perl. Worse, not all of these chars relevant # as far as this app goes (in fact, \ and " might do it, # although the "original" problem had to do with ; - see below) # Well, not QUITE a copy. Orig string had parens in it. I suspect # we COULD get away w/trigramming parens - between our code and "browser # interpretation", most of what I saw "onscreen" was parens even when # the trigramming was done. Nonetheless, the trigrams were in the log, # making it confusing there ... my ($shell_chars) = ' "`;{}|\\\''; # Add more when needed... # Next character must match trigram character in "the rest of the # system"; most probably core.h. Of course, the user could compile # a different character into the c programs, leaving this program to twist # in the wind... my ($trigram_char) = '%'; if ($ENV{"PATH_INFO"}) { $command = "$topdir/bin/parse_path_info -nonewline"; ($path_info_string,$exit_status,@status) = &backtick($command); if (($path_info_string eq "") || ($exit_status != 0)) { $extra_info = ($path_info_string) ? $path_info_string : "Empty return from parse_path_info"; print STDERR " *** ALERT: parse_path_info problem\n"; print STDERR " *** PATH_INFO: " . $ENV{"PATH_INFO"} . "\n"; print STDERR &format_backtick_return_status ($command,$exit_status,@status,$extra_info); # Not sure we shouldn't abort, but historically we didn't. This # mod to avoid "undefined variable" perl errors which, being undated, # are hard to ID $object = $protocol = "XXX-ERROR-XXX"; $level = 9999999; } else { # -1 takes care of "odd number of elements in hash assignment" # (which occurs when there is no value for the last field parse_path_info # puts out %path_info=split(/[,=]/,$path_info_string,-1); $object= (defined $path_info{"object"}) ? $path_info{"object"} : ""; $protocol= (defined $path_info{"protocol"}) ? $path_info{"protocol"} : ""; $level= (defined $path_info{"level"}) ? $path_info{"level"} : ""; } } else { $object = $protocol = $level = ""; } # # Put all other PATH_INFO fields into environment. At the moment (Aug 04), # these are the "extended URL" fields (stuff in curly brackets). # Also, for convenience (otheropt, a prime consumer of this info, is a # c shell script, and I want to do as little programming in csh as I can), # put out a comma separated list of key=value pairs $path_info_prefix = "PATH_INFO_"; $ENV{"PATH_INFO_PREFIX"} = $path_info_prefix; if ($ENV{"PATH_INFO"}) { $tmp = ""; while ( ($key,$value) = each %path_info ) { ($key =~ /^(object|level|protocol)$/) && next; $ENV{$path_info_prefix.$key} = $value; $tmp .= "$key=$value,"; } chop $tmp; $ENV{$path_info_prefix . "OPTION_STRING"} = $tmp; } # # deal with the object, protocol, and level # $ENV{'PROTOLEV'} = $protolev = ($protocol && defined($level)) ? "$protocol$level" : ""; # LOCAL_SERVER_ROOTS is an env var from build-env.pl. Its contents consist of host # names and their local JGOFS server roots. $object is a remote object spec. If its host name is # mapped into a local server root, put the local object spec into the environment instead of the remote one. # There is much potential confusion between the "host name field in an object spec", dns names of data servers, # and httpd configuration virtual server names (and, I suppose, NON-virtual server names). No words of # wisdom here, just the caveat # The idea is to execute the local data server's methods on the local object spec instead of the # OOserver's methods on the remote object spec. This idea evades any http protection on the data server. # I suspect that the next paragraph is made obsolete by the dual_server approach. Too lazy # at the moment (Jul 16) to get into revising the paragraph, though. # As of Jan 15, there ARE no OOserver methods - they have been soft-linked to a particular local data server. # Accordingly, only 1 virtual data server can use a particular OOserver. The better way to do this is to have # each piece of OOserver decide at run time which method to run. Note that with the Jan 15 implementation, # any links displayed by the "OOserver methods" will in fact point to the data server. This may be an issue # in those OOserver pieces that really SHOULD use local methods. The persistent object stuff is the most # obvious candidate for trouble; maybe the time splitter and time converters, too ... hmm, maybe this is NOT # a problem since the data server is in fact compiled with this specific OOserver in it. Guess we'll see! ($local_object_spec,$local_root) = &return_local_object_info($object,"LOCAL_SERVER_ROOTS"); # We assume that the presence of local information, which from a config standpoint means the presence of LOCAL_SERVER_ROOTS, # means we should use the local info. No particular reason for this - might be better to have separate config params, but # not doing it now $ENV{'OBJECT'} = ($local_object_spec) ? $local_object_spec : $object; $ENV{'LOCAL_SERVER_ROOT'} = ($local_object_spec) ? $local_root : ""; # The following object-related URLs seem to be OK to be left as remote object specs. They are most used # to pass to other pieces of the OOserver. When those pieces actually get around to doing something, # presumably they will use the OBJECT env var. Operative word in sentence 1 could well be "seem", however $ENV{'OBJEXT'} = $ENV{'DISPOBJ'} = $object; $protolev && ($ENV{'OBJEXT'} .= ".$protolev"); $ENV{'URLOBJX'} = $ENV{'URLOBJ'} = ""; if ($object ne "") { $command = "$topdir/bin/fixurl " . '"' . $object . '"'; ($urlobj,$exit_status,@status) = &backtick($command); ($exit_status == 0) || die &format_backtick_return_status($command,$exit_status,@status,$urlobj); chomp($urlobj); $ENV{'URLOBJX'} = $ENV{'URLOBJ'} = $urlobj; $protolev && ($ENV{'URLOBJX'} .= ".$protolev"); } # # deal with the sub-selections, if any # $ENV{'SUBSELS'} = $ENV{'DISPSS'}= ""; if ($ENV{'QUERY_STRING'}) { if ($ENV{'QUERY_STRING'} ne "") { $subsels = "$ENV{'QUERY_STRING'}"; $ENV{'SUBSELS_QS'} = $subsels_qs = "$subsels"; # Test below should be unneeded - QS would not contain htmlesc'ed things ### Check QS for htmlescaped operator - if found, unescape for subsels ### ex: press<200 ---> press<200 ### ### if ($subsels_qs =~ /[&;]/) { ### $command = "$topdir/bin/htmlunesc \"$subsels_qs\""; ### ($subsels,$exit_status,@status) = &backtick($command); ### ($exit_status == 0) || ### die &format_backtick_return_status ### ($command,$exit_status,@status,$subsels); ### } else { ### $subsels = "$subsels_qs"; ### } # Another small novella ... # Trigramming of subsels has been "in here" (and in build-opt-env) # since at least 1999. It was done w/trigram_util, and only blanks # were trigrammed. It's not clear why this was done, since trigram- # ming was added when outer added string operators, and outer # has trigrammed since the beginning. I think that perhaps this # was for selopt, which apparently did NOT trigram when it added # string ops (selopt.pl apparently DOES). # Anyway, the matter was revisited in 2009, motivated by shell- # sensitive characters (such as semi-colons) in the data portion # of selections. Note that this is an ENTIRELY different matter # than trigramming the blanks around string ops. THAT is protection # because of http issues, and the trigramming technique was chosen # because other webbish stuff did trigramming for, apparently, the # same reason. THIS is because the general technique of slapping # quotation marks around strings to protect their contents from # shell translation does not necessarily work. Notably, an # embedded quotation mark or a trailing backslash will defeat # quotation marks. Having said that, the trouble has arisen with # semicolons, for which I think quoting should work. # Note that this code is in "the wrong place" and is the wrong # kind. Shell character protection should occur immediately before # we go to the shell, and the shell should immediately remove # the protection, leaving the string as close as possible to # "what it's supposed to be" for as long as possible. The timing # issue would have the protection code "at" the shell invokation, at # the same place that the quotation marks (presumably! that would # explain the semi-colon stuff!) are added. Allowing the shell to # undo the protection means that we would use backslashes, etc to # do the protection, not trigramming. Nonetheless, we are going to # take a shot here, since what's going on "out there" occurs in # lots of places. # So - the theory here is that, although we are trigramming the # whole selection string, the intent is to trigram the stuff AFTER # the selection operator, and leave it that way until outer gets it. # outer will untrigram. The operant principle is that ONLY outer # will fiddle with the stuff after the operator. # Details: # 1) using trigram_util is no good, since IT is invoked # by the shell. We'd have to protect the unprotected string # before we sent it to trigram_util, defeating the purpose. # 2) we must NOT trigram the trigram character because # the selection string may contain and already trigrammed character # delimiting the operator. There is only 1 untrigramming done, so # we cannot trigram more than once. THIS MEANS THAT IF THE TRIGRAM # CHARACTER IS IN THE DATA, THERE IS THE POTENTIAL FOR TROUBLE, and # we will not find out until "later", if at all. With the new function # we COULD find out earlier if we want - the function call below # will return a WARN status in such a case. We are ignoring # since the trouble has always been there (although if the datum # happened to contain a legit trigram, say, "humidity90%20Mpswindpeed", # the data will just end up altered) # 3) Not really clear which characters to trigram. We've # stolen the string used by another shell character protection scheme, # but who knows if that was any good? Certainly it's curious that # <, > and & are not in the string # 4) We are depending on the trigram character being "safe". Note # that the default character, %, is not necessarily perl-safe, for # example (I think it is the modulo operator). # 5) Since most if not all of the OO code slaps quotation marks # around the selection string anyway, it's not really clear how to # test things. Note all the quotation marks at the beginning of # this if block, for example - clearly there is no confidence in # what we're doing. Isn't it great when you know the language is # which you're coding? ($status,$trigrammed_subsels) = &trigram($subsels,$shell_chars,$trigram_char); ($status eq "NG") && die ("trigram error: $trigrammed_subsels\n"); ($status eq "OK") || ($status eq "WARN") || die ("Internal error: trigram func returned unknown status: $status\n"); $ENV{'SUBSELS'} = $trigrammed_subsels; # # Check QS for operator that should be htmlescaped for displaying # ex: press<200 ---> press<200 # if ($subsels =~ /[<>]/) { $command = "$topdir/bin/htmlesc \"$subsels\""; ($dispss,$exit_status,@status) = &backtick($command); ($exit_status == 0) || die &format_backtick_return_status ($command,$exit_status,@status,$dispss); } else { $dispss = "$subsels_qs"; } $command = "$topdir/bin/trigram_util -un \"$dispss\""; ($disp_style,$exit_status,@status) = &backtick($command); ($exit_status == 0) || die &format_backtick_return_status ($command,$exit_status,@status,$disp_style); $ENV{'DISPSS'} = "$disp_style"; $ENV{'DISPOBJ'} = "$object($disp_style)"; } } } sub return_local_object_info # Most of the work is parsing env var LOCAL_SERVER_ROOTS # Input args are object under consideration and the name of the local server roots env var # Returns 2 values: local object spec and local server root. If these cannot be determined, returns # empty strings. # Dies on any error conditions { my ($object,$local_server_root_env_var_name,$dummy) = @_; # Next 2 must match vals in build-env.pl my ($env_var_pairs_separator) = ','; my ($env_var_within_pair_separator) = '|'; my ($qm_pairs_sep) = quotemeta($env_var_pairs_separator); my ($qm_within_pair_sep) = quotemeta($env_var_within_pair_separator); my ($local_server_root_env_val); # Note: $local root not needed for build-opt-env.pl purposes, but is checked here anyway # Now it has build-opt-env.pl purposes! my ($data_server_spec,$local_root); my ($host_and_port_from_object,$local_object_spec); ((defined $local_server_root_env_var_name) && ( ! (defined $dummy))) || die ("Internal error: return_local_object_info called w/wrong # args"); ($local_server_root_env_val = $ENV{$local_server_root_env_var_name}) || return "",""; foreach (split /$qm_pairs_sep/,$local_server_root_env_val) { $_ || die ("Empty field in env var $local_server_root_env_var_name. env var val: -->$local_server_root_env_val<--"); ($data_server_spec,$local_root,$dummy) = split/$qm_within_pair_sep/; (defined $dummy) && die("More than 2 fields where pair expected in env var $local_server_root_env_var_name. env var val: -->$local_server_root_env_val<--"); ($data_server_spec && $local_root) || die ("Missing/empty/0 field in env var $local_server_root_env_var_name. env var val: -->$local_server_root_env_val<--"); $data_server_spec = &whitespace_strip($data_server_spec); $local_root = &whitespace_strip($local_root); ($data_server_spec && $local_root) || die ("all-whitespace or 0 field in env var $local_server_root_env_var_name. env var val: -->$local_server_root_env_val<--"); # Remove optional prefixes ( ($dummy) = ($data_server_spec =~ qq|^//(.*)|) ) && ($data_server_spec = $dummy); ( ($dummy) = ($local_root =~ qq|^/(.*)|) ) && ($local_root = $dummy); ($data_server_spec && $local_root) || die ("all-slash or 0 field in env var $local_server_root_env_var_name. env var val: -->$local_server_root_env_val<--"); ($data_server_spec =~ qq|^/|) && die ("data server name in env var $local_server_root_env_var_name starts w/illegal # of slashes. env var val: -->$local_server_root_env_val<--"); ($local_root =~ qq|^/|) && die ("local root in env var $local_server_root_env_var_name starts w/illegal # of slashes. env var val: -->$local_server_root_env_val<--"); # Return local root as absolute file spec $local_root = '/' . $local_root; # Relying on format of $object since it presumably "comes from us" via parse_path_info # Can rely, but if it's a local object, there might not BE leading slashes! ($object =~ qq|^//|) || return $object,$local_root; ($host_and_port_from_object,$local_object_spec) = ($object =~ qq|^//(.+?)(/.+)|); ($host_and_port_from_object eq $data_server_spec) && (return $local_object_spec,$local_root); } return "",$local_root; } sub print_to_stderr_env{ print STDERR "environment established at present\n"; while (($key,$value) = each %ENV) { print STDERR "$key=$value\n"; } # copy_file code pre-May 2007 #sub copy_file() #{ # use Fcntl; # # $to = pop(@_); # $from = pop(@_); # # open(FROM,"$from") || die "Cannot open $from: $!\n"; # (undef,undef,$permissions,undef) = stat $from; ## Note: although doc says "calls open(2)", the return status seems to ## be the normal 0=bad rather than the open return of -1=bad. Empirical ## test (+observation that in perl, does not have to return file descrip) ## Could not get sysopen to both create and write; hence double open ## (Turns out that I ANDed in O_WRONLY() instead of ORing. Might be ## worth another try... when the error checking on the copy loop is ## added, for example) ## Should really mask owner write permission into $permissions... ## More notes: when the directory in $to did not allow O_CREAT(), ## program apparently did not return here after failure. ?? (May 07) # sysopen ( TO, $to, (O_CREAT() | O_WRONLY()), $permissions ) # || die "Cannot create $to: $!\n"; # while () { # print TO; # } # # close(FROM); # close(TO); #} }