Using execvp in C to copy files under linux - c

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

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.

ls command does not work in the Program written in C(shell script)

void main() {
char *args[MAX_LINE];
char arg1[MAX_LINE/2] = "\0";
char arg2[MAX_LINE/2] = "\0";
printf("ubos>");
fflush(stdout);
fgets(arg1, sizeof(arg1), stdin);
arg1[strlen(arg1) - 1] = '\0';
fgets(arg2, sizeof(arg2), stdin);
arg2[strlen(arg2) - 1] = '\0';
abc:
printf("You typed: %s %s\n", arg1, arg2);
fflush(stdin);
args[0] = arg1;
args[1] = arg2;
args[2] = '\0';
int i = 0;
for (i = 0; i < MAX; i++) {
printf("Vlue of arg[%d] =%s\n", i, args[i]);
}
if (strcmp(args[0], "ls") == 0) {
execvp(args[0], args);
goto abc;
} else
if (strcmp(args[0], "ps") == 0) {
}
printf("Something is not correct...\n");
exit(0);
}
when I run this code with ls command this is the result. I don't know what is the problem with this code. When I run this code to execute another .c file it execute perfectly but when I try to use ls or ps command it does not let me use it and throws the error.
I have one more question why this goto does not working in this program .
ubos>ls
You typed: ls
Vlue of arg[0] =ls
Vlue of arg[1] =
Vlue of arg[2] =(null)
ls: cannot access : No such file or directory
so here is my mistake and can you give me some advise on how to use exec command and its different types.
If they don't provide an argument, set that argument in args to 0. Then ls won't receive an empty string as its first argument, it won't get any arguments at all.
args[0] = arg1;
if (strlen(arg2) == 0) {
args[1] = 0;
} else {
args[1] = arg2;
args[2] = 0;
}
You also need to check for this in the loop that prints all the arguments.
for(i=0;i<MAX && arg[i];i++)
{
printf("Value of arg[%d] =%s\n",i, args[i]);
}
if (strlen(arg2) == '\0')
{
args[0] = arg1;
args[1] = '\0';
}
else
{
args[0] = arg1;
args[1] = arg2;
args[2] = 0;
}
for(i=0;i<MAX && args[i];i++)
{
printf("Vlue of args[%d] =%s\n",i, args[i]);
}
I was passing the blank space as the second argument. so it was showing me the error. Above code is the solution of my problem..

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.

Why when I call method, execvp fails?

If you call store_commands() method anywhere in my code, execution of the command fails for some reason.
such as my main method
int main (int argc, char * argv[]) {
char *command;
store_commands(); // problem
while ( (command = readline(" $ "))!= NULL ) { // scan stdin
rl_bind_key('\t',rl_complete);
splitCommands(&mainVars, command, argv);
}
return 0;
}
my store commands method
void store_commands() {
char *newEnv;
DIR * dir;
char *new ;
struct dirent * entry;
char *env = getenv("PATH");
do {
newEnv = strsep(&env, ":");
if(newEnv != NULL)
if(strlen(newEnv) > 0) {
dir = opendir(newEnv);
if( dir == NULL ) break;
if(flag == 1) {
flag = 0;
while((entry = readdir(dir)) != NULL) {
new = malloc(strlen(entry->d_name) + 1) ;
new = strcpy(new, entry->d_name);
commands[++count] = new; // add possible commands into an array
//printf("---%i %s\n", count ,commands[count]);
}
}
closedir(dir); // close directory
}
} while(newEnv);
}
test cases
without store_commands()
**ls**
comm[0]: 'ls' and comm[1]: '(null)' // command received here
Makefile
main.c
target
libedit.2.dylib
with store_commands()
**ls**
comm[0]: 'ls' and comm[1]: '(null)' // command received here again but....
Execution of the command is failed
: No such file or directory
You are corrupting the environment with strsep. Call strdup on your env.
A minimal example:
#include <stdlib.h>
int main ()
{
char* z = getenv("PATH"); // <---- z points to the actual env, not a copy
*z = 0; // <---- corrupt the environment
system("ls"); // <---- fail
}

Resources