# Getting data into the MOO

Michael Brundage (anomaly@u.washington.edu)
Fri, 26 Apr 1996 16:52:30 PDT

- Messages sorted by:[ date ][ thread ][ subject ][ author ]

    On Fri, 26 Apr 1996, Chuck Adams wrote:
    > Michael Brundage drew these hieroglyphs:
    > > I used (a modified version of) Dave's  script with
    > > 	mathematica | server.perl
    > > to allow the MOO to create Mathematica(tm) graphics in response to user
    > > commands (and then convert these graphics to gif, and then feed the image
    > > back in a web page).  It worked like a charm! :)
    >
    > for the perl-uninclined, where might we get our hands on such a beast?

    Lord knows I'm not a perl guru either.  I just took what Dave posted and
    skimmed through the Camel book, and put together an ugly hack (which
    works, so maybe that's enough).  For those who want it, here it is;
    however, I don't make any promises that it will work on your system, not
    corrupt your filesystem, make large transfers from your bank account to
    mine, etc., etc.

    First, Dave Van Buren's original perl script (with MOO code), posted to
    moo-cows sometime around July, 95:

    #!/usr/local/bin/perl
    #      REPLACE ABOVE LINE WITH YOUR PATH TO PERL!
    #
    # vanilla_server -- takes dotted input, runs a unix command
    # as if it were the input file and returns results as a dotted
    # list of strings.  Operates like a batch facility.
    #
    # This server is only illustrative and has not been made bulletproof or
    # particularly secure.  See also: the perl camel and llama books, and the
    # perl man page.  There is a useful newsgroup comp.lang.perl.  This code
    # is adapted from page 225 of Schwartz, RL "Learning Perl", O'Reilly and
    # Associates, 1994.
    #
    # Syntax:  vanilla_server <port-number> <unix-command> [&]
    #          vanilla_server 8181 "ls -l" &
    #
    # Example: run the vanilla server in the background
    #
    #          vanilla_server 4242 "cat" &
    #
    # Now telnet to port 4242 on that machine:
    #
    #          telnet `hostname` 4242
    #
    # And enter the following lines (a dot is the first character on each line):
    #
    #          .abc
    #          .def
    #          .
    #
    # You should get the output:
    #
    #          .abc
    #          .def
    #          .
    #
    # And here is a little MOO-code client (requires wiz-perms usually for
    # open_network_connection and read).  Define the verb on your player class as
    # @connect-to any at any, then edit it to contain the following:
    #
    #   "@connect-to -- makes a network connection to host, port";
    #   "Syntax:  @connect-to <host-name> at <port-number>";
    #   "         @connect-to lambda.parc.xerox.com at 8888";
    #   "         This example is fun because it gives you a MOO client for MOO";
    #   c = open_network_connection(dobjstr, tonum(iobjstr));
    #   if (typeof(c) == OBJ)
    #     fork tid (0)
    #       while (1)
    #         notify(c, read(player));
    #       endwhile
    #     endfork
    #     while (typeof(line = read(c)) == STR)
    #       player:tell("", line);
    #     endwhile
    #     kill_task(tid);
    #     player:tell("@connect-to finished.");
    #   else
    #     player:tell("Could not @connect-to ", dobjstr, " at ", iobjstr, ".");
    #   endif
    #
    # Enjoy!                                      -- Dave Van Buren (July 11, 1995)
    #                                                dave@ipac.caltech.edu
    #
    #
    # Give proper syntax for ill-formed invocations.
    #
      if ($#ARGV != 1) {
        print "Syntax:  vanilla_server <port-number> <unix-command> [&]\n";
        exit;
      }
    #
    # Set up serviced command and server sockets.
    #
      $listen_to = @ARGV[0];
      $command = @ARGV[1];
      &do_socket($listen_to);
    #
    # Main execution loop, fork a child to handle each new connection.
    #
      for (;;) {
        accept(NS,S) || die "cannot accept socket\n";
        if (($pid = fork()) == 0) {
    #
    #     is child process
    #
          close(S);
    #
    #     read data, process and spit back out to connection
    #
          $infile=&get_dotted_text_to_file;
          $outfile=&ranfile();
          system("$command < $infile > $outfile");
          &put_dotted_text_from_file($outfile);
    #
    #     cleanup
    #
          unlink($infile);
          unlink($outfile);
          close NS;
          exit;
        } elsif(defined $pid) {
    #
    #     is parent process
    #
          close NS;
        }
      }
    #
    # do_socket -- initializes socket on passed port.  The file socket.ph is
    # derived from the C file socket.h by running the perl utility program
    # h2ph on it (usually found in the same place as perl itself).
    #
      sub do_socket {
        require 'socket.ph';
        $sockaddr = 'S n a4 x8';
        ($name,$aliases,$proto)=getprotobyname('tcp');
        $port=@_[0];
        $thisport=pack($sockaddr,&AF_INET,$port,"\0\0\0\0");
        socket(S,&PF_INET,&SOCK_STREAM,$proto)||die "cannot bind socket\n";
        bind(S,$thisport);
        listen(S,5)||die "cannot listen socket\n";
      }
    #
    # get_dotted_text_to_file -- return the name of a file containing
    # the text sent to the connection.  Data to be read consists of lines starting
    # with a dot.  The dot is stripped when read.  A single dot on a line
    # terminates the input list.  Here's a sample:
    #
    #   |.first line                                  |first line
    #   |.second line   --from connection to file->   |second line
    #   |.              <-from file to connection--   |EOF
    #   |EOF
    #
    # where the "|" marks the location just prior to the first character in a line
    # and EOF denotes the end of the file.  When sending data, the client needs
    # (obviously) to prepend the dot to all data lines.  The routine returns the
    # name of a file containing the data read.
    #
      sub get_dotted_text_to_file {
        local($line,$done,@text,$filename);
        $filename = &ranfile();
        open(FH,">".$filename);
        $done=0;
        while (!($done)) {
          $line=<NS>;
          if (!($line=~/^\.\s*$/)) {
            chop($line);
            chop($line);
            if (length($line)>1) {
              $line=substr($line,1,length($line)-1);
              print FH $line."\n";
            }
          } else {
            close(FH);
            $done=1;
          }
        }
        $filename;
      }
    #
    # put_dot_teminated_text_from_file -- sends the contents of a file to the
    # connection a line at a time.  Each line is preceded by a dot.  The last
    # line sent is ".", a single dot on a line all by itself.  The receiving
    # client needs to detect the single dot as the end of the list and strip
    # the leading dots from list elements.  Pass to this routine the name of
    # the file to be sent.  See documentation for get_dotted_text_to_file
    # for more info.
    #
      sub put_dotted_text_from_file {
        local($filename);
        $filename=@_[0];
        open(FH,$filename) || die "cannot open $filename";
        select(NS); $|=1;
        while (<FH>) {
          print NS ".".$_;
        }
        print NS ".\n";
        select(STDOUT);
      }

    #
    # ranfile -- generate a random file name, but with the extension "rf" for
    # ease of manipulation.  There are 26^8 possible names, a repeat can be
    # expected after roughly 26^4 tries, or about half a million.  Probably good
    # enough for almost any purpose.
    #
      sub ranfile {
        srand;
        local(@c,$filename,$time,$ltime);

    @c=("a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q",
            "r","s","t","u","v","w","x","y","z");
        $filename=@c[rand(@c)].@c[rand(@c)].@c[rand(@c)].@c[rand(@c)].
            @c[rand(@c)].@c[rand(@c)].@c[rand(@c)].@c[rand(@c)].".rf";
    }

    -----

    So, that's what Dave provided, an excellent starting point.  However, it
    didn't do things quite like I needed (for example, the MOO needed to know
    the filename where the output would be, so it could include that in the
    web page), and also it seemed to leave defunct processes floating around
    on our system.  I made the following quick and dirty hack of it.  Note
    that there isn't much in the way of a security check, and Mathematica can,
    in theory, execute shell commands.  You will want to filter such things
    out at the script level (and maybe the MOO level too), and provide a
    better security mechanism than I have here:

    #!/usr/local/bin/perl
    # mma.pl
    # Michael Brundage 10/14/95 brundage@math.washington.edu
    # Overall design taken from pp. 146, 216 of the Camel book
    #  and a similar script by Dave Van Buren posted to moo-cows
    #
    # Usage:
    #     mma.pl [port] | math [ > results &]
    # (replacing mma.pl with the name of this script, and math with the name
    #   of your Mathematica program)  port defaults to $default_port
    #
    # mma.pl accepts connections from a MOO,
    #  takes a graphics command sent from that site and then sends it to
    #  Mathematica for graphing and conversion into gif (since Mma outputs its
    #  graphics in a goofy mathematica `postscript').
    #
    # Call up this script using open_connection(), and then send two lines of
    #  information: the filename, followed by the graphics command.  The
    graphics
    #  will end up in $image_path/filename.gif
    #

    $default_port = 9001;    # Port to which we listen for input
    $security_check="insert your own authorization key here";
    #
    # Mathematica Postscript -> ppm and ppm -> gif conversion software:
    $pstoppm     = '/usr/local/mathematica/Bin/Display/rasterps -format ppm';
    $ppmtogif    = '/user7/brundage/Bin/ppmtogif';
    # Path where image files should be kept (appended to the filename)
    $image_path  = '/user7/brundage/.www/av/mars/';
    $security_log = 'security_report';

    # ordinarily, I'd say you don't need to change anything below, but this
    # is such a hack you probably will.  Feel free to customize as you see fit
    #
    $port = ($#ARGV==0) ? $ARGV[0] : $default_port;
    &handle_socket($port);
    for (;;) {
       accept(NS, S) || die "cannot accept socket\n";
       #
    unless (fork) {
    FORK: if ($pid = fork) {         # parent here: $pid = child process id
             close NS;
          } elsif (defined $pid) {   # child here: $pid = 0
             sleep 1 until getppid == 1;
             close S;
             if(($_ = &get_text())!=$security_check) {
               open(FH, $security_log);
               print FH `date`.$_;
               close NS;
               exit;
             }
             $_ = &get_text();     # Next, get the filename
                $name = "$image_path$_";
                $_ = &get_text();     # Next, get the command to execute
                if(length > 0) {
                   print "Display[\"$name.ps\", $_]\n";
                   print "Run[\"$pstoppm $name.ps > $name.ppm\"]\n";
                   print "Run[\"$ppmtogif $name.ppm > $name.gif\"]\n";
                   print "Run[\"chmod 0644 $name.gif\"]\n";
                   # I had difficulties getting the script to wait for
                   # Mathematica to finish before converting the images,
                   # and still say goodbye to the connection in a timely
                   # fashion.  So, I let Mathematica do the commands
                   # necessary.  You may want to improve this quick-and-dirty
                   # method, and also provide a way for the MOO to know when
                   # the files are ready (right now, I just wait an estimated
                   # length of time for this to finish, and then feed the user
                   # the web page -- a most inelegant approach)
                }
             close NS; # Say goodbye to the MOO
             # now you'll want to clean up these extraneous output files,
             # but not before the user is finished with them (they might want
             # them to persist for an hour or two).  Either do this here,
             # or with another script, or something.
             exit;
          } elsif($! =~ /No more process/) { # recoverable fork error
             sleep 5;
             redo FORK;
          } else {     # strange error with fork;
             die "Fork failed: $!\n";
          }
          exit;
       }
       wait;
    }
    #
    # get_text(stream)
    # Returns a line of input from the stream (stripped of newlines)
    #

    sub get_text {
      $_ = <NS>;
      chop;
      chop;
      $_;
    }

    #
    # handle_socket(port number) -- initializes SOCK on $port
    # requires socket.ph (h2ph /usr/include/sys/socket.h > socket.ph)
    # This code is taken almost directly from the sample server program in
    # R. Schwartz'  "Llama book" (Learning Perl, from O'Reilly and Associates)
    #

    sub handle_socket {
       require 'include/socket.ph';
       $sockaddr = 'S n a4 x8';
       ($name, $aliases, $proto) = getprotobyname('tcp');
       $port = @_[0];
       $thisport = pack($sockaddr, &AF_INET, $port, "\0\0\0\0");
       socket(S, &PF_INET, &SOCK_STREAM, $proto) || die "cannot bind
    socket\n";
       bind(S, $thisport);
       listen(S, 5) || die "cannot listen to socket\n";
    }
    --------------------------

    And here's some MOO code to talk to the script:

    #343:open_connection   this none this
    "Usage: open_connection(request)";
    "Open a connection to this.hostname and this.port, where we expect a
    script";
    " to be running that will take our mathematica request (which is a ";
    " Mathematica graphics command).";
    if (caller_perms().wizard)
      if (typeof(c = open_network_connection(this.hostname, this.port)) ==
    OBJ)
        "First, send the security check";
        for line in ({this.security, @args})
          if (index(line, "Run") || index(line, "!"))
            return E_PERM;
          else
            notify(c, line);
          endif
        endfor
        res = {""};
        while (typeof(line = read(c)) == STR)
          res = {@res, line};
        endwhile
        return res;
      endif
      return 0;
    endif
    return E_PERM;

    ---------

    Nothing fancy! It's ugly, but I used it for a few months to provide
    graphics on demand to students, in response to equations they entered.
    You may need to set up your Mathematica process (which I kept running
    continually in the background, to save on the overhead of
    launching/quitting mathematica every time someone wanted an image).  The
    init.m file I used is

    SetOptions[Plot3D, DisplayFunction -> Identity]
    << Graphics`Graphics`
    << Graphics`Polyhedra`

    Use at your own risk, and all that jazz.  Hope someone finds it useful!

    michael

    anomaly@u.washington.edu