Open TTY to use with execlp and dup - c++

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.

Related

Linux - Get window title from pid

I am making an app blocker with c++ and I need it to block urls. To block urls it will get the window name of the current tab that it is focused on. Here is the code I've got so far:
#include <iostream>
#include <glob.h>
#include <cstdio>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
using namespace std;
pid_t find_pid(const char *process_name)
{
pid_t pid = -1;
glob_t pglob;
char *procname, *readbuf;
int buflen = strlen(process_name) + 2;
unsigned i;
/* Get a list of all comm files. man 5 proc */
if (glob("/proc/*/comm", 0, NULL, &pglob) != 0)
return pid;
/* The comm files include trailing newlines, so... */
procname = static_cast<char*>(malloc(buflen));
strcpy(procname, process_name);
procname[buflen - 2] = '\n';
procname[buflen - 1] = 0;
/* readbuff will hold the contents of the comm files. */
readbuf = static_cast<char*>(malloc(buflen));
for (i = 0; i < pglob.gl_pathc; ++i) {
FILE *comm;
char *ret;
/* Read the contents of the file. */
if ((comm = fopen(pglob.gl_pathv[i], "r")) == NULL)
continue;
ret = fgets(readbuf, buflen, comm);
fclose(comm);
if (ret == NULL)
continue;
/*
If comm matches our process name, extract the process ID from the
path, convert it to a pid_t, and return it.
*/
if (strcmp(readbuf, procname) == 0) {
pid = (pid_t)atoi(pglob.gl_pathv[i] + strlen("/proc/"));
break;
}
}
/* Clean up. */
free(procname);
free(readbuf);
globfree(&pglob);
return pid;
}
int main(int argc, char const *argv[])
{
cout << find_pid("brave") << endl;
return 0;
}
I copied and modified code from this post - linux - get pid of process. Please help me.

Linux - Shell does not redirect outputs

I created a simple shell in Linux using fork() and execvp(). It works fine with cat, ls etc. but when I try to redirect its output like ./hello.o > output.txt it doesn't work.
I am guessing I didn't provide the write path to look for the definitions. My shell is currently searching on /bin/ path where most of the commands are stored.
Here is my code:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define ARG_SIZE 100 // MAX LENGTH FOR ARGUMENTS
#define PATH "/bin/" // PATH FOR ARGUMENTS
int main() {
char inputLine[BUFSIZ];
char *argv[ARG_SIZE];
// for path + argv
char programPath[200];
while (1) {
printf("myshell> ");
// check if ctrl + D is pressed
if (fgets(inputLine, BUFSIZ, stdin) == NULL)
break;
inputLine[strlen(inputLine) - 1] = '\0';
// check if exit is typed
if (strcmp(inputLine, "exit") == 0)
break;
int i = 0;
argv[0] = strtok(inputLine, " \n");
for (i = 0; argv[i] && i < ARG_SIZE-1; ++i)
argv[++i] = strtok(NULL, " \n");
// create a fork call
pid_t pid = fork();
if (pid == -1) {
perror("fork");
exit(1);
}
// parent
if (pid != 0) {
wait();
// child
} else {
strcat(programPath, argv[0]);
// will not return unless it fails
execvp(programPath, argv);
perror("execvp");
exit(1);
}
}
return 0;
}

Programming language to choose to execute bash commands

