Child process unable to read from created pseudo terminal - c

I'm trying to write an app that can login to SSH with a password, by using pseudo terminals. But if I write() to the master device then the data somehow does not appear in the slave device. Here's a simple test case:
#include <sys/wait.h>
#include <sys/types.h>
#include <stdio.h>
#include <unistd.h>
#ifdef __linux__
#include <pty.h>
#else
#include <util.h>
#endif
int
main() {
int master;
pid_t pid = forkpty(&master, NULL, NULL, NULL);
if (pid == 0) {
int ch;
read(0, &ch, 1);
_exit(1);
} else {
printf("Press Enter to send a byte.\n");
getchar();
write(master, "1", 1);
printf("Done. Waiting for process to exit...\n");
waitpid(pid, NULL, 0);
return 0;
}
}
The app will first output "Press Enter to send a byte". After pressing Enter, I expect the child process's read() to return. But the read() there seems to block indefinitely even though the master's write() succeeds, so the master waits forever on the waitpid(). What's going on?

The problem is that you didn't modify the line discipline of the PTY. The default line discipline is line oriented, so no input will be sent to the slave process until a newline character is read. (You can see this by sending a "\n" to the slave, instead of just "1".) You can run the PTY in RAW mode by, in the child process, calling tcgetattr, cfmakeraw and tcsetattr, like follows:
if (pid == 0) {
int ch;
struct termios t;
tcgetattr(0, &t);
cfmakeraw(&t);
tcsetattr(0, TCSANOW, &t);
read(0, &ch, 1);
_exit(1);
} else {
This seems to work for me.

The example code at this blog post should probably be of help. The author has an update to his original problem (very similar to yours) with a usable spawn (char *argv[]); function given.

Related

Issue with CTRL+D on Windows Subsystem for Linux

I was working on an assignment using Windows Subsystem for Linux. Below is the C Code that was used to write a mini-shell for this assignment.
I ran into an interesting issue using WSL. On line 35, you can see that I call the read function to read in the buffer and it does a check for null. When pressing Ctrl+D, while using WSL, it will go into the if statement and prints the print message on line 36 infinitely and does not stop until I used Ctrl+C to exit. When running this program on a Linux machine, it behaves appropriately and prints once, and brings us to the top of the loop.
Any ideas as to what this bug could be?
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <errno.h>
#include <string.h>
#include <sys/types
#include <unistd.h>
#include <error.h>
char prompt[] = "$ ";
static int
Fork()
{
pid_t pid;
if ((pid = fork()) < 0)
error(EXIT_FAILURE, errno, "fork error");
return(pid);
}
int
main(void)
{
long MAX = sysconf(_SC_LINE_MAX);
char buf[MAX];
pid_t pid;
int status, n;
do {
write(STDOUT_FILENO, prompt, strlen(prompt));
fflush(NULL);
memset(buf, 0, MAX);
if((n = read(STDIN_FILENO, buf, MAX)) == 0) {
printf("use exit to exit shell\n");
continue;
}
buf[strlen(buf) - 1] = '\0'; // chomp '\n'
if (strncmp(buf, "exit", MAX) == 0) { // match
break;
}
pid = Fork();
if (pid == 0) { // child
execlp(buf, buf, (char *)NULL);
error(EXIT_FAILURE, errno, "exec failure");
}
// parent
if ((pid = waitpid(pid, &status, 0)) < 0)
error(EXIT_FAILURE, errno, "waitpid error");
} while(1);
exit(EXIT_SUCCESS);
}
The program is in C but there are no options available to insert C Code snippets.
Documentation on read() (Linux manpages v 3.54) does not specify that end of file (ctrl/D) causes read to return anything besides of 0. On the contrary, it says that return value zero indicates end of file. So you're relying upon undefined behavior.
Somehow on your Linux ctrl/D causes error, thus read() returns -1. Your program in this case exits the loop. Or, ctrl/D is read literally, then read() returns 1.
Different OSs use different keystrokes for EOF.

c - spawned a bash shell. Shell died but pipe not broken?

Problem
I'm trying to pipe contents from the main routine to a execvp'd bash shell. I'm encountering a problem where when I write "exit" into the subshell, it doesn't tell me that the pipe is really broken. It should be though - right? The process died and thus the pipe fd should also return an EOF or a SIGPIPE. It doesn't, however, and just keeps on reading/writing like normal.
Code
The code is attached here:
/************************************************************
* Includes:
* ioctl - useless(?)
* termios, tcsetattr, tcgetattr - are for setting the
* noncanonical, character-at-a-time terminal.
* fork, exec - creating the child process for part 2.
* pthread, pipe - creating the pipe process to communicate
* with the child shell.
* kill - to exit the process
* atexit - does some cleanups. Used in termios, tcsetattr,
* tcgetattr.
************************************************************/
#include <sys/ioctl.h> // ioctl
#include <termios.h> // termios, tcsetattr, tcgetattr
#include <unistd.h> // fork, exec, pipe
#include <sys/wait.h> // waitpid
#include <pthread.h> // pthread
#include <signal.h> // kill
#include <stdlib.h> // atexit
#include <stdio.h> // fprintf and other utility functions
#include <getopt.h> // getopt
/**********************
* GLOBALS
**********************/
pid_t pid;
/**********************
* CONSTANTS
**********************/
static const int BUFFER_SIZE = 16;
static const int STDIN_FD = 0;
static const int STDOUT_FD = 1;
static const int STDERR_FD = 2;
// these attributes are reverted to later
struct termios saved_attributes;
// to revert the saved attributes
void
reset_input_mode (void) {
tcsetattr (STDIN_FILENO, TCSANOW, &saved_attributes);
}
// to set the input mode to correct non-canonical mode.
void
set_input_mode (void) {
struct termios tattr;
/* Make sure stdin is a terminal. */
if (!isatty (STDIN_FILENO))
{
fprintf (stderr, "Not a terminal.\n");
exit (EXIT_FAILURE);
}
/* Save the terminal attributes so we can restore them later. */
tcgetattr (STDIN_FILENO, &saved_attributes);
atexit (reset_input_mode);
/* Set the funny terminal modes. */
tcgetattr (STDIN_FILENO, &tattr);
tattr.c_lflag &= ~(ICANON|ECHO); /* Clear ICANON and ECHO. */
tattr.c_cc[VMIN] = 1;
tattr.c_cc[VTIME] = 0;
tcsetattr (STDIN_FILENO, TCSAFLUSH, &tattr);
}
// pthread 1 will read from pipe_fd[0], which
// is really the child's pipe_fd[1](stdout).
// It then prints out the contents.
void* thread_read(void* arg){
int* pipe_fd = ((int *) arg);
int read_fd = pipe_fd[0];
int write_fd = pipe_fd[1];
char c;
while(1){
int bytes_read = read(read_fd, &c, 1);
if(bytes_read > 0){
putchar(c);
}
else{
close(read_fd);
close(write_fd);
fprintf(stdout, "The read broke.");
fflush(stdout);
break;
}
}
}
// pthread 2 will write to child_pipe_fd[1], which
// is really the child's stdin.
// but in addition to writing to child_pipe_fd[1],
// we must also print to stdout what our
// argument was into the terminal. (so pthread 2
// does extra).
void* thread_write(void* arg){
set_input_mode();
int* pipe_args = ((int *) arg);
int child_read_fd = pipe_args[0];
int child_write_fd = pipe_args[1];
int parent_read_fd = pipe_args[2];
int parent_write_fd = pipe_args[3];
char c;
while(1) {
int bytes_read = read(STDIN_FD, &c, 1);
write(child_write_fd, &c, bytes_read);
putchar(c);
if(c == 0x04){
// If an EOF has been detected, then
// we need to close the pipes.
close(child_write_fd);
close(child_read_fd);
close(parent_write_fd);
close(parent_read_fd);
kill(pid, SIGHUP);
break;
}
}
}
int main(int argc, char* argv[]) {
/***************************
* Getopt process here for --shell
**************************/
int child_pipe_fd[2];
int parent_pipe_fd[2];
pipe(child_pipe_fd);
pipe(parent_pipe_fd);
// We need to spawn a subshell.
pid = fork();
if(pid < 0){
perror("Forking was unsuccessful. Exiting");
exit(EXIT_FAILURE);
}
else if(pid == 0){ // is the child.
// We dup the fd and close the pipe.
close(0); // close stdin. child's pipe should read.
dup(child_pipe_fd[0]); // pipe_fd[0] is the read. Make read the stdin.
close(child_pipe_fd[0]);
close(1); // close stdout
dup(parent_pipe_fd[1]); // pipe_fd[1] is the write. Make write the stdout.
close(parent_pipe_fd[1]);
char* BASH[] = {"/bin/bash", NULL};
execvp(BASH[0], BASH);
}
else{ // is the parent
// We dup the fd and close the pipe.
//
// create 2 pthreads.
// pthread 1 will read from pipe_fd[0], which
// is really the child's pipe_fd[1](stdout).
// It then prints out the contents.
//
// pthread 2 will write to pipe_fd[1], which
// is really the child's pipe_fd[0](stdin)
// but in addition to writing to pipe_fd[1],
// we must also print to stdout what our
// argument was into the terminal. (so pthread 2
// does extra).
//
// We also need to take care of signal handling:
signal(SIGINT, sigint_handler);
/*signal(SIGPIPE, sigpipe_handler);*/
int write_args[] = {child_pipe_fd[0], child_pipe_fd[1],
parent_pipe_fd[0], parent_pipe_fd[1]};
pthread_t t[2];
pthread_create(t, NULL, thread_read, parent_pipe_fd);
pthread_create(t+1, NULL, thread_write, write_args);
pthread_join(t[0], NULL);
pthread_join(t[1], NULL);
int status;
if (waitpid(pid, &status, 0) == -1) {
perror("Waiting for child failed.");
exit(EXIT_FAILURE);
}
printf("Subshell exited with the error code %d", status);
exit(0);
}
return 0;
}
The program basically pipes inputs from the terminal into the subshell and tries to execute them and return the outputs. To write to the pipe, I have a pthread that writes the stdin inputs into the subshell. To read to the pipe, I have a pthread that reads the pipe to the parent. To detect the broken pipe via the subshell dying(calling exit), I detect the EOF character from the read thread.
My attempts
I added a check for the 0x04 character(EOF), I checked for read_bytes == 0 or read_bytes < 0. It seems that it never gets the memo unless I explicitly close the pipes on the writing end. It only meets the EOF character if I send the character ^D(which, in my code, handles via closing all pipes of the child & parent).
Any comments would be appreciated! Thank you.
Your parent process is holding copies of the child's file descriptors. Thus, even after the child has exited, those FDs are still open -- so the other ends of those pipelines remain open as well, preventing any SIGPIPE.
Modify your code as follows:
else {
// pid >0; this is the parent
close(child_pipe_fd[0]); // ADD THIS LINE
close(parent_pipe_fd[1]); // ADD THIS LINE

result of child process's exection of some system command can't send to the father process with pipe

Maybe this is not a compact title, I am very sorry about that:). I try redirecting stdin/stdout of a child process to its parent process with pipes. The child process execute a system command from the father process input and return the exec result to the father process with a pipe. Here I implemented "cat -n" and "tr /a-z/ /A-Z/", the former works fine, but later haven't return any results. What has caused this? Thank you.
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <signal.h>
#include <assert.h>
#include <sys/sem.h>
#define ERR_EXIT(m) \
do { \
perror(m); \
exit(EXIT_FAILURE); \
} while( 0)
int main(int argc, char *argv[])
{
int chi_pipe[2], par_pipe[2];
if (pipe(chi_pipe) == -1 || pipe(par_pipe) == -1)
ERR_EXIT("pipe error");
/* Set O_NONBLOCK flag for the read end (pfd[0]) of the pipe. */
if (fcntl(chi_pipe[0], F_SETFL, O_NONBLOCK) == -1) {
fprintf(stderr, "Call to fcntl failed.\n"); exit(1);
}
/* Set O_NONBLOCK flag for the read end (pfd[0]) of the pipe. */
if (fcntl(chi_pipe[1], F_SETFL, O_NONBLOCK) == -1) {
fprintf(stderr, "Call to fcntl failed.\n"); exit(1);
}
pid_t pid;
pid = fork();
if (pid == -1)
ERR_EXIT("fork error");
if (pid == 0)
{
close(chi_pipe[0]); // I don't read in channel 1
close(par_pipe[1]); // I don't write in channel 2
dup2(chi_pipe[1], STDOUT_FILENO);
close(STDIN_FILENO);
dup2(par_pipe[0], STDIN_FILENO);
execlp("cat", "cat" , "-n", NULL);
//execlp("tr", "tr" , "/a-z/", "/A-Z/", NULL);
sleep(10);
close(chi_pipe[1]);
close(par_pipe[0]);
_exit(0);
}
close(par_pipe[0]);
close(chi_pipe[1]);
while(1) {
char input[1024];
memset(input, 0 , 1024);
fgets(input, 1024 ,stdin);
write(par_pipe[1], input, strlen(input));
char buf[3*1024];
int count = 0;
while (count <= 0)
count=read(chi_pipe[0], buf, 1024*3);
if (count >= 1)
{
printf("buf=%s", buf);
printf("\n");
}
}
close(par_pipe[1]);
close(chi_pipe[0]);
return 0;
}
A couple of points:
You are suffering from the need to perform non-blocking I/O. You are reading a line from a file, then writing it to a pipe. But there is no guarantee tr will conveniently write that line back translated. It might wait for the next line to come in. There is no line discipline in place. What you need to do is read from your file, write to tr (if the pipe is not full) and read from tr (if bytes are ready) at the same time. Or, more accurately, according to availability of data on the fd (to read) or the availability of space in the pipe (to write). Otherwise you will run into deadlock problems. tr isn't writing because it would rather read more first, and it hasn't got EOF. You aren't reading from tr because it hasn't written yet, so you aren't reading any more from the file either. To do this, you want to use select() (or poll()).
The only way execlp will return is if the exec fails; in that case you don't want to exit(0) as it's necessarily an error.

C Named pipe (fifo). Parent process gets stuck

I want to make a simple program, that fork, and the child writes into the named pipe and the parent reads and displays from the named pipe.
The problem is that it enters the parent, does the first printf and then it gets weird, it doesn't do anything else, does not get to the second printf, it just ways for input in the console.
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
void main()
{
char t[100];
mkfifo("myfifo",777);
pid_t pid;
pid = fork();
if (pid==0)
{
//execl("fifo2","fifo2",(char*)0);
char r[100];
printf("scrie2->");
scanf("%s",r);
int fp;
fp = open("myfifo",O_WRONLY);
write(fp,r,99);
close(fp);
printf("exit kid \n");
exit(0);
} else
{
wait(0);
printf("entered parent \n"); // <- this it prints
// whats below this line apparently its not being executed
int fz; printf("1");
fz = open("myfifo",O_RDONLY); printf("2");
printf("fd: %d",fz);
char p[100];
int size;
printf("------");
//struct stat *info;
//stat("myfifo",info); printf("%d",(*info).st_size);
read(fz,p,99);
close(fz);
printf("%s",p);
printf("exit"); exit(0);
}
}
You really should be checking the return value on function calls for errors, especially mkfifo() and open().
Your call to wait() is going to cause problems in its current location. Opening a FIFO for reading normally blocks until some other process opens the same FIFO for writing, and vice versa1. The parent is waiting for the child to terminate and the child is waiting for a reader process, i.e., the parent, to connect to the FIFO.
1 - see note on open() below for using O_NONBLOCK with a FIFO
Moving the wait() call to just before the parent process exits along with changing the mode in the call to mkfifo() to 0666 seems to resolve some of your immediate problems.
It is also good practice to remove the FIFO when you are finished with it.
unlink("myfifo");
From the open() function documentation in IEEE Std 1003.1-2004:
When opening a FIFO with O_RDONLY or O_WRONLY set:
If O_NONBLOCK is set, an open() for reading-only shall return without delay. An open() for writing-only shall return an error if no process currently has the file open for reading.
If O_NONBLOCK is clear, an open() for reading-only shall block the calling thread until a thread opens the file for writing. An open() for writing-only shall block the calling thread until a thread opens the file for reading.
The following example is a combination of the code in your original question and the FIFO page of Beej's Guide to Unix IPC:
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <unistd.h>
#define FIFO_NAME "myfifo"
int main(void)
{
char buf[256];
int num, fd;
pid_t pid;
if (mkfifo(FIFO_NAME, 0666) < 0)
perror("mkfifo");
pid = fork();
if (pid == 0)
{
printf("child - waiting for readers...\n");
if ((fd = open(FIFO_NAME, O_WRONLY)) < 0)
perror("child - open");
printf("child - got a reader -- type some stuff\n");
while (fgets(buf, sizeof(buf), stdin), !feof(stdin))
{
if ((num = write(fd, buf, strlen(buf))) < 0)
perror("child - write");
else
printf("child - wrote %d bytes\n", num);
}
close(fd);
exit(0);
}
else
{
printf("parent - waiting for writers...\n");
if ((fd = open(FIFO_NAME, O_RDONLY)) < 0)
perror("parent - open");
printf("parent - got a writer\n");
do
{
if ((num = read(fd, buf, sizeof(buf))) < 0)
perror("parent - read");
else
{
buf[num] = '\0';
printf("parent - read %d bytes: \"%s\"\n", num, buf);
}
} while (num > 0);
close(fd);
wait(0);
}
unlink(FIFO_NAME);
return 0;
}
This example was tested in Linux. Press Ctrl-D to terminate the program.
First of all, try fprintf to stderr instead of printf (to stdout)
The stderr is unbuffered.
Then you can tell what actually gets printed and what does not.
or at least add fflush before waiting for anything.

How to output to a file with information from a pipe in C?

I'm confused about what I'm doing wrong when I'm attempting to output to a file after I've execed a second program.
Say I have input file that has the following names:
Marty B. Beach 7 8
zachary b. Whitaker 12 23
Ivan sanchez 02 15
Jim Toolonganame 9 03
After my programs finish, it will convert the student's names to their usernames and output it to a file such as this:
mbb0708
zbw1223
is0215
jt0903
Currently as my program stands, it outputs nothing to the file and the terminal seems to be in an infinite loop despite self testing my converter program before and making sure it outputs names correctly to stdout.
I'm not sure what I'm doing wrong here? First time programming with pipes. I know have to make use of the read and write commands extract the data, but with the dup2 command is that necessary for the read command alone?
manager.c
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
int main(int argc, char** argv)
{
pid_t pid;
int nbytes;
/*Buffer to hold data from pipe*/
char buffer[BUFSIZ + 1];
/*Pipe Information*/
int commpipe[2];
if(pipe(commpipe))
{
fprintf(stderr, "Pipe failed.\n");
return EXIT_FAILURE;
}
if((pid = fork()) == -1)
{
fprintf(stderr,"Fork error. Exiting.\n");
exit(1);
}
else if(pid == 0)
{
/*This is the child process. Close our copy of the write end of the file descriptor.*/
close(commpipe[1]);
/* Connect the read end of the pipe to standard input*/
dup2(commpipe[0], STDIN_FILENO);
/*Program will convert the Student's name to their respective names*/
execl("converter","converter",NULL);
/*Exit if failure appears*/
exit(EXIT_FAILURE);
}
else
{
FILE *file;
file = fopen("usernames.txt","a+"); //append a file(add text to a file or create a file it does not exist)
/*Close or copy of the read end of the file descriptor */
//close(commpipe[1]);
nbytes = write(commpipe[1], buffer, BUFSIZ);
//Read from pipe here first?
//Output to usernames.txt the usernames of the user from the pipe.
fprintf(file, "%s", buffer);
/*Wait for the child process to finish*/
waitpid(pid, NULL, 0);
}
return 0;
}
One problem is that after manager has sent all the data to converter, the manager is not closing commpipe[1]. Because of that, converter will never get EOF on stdin so will not exit.
Most likely manager isn't getting any data back from converter due to buffering. Some implementations of stdio use full-buffer buffering (as opposed to line-buffering) when not writing to a terminal. Once you fix the previous error and get the process to close, that will flush stdout. You can also consider adding fflush(stdout) after your puts line.
Have a look at the OpenGroup site, there's an example that looks similar to yours. I suggest you get the sample working first with some hard coded. Once that is working, add the code to read and write the results.
I made some minor changes to get the example working:
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <assert.h>
int main(int argc, char** argv){
int fildes[2];
const int BSIZE = 100;
char buf[BSIZE];
ssize_t nbytes;
int status;
status = pipe(fildes);
if (status == -1 ) {
/* an error occurred */
printf("Error!\n");
exit(-1);
}
printf("Forking!\n");
switch (fork()) {
case -1: /* Handle error */
printf("Broken Handle :(\n");
break;
case 0: /* Child - reads from pipe */
printf("Child!\n");
close(fildes[1]); /* Write end is unused */
nbytes = read(fildes[0], buf, BSIZE); /* Get data from pipe */
/* At this point, a further read would see end of file ... */
assert(nbytes < BSIZE); /* Prevent buffer overflow */
buf[nbytes] = '\0'; /* buf won't be NUL terminated */
printf("Child received %s", buf);
close(fildes[0]); /* Finished with pipe */
fflush(stdout);
exit(EXIT_SUCCESS);
default: /* Parent - writes to pipe */
printf("Parent!\n");
close(fildes[0]); /* Read end is unused */
write(fildes[1], "Hello world\n", 12); /* Write data on pipe */
close(fildes[1]); /* Child will see EOF */
/* Note that the Parent should wait for a response from the
child here, because the child process will be terminated once
the parent exits */
exit(EXIT_SUCCESS);
}
return 0;
}
As I understand, your converter program reads lines from stdin and writes them to stdout. As a pipe is a uni-directional entity, you will need TWO of them to communicate with the manager - one to send data to the converter and one to receive output from it.
Maybe you should consider enhancing the converter to take (as optional arguments) the name of an input and output file.

Resources