Implementing Pipes in a C shell (Unix) - c

Basically I have created a shell using standard POSIX commands, I want to be able to Implement Piping as well. Right now it handles commands correctly, and can do background processing with &. But I need to be able to pipe using | and >> as well.
For example something like this:
cat file1 file2 >> file3
cat file1 file2 | more
more file1 | grep stuff
Here is the code I have currently. I also want to AVOID "SYSTEM" calls. I know U need to use dup2, but the way I did my code is a bit odd, so im hoping if someone can tell me if it is feasible to implement pipes in this code? thanks! I know dup2 is used, but also im def. confused at how to implement >> as WELL as |
#include <sys/wait.h>
#include <sys/types.h>
#include <unistd.h>
#include <string.h>
#include <string>
#include <iostream>
#include <stdlib.h>
#include <stdio.h>
using namespace std;
void Execute(char* command[],bool BG)
{
//Int Status is Used Purely for the waitpid, fork() is set up like normal.
int status;
pid_t pid = fork();
switch(pid)
{
case 0:
execvp(command[0], command);
if(execvp(command[0], command) == -1)
{
cout << "Command Not Found" << endl;
exit(0);
}
default:
if(BG == 0)
{
waitpid(pid, &status, 0);
//Debug cout << "DEBUG:Child Finished" << endl;
}
}
}
bool ParseArg(char* prompt, char* command[], char Readin[],bool BG)
{
fprintf(stderr, "myshell>");
cin.getline(Readin,50);
prompt = strtok(Readin, " ");
int i = 0;
while(prompt != NULL)
{
command[i] = prompt;
if(strcmp(command[i], "&") == 0){
//Debug cout << "& found";
command[i] = NULL;
return true;
}
//Debug cout << command[i] << " ";
i++;
prompt = strtok(NULL, " ");
}
return false;
}
void Clean(char* command[])
{
//Clean Array
for(int a=0; a < 50; a++)
{
command[a] = NULL;
}
}
int main()
{
char* prompt;
char* command[50];
char Readin[50];
bool BG = false;
while(command[0] != NULL)
{
Clean(command);
BG = ParseArg(prompt, command, Readin, BG);
if(strcmp(command[0], "exit") == 0 || strcmp(command[0], "quit") == 0 )
{
break;
}
else
{
Execute(command,BG);
}
}
return 1;
}

