--- /dev/null
+#!/usr/bin/perl
+################################################################################
+#
+# File: ecrd: ECR Daemon
+# Description: This script implements a daemon that handles requests for
+# queries about information on ECRs contained in the Quintus
+# database. In addition to lessoning the amount of time it takes
+# for database opens, access to Quintus data is only available
+# on certain machines. Additionally, for Perl to access this
+# Informix database the Informix version of DBD would need to be
+# locally installed. By calling this daemon instead clients need
+# not have to install Informix and then code all the necessary
+# code to access Quintus data as well as have to understand the
+# structure of the database. Instead clients need only say "Give
+# me what you got on ECR #<whatever>".
+# Author: Andrew@DeFaria.com
+# Created: Tue Feb 15 09:54:59 PST 2005
+# Modified:
+# Language: Perl
+#
+# (c) Copyright 2005, LynuxWorks, all rights reserved.
+#
+################################################################################
+use strict;
+use warnings;
+
+use IO::Socket;
+use Net::hostent;
+use POSIX qw(setsid);
+use DBI;
+
+my $ecrdb = "lynxmigr1";
+my $port = (!defined $ENV {ECRDPORT}) ? 1500 : $ENV {ECRDPORT};
+
+# Global variables
+my $DB;
+my $ecrserver;
+my $ecrclient;
+my $sth;
+my $statement;
+
+# Options
+my $verbose;
+my $debug;
+my $daemon_mode;
+my $quiet_mode;
+my $multithreaded;
+my $timeout = 10;
+
+# ECR translations. Note the Quintus database stores certain choice lists as
+# enumerations. They I guess they are configurable. The right thing to do
+# would be to figure out how to look up the definition given the number. But
+# we are gonna cheat here and hard code a few important enumerations.
+my @defstatus = (
+ "Open",
+ "Closed",
+ "Fixed",
+ "Not a bug",
+ "Obsolete",
+ "Defered",
+ "Duplicate"
+);
+my @state = (
+ "Reported",
+ "Assigned",
+ "Selected",
+ "Resolved",
+ "Integrated",
+ "Retired",
+ "Reviewed",
+ "Pending Review"
+);
+my @priority = (
+ "Low",
+ "Medium",
+ "High",
+ "Critical"
+);
+my @severity = (
+ "Low",
+ "Medium",
+ "High",
+ "Critical"
+);
+
+# Pid
+my $pid = $$;
+
+my $me = `basename $0`;
+chomp $me;
+my $ecrdversion = "1.3";
+
+my @all_fields = (
+ "productdefect", # integer
+ "componentdefect", # integer
+ "defectdefectdup", # integer
+ "workgroupdefect", # integer
+ "reporterdefect", # integer
+ "resolverdefect", # integer
+ "confirmerdefect", # integer
+ "buildversdefect", # integer
+ "rpt_versdefect", # integer
+ "res_versdefect", # integer
+ "conf_versdefect", # integer
+ "state", # integer
+ "resolverstatus", # integer
+ "confirmerstatus", # integer
+ "escstatus", # integer
+ "owner", # integer
+ "severity", # integer
+ "priority", # integer
+ "summary", # varchar(80,0)
+ "datereported", # datetime year to second
+ "dateresolved", # datetime year to second
+# "description", # text
+# Note: Some descriptions fields are huge containing things like
+# uuencoded tar files! They are so huge that they cause this server
+# to fail (not sure why - it shouldn't but it does. So this hack
+# returns only the first 50K of description to avoid that problem.
+ "description [1,50000]", # text
+ "cclist", # varchar(80,0)
+ "dateconfirmed", # datetime year to second
+ "datemodified", # datetime year to second
+ "fix_by_date", # date
+ "fix_by_version", # integer
+ "history", # text
+ "likelihood", # integer
+ "estfixtime", # datetime year to second
+ "actfixtime", # datetime year to second
+ "resolution", # text
+ "businessimpact", # integer
+ "origin", # integer
+ "docimpact", # integer
+ "report_platform", # integer
+ "resolve_platform", # integer
+ "confirm_platform", # integer
+ "test_file", # varchar(64,0)
+ "visibility", # integer
+ "misc", # varchar(80,0)
+ "defecttype", # integer
+ "defstatus", # integer
+ "customertext", # text
+ "modifiedby", # varchar(20,0)
+ "classification", # integer
+ "datefixed" # datetime year to second
+);
+
+# Forwards
+sub CloseDB;
+sub GetRequest;
+
+sub timestamp {
+ my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst) = localtime(time);
+
+ $mday = "0$mday" if $mday < 10;
+ $mon = "0$mon" if $mon < 10;
+ $hour = "0$hour" if $hour < 10;
+ $min = "0$min" if $min < 10;
+ $year += 1900;
+
+ return "$mon/$mday/$year $hour:$min";
+} # timestamp
+
+sub log_message {
+ print "[$pid] " . timestamp . " @_\n" if defined $verbose;
+} # log_message
+
+sub log_error {
+ print STDERR "[$pid] " . timestamp . " ERROR: @_\n"
+} # log_error
+
+sub log_warning {
+ print STDERR "[$pid] " . timestamp . " WARNING: @_\n"
+} # log_error
+
+sub debug {
+ print "[$pid] " . timestamp . " @_\n" if defined $debug;
+} # debug
+
+sub verbose {
+ print "[$pid] " . timestamp . " @_\n" if !defined $quiet_mode;
+} # verbose
+
+sub DBError {
+ my $msg = shift;
+ my $statement = shift;
+
+ if (!defined $DB) {
+ print "Catostrophic error: DB undefined!\n";
+ exit 1;
+ } # if
+
+ print $msg . "\nError #" . $DB->err . " " . $DB->errstr . "\n";
+ print "SQL Statement: $statement\n" if defined $statement;
+
+ exit $DB->err;
+} # DBError
+
+sub timeout {
+ debug "After $timeout seconds of inactivity client timed out";
+
+ my $hostinfo = gethostbyaddr ($ecrclient->peeraddr);
+ my $host = $hostinfo->name || $ecrclient->peerhost;
+ debug "Closing connection to $host";
+
+ # Close client's connection
+ close $ecrclient;
+
+ # Set up signal handlers again
+ $SIG{ALRM} = \&timeout;
+ $SIG{INT} = $SIG{QUIT} = 23234
+\&interrupt;
+ GetRequest;
+} # timeout
+
+sub interrupt {
+ log_warning "Interrupted - closing down...";
+ close $ecrserver;
+ verbose "Connection closed";
+ CloseDB;
+
+ exit;
+} # interrupt
+
+sub GetClientAck {
+ my $client = shift;
+ my $clientresp;
+
+ debug "ENTER: GetClientAck";
+ alarm $timeout;
+ while (defined $client and defined ($clientresp = <$client>)) {
+ chomp $clientresp;
+ chop $clientresp if $clientresp =~ /\r/;
+ if ($clientresp eq "ACK") {
+ return
+ } # if
+ log_warning "Received $clientresp from client - expected ACK";
+ } # while
+ debug "EXIT: GetClientAck";
+} # GetClientAck
+
+sub GetClientCmd {
+ my $client = shift;
+ my $clientresp;
+
+ alarm $timeout;
+ while (defined $client and defined ($clientresp = <$client>)) {
+ chomp $clientresp;
+ return $clientresp;
+ } # while
+} # GetClientResponse
+
+sub SendClientAck {
+ my $client = shift;
+
+ debug "ENTER: SendClientAck";
+ print $client "ACK\n";
+ debug "EXIT: SendClientAck";
+} # SendClientAck
+
+sub SendClientResponse {
+ my $client = shift;
+ my $response = shift;
+
+ print $client "$response\n";
+} # SendClientResponse
+
+sub EnterDaemonMode {
+ my $logfile = shift;
+ my $errorlog = shift;
+
+ $logfile = "/dev/null" if $logfile eq "";
+ $errorlog = "/dev/null" if $errorlog eq "";
+
+ # Change the current directory to /
+ chdir '/'
+ or die "$me: Error: Can't chdir to / ($!)";
+
+ # Turn off umask
+ umask 0;
+
+ # Redirect STDIN to /dev/null
+ open STDIN, '/dev/null'
+ or die "$me: Error: Can't redirect /dev/null ($!)";
+
+ # Redirect STDOUT to logfile
+ open STDOUT, ">>$logfile"
+ or die "$me: Error: Can't redirect stdout to $logfile ($!)";
+
+ # Redirect STDERR to errorlog
+ open STDERR, ">>$errorlog"
+ or die "$me: Error: Can't redirect stderr to $errorlog ($!)";
+
+ # Now fork the daemon
+ defined (my $pid = fork)
+ or die "$me: Error: Can't create daemon ($!)";
+
+ # Now the parent exits
+ exit if $pid;
+
+ # Set process to be session leader
+ setsid
+ or die "$me: Error: Can't start a new session ($!)";
+} # EnterDaemonMode
+
+sub OpenDB {
+ # Connect to database. Note this is using anonymous access (read only)
+ $DB = DBI->connect("DBI:Informix:$ecrdb")
+ or DBError "Unable to open database";
+ log_message "Opened $ecrdb database";
+
+ # Setup our select statement with placeholders
+ $statement = "select ";
+
+ # Build up the field list
+ my $first_time = 1;
+ foreach (@all_fields) {
+ if ($first_time) {
+ $first_time = 0;
+ $statement .= $_;
+ } else {
+ $statement .= ",$_";
+ } # if
+ } # foreach
+
+ # Now add the table and condition
+ $statement .= " from defect where pkey=?";
+
+ $sth = $DB->prepare ($statement)
+ or DBError "Unable to prepare statement", $statement;
+} # OpenDB
+
+sub CloseDB {
+ $DB->disconnect ()
+ or DBError "Unable to disconnect from database!";
+ verbose "Closed $ecrdb database";
+} # CloseDB
+
+sub Usage {
+ my $msg = shift;
+
+ print "$msg\n\n" if defined $msg;
+
+ print "Usage: $me [ -D ] [ -v ] [ -d ] [-p <port>] [ -m ] [ -q ]\n\n";
+ print "Where:\t-D\tEnter Daemon mode\n";
+ print "\t-v\tVerbose mode (Default off)\n";
+ print "\t-d\tDebug mode (Default off)\n";
+ print "\t-p\tPort number to use (Default 1500)\n";
+ print "\t-m\tMultithreaded (Default off)\n";
+ print "\t-q\tQuiet mode (Default on)\n";
+ exit 1;
+} # Usage
+
+sub GetECRRecord {
+ my $ecr = shift;
+
+ if ($ecr =~ /\D/) {
+ log_error "ECR $ecr is not numeric!";
+ return ();
+ } # if
+
+ my %fields;
+ my $record;
+ my $value;
+
+ $sth->execute ($ecr)
+ or DBError "Unable to execute statement", $statement;
+
+ my $row = $sth->fetchrow_arrayref;
+
+ if (!defined $row) {
+ # @row is empty if there was no ECR by that number
+ log_error "ECR $ecr not found!";
+ return ();
+ } # if
+
+ my @rows = @{$row};
+ foreach (@all_fields) {
+ my $value = shift @rows;
+
+ # Transform newlines to "\n" so the field is treated as one large field
+ $value =~ s/\n/\\n/g if defined $value;
+
+ # Perform some choice list field translations. Again this would be
+ # better done by doing database lookups to translate the enums...
+ $value = $defstatus [$value] if /defstatus/ and defined $value;
+ $value = $state [$value] if /state/ and defined $value;
+ $value = $priority [$value] if /priority/ and defined $value;
+ $value = $severity [$value] if /severity/ and defined $value;
+ # Fix description field back
+ if (/^description/) {
+ $_ = "description";
+ } # if
+ $fields {$_} = $value
+ } # foreach
+
+ return %fields;
+} # GetECRRecord
+
+sub ServiceClient {
+ my $ecrclient = shift;
+
+ # Service this client
+ my $hostinfo = gethostbyaddr ($ecrclient->peeraddr);
+ my $host = $hostinfo->name || $ecrclient->peerhost;
+
+ verbose "Connect from $host";
+ log_message "Waiting for command from $host";
+ while () {
+ GetClientAck ($ecrclient);
+ $_ = GetClientCmd ($ecrclient);
+ next unless /\S/; # Skip blank requests
+ last if /quit|exit/i;
+
+ if (/\*/) {
+ log_message "$host requests a list of all ECR #'s";
+ SendClientAck ($ecrclient);
+ ReturnAllECRNbrs ($ecrclient);
+ SendClientAck ($ecrclient);
+ next;
+ } # if
+
+ log_message "$host requests information about ECR $_";
+ SendClientAck ($ecrclient);
+ my %fields = GetECRRecord $_;
+
+ if (%fields) {
+ SendClientResponse ($ecrclient, "ecr: $_");
+ while (my ($key, $value) = each (%fields)) {
+ $value = !defined $value ? "" : $value;
+ SendClientResponse ($ecrclient, "$key: $value");
+ } # while
+ } else {
+ SendClientResponse ($ecrclient, "ECR $_ was not found");
+ } # if
+ SendClientAck ($ecrclient);
+ } # while
+
+ verbose "Closing connection from $host at client's request";
+ close $ecrclient;
+} # ServiceClient
+
+sub Funeral {
+ my $childpid = wait;
+ $SIG{CHLD} = \&Funeral;
+ log_message "Child has died" . ($? ? " with status $?" : "");
+} # Funeral
+
+sub GetRequest {
+ # Now wait for an incoming request
+ while ($ecrclient = $ecrserver->accept ()) {
+ my $hostinfo = gethostbyaddr ($ecrclient->peeraddr);
+ my $host = $hostinfo->name || $ecrclient->peerhost;
+ log_message "$host is requesting service";
+ if (defined ($multithreaded)) {
+ my $childpid;
+
+ log_message "Spawning child to handle request";
+
+ die "$me: ERROR: Can't fork: %!" unless defined ($childpid = fork ());
+
+ if ($childpid) {
+ # In parent - set up for clean up of child process
+ log_message "Parent produced child ($childpid)";
+ $SIG{CHLD} = \&Funeral;
+ log_message "Parent looking for another request to service";
+ } else {
+ # In child process - ServiceClient
+ $pid = $$;
+ debug "Child [$pid] has been born";
+ ServiceClient ($ecrclient);
+ log_message "Child finished servicing requests";
+ kill ("TERM", $$);
+ exit;
+ } # if
+ } else {
+ ServiceClient ($ecrclient);
+ } # if
+ } # while
+
+ close ($ecrserver);
+} # GetRequest
+
+sub ProcessRequests {
+ # The subroutine handles processing of requests by using a socket to
+ # communicate with clients.
+ $ecrserver = IO::Socket::INET->new (
+ Proto => 'tcp',
+ LocalPort => $port,
+ Listen => SOMAXCONN,
+ Reuse => 1
+ );
+
+ die "$me: Error: Could not create socket ($!)\n" unless $ecrserver;
+
+ verbose "ECR DB Server (ecrd V$ecrdversion) accepting clients on port $port";
+
+ GetRequest;
+} # ProcessRequests
+
+sub ReturnAllECRNbrs {
+ my $ecrclient = shift;
+
+ my $statement = "select pkey from defect";
+
+ my $sth = $DB->prepare ($statement)
+ or DBError "Unable to prepare statement", $statement;
+
+ $sth->execute ()
+ or DBError "Unable to execute statement", $statement;
+
+ log_message "Returning all ECR numbers...";
+ while (my @row = $sth->fetchrow_array) {
+ SendClientResponse ($ecrclient, $row [0]);
+ } # while
+
+ log_message "All ECR numbers returned";
+} # ReturnAllECRNbrs
+
+# Start main code
+# Reopen STDOUT.
+open STDOUT, ">-" or die "Unable to reopen STDOUT\n";
+
+# Set unbuffered output
+$| = 1;
+
+while ($ARGV [0]) {
+ if ($ARGV [0] eq "-D") {
+ $daemon_mode = 1;
+ } elsif ($ARGV [0] eq "-v") {
+ $verbose = 1;
+ undef ($quiet_mode);
+ } elsif ($ARGV [0] eq "-d") {
+ $debug = 1;
+ undef ($quiet_mode);
+ } elsif ($ARGV [0] eq "-m") {
+ $multithreaded = 1;
+ } elsif ($ARGV [0] eq "-q") {
+ $quiet_mode = 1;
+ undef ($verbose);
+ } elsif ($ARGV [0] eq "-p") {
+ shift @ARGV;
+ Usage "Must specify a port # after -p" if (!defined $ARGV [0]);
+ $port = $ARGV[0];
+ } else {
+ Usage "Unknown parameter found: " . $ARGV[0];
+ } # if
+
+ shift @ARGV;
+} # while
+
+my $tmp = (!defined $ENV {TMP}) ? "/tmp" : $ENV {TMP};
+my $ecrd_logfile = "$tmp/$me.log";
+my $ecrd_errfile = "$tmp/$me.err";
+
+EnterDaemonMode ($ecrd_logfile, $ecrd_logfile) if defined ($daemon_mode);
+
+OpenDB;
+
+# Set up signal handlers
+$SIG{ALRM} = \&timeout;
+$SIG{INT} = $SIG{QUIT} = \&interrupt;
+
+ProcessRequests;