Linux shell that executes commands in parallel
Process Control
This assignment involves creating a parallel shell tool called PELL. It provides users of pell with the ability to execute commands, execute multistep processes connected by pipes, and execute concurrent independent processes.
PELL supports the following major commands:
conccmd1 args1 , cmd2 args2 , …
conc causes PELL to execute the commands concurrently (i.e., in parallel). There is no communication between the commands, these simply happen in parallel. fork each of the children. The maximum number of commands is 5. If any of the commands redirect input or output, you must do the redirection after forking, but before execing. The getCommands function has been provided to simplify the process of getting the command arguments and redirection values.
Example: conc ls –l /bin > lsOne.txt , ls –l /usr/bin > lsTwo.txt , ls –l /etc > lsThree.txt
- Each of the ls commands are executing in parallel.
- The conc command prints each of the parallel commands showing the parent’s process Id, child’s process ID, the command, and command arguments. Your actual PID values will be different.
33009 33011: ls –l /bin
33009 33012: ls –l /usr/bin
33009 33013: ls –l /etc
That output is written to stderr to not interfere with stdout.
- Since there are three commands, PELL has to create three children, redirect stdout for each child, and execvp to the particular command for each child.
pipecmd1 args1 , cmd2 args2
pipe causes PELL to create a pipe and fork a child for each cmdi. There are only two commands. cmd1 can have stdin redirected from a file. cmd2 can have stdout redirected to a file.
You will have to use dup2 to redirect the pipes.
Example: pipe ls –l Data , sort –k5 -n > sort.out
- The pipe command prints each step showing a sequence, parent’s process ID the child’s process ID, and its command
1 33043 33045: ls –l Data
2 33043 33046: sort –k5 –n
- Since there are two commands, PELL has to create one pipe, and two children. The pipe is the output for step 1 and the input for step 2.
Solution
cs3423p8.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdarg.h>
#include <errno.h>
#include <string.h>
#include <sys/wait.h>
#include <unistd.h>
#include <fcntl.h>
#include “cs3423p8.h”
int concCmd(Cmd cmdM[], int iCmdCnt, Token tokenM[], int iTokenCnt)
{
int i, j;
printf(“Commands: %d, Tokens: %d\n”, iCmdCnt, iTokenCnt);
printf(“Cmd: %s\n”, cmdM[0].szCmdNm);
printf(“Command ArgBeg ArgEnd Redirect(in/out) -> Path\n”);
for (i = 0; i < iCmdCnt; i += 1)
{
printf(“%5s %6s %11s %5d / %2d ->%20s\n”, cmdM[i].szCmdNm, tokenM[cmdM[i].iBeginIdx], tokenM[cmdM[i].iEndIdx], cmdM[i].iStdinRedirectIdx,
cmdM[i].iStdoutRedirectIdx, tokenM[cmdM[i].iStdoutRedirectIdx]);
}
// start code
long lForkPid;
long lWaitPid;
int iExitStatus = 0;
char szInput[20];
char *execArgv[20];
// create a child process
lForkPid = fork();
// Both the parent and child continue here
switch (lForkPid)
{
case -1:
errExit(“fork failed: %s”, strerror(errno));
break;
case 0: // child process
printf(“Child Process: PID=%ld, PPID=%ld\n”, (long) getpid(), (long) getppid());
// Redirect stdout to file
int file = open(“./Dout/myfile.txt”, O_CREAT | O_WRONLY, 0666); // output file
if (file < 0)
{
printf(“File open error\n”);
return 1;
}
//Now we redirect standard output to the file using dup2
if (dup2(file, 1) < 0)
{
printf(“Dup error exit\n”);
return 1;
}
// invoke a different executable for the child
execArgv[0] = “ls”; // command
execArgv[1] = “-l”; // flag list
execArgv[2] = NULL; // null
execvp(“ls”, execArgv); // command
close(file);
errExit(“Child process failed to exec: %s”, strerror(errno));
break;
default: // parent process
lWaitPid = wait(&iExitStatus);
if (lWaitPid == -1)
errExit(“wait error: %s”, strerror(errno));
printf(“Parent Process: PID=%ld, PPID=%ld\n”, (long) getpid(), (long) getppid());
printf(“Parent Process: my child’s PID=%ld\n”, lForkPid);
printf(“Parent Process: wait pid=%ld\n”, lWaitPid);
printf(“Parent Process: exit status=%d\n”, iExitStatus);
}
printf(“My PID=%ld\n”, (long) getpid());
return 0;
}
int pipeCmd(Cmd cmdM[], int iCmdCnt, Token tokenM[], int iTokenCnt)
{
return 0;
}
void errExit(const char szFmt[], …)
{
va_list args; // This is the standard C variable argument list type
va_start(args, szFmt); // This tells the compiler where the variable arguments
// begins. They begin after szFmt.
printf(“ERROR: “);
vprintf(szFmt, args); // vprintf receives a printf format string and a
// va_list argument
va_end(args); // let the C environment know we are finished with the
// va_list argument
printf(“\n”);
exit(ERROR_PROCESSING);
}
cs3423p8.h
#ifndef CS3423P8_H_
#define CS3423P8_H_
/**********************************************************************
cs3423p8.h
Purpose:
Defines constants for
boolean values
maximum sizes
Defines typedefs for
Cmd – describes a command
Token – character string token
Prototypes
Notes:
**********************************************************************/
#define TRUE 1
#define FALSE 0
#define MAX_COMMANDS 5
#define MAX_TOKEN_SZ 100
#define MAX_PATH 500
#define MAX_TOKENS 30
#define MAX_ARGS 6
#define ERROR_PROCESSING 99
// Cmd – represents a command, its list of arguments and
// subscripts into the token array for redirection of stdin and/or stdout
typedef struct Cmd
{
int iBeginIdx; // Beginning subscript in tokenM for first arg
// (this will be 0 when there aren’t any arguments)
int iEndIdx; // Ending subscript in tokenM for last arg. If
// there is redirection, this subscript would be
// before that. (this is -1 for no arguments)
char szCmdNm[MAX_TOKEN_SZ + 1]; // command name (e.g., ls)
int iStdinRedirectIdx; // Subscript in tokenM for the stdin redirect; 0 for no redirect
int iStdoutRedirectIdx; // Subscript in tokenM for the stdout redirect; 0 for no redirect
} Cmd;
typedef char Token[MAX_TOKEN_SZ + 1];
int concCmd(Cmd cmdM[], int iCmdCnt, Token tokenM[], int iTokenCnt);
int pipeCmd(Cmd cmdM[], int iCmdCnt, Token tokenM[], int iTokenCnt);
void errExit(const char szFmt[], … );
#endif /* CS3423P8_H_ */
cs3423p8Driver.c
/**********************************************************************
Purpose:
This driver reads a stream input file to receive commands which
will exercise the student’s concCmd and pipeCmd functions.
Command Parameters:
pell < commandFile
Input:
Lines of text containing conc or pipe commands:
conc cmd1 args1 , cmd2 args2 , …
conc causes PELL to execute the commands concurrently (i.e., parallel).
There is no communication between the commands, these simply happen
concurrently (in parallel).
pipe cmd1 args1 , cmd2 args2
pipe causes PELL to create a pipe and fork a child for each cmdi.
The pipe is the output for step 1 and the input for step 2. Also,
cmd1 can have stdin redirected from a file. cmd2 can have stdout
redirected to a file.
Results:
For each command read from stdin:
– Prints the tokens
– Prints the command information
– Depending on the command:
concCmd:
– prints the parent PID, child PID, and the command
Example:
33009 33011: ls -l /bin > lsOne.txt
33009 33012: ls -l /usr/bin > lsTwo.txt
33009 33013: ls -l /etc > lsThree.txt
pipeCmd, for each child:
– prints step, parent PID, child Pid, and the command
Example:
1 33043 33045: ls -l Data
2 33043 33046: sort -k5 -n
Notes:
We print the parent PID and child PID information to stderr to not
interfere with stdout.
**********************************************************************/
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include “cs3423p8.h”
#define MAX_BUFFER_SZ 500
void processCommands(FILE *pfileCommand);
void prtCmdList(Cmd cmdM[], int iCmdCnt);
int getCmdList(Cmd cmdM[], Token tokenM[], int iTokenCnt);
int split(Token tokenM[], int iMaxToken, char szInput[], char cDelim);
int main(int argc, char *argv[])
{
// We don’t expect a command argument
if (argc > 1)
errExit(“Usage: pell < inputFile”);
processCommands(stdin);
return 0;
}
/******************** processCommands **************************************
void processCommands(FILE *pfileCommand)
Purpose:
Reads the Command file to process commands. There are several types of
records (see the program header for more information).
Parameters:
I FILE *pfileCommand command stream input
Notes:
This calls:
split
getCmdList
concCmd
pipeCmd
**************************************************************************/
void processCommands(FILE *pfileCommand)
{
// variables for command processing
char szInputBuffer[MAX_BUFFER_SZ + 1]; // input buffer for a single text line
// variables for tokenizing
int iTokenCnt;
Token tokenM[MAX_TOKENS];
// variables to understand the commands in the input text
int iCmdCnt;
Cmd cmdM[MAX_COMMANDS];
// misc
int rc;
// get command data until EOF
while (fgets(szInputBuffer, MAX_BUFFER_SZ, pfileCommand) != NULL)
{
// if the line is just a line feed, ignore it
if (szInputBuffer[0] == ‘\n’)
continue;
// see if the command is a comment
if (szInputBuffer[0] == ‘*’)
{
printf(“%s”, szInputBuffer);
continue; // it was just a comment
}
printf(“>>> %s”, szInputBuffer);
// split the line based on spaces
iTokenCnt = split(tokenM, MAX_TOKENS, szInputBuffer, ‘ ‘);
// print the tokens
int i;
printf(“%3s %s\n”, “Seq”, “Token”);
for (i = 0; i < iTokenCnt; i++)
printf(” %3d ‘%s’\n”, i, tokenM[i]);
if (iTokenCnt <= 0)
errExit(“Command was blank”);
// get the command list for this command
memset(cmdM, 0, sizeof(cmdM));
iCmdCnt = getCmdList(cmdM, tokenM, iTokenCnt);
prtCmdList(cmdM, iCmdCnt);
// process the particular command
if (strcmp(tokenM[0], “conc”) == 0)
{ // conc command
rc = concCmd(cmdM, iCmdCnt, tokenM, iTokenCnt);
if (rc != 0)
printf(“*** concCmd returned %d\n”, rc);
} else if (strcmp(tokenM[0], “pipe”) == 0)
{ // pipe command
rc = pipeCmd(cmdM, iCmdCnt, tokenM, iTokenCnt);
if (rc != 0)
printf(“*** pipeCmd returned %d\n”, rc);
} else
errExit(“Invalid command: ‘%s'”, tokenM[0]);
}
printf(“\n”); // good place for a breakpoint
}
/******************** split **************************************
int split(Token tokenM[], int iMaxToken, char szInput[], char cDelim)
Purpose:
Tokenizes the input text by splitting it on the specified
delimiter.
Parameters:
O Token tokenM[] array of tokens for the input test
I int iMaxToken the maximum number of characters in a token (not
including zero byte
I char szInput[] input text to be tokenized
I char cDelim delimiter character (e.g., ‘ ‘)
Returns:
Count of number of entries in tokenM.
Notes:
– Linefeed and ‘\0’ are also used as delimiters for the last token
– If we encounter two or more adjacnet delimiters, we ignore them.
– If a token is larger than the specified max token size, we
truncate the string.
**************************************************************************/
int split(Token tokenM[], int iMaxToken, char szInput[], char cDelim)
{
int i; // used to traverse the input text string
int iTokenBeg = 0; // subscript where the token begins
int iTokenEnd = -1; // subscript to the delimiter following the token
int iTokenIdx = 0; // where to place next token in tokenM
int iTokenSize; // size (in bytes) of the token without zero byte
// We will actually include touching the ‘\0’ or ‘\n’ since that will
// mark the end of the last token.
int iLen = strlen(szInput);
for (i = 0; i <= iLen; i += 1)
{
// Is it end of line, line feed or the delim?
if (szInput[i] == ‘\0’ || szInput[i] == ‘\n’ || szInput[i] == cDelim)
{
if (iTokenBeg == i)
{ // only a delimiter, nothing in token so ignore it
iTokenBeg = i + 1;
continue;
}
// see if the token is too long
if ((i – iTokenBeg) > iMaxToken)
iTokenSize = iMaxToken; // truncate it
else
iTokenSize = i – iTokenBeg;
memcpy(&tokenM[iTokenIdx][0], &szInput[iTokenBeg], iTokenSize);
tokenM[iTokenIdx][iTokenSize] = ‘\0’; // terminate it
iTokenIdx++;
iTokenBeg = i + 1;
}
}
return iTokenIdx;
}
/******************** gettCmdList **************************************
int getCmdList(Cmd cmdM[], Token tokenM[], int iTokenCnt)
Purpose:
Parse through the token array to determine the commands. It
saves the beginning and ending subscripts for each command’s arguments.
It also determines whether the command has a redirected stdin
and/or stdout.
Parameters:
O Cmd cmdM[] array of commands
I Token tokenM[] array of tokens for the input test
I int iTokenCnt number of entries in tokenM
Returns:
Count of number of entries in cmdM.
Notes:
– commands are separated by commas
**************************************************************************/
int getCmdList(Cmd cmdM[], Token tokenM[], int iTokenCnt)
{
int i; // subscript to current token
char cChar; // current character in input text
int iCmdCnt = 0; // count of number of entries in cmdM
// Iterate through the array of tokens. We actually
// go to one item beyond the end so that we can process
// the last token normally. (We pretend there is an
// ending token after the last token.)
for (i = 1; i <= iTokenCnt; i += 1)
{
Cmd *pCmd = &(cmdM[iCmdCnt]);
if (i == iTokenCnt)
cChar = ‘,’; // pretend an ending delim
else
cChar = tokenM[i][0];
switch (cChar)
{
case ‘,’: // delimiter between commands
if (pCmd->iBeginIdx == 0)
errExit(“no command, cmd arg: %d\n”, i);
// If we haven’t yet marked the end of the command’s
// arguments, assume it is right before the comma.
// Note that redirection also set the iEndIdx.
if (pCmd->iEndIdx == 0)
pCmd->iEndIdx = i – 1;
// Check for no command arguments
if (pCmd->iBeginIdx > pCmd->iEndIdx)
{ // no args
pCmd->iBeginIdx = 0;
pCmd->iEndIdx = -1;
}
iCmdCnt += 1;
break;
case ‘<‘: // stdin redirection
if (i + 1 >= iTokenCnt) // need another arg
errExit(“redirect requires additional arg, cmd arg: %d\n”, i);
pCmd->iStdinRedirectIdx = i + 1;
// If we haven’t yet marked the end of the command’s
// arguments, assume it is right before the <.
if (pCmd->iEndIdx == 0)
pCmd->iEndIdx = i – 1;
break;
case ‘>’:
if (i + 1 >= iTokenCnt) // need another arg
errExit(“redirect requires additional arg, cmd arg: %d\n”, i);
pCmd->iStdoutRedirectIdx = i + 1;
// If we haven’t yet marked the end of the command’s
// arguments, assume it is right before the >.
if (pCmd->iEndIdx == 0)
pCmd->iEndIdx = i – 1;
break;
default:
// check if at the beginning of the command
if (pCmd->iBeginIdx == 0)
{ // not comma, <, > if iBeginIdx is 0, we need to record
// where the arguments might begin
strcpy(pCmd->szCmdNm, tokenM[i]);
pCmd->iBeginIdx = i + 1;
}
}
}
return iCmdCnt;
}
/******************** prtCmdList **************************************
void prtCmdList(Cmd cmdM[], int iCmdCnt)
Purpose:
Prints information for each command in the list of commands.
Parameters:
I Cmd cmdM[] array of commands
int iCmdCnt count of number of entries in cmdM.
**************************************************************************/
void prtCmdList(Cmd cmdM[], int iCmdCnt)
{
int i;
printf(“%-20s %5s %-5s %-5s %-6s\n”, “Command”, “Begin”, “End”, “stdin”, “stdout”);
for (i = 0; i < iCmdCnt; i += 1)
{
printf(“%-20s %5d %5d %5d %6d\n”, cmdM[i].szCmdNm, cmdM[i].iBeginIdx, cmdM[i].iEndIdx, cmdM[i].iStdinRedirectIdx, cmdM[i].iStdoutRedirectIdx);
}
}