I'm working on a custom shell that can handle multiple pipes. But every time I execute a new pipeline and check the process with ls -l /proc/pid/fd I get something like in the picture below and the list keeps expanding with every new pipeline executed:
Question: Is this considered as a fd leak? And how do I fix it?
Here's a code snippet for my pipeline execution:
enum PIPES {READ, WRITE};
void execute_pipeline(char*** pipeline)
{
int fd[2];
int fd_backup = 0;
pid_t child_pid;
while (*pipeline != '\0')
{
pipe(fd);
child_pid = fork();
if(child_pid == -1)
{
perror("fork");
exit(1);
}
else if(child_pid == 0)
{
dup2(fd_backup, 0);// (old, new)
close(fd[READ]);
if(*(pipeline + 1) != '\0')
{
dup2(fd[WRITE], 1);
}
execvp((*pipeline)[0], *pipeline);
exit(1);
}
else// Parent process
{
wait(NULL);
close(fd[WRITE]);
fd_backup = fd[READ];
pipeline++;
}
}
}
EDIT
An example how to call execute_pipeline:
char *ls[] = {"ls", "-l", NULL};
char *sort[] = {"sort", "-r", NULL};
char *head[] = {"head", "-n", "3", NULL};
char **pipeline[] = {ls, sort, head, NULL};
execute_pipeline(pipeline);
As tadman pointed out, it is easier to use a command struct to pass things around.
We can't [well, we could but shouldn't] do a wait [in the parent] during pipe construction. That has to be a separate loop later. We would hang the parent after the first child is created.
If the first child had a large amount of output, the kernel pipe buffers might fill up and the first child would block. But, since the second child has not been created, there is nothing to read/drain the first child's output and unblock it.
Also, it is important to close the pipe units after doing dup2 and ensure the previous pipe stage units are closed in the parent.
Here's a refactored version that does all that.
As to your original issue with file descriptor leakage, I think I fixed that by adding some more close calls. The program has some self verification code on this:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <dirent.h>
#include <sys/wait.h>
#define FREEME(ptr_) \
do { \
if (ptr_ == NULL) \
break; \
free(ptr_); \
ptr_ = NULL; \
} while (0)
#define CLOSEME(fd_) \
do { \
if (fd_ < 0) \
break; \
close(fd_); \
fd_ = -1; \
} while (0)
// command control
typedef struct {
unsigned int cmd_opt; // options
int cmd_cldno; // child number
char *cmd_buf; // command buffer
int cmd_argc; // argument count
char **cmd_argv; // arguments
int cmd_pipe[2]; // pipe units
pid_t cmd_pid; // child pid number
int cmd_status; // child status
} cmd_t;
#define CMD_FIRST (1u << 0)
#define CMD_LAST (1u << 1)
char linebuf[1000];
int cmdcount;
cmd_t *cmdlist;
int opt_d;
int opt_l;
#define dbg(fmt_...) \
do { \
if (opt_d) \
printf(fmt_); \
} while (0)
// show open fd's
void
fdshow1(int cldid)
{
char buf[100];
fprintf(stderr,"CLD: %d\n",cldid);
sprintf(buf,"ls -l /proc/%d/fd 1>&2",getpid());
system(buf);
}
// show open fd's
void
fdshow2(int cldid)
{
char dir[100];
char lnkfm[1000];
char lnkto[1000];
int len;
DIR *xf;
struct dirent *ent;
char *bp;
char obuf[1000];
sprintf(dir,"/proc/%d/fd",getpid());
xf = opendir(dir);
bp = obuf;
bp += sprintf(bp,"%d:",cldid);
while (1) {
ent = readdir(xf);
if (ent == NULL)
break;
if (strcmp(ent->d_name,".") == 0)
continue;
if (strcmp(ent->d_name,"..") == 0)
continue;
sprintf(lnkfm,"%s/%s",dir,ent->d_name);
len = readlink(lnkfm,lnkto,sizeof(lnkto));
lnkto[len] = 0;
if (strstr(lnkto,"pipe") != 0)
bp += sprintf(bp," %s-->%s",ent->d_name,lnkto);
switch (ent->d_type) {
case DT_FIFO:
break;
}
}
bp += sprintf(bp,"\n");
fputs(obuf,stderr);
fflush(stderr);
closedir(xf);
}
// show open fd's
void
fdshow(int cldid)
{
fdshow2(cldid);
}
// pipeadd -- add single command to pipe
void
pipeadd(char *buf)
{
char *cp;
char *bp;
char *sv;
cmd_t *cmd;
dbg("pipeadd: buf='%s'\n",buf);
cmdlist = realloc(cmdlist,(cmdcount + 1) * sizeof(cmd_t));
cmd = &cmdlist[cmdcount];
memset(cmd,0,sizeof(cmd_t));
cmd->cmd_pipe[0] = -1;
cmd->cmd_pipe[1] = -1;
cmd->cmd_cldno = cmdcount;
++cmdcount;
bp = buf;
while (1) {
cp = strtok_r(bp," \t",&sv);
bp = NULL;
if (cp == NULL)
break;
cmd->cmd_argv = realloc(cmd->cmd_argv,
(cmd->cmd_argc + 2) * sizeof(char **));
cmd->cmd_argv[cmd->cmd_argc + 0] = cp;
cmd->cmd_argv[cmd->cmd_argc + 1] = NULL;
cmd->cmd_argc += 1;
}
}
// pipesplit -- read in and split up command
void
pipesplit(void)
{
char *cp;
char *bp;
char *sv;
cmd_t *cmd;
printf("> ");
fflush(stdout);
fgets(linebuf,sizeof(linebuf),stdin);
cp = strchr(linebuf,'\n');
if (cp != NULL)
*cp = 0;
bp = linebuf;
while (1) {
cp = strtok_r(bp,"|",&sv);
bp = NULL;
if (cp == NULL)
break;
pipeadd(cp);
}
cmd = &cmdlist[0];
cmd->cmd_opt |= CMD_FIRST;
cmd = &cmdlist[cmdcount - 1];
cmd->cmd_opt |= CMD_LAST;
if (opt_d) {
for (cmd_t *cmd = cmdlist; cmd < &cmdlist[cmdcount]; ++cmd) {
dbg("%d:",cmd->cmd_cldno);
for (int argc = 0; argc < cmd->cmd_argc; ++argc)
dbg(" '%s'",cmd->cmd_argv[argc]);
dbg("\n");
}
}
}
// pipefork -- fork elements of pipe
void
pipefork(void)
{
cmd_t *cmd;
int fdprev = -1;
int fdpipe[2] = { -1, -1 };
for (cmd = cmdlist; cmd < &cmdlist[cmdcount]; ++cmd) {
// both parent and child should close output side of previous pipe
CLOSEME(fdpipe[1]);
// create a new pipe for the output of the current child
if (cmd->cmd_opt & CMD_LAST) {
fdpipe[0] = -1;
fdpipe[1] = -1;
}
else
pipe(fdpipe);
cmd->cmd_pid = fork();
if (cmd->cmd_pid < 0) {
printf("pipefork: fork fail -- %s\n",strerror(errno));
exit(1);
}
// parent the input side for the next pipe stage
if (cmd->cmd_pid != 0) {
CLOSEME(fdprev);
fdprev = fdpipe[0];
continue;
}
// connect up our input to previous pipe stage's output
if (fdprev >= 0) {
dup2(fdprev,0);
CLOSEME(fdprev);
}
// connect output side of our pipe to stdout
if (fdpipe[1] >= 0) {
dup2(fdpipe[1],1);
CLOSEME(fdpipe[1]);
}
// child doesn't care about reading its own output
CLOSEME(fdpipe[0]);
if (opt_l)
fdshow(cmd->cmd_cldno);
// off we go ...
execvp(cmd->cmd_argv[0],cmd->cmd_argv);
}
CLOSEME(fdpipe[0]);
CLOSEME(fdpipe[1]);
if (opt_l)
fdshow(-1);
}
// pipewait -- wait for pipe stages to complete
void
pipewait(void)
{
pid_t pid;
int status;
int donecnt = 0;
while (donecnt < cmdcount) {
pid = waitpid(0,&status,0);
if (pid < 0)
break;
for (cmd_t *cmd = cmdlist; cmd < &cmdlist[cmdcount]; ++cmd) {
if (pid == cmd->cmd_pid) {
cmd->cmd_status = status;
++donecnt;
break;
}
}
}
}
// pipeclean -- free all storage
void
pipeclean(void)
{
for (cmd_t *cmd = cmdlist; cmd < &cmdlist[cmdcount]; ++cmd)
FREEME(cmd->cmd_argv);
FREEME(cmdlist);
cmdcount = 0;
}
// main -- main program
int
main(int argc,char **argv)
{
char *cp;
--argc;
++argv;
for (; argc > 0; --argc, ++argv) {
cp = *argv;
if (*cp != '-')
break;
switch (cp[1]) {
case 'd':
opt_d = ! opt_d;
break;
case 'l':
opt_l = ! opt_l;
break;
default:
break;
}
}
while (1) {
pipesplit();
pipefork();
pipewait();
pipeclean();
}
return 0;
}
Let's be accurate regarding the file descriptors and bear in mind that during fork and execvp file descriptors are inherited by child processes unless marked as w/ CLOEXEC flag. Check the man pages:
fork(2):
The child inherits copies of the parent's set of open file descriptors. Each file descriptor in the child refers to the same open file description (see open(2)) as the corresponding file descriptor in the parent.
open(2):
By default, the new file descriptor is set to remain open across an execve(2) (i.e., the FD_CLOEXEC file descriptor flag described in fcntl(2) is initially disabled); the O_CLOEXEC flag, described below, can be used to change this default. The file offset is set to the beginning of the file (see lseek(2)).
But this behaviour, I supposed, exactly what you were relying on by calling fork after pipe...
No other words, lets' draw this:
stdin, stdout
/\
/ \
/ \
/ \
/ R; W; \
/ \
Child - - Parent
stdin/out, R(del),W stdin/out, R(fd_backup), W(del)
/\
/ \
/ \
/ \
/ R1; W1;\
/ \
Child - - Parent
stdin/out, R(fd_backup), stdin, stdout
R1(del), W1 R(fd_backup - old);
R1(fd_backup - new); W1(del)
/ \
/ \
/ \
/R2; W2;\
/ \
/ \
Child - - Parent
stdin, stdout, stdin, stdout
R(fd_backup - old), R (fd_backup - old),
R1(fd_backup - new), R1 (fd_backup - new),
R2(del),W2 R2 (fd_backup - newest!),
I hope the picture is self explanatory.
Child processes will die anyway and all their fds will be closed (so no issue with them). But the parent process is left with 3 opened fds and they keep growing with each pipe executed.
Related
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
My C shell can successfully handle redirection (e.g. ls -al > output.txt, ./pre < input1.txt, etc.) and multiple pipes (i.e. cmd1 | cmd 2 | cmd 3). However, my code is not working when I try to do input and output redirection together with a pipe, such as ./pre < input.txt | ./sort > output.txt. No output file is ever made, although the ./pre does successfully execute.
pre is an executable that prints names with GPAs over 3.0
sort is an executable that alphabetizes a list of names
input.txt is a file with names and GPAs (Name 3.1...).
CODE:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#ifndef READ
#define READ 0
#endif
#ifndef WRITE
#define WRITE 1
#endif
void clearArgumentContainer (int argumentContainer[]);
int main() {
/* professor-supplied variables for commands and command parsing */
char *iPath, *oPath, *argv[20], buf[80], n, *p;
int m, status, inword, continu;
int start[20];
/* flags for redirection (note: C does not have type bool; using integer value 0 or 1) */
int inputRedirectFlag, outputRedirectFlag;
/* variables for piping */
int count, pipes;
pid_t pid;
/* pipes */
int l_pipe[2], r_pipe[2];
/* required container for handling arguments */
int argumentContainer[20] = { 0 };
while (1) {
inword = m = continu = count = pipes = pid = 0;
p = buf;
/* redirection flags reset */
inputRedirectFlag = outputRedirectFlag = 0;
/* shell prompt */
printf("\nshhh> ");
/* command parsing */
while ((n = getchar()) != '\n' || continu)
{
if (n == ' ') {
if (inword)
{
inword = 0;
*p++ = 0;
}
}
else if (n == '\n')
continu = 0;
else if (n == '\\' && !inword)
continu = 1;
else {
if (!inword)
{
inword = 1;
argv[m++] = p;
*p++ = n;
}
else
*p++ = n;
}
} /* end of command parsing */
*p++ = 0;
argv[m] = 0;
/* user wishes to terminate program */
if (strcmp(argv[0], "exit") == 0)
exit(0);
/* manage redirection */
while (argv[count] != 0) {
if (strcmp(argv[count], "|") == 0) {
argv[count] = 0;
argumentContainer[pipes + 1] = count + 1;
++pipes;
}
else if (strcmp(argv[count], "<") == 0) {
iPath = strdup(argv[count + 1]); /* copy string argument (file string) */
argv[count] = 0;
argv[count + 1] = 0;
inputRedirectFlag = 1;
}
else if (strcmp(argv[count], ">") == 0) {
oPath = strdup(argv[count + 1]); /* copy string argument (file string) */
argv[count] = 0;
argv[count + 1] = 0;
outputRedirectFlag = 1;
}
else {
argumentContainer[count] = count;
}
++count;
} /* end of redirection management */
/* execute commands [<= in for-loop; n pipes require n+1 processes] */
for (int index = 0; index <= pipes; ++index) {
if (pipes > 0 && index != pipes) { /* if user has entered multiple commands with '|' */
pipe(r_pipe); /* no pipe(l_pipe); r_pipe becomes next child's l_pipe */
}
/* switch-statement for command execution */
switch (pid = fork()) {
/* fork() error */
case -1: perror("fork failed");
break;
case 0: /* child process manages redirection and executes */
if ((index == 0) && (inputRedirectFlag == 1)) {
int inputFileDescriptor = open(iPath, O_RDONLY , 0400);
if (inputFileDescriptor == -1) {
perror("input file failed to open\n");
return(EXIT_FAILURE);
}
close(READ);
dup(inputFileDescriptor);
close(inputFileDescriptor);
} /* end of input redirection management */
if ((index == pipes) && (outputRedirectFlag == 1)) {
int outputFileDescriptor = open(oPath, O_WRONLY | O_CREAT, 0755);
if (outputFileDescriptor < 0) {
perror("output file failed to open\n");
return(EXIT_FAILURE);
}
close(WRITE);
dup(outputFileDescriptor);
close(outputFileDescriptor);
} /* end of output redirection management */
/* manage pipes if (a) first child process, (b) middle child process, or (c) third/final child process */
if (pipes > 0) {
if (index == 0){ /* first child process */
close(WRITE);
dup(r_pipe[WRITE]);
close(r_pipe[WRITE]);
close(r_pipe[READ]);
}
else if (index < pipes) { /* middle child process */
close(READ);
dup(l_pipe[READ]);
close(l_pipe[READ]);
close(l_pipe[WRITE]);
close(WRITE);
dup(r_pipe[WRITE]);
close(r_pipe[READ]);
close(r_pipe[WRITE]);
}
else { /* third/final child process */
close(READ);
dup(l_pipe[READ]);
close(l_pipe[READ]);
close(l_pipe[WRITE]);
}
}
/* execute command */
execvp(argv[argumentContainer[index]], &argv[argumentContainer[index]]);
/* if execvp() fails */
perror("execution of command failed\n");
break;
default: /* parent process manages the pipes for child process(es) */
if (index > 0) {
close(l_pipe[READ]);
close(l_pipe[WRITE]);
}
l_pipe[READ] = r_pipe[READ];
l_pipe[WRITE] = r_pipe[WRITE];
/* parent waits for child process to complete */
wait(&status);
break;
} /* end of switch-statement for command execution */
} /* end of loop for all pipes */
// clear all executed commands
for (int i = 0; i < 20; ++i) {
argv[i] = 0;
}
}
}
void clearArgumentContainer (int argumentContainer[]){
// clear argument container
for (int i = 0; i < 20; ++i) {
argumentContainer[i] = 0;
}
}
Here is the input file I'm using:
Tim 3.5
Todd 2.1
Beth 3.9
Jason 3.5
Zander 3.3
Alex 3.5
Tyler 3.5
Lauren 3.6
Jack 2.3
Amir 3.4
Beth 3.2
pre executable will list only those names with GPAs higher than 3.0
sort executable will sort list of names in alphabetical order
Here is my final working code. It does not implement built-ins like cd, but I plan to implement those built-ins soon! Program does meet the requirements for the assignment.
My major change occurred in the redirection handling. I had to remove a line of code in two places:
argv[count + 1] = 0;
From the redirection for "<" and ">."
I also added code to handle the connections of the pipes, whether my process was the first child, the last, or one in between.
Code:
/***********************************************************************************************
***********************************************************************************************
Student: Douglas Adolph
Course: Operating Systems
Project #: 2
Program emulates shell, and can do the following:
1. Can execute a command with the accompanying arguments.
2. Recognize multiple pipe requests and handle them.
3. Recognize redirection requests and handle them.
4. Type "exit" to quit the shhh shell.
Notes:
Shell built-ins (cd, echo, etc.) not yet implemented
REFERENCED:
1. http://www.thinkplexx.com/learn/article/unix/command
2. http://man7.org/linux/man-pages/man2/open.2.html
3. https://stackoverflow.com/questions/19846272/redirecting-i-o-implementation-of-a-shell-in-c
**********************************************************************************************
*********************************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#ifndef READ
#define READ 0
#endif
#ifndef WRITE
#define WRITE 1
#endif
void clearArgIndexContainer (int argLocation[]);
int main() {
/* variables for command parsing and storage*/
char n, *parser, buf[80], *argv[20];
int m, status, inword, continu;
/* variables and flags for redirection (note: C does not have type bool; using integer value 0 or 1) */
char *in_path, *out_path;
int inputRedirectFlag, outputRedirectFlag;
/* variables for piping */
int count, pipes;
pid_t pid;
/* left and right pipes */
int l_pipe[2], r_pipe[2];
/* container for recording argument locations in argv[] */
int argLocation[20] = { 0 };
while (1) {
/* reset parsing and piping variable values */
m = inword = continu = count = pipes = pid = 0;
/* begin parsing at beginning of buffer */
parser = buf;
/* reset redirection flags */
inputRedirectFlag = outputRedirectFlag = 0;
/* print shell prompt */
printf("\nshhh> ");
/* parse commands */
while ((n = getchar()) != '\n' || continu)
{
if (n == ' ') {
if (inword)
{
inword = 0;
*parser++ = 0;
}
}
else if (n == '\n')
continu = 0;
else if (n == '\\' && !inword)
continu = 1;
else {
if (!inword)
{
inword = 1;
argv[m++] = parser;
*parser++ = n;
}
else
*parser++ = n;
}
} /* end of command parsing */
/* append terminating character to end of parser buffer and argv buffer */
*parser++ = 0;
argv[m] = 0;
/* user wishes to terminate program */
if (strcmp(argv[0], "exit") == 0)
exit(0);
/* manage redirection */
while (argv[count] != 0) {
if (strcmp(argv[count], "|") == 0) {
argv[count] = 0;
argLocation[pipes + 1] = count + 1;
++pipes;
}
else if (strcmp(argv[count], "<") == 0) {
in_path = strdup(argv[count + 1]);
argv[count] = 0;
inputRedirectFlag = 1;
}
else if (strcmp(argv[count], ">") == 0) {
out_path = strdup(argv[count + 1]);
argv[count] = 0;
outputRedirectFlag = 1;
}
else {
argLocation[count] = count;
}
++count;
} /* end of redirection management */
/* execute commands [<= in for-loop; n pipes require n+1 processes] */
for (int index = 0; index <= pipes; ++index) {
if (pipes > 0 && index != pipes) { /* if user has entered multiple commands with '|' */
pipe(r_pipe); /* no pipe(l_pipe); r_pipe becomes next child's l_pipe */
}
/* switch-statement for command execution */
switch (pid = fork()) {
case -1: perror("fork failed"); /* fork() error */
break;
case 0: /* child process manages redirection and executes */
if ((index == 0) && (inputRedirectFlag == 1)) {
int inputFileDescriptor = open(in_path, O_RDONLY , 0400);
if (inputFileDescriptor == -1) {
perror("input file failed to open\n");
return(EXIT_FAILURE);
}
close(READ);
dup(inputFileDescriptor);
close(inputFileDescriptor);
} /* end of input redirection management */
if ((index == pipes) && (outputRedirectFlag == 1)) {
//printf("DEBUG: here we should be about to create our output file\n");
int outputFileDescriptor = creat(out_path, 0700);
if (outputFileDescriptor < 0) {
perror("output file failed to open\n");
return(EXIT_FAILURE);
}
close(WRITE);
dup(outputFileDescriptor);
close(outputFileDescriptor);
} /* end of output redirection management */
/* manage pipes if (a) first child process, (b) in-between child process, or (c) final child process */
if (pipes > 0) {
if (index == 0){ /* first child process */
close(WRITE);
dup(r_pipe[WRITE]);
close(r_pipe[WRITE]);
close(r_pipe[READ]);
}
else if (index < pipes) { /* in-between child process */
close(READ);
dup(l_pipe[READ]);
close(l_pipe[READ]);
close(l_pipe[WRITE]);
close(WRITE);
dup(r_pipe[WRITE]);
close(r_pipe[READ]);
close(r_pipe[WRITE]);
}
else { /* final child process */
close(READ);
dup(l_pipe[READ]);
close(l_pipe[READ]);
close(l_pipe[WRITE]);
}
}
/* execute command */
execvp(argv[argLocation[index]], &argv[argLocation[index]]);
/* if execvp() fails */
perror("execution of command failed\n");
break;
default: /* parent process manages the pipes for child process(es) */
if (index > 0) {
close(l_pipe[READ]);
close(l_pipe[WRITE]);
}
l_pipe[READ] = r_pipe[READ];
l_pipe[WRITE] = r_pipe[WRITE];
/* parent waits for child process to complete */
wait(&status);
break;
} /* end of switch-statement for command execution */
} /* end of loop for all pipes */
// clear all executed commands
for (int i = 0; i < 20; ++i) {
argv[i] = 0;
}
}
}
void clearArgIndexContainer (int argLocation[]){
// clear argument container
for (int i = 0; i < 20; ++i) {
argLocation[i] = 0;
}
}
This is where I removed the lines making argv[count + 1] = 0; :
/* manage redirection */
while (argv[count] != 0) {
if (strcmp(argv[count], "|") == 0) {
argv[count] = 0;
argLocation[pipes + 1] = count + 1;
++pipes;
}
else if (strcmp(argv[count], "<") == 0) {
in_path = strdup(argv[count + 1]);
argv[count] = 0;
inputRedirectFlag = 1;
}
else if (strcmp(argv[count], ">") == 0) {
out_path = strdup(argv[count + 1]);
argv[count] = 0;
outputRedirectFlag = 1;
}
else {
argLocation[count] = count;
}
This was my major addition to handle to pipes:
/* manage pipes if (a) first child process, (b) in-between child process, or (c) final child process */
if (pipes > 0) {
if (index == 0){ /* first child process */
close(WRITE);
dup(r_pipe[WRITE]);
close(r_pipe[WRITE]);
close(r_pipe[READ]);
}
else if (index < pipes) { /* in-between child process */
close(READ);
dup(l_pipe[READ]);
close(l_pipe[READ]);
close(l_pipe[WRITE]);
close(WRITE);
dup(r_pipe[WRITE]);
close(r_pipe[READ]);
close(r_pipe[WRITE]);
}
else { /* final child process */
close(READ);
dup(l_pipe[READ]);
close(l_pipe[READ]);
close(l_pipe[WRITE]);
}
}
I still have more work to do, namely implementing the built-ins, but also putting more of this into separate functions to clean up the code and make it more readable. I was also given some good advice on some expressions that might be better written, and I'll soon tackle that.
I wrote a little daemon.
This is the flow of the daemon, in general:
Get variables
Get all rows from the database (may be either MySQL or Oracle) that meets the query parameters (in this case get all rows that has this current time).
If any rows were found then run a Perl script for each row (using execv).
This daemon works well, but the problem is that when I have two rows or more coming back from the query, they start, but the Perl script outputs are mixed. They are supposed to run independently without interfering with each other.
Am I doing something wrong?
Is this a memory leak?
Here is my code :
/*
============================================================================
Name : main.c
Description : Daemon for scheduler
============================================================================
*/
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>
#include <syslog.h>
#include <string.h>
#include <strings.h>
#include <regex.h>
#include <time.h>
#ifdef MYSQL_CODE
#include <mysql.h>
#endif
#ifdef ORACLE_CODE
#include <ocilib.h>
#endif
#define DAEMON_NAME "schedulerd"
void start_job(char *automation_user,char *automation_path,char *automation_log_path, char *id, char *site, char *db_type,char *db_host,char *db_user,char *db_password,char *db_schemata){
pid_t pid;
pid = fork();
if (pid < 0) { exit(EXIT_FAILURE); }
//We got a good pid, Continue to the next result
if (pid > 0) {
return;
}
char *file_format = "scheduler";
char *seperator = "_";
char *postfix = "_XXXXXX";
char *extension = ".log";
//get Time
time_t now;
struct tm *now_tm;
char hour[2],min[2],sec[2];
now = time(NULL);
now_tm = localtime(&now);
sprintf(hour, "%d", now_tm->tm_hour);
sprintf(min, "%d", now_tm->tm_min);
sprintf(sec, "%d", now_tm->tm_sec);
char *str = (char *)automation_log_path;
strcat(str,(char *)file_format);
strcat(str,seperator);
strcat(str,hour);
strcat(str,seperator);
strcat(str,min);
strcat(str,seperator);
strcat(str,sec);
strcat(str,postfix);
// buffer to hold the temporary file name
char nameBuff[128];
int filedes = -1;
// memset the buffers to 0
memset(nameBuff,0,sizeof(nameBuff));
// Copy the relevant information in the buffers
strncpy(nameBuff,str,128);
// Create the temporary file, this function will replace the 'X's
filedes = mkstemp(nameBuff);
if(filedes<1)
{
syslog (LOG_NOTICE, "Creation of temp file [%s] failed with error [%s]\n",nameBuff,strerror(errno));
}else{
mode_t perm = 0644;
fchmod(filedes, perm); //Change permission to the file so everyone can read
close(filedes); // Close created file
//Rename file
int ret;
char newname[128];
sprintf(newname, "%s%s", nameBuff,extension);
ret = rename(nameBuff, newname);
if(ret != 0) {
syslog (LOG_NOTICE, "Renaming of temp file %s to %s failed \n",nameBuff,newname);
exit(EXIT_FAILURE);
}
char statement[256];
sprintf(statement, "UPDATE scheduler_list SET log_file='%s' WHERE id='%s'", newname,id);
syslog (LOG_NOTICE,"Adding to DB : %s\n", statement);
char command[2048];
sprintf(command, "cd %s ; ./runner.pl -site %s -log %s -scheduler_id %s -command \\\"./run_me.pl\\\"", automation_path,site,newname,id);
//sprintf(command, "cd /net/10.7.5.50/opt/trunk/ ; ./runner.pl -site %s -log %s -scheduler_id %s -command \\\"./run_me.pl\\\"",site,newname,id);
if (strcasestr(db_type,"mysql")){/* mysql */
#ifdef MYSQL_CODE
MYSQL *conn;
//mysql_free_result(res); // Free mysql
conn = mysql_init(NULL);
/* Connect to database */
if (!mysql_real_connect(conn, db_host,db_user, db_password, db_schemata, 0, NULL, 0)) {
syslog (LOG_NOTICE,"%s\n", mysql_error(conn));
exit(EXIT_FAILURE);
}
if (mysql_query(conn,statement)) {
syslog (LOG_NOTICE,"%s\n", mysql_error(conn));
exit(EXIT_FAILURE);
}
#endif
}else{
#ifdef ORACLE_CODE
OCI_Connection* cn;
OCI_Statement* st;
OCI_Initialize(NULL, NULL, OCI_ENV_DEFAULT);
char query_command[128];
sprintf(query_command, "%s:1521/%s", db_host,db_schemata);
cn = OCI_ConnectionCreate(query_command, db_user, db_password, OCI_SESSION_DEFAULT);
st = OCI_StatementCreate(cn);
OCI_Prepare(st, statement);
OCI_Execute(st);
OCI_Commit(cn);
OCI_Cleanup();
#endif
}
char *args[] = {"sudo", "-u", automation_user, "bash","-c",command,NULL};
FILE *log_file_h = fopen(newname, "w");
if (log_file_h == NULL)
{
syslog (LOG_NOTICE,"Error opening file %s !\n",newname);
exit(EXIT_FAILURE);
}
syslog (LOG_NOTICE,"Starting scheduler job %s , command : %s",id, command);
fclose(log_file_h);
execv("/usr/bin/sudo",args);
syslog (LOG_NOTICE,"Failed to start job %s ",id);
perror("error");
}
exit(EXIT_FAILURE);
}
void process(char *automation_user,char *automation_path, char *automation_log_path ,char *db_type,char *db_host,char *db_user,char *db_password,char *db_schemata){
if (strcasestr(db_type,"mysql")){/* mysql */
#ifdef MYSQL_CODE
MYSQL *conn;
MYSQL_RES *res;
MYSQL_ROW row;
conn = mysql_init(NULL);
/* Connect to database */
if (!mysql_real_connect(conn, db_host,db_user, db_password, db_schemata, 0, NULL, 0)) {
syslog (LOG_NOTICE,"%s\n", mysql_error(conn));
return;
}
/* send SQL query */
if (mysql_query(conn, "SELECT id,site from scheduler_list where start_date = DATE_FORMAT(now(),'%Y-%m-%d %k:%i:00') AND id != father_id AND run='yes'")) {
syslog (LOG_NOTICE,"%s\n", mysql_error(conn));
return;
}
res = mysql_use_result(conn);
/* output table name */
while ((row = mysql_fetch_row(res)) != NULL){
char *id = malloc(strlen(row[0]) +1);
strcpy(id,row[0]);
char *site = malloc(strlen(row[1]) +1);
strcpy(site,row[1]);
start_job(automation_user,automation_path,automation_log_path,id,site,db_type, db_host,db_user,db_password,db_schemata);
}
/* close connection */
mysql_free_result(res);
mysql_close(conn);
#endif
}else{/* oracle */
#ifdef ORACLE_CODE
OCI_Connection* cn;
OCI_Statement* st;
OCI_Resultset* rs;
OCI_Initialize(NULL, NULL, OCI_ENV_DEFAULT);
char query_command[128];
sprintf(query_command, "%s:1521/%s", db_host,db_schemata);
cn = OCI_ConnectionCreate(query_command, db_user, db_password, OCI_SESSION_DEFAULT);
st = OCI_StatementCreate(cn);
OCI_ExecuteStmt(st, "SELECT id,site from scheduler_list where to_char(start_date, 'yyyy-mm-dd hh24:mi') = to_char(SYSDATE, 'yyyy-mm-dd hh24:mi') AND id != father_id AND run='yes'");
rs = OCI_GetResultset(st);
while (OCI_FetchNext(rs)){
char *id = malloc(strlen(OCI_GetString(rs, 1)) +1);
strcpy(id,OCI_GetString(rs,1));
char *site = malloc(strlen(OCI_GetString(rs,2)) +1);
strcpy(site,OCI_GetString(rs,2));
start_job(automation_user,automation_path,automation_log_path,id,site,db_type, db_host,db_user,db_password,db_schemata);
}
OCI_Cleanup();
#endif
}
}
char * set_conf_param (char *line, int addSlash){
char *param = malloc(strlen(line) + 2 + addSlash);
strcpy(param,line);
param = strchr(line,'=');
param = param+1; //Remove '='
strtok(param, "\n"); //remove /n
if (addSlash == 1){
int len = strlen(param);
param[len] = '/';
param[len+1] = '\0';
}
return strdup(param);
}
int main(int argc, char *argv[]) {
FILE * fp;
char * line = NULL;
size_t len = 0;
int found_db = 0;
ssize_t read;
pid_t pid, sid;
char *automation_user=NULL,*automation_log_path=NULL ,*db_type=NULL, *db_host=NULL , *db_user=NULL, *db_password=NULL, *db_schemata=NULL;
char *automation_path = getenv("AUTOMATION_PATH");
//char *automation_path = "/net/10.7.5.50/opt/trunk/";
char *automation_user_search = "automation_user=";
char *automation_log_path_search = "automation_log=";
char *db_type_search = "type=";
char *db_host_search = "host=";
char *db_user_search = "user=";
char *db_password_search = "password=";
char *db_schemata_search = "schemata=";
const char comment = '#';
/* Change the working directory to the root directory */
/* or another appropriated directory */
chdir(automation_path);
//Set our Logging Mask and open the Log
setlogmask(LOG_UPTO(LOG_NOTICE));
openlog(DAEMON_NAME, LOG_CONS | LOG_NDELAY | LOG_PERROR | LOG_PID, LOG_USER);
syslog(LOG_NOTICE, "Entering Daemon");
//Read framework.conf
fp = fopen("framework.conf", "r");
if (fp == NULL){
syslog (LOG_NOTICE,"Failed to open framework.conf");
exit(1);
}
//Read framework.conf
fp = fopen("framework.conf", "r");
if (fp == NULL){
syslog (LOG_NOTICE,"Failed to open framework.conf");
exit(1);
}
while ((read = getline(&line, &len, fp)) != -1) {
//If line commented
if (strchr(line,comment) != NULL){
continue;
}
if (strstr(line,automation_user_search) != NULL){
automation_user = set_conf_param(line,0);
}
else if (strstr(line,automation_log_path_search) != NULL){
automation_log_path = set_conf_param(line,1);
}
else if (db_type!=NULL && strcasestr(line,db_type) != NULL){
found_db = 1;
}
else if (strstr(line,db_type_search) != NULL){
db_type = set_conf_param(line,0);
}
else if (found_db && db_host==NULL && strstr(line,db_host_search) != NULL){
db_host = set_conf_param(line,0);
}
else if (found_db && db_user==NULL && strstr(line,db_user_search) != NULL){
db_user = set_conf_param(line,0);
}
else if (found_db && db_password==NULL && strstr(line,db_password_search) != NULL){
db_password = set_conf_param(line,0);
}
else if (found_db && db_schemata==NULL && strstr(line,db_schemata_search) != NULL){
db_schemata = set_conf_param(line,0);
}
}
fclose(fp);
if (line)
free(line);
if (automation_user==NULL){
automation_user = "root";
}
//Fork the Parent Process
pid = fork();
if (pid < 0) { exit(EXIT_FAILURE); }
//We got a good pid, Close the Parent Process
if (pid > 0) { exit(EXIT_SUCCESS); }
//Change File Mask
umask(0);
//Create a new Signature Id for our child
sid = setsid();
if (sid < 0) { exit(EXIT_FAILURE); }
//Close Standard File Descriptors
close(STDIN_FILENO);
close(STDOUT_FILENO);
close(STDERR_FILENO);
//----------------
//Main Process
//----------------
while(1){
process(automation_user,automation_path,automation_log_path,db_type,db_host,db_user,db_password,db_schemata); //Run our Process
sleep(60); //Sleep for 60 seconds
}
//Close the log
closelog ();
exit(EXIT_FAILURE);
}
You don't appear to have made any effort to write a minimal, complete verifiable example.
This is just a dump of your whole program, including dependencies on a config file you don't provide, and databases whose schemata you don't show. None of which are even relevant to your question.
At least you edited out the company name, but for reference it's still visible in the edit history.
threads in C are writing each other stdout
I can't see any threads in your program, and you don't fork threads anyway - what you have are child processes
... are writing each other stdout
well, stdout is inherited from the parent, so all children (and their sudo/bash/perl/whatever children) are writing to the same place. The writes themselves are locked, but can be interleaved.
If you want their output not to be interleaved, have them write to different places, and figure out how to display/combine them later.
Per-child-process temporary files are a popular choice, but note the parent will have to track child process completion in order to know when to print and then delete each file. Also, you should probably detach child processes from the terminal, close stdin, and consider what to do with stderr.
Is this a memory leak?
No.
I guess (after a quick glimpse) that the problem is in
execv("/usr/bin/sudo",args);
you should make sure that only one instance is running.
this can be done by mutual exclusion.
A good description is on:
http://www.thegeekstuff.com/2012/05/c-mutex-examples/
In short:
// create a global mutex variable
pthread_mutex_t lock;
// initialise it in main
pthread_mutex_init(&lock, NULL);
// enclose your critical path
pthread_mutex_lock(&lock);
execv("/usr/bin/sudo",args);
pthread_mutex_unlock(&lock);
Hope it Helps
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]:
#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>
char *prompt = "% ";
int
main()
{
int pid;
//int child_pid;
char line[81];
char *token;
char *separator = " \t\n";
char **args;
char **args2;
char *cp;
char *ifile;
char *ofile;
int i;
int j;
int err;
//int check;
//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;
err = 0;
while (1) {
cp = args[i++];
if (cp == NULL)
break;
switch (*cp) {
case '<':
if (cp[1] == 0)
cp = args[i++];
else
++cp;
ifile = cp;
if (cp == NULL)
err = 1;
else
if (cp[0] == 0)
err = 1;
break;
case '>':
if (cp[1] == 0)
cp = args[i++];
else
++cp;
ofile = cp;
if (cp == NULL)
err = 1;
else
if (cp[0] == 0)
err = 1;
break;
default:
args2[j++] = cp;
break;
}
}
args2[j] = NULL;
// we got something like "cat <"
if (err)
continue;
// no child arguments
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);
}
// trying to get this to work
// NOTE: now it works :-)
// open stdout
if (ofile != NULL) {
// args[1] = NULL;
int fd2;
//printf("PLEASE WORK");
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);
}
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");
wait(NULL);
//waitpid(pid, 0, 0);
}
}
exit(0);
}
UPDATE:
If you're still around do you think you could help me with creating a pipe?
Sure. It's too big to post here. See: http://pastebin.com/Ny1w6pUh
Wow did you create all 3300 lines?
Yes.
I borrowed xstr from another SO answer of mine [with bugfix and enhancement]. The dlk 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 with foo_ [standard for me]. The macro "trickery" with DLHDEF and DLKDEF to simulate inheritance/templates is also something I do [when necessary].
Many function vars reuse my style: idx for index var [I would never use i/j, but rather xidx/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:
for (fooidx = 0; fooidx <= 10; ++fooidx)
Or:
for (indexForFooArray = 0; indexForFooArray <= 10; ++indexForFooArray)
I use the do { ... } while (0) to avoid if/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 a do/while loop that can't be done more easily/better with a while or for loop--YMMV. In fact, a number of languages don't even have do/while at all.
Also, I use lower case unless it's a #define [or enum] 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. A make clean would erase the .proto. v2 won't do that
I'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".
Although my program works correctly in all cases, it doesn't use a pipe to connect the output of the first of two commands to the second when they're separated by a pipe symbol. I wrote the output of the first command to a file, then redirected the standard input of the second command to the file when the process to run that command was run. I need to use a pipe system call to create the pipe and obtain the file descriptors
for the pipe, and then run the two processes at the same time. It is a homework question and I have done 99% of the work but somehow am not able to get the pipe system call working... what I've been trying is that for an input like: Command 1 | Command 2
inside the child process for command 2 I close FD[0] then dup FD[1] and for command 1 close FD[1] then dup FD[1] and close FD[0].
I am hell confused with the file descriptors when using pipe.... I have to use a pipe
Any sort of help is appreciated. Execute function is where I am forking the processes.
Here's my code...
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <regex.h>
/* Global Variables */
extern char **environ; /* Environmental Variables */
char *pathList[10]; /* List of paths from the $PATH */
int pathCount; /* Count of the # of paths in $PATH */
char *pathSet; /* Variable through which $PATH is retrieved */
int hasPipe = 0;
int cmdNo = 0;
/* This function takes the 'finalPath', the full path to executable,argList[],the
full command-line input arguments and argCount, the number of arguments from
command-line as input. It the creates a child process, in turn invokes the
execve() that finally executes the executable in 'finalPath' with the arguments
in 'argText' all stored into the args[] appropriately. Child process also handles
input and output file re-direction.
*/
void execute(char *finalPath, char *argList[], int argCount)
{
int k,fd,ofound,pos,i; /* flags and temporary variables */
pid_t pid; /* process ID */
int status, which;
char msg[100];
char *args[4]; /* argument list for execve() */
int spCase = 0;
ofound = 0;
pos=0;
pid = fork(); /* Creating a new process using fork() */
if (pid == -1) /* Checking for errors in process creation */
{
write(1,"Fork failed.\n",12);
exit(1);
}
/**************************
Checking for parent process
***************************/
if (pid != 0)
{
which = wait(&status);
if (which == -1)
{
write(1,"Wait failed.\n",12);
exit(1);
}
if (status & 0xff)
{ /* Case of abnormal termination */
sprintf(msg,"ERROR: <dShell> # process %d terminated abnormally for reason %d\n",which, status & 0xff);
write(1,msg,strlen(msg));
}
else
{ /* Case of normal termination */
sprintf(msg,"process %d terminated normally with status %d\n",which, (status >> 8) & 0xff);
write(1,msg,strlen(msg));
}
}
/*************************
Checking for child process
**************************/
if (pid == 0)
{
char argText[50];
argText[0] = '\0';
int std_fd;
if(cmdNo==0 && hasPipe)
{
close(1);
std_fd = open("temp.out", O_WRONLY | O_CREAT | O_TRUNC, S_IRWXU);
dup(std_fd);
}
else if(cmdNo==1 && hasPipe)
{
close(0);
std_fd = open("temp.out", O_RDONLY);
dup(std_fd);
}
/* Finding the first re-direction operator */
for( i = 0; i < argCount ; ++i)
{
if( ofound != 1 && ofound != 2)
{
if( strcmp(argList[i],"<") == 0 )
{
fd = open(argList[i+1],O_RDONLY);
if (fd < 0)
{
sprintf(msg,"ERROR: %s could not be opened\n", argList[i+1]);
write(1, msg, strlen(msg));
exit(5);
}
ofound = 1;
strcpy(argText,"\0");
close(0);
dup(fd);
close(fd);
}
else if(strcmp(argList[i],">") == 0)
{
fd = open(argList[i+1],O_CREAT | O_WRONLY, 0777);
pos = i;
ofound = 2;
strcpy(argText,"\0");
if (fd < 0)
{
sprintf(msg,"ERROR: %s could not be opened\n", argList[i+1]);
write(1, msg, strlen(msg));
exit(5);
}
close(1);
dup(fd);
close(fd);
}
}
}
/* If input re-direction operator is found check for an output re-direction along with it */
if(ofound == 1)
{
for( k = 0; k < argCount && ofound != 2; ++k)
{
if( strcmp(argList[k],">") == 0 )
{
fd = open(argList[k+1],O_CREAT | O_WRONLY , 0777);
spCase = 1;
ofound = 2;
strcpy(argText,"\0");
if (fd < 0)
{
sprintf(msg,"ERROR: %s could not be opened\n", argList[k+1]);
write(1, msg, strlen(msg));
exit(5);
}
close(1);
dup(fd);
close(fd);
}
}
}
/* If the re-direction operators are not found */
if( ofound == 0 )
{
for(i = 1; i < argCount; ++i)
{
strcat(argText, argList[i]);
strcat(argText, " ");
}
spCase = 2;
}
/* Case when both arguments and output re-direction operators are found */
if (spCase == 0)
{
if(pos == 0)
{
for( i = 3; i<argCount; ++i)
{
strcat(argText, argList[i]);
strcat(argText," ");
}
}
if(pos == argCount - 2)
{
for( i = 1; i<argCount - 2; ++i)
{
strcat(argText, argList[i]);
strcat(argText," ");
}
}
}
argText[strlen(argText)-1] = '\0'; /*because I added an extra space so trimming that*/
/* Running the execve */
args[0] = finalPath;
if(strlen(argText) == 0) /* checking if argText is populated */
{
args[1] = NULL;
}
else
{
args[1] = argText;
args[2] = NULL;
}
/* Execute command,if it returns that means it failed and need to display error and exit */
execve(args[0], args, environ);
sprintf(msg, "ERROR! execve() failed");
write(1, msg, strlen(msg));
}
}
/*******************************************************************************
This function checks if the path is accessible and continues to execute the
command. If the path does not exist of is not accessible, variable 'retFlag'
is used to return 0 to the calling function.
********************************************************************************/
int checkPath(char *exepath, char *argList[], int argCount, int flag)
{
char *finalPath;
int retFlag = flag;
if(access(exepath,X_OK) == 0)
{
finalPath = exepath;
retFlag = 1;
execute(finalPath,argList,argCount);
return retFlag;
}
else
return retFlag;
}
/**********************************************************************************
This function checks if the first argument is a path and if so calls checkPath().
Else it gets the paths set to the $PATH variable, tokenizes it, pads it with the
first token of input command and calls checkPath(). If the correct path is established,
the variable 'found' is used to kick out of the for loop.
************************************************************************************/
void setPath(char *argList[], int argCount)
{
char *exepath;
char com[50];
char emsg[80];
char *command;
int i,found = 0;
/* Seperating the command if redirection is used */
if( strcmp(argList[0],"<") == 0 || strcmp(argList[0],">") == 0 )
{
command = argList[2];
}
else
command = argList[0];
/* In case of no redirection, storing the commands and arguments into a array */
if(strcmp(command,"#") == 0) /* Checking for comment statements */
{
write(1,"ERROR: No command(s) found. Only comment present/n",48);
}
else
{
if(strstr(command,"/")) /* Checking if the entire path is given as a part of the command */
{
exepath = command;
found = checkPath(exepath,argList,argCount,0);
}
else /* building the path and storing it in 'com' */
{
for(i = 0; i< pathCount && found != 1; i++)
{
sprintf(com,"%s%s%s",pathList[i],"/",command);
exepath = com;
found = checkPath(exepath,argList,argCount,0);
}
}
if(found == 0)
{
sprintf(emsg,"%s%s",command,":COMMAND DOES NOT EXIST");
write(1,emsg,sizeof(emsg));
write(1,"\n",1);
}
}
}
/* Tokenizes commands into words */
void tokens(char *cmdStr)
{
char cmd[100];
strcpy(cmd,cmdStr);
char *result;
char delims[] = " , ";
char *argList[20];
int argCount = 0;
/*Tokenize the individual command into strings */
result = strtok(cmd,delims);
while( result != NULL )
{
argList[argCount] = result;
result = strtok( NULL, delims );
++argCount;
}
setPath(argList,argCount);
}
/* Tokenizes multiple commands into single commands */
void tokenize(char *inputStr)
{
int i,cmdCount = 0;
char *cmdResult;
char *cmdStr[100];
char delimiters[] = "|";
cmdResult = strtok(inputStr, delimiters);
while(cmdResult != NULL)
{
cmdStr[cmdCount]=cmdResult;
cmdResult = strtok(NULL, delimiters);
cmdCount++;
}
if( cmdCount > 1 )
hasPipe = 1;
else
hasPipe = 0;
for( i=0; i<cmdCount ; i++)
{
cmdNo = i%cmdCount;
tokens(cmdStr[i]);
}
}
int main(int argc, char *argv[])
{
char prompt[8]; /* String that stores the personalized prompt */
char *path; /* Temporary variable used for tokenization*/
char ch; /* Temporary variable used in read() */
int chCount; /* # of characters read from the prompt */
int entry; /* return variable of read() */
int flag; /* Flag to go read the next command when newline is found */
regex_t reIgnore;
char pattern[20]="^\\s*$|^#.*";
/* Tokenizing the paths asociated with the $PATH and storing them in a array declared globally */
pathCount = 0;
pathSet = getenv("PATH");
if ( !pathSet)
{
write(1, "ERROR: PATH environment does not exist.\n", 40);
exit(1);
}
path = strtok(pathSet,":");
while(path != NULL)
{
pathList[pathCount] = path;
path = strtok(NULL,":");
++pathCount;
}
/* Checks for blanks and tabs in Step 2 */
if ( regcomp(&reIgnore, pattern, REG_EXTENDED) )
{
write(1, "Error. \n",9);
exit(2);
}
sprintf(prompt,"<dShell> # "); /* Storing the personalized shell prompt into 'prompt' */
/* Reading the input from command line and passing it to tokenize() */
while(1)
{
char inputStr[100]; /* String into which inputs are read into */
chCount = 0;
flag = 0;
hasPipe = 1;
write(1,prompt,strlen(prompt)); /* Printing out the personalized shell prompt */
/* This will read a character 1 by 1 until it reaches the end of file */
entry = read(0,&ch,1);
if(!entry)
exit(0);
/* Reading the input and storing it in inputStr as long as newline is not encountered */
while( entry != 0 && flag == 0 )
{
/* A newline has been found so a new command will need to be executed */
/* The inputStr till this point is sent to tokenize() */
if( ch == '\n' )
{
inputStr[chCount] = '\0';
flag = 1;
if(chCount > 0) {
if(strcmp(inputStr,"exit") == 0)
exit(3);
else
tokenize(inputStr);
}
}
inputStr[chCount] = ch;
chCount++;
if(flag == 0)
entry = read( 0, &ch, 1 );
}
}
}
See the man page for pipe(2). It has this example:
#include <sys/wait.h>
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
int
main(int argc, char *argv[])
{
int pipefd[2];
pid_t cpid;
char buf;
assert(argc == 2);
if (pipe(pipefd) == -1) {
perror("pipe");
exit(EXIT_FAILURE);
}
cpid = fork();
if (cpid == -1) {
perror("fork");
exit(EXIT_FAILURE);
}
if (cpid == 0) { /* Child reads from pipe */
close(pipefd[1]); /* Close unused write end */
while (read(pipefd[0], &buf, 1) > 0)
write(STDOUT_FILENO, &buf, 1);
write(STDOUT_FILENO, "\n", 1);
close(pipefd[0]);
_exit(EXIT_SUCCESS);
} else { /* Parent writes argv[1] to pipe */
close(pipefd[0]); /* Close unused read end */
write(pipefd[1], argv[1], strlen(argv[1]));
close(pipefd[1]); /* Reader will see EOF */
wait(NULL); /* Wait for child */
exit(EXIT_SUCCESS);
}
}