#include <cstdio>
#include <QtCore/QProcess>
int main (int argc, char** argv) {
// if we remove 3 following lines, the problem described below doesn't exists!!
QProcess process;
process.start ("asdqwe"); // doesn't matter what we try to execute here.
process.waitForStarted (1000);
while (true) {
char buf[100];
if (scanf ("%s", buf) == EOF) { // it looks like stdin is closed!
printf("FAIL\n");
return 1;
}
printf ("%s\n", buf);
}
return 0;
}
This code is just a snippet to show the problem. In the full application I need read/write communication with process.
I compile it with:
g++ -o out ./main.cpp -I /usr/include/qt4/ -lQtCore
And execute it from bash command line in terminal.
Why this program sometimes prints FAIL and sometimes will stay in loop?
Edit:
This is not question about scan/printf.
The same problem is if I use iostreams + string. This question is about interaction of QProcess with file descriptors of parent process.
Your scanf was interrupted by SIGCHLD signal that was caught when child process terminated. In this case EOF is also returned.
QProcess stuff does set up signal handler for SIGCHLD (check sources): (4.5.3 here)
Q_GLOBAL_STATIC(QProcessManager, processManager)
QProcessManager::QProcessManager()
{
#if defined (QPROCESS_DEBUG)
qDebug() << "QProcessManager::QProcessManager()";
#endif
// initialize the dead child pipe and make it non-blocking.
// (pipe and fcntl skipped - P. Shved.)
// set up the SIGCHLD handler, which writes a single byte to the dead
// child pipe every time a child dies.
struct sigaction oldAction;
struct sigaction action;
memset(&action, 0, sizeof(action));
action.sa_handler = qt_sa_sigchld_handler;
action.sa_flags = SA_NOCLDSTOP;
::sigaction(SIGCHLD, &action, &oldAction);
if (oldAction.sa_handler != qt_sa_sigchld_handler)
qt_sa_old_sigchld_handler = oldAction.sa_handler;
}
#include <cstdio>
#include <QtCore/QProcess>
int main (int argc, char** argv) {
// if we remove 3 following lines, the problem described below doesn't exists!!
QProcess process;
process.start ("asdqwe"); // doesn't matter what we try to execute here.
process.waitForStarted (1000);
while (true) {
char buf[100];
if (scanf ("%s", buf) == EOF) { // it looks like stdin is closed!
if (errno == EINTR) {
errno = 0;
continue;
}
printf("FAIL\n");
return 1;
}
printf ("%s\n", buf);
}
return 0;
}
I really use streams, I had to use
cin.clear();
errno = 0;
Related
I'm fairly new to C++ programming and have only been lurking here on SO but I've run into an issue when trying to write a program that will be crosscompiled and executed on an embedded system and hope someone more knowledable might be able to help me.
The goal of my programm A is to allow another program B on said embedded system to execute different tasks and get system information as well as my program A being a watchdog for the program B. This means my program A has to run as a daemon as it's supposed to constantly monitor and interact with program B. All these tasks and the gathering of information is supposed to run in their own thread each to prevent program A from blocking or hanging if a task takes some time or something similar. I have been using async() and futures to acomplish that.
One of the tasks is to allow program B to execute shell commands and get the output of those commands back. So far this works in my program. I've been using the functionality of popen and pclose to create a pipe and get the result of the executed shell command.
My next goal was to try tell program B if the shell command executed by program A failed or succeeded. From the information I could find online pclose() returns the exit code of the underlaying pipe or returns -1 if something within pclose() failed.
To demo all this I wrote a small testing program an I was able to successfully get the return code from pclose(). But when I integrated my small demo program into the main program I ran into some issues as pclose() always returns -1 regardless of if the command succeeds or fails.
I read online that popen() and pclose() are not really considered thread safe and so I looked around to implement my own version of this functionality, simplified down to my needs using pipe, fork and the exec* family of functions. (In my main.cpp below you can find both the version with the regular popen() and pclose() "runCommandDefaultPopen()" as well as my simplified own implementation "runCommandCustomPopen()")
But my own implementation didn't fix the issues and it seemed like waitpid didn't even interact with the return code variable provided so I tried around some other stuff and quickly narrowed it down to the issue being the daemonizing of the program. I attached the daemonize.h and daemonize.cpp files below. Without daemonizing the process my program works as expected so I assume something breaks when detaching the process. Possibly some signals are not handled correctly or I'm missing a vital step when daemonizing my process.
Sadly I don't know what I'm doing wrong, if I'm missing something or if this is just an edge case that's not supported and I should be using some other means of implementing this functionality.
Daemonized code
My main.cpp:
#include <future>
#include <iostream>
#include <ostream>
#include <string>
#include <sys/stat.h>
#include <sys/syslog.h>
#include <sys/wait.h>
#include <thread>
#include <unistd.h>
#include "daemonize.h"
const int READ = 0;
const int WRITE = 1;
int runCommandCustomPopen(std::string command) {
pid_t child_pid;
int rc = -999;
int fd[2];
pipe(fd);
if ((child_pid = fork()) == -1) {
perror("fork");
exit(1);
}
/* here the child begins */
if (child_pid == 0) {
/* redirect stdout to pipe */
close(fd[READ]);
dup2(fd[WRITE], 1);
execl("/bin/sh", "sh", "-c", command.c_str(), NULL);
} else {
close(fd[WRITE]);
}
close(fd[WRITE]);
while (waitpid(child_pid, &rc, 0) == 0) {
if (errno != EINTR) {
rc = -1;
break;
}
}
/* Read in the pipe and save the content to a buffer */
std::array<char, 16> buffer;
std::string commandResult;
FILE *fdd = fdopen(fd[READ], "r");
while (::fgets(buffer.data(), buffer.size(), fdd) != nullptr) {
commandResult += buffer.data();
}
syslog(LOG_DEBUG, "Command result is: %s", commandResult.c_str());
return rc;
}
int runCommandDefaultPopen(std::string command) {
int rc = -999; // the return code variable
std::array<char, 16> buffer;
std::string commandResult;
// A wrapper function to be able to get the return code while still using the
// automatic close function wizzardy of unique_ptr
auto pclose_wrapper = [&rc](FILE *cmd) { rc = ::pclose(cmd); };
{
const std::unique_ptr<FILE, decltype(pclose_wrapper)> pipe(
::popen(command.c_str(), "r"), pclose_wrapper);
if (!pipe) {
throw std::runtime_error("popen() failed and could not run the command");
}
/* Read in the pipe and save the content to a buffer */
while (::fgets(buffer.data(), buffer.size(), pipe.get()) != nullptr) {
commandResult += buffer.data();
}
}
syslog(LOG_DEBUG, "Command result is: %s", commandResult.c_str());
return rc;
}
int main(int argc, char *argv[]) {
// Daemonize process
bool rd = true;
std::string pidfile = "/tmp/testing.pid";
daemonize(&rd, pidfile);
// some test command
std::string cmd = "ls /var/bla/; sleep 2; echo test";
// Test the functions with futurs and "standalone"/blocking
std::future<int> _fut, _fut2;
syslog(LOG_DEBUG, "runCommandCustomPopen no async: %d", runCommandCustomPopen(cmd));
_fut = std::async(std::launch::async, runCommandCustomPopen, cmd);
_fut2 = std::async(std::launch::async, runCommandDefaultPopen, cmd);
while (rd) {
if (_fut.valid() &&
_fut.wait_for(std::chrono::seconds(0)) == std::future_status::ready) {
syslog(LOG_DEBUG, "runCommandCustomPopen: %d", _fut.get());
}
if (_fut2.valid() &&
_fut2.wait_for(std::chrono::seconds(0)) == std::future_status::ready) {
syslog(LOG_DEBUG, "runCommandDefaultPopen: %d", _fut2.get());
}
syslog(LOG_DEBUG, "waiting...");
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
return 0;
}
My daemonize.h:
#ifndef DAEMONIZE_H_
#define DAEMONIZE_H_
#include <string>
void daemonize(bool *rundaemon, std::string pidFilePath);
#endif /* DAEMONIZE_H_ */
My daemonize.cpp:
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <string>
#include <sys/stat.h>
#include <sys/types.h>
#include <syslog.h>
#include <unistd.h>
/* declare helper methods */
void signalHandler(int sig);
void createPidFile(std::string pidFilePath);
/* global variables */
bool *_p_rundaemon;
void daemonize(bool *rundaemon, std::string pidFilePath) {
pid_t pid;
struct sigaction newSigAction;
_p_rundaemon = rundaemon;
/* Fork off the parent process */
pid = fork();
/* An error occurred */
if (pid < 0)
exit(EXIT_FAILURE);
/* Success: Let the parent terminate */
if (pid > 0)
exit(EXIT_SUCCESS);
/* On success: The child process becomes session leader */
if (setsid() < 0)
exit(EXIT_FAILURE);
/* Catch, ignore and handle signals */
signal(SIGCHLD, SIG_IGN);
/* Set up a signal handler */
newSigAction.sa_handler = signalHandler;
sigemptyset(&newSigAction.sa_mask);
newSigAction.sa_flags = 0;
/* Signals to handle */
sigaction(SIGHUP, &newSigAction, NULL); /* catch hangup signal */
sigaction(SIGTERM, &newSigAction, NULL); /* catch term signal */
sigaction(SIGINT, &newSigAction, NULL); /* catch interrupt signal */
/* Fork off for the second time*/
pid = fork();
/* An error occurred */
if (pid < 0)
exit(EXIT_FAILURE);
/* Success: Let the parent terminate */
if (pid > 0)
exit(EXIT_SUCCESS);
/* Set new file permissions */
umask(0);
/* Change the working directory to the root directory */
/* or another appropriate directory */
chdir("/");
/* Close all open file descriptors */
int x;
for (x = sysconf(_SC_OPEN_MAX); x >= 0; x--) {
close(x);
}
createPidFile(pidFilePath);
}
void signalHandler(int sig) {
switch (sig) {
case SIGINT:
case SIGTERM:
*_p_rundaemon = false;
break;
}
}
void createPidFile(std::string pidFilePath) {
/* Ensure only one copy */
int pidFilehandle = open(pidFilePath.c_str(), O_RDWR | O_CREAT, 0600);
if (pidFilehandle == -1) {
/* Couldn't open lock file */
syslog(LOG_WARNING, "Could not open PID lock file %s, exiting",
pidFilePath.c_str());
exit(EXIT_FAILURE);
}
/* Try to lock file */
if (lockf(pidFilehandle, F_TLOCK, 0) == -1) {
/* Couldn't get lock on lock file */
syslog(LOG_WARNING, "Could not lock PID lock file %s, exiting",
pidFilePath.c_str());
exit(EXIT_FAILURE);
}
/* Get and format PID */
std::string pid = std::to_string(getpid());
/* write pid to lockfile */
write(pidFilehandle, pid.c_str(), pid.length());
syslog(LOG_INFO, "Createded PID lock file %s for process %s",
pidFilePath.c_str(), pid.c_str());
}
Compile
$CXX main.cpp daemonize.cpp -o pcloseTest -Wall -Werror -pthread
With $CXX being my arm-crosscompiler in my cross compile toolchain
Output
Here is the output of /var/log/messages from the embedded system with the program running as daemonized process:
May 9 09:55:38 MY-EMBEDDED-SYSTEM daemon.info MyDemoProg[9988]: Createded PID lock file /tmp/testing.pid for process 9988
May 9 09:55:40 MY-EMBEDDED-SYSTEM daemon.debug MyDemoProg[9988]: Command result is: test
May 9 09:55:40 MY-EMBEDDED-SYSTEM daemon.debug MyDemoProg[9988]: runCommandCustomPopen no async: -999
May 9 09:55:40 MY-EMBEDDED-SYSTEM daemon.debug MyDemoProg[9988]: waiting...
May 9 09:55:40 MY-EMBEDDED-SYSTEM daemon.debug MyDemoProg[9988]: waiting...
May 9 09:55:41 MY-EMBEDDED-SYSTEM daemon.debug MyDemoProg[9988]: waiting...
May 9 09:55:41 MY-EMBEDDED-SYSTEM daemon.debug MyDemoProg[9988]: waiting...
May 9 09:55:42 MY-EMBEDDED-SYSTEM daemon.debug MyDemoProg[9988]: waiting...
May 9 09:55:42 MY-EMBEDDED-SYSTEM daemon.debug MyDemoProg[9988]: Command result is: test
May 9 09:55:42 MY-EMBEDDED-SYSTEM daemon.debug MyDemoProg[9988]: Command result is: test
May 9 09:55:42 MY-EMBEDDED-SYSTEM daemon.debug MyDemoProg[9988]: runCommandCustomPopen: -999
May 9 09:55:42 MY-EMBEDDED-SYSTEM daemon.debug MyDemoProg[9988]: runCommandDefaultPopen: -1
May 9 09:55:42 MY-EMBEDDED-SYSTEM daemon.debug MyDemoProg[9988]: waiting...
May 9 09:55:43 MY-EMBEDDED-SYSTEM daemon.debug MyDemoProg[9988]: waiting...
May 9 09:55:43 MY-EMBEDDED-SYSTEM daemon.debug MyDemoProg[9988]: waiting...
May 9 09:55:44 MY-EMBEDDED-SYSTEM daemon.debug MyDemoProg[9988]: waiting...
As you can see when daemonizing the process the default popen() and pclose() return -1 and the custom one does not change the inital value of the variable rc.
Non-daemonized code
Here is the same code but able to run as a regular programm in the commandline (main2.cpp):
#include <future>
#include <iostream>
#include <ostream>
#include <string>
#include <sys/stat.h>
//#include <sys/syslog.h>
#include <sys/wait.h>
#include <thread>
#include <unistd.h>
//#include "daemonize.h"
const int READ = 0;
const int WRITE = 1;
int runCommandCustomPopen(std::string command) {
pid_t child_pid;
int rc = -999;
int fd[2];
pipe(fd);
if ((child_pid = fork()) == -1) {
perror("fork");
exit(1);
}
/* here the child begins */
if (child_pid == 0) {
/* redirect stdout to pipe */
close(fd[READ]);
dup2(fd[WRITE], 1);
execl("/bin/sh", "sh", "-c", command.c_str(), NULL);
} else {
close(fd[WRITE]);
}
close(fd[WRITE]);
while (waitpid(child_pid, &rc, 0) == 0) {
if (errno != EINTR) {
rc = -1;
break;
}
}
/* Read in the pipe and save the content to a buffer */
std::array<char, 16> buffer;
std::string commandResult;
FILE *fdd = fdopen(fd[READ], "r");
while (::fgets(buffer.data(), buffer.size(), fdd) != nullptr) {
commandResult += buffer.data();
}
// syslog(LOG_DEBUG, "Command result is: %s", commandResult.c_str());
std::cout << "Command result is: " << commandResult << std::endl;
return rc;
}
int runCommandDefaultPopen(std::string command) {
int rc = -999; // the return code variable
std::array<char, 16> buffer;
std::string commandResult;
// A wrapper function to be able to get the return code while still using the
// automatic close function wizzardy of unique_ptr
auto pclose_wrapper = [&rc](FILE *cmd) { rc = ::pclose(cmd); };
{
const std::unique_ptr<FILE, decltype(pclose_wrapper)> pipe(
::popen(command.c_str(), "r"), pclose_wrapper);
if (!pipe) {
throw std::runtime_error("popen() failed and could not run the command");
}
/* Read in the pipe and save the content to a buffer */
while (::fgets(buffer.data(), buffer.size(), pipe.get()) != nullptr) {
commandResult += buffer.data();
}
}
// syslog(LOG_DEBUG, "Command result is: %s", commandResult.c_str());
std::cout << "Command result is: " << commandResult << std::endl;
return rc;
}
int main(int argc, char *argv[]) {
// setlogmask(LOG_UPTO(LOG_DEBUG));
// openlog("MyDemoProg", LOG_PID, LOG_DAEMON);
// bool rd = true;
// std::string pidfile = "/tmp/testing.pid";
// daemonize(&rd, pidfile);
std::string cmd = "ls /var/bla/; sleep 2; echo test";
std::future<int> _fut, _fut2;
// syslog(LOG_DEBUG, "runCommandCustomPopen no async: %d",
// runCommandCustomPopen(cmd));
std::cout << "runCommandCustomPopen no async: " << runCommandCustomPopen(cmd)
<< std::endl;
_fut = std::async(std::launch::async, runCommandCustomPopen, cmd);
_fut2 = std::async(std::launch::async, runCommandDefaultPopen, cmd);
// while (rd) {
while (true) {
if (_fut.valid() &&
_fut.wait_for(std::chrono::seconds(0)) == std::future_status::ready) {
// syslog(LOG_DEBUG, "runCommandCustomPopen: %d", _fut.get());
std::cout << "runCommandCustomPopen: " << _fut.get() << std::endl;
}
if (_fut2.valid() &&
_fut2.wait_for(std::chrono::seconds(0)) == std::future_status::ready) {
// syslog(LOG_DEBUG, "runCommandDefaultPopen: %d", _fut2.get());
std::cout << "runCommandDefaultPopen: " << _fut2.get() << std::endl;
}
// syslog(LOG_DEBUG, "waiting...");
std::cout << "waiting..." << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(500));
}
return 0;
}
Compile
$CXX main2.cpp -o pcloseTest2 -Wall -Werror -pthread
With $CXX being my arm-crosscompiler in my cross compile toolchain
Output
And here the corresponding output from the commandline where all commands - async or not - return the expected 0 to signal they succeeded.
root#MY-EMBEDDED-SYSTEM:~# /opt/tmp/pcloseTest2
ls: cannot access '/var/bla/': No such file or directory
runCommandCustomPopen no async: Command result is: test
0
waiting...
ls: cannot access '/var/bla/': No such file or directory
ls: cannot access '/var/bla/': No such file or directory
waiting...
waiting...
waiting...
waiting...
Command result is: test
Command result is: test
runCommandCustomPopen: 0
runCommandDefaultPopen: 0
waiting...
waiting...
waiting...
waiting...
waiting...
waiting...
waiting...
^C
I hope this gives an insight into what I'm trying to do and maybe someone has an idea on how to resolve this issue and get the correct return value from the forked process so I can handle the execution of the command better.
Thank you so much in advance! If any further information are needed or more details need to be given let me know!
I have a program which needs to launch other programs, possibly substituting their stdio with files and pipes. While it appears to "work" in that the sub-process does get it's I/O from the source pipe, unfortunately, it also causes a hang. The sub-process is seemingly never getting an EOF.
Here is a minimal reproduction of the code, why does this hang after printing "Hello World\n"?
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
int main(int argc, char *argv[]) {
switch (pid_t pid = fork()) {
case 0: {
// in child
// replace the child's stdin with whatever filename is given as argv[1]
freopen(argv[1], "r+b", stdin);
// construct an argv array for to exec, no need for anything except
// argv[0] since we want it to use stdin
char path[] = "/bin/cat";
char *args[] = {path, NULL};
// run it!
execv(args[0], args);
abort(); // we should never get here!
}
case -1:
// error
return -1;
default: {
// in parent, just wait for the sub-process to terminate
int status;
const auto r = waitpid(pid, &status, __WALL);
if (r == -1) {
perror("waitpid");
return -1;
}
break;
}
}
}
# runs printf creating a pipe, which is then passed as the argv of my test program
./test >(printf "Hello\n")
./test <(printf "Hello\n")
Switch to >(...) to <(...) to read from printf rather than write to it.
freopen(argv[1], "rb", stdin);
Don't use r+. You're only reading from the file, so make it r.
How to use the function of "CreatePipe" and "CreateProcessW" in Linux, when I compile the C++ code in Linux, there have some errors as follow: 'CreatePipe' was not declared in this scope. 'CreateProcessW' was not declared in this scope.
Posix/Linux:
int pipe(int pipefd[2]);
pipefd[0] refers to the read end of the pipe.
pipefd[1] refers to the write end of the pipe.
Linux specific:
int pipe2(int pipefd[2], int flags);
When it comes to CreateProcess, the Posix/Linux version is done in a few steps.
Call fork() to create a new process - still running the same program - so two processes will now continue running the same program from the same point where fork() was called. Determining if it's the parent process or child process is done by checking the return value (the process id) from fork().
dup the file descriptors returned by pipe using int dup2(int oldfd, int newfd); to replace stdin and stdout for the new process.
Execute a program in the new process using one of the exec* functions.
// create pipes here
if(pid_t pid = fork(); pid == -1) {
// fork failed
} else if(pid == 0) { // child process goes here (with a new process id)
// dup2(...)
// exec*()
} else { // parent process goes here
// do parenting stuff
}
Example:
#include <unistd.h>
#include <sys/types.h>
#include <cstdio>
#include <cstdlib>
#include <iostream>
#include <stdexcept>
struct Pipe {
Pipe() {
if(pipe(io)) throw std::runtime_error("pipe failure");
}
~Pipe() {
close_rd();
close_wr();
}
void close_rd() { closer(io[0]); }
void close_wr() { closer(io[1]); }
int rd() { return io[0]; }
int wr() { return io[1]; }
private:
void closer(int& fd) {
if(fd != -1) {
close(fd);
fd = -1;
}
}
int io[2];
};
int main() {
Pipe parent_write, parent_read;
if(pid_t pid = fork(); pid == -1) {
// fork failed
return 1;
} else if(pid == 0) { // child process goes here (with a new process id)
// close file descriptors we don't need:
parent_write.close_wr();
parent_read.close_rd();
// duplicate into the place where stdin/stdout was
dup2(parent_write.rd(), fileno(stdin));
dup2(parent_read.wr(), fileno(stdout));
// execute a program
execl("/bin/ls", "/bin/ls", nullptr);
// exec* functions never returns if successful, so if we get here, it failed:
std::exit(1);
} else { // parent process goes here
std::cout << "child process " << pid << " started\n";
}
// close file descriptors we don't need:
parent_write.close_rd();
parent_read.close_wr();
// read data from child process using the file descriptor in parent_read.rd()
char buf[1024];
ssize_t rv;
while((rv = read(parent_read.rd(), buf, 1024))) {
write(fileno(stdout), buf, static_cast<size_t>(rv));
}
// use write(parent_write.wr(), ...) to write to the child.
}
I am trying to read some data from stdin in a separate thread from main thread. Main thread should be able to communicate to this waiting thread by writing to stdin, but when I run the test code (included below) nothing happens except that the message ('do_some_work' in my test code) is printed on the terminal directly instead of being output from the waiting thread.
I have tried a couple of solutions listed on SO but with no success. My code mimics one of the solutions from following SO question, and it works perfectly fine by itself but when coupled with my read_stdin_thread it does not.
Is it possible to write data into own stdin in Linux
#include <unistd.h>
#include <string>
#include <iostream>
#include <sstream>
#include <thread>
bool terminate_read = true;
void readStdin() {
static const int INPUT_BUF_SIZE = 1024;
char buf[INPUT_BUF_SIZE];
while (terminate_read) {
fd_set readfds;
struct timeval tv;
int data;
FD_ZERO(&readfds);
FD_SET(STDIN_FILENO, &readfds);
tv.tv_sec=2;
tv.tv_usec=0;
int ret = select(16, &readfds, 0, 0, &tv);
if (ret == 0) {
continue;
} else if (ret == -1) {
perror("select");
continue;
}
data=FD_ISSET(STDIN_FILENO, &readfds);
if (data>0) {
int bytes = read(STDIN_FILENO,buf,INPUT_BUF_SIZE);
if (bytes == -1) {
perror("input poll: read");
continue;
}
if (bytes) {
std::cout << "Execute: " << buf << std::endl;
if (strncmp(buf, "quit", 4)==0) {
std::cout << "quitting reading from stdin." << std::endl;
break;
}
else {
continue;
}
}
}
}
}
int main() {
std::thread threadReadStdin([] () {
readStdin();
});
usleep(1000000);
std::stringstream msg;
msg << "do_some_work" << std::endl;
auto s = msg.str();
write(STDIN_FILENO, s.c_str(), s.size());
usleep(1000000);
terminate_read = false;
threadReadStdin.join();
return 0;
}
A code snippet illustrating how to write to stdin that in turn is read by threadReadStdin would be extremely helpful.
Thanks much in advance!
Edit:
One thing I forgot to mention here that code within readStdin() is a third party code and any kind of communication that takes place has to be on its terms.
Also, I am pretty easily able to redirect std::cin and std::cout to either fstream or stringstream. Problem is that when I write to redirected cin buffer nothing really appears on the reading thread.
Edit2:
This is a single process application and spawning is not an option.
If you want to use a pipe to communicate between different threads in the same program, you shouldn't try using stdin or stdout. Instead, just use the pipe function to create your own pipe. I'll walk you through doing this step-by-step!
Opening the channel
Let's create a helper function to open the channel using pipe. This function takes two ints by reference - the read end and the write end. It tries opening the pipe, and if it can't, it prints an error.
#include <unistd.h>
#include <cstdio>
#include <thread>
#include <string>
void open_channel(int& read_fd, int& write_fd) {
int vals[2];
int errc = pipe(vals);
if(errc) {
fputs("Bad pipe", stderr);
read_fd = -1;
write_fd = -1;
} else {
read_fd = vals[0];
write_fd = vals[1];
}
}
Writing a message
Next, we define a function to write the message. This function is given as a lambda, so that we can pass it directly to the thread.
auto write_message = [](int write_fd, std::string message) {
ssize_t amnt_written = write(write_fd, message.data(), message.size());
if(amnt_written != message.size()) {
fputs("Bad write", stderr);
}
close(write_fd);
};
Reading a message
We should also make a function to read the message. Reading the message will be done on a different thread. This lambda reads the message 1000 bytes at a type, and prints it to standard out.
auto read_message = [](int read_fd) {
constexpr int buffer_size = 1000;
char buffer[buffer_size + 1];
ssize_t amnt_read;
do {
amnt_read = read(read_fd, &buffer[0], buffer_size);
buffer[amnt_read] = 0;
fwrite(buffer, 1, amnt_read, stdout);
} while(amnt_read > 0);
};
Main method
Finally, we can write the main method. It opens the channel, writes the message on one thread, and reads it on the other thread.
int main() {
int read_fd;
int write_fd;
open_channel(read_fd, write_fd);
std::thread write_thread(
write_message, write_fd, "Hello, world!");
std::thread read_thread(
read_message, read_fd);
write_thread.join();
read_thread.join();
}
It seems like I have stumbled upon the answer with the help of very constructive responses from #Jorge Perez, #Remy Lebeau and #Kamil Cuk. This solution is built upon #Jorge Perez's extremely helpful code. For brevity's sake I am not including the whole code but part comes from the code I posted and a large part comes from #Jorge Perez's code.
What I have done is taken his approach using pipes and replacing STDIN_FILENO by the pipe read fd using dup. Following link was really helpful:
https://en.wikipedia.org/wiki/Dup_(system_call)
I would really appreciate your input on whether this is a hack or a good enough approach/solution given the constraints I have in production environment code.
int main() {
int read_fd;
int write_fd;
open_channel(read_fd, write_fd);
close(STDIN_FILENO);
if(dup(read_fd) == -1)
return -1;
std::thread write_thread(write_message, write_fd, "Whatsup?");
std::thread threadReadStdin([] () {
readStdin();
});
write_thread.join();
threadReadStdin.join();
return 0;
}
I am trying to create a minimal code to use pipe/fork/execlp.
So far so good, I am using execlp with bash -c, so if I do.
echo asd |./a.out cat
> asd
So it is working as expected.
But if I try to use anything that needs a TTY, it does not work.
Like ./a.out vim, I get "Vim: Warning: Input is not from a terminal"
And the vim that was open does not works as expected.
I tried to find on the internet an example on how to open a TTY, the only one that I found was:
http://www.danlj.org/lad/src/minopen.c
My Code, so far is:
#include <iostream>
#include <cstdio>
#include <string.h>
#include <cstdlib>
#include <unistd.h>
#include <sys/wait.h>
typedef struct pCon{
int fout[2];
int fin[2];
int fd[2];
int pid1, pid2;
} connectionManager;
std::string command = "";
/*
* Implementation
*/
void childFork(connectionManager *cm);
int main(int argc, char *argv[]) {
int size;
if(argc < 2) exit(1);
else command = argv[1];
connectionManager *cm = new connectionManager;
pipe(cm->fd);
if((cm->pid1 = fork()) == -1)exit(1);
if (cm->pid1 == 0)
{
const unsigned int RCVBUFSIZE = 2000;
char echoString[RCVBUFSIZE];
while((size = read(fileno(stdin),echoString,RCVBUFSIZE)) > 0)
write(cm->fd[1], echoString, size);
close(cm->fd[1]);
}
else
childFork(cm);
return 0;
}
void childFork(connectionManager *cm){
char *buffer = new char[2000];
int size;
close(cm->fd[1]);
dup2(cm->fd[0], 0);
close(cm->fd[0]);
pipe(cm->fout);
if((cm->pid2 = fork()) == -1)exit(1);
if (cm->pid2 == 0)
{
close(cm->fout[0]);
int returnCode = execlp("bash", "bash", "-c", command.c_str(), NULL);
if(returnCode!=0)
std::cerr << "Error starting the bash program" << std::endl;
}
else
{
close(cm->fout[1]);
while((size = read(cm->fout[0], buffer, 2000 )) > 0 )
write(fileno(stdout), buffer, size);
}
}
I tried to keep the minimal necessary code to make it work.
Is there any way to implement TTY on this code, I know that does not seems to be such trivial task.
Can someone help me with that?
I also tried to open the tty and dup it, but no luck so far.
Try to use pseudo terminal. You can use opentty. For your purpose you can use forkpty which combines pty with fork. I've created a small example for you. About the same as your program, just it works. I've kept it simple, so I don't handle the terminal control characters.
#include <pty.h>
#include <unistd.h>
#include <fcntl.h>
#include <termios.h>
#include <sys/select.h>
int main(int argc, char *argv[])
{
if (argc<1) return 1;
int master;
pid_t pid = forkpty(&master, NULL, NULL, NULL); // opentty + login_tty + fork
if (pid < 0) {
return 1; // fork with pseudo terminal failed
}
else if (pid == 0) { // child
char *args[] = { argv[1], argv[2], NULL }; // prg + 1 argument
execvp(argv[1], args); // run the program given in first param
}
else { // parent
struct termios tios;
tcgetattr(master, &tios);
tios.c_lflag &= ~(ECHO | ECHONL);
tcsetattr(master, TCSAFLUSH, &tios);
while(1) {
fd_set read_fd, write_fd, err_fd;
FD_ZERO(&read_fd);
FD_ZERO(&write_fd);
FD_ZERO(&err_fd);
FD_SET(master, &read_fd);
FD_SET(STDIN_FILENO, &read_fd);
select(master+1, &read_fd, &write_fd, &err_fd, NULL);
if (FD_ISSET(master, &read_fd))
{
char ch;
int c;
if (c=read(master, &ch, 1) != -1) // read from program
write(STDOUT_FILENO, &ch, c); // write to tty
else
break; // exit when end of communication channel with program
}
if (FD_ISSET(STDIN_FILENO, &read_fd))
{
char ch;
int c=read(STDIN_FILENO, &ch, 1); // read from tty
write(master, &ch, c); // write to program
}
}
}
return 0;
}
For compiling use -lutil .
While running a new tty device appears in /dev/pts .
vim accepts it as a terminal.