How to use execvp() to execute a command - c

So I'm trying to create a custom shell for my school project. My method was to create child process, and have that process execute the command using the execvp() function that my professor briefly mentioned in class that we are meant to use. Here's my code, as always, any help is appreciated.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#define MAX_LINE 80
int main(int argc, char *argv[])
{
char *input = (char*)malloc(MAX_LINE*sizeof(char));
int should_run = 1;
while(should_run){
printf("osh>");
fflush(stdout);
pid_t pid;
pid = fork();
if(pid < 0){
printf("error with creating chiled process");
return 0;
}
if(pid == 0){
fgets(input, MAX_LINE, stdin);
char *token = strtok(input," ");
if(execvp(token[0], token) < 0){
printf("Error in execution.");
return(0);
}
//should_run = 0;
}
waitpid(pid, 1, 0);
}
return 0;
}

The prototype of execvp is
int execvp(const char *file, char *const argv[]);
It expects a pointer to char as the first argument, and a NULL-terminated
pointer to an array of char*. You are passing completely wrong arguments.
You are passing a single char as first argument and a char* as the second.
Use execlp instead:
int execlp(const char *file, const char *arg, ...
/* (char *) NULL */);
So
char *token = strtok(input," \n");
if(token == NULL)
{
fprintf(stderr, "only delimiters in line\n");
exit(1);
}
if(execlp(token, token, NULL) < 0){
fprintf(stderr, "Error in execution: %s\n", strerror(errno));
exit(1);
}
Also the convention in UNIX is to print error messages to stderr and a process with an error should
have an exit status other than 0.

