[Next] [Up] [Previous] [Contents]
Next: Supplementary Code Up: Development... Previous: Checking on a Specific

The Parts Server Code

#!/usr/local/bin/perl/perl
# $Id: familyd,v 1.4 1996/07/18 19:27:05 fparts Exp $
# add server directory to INCLUDE path.
unshift(@INC, '/usr2/cuser/fparts/server/');
require 'sys/socket.ph';
require 'sys/errno.ph';
require 'sys/wait.ph';
require 'sys/ipc.ph';

# spawn child to be the server master process and exit
# (automatic background execution)
fork && exit;

# customizations
$logfile = "/usr2/cuser/fparts/server/server-log-exp";
$pidfile = "/usr2/cuser/fparts/server/server-pid-exp";
$maintainer = "gmm7689@sudcv91.ed.ray.com";
$port = 4000;
$IPC_KEY = 4000;  # semaphore and queue number
$display = "localhost:1.0";
# end customizations

# store server PID in $pidfile, after making sure another isn't running.

# make the file writable
chmod(0640, $pidfile);

# open the file for read/write
open(PIDFILE, "+< $pidfile") || open(PIDFILE, "+> $pidfile") || 
  die "Unable to open $pidfile: $!";

# check if a PID is in the file
if(($_ = <PIDFILE>) > 0)  
  {
    # lose the newline
    chop;

    # if the process is running, ps will return two lines, else one line.
    (`ps $_ | wc -l` == 2) && 
        die("\n$0: The server is already running, exiting.\n");
  }
# return to the beginning of the file
seek(PIDFILE, 0, 0);
# write our PID
print PIDFILE "$$\n";
# truncate the file to the length of our PID plus newline (current position)
truncate(PIDFILE, tell(PIDFILE));
# close the file
close(PIDFILE);
# make it read-only
chmod(0440, $pidfile);

# identify this as the master process (via ps)
$0 = "Family of Parts Server Master";

# change the DISPLAY for this process to the Xvfb server
$ENV{'DISPLAY'} = $display;

# create System V IPC queue to receive log messages
$queue = msgget($IPC_KEY, 0600 | &IPC_CREAT);

# reset the server log on a SIGHUP.  (Useful for nightly maintenance.)
$SIG{'HUP'} = 'reset_log';

# if we were able to create a queue, spawn the logger process
if(defined($queue)) 
  {
    if($master = fork) {
      # pass TERM to server master so it can clean up.
      $SIG{'TERM'} = 'pass_sigterm';

      # identify this as the logger process (via ps)
      $0 = "Family of Parts Server Logger";
      # create a filehandle to the server log and select it
      (open(LOGFILE, ">>$logfile") && select(LOGFILE)) ||
      warn "Unable to open server log.\n";

      # force a flush after every write or print
      $| = 1;

      # record that a logger started
      print "$$:".&t.": Logger started.\n";
      print "$$:".&t.": Logger spawned master process $master.\n";

      # receive and save messages forever
      while(1)
        {
          msgrcv($queue, $message, 1024, 0, 0);
          print $message;
        }
    
      exit 0;
    }

    # $logger is parent process
    $logger = getppid; 
  }
else # no queue, so individual processes will do their own logging 
  {
    $error = $!;
    # create a filehandle to the server log and select it
    (open(LOGFILE, ">>$logfile") && select(LOGFILE)) ||
    warn "Unable to open server log.\n";

    # force a flush after every write or print
    $| = 1;

    # record the failure
    print "$$:".&t.": Unable to create logger queue. $error\n";
  }

# create System V IPC semaphore for the children, to prevent multiple
# children running CADDS at the same time.
$semaphore = semget($IPC_KEY, 1, 0600 | &IPC_CREAT );

if(defined($semaphore))
  {
    # remove semaphore locking on SIGUSR1 (signaled on a semaphore error.)
    # this is to (hopefully) prevent children from hanging forever
    # because of a semaphore error.
    $SIG{'USR1'} = 'sem_remove';
  }