Pipes and redirections are different, actually. To implement a redirection (such as >>) you have to use dup2 indeed. First, open the desired file with appropriate flags (for >> they'll be O_WRONLY|O_CREAT|O_APPEND). Second, using dup2, make stdout (file descriptor 1) a copy of this newly opened fd. Finally, close newly opened fd.
To create a pipe, you'll need a pipe syscall. Read its manpage, it contains example code. Then you'll also need dup2 to make file descriptors returned by pipe be stdin for one process and stdout for another, respectively.

You should be able to implement pipes and output redirection with your shell, but there are a few things I noticed:
Your code for reading input, parsing, and output are mixed together, you may want to separate this functionality.
strtok won't work very well as a parser for shell commands. It will work for very simple commands, but you may want to look into creating or finding a better parser. A command like echo "hello world" will be problematic with your current parsing method.
You may want to create a simple structure for holding your parsed commands.
Here is some pseudocode to get you started:
#define MAX_LINE 10000
#define MAX_COMMANDS 100
#define MAX_ARGS 100
// Struct to contain parsed input
struct command
{
// Change these with IO redirection
FILE *input; // Should default to STDIN
FILE *output; // Should default to STDOUT
int num_commands;
int num_args[MAX_COMMANDS]; // Number of args for each command
char* command_list[MAX_COMMANDS]; // Contains the programs to be run
char* args_list[MAX_COMMANDS][MAX_ARGS]; // The args for each command
boolean background_task;
boolean append;
}
int main()
{
char input[MAX_LINE];
while (1)
{
struct command cmd;
print_prompt();
read_input(input);
parse_input(input, &cmd);
execute(&cmd);
}
}
Good luck with this project!

Related

STDIN input size restriction to 1k in C program

Probably a silly question, with read and other functions you can specify the number of bytes you want to read, however when reading from stdin I find that I can only type 1024 characters in the prompt, if I type the 1025 character, it's not written and if I want the line to be read (pressing ENTER key) I need to remove the 1024 character in order to leave space for '\n' I suppose. This occurs only in my c program not the shell so what's causing this restriction?
#include <unistd.h>
#include <stdio.h>
int main() {
char buf[2048];
int c;
c = read(fileno(stdin), &buf, sizeof(buf));
printf("%s\n", buf);
return 0;
}
Transferring select comments to form an answer.
General diagnosis
This is a property of the terminal driver on your system, rather than of the program or the C library. Modern shells such as Bash don't read a single line; they read characters as they become available using non-canonical input. See also Canonical vs non-canonical terminal input.
Barmar noted:
Note that read() doesn't add a null terminator to the input that it reads, but printf() expects a null-terminated string.
Instead of adding a null terminator, you could tell printf() how many characters to print:
printf("%.*s\n", c, buf);
That is, however, tangential to the question of how to get a long line of input.
If you use an open source o/s, you can modify the terminal driver source code and recompile your kernel to allow you to type more than 1 KiB on a single line, but anything much short of that isn't going to work. The terminal driver imposes a limit; you have to change the terminal driver to change that limit. If you're on Linux, you can poke around the /proc file system to see if there's a dynamic configuration parameter you can change (so you don't have to recompile the kernel, but you do have to alter the settings of the terminal driver); I've not heard of that being possible.
The limit can be a nuisance if you copy'n'paste more than 1 KiB of text with no newlines in it from a browser and want to paste it into a file on your system. Use a program such as Vim to manage it — it puts the terminal into a non-canonical mode and therefore doesn't run into the limit.
Using POSIX termios to slurp input from a terminal
If you want a program to read from a terminal without the line lengths (but also with line editing such as erase or kill processing), then you could consider this program — slurp:
/*
#(#)File: $RCSfile: slurp.c,v $
#(#)Version: $Revision: 1.3 $
#(#)Last changed: $Date: 2018/10/28 17:14:24 $
#(#)Purpose: Put terminal into non-canonical mode to slurp input
#(#)Author: J Leffler
*/
/*TABSTOP=4*/
#include "posixver.h"
#include "stderr.h"
#include <assert.h>
#include <fcntl.h>
#include <signal.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <termios.h>
#include <unistd.h>
static const char optstr[] = "a:ho:V";
static const char usestr[] = "[-hV][-a output | -o output]";
static const char hlpstr[] =
" -a output Append to named file (creating it if necessary)\n"
" -h Print this help message and exit\n"
" -o output Output to named file (truncating it if it exists)\n"
" -V Print version information and exit\n"
;
static struct termios saved = { 0 };
static bool sigint_enabled = false;
static bool sigquit_enabled = false;
static bool slurping = false;
static void reset_termios(void);
static void set_non_canonical(void);
static void sig_handler(int signum);
static void set_signal_handling(void);
static void slurp(int ofd, const char *filename);
#ifndef lint
/* Prevent over-aggressive optimizers from eliminating ID string */
extern const char jlss_id_slurp_c[];
const char jlss_id_slurp_c[] = "#(#)$Id: slurp.c,v 1.3 2018/10/28 17:14:24 jonathanleffler Exp $";
#endif /* lint */
int main(int argc, char **argv)
{
const char *filename = "standard output";
int ofd = STDOUT_FILENO;
int oflag = 0;
err_setarg0(argv[0]);
int opt;
while ((opt = getopt(argc, argv, optstr)) != -1)
{
switch (opt)
{
case 'h':
err_help(usestr, hlpstr);
/*NOTREACHED*/
case 'o':
case 'a':
if (ofd != STDOUT_FILENO)
{
err_remark("the -a and -o flags are mutually exclusive\n");
err_usage(usestr);
}
oflag = (opt == 'o') ? O_TRUNC : O_APPEND;
if ((ofd = open(optarg, O_WRONLY | O_CREAT | oflag, 0644)) < 0)
err_syserr("failed to open file %s for writing: ", optarg);
filename = optarg;
break;
case 'V':
err_version("PROG", &"#(#)$Revision: 1.3 $ ($Date: 2018/10/28 17:14:24 $)"[4]);
/*NOTREACHED*/
default:
err_usage(usestr);
/*NOTREACHED*/
}
}
if (optind != argc)
{
err_remark("unexpected file name options (first is '%s')\n", argv[optind]);
err_usage(usestr);
}
set_non_canonical();
if (slurping)
set_signal_handling();
slurp(ofd, filename);
return 0;
}
static void reset_termios(void)
{
tcsetattr(STDIN_FILENO, 0, &saved);
}
static void set_non_canonical(void)
{
if (tcgetattr(STDIN_FILENO, &saved) == 0)
{
struct termios modified = saved;
atexit(reset_termios);
/*
** On macOS 10.14 (at least), if you don't reset ISIG, the
** signal characters are not transferred to the program, so
** you can't detect those signals. With ICANON reset, they
** don't generate the signal either. The code does not try
** to handle the suspend (^Z) key specially, nor any other
** keys than EOF, INTR, QUIT.
*/
modified.c_lflag &= ~(ICANON | ISIG);
modified.c_cc[VMIN] = 1;
modified.c_cc[VTIME] = 0;
tcsetattr(STDIN_FILENO, TCSANOW, &modified);
slurping = true;
}
}
static void sig_handler(int signum)
{
reset_termios();
_exit(128 + signum);
}
/* Almost worth a data structure and a loop, but not quite */
static void set_signal_handling(void)
{
/* Simulate SIGINT and SIGQUIT */
if (signal(SIGINT, SIG_IGN) != SIG_IGN)
{
(void)signal(SIGINT, sig_handler);
sigint_enabled = true;
}
if (signal(SIGQUIT, SIG_IGN) != SIG_IGN)
{
(void)signal(SIGQUIT, sig_handler);
sigquit_enabled = true;
}
/* Have program terminate when sent normal signals */
if (signal(SIGHUP, SIG_IGN) != SIG_IGN)
(void)signal(SIGHUP, sig_handler);
if (signal(SIGTERM, SIG_IGN) != SIG_IGN)
(void)signal(SIGTERM, sig_handler);
if (signal(SIGPIPE, SIG_IGN) != SIG_IGN)
(void)signal(SIGPIPE, sig_handler);
}
static void slurp(int ofd, const char *filename)
{
char buffer[4096];
int nbytes;
while ((nbytes = read(STDIN_FILENO, buffer, sizeof(buffer))) > 0)
{
/* Simulate EOF and interrupt and quit signals */
if (nbytes == 1 && slurping)
{
if (buffer[0] == saved.c_cc[VEOF])
break;
if (sigint_enabled && buffer[0] == saved.c_cc[VINTR])
exit(128 + SIGINT);
if (sigquit_enabled && buffer[0] == saved.c_cc[VQUIT])
exit(128 + SIGQUIT);
}
if (write(ofd, buffer, nbytes) != nbytes)
err_syserr("failed to write %d bytes to %s: ", nbytes, filename);
}
}
The library code used is available in my SOQ (Stack Overflow Questions) repository on GitHub as files stderr.c, stderr.h and posixver.h in the libsoq sub-directory.
This deals with most of the traps for the unwary. It does its best to reset the terminal back to the initial ('known good') state when it exits. It does simulate EOF, interrupt and quit keyboard signals, but it does not simulate regular terminal processing such as erase or kill.
It doesn't make sense to use this when the standard input is not a terminal, but the code should handle that OK too (it simply does normal reads). You can send the output to standard output (default) or to a file (-o file to create or truncate a file, -a file to append or create a file).