As Pablo's states, you are passing the wrong arguments to execvp().
You can consider coding by yourself a function (char **strsplit(char *str, char delim)) which takes a string and split it into smaller pieces, returning an array of strings.
Also don't ignore compiler's warnings, they tell you a lot of things, and I suggest you to compile with gcc -Wall -Wextra -Werror to get almost any possible error in your program.
I tell you this because waitpid() takes as second argument a pointer to integer, to get an update of the status of the forked program. With this status you how the program exited (normally, segf, bus error...), you can use it to print an error if something went wrong.
You can consider using execv() instead (I know I'm going off topic, but you can learn useful things doing this), and find by yourself the correct executable(s).
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <dirent.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/stat.h>
#define MAX_LINE 255
char **strsplit(char *str, char delim);
char *strjoin(char const *s1, char const *s2);
int isexec(char *path)
{
struct stat buf;
lstat(path, &buf);
if (S_ISREG(buf.st_mode) && (S_IXUSR & buf.st_mode))
return (1);
return (0);
}
static char *find_exec_readdir(char *paths, char *cmd)
{
DIR *dir;
struct dirent *dirent;
char *exec;
exec = NULL;
if ((dir = opendir(paths)) != NULL)
{
while ((dirent = readdir(dir)) != NULL)
{
if (!strcmp(dirent->d_name, cmd))
{
exec = strdup(dirent->d_name);
break ;
}
}
if (closedir(dir))
dprintf(2, "Failed closing dir.\n");
}
return (exec);
}
char *find_exec(char *cmd, char **paths)
{
char *exec;
char *path;
char *tmp;
int i;
i = -1;
exec = NULL;
path = NULL;
if ((cmd[0] == '.' || cmd[0] == '/'))
{
if (isexec(cmd))
return (strdup(cmd));
return (NULL);
}
while (paths[++i])
if ((exec = find_exec_readdir(paths[i], cmd)) != NULL)
{
tmp = strjoin(paths[i], "/");
path = strjoin(tmp, exec);
free(tmp);
free(exec);
break ;
}
return (path);
}
int handle_return_status(int status)
{
int sig;
int i;
if (!WIFEXITED(status) && WIFSIGNALED(status))
{
sig = WTERMSIG(status);
i = -1;
while (++i <= 13)
{
if (print_signal_error(sig))
{
return (-1);
}
}
dprintf(2, "Process terminated with unknown signal: %d\n", sig, NULL);
return (-1);
}
return (0);
}
int main(int argc, char *argv[])
{
char *input = NULL;
char **command = NULL;
int should_run = 1;
int status = 0;
(void)argc;
(void)argv;
if ((input = (char*)malloc(MAX_LINE*sizeof(char))) == NULL)
return (dprintf(2, "Failed to malloc, abort.\n"));
while(should_run){
printf("osh> ");
fflush(stdout);
pid_t pid;
pid = fork();
if(pid < 0)
return (dprintf(2, "error with creating chiled process\n"));
if(pid == 0){
fgets(input, MAX_LINE, stdin);
command = strsplit(input, ' ');
command[0] = find_exec(command[0], strsplit(getenv("PATH"), ':'));
if(execv(command[0], &command[1]) < 0)
return (dprintf(2, "Error in execution.\n"));
//should_run = 0;
}
waitpid(pid, &status, 0);
handle_ret_status(status);
}
return 0;
}

Related

Address out of bounds in C, problems with making an ls command

I am trying to make a some what shell in C but I am having problems with making the ls command. mkdir, and cd work fine but with ls it gives me
"Address out of bounds segmentation error"
Hope somebody can help me. Here's my code.
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <readline/readline.h>
#include <unistd.h>
#include <sys/wait.h>
int main() {
printf("\033[1;33mWelcome To Crisp Bacon Shell\n");
while (1) {
printf("\033[0m%s $", hostname);
input = readline("");
command = get_input(input);
child_pid = fork();
if (child_pid < 0) {
perror("Fork failed");
exit(1);
}else if (child_pid == 0) {
/* Never returns if the call is successful */
execvp(command[0], command);
printf("This won't be printed if execvp is successul\n");
} else {
waitpid(child_pid, &stat_loc, WUNTRACED);
}
free(input);
free(command);
}
return 0;
}
char **get_input(char *input) {
char **command = malloc(8 * sizeof(char *));
char *separator = " ";
char *parsed;
int index = 0;
parsed = strtok(input, separator);
while (parsed != NULL) {
command[index] = parsed;
index++;
parsed = strtok(NULL, separator);
}
command[index] = NULL;
return command;
}
The only thing I understand it has something to do with memory and references or pointers but I tried changing everything from & refrencing to pointers and it just gave me more errors what do I do?
There were many undeclared variables in your code snippets. You also need to fetch the hostname, it isn't a global variable. It's also a best practice to declare your functions before using them.
This works fine:
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <readline/readline.h>
#include <unistd.h>
#include <sys/wait.h>
char **get_input(char *input) {
char **command = malloc(8 * sizeof(char *));
char *separator = " ";
int index = 0;
char *parsed = strtok(input, separator);
while (parsed != NULL && index < 8) { // you need to make sure the index does not overflow the array
command[index] = parsed;
index++;
parsed = strtok(NULL, separator);
}
command[index] = NULL;
return command;
}
int main() {
printf("\033[1;33mWelcome To Crisp Bacon Shell\n");
while (1) {
// hostname does not exist, you need to fetch it
char hostname[1024];
gethostname(hostname, 1023); // POSIX only
printf("\033[0m%s $", hostname);
char *input = readline(NULL);
char **command = get_input(input);
pid_t child_pid = fork();
if (child_pid < 0) {
perror("Fork failed");
exit(1);
} else if (child_pid == 0) {
/* Never returns if the call is successful */
execvp(command[0], command);
printf("This won't be printed if execvp is successul\n");
} else {
waitpid(child_pid, NULL, WUNTRACED); // since you don't use the middle argument, no need to point to valid data
}
free(input);
free(command);
}
return 0;
}

Implementing a command line interpreter in C, special case

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/wait.h>
#define BUFFER 64
char *read_command(void);
char **parse_line(char *line);
int execute(char **arguments);
int main(void)
{
char *command = NULL;
char **arguments;
int status;
do
{
printf("protoulis_7968> ");
command = read_command();
arguments = parse_line(command);
status = execute(arguments);
free(arguments);
free(command);
}while(status);
}
char *read_command(void)
{
char *command = NULL;
ssize_t buf = 0;
getline(&command, &buf, stdin);
return command;
}
char **parse_line(char *line)
{
int buffer = BUFFER;
int pos = 0;
char **tokens = malloc(buffer * sizeof(char*));
char *token;
if (!tokens)
{
printf("Error allocating memory with malloc\n");
exit(0);
}
token = strtok(line, " \t\r\n\a");
while(token != NULL)
{
tokens[pos] = token;
pos++;
if (pos >= buffer)
{
buffer += BUFFER;
tokens = realloc(tokens, buffer * sizeof(char*));
if (!tokens)
{
printf("Error reallocating memory!\n");
exit(0);
}
}
token = strtok(NULL, " \t\r\n\a");
}
tokens[pos] = NULL;
return tokens;
}
int execute(char **arguments)
{
// printf("%*c\n", arguments);
int pid, waitPid, status;
pid = fork();
if(pid == 0) //child process
{
if (execvp(arguments[0], arguments) == -1)
perror("Error with EXECVP\n");
}
else if (pid < 0)
perror("Error PID < 0\n");
else //parent process
{
do
{
waitPid = waitpid(pid, &status, WUNTRACED);
}while(!WIFEXITED(status) && !WIFSIGNALED(status));
}
return 1;
}
Well, I have written the above code simulating a command line interpreter in C. I would like to be able to execute multiple commands by entering them in one line. I mean I want to pass as input for example the line: ls -l ; touch hello.c ; pwd. Having passed this entire line I want to separate the commands by the semicolon and let the system execute each command in any order. I believe I have to use the strtok function but have done many attempts and managed nothing. Any help would be really appreciated!
strtok will not suffice in your case. The reason is that it will take you to the next subcommand but to be able to execute this subcommand, you have to have it as a single string.
Two ways to solve this issue:
Count how many ';' there is, replace them by '\0' to have several contiguous strings in memory, then execute them one by one.
Write a function that splits your command string into a 2d array of subcommands, then execute them one by one.
Here's a code that does that if you need some inspiration:
Using linked lists: https://github.com/yoones/chelpers/blob/master/src/split.c
2d array version: https://github.com/yoones/hsn/blob/master/src/tools/split.c

How to use execvp()

The user will read a line and i will retain the first word as a command for execvp.
Lets say he will type "cat file.txt" ... command will be cat . But i am not sure how to use this execvp(), i read some tutorials but still didn't get it.
#include <stdio.h>
#include <stdlib.h>
int main()
{
char *buf;
char command[32];
char name[32];
char *pointer;
char line[80];
printf(">");
while((buf = readline(""))!=NULL){
if (strcmp(buf,"exit")==0)
break;
if(buf[0]!=NULL)
add_history(buf);
pointer = strtok(buf, " ");
if(pointer != NULL){
strcpy(command, pointer);
}
pid_t pid;
int status;
if ((pid = fork()) < 0) {
printf("*** ERROR: forking child process failed\n");
exit(1);
}
else if (pid == 0) {
if (execvp(command, buf) < 0) {
printf("*** ERROR: exec failed\n");
exit(1);
}
}
else
while (wait(&status) != pid)
;
free(buf);
printf(">");
}///end While
return 0;
}
The first argument is the file you wish to execute, and the second argument is an array of null-terminated strings that represent the appropriate arguments to the file as specified in the man page.
For example:
char *cmd = "ls";
char *argv[3];
argv[0] = "ls";
argv[1] = "-la";
argv[2] = NULL;
execvp(cmd, argv); //This will run "ls -la" as if it were a command
In cpp, you need to pay special attention to string types when using execvp:
#include <iostream>
#include <string>
#include <cstring>
#include <stdio.h>
#include <unistd.h>
using namespace std;
const size_t MAX_ARGC = 15; // 1 command + # of arguments
char* argv[MAX_ARGC + 1]; // Needs +1 because of the null terminator at the end
// c_str() converts string to const char*, strdup converts const char* to char*
argv[0] = strdup(command.c_str());
// start filling up the arguments after the first command
size_t arg_i = 1;
while (cin && arg_i < MAX_ARGC) {
string arg;
cin >> arg;
if (arg.empty()) {
argv[arg_i] = nullptr;
break;
} else {
argv[arg_i] = strdup(arg.c_str());
}
++arg_i;
}
// Run the command with arguments
if (execvp(command.c_str(), argv) == -1) {
// Print error if command not found
cerr << "command '" << command << "' not found\n";
}
Reference:
execlp、execvp用法與範例

Why are some functions not supported by my shell?

I am experimenting with writing a simple shell to support all the usual functions.
So far, despite my haphazard approach to it, I have been met with success - I have been able to fork() new processes with parameters but other certain methods don't seem to run.
When I run my shell, functions like pwd, ls, help work whilst other functions such as cd, mkdir don't - why is this & what research/investigation can I do in order to begin to address this?
I've included my code so far (although I'm not sure if it will actually help).
Thanks very much
EDIT: My output, when my shell is running in cygwin, if I type "cd /cygdrive/c" the output is "Command not found"
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
const int MAX_ARGS = 9;
size_t nBytes = 64; //Data type representing size of objects (unsigned)
int bytesRead = -1;
char *cmd = NULL;
char prompt[] = "DaSh-> ";
int argc;
char **argv;
int pid;
int childpid;
int status;
void process();
int readcmd();
int main() {
setvbuf(stdout, NULL, _IONBF, 0);
setvbuf(stderr, NULL, _IONBF, 0);
while (1) {
bytesRead = -1;
printf(prompt);
while (bytesRead == -1) {
bytesRead = readcmd();
}
process(cmd); //process the single line input into arguments
if (strcmp(argv[0], "exit") == 0) { //If user types exit
exit(0);
}
childpid = fork();
if (childpid == 0) { //This is run by the child
execvp(argv[0], argv);
printf("Command not recognised\n");
exit(1);
}
else if (childpid > 0 ) {
waitpid(-1, &status, 0);
}
}
return 0;
}
int nrows = 10;
int ncolumns = 2;
void process(char argStr[]) {
argv = malloc(MAX_ARGS * sizeof(char *)); //Array of pointers to the char first letter of each argument
char delims[] = " \n"; //Delimit about space
int i = 0;
argv[i] = strtok(argStr, delims);
while (argv[i] != NULL) {
argv[++i] = strtok( NULL, delims);
}
argc = i;
}
int readcmd() {
return getline(&cmd, &nBytes, stdin);
}

