2

Here is my whole code, I have an idea on what needs to be replaced by I'm not sure on how to go about it. The goal of the program is to read files within Linux using a simple shell, I have done this using forks but now I need to translate it to be multithreaded.

// Header files
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/wait.h>
#include <pthread.h>
///////////////////////////////////////////////////////////////////////////////////////////////////
#define LINE_LEN 80            //length of each command line.
#define MAX_ARGS 64            //maximum amounts of arguments of each command.
#define MAX_ARG_LEN 16         //length of each argument.
#define MAX_PATHS 64           //maximum amounts of paths.
#define MAX_PATH_LEN 96        //maximum length of each path.
#define WHITESPACE " .,''\t\n" //delimeters for command line.
#define TRUE 1                 //1 for TRUE
#ifndef NULL                   //if NULL is not defined, then define NULL as 0
#define NULL 0
#endif
///////////////////////////////////////////////////////////////////////////////////////////////////
// each command line has a name,
// amount of arguments --> argcount
//arguments is in an array --> argvalue[MAX_ARGS]
typedef struct {
    char *name;                 //command name
    int argcount;               //number of arguments
    char *argvalue[MAX_ARGS];   //argument values (array of values) AKA Strings
} command_t;
// function prototypes
char *lookupPath(char **, char **);
int parseCommand(char *, command_t *);
int parsePath(char **);
void printPrompt();
void readCommand(char *);
/***************************************************************
 * Function main. This function performs the bulk of the
 * program's purpose: calling each of the seperate functions 
 * in the order of steps specified in the program description.
 * 
 * In short, the path is first parsed, then user is prompted to 
 * input a command, the command is read, parsed, validated, and,
 * if a valid command it is executed by a thread using 
 * the execv function. The program closes when the user enters 
 * "stop","exit", or "quit.
 * 
 * Shorthand:
 * Read/print promt > validate > execute > leave with 1 of 3 commands
 **************************************************************/
int main(int argcount, char *argvalue[]) {
    //initialize variables 
    char *pathv[MAX_PATHS];                       //array of paths
    command_t command;                            //intialize command_t struct variable command
    char *commandLine = (char *)malloc(LINE_LEN); //command line
    //get the directory paths from PATH
    parsePath(pathv);
    //print the first prompt
    printPrompt();
    //loop the program to allow the user to input multiple commands or until the user wishes to exit
    while (TRUE) {
        //read the command from the keyboard, then store it into the commandLine.
        readCommand(commandLine);
        //if command is 'stop','exit', or 'quit' then exit the program.
        if (strcasecmp(commandLine, "stop\n") == 0 ||strcasecmp(commandLine, "exit\n") == 0 || strcasecmp(commandLine, "quit\n") == 0) {
            free(commandLine);
            break;
        }
        //parse the command
        parseCommand(commandLine, &command);
        //assign the path to the command name
        command.name = lookupPath(command.argvalue, pathv);
        //check if command is valid
        if (command.name == NULL) {
            printf("Command name is NULL \n");//failed response
            printPrompt();//open promt again
            continue;
        }else {//if command is valid, fork and execute command
        /*******************************************
         *******************************************
         *Note : PThread would replace this I think*
         *******************************************
        *******************************************/
            // create a child process.
            int childPid = fork();
            //create temp and append path to command name
            char temp[LINE_LEN];
            strcpy(temp, "/bin/");
            strcat(temp, command.name);
            //if fork is successful
            if (childPid == 0) {
                //execute the command.
                execv(temp, argvalue);
            }
            //wait for child to terminate
            if (wait(NULL) > 0) {
                printPrompt();
            }
        }
    }
    //terminate shell
    printf("Exiting now...\n");
    return 0;
}
/**********************************************************
 * Function lookupPath: Searches for the directories identified
 * by the dir argument to see if argvalue[0] (the file name)
 * appears there. If so, allocate a new string, place the full
 * path name in it, then return the string.
 * 
 * Shorthand:
 * Using "dir" to see if in argvalues[], if it is allocate string (char *)
 *********************************************************/
char *lookupPath(char **argvalue, char **dir) {
    //initialize Variables
    char pName[MAX_PATH_LEN];           //path name array
    int x;                              //loop variable
    //check to see if command name is already an absolute path name
    if (*argvalue[0] == '/') {
        if (access(argvalue[0], 0) == 0) {
            return argvalue[0];
        }
        return NULL;
    }
    //look in PATH directories
    for (x = 0; a < MAX_PATHS; x++) {
        //temporary absolute path
        char tpath[MAX_PATH_LEN];
        //if current directory is NULL, exit loop
        if (dir[x] == NULL) {
            break;
        }
        //copy dir[x] to tpath
        strcpy(tpath, dir[x]);
        //append '/' to tpath
        strcat(tpath, "/");
        strcat(tpath, argvalue[0]);
        strcpy(pName, tpath);
        //use access() to see if the file is in a dir
        if (access(pName, 0) == 0) {
            return argvalue[0];
        }
    }
    return NULL;
}
/**********************************************************
 * Function parseCommand: Determines the command name and
 * constructs the parameter list. This function builds 
 * argvalue[] and sets the argcount value, where argcount is the
 * number of "tokens" or words on the command line. argvalue[]
 * is an array of strings (pointers to char *). The last
 * element in argvalue[] must be NULL. As we scan the command
 * line from the left, the first token goes into argvalue[0],
 * the second in argvalue[1], and so on. Each time we add a
 * token to argvalue[], we increment argcount. 
 * 
 * Shorthand: 
 * argcount = number of arguments
 * argvalue - strings turned into arrays
 *********************************************************/
