cat with fork execvp pipe and dup2 - c

This is one step of a set of exercises I'm doing. The program I write should take more than two arguments. The use of the first argument is not implemented yet. The rest of the arguments are a list of directories.
In this step what I have to do is to create an instance of cat for each directory given in arguments, take the contents of the all files of each directory using cat and print the content. I should be able to handle paths such as /home/directory and /home/directory/ both (with the last / or without)
Currently what I am doing is trying to run cat with argument /home/directory/* so that it will read all the files in the given directory and return the content of them. This is my code:
#include "Step1.h"
int main(int argc,char* argv[])
{
if(argc < 3)
{
printf("Usage: ./Step1 <exclusions file> <folder1> <folder2> <folder3> ...\n");
return -1;
}
int i;
for(i=2; i<argc; i++)
{
int catpipe[2];
if(pipe(catpipe))
{
printf("Error in pipe\n");
return -1;
}
pid_t pid = fork();
if(pid < 0)
{
printf("Error in fork\n");
return -1;
}
if(!pid)
{
dup2(catpipe[1],1); // cat pipe is used to get the output of cat program into this program.
close(catpipe[1]);
close(catpipe[0]);
char* args[3];
args[0] = "/bin/cat";
int length = strlen(argv[i]);
char* path;
if(argv[i][length - 1] != '/') // the path given does not have the ending /
{
path = malloc(length + 3);
strcpy(path,argv[i]);
path[length] = '/'; //append / at the end
path[length+1] = '*'; // append * at the end
path[length+2] = '\0';
}
else
{
path = malloc(length + 2); // the path contains the ending /
strcpy(path,argv[i]);
path[length] = '*'; // append * at the end
path[length+1] = '\0';
}
args[1] = path;
args[2] = NULL;
printf("%s\n",path);
execvp("/bin/cat",args);
}
close(catpipe[1]);
char buffer[200];
int total = read(catpipe[0],buffer,200); // read the output of cat program and print it.
buffer[total]='\0';
printf("The buffer contains: %s\n",buffer);
}
return 0;
}
I ran this code as follows:
mod#mod-Inspiron-N5110:~/project$ ./Step1 exclusions ./testdirectory1 ./testdirectory2/
and the result I got is:
/bin/cat: ./testdirectory1/*: No such file or directory
The buffer contains:
The buffer contains: ./testdirectory2/*
mod#mod-Inspiron-N5110:~/project$ /bin/cat: ./testdirectory2/*: No such file or directory
mod#mod-Inspiron-N5110:~/project$
but when I do:
mod#mod-Inspiron-N5110:~/project$ /bin/cat ./testdirectory1/*
The result is:
Testline 1 of testfile1 in testdirectory1
Test line 1 of testfile1 in testdirectory1
Testline 1 of testfile2 in testdirectory1
Testline 1 of testfile3 in testdirectory1
Please help me to get this result with my program.
Thanks in advance

I think I figured out the answer for my own question. I'm posting this in belief that somebody just like me would find it useful one day. But I'm not very good at C yet. So if this answer is wrong or if there's some mistake/weakness in this, feel free to correct it.
The key for my answer is using the function wordexp(). It can be used to expand the given path: tilde, regular expressions, variables, and all. So this is my answer:
#include "Step1.h"
int main(int argc,char* argv[])
{
if(argc < 3)
{
printf("Usage: ./Step1 <exclusions file> <folder1> <folder2> <folder3> ...\n");
return -1;
}
int i;
for(i=2; i<argc; i++)
{
int catpipe[2];
if(pipe(catpipe))
{
printf("Error in pipe\n");
return -1;
}
pid_t pid = fork();
if(pid < 0)
{
printf("Error in fork\n");
return -1;
}
if(!pid)
{
dup2(catpipe[1],1); // cat pipe is used to get the output of cat program into this program.
close(catpipe[1]);
close(catpipe[0]);
int length = strlen(argv[i]);
char* path;
if(argv[i][length - 1] != '/') // the path given does not have the ending /
{
path = malloc(length + 3);
strcpy(path,argv[i]);
path[length] = '/'; //append / at the end
path[length+1] = '*'; // append * at the end
path[length+2] = '\0';
}
else
{
path = malloc(length + 2); // the path contains the ending /
strcpy(path,argv[i]);
path[length] = '*'; // append * at the end
path[length+1] = '\0';
}
wordexp_t p;
char **w;
wordexp(path, &p, 0);
char** args = malloc((p.we_wordc + 2) * sizeof(char*));
args[0] = "/bin/cat";
w = p.we_wordv;
int j;
for (j = 0; j < p.we_wordc; j++)
args[j+1] = w[j];
args[p.we_wordc + 1] = NULL;
execvp("/bin/cat",args);
}
close(catpipe[1]);
char buffer[1024];
int total = read(catpipe[0],buffer,1024); // read the output of cat program and print it.
buffer[total]='\0';
printf("The buffer contains: %s\n",buffer);
}
return 0;
}

So basically what you get wrong in your code is that you assume that if you give /directory/* as an argument to cat, cat will expand it as "many files", whereas cat understands that you want to open a file called *.
The simple way to have a * expansion would be to call cat through a shell, which will be doing the expansion from * to "many files".
To achieve so, you can replace your execvp() with a system(…) call, which is basically execl("sh", "sh", "-c", …, NULL) where … is your command (i.e. you could simply call "/bin/sh", "-c", "/bin/cat", "/directory/*") and there * should be expanded by the shell.
Or you can work your own recursive directory walk algorithm, such as that one.

Related

I'm trying to execute read in lines from file in C in a shell environment

I can compile the code, execute it with the file as a command line argument, but nothing happens. The interactive mode function prompts as normal, but not the batchMode function.
I'm trying to read in a line, and then execute that line.
example file
date
ls -la
cd
(Without spacing between lines. I can't get the formatting right on here.)
#include<unistd.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>
#define bSize 1000
void driveLoop();
char *userInput(void);
void removeExit(char *original, char *subString); // removes string with substring "exit"
void batchMode(char *c);
int main(int argc, char **argv){
char *fTemp;
if (argc == 1)
driveLoop(); // calls the loop function that accepts input and executes commands.
else if (argc == 2)
batchMode(&argv[1][0]);
return 0;
}
void driveLoop(void){
char *comTokens[100];
char *tempTokens;
char *command;
char *cd;
char *cdDir;
char* cdTemp;
char cdBuf[bSize];
char checkExit[] = "exit";
for (;;){
printf("> ");
command = userInput(); // reads input
if (!*command) // allows for empty string error
break;
char *exitPtr = strstr(command, "exit"); // returns a value to a pointer if substring is found
removeExit(command, "exit");
puts(command); // updates the array after the function filter
int i = 0;
tempTokens = strtok(command, " \t\n"); // tokens are how the computer recognizes shell commands
while (tempTokens && i < 99){ // geeksforgeeks.com
comTokens[i++] = tempTokens;
tempTokens = strtok(NULL, "\t\n");
}
if (strcmp(comTokens[0], "exit") == 0) // exit if input is "exit" only
exit(0);
if(strcmp(comTokens[0], "cd") == 0){ // built in change directory command
cd = getcwd(cdBuf, sizeof(cdBuf));
cdDir = strcat(cd, "/");
cdTemp = strcat(cdDir, comTokens[1]); // cplusplus.com reference
chdir(cdTemp);
continue;
}
comTokens[i] = NULL;
pid_t cFork = fork(); // creates duplicate child process of parent
if (cFork == (pid_t) - 1){ // error check
perror("fork");
}
else if (cFork == 0) { // error codes found on cplusplus.com
execvp(comTokens[0], comTokens);
perror("exec");
}
else { // children are returned. parent executes
int status;
waitpid(cFork, &status, 0);
if (exitPtr != NULL){ // if substring exit was found, exit the program
exit(0);
}
}
}
}
char *userInput(void){ // referenced Linux man page - getline(3) (linux.die.net)
char *input = NULL;
size_t size = 0;
getline(&input, &size, stdin); // updates the size as it goes along
return input;
}
void removeExit(char *original, char *subString){ // removes exit from string
char *ex;
int len = strlen(subString);
while ((ex = strstr(original, subString))){ // Referenced from a Stack Overflow page.
*ex = '\0';
strcat(original, ex+len);
}
}
void batchMode(char *c){
char *tok[100];
char *batchTokens;
char *batchBuffer = NULL;
size_t batchSize = 0;
FILE *fp = fopen(c, "r");
unsigned int line = 1;
char buffer[bSize];
while(fgets(buffer, sizeof(buffer), fp)){
int i = 0;
char *toks = strtok(buffer, "\t\n");
while (toks && i < 99){
tok[i] = malloc (strlen(toks) + 1);
strcpy(tok[i++], toks);
toks = strtok(NULL, " \t\n");
}
tok[i] = NULL;
pid_t bFork = fork();
if (bFork == (pid_t) - 1)
perror("fork");
else if (bFork == 0){
execvp(tok[i], tok);
perror("exec");
}
else {
int status;
waitpid(bFork, &status, 0);
}
}
}
side note. This is a re-attempt from a previous question that was locked for inadequate information. I've updated my code and tried to be as detailed as possible.
I'll happily provide anything further to help answer my question.
Thank you all.
edit
I put in a fprintf to verify that it reads the file in, and it does.
First note in your input file, the last command cd isn't a system command, it is a shell built-it, so you would expect it to fail (unless handled specially).
Allocating for each token (tok[i]) as discussed with either strdup (if available) or simply malloc (strlen(toks) + 1); allows you to copy the current token to the block of memory allocated. Now each tok[i] will reference the individual token saved (instead of all pointing to the last token -- due to all being assigned the same pointer)
The biggest logic error in batchMode as your call to execvp with execvp (tok[i], tok); instead of properly providing the file to execute as tok[0]. Be mindful if the file to execute isn't in your PATH, you must provide an absolute path in your input file.
Making the changes, your batchMode could be written as follows (and removing all the unused variables and moving char *tok[100]; within the while loop so it is declared within that scope):
#include <sys/types.h>
#include <sys/wait.h>
...
void batchMode(char *c)
{
char buffer[bSize];
FILE *fp = fopen (c, "r");
if (!fp) {
perror ("fopen-c");
return;
}
while (fgets (buffer, sizeof(buffer), fp)) {
int i = 0;
char *tok[100];
char *toks = strtok(buffer, " \t\n");
while (toks && i < 99){
tok[i] = malloc (strlen(toks) + 1);
strcpy(tok[i++], toks);
toks = strtok(NULL, " \t\n");
}
tok[i] = NULL;
pid_t bFork = fork();
if (bFork == (pid_t) - 1)
perror("fork");
else if (bFork == 0){
execvp (tok[0], tok);
perror("exec");
}
else {
int status;
waitpid(bFork, &status, 0);
}
}
}
Example Input Files
I have two simple input files tested:
$ cat dat/toksfile.txt
echo hello
echo goodbye
$ cat dat/toksfile2.txt
date
ls -al dat/toksfile.txt
cd
Example Use/Output
$ ./bin/shellorbatch dat/toksfile.txt
hello
goodbye
$ ./bin/shellorbatch dat/toksfile2.txt
Tue Feb 4 23:47:00 CST 2020
-rw-r--r-- 1 david david 24 Feb 4 23:24 dat/toksfile.txt
exec: No such file or directory
Look things over and let me know if you have questions:

Problems of History for my own Shell in C

As a project, I have to make my own shell. I did it but I have some problems with the history feature.
Here's piece of my code:
int main(int argc, char* argv[])
{
char saisie[300], cwd[1024];
char* nom = getenv("USER");
char* backup[MAXCMD];
int boucle = 1, n = 0, i, u = 0, b = 0;
for(i = 0; i < MAXCMD; i++)
{
backup[i] = NULL;
}
for(i = 0; i < MAX_INPUT_SZ; i++)
{
saisie[i] = 0;
}
char* cmd[MAXPARAMS]; //MAXPARAMS is 20
while( boucle == 1)
{
printf("%s#sam:~ %s> ", nom, (getcwd(cwd, sizeof(cwd))));
fgets(saisie,MAX_INPUT_SZ,stdin);
printf("\n");
split_input(saisie, cmd);
free(backup[u]);
backup[u] = strdup(saisie);
u = (u + 1) % MAXCMD;
b = switchcmd(cmd,backup,b,u);
start(cmd,b);
b = 0; //débloquage fonction start
}
return 0;
}
I print the history with this fonction:
int historique(char* backup[], int u)
{
int i = u;
int place = 1;
do
{
if (backup[i])
{
printf("%4d: %s\n", place, backup[i]);
place++;
}
i = (i + 1) % MAXCMD;
} while (i != u);
return 0;
}
B is used to block the execution fonction (start) when user enter "cd" or "history", because it will generate an error.
Here's the fonction triggered when user enters "cd", "history", or "exit":
int switchcmd(char** cmd,char** backup, int b,int u)
{
int i, n = 3, switch_value = 0;
char* error;
char* listcmd[n];
listcmd[0] = "cd";
listcmd[1] = "exit";
listcmd[2] = "history";
for (i = 0; i < n; ++i)
{
if(strcmp(cmd[0], listcmd[i]) == 0)
{
switch_value = i + 1;
break;
}
}
switch (switch_value)
{
case 1:
chdir(cmd[1]);
b = 1;
error = strerror(errno);
if (*error != 0)
{
printf("sam: %s: %s\n", cmd[0], error);
}
break;
case 2:
printf("Bye bye\n");
exit(0);
case 3:
historique((char**)backup,u);
b = 1;
break;
}
return b;
}
When I execute my shell, and enter these commands successively, they work. °i1
> clear
> ls -a -l
> ls -a
> cd ..
> man chdir
Then "history" for printing the history, I have this : °i2
1: clear
2: ls
3: ls
4: cd
5: man
6: history
and I want this output, with all parameters: °i3
1: clear
2: ls -a -l
3: ls -a
4: cd ..
5: man chdir
6: history`
I dont know why, and I don't understand why strdup does not duplicate my cmd in backup at it should.
Any help please?
When the user command is store in ' saisie ', this command is duplicate and split in array of parameters. And I use ' cmd ' in the execution fonction, with execvp.
Then there is your big problem, cmd has a fixed length of 1, if you use that to stored the command arguments for execvp, then you can only store one thing: NULL.
You have two options:
Use a large fixed size, for example char *cmd[100] where you can store up
to 99 arguments and no more. This is the easiest solution but it is not flexible
enough. Although some systems have a limit on the number of arguemnts you can
pass to a new process, I don't know if there is a limit for all systems,
this and this might help you there.
Dynamically create an array of char pointers depending on the command line.
This is more work but this is also the more flexible solution. Assuming that
your command line does not have support for pipes (|) and redirections (<,
<<, >, >>), then split_input could look like this:
char **split_input(const char *cmd)
{
if(cmd == NULL)
return NULL;
char **argv = NULL, **tmp;
char *line = strdup(cmd);
if(line == NULL)
return NULL;
const char *delim = " \t\n";
char *token = strtok(line, delim);
if(token == NULL)
{
free(line);
return NULL;
}
size_t len = 0;
do {
char *arg = strdup(token);
if(arg == NULL)
{
free_argv(argv);
free(line);
return NULL;
}
tmp = realloc(argv, (len + 2) * sizeof *argv);
if(tmp == NULL)
{
free_argv(argv);
free(line);
return NULL;
}
argv = tmp;
argv[len++] = arg;
argv[len] = NULL; // argv must be NULL terminated
} while(token = strtok(NULL, delim));
free(line);
return argv;
}
void free_argv(char **argv)
{
if(argv == NULL)
return;
for(size_t i = 0; argv[i]; ++i)
free(argv[i]);
free(argv);
}
Now you can use it like this:
while( boucle == 1)
{
printf("%s#sam:~ %s> ", nom, (getcwd(cwd, sizeof(cwd))));
fgets(saisie,MAX_INPUT_SZ,stdin);
printf("\n");
char **argv = split_input(saisie);
if(argv == NULL)
{
fprintf(stderr, "Cannot split command line, not enough memory\n");
continue;
}
free(backup[u]);
backup[u] = strdup(argv[0]); // <-- passing argv[0], not argv
// but perhaps what you really want
// is strdup(saisie)
u = (u + 1) % MAXCMD;
b = switchcmd(argv,backup,b,u);
start(argv,b);
b = 0; //débloquage fonction start
free_argv(argv);
}
You are also doing
backup[u] = strdup(cmd);
but the problem is that cmd is an array of char pointers, strdup expects a
const char*, you are passing the wrong type. It should be strdup(cmd[0]) or
strdup(saisie) if you want to store the whole command.

Strange behavior when manipulating char* array in C

I'm trying to create a command line shell for an Operating Systems class. One of our assignments is to create a builtin "history" command that prints out the last 10 commands executed in the shell. Here is the code I have written for the "history" command:
char* cmd_hsitory[10]; // This is a global variable
int add_history(char **args) {
cmd_history[9] = NULL;
for(int i = 8; i >= 0; i--) {
cmd_history[i+1] = cmd_history[i];
}
cmd_history[0] = *args;
return 1;
}
Where the char **args argument is the last command exectued. Here is the function that prints the history:
int lsh_history(char **args) {
printf("Last 10 commands: \n");
for(int i = 0; i < 10; i++) {
printf("%s\n", cmd_history[i]);
}
return 1;
}
Some strange behavior is happening with this code. For instance, when I run the commands [cd, cd, ls, history] in succession, this is the printed output:
Last 10 commands:
ls
ls
cd
(null)
(null)
(null)
(null)
(null)
(null)
(null)
The first problem here is that I ran the cd command twice, and the ls command only once. If I run the "history" command again I get:
Last 10 commands:
history
ls
ls
cd
(null)
(null)
(null)
(null)
(null)
(null)
This seems correct with the exception of the 2 ls commands vs the 1 cd command.
This isn't very consistent, though, as sometimes I'll get mixed-up commands and the "history" command will show up several times.
If someone told me what's glaringly wrong with my code, that would be of great help. Thanks!
EDIT: Here is the full source code:
P.s. Most of this code is pulled from the internet (Stephen Brennan) and I'm building on top of it to learn. I will not submit this code as my assignment.
#include <sys/wait.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
/*
Function Declaration for history queue
*/
int add_history(char **args);
/*
Function Declarations for builtin shell commands:
*/
int lsh_cd(char **args);
int lsh_help(char **args);
int lsh_exit(char **args);
int lsh_history(char **args);
/*
List of builtin commands, followed by their corresponding functions.
*/
char *builtin_str[] = {
"cd",
"help",
"exit",
"history"
};
int (*builtin_func[]) (char **) = {
&lsh_cd,
&lsh_help,
&lsh_exit,
&lsh_history
};
char *cmd_history[10];
int lsh_num_builtins() {
return sizeof(builtin_str) / sizeof(char *);
}
int add_history(char **args) {
cmd_history[9] = NULL;
for(int i = 8; i >= 0; --i) {
cmd_history[i+1] = cmd_history[i];
}
cmd_history[0] = NULL;
cmd_history[0] = *args;
return 1;
}
/*
Builtin function implementations.
*/
/**
#brief Builtin command: command history.
#param args List of args. args[0] is "history".
#return Always returns 1 to continue executing.
*/
int lsh_history(char **args) {
printf("Last 10 commands: \n");
for(int i = 0; i < 10; i++) {
printf("%s\n", cmd_history[i]);
}
return 1;
}
/**
#brief Bultin command: change directory.
#param args List of args. args[0] is "cd". args[1] is the directory.
#return Always returns 1, to continue executing.
*/
int lsh_cd(char **args)
{
if (args[1] == NULL) {
chdir("/Users/Landon/");
} else {
if (chdir(args[1]) != 0) {
perror("lsh");
}
}
return 1;
}
/**
#brief Builtin command: print help.
#param args List of args. Not examined.
#return Always returns 1, to continue executing.
*/
int lsh_help(char **args)
{
int i;
printf("Stephen Brennan's LSH\n");
printf("Type program names and arguments, and hit enter.\n");
printf("The following are built in:\n");
for (i = 0; i < lsh_num_builtins(); i++) {
printf(" %s\n", builtin_str[i]);
}
printf("Use the man command for information on other programs.\n");
return 1;
}
/**
#brief Builtin command: exit.
#param args List of args. Not examined.
#return Always returns 0, to terminate execution.
*/
int lsh_exit(char **args)
{
return 0;
}
/**
#brief Launch a program and wait for it to terminate.
#param args Null terminated list of arguments (including program).
#return Always returns 1, to continue execution.
*/
int lsh_launch(char **args)
{
pid_t pid;
int status;
pid = fork();
if (pid == 0) {
// Child process
if (execvp(args[0], args) == -1) {
perror("lsh");
}
exit(EXIT_FAILURE);
} else if (pid < 0) {
// Error forking
perror("lsh");
} else {
// Parent process
do {
waitpid(pid, &status, WUNTRACED);
} while (!WIFEXITED(status) && !WIFSIGNALED(status));
}
return 1;
}
/**
#brief Execute shell built-in or launch program.
#param args Null terminated list of arguments.
#return 1 if the shell should continue running, 0 if it should terminate
*/
int lsh_execute(char **args)
{
int i;
if (args[0] == NULL) {
// An empty command was entered.
return 1;
}
for (i = 0; i < lsh_num_builtins(); i++) {
if (strcmp(args[0], builtin_str[i]) == 0) {
return (*builtin_func[i])(args);
}
}
return lsh_launch(args);
}
#define LSH_RL_BUFSIZE 1024
/**
#brief Read a line of input from stdin.
#return The line from stdin.
*/
char *lsh_read_line(void)
{
int bufsize = LSH_RL_BUFSIZE;
int position = 0;
char *buffer = malloc(sizeof(char) * bufsize);
int c;
if (!buffer) {
fprintf(stderr, "lsh: allocation error\n");
exit(EXIT_FAILURE);
}
while (1) {
// Read a character
c = getchar();
if (c == EOF) {
exit(EXIT_SUCCESS);
} else if (c == '\n') {
buffer[position] = '\0';
return buffer;
} else {
buffer[position] = c;
}
position++;
// If we have exceeded the buffer, reallocate.
if (position >= bufsize) {
bufsize += LSH_RL_BUFSIZE;
buffer = realloc(buffer, bufsize);
if (!buffer) {
fprintf(stderr, "lsh: allocation error\n");
exit(EXIT_FAILURE);
}
}
}
}
#define LSH_TOK_BUFSIZE 64
#define LSH_TOK_DELIM " \t\r\n\a"
/**
#brief Split a line into tokens (very naively).
#param line The line.
#return Null-terminated array of tokens.
*/
char **lsh_split_line(char *line)
{
int bufsize = LSH_TOK_BUFSIZE, position = 0;
char **tokens = malloc(bufsize * sizeof(char*));
char *token, **tokens_backup;
if (!tokens) {
fprintf(stderr, "lsh: allocation error\n");
exit(EXIT_FAILURE);
}
token = strtok(line, LSH_TOK_DELIM);
while (token != NULL) {
tokens[position] = token;
position++;
if (position >= bufsize) {
bufsize += LSH_TOK_BUFSIZE;
tokens_backup = tokens;
tokens = realloc(tokens, bufsize * sizeof(char*));
if (!tokens) {
free(tokens_backup);
fprintf(stderr, "lsh: allocation error\n");
exit(EXIT_FAILURE);
}
}
token = strtok(NULL, LSH_TOK_DELIM);
}
tokens[position] = NULL;
return tokens;
}
/**
#brief Loop getting input and executing it.
*/
void lsh_loop(void)
{
char *line;
char **args;
int status;
do {
printf("> ");
line = lsh_read_line();
args = lsh_split_line(line);
status = lsh_execute(args);
add_history(args);
free(line);
free(args);
} while (status);
}
/**
#brief Main entry point.
#param argc Argument count.
#param argv Argument vector.
#return status code
*/
int main(int argc, char **argv)
{
// Load config files, if any.
// Run command loop.
lsh_loop();
// Perform any shutdown/cleanup.
return EXIT_SUCCESS;
}
You need to strdup() the arg strings over to your cmd_history pointers, not just have your cmd_history pointers reference the original command string. When you free(line), you're putting the memory that your cmd_history pointers are referencing back into the free pool. In the next iteration of the loop you may or may not overwrite that data.
You wrote ((Comments Added)):
void lsh_loop(void)
{
...
line = lsh_read_line(); //mallocs line
args = lsh_split_line(line); //mallocs args array; consisting of references into line[]
status = lsh_execute(args); //no promblem here
add_history(args); // ***PERMANETNLY*** stores args[0] into cmd_history[]!!!
free(line); //frees line[]
free(args); //frees the pointer array into freed line[], which was copied to cmd_history[] --> cmd_history[] points to unallocated memory!!!
}
The main problem is related to strtok(): It returns a pointer into the spitted string, within the original string: If you later free the token pointer array or the original string and use it later --> UB.
Additionally lsh_history() kills also last entry which would leave a memory leak behind, if you would allocated that entry firmly.
Solution:
Establish for cmd_history[] your own storage of the strings, duplicate and free them properly in lsh_history().

