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

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.

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:

Writing my own shell in c - free() causing problems

I'm trying to write my own shell, but something is not working with the allocations and free. I reviewed my code over and over, and I cannot understand why my free function is causing me problems... When I don't use it, everything works fine, but when I use it, the code stops working after the second iteration...
I would really appreciate your help...
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <limits.h> //for PATH_MAX - longest path name permited in linux
#include <sys/wait.h>
#include <fcntl.h>
typedef struct{
char **parametersArray; //this array contains the command and the parameters
int size_of_array; //the number of strings in the array
int toFileFlag; //if we wnat to write to file
char *toFile; //name of file to write to
int fromFileFlag;//if we wnat to read from file
char *fromFile; //name of file to read to
}UserInput;
int runInBackground = 0; //is command running in background? if so, runInBackground=1;
//********************************************************************************************************************
//functions list:
UserInput* inputTokenization(char *a); //recieve string of the user input, and returns pointer to struct of UserInput.
void execCommand(UserInput *user_input); //exec the requestd command with the parameters given
void free_All_Allocations(UserInput *userinput);
//*******************************************************************************************************************
int main(int argc, char *argv[])
{
char userInputTxt[LINE_MAX]; //the line the user enters (hopefully command+parameters)
UserInput *u_i;
int i = 0;
while(1)
{
i = 0;
printf("\033[1;35m"); //print in with color (purple)
printf("### "); //### is the prompt I chose
fflush(stdout);
memset(userInputTxt, 0, LINE_MAX); //cleaning array from previous iteration
read(0, userInputTxt, LINE_MAX);
if(strcmp(userInputTxt, "exit\n") == 0) //exit the program if the user enters "exit"
exit(EXIT_SUCCESS);
u_i = inputTokenization(userInputTxt); //parsing the char array userInputTxt
execCommand(u_i);
free_All_Allocations(u_i);
}
}
UserInput* inputTokenization(char *a)
{
int i=0, size;
size = strlen(a);
UserInput *user_input = (UserInput*)malloc(sizeof(UserInput)*1);
if(user_input == NULL)
{
perror("failed to allocate memory");
exit(EXIT_FAILURE);
}
user_input->fromFileFlag = 0;
user_input->toFileFlag = 0;
user_input->size_of_array = 2;
//counting how many token we have
while(i<size)
{
if(a[i] == ' ')
(user_input->size_of_array)++;
if (a[i] != '<' || a[i] != '>' )
break;
i++;
}
printf("%d\n", user_input->size_of_array);
//we don't want to change original array(a), so we'll copy a to tmp and use tmp
char *tmp = (char*)malloc(size+1);
if(tmp == NULL)
{
perror("failed to allocate memory");
exit(EXIT_FAILURE);
}
strncpy(tmp, a, size-1);
//we'll allocate array of arrays. It's size: number of tokens in the original array, even though we might not use all of it-
//some tokens might be name of file to read or write to
user_input->parametersArray = (char**)malloc(user_input->size_of_array);
if(user_input->parametersArray == NULL)
{
perror("failed to allocate memory");
exit(EXIT_FAILURE);
}
i=0;
char* token = strtok(tmp, " ");
user_input->parametersArray[i] = (char*)malloc(strlen(token)+1);
if(user_input->parametersArray[i] == NULL)
{
perror("failed to allocate memory");
exit(EXIT_FAILURE);
}
strcpy(user_input->parametersArray[i], token);
i++;
while(token != NULL)
{
token = strtok(NULL, " ");
if(token !=NULL)
{
if(strcmp(token, "<") != 0 && strcmp(token, ">") !=0 && strcmp(token, "&") != 0)
{
user_input->parametersArray[i] = (char*)malloc(strlen(token)+1);
if(user_input->parametersArray[i] == NULL)
{
perror("failed to allocate memory");
exit(EXIT_FAILURE);
}
strcpy(user_input->parametersArray[i], token);
i++;
continue;
}
if(strcmp(token, "<") == 0)
{
user_input->fromFileFlag = 1;
token = strtok(NULL, " ");
if(token !=NULL)
{
user_input->fromFile = (char*)malloc(strlen(token)+1);
if(user_input->fromFile == NULL)
{
perror("failed to allocate memory");
exit(EXIT_FAILURE);
}
strcpy(user_input->fromFile, token);
}
}
if(strcmp(token, ">") == 0)
{
user_input->toFileFlag = 1;
token = strtok(NULL, " ");
if(token != NULL)
{
user_input->toFile = (char*)malloc(strlen(token)+1);
if(user_input->toFile == NULL)
{
perror("failed to allocate memory");
exit(EXIT_FAILURE);
}
strcpy(user_input->toFile, token);
}
}
if(strcmp(token, "&") == 0)
{
runInBackground = 1;
break;
}
}
}
user_input->parametersArray[i] = NULL;
free(tmp);
return user_input;
}
void execCommand(UserInput *user_input)
{
pid_t pid;
int status;
pid = fork();
if(pid == -1) //fork failed
{
perror("fork() failed");
exit(EXIT_FAILURE);
}
if(pid == 0) //child process
{
if(user_input->fromFileFlag == 1) //if we have file to read from
{
close(0);
if(open(user_input->fromFile, O_RDONLY) == -1)
{
perror("open file to read failed");
exit(EXIT_FAILURE);
}
}
if(user_input->toFileFlag == 1) //if we have file to write to
{
close(1);
if(open(user_input->toFile, O_WRONLY | O_CREAT, 0766) == -1)
{
perror("open file to write failed");
exit(EXIT_FAILURE);
}
}
if(execvp(user_input->parametersArray[0], user_input->parametersArray) == -1)
{
perror("execvp() failed");
exit(EXIT_FAILURE);
}
}
if(runInBackground == 0) //as long as this is the only command to execute,
waitpid(pid, &status, 0); //wait until chile process (execvp) finish. Otherwise, father process go again, and chile process run in background
}
void free_All_Allocations(UserInput *userinput)
{
int i=0;
while(userinput->parametersArray[i] != NULL)
{
free(userinput->parametersArray[i]);
i++;
}
free(userinput->parametersArray);
if(userinput->fromFileFlag == 1)
free(userinput->fromFile);
if(userinput->toFileFlag == 1)
free(userinput->toFile);
free(userinput);
}
I recommend the use of valgrind.
Compile with your code with the flag -ggdb3 and then execute valgrind with your program. It'll show you all the invalid reads and writes during the execution of the program. Not only that, it'll tell you exactly in what line they happen and the corresponding function call trace.
This question is a great starting point if you're a beginner to valgrind.
One problem is that your "counting how many token we have" is wrong. It will exit on the first iteration, because the condition a[i] != '<' || a[i] != '>' will always be true. I think the comparison you want is a[i] == '<' || a[i] == '>', which will exit the loop if either of those characters is found. This will result in user_input->size_of_array being 2 (or 3, if the first character in a is a space). Later, when you actually pull tokens from the string, you write past allocated memory if there are more than two (or possibly three) tokens.
This counting loop itself is flawed, because it counts differently than the loop that actually extracts the tokens. (For example, if a token is "a>b", your counter would stop but the tokenizer loop would treat that as a token and keep going.) It would be better to use the same sort of loop to count the token, using strtok, or better yet use a way to dynamically resize your parameters array so you only need to make one pass and don't need to count. With either loop, multiple adjacent spaces result in an empty token.
But that isn't all. The count being wrong isn't currently an issue because of the next problem: the allocation for user_input->parametersArray uses the wrong size. Since you want user_input->size_of_array elements, you should use
user_input->parametersArray = malloc(user_input->size_of_array * sizeof(char *));
or, to avoid problems with the proper type, you can go with
user_input->parametersArray = malloc(user_input->size_of_array * sizeof(*user_input->parametersArray));
Note that I have removed the cast of the return value from malloc. It is not necessary in C, and can lead to subtle problems if the type used in the cast is not the correct one.
Another problem is the call to strncpy. Since the length of the string is size, the terminating nul character will not be copied. Since you now the buffer is big enough, you should just use strcpy (or strcpy_s if your compiler is new enough to support it).