else
  {
    $error = $!;
    &report("semaphore: $!.  Continuing without semaphores.\n");

    # report error to proper person
    open(MAIL, "|/usr/ucb/mail -s 'Generate Server Error' $maintainer");
    print MAIL "Semaphore error.  Locking disabled.\n";
    print MAIL "$error\n";
    close(MAIL);
  }

# report server master process
&report("Server process master\n");

# get $proto necessary for socket()
($name, $aliases, $proto) = getprotobyname('tcp');

# build an internet domain port address
$sockaddr = 'S n a4 x8';
$thisport = pack($sockaddr, &AF_INET, $port, "\0\0\0\0");

# create a socket in the internet domain, of type stream, using tcp
unless(socket(S, &AF_INET, &SOCK_STREAM, $proto))
  {
    $error = $!;
    &report("socket: $!\n");
    kill 'TERM', $logger;
    die "socket: $error";
  }
&report("socket ok\n");

# bind the socket to the port
unless(bind(S, $thisport))
  {
    $error = $!;
    &report("bind: $!\n");
    kill 'TERM', $logger;
    die "bind: $error";
  }
&report("bind ok\n");

# listen to the socket
unless(listen(S, 5))
  {
    $error = $!;
    &report("listen: $!\n");
    kill 'TERM', $logger;
    die "listen: $error";
  }
&report("listen ok\n");

# add subroutine to shutdown socket and server on a SIGTERM
$SIG{'TERM'} = 'server_exit';

# clean up after exited child on a USR2 signal.
$SIG{'USR2'} = 'cleanup';

# force a flush after every write or print on NS and S
select(NS); $| = 1; 
select(S); $| = 1; 
unless($queue){select(LOGFILE);}

$con = 0;

# loop forever, keeping track of number of connections
while(1)
  {
    $con++;
    &report("Listening for connect $con.\n");
 
    # wait for a connection, accept only returns on a connection or an
    # error
ACCEPT:
    unless($addr = accept(NS, S)) 
      {    
        # set $dont_log to 1 when handling HUP and USR2 signals
	if($dont_log) { $dont_log = 0; }
        else
	  {
            # report accept failure
	    &report("accept: $!\n");
          }
          
        goto ACCEPT;
      }

    &report("accept ok\n");

    # spawn a child to deal with connection
    fork && next;
    
    # get pid of master process
    $master = getppid;

    # ignore TERM signals
    $SIG{'TERM'} = 'IGNORE';

    # identify this child (viewed by ps)
    $0 = "Family of Parts Server Connection $con";
  
    # child that actually handles the connection
    print "$master:".&t.": Child $$ forked.\n";

    # get remote hostname, IP, and remote port from connection
    ($af, $port, $inetaddr) = unpack($sockaddr, $addr);
    ($name) = gethostbyaddr($inetaddr, &AF_INET);
    $inetaddr = join('.', unpack('C4', $inetaddr));

    # report connection with hostname, IP and remote port
    &report("Connect from $name ($inetaddr) port $port.\n");

    # do something with the connection.
    $status = &handle_connection();

    print NS "$status\n";
    # close the connection
    close(NS);

    # log that we've closed the connection
    &report("Connection $con closed.\n");

    # log that we're exiting, and exit
    &report("Exiting with generate status $status.\n");

    unlink("$lockfile$$", "$tmp_message_log", "$tmp_output_log",
           "$tmp_cadds_script");

    # tell the master process to clean up after us
    kill 'USR2', $master;

    exit 0;
  }

exit;

# end of main routine

# clean up after an exited child, don't log the accept failure
sub cleanup { 
  $dont_log = 1;
  sleep(5);  # sleep long enough for the child to exit
  while($status = waitpid(-1, &WNOHANG)){}
}
  
sub server_exit {
  # shut down the socket, (no more connections)
  shutdown(S, 2);

  &report("Waiting for children to exit.\n");

  # stop accepting USR2 cleanup requests;
  $SIG{'USR2'} = 'IGNORE';

  # wait returns -1 when no children are left.
  while(wait != -1){}

  &report("Children exited.  Killing logger and exiting.\n");

  # give logger time to record message
  sleep(5);

  # kill the logger, in case logger didn't send the TERM
  if($queue){ kill 'TERM', $logger; }

  unlink($pidfile);
  exit 0;
}

