I am trying to create a basic shell for Linux using C. I have gotten it to work until I try to do output redirection and it just destroys everything. When I run this code, it goes straight to the default case of the fork(). I have no idea why. If I get rid of the for loop in the child process it works, but even with the for loop I don't understand why the child process is never even entered. If I put a print statement at the top of the child process it doesn't get printed.
When I run this in command line, I get the prompt and type something like "ls", which worked before I added the for loop, but now I just get "% am i here" and if I press enter it just keeps giving me that same line. My goal is to be able to type "ls > output" and have it work. I think the input redirection works, but honestly I haven't even played with it much because I am so utterly confused as to what is going on with the output redirection. Any help would be greatly appreciated, I've spent 4 hours on the same like 15 lines trying to get this to work.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <signal.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/wait.h>
#define HISTORY_COUNT 20
char *prompt = "% ";
int
main()
{
int pid;
//int child_pid;
char line[81];
char *token;
char *separator = " \t\n";
char **args;
char **args2;
char **args3;
char cmd[100];
char *hp;
char *cp;
char *ifile;
char *ofile;
int check;
int pfds[2];
int i;
int j;
int current = 0;
int p = 0;
//int check;
char *hist[HISTORY_COUNT];
//char history[90];
//typedef void (*sighandler_t) (int);
args = malloc(80 * sizeof(char *));
args2 = malloc(80 * sizeof(char *));
signal(SIGINT, SIG_IGN);
while (1) {
fprintf(stderr, "%s", prompt);
fflush(stderr);
if (fgets(line, 80, stdin) == NULL)
break;
// split up the line
i = 0;
while (1) {
token = strtok((i == 0) ? line : NULL, separator);
if (token == NULL)
break;
args[i++] = token;
/* build command array */
}
args[i] = NULL;
if (i == 0){
continue;
}
// assume no redirections
ofile = NULL;
ifile = NULL;
// split off the redirections
j = 0;
i = 0;
while (1) { //stackoverflow.com/questions/35569673
cp = args[i++];
if (cp == NULL)
break;
switch (*cp) {
case '<':
if (cp[1] == 0)
cp = args[i++];
else
++cp;
ifile = cp;
break;
case '>':
if (cp[1] == 0)
cp = args[i++];
else
++cp;
ofile = cp;
break;
case '|':
if(cp[1] ==0){
cp = args[i++];
if(pipe(pfds) == -1){
perror("Broken Pipe");
exit(1);
}
p = 1;
}
else{
++cp;
}
break;
default:
args2[j++] = cp;
args3[cp++] = cp
break;
}
}
args2[j] = NULL;
if (j == 0)
continue;
switch (pid = fork()) {
case 0:
// open stdin
if (ifile != NULL) {
int fd = open(ifile, O_RDONLY);
if (dup2(fd, STDIN_FILENO) == -1) {
fprintf(stderr, "dup2 failed");
}
close(fd);
}
// open stdout
if (ofile != NULL) {
// args[1] = NULL;
int fd2;
if ((fd2 = open(ofile, O_WRONLY | O_CREAT, 0644)) < 0) {
perror("couldn't open output file.");
exit(0);
}
// args+=2;
printf("okay");
dup2(fd2, STDOUT_FILENO);
close(fd2);
}
if(p == 1){ //from stackoverflow.com/questions/2784500
close(1);
dup(pfds[1]);
close(pfds[0]);
execvp(args2[0], args2);
break;
}
if(strcmp(args2[0], "cd") == 0){ //cd command
if(args2[1] == NULL){
fprintf(stderr, "Expected argument");
}
else{
check = chdir(args2[1]);
if(check != 0){
fprintf(stderr,"%s",prompt);
}
}
break;
}
execvp(args2[0], args2); /* child */
signal(SIGINT, SIG_DFL);
fprintf(stderr, "ERROR %s no such program\n", line);
exit(1);
break;
case -1:
/* unlikely but possible if hit a limit */
fprintf(stderr, "ERROR can't create child process!\n");
break;
default:
//printf("am I here");
if(p==1){
close(0);
dup(pfds[0]);
close(pfds[1]);
//execvp();
}
wait(NULL);
//waitpid(pid, 0, 0);
}
}
exit(0);
}
I added a separate argument pass to capture and remember the I/O redirections and remove them from the arg list passed to the child.
Here's the corrected code [please pardon the gratuitous style cleanup]:
UPDATE:
Sure. It's too big to post here. See: http://pastebin.com/Ny1w6pUh
Yes.
I borrowed
xstr
from another SO answer of mine [with bugfix and enhancement]. Thedlk
was new, but I do many of those, so was easy. Most of it was new code.But ... It was composed of fragments/concepts I've done before: tgb, FWD, BTV, sysmagic. Notice all struct members for
struct foo
are prefixed withfoo_
[standard for me]. The macro "trickery" withDLHDEF
andDLKDEF
to simulate inheritance/templates is also something I do [when necessary].Many function vars reuse my style:
idx
for index var [I would never usei/j
, but ratherxidx/yidx
],cp
for char pointer,cnt
for count,len
for byte length, etc. Thus, I don't have to "think" about small stuff [tactics] and can focus on strategy.The above
idx
et. al. is a "signature style" for me. It's not necessarily better [or worse] than others. It comes from the fact that I started using C when the linker/loader could only handle 8 character symbols, so brevity was key. But, I got used to using the shorter names. Consider which is clearer/better:Or:
I use the
do { ... } while (0)
to avoidif/else
ladders a lot. This is called a "once through" loop. This is considered "controversial", but, in my experience it keeps the code cleaner. Personally, I've never found a [more standard] use of ado/while
loop that can't be done more easily/better with awhile
orfor
loop--YMMV. In fact, a number of languages don't even havedo/while
at all.Also, I use lower case unless it's a
#define
[orenum
] which is always upper. That is, I use "snake case" (e.g.fooidx
) and not "camel hump case" (e.g.indexForFooArray
).The
.proto
file containing function prototypes is auto-generated. This is a huge time saver. Side note: Be sure you have at least v2 from the external link as there was a bug in the Makefile. Amake clean
would erase the.proto
. v2 won't do thatI've developed my own style over the years. Turns out that the linux kernel style was "borrowed from mine". Not actually :-) Mine came first. But ... They, in parallel, came up with something that is a 99% match to mine:
/usr/src/kernels/whatever_version/Documentation/CodingStyle
.Consistency to a given style [one's own] is key. For a given function, you don't have to worry about what you'll name the variables, what indent or blank lines you'll use.
This helps a reader/new developer. They can read a few functions, see the style in play, and then go faster because all functions have similar style.
All this allows you to "go faster" and still get high quality code on the first try. I'm also quite experienced.
Also, my code comments focus on "intent". That is, what do you want the code to do in real world terms. They should answer the "what/why" and the code is the "how".