I have a C program I am working on that takes a certain number of inputs and runs them as system commands. The rest get passed on to the shell for execution. It was suggested however, that I should try to make use of fork and exec in order to run commands. I'm stumped on how to make this happen though.
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#define MAX_BUFFER 1024 // max line buffer
#define MAX_ARGS 64 // max # args
#define SEPARATORS " \t\n" // token sparators
extern char **environ;
/*******************************************************************/
int main (int argc, char ** argv)
{
char linebuf[MAX_BUFFER]; // line buffer
char cmndbuf[MAX_BUFFER]; // command buffer
char * args[MAX_ARGS]; // pointers to arg strings
char ** arg; // working pointer thru args
char * prompt = "==>" ; // shell prompt
// keep reading input until "quit" command or eof of redirected input
while (!feof(stdin)) {
// get command line from input
fputs (prompt, stdout); // write prompt
fflush(stdout);
if (fgets(linebuf, MAX_BUFFER, stdin )) { // read a line
// tokenize the input into args array
arg = args;
*arg++ = strtok(linebuf,SEPARATORS); // tokenize input
while ((*arg++ = strtok(NULL,SEPARATORS)));
// last entry will be NULL
if (args[0]) { // if there's anything there
cmndbuf[0] = 0; // set zero-length command string
// check for internal/external command
if (!strcmp(args[0],"clr")) { // "clr" command
strcpy(cmndbuf, "clear");
} else
if (!strcmp(args[0],"cd"))
{
int ret;
if (!args[1])
strcpy(cmndbuf, "pwd");
ret = chdir(args[1]);
strcpy(cmndbuf, "pwd");
}else
if (!strcmp(args[0],"dir")) { // "dir" command
strcpy(cmndbuf, "ls -al ");
if (!args[1])
args[1] = "."; // if no arg set current directory
strcat(cmndbuf, args[1]);
} else
if (!strcmp(args[0],"environ")) { // "environ" command
char ** envstr = environ;
while (*envstr) { // print out environment
printf("%s\n",*envstr);
envstr++;
} // (no entry in cmndbuf)
} else
if (!strcmp(args[0],"quit")) { // "quit" command
break;
} else { // pass command on to OS shell
int i = 1;
strcpy(cmndbuf, args[0]);
while (args[i]) {
strcat(cmndbuf, " ");
strcat(cmndbuf, args[i++]);
}
}
// pass any command onto OS
if (cmndbuf[0])
system(cmndbuf);
}
}
}
return 0;
}
I am assuming you have a POSIX system, eg GNU/Linux.
This is a very common question. The first answer would be to study the implementation of system function inside free C libraries like GNU Libc. Also many good books on Unix cover this question.
As a clue, system works with fork-ing and then in the child process execve of the shell /bin/sh
An example of "system" function implementation from The UNIX Programming Environment by Brian Kernighan and Rob Pike.
#include <signal.h>
system(s) /* выполнить командную строку s */
char *s;
{
int status, pid, w, tty;
int (*istat)(), (*qstat)();
extern char *progname;
fflush(stdout);
tty = open("/dev/tty", 2);
if (tty == 1) {
fprintf(stderr, "%s: can't open /dev/tty\n", progname);
return 1;
}
if ((pid = fork()) == 0) {
close(0); dup(tty);
close(1); dup(tty);
close(2); dup(tty);
close(tty);
execlp("sh", "sh", " c", s, (char *) 0);
exit(127);
}
close(tty);
istat = signal(SIGINT, SIG_IGN);
qstat = signal(SIGQUIT, SIG_IGN);
while ((w = wait(&status)) != pid && w != 1);
if (w == 1)
status = 1;
signal(SIGINT, istat);
signal(SIGQUIT, qstat);
return status;
}
Note that the book was written before the C standard was finalized, so it does not use prototypes.
Related
Trying to create a new bash shell in C and bring it to the user, this is my code:
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
char* secretpass = "password";
char password[50];
printf("%s", "Password: ");
fgets(password, 50, stdin);
password[strcspn(password, "\n")] = 0;
if (!strcmp(password, secretpass)){
pid_t pid = fork();
if (pid == 0){
execl("/bin/bash", "bash", NULL);
}
}
return 0;
}
After running the code (ELF), i get a new bash shell in ps but it's not my shell because echo $$ brings the first shell, what can I do to get the new shell to screen? kernel module will help?
EDIT:
edited my code for more help, /dev/chardev is a char device that come up with the boot process, the driver is also 0666 (.rw.rw.rw.) writable for everyone, the system(cmd) says at there is no permission at console, even if I do the command myself after execve.
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <stdlib.h>
#include <unistd.h>
#include <pwd.h>
#define MAX 50
#define USERNAME 2
int main(int argc, char const *argv[])
{
// Declare variables.
const char* username = argv[USERNAME];
char* password = (char*)calloc(MAX, sizeof(char));
char* cmd = (char*)calloc(5 * MAX, sizeof(char));
char* secretpass = "password";
printf("%s", "Password: ");
fgets(password, MAX, stdin);
password[strcspn(password, "\n")] = 0;
if (!strcmp(password, secretpass)){
int err;
struct passwd* pw_user = getpwnam(username);
//printf("-%s-%s-%d-%d-%s-%s-%s-\n", pw_user->pw_name, pw_user->pw_passwd,
//pw_user->pw_uid, pw_user->pw_gid, pw_user->pw_gecos,
//pw_user->pw_dir, pw_user->pw_shell);
if ( (err = fchown(0, pw_user->pw_uid, pw_user->pw_gid) ) != 0)
printf("%s %d\n", "fchown error", err);
if ( (err = setpgid(0, 0) ) != 0)
printf("%s %d\n", "setpgid error", err);
if ( (err = tcsetpgrp(0, getpid()) ) != 0)
printf("%s %d\n", "tcsetpgrp error", err);
if ( (err = chdir(pw_user->pw_dir) ) != 0)
printf("%s %d\n", "chdir error", err);
if ( (err = setgid(pw_user->pw_gid) ) != 0)
printf("%s %d\n", "setgid error", err);
if ( (err = setuid(pw_user->pw_uid) ) != 0)
printf("%s %d\n", "setuid error", err);
sprintf(cmd, "%s \"%d %d %d\" %s", "echo", pw_user->pw_uid, pw_user->pw_gid, getpid(), "> /dev/chardev");
system(cmd);
const char *args[] = {"bash", "--rcfile", "/etc/bashrc", NULL};
char LOGNAME[MAX];
char HOME[MAX];
char USER[MAX];
sprintf(LOGNAME, "%s%s", "LOGNAME=", pw_user->pw_name);
sprintf(HOME, "%s%s", "HOME=",pw_user->pw_dir);
sprintf(USER, "%s%s", "USER=", pw_user->pw_name);
const char *env[] = {"SHELL=/bin/bash", LOGNAME, HOME, USER, "IFS= ","PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin", "TTY=tty1", NULL}; /* need to generate these; TTY is passed to you */
execve("/bin/bash", args, env);
}
else
execl("/bin/login", "login", NULL);
return 0;
}
always setpgid error and if username isn't root there are also setuid and chdir errors.
From the comments: you're trying to write a login program.
Ok. That's a bit more, and you're going about this all the wrong way. We don't want to fork at all. Let init worry about waiting. Anyway, we get to write a long sequence here:
int targetuid = ... ; /* You need a strategy for getting this */
int targetgid = ... ; /* You need a strategy for getting this */
const char *homdir = ... ; /* You need a strategy for getting this */
if (!strcmp(password, secretpass)){
/* Start up the user's shell */
fchown(0, targetuid, targetgid);
setpgid(0, 0);
tcsetpgrp(0, getpid());
chdir(homedir);
setgid(targetgid);
setuid(targetuid);
const char *args[] = {"-bash", NULL};
const char *env[] = {"SHELL=/bin/bash", "LOGNAME=...", "HOME=...", "USER=...", IFS="...", PATH=/bin:/usr/bin", "TERM=...", NULL }; /* need to generate these; TERM is passed to you */
execve("/bin/bash", args, env);
}
This is very much involved and I actually don't recommend this unless you really have to. I learned a ton when I tried this but it took forever to get it working right.
Particular subpoints: 1) The tty device needs to be owned by the user after a successful login. Thus the fchown(0, ...) call to give ownership to the user. 2) The chdir() first is traditional; you could reverse the order if you wanted to but I don't see why. 3) Starting the shell with a leading - in argv0 tells the shell that it's a login shell. Check in ps -f and you can see this.
I picked up your new code; it actually looks pretty good. The only mistake I can spot is my own; the variable is TERM not TTY (now corrected in my sample above) and the best place to get its value is getenv(). On running your code I only had to make only one correction; that is putting the -bash back. The only error it spits out is the one about chardev; what is chardev?
I guess your failures aren't in this code at all but rather in your kernel.
Info from chat: OP has a custom kernel with a custom /dev/chardev; I can't explain the failures as the code works for me. There may or may not be other changes to the kernel.
Before I start, I just want to say that this is for a school assignment of mine. I'm really close to finishing, except well, since I'm here, obvious to say, I'm stuck on a problem :(.
First of I'll explain what my assignment wants:
The assignment wants me to create a command-line program in C that allows the user to type in N number of /bin/ commands (E.g. > /bin/ps /bin/ls "/bin/which gcc"). Once the user enters the command and hits the enter key, the parent process (the parent process is the program) will create N child processes (i.e. no. of /bin/ commands entered = no. of child processes parent process will create). Each child will run one of the N commands. All the children will be running concurrently, with the parent waiting for each child to terminate.
Once a child terminates, the parent will print whether the command executed successfully or not (E.g. "Command /bin/ps has completed successfully" or "Command /bin/ps has not completed successfully") and once all children have been terminated, the parent will print "All done, bye!"
The issue:
So I've managed to get my child processes to run concurrently, the only issue is that I'm not sure how to pipe the value of the command (like /bin/ps or /bin/which gcc) from the child process to the parent process to print out the success or not message. I've tried putting the write pipe above my execv which allows me to pipe what I want but the execv won't output anything and I can't put my pipe code below my execv because in that case, then while my execv output will show, my pipe won't. I did think that it might be due to close(1) but commenting that out didn't change the result.
So what I am trying to achieve is something like this:
> /bin/ls "/bin/which gcc" /bin/domainname /bin/fake_command
Output:
/usr/bin/gcc
localdomain
Command /bin/which gcc has completed successfully
Command /bin/domainname has completed successfully
a.txt b.c
Command /bin/ls has completed successfully
Command /bin/fake_command has not completed successfully
All done, bye!
>
But right now, I'm getting:
> /bin/ls "/bin/which gcc" /bin/domainname /bin/fake_command
Output:
Command /bin/which gcc has completed successfully
Command /bin/domainname has completed successfully
Command /bin/ls has completed successfully
Command /bin/fake_command has not completed successfully
>
As you can see, my execv output for the /bin/ commands aren't shown in the output.
I've tried searching SO for people who faced this similar issue as me but none of their solutions managed to work for me which is why I'm asking here. If there's anything you're not clear about, please let me know and I will try my best to explain.
The code:
q1.c
#include "q1.h"
int main(int argc, char *argv[])
{
int
child_status,
pipe_array[2];
pid_t child;
char *success_or_fail;
char *msg_buffer = malloc(CHAR_MAX);
if (msg_buffer == NULL)
{
return -1;
}
struct timespec tw = {.tv_sec = 0, .tv_nsec = 10000000L};
Tuple *process_tuple;
size_t tuple_size = sizeof(*process_tuple) + sizeof(pid_t) + sizeof(char *);
process_tuple = calloc(argc, tuple_size);
if (process_tuple == NULL)
{
return -1;
}
// if (pipe(pipe_array) == -1)
// {
// perror("pipe: ");
// return -1;
// }
for (int j = 1; j < argc; j++)
{
child = fork();
if (child == 0)
{
int
executed,
num_of_words,
num_of_chars;
char
string[strlen(argv[j]) + 1],
*backup = argv[j];
snprintf(string, sizeof(string), "%s", argv[j]);
num_of_chars = get_num_of_chars(string);
num_of_words = get_num_of_words(string);
char *command[num_of_chars + 1];
preparing_the_command(num_of_words, string, command);
// close(pipe_array[0]);
// close(1);
// dup2(pipe_array[1], STDOUT_FILENO);
// write(pipe_array[1], backup, sizeof(backup));
process_tuple[j - 1].pid = getpid();
process_tuple[j - 1].command = backup;
printf(" %i-PID -> %i\n %i-Command -> %s\n\n", process_tuple[j - 1].pid, process_tuple[j - 1].pid, process_tuple[j - 1].pid, process_tuple[j - 1].command);
executed = execv(command[0], command);
nanosleep(&tw, 0);
if (executed == -1)
{
exit(EXIT_FAILURE);
}
else
{
exit(EXIT_SUCCESS);
}
}
else if (child == -1)
{
perror("fork() failed: ");
exit(EXIT_FAILURE);
}
}
printf(" PID -> %i\n Command -> %s\n\n", process_tuple[0].pid, process_tuple[0].command);
// while ((child = waitpid(-1, &child_status, 0)) != -1)
// {
// for (int o = 0; o < argc; o++)
// {
// printf(" PID -> %i\n Command -> %s\n\n", process_tuple[o].pid, process_tuple[o].command);
// }
// close(0);
// close(pipe_array[1]);
//
// dup2(pipe_array[0], STDIN_FILENO);
// char *recipient;
//
// read(pipe_array[0], recipient, sizeof(recipient));
// if (!(WIFEXITED(child_status) && (WEXITSTATUS(child_status) == 0)))
// {
// success_or_fail = "not completed successfully";
// }
// else
// {
// success_or_fail = "completed successfully";
// }
// snprintf(msg_buffer, CHAR_MAX, "Command %s has %s\n", recipient, success_or_fail);
// fputs(msg_buffer, stdout);
// }
fputs("All done, bye!\n", stdout);
free(msg_buffer);
return 0;
}
int get_num_of_chars(const char string[])
{
int
i = 0,
num_of_chars = 0;
while (string[i++] != '\0')
{
if (string[i] != ' ' && string[i] != '\t')
{
num_of_chars++;
}
}
return num_of_chars;
}
int get_num_of_words(const char string[])
{
int
i = 0,
num_of_words = 0;
bool is_not_separator = false;
while (string[i++] != '\0')
{
if (string[i] == ' ' || string[i] == '\t')
{
is_not_separator = false;
}
else if (!is_not_separator)
{
is_not_separator = true;
num_of_words++;
}
}
return num_of_words;
}
void preparing_the_command(int num_of_words, char string[], char *command[])
{
char *token;
for (int j = 0; j < num_of_words && (token = strtok_r(string, " ", &string)); j++)
{
command[j] = token;
}
command[num_of_words] = (void *) NULL;
}
q1.h
#ifndef ASSIGNMENT2Q1_Q1_H
#define ASSIGNMENT2Q1_Q1_H
/***************
** LIBRARIES **
***************/
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <time.h>
#include <unistd.h>
#include <limits.h>
/*************
** STRUCTS **
*************/
typedef struct
{
pid_t pid;
char *command;
} Tuple;
/*************************
** FUNCTION PROTOTYPES **
*************************/
int get_num_of_chars(const char string[]);
int get_num_of_words(const char string[]);
void preparing_the_command(int num_of_words, char string[], char *command[]);
#endif //ASSIGNMENT2Q1_Q1_H
I am trying to use the execv() function.
I am trying to pass in my argument command to the left side.
execv(file,arguments);
I am using a char * to parse the incoming user input for my shell.
The second argument of execv takes a char * const*.
Is there a way I can cast a char * const to a char * const*?
I try this below,
char * arg;
char *const a[] = (char *const)arg;
error: invalid initializer
char *const a[] = (char *const)arg;
^
But it does not work and gives me errors.
Help would be apprecieated.
The error in char *const a[] = (char *const)arg; is not due to an improper conversion. It is because char *const a[] declares an array, and the initializers for an array must be in braces1, { … }, but you have specified just one initializer without braces.
Furthermore, the argv parameter to execv should be an array of pointers in which the first points to a string containing the file name of the program being executed (this is by convention, not required) and the last is a null pointer. Thus, your definition of a ought to be something like:
char * const a[] = { FileNameOfProgram, arg, NULL };
Footnote
1 Except when a string literal is used to initialize an array, but that is not the case here.
You're trying to initialize an array. Instead of doing this,
char * arg;
char *const a[] = (char *const)arg;
do this:
char * arg;
char *const a[] = {(char *const)arg};
It's quite normal to do an execv after eliminating the command name and some of the first parameters. For example, if you have some code like (you had better to post a complete and verifiable example) let's assume you are doing something like this (if you want an example, look for the xargs(1) manpage, you have a command, and after processing the options and their parameters, you want to eliminate all of them, and execute the rest as if it was a command line, e.g. I have a command to execute repeatedly a command, delaying some specified time, like:
cont -t 0.1 -- df -k .
I use <getopts.h> to process the options of my cont program, then execute repeatedly the command df -k. Options allow to show a version for the program, to specify timeout, be verbose, or the number of times to execute the command. I wrote it just now, to show you how to do it (the example includes fork(2) use, execvp(2) and redirection to capture the output of the command to be able to go back to the origin, once known the number of lines we have received, the program uses an ANSI escape to move the cursor back to the beginning.)
#include <stdlib.h>
#include <stdio.h>
#include <getopt.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#define F(_fmt) "%s:%d: " _fmt, __FILE__, __LINE__
#define FLAG_VERBOSE (1<<0)
#define FLAG_VERSION (1<<1)
#define FLAG_DELAY (1<<2)
#define FLAG_NTIMES (1<<3)
int flags = 0;
useconds_t delay = 1000000;
size_t ntimes;
void doVersion(void)
{
fprintf(stderr,
"cont: v1.0\n"
"(C) Luis Colorado. All rights reserved.\n"
"License: BSD\n");
exit(EXIT_SUCCESS);
}
ssize_t loop(int argc_unused, char **argv)
{
int fd[2];
int res = pipe(fd);
res = fork();
if (res < 0) {
fprintf(stderr,
F("fork: ERROR %d: %s\n"),
errno,
strerror(errno));
return -1;
} else if (res == 0) { /* child */
close(fd[0]); /* not going to use it */
dup2(fd[1], 1); /* redirect output to pipe */
close(fd[1]);
execvp(argv[0], argv);
fprintf(stderr,
F("execv: ERROR %d: %s\n"),
errno, strerror(errno));
return -1;
} else { /* parent */
pid_t cld_pid = res;
close(fd[1]); /* no writing to the pipe */
FILE *f = fdopen(fd[0], "rt"); /* just reading */
int c;
size_t lines = 0;
while((c = fgetc(f)) != EOF) {
if (c == '\n') lines++;
putc(c, stdout);
}
wait(NULL);
return lines;
}
} /* loop */
int main(int argc, char **argv)
{
int opt;
float t;
while ((opt = getopt(argc, argv, "t:Vvn:")) >= 0) {
switch(opt) {
case 't': flags |= FLAG_DELAY;
t = atof(optarg);
break;
case 'V': flags |= FLAG_VERSION;
break;
case 'v': flags |= FLAG_VERBOSE;
break;
case 'n': flags |= FLAG_NTIMES;
ntimes = atoi(optarg);
break;
/* ... */
}
}
if (flags & FLAG_VERSION)
doVersion();
/* the next pair of sentences is like `shift optind' in the shell. */
/* trick, don't move the parameters, just move the pointer */
argc -= optind; /* adjust the number of parameters. */
argv += optind; /* advance the pointer to the proper place */
/* NOW, argc && argv are identical to the original ones, but lacking the
* first `optind' argument strings. As the original string array ended
* in a NULL, there's no need to construct it from allocating memory.
* Anyway, we're not going to use after it's consumed in main(). */
if (flags & FLAG_VERBOSE) {
char *sep = "About to execute: ";
int i;
for (i = 0; i < argc; i++) {
fprintf(stderr, "%s%s", sep, argv[i]);
sep = " ";
}
fprintf(stderr, "\n");
}
if (flags & FLAG_DELAY) {
delay = t * 1.0E6;
}
size_t total_lines = 0;
ssize_t n = 0;
while(!(flags & FLAG_NTIMES) || ntimes--) {
/* move up as many lines as input from subcommand */
if (n) printf("\r\033[%ldA#\b", n);
n = loop(argc, argv);
if (n < 0) {
/* we have already written the error */
exit(EXIT_FAILURE);
}
usleep(delay);
total_lines += n;
}
if (flags & FLAG_VERBOSE) {
fprintf(stderr,
F("Total lines: %lu\n"),
total_lines);
}
exit(EXIT_SUCCESS);
}
you can download a complete version of this program from Github
I've been writing a shell program in C. The program is working as expected in Linux (Ubuntu 16.04) but I'm getting unexpected output in MacOS (10.14.2 Mojave).
/* A shell program.
*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
void input(char* argv[]);
void print_arr(char *argv[]); // For debugging
int
main(void)
{
while (1)
{
pid_t pid;
char *argv[100];
// Display shell prompt
write(1, "(ash) $ ", 8);
// Take user input
input(argv);
// print_arr(argv); // DEBUG STATEMENT
if (argv[0] != NULL)
{
// Exit if exit command is entered
if (strcmp(argv[0], "exit") == 0)
{
exit(0);
}
// Create child process
if ((pid = fork()) > 0)
{
wait(NULL);
}
else if (pid == 0)
{
// print_arr(argv); // DEBUG STATEMENT
execvp(argv[0], argv);
printf("%s: Command not found\n", argv[0]);
exit(0);
}
else
{
printf("Fork Error!\n");
}
}
}
}
/* Takes input from user and splits it in
tokens into argv. The last element in
argv will always be NULL. */
void
input(char* argv[])
{
const int BUF_SIZE = 1024;
char buf[BUF_SIZE];
int i;
buf[0] = '\0';
fgets((void*) buf, BUF_SIZE, stdin);
i = 0;
argv[i] = strtok(buf, " \n\0");
while (argv[i] != NULL)
{
argv[++i] = strtok(NULL, " \n\0");
}
}
/* Print argv for debugging */
void
print_arr(char *argv[])
{
int i = 0;
while (argv[i] != NULL)
{
printf("%d: %s\n", i, argv[i]);
++i;
}
}
In Linux:
(ash) $ ls
// files and folders are listed
In MacOS (with debug statements):
(ash) $ ls
0: p?M??
0: ??M??
: Command not found
(ash) $ ls
0: ls
0: ??M??
: Command not found
(ash) $ ls
0: ls
0: ??M??
I don't understand that why are the contents of char* argv[] getting modified across fork()?
I've also tried it in the default clang compiler and brew's gcc-4.9, the results are same.
When a program behaves different for no good reason, that's a VERY good sign of undefined behavior. And it is also the reason here.
The array buf is local to the function input and ceases to exist when the function exits.
One way of solving this is to declare buf in main and pass it to input. You will also need the size of the buffer for fgets.
void
input(char * argv[], char * buf, size_t size)
{
buf[0] = '\0';
fgets(buf, sizeof buf, stdin);
argv[0] = strtok(buf, " \n\0");
for(int i=0; argv[i] != NULL; i++) argv[i+1] = strtok(NULL, " \n\0");
}
Another solution (although I suspect many will frown upon it) is to declare buf as static, but then you would need to change BUF_SIZE to a #define or a hard coded value, since you cannot have a static VLA.
#define BUF_SIZE 1024
void
input(char * argv[])
{
static char buf[BUF_SIZE];
buf[0] = '\0';
fgets(buf, sizeof buf, stdin);
argv[0] = strtok(buf, " \n\0");
for(int i=0; argv[i] != NULL; i++) argv[i+1] = strtok(NULL, " \n\0");
}
I removed the cast to void* since it's completely unnecessary. I also changed the while loop to a for loop to make the loop variable local to the loop.
I have an assignment to make a shell in C code, and I have a solution that works most of the time. My solution works if the program exists, and I can exit my shell with either Control-D or by typing exit. But when I try a command that I know doesn't exist, my shell will print an error message saying command not found but I will have to either type exit or press Control-D the same amount of times as a invalid command was entered i.e. if I type a wrong command 3 times, I then have to hit Control-D 3 times. I really don't know what is going on here. I checked all the variables and read is -1 when I press Control-D but the if statement seems to be skipped.
Here is the parts of my source code that I think the problem is in:
comp20200Shell.c
#include "comp20200Shell_header.h"
#include <signal.h>
/*
* Name: ****
* Student Number: ****
* Email: ****
*
* This is the main function of my shell implementation.
*
*/
int main(void)
{
bool end_program = false;
size_t length = 0;
ssize_t read;
char* current_directory = NULL;
char* current_time = NULL;
/* Sets up signal handler to catch SIGINT*/
if(signal(SIGINT, sigintHandler) == SIG_ERR)
{
error("An error occured while setting a signal handler\n");
}
/* Infinitive loop, so after command or invalid comman will prompt again*/
while(end_program != true)
{
char* input = NULL;
/* Gets current working directory */
current_directory = return_current_directory();
/* Gets current date and time */
current_time = return_time();
/* Prints Prompt */
printf("%s\x5b%s\x5d %s%s %s%s%s", MAGENTA_TEXT, current_time, GREEN_TEXT, current_directory, BLUE_TEXT, PROMPT, RESET_COLOUR);
/* Frees the pointers returned by return_time() and return_current_directory() */
free(current_time);
free(current_directory);
/* Reads one line from standard input */
read = getline(&input, &length, stdin);
/* Checks if ctrl d, i.e. end of file is found or exit is typed */
if(strcmp(input, "exit\n") == 0 || read == -1)
{
if(read == -1)
{
putchar('\n');
}
/* Frees input */
free(input);
return(0);
}
/* Removes newline character that will be at the end */
remove_trailing_newline(input);
/* Passes input to process input, and the return value is passed in to process errors */
process_errors(process_input(&input));
/* Frees input */
free(input);
}
return(0);
}
process_input.c
#include "comp20200Shell_header.h"
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
/*
* Name: ****
* Student Number: ****
* Email: ****
*
* This function is used to process the command entered by the user
*
* return: the error value or 0 when everything whent ok
* arguments: the command entered by the user
*
*/
int process_input(char** input)
{
bool redirect_stdout = false;
bool redirect_stderr = false;
pid_t child_pid;
int child_status;
char** argument = malloc(sizeof(char*));
int count = 0;
char* temp = strtok(*input, " ");
while(temp != NULL)
{
argument[count] = temp;
count ++;
argument = realloc(argument, (count+2) * sizeof(char *));
temp = strtok(NULL, " ");
}
argument[count] = NULL;
if(argument[0] == NULL)
{
return(0);
}
else if(strcmp(argument[0], "cd") == 0)
{
return(change_directory(argument[1]));
}
int index;
for(index = 1; argument[index] != NULL; index++)
{
if(strcmp(argument[index], ">0") == 0)
{
if(argument[index + 1] == NULL)
{
return(EINVAL);
}
redirect_stdout = true;
break;
}
else if(strcmp(argument[index], ">2") == 0)
{
if(argument[index + 1] == NULL)
{
return(EINVAL);
}
redirect_stderr = true;
break;
}
}
child_pid = fork();
if(child_pid == 0)
{
int file;
if(redirect_stdout == true)
{
file = open(argument[index + 1], O_WRONLY|O_CREAT|O_TRUNC, 0666);
dup2(file, 1);
edit_arguments(argument, index);
execvp(argument[0], argument);
return(-1);
}
else if(redirect_stderr == true)
{
file = open(argument[index + 1], O_WRONLY|O_CREAT|O_TRUNC, 0666);
dup2(file, 2);
edit_arguments(argument, index);
execvp(argument[0], argument);
return(-1);
}
execvp(argument[0], argument);
return(-1);
}
else
{
wait(&child_status);
}
return(child_status);
}
comp20200Shell_header.h
/*
* Name: ****
* Student Number: ****
* Email: ****
*
* This is my header file, It includes all common used headerfiles on the top.
* Any specific header file that is only used once will be included with the .c file that needs it.
*
*/
/* included headerfiles begin */
#include <ctype.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <stdbool.h>
/* included headerfiles end */
/* defenitions begin */
#define PROMPT "# "
#define BUFFER_SIZE 1024
#define BLUE_TEXT "\x1B[34m"
#define MAGENTA_TEXT "\x1B[35m"
#define GREEN_TEXT "\x1B[32m"
#define RESET_COLOUR "\x1B[0m"
/* defenitions end */
/* Function prototypes begin */
void remove_trailing_newline(char *input);
void sigintHandler(int sig_num);
int process_input(char** input);
char* return_time(void);
void error(const char *fmt, ...);
int change_directory(char* path);
char* return_current_directory(void);
void process_errors(int return_value);
void edit_arguments(char** argument, int index);
/* Function prototypes end */
I have omitted the rest of the source code as I don't think the problem is there.
In your child, after the call to execvp you need to call exit(EXIT_FAILURE); instead of return -1;. Otherwise your child will continue running, and will interpret the next command (that is why you need to exit N times where N is the number of inexistant commands you tried to invoke).
After the change, your parent process will see that the child terminated with a non-zero return code and should interpret the error code. There is no real way to distinguish between a failure from the execvp (due to a non-existent command) or from the invoked process. I would recommend printing the error from execvp if there is one in the child before the exit.
Note that if execvp succeed, it will never return, so the code following a call to execvp can only be executed if the command failed.
So, my recommendation is doing this:
if(child_pid == 0)
{
int file;
if(redirect_stdout == true)
{
file = open(argument[index + 1], O_WRONLY|O_CREAT|O_TRUNC, 0666);
dup2(file, 1);
edit_arguments(argument, index);
execvp(argument[0], argument);
perror("execvp");
exit(EXIT_FAILURE);
}
else if(redirect_stderr == true)
{
file = open(argument[index + 1], O_WRONLY|O_CREAT|O_TRUNC, 0666);
dup2(file, 2);
edit_arguments(argument, index);
execvp(argument[0], argument);
perror("execvp");
exit(EXIT_FAILURE);
}
execvp(argument[0], argument);
perror("execvp");
exit(EXIT_FAILURE);
}
else
{
wait(&child_status);
}
You should be doing exit(1); or equivalent instead of return(-1);. You might want to use _exit(1);, or _exit(255); (or _exit(-1);, but it is equivalent to _exit(255);). You might well want to print an error message to standard error before you exit.
When you don't exit, you end up with two, then three, then N shells all trying to read input from the terminal. You have to make each one quit separately by indicating EOF with Control-D. If you tried typing commands, then it would become a lottery which shell gets each character, and that leads to chaos (and grave danger; you may have thought you typed grep fungible form.file | tr -d 'fr' > /tmp/x33 but if one of the shells got rm -fr /, you've got trouble!).
Instead of return -1, you can use exit(1) or exit (-1) to exit from that portion if it fails to execute due to some error.