# pass TERM to master from logger, collect queue messages while waiting
# for master to exit, close the log, and exit.
sub pass_sigterm {
  # ignore any more TERM signals
  $SIG{'TERM'} = 'IGNORE';

  # record that we received the signal and pass it along.
  print "$$:".&t.": Logger received TERM, passing to master.\n";
  kill 'TERM', $master; 

  # record messages while waiting for master to exit
  while(waitpid($master, &WNOHANG) == 0)
    {
      if(msgrcv($queue, $message, 1024, 0, &IPC_NOWAIT)) { print $message; }
    }

  # log any remaining messages
  while(msgrcv($queue, $message, 1024, 0, 0)) { print $message; }

  # log that we're exiting, close the log, and exit
  print "$$:".&t.": Logger exiting.\n";
  close(LOGFILE);
  exit 0;
}

sub reset_log {
  truncate(LOGFILE, 0);
  seek(LOGFILE, 0, 0);
  print "$$:".&t.": Logfile restarted.\n";

  # have to clear out $message or it will get recorded in the new log.
  $message = "";

  # set $log_reset to 1 so accept failure isn't recorded in the new
  # log.
  $log_reset = 1;
}

# returns <day of year>:hh:mm.ss
# for time stamp of log entries.
sub t {
  ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday) = localtime();
  $date = sprintf("%03d:%02d:%02d.%02d", $yday, $hour, $min, $sec);
}

# semaphore error, so don't use it anymore
sub sem_remove { undef($semaphore); }

sub report {
  local($report) = @_;
  $message = "$$:".&t.": $report";

  # if we are using a queue and logger, send the message to the queue,
  # else print it to the logfile.
  if(defined($queue)) {
    msgsnd($queue, $message, 0); }
  else {
    print $message; }
}