Global variable not staying set, maybe caused by fork()

I'm trying to write a very very simple unix shell in C, and I have the basics of what I need working, except support for a history command. I have a global 2D char array that holds the history of all entered commands. Commands are added before the fork() system call, and I was originally printing out the value of the history global array after strings were added, and they were printing out correctly, so I'm not sure why it doesn't print out when the command "history" is used at the shell.
Thank to anyone who takes a look.
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include "myhistory.h"
int BUFFER_SIZE = 1024;
char history[100][80];
int command_index = 0;
int main(int argc, char *argv[]){
int status = 0;
int num_args;
pid_t pid;
while(1){
char *buffer_input, *full_input;
char command[BUFFER_SIZE];
char *args[BUFFER_SIZE];
printf("myshell> ");
buffer_input = fgets(command, 1024, stdin);
full_input = malloc(strlen(buffer_input)+1);
strcpy(full_input, buffer_input);
if (command_index >= 100) {
command_index = 0;
}
strncpy(history[command_index], full_input, strlen(full_input) + 1);
command_index += 1;
parse_input(command, args, BUFFER_SIZE, &num_args);
//check exit and special command conditions
if (num_args==0)
continue;
if (!strcmp(command, "quit" )){
exit(0);
}
if(!strcmp(command, "history")){
int i;
fprintf(stderr,"%d\n",(int)pid);
for(i = 0; i < command_index; i++){
fprintf(stdout, "%d: %s\n",i+1,history[command_index]);
}
continue;
}
errno = 0;
pid = fork();
if(errno != 0){
perror("Error in fork()");
}
if (pid) {
pid = wait(&status);
} else {
if( execvp(args[0], args)) {
perror("executing command failed");
exit(1);
}
}
}
return 0;
}
void parse_input(char *input, char** args,
int args_size, int *nargs){
char *buffer[BUFFER_SIZE];
buffer[0] = input;
int i = 0;
while((buffer[i] = strtok(buffer[i], " \n\t")) != NULL){
i++;
}
for(i = 0; buffer[i] != NULL; i++){
args[i] = buffer[i];
}
*nargs = i;
args[i] = NULL;
}
Change:
fprintf(stdout, "%d: %s\n",i+1,history[command_index]);
to:
fprintf(stdout, "%d: %s\n",i+1,history[i]);

Resources