C Language- freeing memory after using strtok to build char**

and sorry for the title I couldn't think of a better way to phrase it.
so I have a C assignment working with fork and exec.
I have three programs called ps, echo and history all of them take different arguments. The final program is called shell and it takes in commands from stdin and calls for exec upon taking a proper command.
example:
ps -a
echo Hello World
history 1.txt
once it reads a line and find it's a valid command it makes a child process and calls for exec.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
const int MAX_LINE = 100;
const char *HISTORY = "./history";
const char *PS = "./ps";
const char *ECHO = "./echo";
void call_cmd(int cmd, char *const argv[]);
/* main function */
int main(int argc, char** argv)
{
FILE * out;
char line[MAX_LINE], line_print[MAX_LINE], seps[] = " \n", rm[80];
char *first, *tmp, ** params;
pid_t pid;
int cmd = -1, i = 0,j= 0;
if (argc != 2)
{
printf("Invalid arguments");
return EXIT_FAILURE;
}
out = fopen(argv[1],"w");
if (out == NULL)
{
perror("Couldn't open file to write");
return EXIT_FAILURE;
}
while(fgets(line,sizeof(line),stdin) != NULL)
{
strcpy(line_print,line);
params = (char**) malloc(sizeof(char*));
tmp = strtok(line,seps);
while (tmp != NULL)
{
if(i != 0)
params = (char**) realloc(params,sizeof(char*) * (i + 1));
params[i] = tmp;
j++;
tmp = strtok(NULL,seps);
i++;
}
first = params[0];
if (strcmp("exit",first) == 0)
{
sprintf(rm,"rm %s",argv[1]);
system(rm);
exit(0);
}
if(strcmp("echo",first) == 0)
cmd = 0;
if(strcmp("history",first) == 0)
cmd = 1;
if(strcmp("ps",first) == 0)
cmd = 2;
if(cmd == -1){
perror("\nInvalid Command\n");
}
if(cmd >= 0)
{
fprintf(out,"%s",line_print);
pid = fork();
if (pid == -1)
{
perror("Error Creating Child");
return EXIT_FAILURE;
}
if(pid == 0)
{
call_cmd(cmd,params);
exit(0);
}
}
for (i = 0; i < j ; i++)
free(params[i]);
free(params);
i = j = 0;
cmd = -1;
}
fclose(out);
return EXIT_SUCCESS;
}
void call_cmd(int cmd, char *const argv[])
{
switch(cmd)
{
case 0:
execv(ECHO, argv);
break;
case 1:
execv(HISTORY, argv);
break;
default:
execv(PS, argv);
break;
}
}
that is my code so far, it behaves in a weird way causing segmentation faults,
I'm pretty sure it's because of the way I split the parameters and free them.
example output:
*** Error in `./shell': double free or corruption (out): 0x00007ffe58f1a630 ***
Parent Id: 1928
Aborted (core dumped)
so I keep editing the for loop
for (i = 0; i < j ; i++)
free(params[i]);
all that does is just jump from double free to segmentation faults or I write a command like ps or history and it does nothing, so I must be doing something but I'm truly lost been trying to fix it for two days with, so if you see what I did wrong please point it out.
Thank you.
strtok parses a string in-place so you should not free the individual results. They are portions of the original string. You can use the POSIX function strdup to make copies that can be free'd, and will persist beyond the life of the original buffer contents.
You should add
params[0] = NULL;
right after the initial malloc (or use calloc) otherwise you'll be using an unitialized pointer if the line is empty. Then at the end
free(params);
you don't need to free any of params[i] since those are pointers into the local line[] buffer.

Using execvp in C to copy files under linux

Ladies and gentlemen. I have a problem when using execvp inside a C program to copy files, it just doesn't want to work. The tar and mv commands do not work as well, and i presume that anything that has to do with creating files has some kind of problem as well. The weird thing is that commands like ls (with its options) and echo work as well when called with execvp.
Here is the code in question:
int main () {
char *curr_working_dir, *input, *inputCpy, *command, **args;
struct timeval start, end, duration;
system("clear");
while(1) {
curr_working_dir = getcwd(NULL, 0);
printf("%s $ ", curr_working_dir);
free(curr_working_dir);
fgets(input, 200, stdin);
input[strlen(input) - 1] = '\0';
inputCpy = cpyInput(input);
command = strtok(input, " ");
if(!(strcmp(command, "exit"))) {
break;
}
else if(!(strcmp(command, "cd"))) {
command = strtok(NULL, " ");
struct stat s;
if(!stat(command, &s) && S_ISDIR(s.st_mode)) {
chdir(command);
}
else {
chdir(getenv("HOME"));
}
free(inputCpy);
command = NULL;
}
else {
int pid = fork();
if(pid < 0) {
printf("Error al crear el proceso hijo\n");
}
else if(pid == 0) {
args = constructArgs(inputCpy);
execvp(command, args);
printf("execvp fallo: %s\n", strerror(errno));
}
else {
gettimeofday(&start, NULL);
wait((int*)NULL);
gettimeofday(&end, NULL);
timersub(&end, &start, &duration);
printf("Duracion del comando: %ld.%06ld\n", (long int)duration.tv_sec, (long int)duration.tv_usec);
free(inputCpy);
command = NULL;
}
}
}
return 0;
}
Here are some helper functions that i use in my program:
char **constructArgs(char *input) {
int i = 0;
char *argActual, **args;
argActual = strtok(input, " ");
args = malloc(sizeof(char *));
while(argActual != NULL) {
args[i] = malloc((strlen(argActual) + 1) * sizeof(char));
strcpy(args[i], argActual);
i += 1;
args = realloc(args, (i + 1) * sizeof(char *));
argActual = strtok(NULL, " ");
}
args[i] = NULL;
return args;
}
char *cpyInput(char *input) {
char *cpy;
cpy = malloc((strlen(input) + 1) * sizeof(char));
strcpy(cpy, input);
return cpy;
}
I use constructArgs to build an array of args to a particular program that i will call through execvp by parsing an input string containing the commands with its arguments. I can change directories, call echo, list the contents of directories, but when i try to run cp, or mv, or tar it doesn't do anything.
I have checked that i am sending the correct info to execvp; have a look at this for example:
let's say i have a file in /home/john/r1.sav and i want to copy this to another folder located in /home/john/anotherfolder. I debugged my program and this is what i am getting as the content for both the command and args arguments right before the call to execvp:
command = cp
args[0] = cp
args[1] = /home/john/r1.sav
args[2] = /home/john/anotherfolder
These are the expected inputs for execvp if you want to use cp through it. But it refuses to do anything. execvp receives as its first argument the name of the program to be executed, and an array of the arguments that the program is going to need, with the first argument in the array of arguments having the name of the program as well, which is what i'm doing. Everything works just fine with ls, or echo, but not with cp, or mv or tar.
Thank you in advance for any help you can shed on this peculiar problem.
I came across this post because I had the same issue. I don't know what the issue is, but once I declared an int for the return value it worked!
char *cmd = "cp";
char *argv[3];
argv[0] = "cp";
argv[1] = "source.c";
argv[2] = "destination.c;
int value = execvp(cmd, argv);
//Unreachable

Resources