For my school project I must make a C++ program. The program has to use already installed applications on the Linux OS. To get familiar with managing other processes from within a C++ application I want to make a program that sets a wlan interface to monitor mode. The code that I have written so far is pretty long and doesn't seems efficient. Are there any ways to make my code more compact and efficient? At the end of the program I want to execute iwconfig to check if the wlan really is in monitor mode. What is the best way to do that?
#include <unistd.h>
#include <iostream>
#include <string>
#include <sys/types.h>
#include <sys/wait.h>
using namespace std;
int main(int argc, char *argv[])
{
string wlan = "wlan1";
pid_t ifconfigDown;
ifconfigDown = fork();
if(ifconfigDown == 0)//ifconfig
{
execl("/sbin/ifconfig", "ifconfig", wlan.c_str(), "down",(char*)0 );
}
else //parent
{
usleep(500000);
pid_t iwconfigMode;
iwconfigMode = fork();
if(iwconfigMode == 0)//ifconfig
{
execl("/sbin/iwconfig","iwconfig",wlan.c_str(),"mode","monitor",(char*)0 );
}
else//parent
{
usleep(500000);
pid_t ifconfigUp;
ifconfigUp = fork();
if(ifconfigUp == 0)//ifconfig
{
execl("/sbin/ifconfig", "ifconfig", wlan.c_str(), "up", (char*)0 );
}
else//parent
{
usleep(500000);
pid_t iwconfig;
iwconfig = fork();
if(iwconfig == 0)//iwconfig
{
execl("/sbin/iwconfig", "iwconfig", (char*)0 );
//check if wlan1 is in monitor mode
}
}
}
waitpid(-1, NULL, 0);
return 0;
}
}
string rPopenEnd (string cmd)
{
FILE *fp = popen(cmd.c_str(), "r");
if (fp == NULL)
{
return "ERROR";
}
else
{
uint16_t line_size = 20;
char line[line_size];
string result;
while (fgets(line, line_size, fp))
result += line;
wait(NULL);
return result;
}
}
int main(int argc, char *argv[])
{
rPopenEnd("iwconfig");
}

Read stdout file

I have create following test case for simulating the issue.I have compiled the source code and able to simulate the issue.
1)When the system command,we got some console out ( i.e your job submitted) which is redirect to file using dup2 and create file .stdout.
When I try to read this file as I need job submission information and I did not get data which was on console out. I was able to get data which I wrote it.( confirm file operation).
Can we not read console output from the file which is create by the child process.
*change rundir and cmd
#include <string>
#include <vector>
#include <utility>
#include <sstream>
#include <fstream>
#include <iostream>
#include <iterator>
#include <algorithm>
#include <functional>
#include <map>
#include <sys/stat.h>
#include <fcntl.h>
using namespace std;
int child();
int main()
{
string rundir = "/temp";
pid_t id =child();
string _xafile;
string _afile;
string _afilecmd;
string line;
stringstream ss(stringstream::in | stringstream::out);
ss<<int(id);
_xafile = rundir;
_xafile = _xafile + "/" + ss.str()+".stdout";
cout<<" _xafile is "<<_xafile<<endl;
string cmd ="cat "+ _xafile + ">" + rundir + "/" + "process.txt";
_afile = rundir;
_afile = _afile + "/" + "process.txt";
_afilecmd = "rm -rf "+ _afile;
system(cmd.c_str());
ifstream xafile(_afile.c_str());
while(xafile)
{
string word;
xafile >> word;
cout<<word<<" ";
/* try to read console output but did not read data */
}
system(_afilecmd.c_str());
return 0;
}
int child()
{
string rundir = "/tmp";
string cmd = " tool <input file> -o <outputfile>";
const char* std_out_file;
pid_t pid = fork();
if (pid < 0) {
return -1;
}
if (pid == 0) {
pid_t mypid = getpid();
stringstream sm;
sm << rundir << "/";
if (strlen(std_out_file) == 0)
sm << mypid << ".stdout";
else
sm << std_out_file;
int fd = open(sm.str().c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0644);
dup2(fd, 1);
dup2(fd, 2);
int fd2 = open("/dev/zero", O_RDONLY);
dup2(fd2, 0);
cout<<cmd <<endl<<endl;
// execl("/bin/sh", "/bin/sh", "-c", cmd, NULL);
system(cmd.c_str());
/*When system call it generate console output like : your job submitted */
/* redirect to file */
exit(-1);
}
if (pid > 0)
return pid;
cout<<" child is done"<<endl;
return 0;
}
It's not entirely clear what your thinking is here - your code appears to fork a child which does a bunch of fancy io stuff to try and redirect your applications stdout to a file, and then runs a command with system(). The command in your code has it's own redirects, specifically via "-o" so it probably isn't writing to stdout.
Back in the parent process, you try to open for reading the same file your child process will open for writing. You've got no synchronization, so they could happen in any order. You appear to be trying to use "cat" to read the file and trying to read the stdout of the cat?
What I think you are actually trying to do is the C/C++ equivalent of Perl's
$foo = `ls -l /tmp`;
or something similar - to execute a command and capture the output.
A better way to do this would be to use pipes.
#include <iostream>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
enum { WritePipe = 1, ReadPipe = 0 };
int main(int argc, const char** argv)
{
int pipes[2]; // each process is going to have its own file descriptor.
if(pipe(pipes) != 0) {
perror("pipe failed");
return -1;
}
pid_t pid = fork();
if(pid == 0) {
// child
close(pipes[ReadPipe]); // close the parent pipe in our context.
// redirect stdout and stderr to the pipe.
dup2(pipes[WritePipe], STDOUT_FILENO);
dup2(pipes[WritePipe], STDERR_FILENO);
// close(STDERR_FILENO); // <- or just do this to close stderr.
int result = system("ls -ltr /etc");
close(pipes[WritePipe]);
return result;
}
if(pid < 0) {
perror("fork failed");
return -1;
}
// Parent process has launched the child.
close(pipes[WritePipe]);
char buffer[4097];
int bytesRead;
while((bytesRead = read(pipes[ReadPipe], buffer, sizeof(buffer) - 1)) > 0) {
buffer[bytesRead] = 0;
std::cout << buffer;
}
std::cout << std::endl;
close(pipes[ReadPipe]);
return 0;
}