int parseCommand(char *cLine, command_t *cmd) {
    //initialize Variables
    int argcount;
    char **clPtr;
    clPtr = &cLine;
    argcount = 0;
    //initialize first index of cmd's array argvalue with MAX_ARG_LEN 
    cmd->argvalue[argcount] = (char *)malloc(MAX_ARG_LEN);
    //fill argvalue[] (we will split commands with whitespace)
    while ((cmd->argvalue[argcount] = strsep(clPtr, WHITESPACE)) != NULL) {
        cmd->argvalue[++argcount] = (char *)malloc(MAX_ARG_LEN);
        strcat(cmd->argvalue[0], cmd->argvalue[argcount]);
    }
    //set the command name and argcount
    cmd->argcount = argcount - 1;
    cmd->name = (char *)malloc(sizeof(cmd->argvalue[0]));
    strcpy(cmd->name, cmd->argvalue[0]);
    return 1;
}
/**********************************************************
 * Function parsePath: Reads the PATH variable for this
 * environment, then builds an array, dirs[], of the
 * directories in PATH.
 * 
 * Shorthand:
 * Read path > make array dirs[]
 *********************************************************/
int parsePath(char *dirs[]) {
    //initialize Variables
    char *pathEnvVar;       //path enviroment variable
    char *thePath;          //name of path
    int x;                  //counter for loops
    //set all current directories to NULL
    for (x = 0; x < MAX_PATHS; x++) {
        dirs[i] = NULL;
    }
    //store the value of PATH in pathEnvVar
    pathEnvVar = (char *)getenv("PATH");
    //store the length + 1 of pathEnvVar in thePath (reminder that space is needed bc of how arrays work)
    thePath = (char *)malloc(strlen(pathEnvVar) + 1);
    //store pathEnvVar in thePath
    strcpy(thePath, pathEnvVar);
    for (x = 0; x < MAX_PATHS; x++) {
        //split the value by ':', then store them into the dirs.
        dirs[x] = strsep(&thePath, ":");
        if (dirs[x] == NULL)
            break;
    }
    //loop through all path directories
    for (x = 0; x < MAX_PATHS; x++) {
        //if current directory is null, finish
        if (dirs[x] == NULL) {
            break;
        }
    }
    return 0;
}
/**********************************************************
 * Function printPrompt. Prints prompt which consists 
 * simply of '>>>' for the user to enter a command.  
 * 
 * Shorthand:
 * Print prompt
 *********************************************************/
void printPrompt() {
    const char *promptString = ">>> ";
    printf("%s", promptString);
}
/**********************************************************
 * Function readCommand. Reads command input by the
 * user into buffer using the standard input library.
 * 
 * Shorthand:
 * Reads inputs
 *********************************************************/
void readCommand(char *buffer) {
    fgets(buffer, sizeof(buffer), stdin);
}

So far all I have added is the "

#include <pthread.h>

" in my code as im unsure on how to to go about it further. Any help is appreciated!

Pez
  • 21
  • 2
  • Yo can't just replace `fork()` with `pthread_create()` if you use `execv()`. See answer to this question: https://stackoverflow.com/questions/36588387/what-happens-to-threads-when-exec-is-called – dimich Nov 16 '22 at 05:04
  • See https://stackoverflow.com/questions/14407544/mixing-threads-fork-and-mutexes-what-should-i-watch-out-for – fpmurphy Nov 16 '22 at 05:23
  • Your request does not make senes. You want to run an external program, that is you **need** a new process. This is why you use `fork+exec`. How should this work in a thread only? The exec would replace your current process including all threads. – user1934428 Nov 16 '22 at 08:19

1 Answers1

2

fork() is not the real problem here, it's the subsequent execv(), which overwrites the entire calling process (all the threads) with a new process running the provided command.

execv() and friends are not something you can replace in general with threads, because it's a syscall to the OS for creating an entirely new process to replace the running one. If your requirements include asking the OS to execute an arbitrary external command, then spawning an entirely new process is a fundamental part of that. Your choices are basically fork()/exec*() shown here, or something like system() from stdlib.h that just wraps up that same idiom.

Dan Bonachea
  • 2,408
  • 5
  • 16
  • 31