# return status:
# 0: success
# 1: failure
# 2: aborted because member directory exists. (if part exists, CADDS
# hangs because it asks "Part exists.  Overwrite?" in a dialog)
sub handle_connection
{
  # create names for output log and CADDS script
  $tmp_output_log = "/tmp/family-parts.$$.out";
  $tmp_cadds_script = "/tmp/family-parts.$$.csh";

  # create name for message file, in Unix and CADDS formats
  $tmp_message_log = "/tmp/family-parts.$$.mes";
  $message_log = "=tmp.family-parts@$$";

  # remove any existing files, start new ones
  open(OUT, ">$tmp_message_log");
  close(OUT);
  open(OUT, ">$tmp_output_log");

  # get request to generate parts
  chop($_ = <NS>);
  ($master, $member, $directory) = split;

  # report request to output log and server log
  print OUT "Request: $master $member $directory\n";
  &report("Request: $master $member $directory\n");

  unless($directory =~ /\w/)
    { 
      # bad arguments, report error to proper person
      open(MAIL, "|/usr/ucb/mail -s 'Generate Server Error' $maintainer");
      print MAIL "Invalid Input Received: $master $member $directory";
      close(MAIL);

      return(1);
    }

  # build the name of the directory that will hold the parts
  ($part_dir = "$directory-family/$member") =~ "/(.*)/\L\1\E/";

  # check to see if it exists.
  if(-e $part_dir)
    {
      print OUT "Directory for member part exists.\n";
      &report("Directory for member part exists.\n");
      close(OUT);

      # report error to proper person
      system("cat $tmp_output_log | ".
             "/usr/ucb/mail -s 'Generate Server Error' $maintainer");

      return(2);
    }

  close(OUT);

  # find the users .caddsrc file
  if(-f "$ENV{'HOME'}/.caddsrc") 
    { 
      $caddsrc = "$ENV{'HOME'}/.caddsrc"; 
    }
  elsif(-f "/usr/apl/cadds/scripts/templates/.caddsrc") 
    {
      $caddsrc = "/usr/apl/cadds/scripts/templates/.caddsrc"; 
    }
  else
    { 
      # report error to proper person
      open(MAIL, "|/usr/ucb/mail -s 'Generate Server Error' $maintainer");
      print MAIL "File Error.  Unable to find a .caddsrc\n";
      print MAIL "$!\n";
      close(MAIL);
      &report("Warning: NO .caddsrc file found"); 
    }

  # Read the local file if one exists
  if(-f "$ENV{'HOME'}/.caddsrc-local") 
    { 
      $caddsrc .= " $ENV{'HOME'}/.caddsrc-local";
    }

  unless(open(SCRIPT, ">$tmp_cadds_script"))
    {  
      &report("Unable to write script.\n");
      
      # report error to proper person
      open(MAIL, "|/usr/ucb/mail -s 'Generate Server Error' $maintainer");
      print MAIL "File Error.  Unable to write script file.\n";
      print MAIL "$!\n";
      close(MAIL);
      return(1);
    }

  # make sure no parts or assemblies are read into the LDM
  # run cadds, -cron keeps windows from being mapped, -ldm starts in ldm,
  # passing a command to read the command file
  # -cron removed so windows will show up in an xwud of the Xserver image
  $cadds_command =  "#!/bin/csh -f
source $caddsrc
setenv CV_DB_PARTLISTLIMIT 0
setenv CADDSASSEMBLYLISTLIMIT 0
/usr/apl/cadds/scripts/cadds5 -ldm << EOF >>& $tmp_output_log
Write Message Append name $message_log
Generate Family Filename $master Member $member
Exit Cadds
EOF
";

  print SCRIPT $cadds_command;
  close(SCRIPT);
  chmod(0700, "$tmp_cadds_script");

  # semaphore wait and set
  if(defined($semaphore))
    {
      # wait for semaphore to be zero
      $opstring = pack("sss", 0, 0, 0);
      # Increment the semaphore count
      $opstring .= pack("sss", 0, 1, 0);
      unless(semop($semaphore, $opstring))
        {
	  $error = $!;
          &report("semaphore: $!.  Continuing without semaphores.\n");
	  undef($semaphore);
	  kill 'USR1', getppid, getpgrp;

          # report error to proper person
          open(MAIL, "|/usr/ucb/mail -s 'Generate Server Error' $maintainer");
          print MAIL "Semaphore error.  Locking disabled.\n";
	  print MAIL "$error\n";
          close(MAIL);
        }
      $semset = 1;
    }

  system("$tmp_cadds_script");

  # semaphore remove
  if(defined($semaphore))
    {
      # Decrement the semaphore count
      $opstring = pack("sss", 0, -1, 0);
      unless(semop($semaphore, $opstring))
        {
          $error = $!;
          &report("semaphore: $!.  Continuing without semaphores.\n");
	  undef($semaphore);
	  kill 'USR1', getppid, getpgrp;

          # report error to proper person
          open(MAIL, "|/usr/ucb/mail -s 'Generate Server Error' $maintainer");
          print MAIL "Semaphore error.  Locking disabled.\n";
	  print MAIL "$error\n";
          close(MAIL);
        }
      $semset = 0;
    }

  # look for string in log file indicating success, count how many times it
  # occurs.  If it doesn't occur, generation failed.
  if(`grep -c '1 family member parts filed' $tmp_message_log` == 0) 
    {
      &report("Generation failed.\n");

      # generation failed, report error to proper person
      system("cat $tmp_output_log $tmp_message_log | ".
	     "/usr/ucb/mail -s 'Generate Member Failure'".
	     $maintainer);
      
      return(1);
    }
  
  &report("Generation successful.\n");
  
  $member =~ s/(.*)/\L\1\E/;

  # set the permissions on the -family directory and all part
  # directories everyone can read or write in the part directory,
  # but not delete any files they don't own.  This is necessary
  # for creation of lock and temp files when activating a part, as
  # well as tvf's. 
  chmod(0755, "$directory-family");
  chmod(01775, <$directory-family/*>);
  
  # create the vp_links directory
  mkdir("$directory-family/$member/vp_links", 0);

  # set the permissions on the part files
  chmod(0444, <$directory-family/$member/*>);
  chmod(01775, "$directory-family/$member/vp_links");

  return(0);
}

Last Modified: Wed Aug 28 14:41:29 EDT 1996

Gregory Marr <gregm@alum.wpi.edu>