Redirecting stdin and stdout?

So im trying to redirect the I/O to read command from file then when user runs the output command it will print the compiled command to output file.
For example on the terminal:
./run 2 < test.txt // This would take file using dup and take the input
Then when you want to output the compile:
./run 1 > output.txt // and it would put into an output file
So far i know how to output to a file but my problem is with the input. how do i get the command from the file using the dup2() function? I tried researching this but no luck.
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
char inputForOutput[100];
void functionOutput(int argc, char **argv){
int ofd; //Init of file desc.
ofd = open(argv[1], O_CREAT|O_TRUNC|O_WRONLY);
dup2(ofd, 1);//Duplicates to stdout
system("ls");//Copies commnd given to output_file
}
//Function is called when argument number is == 1
void functionInput(int argc, char **argv){
FILE *ifd;
printf("\n %s \n ", argv[2]);
ifd = fopen(argv[2] , "r");
if (ifd == NULL){
perror("No file found");
exit(1);
}
fscanf(ifd,"%s",inputForOutput);
printf("\n**%s**\n",inputForOutput);
}
int main(int argc, char **argv)
{
int output;
int input;
output = strcmp("1", argv[1]);
input = strcmp("2" ,argv[1]);
if (output == 0 ) { //Fail safe for number of arguments
functionOutput(argc, argv);
}
else if ( input == 0){
functionInput(argc, argv);
}
else{
fprintf(stderr, "How to use: %s function output_file\n", argv[0]); // FAIL SAFE IF INPUT DOES NOT MATCH BOTH FUNCTIONS
}
return 0;
}
To redirect input and output, use this format
myprogram > out.txt < in.txt //read from in.txt, write to out.txt
myprogram < in.txt > out.txt //read from in.txt, write to out.txt
myprogram < in.txt //redirect stdin only
myprogram > out.txt //redirect stdout only
myprogram //no redirection
...
This should work with any program. Example:
int main(void)
{
char buf[1000];
if(fgets(buf, sizeof(buf), stdin))
printf("write: %s\n", buf);
return 0;
}
To redirect stdin/stdout in the program, use the standard method
freopen("output.txt", "w", stdout);
printf("Testing...");
fclose(stdout);
freopen("input.txt", "r", stdin);
char buf[100];
fgets(buf, sizeof(buf), stdin);
fclose(stdin);
Alternatively, set FILE *fin = stdin; FILE* fout = stdout; to redirect the opposite way.
Next, to write a program using argv elements, always test argc first. The code below shows an example.
#include <stdio.h>
#include <string.h>
int redirect(int argc, char **argv, int *index)
{
//no more redirection!
if(*index >= argc)
return 1;
//not enough parameters
if(*index + 1 >= argc)
{
printf("wrong usage\n");
return 0;
}
if(strcmp(argv[*index], "<") == 0)
{
*index++; //next parameter is to redirect input
if(!freopen(argv[*index], "r", stdin))
printf("error, redirect input failed");
}
else if(strcmp(argv[*index], ">") == 0)
{
*index++; //next parameter is to redirect output
if(!freopen(argv[*index], "w", stdout))
printf("error, redirect output failed");
}
else
{
printf("wrong usage\n");
return 0;
}
return 1;
}
int main(int argc, char **argv)
{
int index = 1;
if(!redirect(argc, argv, &index))
return 1;
if(!redirect(argc, argv, &index))
return 1;
//read
char buf[1000];
if(fgets(buf, sizeof(buf), stdin))
{
//write
printf("write: %s\n", buf);
}
fclose(stdin);
fclose(stdout);
return 0;
}
With functionOutput() you have a good first attempt at capturing the output of a system command to a file. Actually, that is the function called when the first argument is 1, so you might want to update your comment. Also, you're creating a file with the name stored in argv[1], which we already know is 1 so it's probably not doing what you expect, and you probably want:
ofd = open(argv[2], O_CREAT|O_TRUNC|O_WRONLY);
With functionInput() you're reading the first non-whitespace entry from the file. If you're telling it to read the file which you output using the functionOutput() function, that is likely to be (some of) the name of the first file which was listed by ls.
I'm finding it unclear what you're wanting to do which isn't that. If you want to find out what the command was which you ran to generate the output, that information is not available from the file itself, because you didn't write it there. If that's what you want, you may want to consider writing the command as the first line of the file, followed by the output. Then when you read it, you can assume that the first line is the command run, followed by the output of that command.
If I understand your question, and you want to run your program in essentially two different modes, (1) you want to take input if there is input to be taken on stdin; and (2) if there is no input waiting, you want to do an output, then select/pselect or poll are what you are looking for.
For example select allows you to check whether there is input ready to be read on a file descriptor (or set of descriptors) and it will return the number of descriptors with input waiting (or -1 and set errno on error). You could simply use the STDIN_FILENO (a/k/a fd 0) to check if there is input on stdin, e.g.
#include <stdio.h>
#include <unistd.h> /* for STDIN_FILENO */
#include <sys/select.h> /* for pselect */
int input (int filedes)
{
fd_set set;
/* declare/initialize zero timeout */
struct timespec timeout = { .tv_sec = 0 };
/* Initialize the file descriptor set. */
FD_ZERO (&set);
FD_SET (filedes, &set);
/* check whether input is ready on filedes */
return pselect (filedes + 1, &set, NULL, NULL, &timeout, NULL);
}
int main (void)
{
if (input (STDIN_FILENO))
puts ("doing input routine");
else
puts ("doing output routine");
return 0;
}
(note: from the man page "select() uses a timeout that is a struct timeval (with seconds and microseconds), while pselect() uses a struct timespec (with seconds and nanoseconds).")
Example Use/Output
$ ./bin/select_peekstdin < file
doing input routine
$ ./bin/select_peekstdin
doing output routine

