qserver.cc
// $Id: qserver.cc,v 2.6 2002/05/18 22:13:02 vickery Exp $
/*
* This is the server's main module for the qrsh project
* developed in CS-701 Spring 2002.
*
* Functions: signal_handler() Handle signals.
* do_exit() Do orderly shutdown.
* main() Main function.
*
* $Log: qserver.cc,v $
* Revision 2.6 2002/05/18 22:13:02 vickery
* Reformatted two of the messages written to the log file.
*
* Revision 2.5 2002/05/14 04:17:54 vickery
* Changed the signal for killing managers from SIGTERM
* to SIGUSR1.
*
* Revision 2.4 2002/05/12 00:47:43 vickery
* Introduced support for client manager processes.
*
* Revision 2.3 2002/05/07 05:07:55 vickery
* Snapshot: User authentication and prompt string processing.
*
* Revision 2.2 2002/05/06 06:33:21 vickery
* Introduced the message passing protocol. Send SC_LOGIN
* message when client connects.
*
* Revision 2.1 2002/04/25 03:06:38 vickery
* First revision for Assignment 4.
*
* Revision 1.3 2002/03/31 06:25:00 vickery
* Added a check for dangling command line options.
*
* Revision 1.2 2002/03/28 19:57:04 vickery
* Added support for SIGTERM as well as SIGINT. This code now
* implements Assignment 3.
*
* Revision 1.1 2002/03/23 04:56:33 vickery
* Initial revision
*
*
*/
#include "qserver.h"
// Globals
// ------------------------------------------------------------------
/*
* Constants
*/
static const int BACKLOG = 5;
static const char* VERSION = "$Revision: 2.6 $";
static const pid_t server_pid = getpid();
/*
* Variables initialized by main and never changed.
*/
char user_name[ USERNAME_MAX ];
char host_name[ HOSTNAME_MAX ];
char login_msg[ MESSAGE_MAX ];
char server_home[ PATHNAME_MAX ];
/*
* The set of client manager processes
*/
vector<pid_t> client_managers;
// Command line options
// ------------------------------------------------------------------
static const char* usageMsg = "Usage: qserver [options]n"
" Options:n"
" -p --port <number> Port number (default is 0x8000)n"
" -l --logfile <file> Log file name (default is ./qserver.log)n"
" -o --overwrite Overwrite logfile (default is to append)n"
" -v --version Display version number, and exitn"
" -h --help Display this message, and exitn";
static const char* short_options = "p:l:ovh?";
static struct option long_options[] =
{
{ "port", required_argument, 0, 'p' },
{ "logfile", required_argument, 0, 'l' },
{ "overwrite", no_argument, 0, 'o' },
{ "version", no_argument, 0, 'v' },
{ "help", no_argument, 0, 'h' },
{ 0, 0, 0, 0 },
};
// signal_handler()
// ------------------------------------------------------------------
/*
* Handle signals.
*
* SIGCHLD: If exit status is 0, remove the process from the
* client_managers vector.
* If exit status is SHUTDOWN_EXIT, shut down.
*
* SIGTERM: Shut down.
* SIGINT: Shut down.
*/
void
signal_handler( int signum )
{
int status = 0;
char msg_buf[ MESSAGE_MAX ];
pid_t which_child = 0;
vector<pid_t>::iterator it = 0;
switch ( signum )
{
case SIGCHLD:
// A manager process has terminated.
which_child = wait( &status );
if ( WIFEXITED(status) )
{
sprintf( msg_buf, "MANAGER (%d) EXITED", which_child );
writeLog( msg_buf, 0 );
// Remove this child from the list of client managers.
it = find( client_managers.begin(), client_managers.end(),
which_child );
if ( it != client_managers.end() )
{
client_managers.erase( it );
}
else
{
fprintf( stderr, "SIGCHLD but %d is not a managern",
which_child );
exit ( 1 );
}
// If this is a shutdown event, tell all the other managers
// to shut down now.
if ( ( (0x0FF & WEXITSTATUS(status)) ==
(0x0FF & SHUTDOWN_EXIT) )
)
{
sprintf( msg_buf, "SHUTDOWN_EXIT BY %d", which_child );
writeLog( msg_buf, 0 );
signal( SIGCHLD, SIG_IGN );
it = client_managers.begin();
while ( it != client_managers.end() )
{
sprintf( msg_buf, " Killing process %d", *it );
writeLog( msg_buf, 0 );
kill( *it, SIGUSR1 );
waitpid( *it, 0, 0 );
sprintf( msg_buf, " Process %d exited", *it );
writeLog( msg_buf, 0 );
it++;
}
exit( 0 ); // Shut down server.
}
break; // Not a shutdown exit
}
// Error: Manager stopped or died.
if ( WIFSIGNALED(status) )
{
sprintf( msg_buf, "*** Manager %d received signal %d ***",
which_child, WTERMSIG(status));
writeLog( msg_buf, 0 );
break;
}
if ( WIFSTOPPED(status) )
{
sprintf( msg_buf, "*** Manager %d received stop %d ***",
which_child, WSTOPSIG(status));
writeLog( msg_buf, 0 );
break;
}
sprintf( msg_buf, "*** Manager %d exit unknown %d ***",
which_child, status );
writeLog( msg_buf, 0 );
break;
case SIGINT:
case SIGTERM:
exit( SIGNAL_EXIT );
break;
default:
sprintf( msg_buf,
"*** UNEXPECTED SIGNAL %d ***", signum );
writeLog( msg_buf, 0 );
exit( SIGNAL_EXIT );
}
}
// do_exit()
// ------------------------------------------------------------------
/*
* Called when a signal arrives or on normal exit.
* Do orderly shutdown of server.
*/
void
do_exit( const int code, const char *reason )
{
char msg_buf[ MESSAGE_MAX ];
pid_t exiting_pid = getpid();
if ( server_pid == exiting_pid )
{
// The server (main) process is exiting.
switch ( code )
{
case SIGNAL_EXIT:
closeLog( "Signal Received" );
break;
default:
closeLog( reason );
break;
}
}
else
{
// A manager process is exiting.
sprintf( msg_buf, "*** do_exit: Unexpected process (%d)",
exiting_pid );
writeLog( msg_buf, 0 );
}
}
// main()
// ------------------------------------------------------------------
/*
* Process command line arguments, open a log file, become a
* daemon, accept connections from clients, and for a process for
* each client.
*
* Arguments
*
* The standard arguments to main().
*
*/
int
main(int argc, char *argv[], char *envp[] )
{
// Get cwd and "server_home"
getcwd( server_home, sizeof( server_home ) );
// Determine user name for authentication
struct passwd *passwd_ptr = getpwuid( geteuid() );
if ( 0 == passwd_ptr )
{
perror( "username" );
exit( 1 );
}
strcpy( user_name, passwd_ptr->pw_name );
// Process command line options
int wellKnownPort = 0x8000;
char logfilePath[ PATH_MAX ];
bool overwriteLogfile = false;
// Specify directory for logfile if not given
if ( (*argv[0] != '.') && (*argv[0] != '/') )
{
strcpy( logfilePath, "./" );
}
else
{
strcpy( logfilePath, "" );
}
strcat( logfilePath, argv[0] );
strcat( logfilePath, ".log" );
int c;
while ( -1 !=
( c = getopt_long( argc,
argv,
short_options,
long_options, 0 ) ) )
{
switch ( c )
{
case 'p':
wellKnownPort = strtol( optarg, 0, 0);
if ( ( wellKnownPort < 0 ) ||
( (wellKnownPort < 1024) && (geteuid() != 0) ) )
{
fprintf( stderr, "Bad port number: %sn", optarg );
exit( 1 );
}
break;
case 'l':
strncpy( logfilePath, optarg, sizeof( logfilePath ) -1 );
break;
case 'o':
overwriteLogfile = true;
break;
case 'v':
printf( "%s: %sn", argv[0], VERSION );
exit( 0 );
case '?':
case 'h':
default:
fprintf( stderr, usageMsg );
exit( 1 );
}
}
// Check for dangling command line options.
if ( optind < argc )
{
fprintf( stderr, "unexpected value: %sn", argv[ optind ] );
fprintf( stderr, usageMsg );
exit( 1 );
}
// Create and bind this server's well-known socket
// ----------------------------------------------------------------
struct hostent *hp;
int server_fd, client_fd;
struct sockaddr_in server_sockaddr, client_sockaddr;
// Get local host's name and IP address
if ( gethostname( host_name, HOSTNAME_MAX ) < 0)
{
perror( "gethostname" );
exit( EXIT_FAILURE );
}
if ((hp = gethostbyname( host_name ) ) == NULL)
{
fprintf( stderr,
"qserver: cannot get local host informationn" );
exit( EXIT_FAILURE );
}
// Initialize local sockaddr struct.
memset( &server_sockaddr, 0, sizeof( server_sockaddr ) );
server_sockaddr.sin_family = AF_INET;
memcpy( &server_sockaddr.sin_addr, hp->h_addr_list[0], hp->h_length );
server_sockaddr.sin_port = htons( wellKnownPort );
// Create and bind the well-known socket
if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
{
perror( "socket" );
exit( EXIT_FAILURE );
}
if ( bind( server_fd,
(struct sockaddr *) &server_sockaddr,
sizeof server_sockaddr) < 0)
{
perror( "bind" );
exit( EXIT_FAILURE );
}
listen( server_fd, BACKLOG );
// Log startup message.
int major = 0, minor = 0;
sscanf( VERSION, "$Revision: %d.%d ", &major, &minor );
sprintf( login_msg,
"QC Remote Shell version %d.%d running on %s port 0x%04Xn",
major, minor, host_name, wellKnownPort );
openLog( logfilePath, login_msg, overwriteLogfile );
// Set up exit processing
on_exit( do_exit, (char *) "Normal Exit" );
signal( SIGINT, signal_handler );
signal( SIGTERM, signal_handler );
signal( SIGCHLD, signal_handler );
// Event Loop
// ----------------------------------------------------------------
while ( true )
{
// Accept a connection
socklen_t saSize = sizeof client_sockaddr;
client_fd = accept( server_fd,
(struct sockaddr *) &client_sockaddr, &saSize );
if (client_fd < 0)
{
perror( "accept" );
exit( 1 );
}
// Log the connection
hp = gethostbyaddr( (const char *)&client_sockaddr.sin_addr,
4, AF_INET );
// Create a process to manage this client.
pid_t client_pid = fork();
switch ( client_pid )
{
case -1:
// Error forking.
writeLog( "fork client manager", strerror( errno ) );
exit( 1 );
break;
case 0:
// Child: Manage client.
manageClient( client_fd, hp->h_name );
fprintf( stderr, "Unexpected return from manageClient "
"at %s line %dn", __FILE__, __LINE__ );
exit( 1 );
break;
default:
// Parent: Do housekeeping, and continue.
close( client_fd );
client_managers.insert( client_managers.end(), client_pid );
char msg_buf[ MESSAGE_MAX ];
sprintf( msg_buf, "%s(%d)", hp->h_name, client_pid );
writeLog( "CONNECT FROM", msg_buf );
break;
}
}
}