I am writing my version of minishell and trying to implement heredoc (<<) in C. I decided to use tmpfile - first I write data from stdin to tmpfile until I reach a delimiter, then I change program's stdin to the fd of the tmpfile with dup2 and, then, try to execute cat command with execve.
I tried to simplify the program and include all relevant functions below:
int main(int argc, char **argv, char **env)
{
t_shell shell;
ft_parse_envs_to_lst(&envs, env); // transform env to linked list
shell.tmpfile = "path to tmpfile";
while (1)
{
char *buf = readline("bash: ");
add_history(buf);
shell.std_in = dup(0);
shell.std_out = dup(1);
shell.f_in = dup(0);
shell.f_out = dup(1);
/* Token is represented by linked list. "cat << eof" translates into "cat" -> ">>" -> "eof",
with token pointing to "cat" */
t_token *token = parse_buffer(buf); // parse_buffer will return pointer to the first token
ft_execute_token(shell, token, env);
free(buf);
}
}
void ft_execute_token(t_shell *shell, t_token *token, t_envs_lst *env)
{
process_next_cmd(shell, token, env);
close(shell->f_in);
close(shell->f_out);
dup2(shell->std_in, STDIN_FILENO);
dup2(shell->std_out, STDOUT_FILENO);
close(shell->std_in);
close(shell->std_out);
}
void process_next_cmd(t_shell *shell, t_token *token, t_envs_lst *env)
{
t_token *prev = ft_get_prev_token(token); // get prev separator (for example, <<) or NULL
t_token *next = ft_get_next_token(token); // get next separator (for example, <<) or NULL
if (prev && (prev->type == DOBINP)) // "<<" will be marked as DOBINP
ft_handle_dobinp(shell, token);
if (next)
process_next_cmd(shell, next->next, env); // recursively go to the next command
if (!prev) // won't run any command on the part after "<<"" but will run "cat"
{
ft_execute_cmd(token, env); // just execve on child process (created with fork), whilst parent is waiting for child
if (next && next->type == DOBINP) // delete tmpfile
{
char **argv = malloc(sizeof(char *) * 3);
argv[0] = "/bin/rm";
argv[1] = shell->tmpfile;
argv[2] = NULL;
pid_t pid = fork();
if (pid == 0)
execve("/bin/rm", argv, NULL);
}
}
}
void handle_dobinp(t_shell *shell, t_token *token)
{
int rd;
int fd;
int buf_size;
char *buf;
fd = open(shell->tmpfile, O_TRUNC | O_CREAT | O_WRONLY, 0777);
buf_size = strlen(token->str);
buf = malloc(buf_size + 1);
printf("program: Start\n");
rd = read(STDIN_FILENO, buf, buf_size);
while (rd > 0)
{
buf[rd] = '\0';
printf("program: Looping (read %s)", buf);
if (strncmp(buf, token->str, buf_size + 1) == 0)
break ;
write(fd, buf, rd);
rd = read(STDIN_FILENO, buf, buf_size);
}
free(buf);
close(fd);
shell->f_in = open(shell->tmpfile, O_RDONLY, 0777);
dup2(shell->f_in, STDIN_FILENO);
close(shell->f_in);
}
I want to execute cat << eof command. Everything works fine but I face with the problem of repeated output (during testing) in handle_dobinp function. Also one more iteration occurs in while cycle in main with empty input (i.e. program executed empty command).
There is only one process running, so I am not sure what is the cause of this behaviour?
Update: I updated program's output according to Edwin Buck comment.
bash$ cat << eof
program: Start
foo
program: Looping (read foo
)
bar
program: Looping (read bar)
program: Looping (read
)
eof
program: Looping (read eof)
foo
bar
bash$
bash$
Improve your logging. I imagine your output is correct, but looking like
bash$ cat << eof
program: Start
foo
program: Looping (read "foo")
program: Looping (read "\n")
bar
program: Looping (read "bar")
program: Looping (read "\n")
eof
program: Looping (read "eof")
program: foo
program: bar
bash$
bash$
Related
I've been writing a shell program in C. The program is working as expected in Linux (Ubuntu 16.04) but I'm getting unexpected output in MacOS (10.14.2 Mojave).
/* A shell program.
*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
void input(char* argv[]);
void print_arr(char *argv[]); // For debugging
int
main(void)
{
while (1)
{
pid_t pid;
char *argv[100];
// Display shell prompt
write(1, "(ash) $ ", 8);
// Take user input
input(argv);
// print_arr(argv); // DEBUG STATEMENT
if (argv[0] != NULL)
{
// Exit if exit command is entered
if (strcmp(argv[0], "exit") == 0)
{
exit(0);
}
// Create child process
if ((pid = fork()) > 0)
{
wait(NULL);
}
else if (pid == 0)
{
// print_arr(argv); // DEBUG STATEMENT
execvp(argv[0], argv);
printf("%s: Command not found\n", argv[0]);
exit(0);
}
else
{
printf("Fork Error!\n");
}
}
}
}
/* Takes input from user and splits it in
tokens into argv. The last element in
argv will always be NULL. */
void
input(char* argv[])
{
const int BUF_SIZE = 1024;
char buf[BUF_SIZE];
int i;
buf[0] = '\0';
fgets((void*) buf, BUF_SIZE, stdin);
i = 0;
argv[i] = strtok(buf, " \n\0");
while (argv[i] != NULL)
{
argv[++i] = strtok(NULL, " \n\0");
}
}
/* Print argv for debugging */
void
print_arr(char *argv[])
{
int i = 0;
while (argv[i] != NULL)
{
printf("%d: %s\n", i, argv[i]);
++i;
}
}
In Linux:
(ash) $ ls
// files and folders are listed
In MacOS (with debug statements):
(ash) $ ls
0: p?M??
0: ??M??
: Command not found
(ash) $ ls
0: ls
0: ??M??
: Command not found
(ash) $ ls
0: ls
0: ??M??
I don't understand that why are the contents of char* argv[] getting modified across fork()?
I've also tried it in the default clang compiler and brew's gcc-4.9, the results are same.
When a program behaves different for no good reason, that's a VERY good sign of undefined behavior. And it is also the reason here.
The array buf is local to the function input and ceases to exist when the function exits.
One way of solving this is to declare buf in main and pass it to input. You will also need the size of the buffer for fgets.
void
input(char * argv[], char * buf, size_t size)
{
buf[0] = '\0';
fgets(buf, sizeof buf, stdin);
argv[0] = strtok(buf, " \n\0");
for(int i=0; argv[i] != NULL; i++) argv[i+1] = strtok(NULL, " \n\0");
}
Another solution (although I suspect many will frown upon it) is to declare buf as static, but then you would need to change BUF_SIZE to a #define or a hard coded value, since you cannot have a static VLA.
#define BUF_SIZE 1024
void
input(char * argv[])
{
static char buf[BUF_SIZE];
buf[0] = '\0';
fgets(buf, sizeof buf, stdin);
argv[0] = strtok(buf, " \n\0");
for(int i=0; argv[i] != NULL; i++) argv[i+1] = strtok(NULL, " \n\0");
}
I removed the cast to void* since it's completely unnecessary. I also changed the while loop to a for loop to make the loop variable local to the loop.
I'm trying to make a program which invokes the ls and grep system calls using exec. Specifically I have to execute ls > tmp; grep -c pattern < tmp in order to count the number of files that fulfill the pattern. As you can see I save the content of ls in tmp file and then I want to use grep to count the files.
Let's supose pattern = txt. I'm trying things like the following code:
char *a = "ls > tmp";
char *b = " -c ";
char *fin = " < tmp";
char *comanda;
if((comanda = malloc(strlen(pattern)+strlen(pattern)+1)) != NULL){
comanda[0] = '\0'; // ensures the memory is an empty string
strcat(comanda,b);
strcat(comanda, pattern);
strcat(comanda,fin);
} else {
return -1;
}
ret = execl("/bin/sh","sh","-c",a,NULL);
ret = execl("/bin/sh","sh","-c",comanda, NULL);
But it shows me the following error: ls: cannot access > tmp: No such file or directory. So I don't know how to get the value of grep, because the execl function does not return the value, so how can I achieve the grep value?
To get the output of a command, you need to use a pipe.
Have a look at : Connecting n commands with pipes in a shell?
You could just do:
ls | grep -c pattern
If you just want to get files with a specific pattern in filename you might want to use find
find your_path/ -name "*pattern*" | wc -l
Have a look at Grabbing output from exec to get the output of execl
Here is an example, replace the 4th argument of execl with whatever you want :)
(execl("/bin/sh", "sh", "-c", "ls > tmp; grep -c 'pattern' < tmp", (char *)NULL);)
#include <unistd.h>
#include <string.h>
int main()
{
int fd[2];
pipe(fd);
if (fork() == 0)
{
close(fd[0]);
dup2(fd[1], 1);
dup2(fd[1], 2);
close(fd[1]);
execl("/bin/sh", "sh", "-c", "find your_path -name '*pattern*' | wc -l", (char *)NULL);
}
else
{
char buffer[1024] = {0};
close(fd[1]);
while (read(fd[0], buffer, sizeof(buffer)) != 0)
{
write(1, buffer, strlen(buffer));
memset (buffer, 0, sizeof(buffer));
}
}
return 0;
}
You're not allocating the correct amount of space for comanda, because you don't add the sizes of all the variables correctly. So if the size is too small, you'll write outside the array bounds when you do all the strcat, and this will cause undefined behavior.
You don't need the temporary file, you can just pipe from ls to grep. I've also added quotes around the pattern in case it contains special characters.
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
int main() {
char *a = "ls | grep -c '";
char *fin = "'";
char *pattern = "foo";
char *comanda;
if((comanda = malloc(strlen(a) + strlen(pattern) + strlen(fin) +1)) != NULL){
strcpy(comanda,a);
strcat(comanda,pattern);
strcat(comanda,fin);
} else {
return -1;
}
int ret = execl("/bin/sh","sh","-c", comanda, (char*)NULL);
perror("execl"); // Can only get here if there's an error
}
I am having an issue that is just seeming to slip past my knowledge. I am writing a simple shell to learn some systems programming for an internship coming up with Unisys. In my shell, it seems that all of the commands I am trying are working besides the ls and even now discovering the wc command. ls and wc works when I type it by itself, but if I give it arguments, it will fail to work and give me an error saying No such file or directory.
here is my code:
#include <sys/types.h>
#include <sys/wait.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sysexits.h>
#include <unistd.h>
#define BUF_SIZE 1024
#define DELIMS " -\r\t\n"
/****************************************************************
* Capture input from the user. Returns the input from the
* standard input file descriptor.
***************************************************************/
char * getInput (char **buffer, size_t buflen)
{
size_t bufsize = BUF_SIZE;
*buffer = malloc(sizeof(char) * bufsize + 1); // allocate space for the buffer
if (!*buffer)
{
fprintf(stderr, "Shell: buffer allocation error\n");
exit(EXIT_FAILURE);
}
printf("$$ ");
fflush(NULL);
int bytesRead = getline(&(*buffer), &bufsize, stdin);
if (bytesRead < 0)
{
printf("Getline error\n");
exit(EXIT_FAILURE);
}
return *buffer; // Not capturing return value right now
}
/****************************************************************
* Tokenize the buffer input from stdin
***************************************************************/
char ** splitLine(char *line)
{
int bufsize = BUF_SIZE;
int pos = 0;
char **tokens = malloc (sizeof(char) * BUF_SIZE + 1);
char *token;
if (!tokens)
{
fprintf(stderr, "Shell: buffer allocation error\n");
exit(EXIT_FAILURE);
}
/* Tokenize the line */
token = strtok(line, DELIMS);
while (token != NULL)
{
tokens[pos] = token;
pos++;
if (pos > bufsize)
{
bufsize += BUF_SIZE;
tokens = realloc(tokens, bufsize * sizeof(char) + 1);
if (!tokens)
{
fprintf(stderr, "Shell: buffer allocation error\n");
exit(EXIT_FAILURE);
}
}
token = strtok(NULL, DELIMS); // continue grabbing tokens
}
tokens[pos] = NULL;
return tokens;
}
/****************************************************************
* Main function
***************************************************************/
int main (int argc, char **argv)
{
char *buf; // buffer to hold user input from standard input stream.
pid_t pid; // Parent id of the current process
int status;
/* Loop while the user is getting input */
while (getInput(&buf, sizeof(buf)))
{
char **args = splitLine(buf);
int i = 0;
/* Print tokens just to check if we are processing them correctly */
while (1)
{
char *token = args[i++];
if (token != NULL)
printf("Token #%d: %s\n", i, token);
else
break;
}
fflush(NULL);
/* Fork and execute command in the shell */
pid = fork();
switch(pid)
{
case -1:
{
/* Failed to fork */
fprintf(stderr, "Shell cannot fork: %s\n", strerror(errno));
continue;
}
case 0:
{
/* Child so run the command */
execvp(args[0], args); // Should not ever return otherwise there was an error
fprintf(stderr, "Shell: couldn't execute %s: %s\n ", buf, strerror(errno));
exit(EX_DATAERR);
}
}
/* Suspend execution of calling process until receiving a status message from the child process
or a signal is received. On return of waitpid, status contains the termination
information about the process that exited. The pid parameter specifies the set of child
process for which to wait for */
if ((pid = waitpid(pid, &status, 0) < 0))
{
fprintf(stderr, "Shell: waitpid error: %s\n", strerror(errno));
}
free(args);
}
free(buf);
exit(EX_OK);
}
For example, I have tried the following commands with output:
ls -la (THE ISSUE)
$$ ls -la
Token #1: ls
Token #2: la
ls: la: No such file or directory
$$
wc -l (THE ISSUE)
$$ wc -l
Token #1: wc
Token #2: l
wc: l: open: No such file or directory
ls
$$ ls
Token #1: ls
Makefile driver driver.dSYM main.c main.o
$$
ps -la
$$ ps -la
Token #1: ps
Token #2: la
UID PID PPID CPU PRI NI VSZ RSS WCHAN STAT TT TIME COMMAND
0 2843 2405 0 31 0 2471528 8 - Us s000 0:00.08 login
501 2845 2843 0 31 0 2463080 1268 - S s000 0:01.08 -bash
501 4549 2845 0 31 0 2454268 716 - S+ s000 0:00.01 ./driv
0 4570 4549 0 31 0 2435020 932 - R+ s000 0:00.00 ps la
$$
which which
$$ which which
Token #1: which
Token #2: which
/usr/bin/which
which -a which
$$ which -a which
Token #1: which
Token #2: a
Token #3: which
/usr/bin/which
and even finally man getline
GETLINE(3) BSD Library Functions Manual GETLINE(3)
NAME
getdelim, getline -- get a line from a stream
LIBRARY
Standard C Library (libc, -lc)
.
.
.
Can anybody help me point out why I am having this issue?
Youve added "-" as a word seperator in the DELIMS macro.
Removing it should fix your problem.
As an aside, its probably best to avoid macros where you can do so easily. Here, I would have used a const char* delims to store the separators. I usually find it easier to declare a variable close to where its used - I think that makes it easier to spot bugs and read the code.
If no args to main then my program should do printenv | sort | less and I've achieved that functionality. If main has arguments then the program should do printenv | grep <parameter list> | sort | less and my problem is that debugging is not working. I can try statement printf in my code and it doesn't do anything. Why? And why is the latter part of my requirement not working? What is wrong with the program?
The expected output is printenv | grep <parameter list> | sort | less. For example I would like to query the environment variables so that executing a.out JOBS COMPIZ UPSTART should be doing the same as a printenv | grep -e 'JOBS\|COMPIZ\|UPSTART' | sort | less.
Instead I get unexpected output when trying to fork a chain of commands.
#include <sys/types.h> /* definierar bland annat typen pid_t */
#include <errno.h> /* definierar felkontrollvariabeln errno */
#include <stdio.h> /* definierar stderr, dit felmeddelanden skrivs */
#include <stdlib.h> /* definierar bland annat exit() */
#include <unistd.h> /* definierar bland annat fork() */
struct command
{
const char **argv;
};
int
spawn_proc (int in, int out, struct command *cmd)
{
pid_t pid;
if ((pid = fork ()) == 0)
{
if (in != 0)
{
dup2 (in, 0);
close (in);
}
if (out != 1)
{
dup2 (out, 1);
close (out);
}
return execvp (cmd->argv [0], (char * const *)cmd->argv);
}
return pid;
}
int
fork_pipes (int n, struct command *cmd)
{
int i;
pid_t pid;
int in, fd [2];
/* The first process should get its input from the original file descriptor 0. */
in = 0;
/* Note the loop bound, we spawn here all, but the last stage of the pipeline. */
for (i = 0; i < n - 1; ++i)
{
pipe (fd);
/* f [1] is the write end of the pipe, we carry `in` from the prev iteration. */
spawn_proc (in, fd [1], cmd + i);
/* No need for the write and of the pipe, the child will write here. */
close (fd [1]);
/* Keep the read end of the pipe, the next child will read from there. */
in = fd [0];
}
/* Last stage of the pipeline - set stdin be the read end of the previous pipe
and output to the original file descriptor 1. */
if (in != 0)
dup2 (in, 0);
/* Execute the last stage with the current process. */
return execvp (cmd [i].argv [0], (char * const *)cmd [i].argv);
}
int
main (int argc, char ** argv)
{
printf("in main...");
int i;
if (argc == 1) {
const char *printenv[] = { "printenv", 0};
const char *sort[] = { "sort", 0 };
const char *less[] = { "less", 0 };
struct command cmd [] = { {printenv}, {sort}, {less} };
return fork_pipes (3, cmd);
}
if (argc > 1) {
char *tmp = argv[1];
for( i=1; i<argc-1; i++)
{
sprintf(tmp, "%s%s%s", tmp, "|", argv[i]);
}
const char *printenv[] = { "printenv", 0};
const char *grep[] = { "grep", "-E", tmp, NULL};
const char *sort[] = { "sort", 0 };
const char *less[] = { "less", 0 };
struct command cmd [] = { {printenv}, {grep}, {sort}, {less} };
return fork_pipes (4, cmd);
}
}
Part of the problem is that you are writing to a read-only memory segment by writing to argv[1] (due to the tmp = argv[1] statement). It is further aggravated by the fact that you are more than likely writing beyond the size of argv[1]. Instead you should concatenate the string to a new writable buffer of sufficient size.
To concatenate the string into the tmp variable you can use code similar to the following:
// Compute required buffer length
int len = 1; // adds 1 to the length to account for the \0 terminating char
for( i=1; i<argc; i++)
{
len += strlen(argv[i]) + 2; // +2 accounts for length of "\\|"
}
// Allocate buffer
tmp = (char*) malloc(len);
tmp[0] = '\0';
// Concatenate argument into buffer
int pos = 0;
for( i=1; i<argc; i++)
{
pos += sprintf(tmp+pos, "%s%s", (i==1?"":"|"), argv[i]);
}
printf("tmp:%s", tmp);
fflush(stdout); // force string to be printed
...
free(tmp);
As far as why the output does not appear, it is most likely due to the fact that printf is line buffered. In other words, it typically won't be printed until an end-of-line (\n) has to be printed or a fflush explicitly forces the buffer to be printed to the console.
Note: don't forget to free() the variable tmp once you are done with it.
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.