process_command.cc
// $Id: process_command.cc,v 2.5 2002/05/19 00:46:41 vickery Exp $
/* Parse and execute a command line. Manages interactions with the
* client during execution.
*
* Functions:
* do_exit The exit builtin command.
* do_shut The shut(down) builtin command.
* do_chdir The cd builtin command.
* do_echo The echo builtin command.
*
* processCommandLine Process a command line.
*
* $Log: process_command.cc,v $
* Revision 2.5 2002/05/19 00:46:41 vickery
* Close stdin pipe when getting zero byte message from client.
* But child executing the command gets eof after just line is
* written to the pipe.
*
* Revision 2.4 2002/05/19 00:20:41 vickery
* Deleted a debugging comment.
*
* Revision 2.3 2002/05/18 22:16:59 vickery
* Implemented forked builtin and external commands, including
* parenting acting as liason between child and client.
*
* Revision 2.2 2002/05/14 04:20:30 vickery
* Added sending SC_SHUT message to client in do_shut.
*
* Revision 2.1 2002/05/12 00:50:07 vickery
* Initial version.
* Handles exit, shutdown, chdir as non-forking builtins.
* Recognizes echo as forking builtin, but does not fork.
* Treats all other commands as "Unrecognized."
*
*/
#include <fcntl.h>
#include <sys/poll.h>
#include "qserver.h"
// Globals
// ------------------------------------------------------------------
extern char *server_home;
extern char *client_ident;
extern int command_pid;
extern int command_status;
extern vector<const char *> stdin_vec; // Typeahead buffer
static vector<const char *> token_vec;
// Builtin Commands
// ==================================================================
typedef int builtin_func_t( int, int, const char *[] );
static builtin_func_t do_exit, do_shut, do_chdir, do_echo;
struct dispatch_t
{
const char *func_name;
const builtin_func_t *func_ptr;
};
dispatch_t no_fork[] =
{
{ "exit", do_exit },
{ "q", do_exit },
{ "shut", do_shut },
{ "shutdown", do_shut },
{ "cd", do_chdir },
};
const int num_no_fork = sizeof( no_fork ) / sizeof( dispatch_t );
dispatch_t do_fork[] =
{
{ "echo", do_echo },
{ "print", do_echo },
};
const int num_do_fork = sizeof( do_fork ) / sizeof( dispatch_t );
// No-fork Builtins
// ==================================================================
// do_exit()
// ------------------------------------------------------------------
/*
* Exit the manager, which will break the connection to the client.
* User may specify the exit code on the command line.
*/
int
do_exit( int fd, int argc, const char *argv[] )
{
int exit_code = 0;
if ( argc > 1 )
{
exit_code = strtoul( argv[ 1 ], 0, 0 );
}
_exit( exit_code );
}
// do_shut()
// ------------------------------------------------------------------
/*
* Exit the manager, indicating to the server that it is to
* shutdown.
*/
int
do_shut( int fd, int argc, const char *argv[] )
{
send_msg( fd, SC_SHUT, "Shutting down the server.n" );
_exit( SHUTDOWN_EXIT );
}
// do_chdir()
// ------------------------------------------------------------------
/*
* Change the current working directory.
*
* If no arguments, change to the server's startup directory
* (server_home).
*/
int
do_chdir( int fd, int argc, const char *argv[] )
{
int result = 0;
if ( argc == 1 )
{
result = chdir( server_home );
}
else
{
result = chdir( argv[ 1 ] );
}
if ( result < 0 )
{
send_msg( fd, SC_STDERR, strerror( errno ) );
return 1;
}
return 0;
}
// Forkable Builtins
// ==================================================================
// do_echo()
// ------------------------------------------------------------------
/*
* Write command line arguments to stdout.
* Return the number of command line arguments as a development
* aid.
*/
int
do_echo( int fd, int argc, const char *argv[] )
{
for ( int i = 1; i < argc; i++ )
{
printf( "%s ", argv[ i ] );
}
printf( "n" );
fflush( stdout );
return argc;
}
// processCommandLine()
// ==================================================================
/*
* Parse and execute a command line.
*
* Arguments: client_fd Socket connection to the client.
* cmd_line The command line to be processed.
*/
void
processCommandLine( int client_fd, const char *cmd_line )
{
char msg_buf[ MESSAGE_MAX ];
char *msg_body = 0;
msg_header_t msg_header;
int result = 0;
// Tokenize the command line into an argument vector
vector<const char *>::iterator vi;
token_vec.erase( token_vec.begin(), token_vec.end() );
char buf[ strlen( cmd_line) + 1 ];
strcpy( buf, cmd_line );
const char *token = strtok( buf, " tnr" );
while ( token != 0 )
{
token_vec.insert( token_vec.end(), token );
token = strtok( 0, " tnr" );
}
int num_tokens = token_vec.size();
if ( num_tokens == 0 )
return;
// Build the argument vector, removing I/O redirection operators
// and operands.
const char *redirect_stdin = 0;
const char *redirect_stdout = 0;
const char *redirect_stderr = 0;
const char *arg_vector[ num_tokens + 1 ];
int arg_count = 0;
for ( vi = token_vec.begin(); vi < token_vec.end(); vi++ )
{
// Check for redirection operators
int redirect_type = -1;
if ( 0 == strcmp( *vi, "<" ) )
redirect_type = 0;
if ( 0 == strcmp( *vi, ">" ) )
redirect_type = 1;
if ( 0 == strcmp( *vi, "2>" ) )
redirect_type = 2;
if ( redirect_type >= 0 )
{
if ( ++vi == token_vec.end() )
{
send_msg( client_fd,
SC_STDERR, "Missing redirection operandn" );
return;
}
switch ( redirect_type )
{
case 0:
redirect_stdin = *vi;
break;
case 1:
redirect_stdout = *vi;
break;
case 2:
redirect_stderr = *vi;
break;
default:
fprintf( stderr, "Bad switch at %s line %dn",
__FILE__, __LINE__ );
exit( 1 );
}
}
else
{
// No redirection for this token; add it to arg vector.
arg_vector[ arg_count++ ] = *vi;
}
}
// Add null pointer to end of argument vector
arg_vector[ arg_count ] = 0;
// Execute non-forking builtins.
for ( int i = 0; i < num_no_fork; i++ )
{
if ( 0 == strcmp( arg_vector[ 0 ], no_fork[ i ].func_name ) )
{
command_status =
no_fork[ i ].func_ptr( client_fd, arg_count, arg_vector );
return;
}
}
// Not a non-forking builtin, so the command, whether builtin or
// external, requires a separate process.
// Create pipes for child's stdin/stdout/stderr
int stdin_pipe[2], stdout_pipe[2], stderr_pipe[2];
if ( pipe( stdin_pipe ) < 0 )
{
perror( "stdin_pipe" );
exit( 1 );
}
if ( pipe( stdout_pipe ) < 0 )
{
perror( "stdout pipe" );
exit( 1 );
}
if ( pipe( stderr_pipe ) < 0 )
{
perror( "stderr pipe" );
exit( 1 );
}
// Fork child to execute the command
// ----------------------------------------------------------------
command_pid = fork();
switch ( command_pid )
{
// Error
// --------------------------------------------------------------
case -1:
perror( "fork a command" );
exit( 1 );
case 0:
// Child Process
// ============================================================
// Child: Clean up pipes
// ------------------------------------------------------------
close( stdin_pipe [ 1 ] );
close( stdout_pipe[ 0 ] );
close( stderr_pipe[ 0 ] );
// Child: Connect stdin/out/err to pipe or file.
// ------------------------------------------------------------
if ( redirect_stdin != 0 )
{
// Stdin redirection; don't need stdin pipe at all.
close( stdin_pipe[ 0 ] );
// Open the input file.
result = open( redirect_stdin, O_RDONLY );
if ( result < 0 )
{
// Input redirection failed.
sprintf( msg_buf,
"Input redirection: %sn", strerror( errno) );
send_msg( client_fd, SC_STDERR, msg_buf );
exit( 1 );
}
if ( dup2( result, STDIN_FILENO ) < 0 )
{
perror( "dup2 redirect to stdin" );
exit( 1 );
}
if ( close( result ) < 0 )
{
perror( "close stdin" );
exit( 1 );
}
}
else
{
// Stdin not redirected; connect it to its pipe.
if ( dup2( stdin_pipe[ 0 ], STDIN_FILENO ) < 0 )
{
perror( "dup2 pipe to stdin" );
exit( 1 );
}
}
if ( redirect_stdout != 0 )
{
// Stdout redirection; don't need stdout pipe at all.
close( stdout_pipe[ 1 ] );
// Open the output file (No Clobber!)
result = open( redirect_stdout,
O_WRONLY | O_CREAT | O_EXCL, 0666 );
if ( result < 0 )
{
// Output redirection failed.
sprintf( msg_buf,
"Output redirection: %sn", strerror( errno) );
send_msg( client_fd, SC_STDERR, msg_buf );
exit( 1 );
}
if ( dup2( result, STDOUT_FILENO ) < 0 )
{
perror( "dup2 redirect to stdout" );
exit( 1 );
}
if ( close( result ) < 0 )
{
perror( "close stdout" );
exit( 1 );
}
}
else
{
// Stdout not redirected; connect it to its pipe.
if ( dup2( stdout_pipe[ 1 ], STDOUT_FILENO ) < 0 )
{
perror( "dup2 pipe to stdout" );
exit( 1 );
}
}
if ( redirect_stderr != 0 )
{
// Stderr redirection; don't need stderr pipe at all.
close( stderr_pipe[ 1 ] );
// Open the output file
int mode = O_WRONLY | O_CREAT;
if ( redirect_stdout &&
( strcmp( redirect_stdout, redirect_stderr ) != 0 ) )
{
mode |= O_EXCL; // Not shared with stdout
}
result = open( redirect_stderr, mode , 0666 );
if ( result < 0 )
{
// Error redirection failed.
sprintf( msg_buf,
"Error redirection: %sn", strerror( errno) );
send_msg( client_fd, SC_STDERR, msg_buf );
exit( 1 );
}
if ( dup2( result, STDERR_FILENO ) < 0 )
{
perror( "dup2 redirect to stderr" );
exit( 1 );
}
if ( close( result ) < 0 )
{
perror( "close stderr" );
exit( 1 );
}
}
else
{
if ( dup2( stderr_pipe[ 1 ], STDERR_FILENO ) < 0 )
{
perror( "dup2 pipe to stderr" );
exit( 1 );
}
}
// Child: Execute do-fork builtins
// ------------------------------------------------------------
for ( int i = 0; i < num_do_fork; i++ )
{
if ( 0 == strcmp( arg_vector[ 0 ], do_fork[ i ].func_name ) )
{
command_status =
do_fork[ i ].func_ptr( client_fd, arg_count, arg_vector );
_exit( command_status );
}
}
// Child: Execute external command.
// ------------------------------------------------------------
execvp( arg_vector[ 0 ], (char **)arg_vector );
perror( "execvp failed" );
// Child: Give up.
// ------------------------------------------------------------
send_msg( client_fd, SC_STDERR, "Unrecognized command.n" );
_exit( 1 );
default:
// Parent Process
// ============================================================
// Parent: Clean up pipes
// ------------------------------------------------------------
close( stdin_pipe [ 0 ] );
close( stdout_pipe[ 1 ] );
close( stderr_pipe[ 1 ] );
// Parent: Manage std out/err and client (stdin) messages
// ------------------------------------------------------------
if ( ! redirect_stdin )
{
// Write anything in the typeahead buffer to the child's
// stdin.
vector<const char *>::iterator it = stdin_vec.begin();
while ( it != stdin_vec.end() )
{
result = write( stdin_pipe[ 1 ], *it, strlen( *it ) );
stdin_vec.erase( it++ );
}
}
// Structs for polling.
struct pollfd poll_list[ 3 ];
int nfds, client_index, stdout_index, stderr_index;
// Event Loop: Read from client, child stdout, and/or child
// stderr, and write to child stdin and/or client. Continue
// until child's stdout and stderr are both closed.
bool stdout_isalive = ! redirect_stdout;
bool stderr_isalive = ! redirect_stderr;;
while ( stdout_isalive || stderr_isalive )
{
// Initialize the pollfd structs.
client_index = stdout_index = stderr_index = -1;
nfds = 0;
// Always accept input from client
poll_list[nfds].fd = client_fd;
poll_list[nfds].events = POLLIN;
poll_list[nfds].revents = 0;
client_index = nfds++;
// Accept input from stdout if it wasn't redirected and
// hasn't closed yet.
if ( stdout_isalive )
{
poll_list[nfds].fd = stdout_pipe[ 0 ];
poll_list[nfds].events = POLLIN;
poll_list[nfds].revents = 0;
stdout_index = nfds++;
}
// Accept input from stderr if it wasn't redirected and
// hasn't closed yet.
if ( stderr_isalive )
{
poll_list[nfds].fd = stderr_pipe[ 0 ];
poll_list[nfds].events = POLLIN;
poll_list[nfds].revents = 0;
stderr_index = nfds++;
}
// Wait for input.
int poll_result = 0;
poll_result = poll( poll_list, nfds, -1 );
if ( poll_result < 0 )
{
perror( "poll" );
if ( errno == EINTR )
{
// Ignore calls to poll that were caused by being
// interrupted.
continue;
}
else
{
exit( 1 );
}
}
// Check if this was a timeout.
// Will occur only if last arg to poll() was > 0.
if ( 0 == poll_result )
{
continue;
}
// Read from each active source, and generate messages
// accordingly.
for (int source = 0; source < nfds; source++ )
{
if ( poll_list[source].revents & POLLIN )
{
// Receive and process a message from the client.
if ( source == client_index )
{
result = recv_msg( client_fd, msg_header, &msg_body );
if ( (unsigned ) msg_header.msg_length ==
strlen( msg_body ) )
{
switch ( msg_header.msg_type )
{
case CS_STDIN:
if ( 0 == msg_header.msg_length )
{
close( stdin_pipe[ 1 ] ); // Indicate eof
}
else
{
// Ignore error writing; client may have exited.
result = write( stdin_pipe[ 1 ], msg_body,
msg_header.msg_length );
}
delete [] msg_body;
break;
default:
fprintf( stderr, "*** Received message type %d "
"while waiting for stdin from client %sn",
msg_header.msg_type, client_ident );
exit( 1 );
break;
}
}
continue;
}
// Process input from child's stdout or stderr.
memset( msg_buf, '0', sizeof( msg_buf ) );
result = read( poll_list[source].fd, msg_buf,
sizeof( msg_buf ) );
if ( result > 0 )
{
int32_t msg_type =
( source == stderr_index ) ? SC_STDERR : SC_STDOUT;
send_msg( client_fd, msg_type, msg_buf );
}
else
{
// Error or end of file from stdout or stderr.
if ( source == stdout_index )
stdout_isalive = false;
else
stderr_isalive = false;
}
}
else
{
if ( source == stdout_index )
stdout_isalive = false;
else if ( source == stderr_index )
stderr_isalive = false;
}
}
}
return;
}
// Should not fall out of fork() switch.
fprintf( stderr, "Invalid switch at %s line %dn",
__FILE__, __LINE__ );
exit( 1 );
}