So to start off, this program has two main parts. The first takes input from the command line using fgets and then makes an argv array with a function called makeargv. The second, takes that argv array and runs it using execvp. The problem I am having is that the program will only run system programs such as "ls", "pwd", "vim", etc. but will not run any program when the directory is specified, such as "./program". I have already tried different versions of exec but the only difference that has made is that then my program will no longer run any commands.
For the below program I cut out all the code that was not relevant to the question to avoid confusion.
#ifndef MAX_CANON
#define MAX_CANON 8192
#endif
int makeargv(const char *s, const char *delimiters, char ***argvp);
int main (int argc, char *argv[]) {
char cmd[MAX_CANON];
char delim[] = "\t";
char **myargv;
printf("Beginning program...\nEnter a command to execute:\n");
while(fgets(cmd, MAX_CANON, stdin) != NULL){ // Here's where I get input from the command line
/* Remove newline from end of command */
if (*(cmd + strlen(cmd) - 1) == '\n' || *(cmd + strlen(cmd) - 1) == ' ' )
*(cmd + strlen(cmd) - 1) = 0;
/*---- Child Code ----*/
if((p = fork()) == 0){
if (makeargv(cmd, delim, &myargv) == -1) { // Here is where I make the argv array
fprintf(stderr, "Child failed to construct an argument array for %s\n", &cmd[0]);
return 1;
}
fprintf(stderr, "Command is: %s\n", *&myargv[0]);
if(execvp(myargv[0], &myargv[0]) == -1){ // Here is where the error keeps appearing
fprintf(stderr, "Error: Failed to execute command!\n");
return 1;
}
return 0;
}
/*---- Parent Code ----*/
Here is the makeargv code
#include <errno.h>
#include <stdlib.h>
#include <string.h>
int makeargv(const char *s, const char *delimiters, char ***argvp) {
int error;
int i;
int numtokens;
const char *snew;
char *t;
if ((s == NULL) || (delimiters == NULL) || (argvp == NULL)) {
errno = EINVAL;
return -1;
}
*argvp = NULL;
snew = s + strspn(s, delimiters); /* snew is real start of string */
if ((t = malloc(strlen(snew) + 1)) == NULL)
return -1;
strcpy(t, snew);
numtokens = 0;
if (strtok(t, delimiters) != NULL) /* count the number of tokens in s */
for (numtokens = 1; strtok(NULL, delimiters) != NULL; numtokens++) ;
/* create argument array for ptrs to the tokens */
if ((*argvp = malloc((numtokens + 1)*sizeof(char *))) == NULL) {
error = errno;
free(t);
errno = error;
return -1;
}
/* insert pointers to tokens into the argument array */
if (numtokens == 0)
free(t);
else {
strcpy(t, snew);
**argvp = strtok(t, delimiters);
for (i = 1; i < numtokens; i++)
*((*argvp) + i) = strtok(NULL, delimiters);
}
*((*argvp) + numtokens) = NULL; /* put in final NULL pointer */
return numtokens;
}
Edit:
Swapped fprintf for perror.
if(execvp(myargv[0], &myargv[0]) == -1){ // Here is where the error keeps appearing
fprintf(stderr, "Error: Failed to execute command!\n");
return 1;
}
if(execvp(myargv[0], &myargv[0]) == -1){ // Here is where the error keeps appearing
perror("Error: Failed to execute command!\n");
return 1;
}
I am now getting a "No such file or directory" error.
FIXED:
The makeargv program was using "\t" as it's delimeter instead of " " so it was not creating the array correctly.
Changing:
char delim[] = "\t";
To:
char delim[] = " ";
Fixes the problem.
There is 2 type of exec:
the first need a path as the environment PATH who contain a location (where find sys executables)
and the others how take real file location.
Form the man page (https://linux.die.net/man/3/exec):
The execlp(), execvp(), and execvpe() functions duplicate the actions of the shell in searching for an executable file if the specified filename does not contain a slash (/) character.
So this mean that you need to use an absolute location, who always begin with /.
You can use getwd() and strcat() to concatenate the 2 strings.
For execvp:
int execvp(const char *file, char *const argv[]); // From the man (I <3 mans)
So:
char argv[3][] = { "/bin/ls", "/", NULL};
if (execvp("/bin/ls", argv) == -1)
return (1);
For evecv:
int execv(const char *path, char *const argv[]);
There is a problem, it need the system PATH. If you don't know what it is type "echo $PATH" in bash. It's a list of directories where the system can find binaries like "ls" in "/bin", concatenate with ":" as sparator. I find the exact definition here.
For your makeargv:
I don't understand why you deference it and take the address after.
execvp(myargv[0], &myargv[0])
char **myargv; //That's we have
char *const argv[]; //that's we need
//so you should try simply:
execvp(myargv[0], myargv);
//or if there is a cast problem
execvp(myargv[0], (char *const[])myargv);
Good luck!
Related
I have been given a c language code
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>
#define MAX_BUFFER 256
#define QUIT_STRING "q"
int makeargv(const char *s, const char *delimiters, char ***argvp);
int main (void) {
char **chargv;
char inbuf[MAX_BUFFER];
for( ; ; ) {
gets(inbuf);
if (strcmp(inbuf, QUIT_STRING) == 0)
return 0;
if ((fork() == 0) && (makeargv(inbuf, " ", &chargv) > 0))
execvp(chargv[0], chargv);
wait(NULL);
}
}
makeargv function which makes tokens out of the string passed as 1st argument (using delimiters passed in 2nd argument) and stores these tokens in the array pointed to by the 3rd argument.
#include <errno.h>
#include <stdlib.h>
#include <string.h>
int makeargv(const char *s, const char *delimiters, char ***argvp) {
int error;
int i;
int numtokens;
const char *snew;
char *t;
if ((s == NULL) || (delimiters == NULL) || (argvp == NULL)) {
errno = EINVAL;
return -1;
}
*argvp = NULL;
snew = s + strspn(s, delimiters); /* snew is real start of string */
if ((t = malloc(strlen(snew) + 1)) == NULL)
return -1;
strcpy(t, snew);
numtokens = 0;
if (strtok(t, delimiters) != NULL) /* count the number of tokens in s */
for (numtokens = 1; strtok(NULL, delimiters) != NULL; numtokens++) ;
/* create argument array for ptrs to the tokens */
if ((*argvp = malloc((numtokens + 1)*sizeof(char *))) == NULL) {
error = errno;
free(t);
errno = error;
return -1;
}
/* insert pointers to tokens into the argument array */
if (numtokens == 0)
free(t);
else {
strcpy(t, snew);
**argvp = strtok(t, delimiters);
for (i = 1; i < numtokens; i++)
*((*argvp) + i) = strtok(NULL, delimiters);
}
*((*argvp) + numtokens) = NULL; /* put in final NULL pointer */
return numtokens;
}
and i need to answer 3 questions which are mentioned below
How would the shell implemented in above code will behave when the user gives an invalid command (i.e. a command for which no executable exists)?
What would happen if the user gives multiple invalid commands?
What happens when the user tries to quit the shell after giving multiple invalid commands.
Here's what i think are the answers to these questions
The execv will return an error, but I do not think it will break the code so it will result in two forks trying to read inputs
More forks will be created
Only one of the forks will quit
Question
Are all of the answers correct? Could any of the answers be improved?
Never have two processes trying to read stdin at once. Race conditions make the resulting environment unusable.
if (fork() ==0){
if(...)
execvp();
_exit(255); /* don't fall back to parent code */
}
I would like to get which command as a parameter of execv. For example user types "firefox" as an input. It will show the file path with which firefox command and i would like to use it in execv.
Here is my try :
int main(void)
{
//char inputBuffer[MAX_LINE]; /*buffer to hold command entered */
char *args[MAX_LINE/2 + 1]; /*command line arguments */
while (1){
char command1[50] = "which ";
char *input = args[0]; /* args[0] holds input */
strcat(command1,input);
char *const parmList[] = {input,NULL};
char buf[100]; /* will convert command to the file path */
char *str;
FILE *file;
if (NULL == (file = popen(command1, "r"))) {
perror("popen");
exit(EXIT_FAILURE);
}
while (fgets(buf, sizeof(buf), file) != NULL) {
}
pclose(file);
int count=0;
for(int m=0;m<buf[m];m++){
if(buf[m] != ' ')
count++;
}
char x5[count];
for(int t=0;t<=count;t++){
x5[t]=buf[t];
}
printf("%s\n\n\n\n",x5); /*i tried to delete spaces from 'buf'
but there is something wrong in file path the output is something like that :
(input : firefox)
usr/bin/firefox
(some weird characters)
because of weird characters execv don't get filepath correctly. I have to this
only with execv
*/
pid_t childpid;
childpid = fork();
if (childpid == -1) {
printf("error");
}
if (childpid == 0) {
execv(x5, parmList);
}
}
}
there are, as you noticed, a few problems with the two loops that are trying to remove any embedded spaces in the value being passed as first param to execv()
Note: always place the literal on the left to have compiler catch error if accidentally wrote an assignment rather than a comparison statement.
1) failed to trim any trailing newline from the buf[] array.
2) those loops are, well, not correct. Suggest:
// remove trailing newline
char * newline = NULL;
if( NULL != (newline = strstr( buf, "\n") ) )
{ // then trim newline
*newline = '\0';
}
// data declarations for removing space characters
char *sourceIndex = buf;
char *targetIndex = buf;
// following loop removes space characters, in place
// and stops when the string terminator char encountered
for( ; sourceIndex; sourceIndex++)
{
if( ' ' != *sourceIndex )
{ // then char to have in final output
*targetIndex = *sourceIndex; // copy it
targetIndex++; // step to next position in target
}
}
*targetIndex = '\0'; // terminate the (possibly shorter) string
...
// pass to new process
if( 0 == childpid )
{ // then child process
execv(buf, parmList); // only returns if execv() failed
perror( "execv failed" );
exit( EXIT_FAILURE );
}
I'm writing a program to parse a command-line argument into three different parts: host name, file path, and file name, however I am unsure of how to parse a single command-line argument and store the separate parts in three different variables.
I need each portion to create a socket on the client-side of my program. So far I've been able to parse the host name portion, but I get stuck after that.
Is there a way that, after parsing a portion of the string?
EDIT:
The string I'm trying to parse is something like camelot.cba.csuohio.edu/~yourloginid/filename.txt
Here's my code
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
int main(int argc, char *argv[])
{
int i, sk;
char buf[256], temp[256];
struct sockaddr_in remote;
struct hostent *hp;
if(argc != 2)
{
printf("Invalid number of arguments. Program terminating...");
exit(1);
}
sk = socket(AF_INET, SOCK_STREAM, 0);
remote.sin_family = AF_INET;
strcpy(buf, argv[1]);
for(i = 0; i < strlen(buf); i++)
{
if(buf[i] == '/')
break;
temp[i] = buf[i];
}
hp = gethostbyname(temp);
return 0;
}
EDIT:
I've implemented a while loop to achieve what I'm looking for, but I feel like it's sloppy. Is there a way I can improve it?
while(tk != NULL)
{
if(c == 0)
strcpy(host, tk);
else if(c == 1)
strcpy(path, tk);
else
strcpy(fname, tk);
c++;
tk = strtok(NULL, "/");
}
char st[] = "camelot.cba.csuohio.edu/~yourloginid/filename.txt";
char *host, *path, *fname;
char *ch[3];
for (int i=0; i < 3; ++i) {
ch[i] = strtok(st, "/");
(if ch[i] == NULL) break;
printf("%s\n", ch[i]);
}
if (ch[0] != NULL) {
host = ch[0];
}
if (ch[1] != NULL) {
path = ch[1];
}
if (ch[2] != null) {
path = ch[2];
}
Output:
camelot.cba.csuohio.edu
~yourloginid
filename.txt
You can parse that with strtok
A rough example for you case would be
const char s[2] = "/";
char *token;
/* get the first token */
token = strtok(argv[1], s);
/* walk through other tokens */
while( token != NULL )
{
printf( " %s\n", token );
token = strtok(NULL, s);
}
I didn't compile it but I hope you can use it as an example.
Here you have a complete example of how to use it
http://www.tutorialspoint.com/c_standard_library/c_function_strtok.htm
Hope this helps.
When you know the delimiters, never forget you have simple pointer arithmetic available to you to split/parse any sting. strtok and sscanf are fine tools, but you can do the same thing manually. Here is a short example to add to your list:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAXS 128
int main (int argc, char **argv) {
if (argc < 2 ) {
fprintf (stderr, "Error: insufficient input, usage: %s host,path,file\n", argv[0]);
return 1;
}
char *line = strdup (argv[1]); /* make a copy of argument string */
if (!line) {
fprintf (stderr, "error: strdup memory allocation/copy failed.\n");
return 1;
}
char *p = line; /* pointer to the argument string */
char *sp = NULL; /* pointer to use as start pointer */
char host[MAXS] = {0}; /* variables to hold tokens */
char path[MAXS] = {0};
char file[MAXS] = {0};
while (*p && *p != ',') p++; /* find the first ',' */
*p++ = 0; /* null-terminate, advance pointer */
strcpy (host, line); /* read/copy host name */
sp = p; /* set start pointer at current pos */
while (*p && *p != ',') p++; /* find next ',' */
*p++ = 0; /* null-terminate, advance pointer */
strcpy (path, sp); /* read/copy path */
strcpy (file, p); /* pointer on file, read/copy file */
printf ("\n host: %s\n path: %s\n file: %s\n\n", host, path, file);
free (line); /* free memory allocate by strdup */
return 0;
}
Output
$ ./bin/split_host_path_file hostname,pathname,filename
host: hostname
path: pathname
file: filename
Updated to prevent potential read beyond end of line with p.
you can also parse with strtok_r as follows, since strtok is not thread safe.
const char *delim="/";
char *str, *savePtr;
char hosts[3][32];
int i;
for(i=0,str=strtok_r(argv[1], delim, &savePtr);(str!=NULL);str=strtok_r(NULL, delim, &savePtr), i++)
{
print("%s\n", str);
strcpy((char *)host[i], (const char *)str);
}
access host array elements, as it will contain the indexed values delimited by "/"
So i am attempting to pass a string array (char** arguments) to a function, fill the array with values and then print those values after returning from the function. The problem occurs when I try to print the first value of "arguments" which gives me a segmentation fault. Why is this? when I print the values in the "getArguments" function all goes as expected. I am new to C and yes this is an assignment. I am not looking for you to write this code for me however I would like an explanation of this behaviour as I try to understand this concept.
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#define BUFFERSIZE 81
int getArguments(char** arguments, char* argument);
void getPath(char* pathBuffer);
int checkForDirectoryChange(char **arguments, int num_args);
int main(int argc, char *argv[]){
char * command;
char ** arguments = NULL;
char * cd_path;
int len, pid, ret_code, cd_requested = 1;
char buffer[BUFFERSIZE];
/* Get user input and the first token */
printf("Enter a command: > ");
command = fgets(buffer,BUFFERSIZE,stdin);
printf("The command entered was %s",buffer);
len = strlen(buffer);
if(buffer[len-1] == '\n')
buffer[len-1]='\0';
cd_requested = getArguments(arguments, command);
printf("The argument passed is now: %s\n", arguments[0]);
if(cd_requested == 0){
fprintf(stdout,"Change directory requested.\n");
}
/*
char * pathBuf;
getPath(pathBuf);
free the memory allocated */
/*
pid = fork();
if(pid){
wait(NULL);
}else{
ret_code = execvp(*arguments, arguments);
if(ret_code){
printf("The fork failed, exiting.");
exit(0);
}
}*/
}
int getArguments(char** arguments, char* command){
int n_spaces = 0,i;
char *token;
token = strtok(command, " ");
/* Loop until we have gotten all of the tokens */
while (token) {
arguments = realloc (arguments, sizeof (char*) * ++n_spaces);
if (arguments == NULL){
printf("Memory allocation failed: token - %d\n", n_spaces);
exit (-1); /* memory allocation failed */
}
arguments[n_spaces-1] = token;
token = strtok (NULL, " ");
}
/* realloc one extra element for the last NULL */
arguments = realloc (arguments, sizeof (char*) * (n_spaces+1));
arguments[n_spaces] = 0;
/* print the result */
for (i = 0; i < (n_spaces+1); ++i)
printf ("arguments[%d] = %s\n", i, arguments[i]);
return strcmp("cd",arguments[0]);
}
int checkForDirectoryChange(char** arguments, int num_args){
return 0;
}
void getPath(char* pathBuffer){
size_t n;
n = confstr(_CS_PATH, NULL, (size_t) 0);
pathBuffer = malloc(n);
if (pathBuffer == NULL)
abort();
confstr(_CS_PATH, pathBuffer, n);
}
It is because getArguments() only reassigned the copy of pointer to pointer of characters inside itself. arguments in main() was not updated.
You should define getArguments() as
int getArguments(char*** arguments, char* command) {
/* ... */
while (token) {
*arguments = realloc (*arguments, sizeof (char*) * ++n_spaces);
if (*arguments == NULL){
printf("Memory allocation failed: token - %d\n", n_spaces);
exit (-1); /* memory allocation failed */
}
(*arguments)[n_spaces-1] = token;
token = strtok (NULL, " ");
}
/* ... */
}
And call it as the following inside main().
cd_requested = getArguments(&arguments, command);
I am coding a basic shell and my first requirement is to test for cd, I have all my conditions for the possible cd commands, after which, I will handing commands like ls. As for now I am really confused about one block of code.
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#define MAX_TOK 50
#define MAX_LEN 100
#define BUFSIZE 81
int tokenize(char *cmd, char tokens[MAX_TOK][MAX_LEN]){
char *token;
int NUM_TOKENS = 0;
//printf("Splitting the string \"%s\" into tokens:\n",cmd);
token = strtok(cmd, " ");
while(token != NULL){
strcpy(tokens[NUM_TOKENS],token);
token = strtok(NULL, " ");
NUM_TOKENS++;
}
return NUM_TOKENS;
}
void decide(char tokens[MAX_TOK][MAX_LEN], int NUM_TOKENS){
char *home = getenv("HOME");
int success;
char *cd = {"cd"};
char *string = tokens[1];
//printf("Number of tokens %d\n", NUM_TOKENS);
//printf("%d\n",strcmp(tokens[0], cd));
if(strcmp(tokens[0], cd) == 0){
if(NUM_TOKENS > 2){
printf("error: Too many arguments\n");
}
else{
printf("Changing to new directory %s \n",tokens[1]);
char *string = tokens[0];
//printf("%s\n", tokens[1]);
success = chdir(tokens[1]);
printf("%d\n",success);
}
}
else{
printf("Completing the %s request\n",tokens[0]);
take_action(tokens[0]);
}
}
void take_action(char *cmd){
printf("%s\n",cmd);
int len;
int return_code;
char buffer[BUFSIZE];
int pid;
pid = fork();
if(pid != 0){
//parent process executing
wait(NULL);
}else{
//child process executing
len = strlen(buffer);
if(buffer[len-1] == '\n'){
buffer[len-1] = '\0';
}
return_code = execlp(cmd, cmd, NULL);
if(return_code != 0){
printf("Error executing %s.\n", cmd);
}
}//end else
}
int main(){
char *cmd;
char tokens[MAX_TOK][MAX_LEN];
int len;
int return_code;
char buffer[BUFSIZE];
int pid;
while(cmd != NULL){
printf("Enter a command\n");
cmd = fgets(buffer, BUFSIZE, stdin);
// find the command
int NUM_TOKENS = tokenize(cmd, tokens);
//print_tokens(NUM_TOKENS, tokens);
decide(tokens,NUM_TOKENS);
}
}//end main
When hardcoding chdir("test") the code runs fine, if a user on the command line enters "cd test" tokens[0] is cd and tokens[1] is the string "test" but chdir(tokens[1]) fails and I don't understand why.
Printing tokens[1] also shows "test" as the string stored. As well when passing a parameter to take_action I am told conflicting types occurs. In both print statements the proper string is shown. As far as I can tell there are no extra spaces because my tokentize function strips them all. I am so confused, these two parts seem so simple but just wont work.
The user does not enter cd test. He enters cd test and then hits the Enter key.
This means that you will have:
token[0] = "cd";
token[1] = "test\n";
And you don't have a directory named "test\n" , it would be "test". You need to strip off the newline character in the last token.
e.g. change main to do
char *tmp;
cmd = fgets(buffer, BUFSIZE, stdin);
if (cmd && (tmp = strrchr(buffer, '\n')) != NULL) {
*tmp = 0;
}
The code
char *string = tokens[0];
printf("%s\n", tokens[1]);
success = chdir(string);
will translate to
success = chdir("cd");
I think you wanted
char *string = tokens[1];
// ^
instead.
token[0] is cd and token[1] is the path. So use char* string=token[1]