I'm implementing a history feature for a command line shell. I've implemented a circular array to hold to ten most recent commands. Each command is also labeled by an integer specifying which total command is. For Example, if 30 total commands were entered, the ten commands in the circular array would be numbered (30, 29, 28, 27,...,21).
If a user were to insert the command "r" followed by a number labeling one of the ten instructions then that instruction is supposed to run. I keep running into a seg fault when trying to ensure that a two word command is accepted properly. Can anyone help point out what the problem is.
int main(void)
{
char inputBuffer[MAX_LINE]; /* buffer to hold the command entered */
int background; /* equals 1 if a command is followed by '&' */
char *args[MAX_LINE/2+1];/* command line (of 80) has max of 40 arguments */
int position, count, rnum = 0;
char historyArray[10][MAX_LINE];
char *holder[MAX_LINE]={0};
while (1){ /* Program terminates normally inside setup */
background = 0;
printf("COMMAND->");
fflush(0);
setup(inputBuffer, args, &background); /* get next command */
position = (count % MOD_VAL);
strcpy(historyArray[position],args[0]);
if(!strcmp("rr",args[0]))
{
strcpy(historyArray[position],historyArray[((position-1)+MOD_VAL)%MOD_VAL]);
printf("%i",count);
printf("%c",'.');
printf("%c",' ');
printf("%s",historyArray[position]);
printf("%c",'\n');
strcpy(args[0],historyArray[position]);
}
else if(!strcmp("r",args[0])) //SEG FAULT OCCURING IN THIS ELSE-IF BLOCK!
{
//args[1] will hold given number
printf("%c",'\n');
printf("%s",args[0]);
printf("%s",args[1]);
printf("%s",args[2]);
printf("%c",'\n'); //PRINT STATEMENTS FOR DEBUGGING
strncpy(holder[0], args[2], MAX_LINE - 1); //SEG FAULT
rnum = atoi(args[1]);
strcpy(historyArray[position],historyArray[((position-(count-rnum))+MOD_VAL)%MOD_VAL]);
strcpy(args[0],historyArray[position]); //CHANGES VALUES OF args[1], args[2]
if(holder[0] != NULL)
{
strncpy(args[1],holder[0],MAX_LINE-1);
args[2] = NULL;
}
else
{
args[1] = NULL;
}
printf("%c",'\n');
printf("%s",args[0]);
printf("%s",args[1]);
printf("%s",args[2]);
printf("%c",'\n');
}
else if(!(strcmp("h",args[0]))||!(strcmp("history",args[0])))
{
int counter = 0;
while(counter < 10)
{
printf("%i",(count - counter));
printf("%c",'.');
printf("%c",' ');
printf("%s", historyArray[((position - counter + MOD_VAL)%MOD_VAL)]);
printf("%c",' ');
printf("%c",'\n');
counter ++;
if(counter > count)
break;
}
}
count++;
pid_t pid1; //Initialize pid_t variable to hold process identifier
pid1 = fork(); //Fork process and assign process identifier to "pid1"
if (pid1 == 0) //Child process
{
//Child process executes the command specified by the user and
//then quits.
execvp(args[0], args);
exit(0);
}
else //Parent process
{
if (background != 1)//Check for inclusion of '&' in command
{
wait(NULL); //Wait for child process to finish executing
}
}
/* the steps are:
(1) fork a child process using fork()
(2) the child process will invoke execvp()
(3) if background == 0, the parent will wait,
otherwise returns to the setup() function. */
}
}
Any assistance is appreciated!
-MATT
Here your args is the array of character pointers.
But strcpy requires two arguments - that should be array or character pointer to which memory allocated by malloc
But your strcpy(historyArray[position],args[0]); takes one argument as character pointer which will not be accepted.
so you can either change the args[] to args[][] or args[0] = malloc(some_no), segfault will be removed.
You note that the crash occurs on the line
else if(!strcmp("r",args[0]))
If I were you, I would load the core file in a debugger and see what the value of args[0] is when passed to strcmp().
I expect you have compiler warnings about type mismatch between char and char*. You declare args as char*. That means args[0] is a char, not a char*. To compare the single character, just use a character instead of strcmp():
else if ('r' != args[0])
Some notes on pitfalls with C's string handling:
strcmp() isn't safe with respect to array boundaries when its arguments are not correctly NUL-terminated
use strncmp() to provide a limit to the number of characters compared
although strncpy() guards against array boundaries, it does not guarantee to NUL-terminate the destination string
strcpy() does not respect array boundaries; it is your responsibility to ensure the destination array is large enough to receive the string being copied to it
You are missing to allocated memory the the char pointers held in args and holder.
So referring to those as pointers to 0-terminated character arrays ("strings") via the str*() family of functions, leads to undefined bahaviour, as the str*() function try to derefernce those pointers not point to valid memory.
Related
I am trying to execute a program with the execvp function within an overseer and client distributed system. The client sends a program to be executed with the arguments:
char buf[500];
int bytes_recieved = 0;
char array[1][26];
bytes_recieved = recv(clientfd, buf, 5000, 0);
buf[bytes_recieved] = '\0';
char buf1[50];
int bytes_recieved1 = 0;
char *array1[4];
for (int i = 0; i < 3; i++){
bytes_recieved1 = recv(clientfd, buf1, 50, 0);
array1[i] = buf1;
printf("%s = buffer\n", buf1);
}
buf1[bytes_recieved] = '\0';
if(bytes_recieved != -1){
printTime();
fprintf(stdout,"atempting to execute program: %s\n", buf);
if(execvp(buf, array1) == -1) {
return 1;
}
}
I'm stuck on trying to figure out what happens when I print out the array of arguments in the program the last argument is the same for all of them? for example I run this in the client program to be executed:
./client 12345 home/user/test_program 1 2 3
the result from a simple printf is:
3
3
3
When I manually assign each argument in the array in the overseer:
array1[0] = "1";
array1[1] = "2";
array1[2] = "3";
and send it to the executed program it prints correctly.
I have also tested that the received buffer from the file descriptor is correctly assigning the variables in the array:
printf("%s = buffer\n", array1[i]);
within the assignment for loop, which returns:
1 = buffer
2 = buffer
3 = buffer
What am I doing wrong?
Let me know if you need any more information.
This is some skeletal code, based on your code fragment. I can't test it — you did not provide an MCVE (Minimal, Complete, Verifiable Example
— or MRE or whatever name SO now uses)
or an
SSCCE (Short, Self-Contained, Correct Example).
char buf[500];
int bytes_received = recv(clientfd, buf, sizeof(buf)-1, 0);
if (bytes_received < 0)
return 1;
buf[bytes_received] = '\0';
char *array1[5] = { buf };
for (int i = 0; i < 3; i++)
{
char buf1[50];
int bytes_received1 = recv(clientfd, buf1, sizeof(buf1)-1, 0);
if (bytes_received1 < 0)
return 1;
buf1[bytes_received1] = '\0';
array1[i + 1] = strdup(buf1);
printf("argument = [%s]\n", buf1);
}
printTime();
printf("atempting to execute program: %s\n", buf);
for (int i = 0; array1[i] != NULL; i++)
printf("argv[%d] = [%s]\n", i, array1[i]);
fflush(0);
execvp(array1[0], array1);
fprintf(stderr, "failed to execute '%s'\n", array1[0]);
return 1;
Multiple changes include:
Using sizeof to determine array sizes.
Changing spelling of "receive".
Subtracting 1 to allow space for a terminal null byte to convert messages to strings.
Making array1 big enough to hold a terminal NULL pointer and initializing (indirectly) the elements after the zeroth to NULL. This is important; the argument array to execvp() must be terminated with a NULL pointer.
Return if the initial receive fails, to avoid indexing by a negative number.
Making the buf1 array local to the loop; ditto bytes_received1.
Return if a subsequent receive fails, to avoid indexing by a negative number.
Making a copy of the strings read using strdup() — this is a key change.
Not trying to null-terminate buf1 at a position based on the data received in buf.
Revising the printing, putting square brackets around the string so trailing spaces or newlines can be spotted more easily.
Printing all the arguments to execvp().
Debating whether the debug output should go to stderr instead of stdout. I ended up leaving it going to stdout, but that isn't necessarily the best choice.
Changing one fprintf(stdout, …) to printf(…) for consistency.
Calling fflush(0) to send any pending output to its devices. With the debugging output going to stdout, if the output is piped to another program, the data will be fully buffered, not line buffered, and won't appear unless you force it to. Calling fflush(stdout) would also be an option. It's probable that you shouldn't (don't) have any file streams other than stdin, stdout, stderr open when you're calling execvp().
You should consider whether other streams (file descriptors) should be closed before reaching here, perhaps using the O_CLOEXEC or FC_CLOEXEC options to ensure that the file descriptors are closed on successful exec, so that the executed process doesn't get active file descriptors it doesn't know about.
Not bothering to check the return value from execvp(). If it returns, it failed; if it succeeds, it doesn't return.
Reporting an error message when execvp() fails to execute.
Leaving return 1; as part of the code after the failed execvp(). It would often be better to use exit(EXIT_FAILURE); or similar (_exit(EXIT_FAILURE) perhaps). Without the larger context of the function that calls this fragment of a function, it isn't possible to know what's best here.
Note that if execvp() fails and returns, rather than exits, you're leaking the memory allocated by strdup(). There should probably be a loop for (int i = 0; i < 3; i++) free(array1[i+1]); to release the copied memory before the return 1;.
The code doesn't check that there was no data truncation — it doesn't know if one of the recv() calls would have read more data than it did because there wasn't enough space to store all the data. You'd probably want to check that the actual data size is smaller than the space available to ensure that there wasn't truncation.
It isn't clear why the program name can be ten times bigger than the arguments. In general, arguments can be bigger than program names, though, since your sample data has arguments like 1, 2, 3, this is not a problem.
I have a method I call from the main method called that executes ls-l on a certain directory, I want it to execute it and send the result as a string to the main method.
My current flawed code:
char *lsl(){
char *stringts=malloc(1024);
chdir("/Users/file/path");
char * lsargs[] = { "/bin/ls" , "-l", NULL};
stringts="The result of ls-l in the created directory is:"+ execv(lsargs[0], lsargs);
return stringts;
}
Currently I am only getting the exec output on the screen, I understand why this is happening(exec getting called before reaching return point). However I don't know how I could possibly do what I want and if it's actually doable.
I was thinking of using pipes and dup2() so I don't let the exec function use stdout but I don't know if it would be possible to put the output in a string.
As Jonathan Leffler already pointed out in comments, there is no '+' operator for concatenating strings in C.
A possibility to dynamically extends strings is to use realloc together with strcat.
For each number of bytes you read from the pipe, you could check the remaining capacity of the originally allocated memory for the string and, if this is not enough, reallocate twice the size.
You have to keep track of the size of the current string yourself. You could do this with a variable of type size_t.
If you combine this with the popen handling, it could look something like this:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(void) {
FILE *fp;
if ((fp = popen("ls -l", "r")) == NULL) {
perror("popen failed");
return EXIT_FAILURE;
}
size_t str_size = 1024;
char *stringts = malloc(str_size);
if (!stringts) {
perror("stringts allocation failed");
return EXIT_FAILURE;
}
stringts[0] = '\0';
char buf[128];
size_t n;
while ((n = fread(buf, 1, sizeof(buf) - 1, fp)) > 0) {
buf[n] = '\0';
size_t capacity = str_size - strlen(stringts) - 1;
while (n > capacity) {
str_size *= 2;
stringts = realloc(stringts, str_size);
if (!stringts) {
perror("stringts realloation failed");
return EXIT_FAILURE;
}
capacity = str_size - strlen(stringts) - 1;
}
strcat(stringts, buf);
}
printf("%s\n", stringts);
free(stringts);
if (pclose(fp) != 0) {
perror("pclose failed");
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
You have several flaws in your code:
char *lsl(){
char *stringts=malloc(1024);
chdir("/Users/file/path");
char * lsargs[] = { "/bin/ls" , "-l", NULL};
stringts="The result of ls-l in the created directory is:"+ execv(lsargs[0], lsargs);
return stringts;
}
If you malloc(3) a 1024 byte buffer into stringts pointer, but then you assign a different value to the pointer, making your buffer to be lost in the immensity of your RAM.
When you do execv(2) call, all the memory of your process is freed by the kernel and reloaded with an execution of the command ls -l, you'll get the output in the standard output of the process, and then you'll get the prompt of the shell. This makes the rest of your program unuseful, as once you exec, there's no way back, and your program is unloaded and freed.
You can add (+) to a pointer value (you indeed add to the address pointing to the string "The result of the ls -l..." and ---as the result of exec is nothing, as a new program is loaded--- you get nothing) If execv fails, then you get a pointer pointing to the previous char to that string, which is a valid expression in C, but makes your program to behave erratically in an Undefined Behaviour. Use strcpy(3), strcat(3), or snprintf(3), depending on the exact text you want to copy in the space of the buffer you allocated.
Your return an invalid address as a result. The problem here is that, if execv(2) works, it doesn't return. Only if it fails you get an invalid pointer that you cannot use (by the reason above), and of course ls -l has not been executed. Well, you don't say what you got as ouptut, so it is difficult for me to guess if you actually exec()d the program or not.
On other side, you have a popen(3) library function that allows you to execute a subprogram and allows you to read from a file descriptor its output (I recommend you not to chdir gratuitously in your program, as that is a global change in your program environment, IMHO it is better to pass ls(1) the directory you want to list as a parameter)
#include <stdio.h>
FILE *lsl() {
/* the call creates a FILE * descriptor that you can use as input and
* read the output of the ls command. It's bad resources use to try to
* read all in a string and return the string instead. Better read as
* much as you can/need and then pclose() the descriptor. */
return popen("/bin/ls -l /Users/file/path|", "rt");
}
and then you can read (as it can be very long output, you probably don't have enought buffer space to handle it all in memory if you have a huge directory)
FILE *dir = lsl();
if (dir) {
char buffer[1024];
while (fgets(buffer, sizeof buffer, dir)) {
process_line_of_lsl(buffer);
}
pclose(dir); /* you have to use pclose(3) with popen(3) */
}
If you don't want to use popen(3), then you cannot use execv(2) alone, and you have to fork(2) first, to create a new process, and exec() in the child process (after mounting the redirection yourself). Read a good introduction to fork()/exec() and how to redirect I/O between fork() and exec(), as it is far longer and detailed to put it here (again)
I have a program A that takes two arguments from stdin and exits with a unique code depending on the arguments. I am writing a program B that calls program A using fork and exec and let program B print out the code program A exits with. For some reason, program A doesn't seem to be getting the data I piped through to it in the child process of fork. I'm not sure if I'm piping the correct data to the child process.
Could someone help me please? Thanks!
Here is my code:
int program_B(void) {
char var_a[256];
char var_b[256];
int fd[2];
// Read from stdin
char *sendarray[2];
sendarray[0] = var_a;
sendarray[1] = var_b;
if(fgets(var_a, MAXLINE, stdin) == NULL) {
perror("fgets");
exit(1);
}
if(fgets(var_b, MAXLINE, stdin) == NULL) {
perror("fgets");
exit(1);
}
if (pipe(fd) == -1) {
perror("pipe");
exit(1);
}
int pid = fork();
// Child process -- error seems to be here.
if (pid == 0) {
close(fd[1]);
dup2(fd[0], fileno(stdin));
close(fd[0]);
execl("program_A", NULL);
perror("exec");
exit(1);
} else {
close(fd[0]);
write(fd[1], sendarray, 2*sizeof(char*));
close (fd[1]);
int status;
if (wait(&status) != -1) {
if (WIFEXITED(status)) {
printf("%d\n", WEXITSTATUS(status));
} else {
perror("wait");
exit(1);
}
}
}
return 0;
}
You are piping the wrong data to the child process.
I am assuming var_a and var_b are the strings you want to send to program A. They are both of type array of chars, which in C is the same thing as pointer to char (Actually there is a small difference between pointers and arrays but this is irrelevant for this problem). So they are actually just pointers to the first byte of each argument. sendarray, however is an array of char-pointers which is the same thing as a pointer to char-pointer. Keep this in mind for a second.
When calling write() the 2nd parameter tells it where the data is in memory. By passing sendarray, write thinks this sendarray points the data you want to write although it actually points to yet another pointer. So what happens is that the pointer values of var_a and var_b (which is what sendarray points to), are written to the pipe.
So you have to pass var_a and var_b to write(), since those are pointers to the actual data you want to send. Also you have to know how long (how many bytes) this data is. If var_a and var_b point to null-terminated strings, you can use strlen() to determine their length.
One last thing: I don't know how exactly your program A obtains 2 arguments from a continuous byte stream like stdin, but assuming it reads it line by line, you obviously have to send a new-line character from program B, as well.
So putting it all together your write statements should look something like this:
write(fd[1], var_a, strlen(var_a));
write(fd[1], "\n", 1);
write(fd[1], var_b, strlen(var_b));
Of course, if any of the assumptions I made is wrong, you have to adopt this code appropriately.
My code is really messy right now, so I think it would be easier to convey what I'm trying to do by just describing it.
I'm working on a shell for homework that needs to be able to redirect output to a file, just like the default shell. We were provided a preexisting shell and asked to modify it. The shell already sets up an argv for execve, but in order to implement redirection I need to remove the last two entries from the argv the program built (> and the file name), and after testing that by just freeing up the last two entries I think it's probably better to just make a copy, minus those two entries, as this program handles freeing up the argv and if I try to do that at some point before the predesignated time to do so I run into problems when I try to run another command.
My point is I'm having a hard time copying part of an array of strings that's going to serve as an argv. I've seen a couple of solutions posted, but they're all in C++, and I'm asked to do this in C.
Alternatively, I suppose it would also be sufficient if I could properly empty part of an argv. Here's the code I tried:
for(i=0;i<10;i++)
{
if(my_argv[i] == NULL)
{
break;
}
if(strcmp(my_argv[i], ">") == 0)
{
if(my_argv[i+1] != NULL)
{
strncpy(fileName, my_argv[i+1], strlen(my_argv[i+1]));
strncat(fileName, "\0", 1);
//bzero(my_argv[i+1], strlen(my_argv[i+1])+1);
//my_argv[i+1] = NULL;
//free(my_argv[i+1]);
} else {
printf("no file name given\n");
return;
}
//bzero(my_argv[i], strlen(my_argv[i])+1);
//my_argv[i] = NULL;
//free(my_argv[i]);
redirectOutput(cmd, fileName);
return;
}
}
The commented sections are where I copied in code from the function that empties my_argv to attempt to free up the contents of argv where the > and file name are. It still runs without those lines, but then I kick the can down the road with having to deal with the extra entries in my redirectOutput() function, which is an absolute train wreck. The free_argv() function looks like this:
void free_argv()
{
int index;
for(index=0;my_argv[index]!=NULL;index++) {
bzero(my_argv[index], strlen(my_argv[index])+1);
my_argv[index] = NULL;
free(my_argv[index]);
}
}
You're going to much more work than you need to do. You can safely do whatever you want to the pre-prepared argv, as long as you do it after the fork(), in the child (before the execve(), of course). No such modifications will affect the parent, and you don't need to worry about any cleanup because the exec replaces the old process image with the new one.
Example:
/* these are already provided: */
char *filename = /* ... */;
char **child_argv = /* ... */;
char **child_env = /* ... */;
pid_t pid = fork();
if (pid == 0) {
/* the child */
char **arg;
for (arg = child_argv; *arg && strcmp(*arg, ">"); arg += 1) { /* empty */ }
*arg = NULL; /* terminate the arg list at the ">", if present */
/* no need to clean up anything before execve() */
execve(filename, child_argv, child_env);
exit(1); /* execve() failed */
} else if (pid < 0) {
/* handle error */
}
/* the parent continues ... */
I have been trying to program a UNIX style shell command prompt in C. Within this program I need it to keep track of the commands that have already been used, so the user can recall the last command by entering 'r'. I made a globally initialized array to hold strings. Whenever the array of characters a user entered needs to be saved, I add it to the global array. I have tried memcpy, simply copying each value using a loop, and just copying the pointer. None of these have been working. I am not super familiar with C and I am sure it is a pointer problem.
Whenever I copy the pointer of inputBuffer to my global array string (it does get copied), however upon leaving the setup function this pointer disappears? I am not exactly sure what I am doing wrong.
Test:
(1)user input --> ls
string[0] = ls
(2)user input --> r
inputBuffer = ls
string[recent] = ls
incorrectly does...
inputBuffer = r
string[recent] = r
(I have included the relevant parts of the code.)
#define MAX_LINE 80 /* 80 chars per line, per command, should be enough. */
#define SAVED_BUFFER 100
char *string[SAVED_BUFFER];
int p = 0;
int recent = -1;
int stringSize = 0;
void setup(char inputBuffer[], char *args[],int *background)
{
int length, /* # of characters in the command line *
/* read what the user enters on the command line */
length = read(STDIN_FILENO, inputBuffer, MAX_LINE);
start = -1;
if (length == 0)
exit(0); /* ^d was entered, end of user command stream */
if (length < 0){
perror("error reading the command");
exit(-1); /* terminate with error code of -1 */
}
if (inputBuffer[0] == 'r' && inputBuffer[1] == '\n' && stringSize > 0) {
int k;
memcpy(inputBuffer, string[recent], strlen(string[recent]) + 1);
printf("%s",inputBuffer);
printf("%s",string[recent]);
}
else {
string[p] = inputBuffer;
printf("%s", string[0]);
stringSize++;
recent++; // one behind strings current array location, to get history
p++; // current string array spot
}
}
int main(void)
{
char inputBuffer[MAX_LINE]; /* buffer to hold the command entered */
int background; /* equals 1 if a command is followed by '&' */
char *args[MAX_LINE/2+1];/* command line (of 80) has max of 40 arguments */
while (1) { /* Program terminates normally inside setup */
background = 0;
printf("COMMAND2->");
fflush(0);
setup(inputBuffer, args, &background); /* get next command */
}
}
When you "save the input buffer" you actually only store a pointer to the inputBuffer array:
string[p] = inputBuffer;
The actual data is not copied, you just store a pointer to the global input buffer. When the next input replaces the old content of inputBuffer, you will see the new content even if you access it through string[recent].
The calls to memcpy don't actually do anything, since the passed input and output buffer all refer to the same memory.
To actually store a copy of the data you have to allocate new memory to store the copy. Since you are dealing with strings, this is most easily done with strdup(), which duplicates a string:
string[p] = strdup(inputBuffer);
Later, once you are done with such a copy and don't need it anymore you have to free the memory used by the copy:
free(string[p]);
Have you tried changing
char *string[SAVED_BUFFER];
to
char string[SAVED_BUFFER][MAX_LINE];
I think that's how you're treating it in the code