program that reacts to inotify and prints the events

I am working in Ubuntu. I want to monitor a folder and print every event that pops up in the subfolders (print files).
I have the following code but it doesn't work. When executed, there is no println of the events.
In the second code I only see the events from the folder. The events from each subfolder do not pop up.
#include <string>
#include <iostream>
#include <stdio.h>
using namespace std;
std::string exec(char* cmd) {
FILE* pipe = popen(cmd, "r");
if (!pipe) return "ERROR";
char buffer[256];
std::string result = "";
while(!feof(pipe)) {
if(fgets(buffer, 256, pipe) != NULL)
result += buffer;
}
pclose(pipe);
cout<<"result is: "<<result<<endl;
return result;
}
int main()
{
//while(1)
//{
string s=exec((char*)"inotifywait -rme create /home/folder/");
cout << s << endl;
//}
return 0;
}
This code only prints the events from the folder I'm monitoring. It doesn't print the events from each subfolder. I don't know how to improve it for my needs.
#include <sys/inotify.h>
#include <sys/ioctl.h>
#include <iostream>
void processNewFiles(int fd, int wd);
int main(int argc, char** argv)
{
const char* dirPath = "/home/folder/" ;//argv[1];
int fd = inotify_init();
int wd = inotify_add_watch(fd, dirPath, IN_CREATE);
if (wd)
{
processNewFiles(fd, wd);
inotify_rm_watch(fd, wd);
}
}
void processNewFiles(int fd, int wd)
{
bool done = false;
do
{
int qLen = 0;
ioctl(fd, FIONREAD, &qLen);
char* buf = new char[qLen];
int num = read(fd, buf, qLen);
if (num == qLen)
{
inotify_event* iev = reinterpret_cast<inotify_event*>(buf);
if (iev->wd == wd && iev->mask & IN_CREATE)
{
std::cout << "New file created: " << iev->name << std::endl;
}
}
delete [] buf;
} while (!done);
}
Your second solution does not work because inotify_add_watch is not working recursivly. You would have to add watches for subdirectories manually. As this might be annoying, it is also possible to use the utility inotifywait as you do in your first example.
Your first example is not working because you're reading from the pipe forever. If you kill the inotifywait process (e.g. if you're the only person on the machine and this is the only inotifywait process just using "killall inotifywait") you will get your output because you'll break out of the loop reading from the pipe. If you output something inside the loop, it will work, too.