execvp - why does my program exit? [duplicate]

This question already has answers here:
program stops after execvp( command.argv[0], command.argv)
(3 answers)
Closed 7 years ago.
I'm executing a program that parses the input to an array and runs function on it. the code is:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <pthread.h>
// arglist - a list of char* arguments (words) provided by the user
// it contains count+1 items, where the last item (arglist[count]) and
// *only* the last is NULL
// RETURNS - 1 if should cotinue, 0 otherwise
int process_arglist(int count, char** arglist);
void main(void) {
while (1) {
char **arglist = NULL;
char *line = NULL;
size_t size;
int count = 0;
if (getline(&line, &size, stdin) == -1)
break;
arglist = (char**) malloc(sizeof(char*));
if (arglist == NULL) {
printf("malloc failed: %s\n", strerror(errno));
exit(-1);
}
arglist[0] = strtok(line, " \t\n");
while (arglist[count] != NULL) {
++count;
arglist = (char**) realloc(arglist, sizeof(char*) * (count + 1));
if (arglist == NULL) {
printf("realloc failed: %s\n", strerror(errno));
exit(-1);
}
arglist[count] = strtok(NULL, " \t\n");
}
if (count != 0) {
if (!process_arglist(count, arglist)) {
free(line);
free(arglist);
break;
}
}
free(line);
free(arglist);
}
pthread_exit(NULL);
}
and my function is:
int process_arglist(int count, char** arglist) {
int i;
for (i = 0; i < count; i++) {
//printf("%s\n", arglist[i]);
execvp(arglist[0], arglist);
}
}
when just printed the names (marked), it did not terminate. but when I try to use execvp, it stops after one iteration. Can someone tell me why and what to do?
This is not a bug, it is the way it is supposed to work. execvp replaces the current process with the new process, keeping some of the file handles open.
If you want to launch a new process, you must use fork() and call execvp() in the child process.
Check the man pages for fork() and execvp().

