LinuxQuestions.org
Help answer threads with 0 replies.
Home Forums Tutorials Articles Register
Go Back   LinuxQuestions.org > Forums > Non-*NIX Forums > Programming
User Name
Password
Programming This forum is for all programming questions.
The question does not have to be directly related to Linux and any language is fair game.

Notices


Reply
  Search this Thread
Old 08-27-2007, 01:31 AM   #1
k_raj_k
LQ Newbie
 
Registered: Aug 2007
Posts: 2

Rep: Reputation: 0
Lightbulb creating a shell


hi all...

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);
}

Last edited by k_raj_k; 08-27-2007 at 02:48 AM.
 
Old 08-27-2007, 06:58 PM   #2
Hko
Senior Member
 
Registered: Aug 2002
Location: Groningen, The Netherlands
Distribution: Debian
Posts: 2,536

Rep: Reputation: 111Reputation: 111
Quote:
Originally Posted by k_raj_k View Post
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;
}

Last edited by Hko; 08-27-2007 at 07:03 PM.
 
Old 08-28-2007, 02:39 AM   #3
ta0kira
Senior Member
 
Registered: Sep 2004
Distribution: FreeBSD 9.1, Kubuntu 12.10
Posts: 3,078

Rep: Reputation: Disabled
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.

ta0kira
 
Old 08-29-2007, 06:21 AM   #4
k_raj_k
LQ Newbie
 
Registered: Aug 2007
Posts: 2

Original Poster
Rep: Reputation: 0
Thumbs up

thanks for the response...

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);
}
 
Old 08-29-2007, 11:44 AM   #5
ta0kira
Senior Member
 
Registered: Sep 2004
Distribution: FreeBSD 9.1, Kubuntu 12.10
Posts: 3,078

Rep: Reputation: Disabled
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.

Something else you might do:
Code:
   execlp(cmd,cmd,NULL);
   fprintf(stderr, "%s: %s\n", argv[0], strerror(errno));
   _exit(1);
ta0kira

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...
 
Old 08-29-2007, 12:28 PM   #6
ta0kira
Senior Member
 
Registered: Sep 2004
Distribution: FreeBSD 9.1, Kubuntu 12.10
Posts: 3,078

Rep: Reputation: Disabled
Quote:
Originally Posted by Hko View Post
IIRC you cannot. Just don't close it.
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.

Last edited by ta0kira; 08-29-2007 at 01:55 PM.
 
  


Reply



Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is Off
HTML code is Off



Similar Threads
Thread Thread Starter Forum Replies Last Post
Shell Assignment : Creating a CLI please help! nsvikin Programming 1 09-05-2006 03:10 PM
creating a user with no shell fredanthony Linux - Newbie 8 07-18-2006 09:28 AM
Help with creating a shell script windisch Programming 66 10-07-2005 06:26 AM
creating a new user using shell only ? ( RH 9 ) CooLMaN Linux - General 4 04-04-2004 11:42 AM
Creating a linux shell with C CragStar Programming 2 10-02-2002 09:13 AM

LinuxQuestions.org > Forums > Non-*NIX Forums > Programming

All times are GMT -5. The time now is 04:45 PM.

Main Menu
Advertisement
My LQ
Write for LQ
LinuxQuestions.org is looking for people interested in writing Editorials, Articles, Reviews, and more. If you'd like to contribute content, let us know.
Main Menu
Syndicate
RSS1  Latest Threads
RSS1  LQ News
Twitter: @linuxquestions
Open Source Consulting | Domain Registration