popen: intercepting user's input

I have a code which runs bc thru popen(). I can intercept the calculator's output and prepend it with "Output=" text. But how can I intercept what user's is writing to bc?
#include <stdio.h>
#include <stdlib.h>
int main(void) {
FILE *in;
char buff[512];
if(!(in = popen("bc", "r"))){
exit(1);
}
while(fgets(buff, sizeof(buff), in)!=NULL){
printf("Output = %s", buff);
}
pclose(in);
return 0;
}
You can combine bc and echo with a pipe: echo '12*4' | bc
Example typing 12*4:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main(void) {
FILE *in;
char buff[512];
char cmd[512];
while (fgets(buff, sizeof(buff), stdin)!=NULL){
strcpy(cmd, "echo '");
strcat(cmd, buff);
strcat(cmd, "' | bc");
if(!(in = popen(cmd, "r"))){
exit(1);
}
fgets(buff, sizeof(buff), in);
printf("output:%s", buff);
}
pclose(in);
return 0;
}
Output:
david#debian:~$ ./demo
12*4
output:48
You need to use pipe() and fork/exec(). However, manual piping is quite complex:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(void) {
int write_pipe[2], read_pipe[2];
pipe(read_pipe); pipe(write_pipe);
#define PARENT_READ read_pipe[0]
#define CHILD_WRITE read_pipe[1]
#define CHILD_READ write_pipe[0]
#define PARENT_WRITE write_pipe[1]
int child = fork();
if (child == 0) { /* in child */
close(PARENT_WRITE);
close(PARENT_READ);
dup2(CHILD_READ, 0); close(CHILD_READ);
dup2(CHILD_WRITE, 1); close(CHILD_WRITE);
execl("/usr/bin/bc", "/usr/bin/bc");
} else { /* in parent */
close(CHILD_READ);
close(CHILD_WRITE);
write(PARENT_WRITE, "2+3\n", 4);
char buff[512];
int output_len=read(PARENT_READ, buff, sizeof(buff));
write(1, buff, output_len);
close(PARENT_READ);
}
return 0;
}
What you're looking to do is to start a subprocess, then simultaneously:
When activity occurs on standard input, execute some function on that input before passing it to the subprocess.
When activity occurs on the subprocess output, execute some function on that output before passing it to standard output.
The system call that allows you to wait for activity on two handles is called poll, but before we do that, we need to create the handles and start the subprocess:
int a[2], b[2];
if(pipe(a)==-1)abort(); // for communicating with subprocess input
if(pipe(b)==-1)abort(); // for communicating with subprocess output
switch(fork()) {
case -1: abort();
case 0: dup2(a[0],0), dup2(b[1],1), execlp("/usr/bin/bc", "bc", 0); exit(1);
};
Note how pipe works: Data written to fildes[1] appears on (i.e., can be read from) fildes[0]. This means we want to read from the standard output of our subprocess, b[0] and write to the standard input of our subprocess a[1].
Before we do that, we can use the poll instruction to wait for activity on either standard input (fd #0), or the subprocess output (b[0]):
for(;;) {
struct pollfd p[2]={0};
p[0].fd = 0; p[1].fd = b[0];
p[0].events = p[1].events = POLLIN;
while (poll(p,2,-1) <= 0);
At this point, there is activity on at least one of these file descriptors. You can see which one by examining the .revents member.
if(p[0].revents & POLLIN) {
r = read(0, buffer, sizeof(buffer));
write(a[1], buffer, r); // check for errors, or perhaps modify buffer
}
if(p[1].revents & POLLIN) {
r = read(b[0], buffer, sizeof(buffer));
write(1, buffer, r); // check for errors, or perhaps modify buffer
}
Note especially we use the opposite member a[1] and b[0] from the member we dup2'd onto the subprocesses standard input (0) and standard output (1).
At this point you can loop back up to poll again:
}
Disconnects (like EOF, program crash, etc) will be presented as read() returning 0, so watch carefully for this case, and break; out of the loop if so desired.

Why is stat() returning EFAULT?

I'm writing a program that when run from two separate bash sessions as two separate processes, opens a named pipe between the two to allow strings to be sent from one to the other.
When the process is first executed from one terminal, it checks stat(fname, buf) == -1 to see if a file at path fname exists and if not, creates it. The process then assumes that since it was the one to make the FIFO, it is the one that will be sending messages through it and continues accordingly.
After that occurs, the program can then be run from another terminal that should determine that it will be the receiver of messages through the pipe by checking stat(fname, buf) == -1. The condition should return false now, and stat(fname, buf) itself should return 0 because there exists a file at fname now.
But for reasons I am unable to discern, when the second process is run, stat(fname, buf) still returns -1. The variable errno is set to EFAULT. The man page for stat() only decribes EFAULT as "Bad address." Any help determining why the error occurs or what is meant by "Bad address." would be greaty appreciated.
I've verified that the file is indeed created by the first process as intended. The first process waits at the line pipe = open(fname, O_WRONLY); because it can't continue until the other end of pipe is opened.
Edit: The following is a self-contained implementation of my code. I have confirmed that it compiles and experiences the problem I described here.
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#define MAX_LINE 80
#define oops(m,x) { perror(m); exit(x); }
int main(int argc, char const *argv[]) {
char line[MAX_LINE];
int pipe, pitcher, catcher, initPitcher, quit;
struct stat* buf;
char* fname = "/tmp/absFIFOO";
initPitcher = catcher = pitcher = quit = 0;
while (!quit) {
if (((!pitcher && !catcher && stat(fname, buf) == -1) || pitcher) && !quit) {
// Then file does not exist
if (errno == ENOENT) {
// printf("We're in the file does not exist part\n");
if (!pitcher && !catcher) {
// Then this must be the first time we're running the program. This process will take care of the unlink().
initPitcher = 1;
int stat;
if (stat = mkfifo(fname, 0600) < 0)
oops("Cannot make FIFO", stat);
}
pitcher = 1;
// open a named pipe
pipe = open(fname, O_WRONLY);
printf("Enter line: ");
fgets(line, MAX_LINE, stdin);
if (!strcmp(line, "quit\n")) {
quit = 1;
}
// actually write out the data and close the pipe
write(pipe, line, strlen(line));
close(pipe);
}
} else if (((!pitcher && !catcher) || catcher) && !quit) {
// The first condition is just a check to see if this is the first time we've run the program. We could check if stat(...) == 0, but that would be unnecessary
catcher = 1;
pipe = open("/tmp/absFIFO", O_RDONLY);
// set the mode to blocking (note '~')
int flags;
flags &= ~O_NONBLOCK;
fcntl(pipe, F_SETFL, flags); //what does this do?
// read the data from the pipe
read(pipe, line, MAX_LINE);
if (!strcmp(line, "quit\n")) {
quit = 1;
}
printf("Received line: %s\n", line);
// close the pipe
close(pipe);
}
}
if (initPitcher)
unlink(fname);
return 0;
}
You have this piece of code:
struct stat* buf;
...
if (((!pitcher && !catcher && stat(fname, buf) == -1)
When you call stat(), buf isn't initalized and there's no telling what it points to.
You must allocate some storage for it, so stat() has a valid place to store the result.
The easiest thing is to just allocate it on the stack:
struct stat buf;
...
if (((!pitcher && !catcher && stat(fname, &buf) == -1)
You have not shown your code, but EFAULT means 'bad address'. This indicates that you have not properly allocated (or passed) your buffer for stat or the filename (fname).
buf isn't initialised anywhere. What exactly do you expect to happen?

Changing the use of system to Fork and exec in C

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.

Resources