Can't solve Segmentation fault (core dumped) in c

we are writing a program that has to mimic the Linux shell.
It is consisted of some parts. Every part does what the previous part did, plus something extra.
Part one runs single commands like ls, pwd etc. No parameters no
redirection.
Part two runs single commands plus redirection.
This is where we are stuck.. We compile myShell 2 without errors. But when we are typing any command we get the segmentation fault.
We're sorry for writing almost all out code here(myShell 1 not included), we just wanted to make sure you have everything you need to tell us where we are wrong.
functions.h
#ifndef FUNCTIONS_H
#define FUNCTIONS_H
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#define max 260
extern int argCounter(char *argLine,char *delim);
extern void myExec(char *argLine,int howMany,char *delim);
extern char **argArray(char* argLine,int howMany,char *args[],char *delim);
void myExecRedir(char **tokens,char* argLine, int howMany);
#endif
functions.c
#include "functions.h"
int argCounter(char *argLine,char *delim)
{
char *temp;
memcpy(temp,argLine,strlen(argLine));
char *pointer = strtok(temp, delim);
int counter = 0;
while (pointer != NULL)
{
counter++;
pointer = strtok(NULL, delim);
}
return counter;
}
void myExec(char *argLine,int howMany,char *delim)
{
char temp[max];
memset(temp,0,max);
char *args[howMany];
argLine[strlen(argLine)-1] = '\0';
//argArray breaks the argLine and returns an array that contains each argument
argArray(argLine,howMany,args,delim);
execvp(args[0],args);
perror("ERROR: wrong command!");
}
char **argArray(char *argLine,int howMany,char *args[],char *delim)
{
args[howMany] = NULL;
char *pointer = strtok(argLine, delim);
int counter = 0;
while (pointer != NULL)
{
args[counter]=pointer;
counter++;
pointer = strtok(NULL, delim);
}
return args;
}
void myExecRedir(char **tokens,char* argLine, int howMany)
{
char *delim3="<";
char *delim4=">";
int howManyIn= argCounter(argLine,delim3);
int howManyOut= argCounter(argLine,delim4);
if(howManyOut= howManyIn)
{
int fdin = open(tokens[1],O_RDWR);
int fdout = open(tokens[2],O_RDWR);
if(dup2(fdin,fdout) >= 0)
{
tokens[1]=NULL;
tokens[2]=NULL;
execvp(tokens[0],tokens);
}
else
{
printf("ERROR in dup2\n");
}
}
else if(howManyIn== 0) //means we only have > redirection
{
int fdout = open(tokens[1],O_RDWR);
if(dup2(2,fdout) >= 0)
{
tokens[2]=NULL;
execvp(tokens[0],tokens);
}
else
{
printf("ERROR in dup2\n");
}
}
else //means we only have redirection
{
int fdin = open(tokens[1],O_RDWR);
if(dup2(fdin,1) >= 0)
{
tokens[2]=NULL;
execvp(tokens[0],tokens);
}
else
{
printf("ERROR in dup2\n");
}
}
}
myShell2.c
#include "functions.h"
int main()
{
printf("myshell2>");
pid_t pid,waitPid;
//WE TRIED WITHOU ALLOCATING MEMORY AS WELL
char *argLine = (char *)malloc(max);
char **args = (char **)malloc(max);
char **args2 =( char **)malloc(max);
char **temp = (char **)malloc(max);
char *delim="><";
char *delim2=" ";
int i,howMany,howMany2,status;
while(fgets(argLine,max,stdin) != NULL)
{
howMany= argCounter(argLine,delim);//howMany redirections
args=argArray(argLine,howMany,args,delim);
if (howMany == 1)//means we are at myShell 1
{
howMany2= argCounter(argLine,delim2);
if(howMany2 ==1)//checking if the command has any parameters (like ls -l)
{
printf("myshell2>");
pid = fork();
if (pid < 0)
{
perror("ERROR: Fork failed.\n");
return -1;
}
else if(pid == 0)
{
myExec(args[0],howMany2,delim2);
perror("ERROR: Child should never arrive here.\n");
}
else
{
waitPid = wait(&status);
if (waitPid == -1)
{
perror("ERROR: Waitpid failed.\n");
return -1;
}
}
}
else
{
printf("ERROR: Wrong number of Arguments!\n");//can't run on myshell 2
return(0);
}
}
//means we have redirection (< or >)
for (i=0; i<howMany; i++)
{
argArray(args[i],2,args2,delim2);//args2 contains the tokens without spaces(delim2)
temp[i] = args2[0];
howMany2 = argCounter(args[i],delim2);
if(howMany2 > 1) // eg. ls -l should not run here
{
printf("ERROR: Wrong number of Arguments!\n");//myShell3 should be running this
return(0);
}
}
printf("myshell2>");
pid = fork();
if (pid < 0)
{
perror("ERROR: Fork failed.\n");
return -1;
}
else if(pid == 0)
{
myExecRedir(temp,argLine,howMany);
perror("ERROR: Child should never arrive here.\n");
}
else
{
waitPid = wait(&status);
if (waitPid == -1)
{
perror("ERROR: Waitpid failed.\n");
return -1;
}
}
}
}
Thank you in advance.
char *temp;
memcpy(temp,argLine,strlen(argLine));
char *pointer = strtok(temp, delim);
...
is wrong, you need to reserve space with malloc:
size_t len = strlen(argline);
char *temp = malloc(len + 1);
if (temp == NULL) return 0;
memcpy(temp, argLine, len);
/* NUL-terminate the string */
temp[len] = '\0';
char *pointer = strtok(temp, delim);
...
free(temp);
or you can use the non-standard function (but available on many implementations) strdup:
char *temp = strdup(argline);
if (temp == NULL) return 0;
char *pointer = strtok(temp, delim);
...
free(temp);
char **args = (char **)malloc(max);
is also wrong, if you need to reserve space for n pointers to char use:
char **args = malloc(sizeof(*args) * n); /* don't cast malloc */
or
char **args = malloc(sizeof(char *) * n);

cat with fork execvp pipe and dup2

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.

Resources