ProgrammingThis forum is for all programming questions.
The question does not have to be directly related to Linux and any language is fair game.
Notices
Welcome to LinuxQuestions.org, a friendly and active Linux Community.
You are currently viewing LQ as a guest. By joining our community you will have the ability to post topics, receive our newsletter, use the advanced search, subscribe to threads and access many other special features. Registration is quick, simple and absolutely free. Join our community today!
Note that registered members see fewer ads, and ContentLink is completely disabled once you log in.
If you have any problems with the registration process or your account login, please contact us. If you need to reset your password, click here.
Having a problem logging in? Please visit this page to clear all LQ-related cookies.
Get a virtual cloud desktop with the Linux distro that you want in less than five minutes with Shells! With over 10 pre-installed distros to choose from, the worry-free installation life is here! Whether you are a digital nomad or just looking for flexibility, Shells can put your Linux machine on the device that you want to use.
Exclusive for LQ members, get up to 45% off per month. Click here for more info.
I am trying to create a shell in unix (using C).
I tried implementing output redirection as a part of it.
But the problem is when I did close(1) and dup()ed it, my standard output is permanently redirected to a file. how can I open the stdout again after closing it.
Code:
/*cmd contains the command to be executed,
sym contains the redirection symbol and
filename contains the name of the file to be redirected to*/
if(strcmp(sym,">")==0)
{
fd=open(filename,O_CREAT|O_RDWR,S_IRWXU|S_IRGRP|S_IROTH);
close(1);
dup(fd);
}
else if(strcmp(sym,">>")==0)
{
fd=open(filename,O_CREAT|O_RDWR|O_APPEND,S_IRWXU|S_IRGRP|S_IROTH);
close(1);
dup(fd);
}
pid=fork();
if(pid==0)
{
execlp(cmd,cmd,NULL);
exit(1);
}
But the problem is when I did close(1) and dup()ed it, my standard output is permanently redirected to a file.
So you're dup()-ing a closed file descriptor. Doesn't sound useful to me (but maybe I am overlooking somethinkg?)
Quote:
how can I open the stdout again after closing it.
IIRC you cannot. Just don't close it.
Quote:
Code:
close(1);
dup(fd);
You are not using the duplicated file descriptor that dup() returns.
In your code, you are opening a file which you do not seem to use. Then you close stdout of your shell process (instead of a process that runs a command from your shell). And then you fork and run a program.
I think you're a bit off-track with the exec() and file descriptor stuff. I did something shell-like once and I found it confusing too. Here's some code that I used (with success). Hope you can use it. You may need to adapt it to your own plan.
Code:
int run_command(const char *command)
{
int fd;
int pipein[2];
pid_t childpid;
if (pipe(pipein) == -1) {
perror("Error opening pipe to child process");
return 0;
}
childpid = fork();
if (childpid == -1) {
perror("Error starting process (forking)");
return 0;
}
if (childpid == 0) {
/* child process */
close(pipein[1]);
if (dup2(pipein[0], 0) == -1) {
perror("Error connecting pipe to stdin");
exit(1);
}
if ((fd = open("/dev/null", O_WRONLY)) == -1) {
perror("Opening stdout dump file");
} else {
if (dup2(fd, 1) == -1) {
perror("Error redirecting stdout of the program");
exit(1);
}
}
if (execvp(command, (char *)NULL) == -1) {
perror("Error running program.");
exit(1);
}
}
/* parent process */
close(pipein[0]);
return childpid;
}
You have it essentially correct. Make the following changes:
1) Remove the close and dup calls.
2) Check the new process for < 0 first. If it is, return an error.
3) In your child process, dup2(fd, STDOUT_FILENO); and close(fd);. If dup2 returns < 0, return an error.
4) Always either return an error or raise(SIGKILL); right after an exec call. Regardless of the return value, if it gets to the next instruction that means it failed. And never allow the child process to continue on back into the main function block. Normally you would not want to return from a child, anyway. _exit(0); is the best way to go if you don't want to SIGKILL since it bypasses the cleanup code.
5) Immediately after the child code block, close(fd);. That will become very important when you start dealing with pipes.
6) I assume your shell is the session leader and the command will need terminal access. To give it terminal access, call tcsetpgrp(STDIN_FILENO, pid);. That will give the child control of the terminal. Using the standard input file descriptor isn't the best method, but will work for now. Normally you might reopen the terminal on another descriptor with ctermid.
7) Next, you should use some form of waitpid to determine when the process is finished. You should not use it without any flags, though, because if it stops for some reason it will hang your shell. After that, regain terminal control with tcsetpgrp(STDIN_FILENO, getpid());. NOTE: that will only work if the shell is the session leader. Otherwise it will stop the process until the real leader gives it terminal control.
I made a small change in the code and i am able to do it.
I have just forked the process b4 dup()ing so that duping is done only in the child process and so when the process returns to the parent it still has the normal stout.
Code:
/*cmd contains the command to be executed,
sym contains the redirection symbol and
filename contains the name of the file to be redirected to*/
pid=fork();
if(pid==0)
{
if(strcmp(sym,">")==0)
{
fd=open(filename,O_CREAT|O_RDWR,S_IRWXU|S_IRGRP|S_IROTH);
close(1);
dup(fd);
}
else if(strcmp(sym,">>")==0)
{
fd=open(filename,O_CREAT|O_RDWR|O_APPEND,S_IRWXU|S_IRGRP|S_IROTH);
close(1);
dup(fd);
}
execlp(cmd,cmd,NULL);
exit(1);
}
You still need to dup2(fd, STDOUT_FILENO);, otherwise your work is in vain. dup(fd); is a useless call when not using the return. The reason it works (by chance) is that FD 1 is closed now and is the first available file descriptor to place the duplicate in. That is hardly something you can count on: Call close(0); before close(1); and my guess is it will fail miserably. You need to change this:
Code:
close(1);
dup(fd);
to this:
Code:
if (dup2(fd, STDOUT_FILENO) < 0) _exit(1);
if (fd != STDOUT_FILENO) close(fd);
dup2 closes the target FD for you and replaces it with a copy of the first argument. This is done atomically, which means that at no point is FD 1 actually closed, whereas closing then duplicating by hand leaves a short gap in the usability of the FD. As you have it, sure you have a working FD 1, but you also have a second open FD for the same file, which can seriously hang a program if both are pointing to a pipe.
I'd also add the O_TRUNC flag to the ">" open mode and change exit(1) to _exit(1) so that it doesn't try to clean up after its copy of its parent, as I'm sure you don't intend for the fork to be a functional duplicate of the parent. Also, you should restore signal handlers immediately after the fork if you use non-default ones in the parent so signal handlers don't attempt to clean up, either. For the most part it's of little effect, but (for example) in one of my applications signal/exit handlers free up shared memory and if a fork did that the main process would fail.
PS Another good practice is to raise(SIGSTOP); right before the exec call so the parent can chain together multiple commands using pipes. While it does that, it changes all of their process groups to that of the first process, then calls kill(pid, SIGCONT); to get them started. This also allows the entire group to be placed in the foreground or background and a signal to one is a signal to all.
Last edited by ta0kira; 08-29-2007 at 01:59 PM.
Reason: comments, comments, comments...
In some cases you can, if it happens to be the same as the controlling terminal:
Code:
#include <stdio.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
int main()
{
/* close the standard descriptors */
close(STDIN_FILENO);
close(STDOUT_FILENO);
close(STDERR_FILENO);
char input[1024];
/* make sure they are closed */
fgets(input, 1024, stdin);
fprintf(stdout, "error!\n");
fflush(stdout);
fprintf(stderr, "error!\n");
fflush(stderr);
/* get name of controlling terminal */
if (strlen(ctermid(NULL)) == 0) return 1;
int terminal = -1;
/* open terminal for standard input */
terminal = open(ctermid(NULL), O_RDONLY);
if (terminal < 0) return 1;
if (dup2(terminal, STDIN_FILENO) < 0) return 1;
if (terminal != STDIN_FILENO) close(terminal);
/* open terminal for standard output and error */
terminal = open(ctermid(NULL), O_WRONLY);
if (terminal < 0) return 1;
if (dup2(terminal, STDOUT_FILENO) < 0) return 1;
if (dup2(terminal, STDERR_FILENO) < 0) return 1;
if (terminal != STDOUT_FILENO && terminal != STDERR_FILENO) close(terminal);
/* test the "new" file descriptors */
fprintf(stderr, "what is your name? ");
if (fgets(input, 1024, stdin) == NULL) return 1;
fprintf(stdout, "your name is: %s", input);
return 0;
}
ta0kira
PS Also try calling with setsid and note that it doesn't work.
LinuxQuestions.org is looking for people interested in writing
Editorials, Articles, Reviews, and more. If you'd like to contribute
content, let us know.