Linux shell that executes commands in parallel

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);

}

}