close(fileno(stdout));
int fd = dup(fileno(stdin));
//printf("Hello World\n");
write(fd, "Hello", 7);
Here for both printf and write is writing Hello to the screen..But I think they should not because I am duplicating the stdin to 1 or stdout closed perviously. And printf is connected to stdout but not stdin but still they are printing...Please explain if I have anything wrong in understanding the dup
The printf and write in your code happen to work, due to historical custom. Here's what's happening:
When a user logs into a terminal on a Unix-like system, or opens a terminal window under X11, file descriptors 0, 1, and 2 are connected to a terminal device, and each of them is opened for both reading and writing. This is the case despite the fact that one normally only reads from fd 0 and writes to fd 1 and 2.
Yet here is the code from 7th edition init.c:
open(tty, 2);
dup(0);
dup(0);
...
execl(getty, minus, tty, (char *)0);
And here is how ssh does it:
ioctl(*ttyfd, TCSETCTTY, NULL);
fd = open("/dev/tty", O_RDWR);
if (fd < 0)
error("%.100s: %.100s", tty, strerror(errno));
close(*ttyfd);
*ttyfd = fd;
...
/* Redirect stdin/stdout/stderr from the pseudo tty. */
if (dup2(ttyfd, 0) < 0)
error("dup2 stdin: %s", strerror(errno));
if (dup2(ttyfd, 1) < 0)
error("dup2 stdout: %s", strerror(errno));
if (dup2(ttyfd, 2) < 0)
error("dup2 stderr: %s", strerror(errno));
(The dup2 function dups arg1 into arg2, closing arg2 first if necessary.)
And here is how xterm does it:
if ((ttyfd = open(ttydev, O_RDWR)) >= 0) {
/* make /dev/tty work */
ioctl(ttyfd, TCSETCTTY, 0);
...
/* this is the time to go and set up stdin, out, and err
*/
{
/* dup the tty */
for (i = 0; i <= 2; i++)
if (i != ttyfd) {
IGNORE_RC(close(i));
IGNORE_RC(dup(ttyfd));
}
/* and close the tty */
if (ttyfd > 2)
close_fd(ttyfd);
Back to your code.
close(fileno(stdout));
This closes fd 1.
int fd = dup(fileno(stdin));
This duplicates fd 0 into the lowest available fd, which is 1, and assigns 1 to fd. (This assumes that fd 0 is open, of course.) Both fd's 0 and 1 are now open for reading and writing (assuming they're connected to a terminal device). On a Linux system you can verify this:
$ cat /proc/self/fdinfo/0
pos: 0
flags: 0100002
mnt_id: 20
$ cat /proc/self/fdinfo/1
pos: 0
flags: 0100002
mnt_id: 20
The 2 in flags, which is the constant O_RDWR, means open for reading and writing.
printf("Hello World\n");
This writes to fd 1, which is, once again, open for reading and writing to your terminal.
write(fd, "Hello", 7);
This writes to fd 1 again. (Note that "Hello" is only 6 bytes, though.)
If you are running your program on a terminal, then stdin and stdout are opens of the same file - your terminal device. So it should not be surprising that writing to stdin - or a dup() of fileno(stdin) - writes to your terminal device, and you see it.
If you run your program with stdin connected somewhere else, like a disk file, you'll see a different result:
./program < file
Related
In this code my program crashes in when I am opening the pipe for writing.
char pipe[30];
int fd, tmp = 2;
sprintf(pipe, "root_%d", getpid());
ret_val = mkfifo(pipe, 0666);
fd = open(pipe, O_WRONLY); //HERE IS CRASHING - SUDDENLY FREEZES
write(fd, &tmp, sizeof(int));
close(fd)
All seems good, but where is my mistake;
It is an expected behavior. From man 7 fifo:
Normally, opening the FIFO blocks until the other end is opened also.
So your open does not return until somebody opens the same pipe for reading. You may want to add O_NONBLOCK flag (and likely get SIGPIPE on writing), or revisit the design.
I have read this post Determine between socket and fd, and the answer recommends creating a structure with two fields, is there another way to test if a descriptor is a socket or a regular file on windows ?
You could try something that should only work on a socket, and see if it fails with ENOTSOCK. getsockname() for example.
this could help:
replace stdin stdout stderr with a socket…
http://www6.uniovi.es/cscene/CS5/CS5-05.html
int exec_comm_handler( int sck )
{
close(0); /* close standard input */
close(1); /* close standard output */
close(2); /* close standard error */
if( dup(sck) != 0 || dup(sck) != 1 || dup(sck) != 2 ) {
perror("error duplicating socket for stdin/stdout/stderr");
exit(1);
}
printf("this should now go across the socket...\n");
execl( "/bin/sh", "/bin/sh", "-c", "/path/to/redirected_program" );
perror("the execl(3) call failed.");
exit(1);
}
Our 'comm handler' first closes the file descriptors for standard input,
standard output, and standard error. It then uses dup(2) to duplicate
the socket file handle. dup(2) will duplicate the given file descriptor
and return the duplicate as the next available descriptor. Since 0, 1,
and 2 are the next available descriptors, they should be the returned
duplicates. Now operations on stdin/stdout/stderr [0/1/2] will act
upon the socket instead of the original stdin/stdout/stderr.
I was unaware that this technique (calling dup(2) on a socket file
descriptor) was possible untill seeing code written by Martin Mares.
I saw following function in setup.c of git source code.
Code:
/* if any standard file descriptor is missing open it to /dev/null */
void sanitize_stdfds(void)
{
int fd = open("/dev/null", O_RDWR, 0);
while (fd != -1 && fd < 2)
fd = dup(fd);
if (fd == -1)
die_errno("open /dev/null or dup failed");
if (fd > 2)
close(fd);
}
It try to open stdio file descriptors (0/1/2) to /dev/null, if they are missing.
My question is:
In while (fd != -1 && fd < 2) , why use 2, but not 3.
Because if fd == 2 you have opened file descriptors 0,1,2 and there is nothing more to do. You only needed to open those 3 descriptors for stdin, stdout and stderr.
If you put there 3, the loop would open file descriptors 0,1,2,3. Then the line if (fd > 2) close(fd); would close the descriptor 3. So it will work in both cases, but the original solution is better.
The reason of fd < 2 is because you have already called the method int fd = open("/dev/null", O_RDWR, 0); .
"The file descriptor returned by a successful call(open) will be the lowest-numbered file descriptor not currently open for the process." So if the stdio file descriptors (0/1/2) are missing, the return of open has already occupied a missed file descriptors. Then you should judge by 2 rather than 3.
I am working on a pseudo terminal library. The code is implemented in C code and the code is used by a web based terminal. The code works as long as I do not use sudo or login.
This is the error I get when I run the server on a Mac:
sh-3.2$ sudo ls
Password:
[1]+ Stopped(SIGTTOU)
sh-3.2$
The above works on Linux:
$ sudo ls
readme.txt
However, I get the following on Linux with sudo bash:
$ sudo bash
bash: cannot set terminal process group (-1): Inappropriate ioctl for device
bash: no job control in this shell
]0;root#ubuntu: /tmproot#ubuntu:/tmp#
Note: the above works, but I have no job control.
I have probably forgot to set some controlling bits on the terminal, but Google has not been very helpful in finding this.
Also, do you know of any good books that explains Pseudo terminal management in great detail.
I have the setsid call, but I am not using openpty. I use the following code when opening the pty:
static int createPty(lua_State* L, char* ttyName, int* pty)
{
*pty = getpt();
if (*pty < 0 || grantpt(*pty) < 0 || unlockpt(*pty) < 0)
return lDoErr(L,"Cannot open PTY: %s",strerror(errno));
if(ptsname_r(*pty, ttyName, PTY_NAME_SIZE-1))
return lDoErr(L,"ptsname_r: %s",strerror(errno));
return 0;
}
I have edited the code below and this code works. The reason my first version did not work was that I tried to create two PTY channels. I wanted to be able to differentiate between stdout and stderr, but the Linux kernel does not allow multiple TIOCSCTTY calls.
static int
childOpenTTY(const char* ttyName)
{
struct termios termbuf;
int fd=open(ttyName, O_RDWR);
if(fd < 0)
doClientError("open %s: %s",ttyName, strerror(errno));
tcsetpgrp(fd, getpid());
ioctl(fd,TIOCSCTTY,NULL);
tcgetattr(fd, &termbuf);
cfmakeraw(&termbuf); /* turn off NL to CR/NL mapping on output. */
tcsetattr(fd, TCSANOW, &termbuf);
return fd;
}
if( (ret = createPty(L, ttyName, &te->pty)) != 0)
return ret;
if ((te->pid = zzbafork()) < 0)
return lDoErr(L,"fork: %s",strerror(errno));
if(te->pid == 0)
{ /* Child process */
static const char efmt[]={"Cannot set '%s' (dup2 err)"};
int fd;
if(setsid() < 0) /* make new process group */
doClientError("setsid: %s",strerror(errno));
fd=childOpenTTY(ttyName);
if(dup2(fd, STDIN_FILENO) != STDIN_FILENO)
doClientError(efmt,"stdin");
if(dup2(fd, STDOUT_FILENO) != STDOUT_FILENO)
doClientError(efmt,"stdout");
if(dup2(fd, STDERR_FILENO) != STDERR_FILENO)
doClientError(efmt,"stderr");
if(fd != STDIN_FILENO && fd != STDOUT_FILENO && fd != STDERR_FILENO)
close(fd);
execve(cmd, (char**)cmdArgv, environ);
/* execve should not return, unless error exec cmd */
doClientError("Executing %s failed: %s",cmd,strerror(errno));
}
It's hard to be sure since there's no actual code shown here, but I suspect you're running into POSIX-style "session" management. You need to execute a setsid call, then open the pty (slave side) such that it becomes the controlling terminal. The openpty and login_tty routines do the low level grunge work for you; are you using those?
I've got a C program I'm writing. Here's what it does:
Create n fifos using mkfifo
Open them for read (with the O_NONBLOCK flag set)
Open them for write
Spawn a thread
In the thread, run in a loop:
Create an fd_set of the file descriptors for all n fifos
Call select(n, &my_set, NULL, NULL, NULL)
For each fd ready for I/O (FD_ISSET(fd, &my_set)):
Read a string from the fd (read(fd, buf, buf_len))
Print the string
If string == "kill", mark the fd as dead and remove it from the list (n--)
If n == 0, terminate the thread
In the main program:
For i = 0 to n
Write to fds[i] with a string (write(fds[i], buf, buf_len))
For i = 0 to n
Write to fds[i] with the string "kill"
Join on the thread I created
Exit
The behavior I'm seeing is that select() will return once with a length of 1, being the first fd in the list. The second time through the loop, select will just sit there forever.
Here's my output:
thread created
Waiting on 4 file descriptors
> Wrote 'Hello to target 0 from writer 0' to 0
> Wrote 'Hello to target 0 from writer 1' to 1
> Wrote 'Hello to target 1 from writer 0' to 2
> Wrote 'Hello to target 1 from writer 1' to 3
> Sending kill to 0:0 (#0)
> Sending kill to 0:1 (#1)
> Sending kill to 1:0 (#2)
> Sending kill to 1:1 (#3)
< Got string: 'Hello to target 0 from writer 0'
Waiting on 4 file descriptors
^C
The OS is Linux, in case it matters.
Link to the code: https://dl.getdropbox.com/u/188590/fifotest.c
(Sorry it's a bit heinous)
Thanks,
Nathan
As Lance Richardson said, the first problem is that you need to pass the number of the maximum file descriptor plus one, not the number of file descriptors.
You then have to clean up the housekeeping in the listener thread - I got most of the data, but ended up listening to 6 file descriptors, not 4. (The reported number was now the largest fd, not the number of file descriptors.)
You also have a problem that you write a string plus a null byte to each pipe, then a second string plus a null byte. Since the scheduling is non-deterministic, the main program actually gets to write both its strings to each fifo, so when the listener thread gets to read it, it reads both strings. When I printed out lengths, I got a total length of 41 read (read_len), but the length of the string per strlen() was 31. In other words, the first read included the 'kill' part, but you didn't notice in the printout because of the trailing null at the end of the first message. Hence you were waiting for that which will never happen.
The first parameter in the call to select() should be the highest-numbered file descriptor plus 1, not the number of file descriptors in the fd_set.
Here's what I changed to fix this issue:
--- fifotest-1.c 2009-05-22 23:44:03.000000000 -0400
+++ fifotest.c 2009-05-22 23:34:00.000000000 -0400
## -34,19 +34,22 ##
sim_arg_t* ifs = arg;
uint32_t num_ifs;
uint32_t select_len;
+ int maxfd;
num_ifs = ifs->num_ifs;
while (num_ifs > 0) {
FD_ZERO (&set);
select_len = 0;
- for (i = 0; i < ifs->num_ifs; ++i) {
+ for (maxfd=0, i = 0; i < ifs->num_ifs; ++i) {
if (ifs->if_list[i].valid) {
FD_SET(ifs->if_list[i].fh, &set);
- ++select_len;
+ if (ifs->if_list[i].fh > maxfd)
+ maxfd = ifs->if_list[i].fh;
+ select_len++;
}
}
printf("Waiting on %d file descriptors\n", select_len);
- ret = select(select_len, &set, NULL, NULL, NULL);
+ ret = select(maxfd+1, &set, NULL, NULL, NULL);
if (ret < 0) {
fprintf(stderr, "Select returned error!\n");
continue;