-2

This is for an assignment.

My code:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(void){

int run=1;

while (run) {
    int perror;
    pid_t pid;
    char in[1536];
    char para[512]="/bin/";
    char *argv[512];

printf("%s..: %s","\x1B[31m","\x1B[37m");
    fgets(in,1530,stdin);
    char *com = strtok (in," \t\r\n");
    char *c2=com;

    strcat (para,com); 
    strcat (para,"\0");

    int i=0;
    while (com != NULL){
        com = strtok (NULL, " \n\r\t");
        if (com!=NULL){
            argv[i]=com;
            i++;
        }
    }

    argv[i]="\0";

    if (strcmp(c2, "exit") == 0|strcmp(c2, "quit") == 0){
        run=0;
    }

    else{ 
        if ((pid=fork())==0){ //Child
            execvp(para,argv);
        }
        else{ //Parent
            waitpid(pid,&perror,0);
        }
    }
}
return 0;
}

Commands like ls and pwd work perfectly without arguments, but when I try to use arguments the first argument is ignored. Sample outputs below.

$ make
$ ./A1T2
..: ls
A1T2  main.c  main.c~  main.c++~  main.c.old  Makefile  Makefile~
..: pwd
/home/kevin/Documents/COS-222/Assignments/Assignment-1/Task-2
..: mkdir one
one: cannot create directory ‘’: No such file or directory
..: mkdir one two
one: cannot create directory ‘’: No such file or directory
..: ls
: cannot access : No such file or directory
two:
..: exit
kevin@Kevin-MATE:~/Documents/COS-222/Assignments/Assignment-1/Task-2$ make
./A1T2
..: ls
A1T2  main.c  main.c~  main.c++~  main.c.old  Makefile  Makefile~  two
..: echo hello world
world 
..: exit
$
Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
DeafFrog
  • 75
  • 9

3 Answers3

2

Note: the first parameter to execvp() is not a string, but rather a pointer to a string, just like all the entries in the argv[] array and the argv[] array is terminated with a final entry of NULL, not '\0'

the following code:

compiles cleanly, 
performs the desired function.

A user entered line of 'ls' will print all the file names in the current directory.

A user entered line of 'ls -la' will print the long version of all the file names in the current directory.

However, a user entered line of 'ls *' will fail with a message about file not found. I.E. 'glob'ing is not being performed.

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

enum
{
    MAX_PARAMETERS = 512,
    MAX_PGM_NAME_LEN = 512,
    MAX_USER_INPUT_LEN =1563
};

int main(void)
{

    int run=1;

    while (run)
    {
        int status;
        pid_t pid;
        char in[MAX_USER_INPUT_LEN];
        char para[MAX_PGM_NAME_LEN]="/bin/";
        char *argv[MAX_PARAMETERS] = {'\0'};
        int i = 1; // index into argv[] list

        // change some terminal screen characteristic
        printf("%s..: %s","\x1B[31m","\x1B[37m");

        printf( "enter program name to be executed and any parameters\n separated by space, tab or newline\n");
        fgets(in,MAX_USER_INPUT_LEN,stdin);

        char *pgmName = strtok (in," \t\r\n");
        if( NULL == pgmName )
        { // then strtok failed
            perror( "strtok failed" );
            exit( EXIT_FAILURE );
        }

        // implied else, strtok successful

        printf( "program to execute: %s\n", pgmName);
        char *c2=pgmName;

        strcat( para, pgmName);
        argv[0] = para;
        printf( "argv[0] = %s\n", argv[0]);

        char * parameter = NULL;
        while (NULL != (parameter = strtok(NULL, " \n\r\t") ) )
        {
            printf( "argv[%d] = %s\n", i, parameter);
            argv[i]=parameter;
            i++;
        }

        argv[i]=NULL; // terminate the parameter list

        if ( (strcmp(c2, "exit") == 0) | (strcmp(c2, "quit") == 0) )
        {
            run=0;
        }

        else
        {
            pid=fork();
            if (-1 == pid)
            { // then fork failed
                perror( "fork() failed");
                exit( EXIT_FAILURE );
            }

            // implied else, fork successful

            if( 0 == pid)
            { //Child
                execvp(para,argv);
                exit( EXIT_FAILURE ); // should never get here
            }
            else
            { //Parent
                waitpid(pid,&status,0);
            }
        }
    }
    return 0;
}
user3629249
  • 16,402
  • 1
  • 16
  • 17
  • suggest NOT passing argv[0] as first parameter to execvp() unless the $PATH environment variable contains the path to where the desired executable is located – user3629249 Aug 09 '15 at 03:15
  • The parent code should report on the exit status of the child, especially when there have been known to be problems getting things to work. Personally, I think there are very few reasons for not writing `execvp(argv[0], argv);` — pass the value of `argv[0]` as the name of program to be found. Note that your code will fail to execute any program not found in `/bin`. On Linux, `/bin` is a symlink to `/usr/bin`; on Mac OS X, for example, there are 37 commands in `/bin` and 1114 in `/usr/bin`, not to mention that using `execvp()` with a `/` in the name passed as the command renders the `p` moot. – Jonathan Leffler Aug 09 '15 at 03:19
  • *the first parameter to execvp() is not a string, but rather a pointer to a string*. The first arg is a `char*`. That's what the OP is passing. Using `char para[]` as a function argument passes a pointer to the array, this is standard C array to pointer conversion, aka "decay". – Peter Cordes Apr 13 '18 at 01:49
1

Put params in argv[0] and shift everything in argv down and call:

execvp (argv[0], argv);
The Brofessor
  • 1,008
  • 6
  • 15
0

I suggest you use main(argc, argv) to receive the command line parameters used to start your code, and then call execvp(argv[1], &argv[1]);

This will simply pass the desired parameters that you provide on the command line when starting your code, thus, if you start your code with...

yourcode ls -l -b

The execvp() method would receive "ls" as the first parameter and "-l" and "-b" as additional parameters.

Remember, don't duplicate code, and always provide comments.

Paul Roub
  • 36,322
  • 27
  • 84
  • 93
Rodney P. Barbati
  • 1,883
  • 24
  • 18
  • The OP is writing a shell that parses multiple lines of commands. Your answer is great if you're writing a wrapper for one command, but that's not the question. – Peter Cordes Apr 13 '18 at 01:51
  • The above code can be used to execute any command with any parameters. – Rodney P. Barbati May 30 '18 at 02:55
  • Right, but only if `main`'s caller has already tokenized it for you. The OP is reading a flat string of user input and has to word-split manually, because they want to fork/exec/wait in a loop for multiple commands. – Peter Cordes May 30 '18 at 03:02