I'm trying to write a C program that creates a pseudo-terminal running a new bash instance, and records all the input and output that goes through it. The eventual goal would be to asynchronously send this to a server, where somebody else could view your terminal activity in real time.
I've completed the pseudo-term creation step, and I can start a new bash instance and log "most" of the input and output. My issue right now is that the pseudo-term isn't properly recognizing arrow keys. They get printed to the screen as ASCII values (^[[A, ^[[[B, ^[[C, ^[[D), instead of moving the cursor around the command line.
Here's the slave portion of the pty, which will run bash:
if(pid == 0){ //child
struct termios term_settings;
close(ptyfds.master);
rc = tcgetattr(ptyfds.slave, &term_settings);
cfmakeraw(&term_settings);
tcsetattr(ptyfds.slave, TCSANOW, &term_settings);
//replace stdin,out,err with the slave filedesc
close(0);
close(1);
close(2);
dup(ptyfds.slave);
dup(ptyfds.slave);
dup(ptyfds.slave);
//We can close original fd and use 0,1,2
close(ptyfds.slave);
//Make this process the session lead
setsid();
//Slave side of PTY becomes the new controlling terminal
ioctl(0, TIOCSCTTY, 1);
char ** child_argv = (char **) malloc(argc * sizeof(char*));
int i;
for(i=1; i<argc; i++){
child_argv[i-1] = strdup(argv[i]); //could be bash, bc, python
}
child_argv[i-1] = NULL;
rc = execvp(child_argv[0], child_argv);
}
And here's the master side of the pty, sending input to the slave and capturing its output.
if(pid == 0){ //parent
fd_set fd_in;
close(ptyfds.slave);
FILE *logFile = fopen("./log", "w");
while(1){
//Add stdin and master fd to object
FD_ZERO(&fd_in);
FD_SET(0,&fd_in);
FD_SET(ptyfds.master, &fd_in);
//intercept data from stdin or from slave out (which is redirected to master)
rc = select(ptyfds.master+1, &fd_in, NULL,NULL,NULL);
switch(rc){
case -1:
fprintf(stderr, "Error %d on select()\n", errno);
exit(1);
default:
if (FD_ISSET(0, &fd_in)){ //There's data on stdin
rc = read(0, input, sizeof(input));
if(rc > 0){
input[rc] = '\0';
write(ptyfds.master, input, rc);//send to master -> slave
fputs(input, logFile);
}
else if(rc < 0){
fprintf(stderr, "Error %d on stdin\n", errno);
exit(1);
}
}
if(FD_ISSET(ptyfds.master, &fd_in)){ //There's data from slave
rc = read(ptyfds.master, input, sizeof(input)-1);
if(rc > 0){
input[rc] = '\0';
write(1, input, rc);//send to stdout
fputs(input, logFile);
}
else if (rc < 0){
fprintf(stderr, "Error %d on read master pty\n", errno);
exit(1);
}
}
}//switch
}//while
}//end parent
I've tried messing around with the termios flags here, but there are none that specify arrow keys.
What do I need to do?
Much of this code came from here.
I think there was a mistake in the example program.
I was able to fix it by moving:
rc = tcgetattr(ptyfds.slave, &term_settings);
cfmakeraw(&term_settings);
tcsetattr(ptyfds.slave, TCSANOW, &term_settings);
into the master section and replacing ptyfds.slave with STDIN_FILENO
(This sets STDIN to raw mode, rather than the slave)
Related
I know the title doesn't explain the problem precisely and I apologize.
I've been writing a program in C of named pipes and the goal of it is to open 2 windows of the terminal, each running either the "Server" or the "Client" file.
After the terminals connected to each other via one named pipe, the Client can then send a string to the Server, the server would create a thread that prints the string it had received, reverses it, and then send it back to the Client via another named pipe. Finally, the Client should print the message it had received back from the Server. The client should be able to keep sending strings until the file is exited or the string "exit" is sent.
What my issue is and what I think causes it:
Everything works fine when the user enters a single word in the string, but when it sends a sentence with spaces in it, the Client's fscanf, that is meant to read from "toClient" named pipe, slices the sentence and it can only receive one word at a time.
So then the next time the client sends a message, it will read the second word of the previous message because it was sliced out and stayed in the named pipe.
I tried to use a while loop to keep reading from "toClient" until all the individual words have been taken out and it works like I expected it to, but after the first message the connection hangs and the client can't send new messages. I think it hangs because the while loop doesn't reach EOF for some reason, maybe because both the Client and Server still have the named pipe open.
Server:
/*function that reverses a string*/
char* revstr(char *str1)
{
int i, len, temp;
len = strlen(str1);
for (i = 0; i < len/2; i++)
{
temp = str1[i];
str1[i] = str1[len - i - 1];
str1[len - i - 1] = temp;
}
return str1;
}
/*function that is run by a thread to handle a client's message*/
void *threadFunc(void *arg){
char *string = (char*)arg;
printf("Received from client: %s\n", string);//print message received to terminal
/*make a connection to "toClient" named pipe that in order to send back the reversed string*/
FILE *fdw;
if (!(fdw = fopen("toClient", "w"))) {
perror("cannot open fifo file for w") ;
exit(EXIT_FAILURE) ;
}
/*irrelevant*/
if(strcmp(string,"exit")==0)
{
fprintf(fdw, " Done\n") ;
fflush(fdw) ;
printf("Shutting down...\n");
exit(0) ;
}
char *string2 = revstr(string);//string2 is the reversed string
fprintf(fdw, " %s\n", string2) ;//send string2 into the named pipe labeled "toClient"
fflush(fdw) ;
printf("Sent message back to client...\n");
}
int main()
{
char s[STR_LEN];
FILE *fdr;
if (mkfifo("toServer", 0777) == -1 && errno != EEXIST) {
perror("cannot create fifo1 file") ;
exit(EXIT_FAILURE) ;
}
if (mkfifo("toClient", 0777) == -1 && errno != EEXIST) {
perror("cannot create fifo2 file") ;
exit(EXIT_FAILURE) ;
}
printf("Waiting for client...\n");
if (!(fdr = fopen("toServer", "r"))) {
perror("cannot open fifo file for r") ;
exit(EXIT_FAILURE) ;
}
printf("Client found, waiting for message...\n");
/*this block waits for a message from the client, then creates a thread to handle it*/
while ( fscanf(fdr, " %s", s) != EOF){
int retcode;
pthread_t t1;
retcode = pthread_create(&t1,NULL,&threadFunc,(void *)(s));
if(retcode!=0)
printf("Create thread failed with error %d\n", retcode);
pthread_join(t1,NULL);
}
printf("Client disconnected\n") ;
return EXIT_SUCCESS ;
}
Client:
{
char s[STR_LEN];
FILE *fdw;
FILE *fdr;
if (mkfifo("toServer", 0777) == -1 && errno != EEXIST) {
perror("cannot create fifo file") ;
exit(EXIT_FAILURE) ;
}
if (mknod("toClient", 0777,0) == -1 && errno != EEXIST) {
perror("cannot create fifo file") ;
exit(EXIT_FAILURE) ;
}
if (!(fdw = fopen("toServer", "w"))) {
perror("cannot open fifo file for w") ;
exit(EXIT_FAILURE) ;
}
puts("Connected to server, enter a message:\n");
/*the user now enters a message into the terminal*/
while ( fgets(s, STR_LEN, stdin) != NULL) {
printf("Sent: %s",s);//print the message to terminal
fprintf(fdw, " %s\n", s) ;//send the message into the named pipe labeled "toServer"
fflush(fdw) ;
/*connect to the server to receive it's response using the named pipe labeled "toClient"*/
if (!(fdr = fopen("toClient", "r"))) {
perror("cannot open fifo file for r") ;
exit(EXIT_FAILURE) ;
}
/*this is where my problem is - this block is essentially meant to read from the named pipe "toClient"*/
if/*while*/ ( fscanf(fdr, " %s", s) != EOF )
{
printf("Received from server: %s\n", s);//print the response received to the terminal
/*Irrelevant*/
if(strcmp(s,"Done")==0)
exit(0);
}
}
return EXIT_SUCCESS ;
}
Terminals when using if on fscanf:
Terminals when using while on fscanf:
I know this thread is really long and the problem is probably due to bad code design but I have no idea what to do after almost 8 hours of trying and being relatively inexperienced with the subject .(It's part of an OS course I'm taking in uni)
Well, this is how scanf with %s specifier works. If you want to read whole line using scanf then use %[^\n]s, which means - read until newline character. Anyways it would be better to use fgets.
I am trying to create a process where several other processes talk to this main one, and I am using pipes to accomplish this. I can get two pipes to work with my current method but as soon as I introduce a 3rd pipe, the program starts to malfunction which I think is due to the reading from individual pipes being blocking functions. Altogether, I am going to need to have 5 receiving pipes and 2 of those receiving pipes will need a sending pipe with them to echo back info. Altogether, this needs a total of 7 pipes. I am very new to Linux and programming in general; I am sorry if this doesn't make sense.
mkfifo(ECHO_COMMAND_PIPE, 0666);
mkfifo(READ_COMMAND_PIPE, 0666);
mkfifo(READ_MATH_PIPE, 0666);
if ((echo_command_pipe = open(ECHO_COMMAND_PIPE, O_WRONLY))==-1){
fprintf(stderr, "echo_command_pipe creation failed");
}
else if ((read_command_pipe = open(READ_COMMAND_PIPE, O_RDONLY))==-1){
fprintf(stderr, "read_command_pipe creation failed");
}
else if ((math_pipe = open(READ_MATH_PIPE, O_RDONLY))==-1){
fprintf(stderr, "math_pipe creation failed");
return 1;
}
int error_cnt;
while(1){
fflush(stdout);
fflush(stdin);
int command_read = read(read_command_pipe, command_buffer, sizeof(command_buffer)-1);
int math_read = read(math_pipe, math_buffer, sizeof(math_buffer)-1);
command_buffer[command_read] = '\0';
math_buffer[math_read] = '\0';
fprintf(stdout, "%s", math_buffer);
if (strlen(command_buffer) < 1){
write(echo_command_pipe, err_buffer, strlen(err_buffer));
error_cnt = error_cnt + 1;
}
else {
write(echo_command_pipe, command_buffer,strlen(command_buffer));
error_cnt = 0;
}
if (error_cnt > 7) break;
}
close(echo_command_pipe);
close(read_command_pipe);
close(math_pipe);
unlink(ECHO_COMMAND_PIPE);
unlink(READ_COMMAND_PIPE);
unlink(READ_MATH_PIPE);
return 0;
}
I've created a PTY (Pseudo Terminal) which Accepts Input and prints the output in C / C++ on Unix:
Nearly Everything works properly, nearly every command. But if I spawn a new shell, every command I type gets printed to the screen a second time before the output. Is there anyway how to disable this echoing ?
That would be the Error:
bash-3.2$ python3
Python 3.7.3 (default, Feb 30 2100, 24:00:00)
Type"help", "copyright", "credits" or "license" for more information.
>>> import stackoverflow //for example
import stackoverflow
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ModuleNotFoundError: No module named 'stackoverflow'
>>>
I know that this type of question has been asked several times, I just didn't manage to implement this function ;(
Here's the code so far:
int InteractiveShell() {
int fdm, fds;
int rc;
char buffer[TOK_BUFSIZE];
fdm = posix_openpt(O_RDWR);
rc = grantpt(fdm);
rc = unlockpt(fdm);
fds = open(ptsname(fdm), O_RDWR); // Open the slave side ot the PTY
if (fork()) { // Create the child process
fd_set fd_in;
// Parent Process
close(fds); // Close the slave side of the PTY
printf("[+] Setup Succeeded\n");
while (1) {
FD_ZERO(&fd_in); // Wait for data from standard input and master side of PTY
FD_SET(0, &fd_in);
FD_SET(fdm, &fd_in);
rc = select(fdm + 1, &fd_in, NULL, NULL, NULL);
switch(rc) {
case -1: fprintf(stderr, "[-] Error %d on select()\n[-] Exiting\n", errno);
exit(1);
default:{
if (FD_ISSET(0, &fd_in)) { // If data on standard input
memset(&buffer, 0, sizeof(buffer));
rc = (int) read(0, buffer, sizeof(buffer));
if (rc > 0){
write(fdm, buffer, rc); // Send data on the master side of PTY
} else {
if (rc < 0) {
fprintf(stderr, "Error %d on read standard input\n", errno);
exit(1);
}
}
}
if (FD_ISSET(fdm, &fd_in)) { // If data on master side of PTY
memset(&buffer, 0, sizeof(buffer));
rc = (int) read(fdm, buffer, sizeof(buffer));
if (rc > 0) {
write(1, buffer, rc);
} else {
if (rc < 0) {
fprintf(stderr, "Error %d on read master PTY\n", errno);
exit(1);
}
}
}
}
} // End switch
} // End while
} else { // CHILD
struct termios slave_orig_term_settings; // Saved terminal settings
struct termios new_term_settings; // Current terminal settings
close(fdm); // Close the master side of the PTY
rc = tcgetattr(fds, &slave_orig_term_settings);
// Set RAW mode on slave side of PTY
new_term_settings = slave_orig_term_settings;
cfmakeraw (&new_term_settings);
tcsetattr (fds, TCSANOW, &new_term_settings);
close(0); // Close standard input (current terminal)
close(1); // Close standard output (current terminal)
close(2); // Close standard error (current terminal)
dup(fds); // PTY becomes standard input (0)
dup(fds); // PTY becomes standard output (1)
dup(fds); // PTY becomes standard error (2)
// Now the original file descriptor is useless
close(fds);
setsid(); // Make the current process a new session leader
ioctl(0, TIOCSCTTY, 1);
// Execution of the program
{rc = execlp("/bin/bash", "-i", (char *) NULL);}
return 1;
}
//return 0;
Any Ideas, suggestions, websites, ... ?
Thanks for your help,
Regards, from Brooks ♟
This is my code I'm trying to use to pass data to the other program.:
static int callWithFile(char* buff) {
int myPipes[2];
if( pipe( myPipes ) < 0 ){
perror("Can't pipe through \n");
exit(13);
}
int pid = fork();
switch(pid){
case 0:
{
if(verbose_flag) printf("pid is %d; pipe fds are.... %d & %d\n", getpid(), myPipes[PIPE_READ], myPipes[PIPE_WRITE]);
//close (myPipes[PIPE_READ]);
write (myPipes[PIPE_WRITE], buff, strlen(buff) + 1);
close (myPipes[PIPE_WRITE]);
char* pipeArg;
if(verbose_flag){
asprintf (&pipeArg, "/proc/%d/fd/%d", getpid(), myPipes[PIPE_READ]);
printf("\n%s\n", pipeArg);
}
asprintf (&pipeArg, "/dev/fd/%d", myPipes[PIPE_READ]);
char* progArgv[] = {
"prog",
"--new_settings",
pipeArg,
//"/dev/fd/0",
NULL
};
// This works just fine
// FILE* fp = fopen(pipeArg, "r");
// if (fp == NULL) {
// perror("Can't open fd pipe file \n");
// exit(14);
// }
// fread(buff, sizeof(char), strlen(buff) + 1, fp);
// printf("buff: %s", buff);
execvp(prog_path, progArgv);
perror("execvp screwed up");
exit(15);
}
case -1:
perror("fork screwed up ");
exit(16);
}
close (myPipes[PIPE_READ]);
close (myPipes[PIPE_WRITE]);
wait(NULL);
puts("done");
}
In all aspects, the code appears to be correct and providing the file descriptor for the other program to read from.
However, for some reason, the other program tells it can't open and read the file.
This is the program that reads the data: https://github.com/tuxedocomputers/tuxedo-control-center/blob/master/src/common/classes/ConfigHandler.ts#L87
It complains: Error on read option --new_settings with path: /dev/fd/4
I already confirmed that it is correct JSON, so that shouldn't be the problem.
As for debugging it, I can't make it run on my machine for some reason.
Cannot launch program because corresponding JavaScript cannot be found..
My objective is to have the equivalent of this in bash:
program <(echo $buff)
Where $buff is the contents of the buff function argument.
Everything in your code is correct except this:
write (myPipes[PIPE_WRITE], buff, strlen(buff) + 1);
See that + 1? That's the failure. You are sending a null byte (AKA character 0 or '\0') to the program when its JSON parser doesn't expect it.
Try this instead (without the + 1):
write (myPipes[PIPE_WRITE], buff, strlen(buff));
As a school project, I'm implementing a IRC server and I've been stuck on this one problem for the day.
My server use select to choose which client is sending data, it then read one command from this user (commands are \r\n separated), parse it and execute it before passing to the next user.
A user can send multiple command at once like so :
"command1\r\ncommand2\r\n"
If this happen, i want the first command to be executed and the second to stay in the stream so that it can be read at the next select() call. (this way, each user only execute one comamnd per "turn").
To do this, I have a FILE *stream per client that stay open as long as it is connected.
This work perfectly (if I send the double comamnd specified above, the two commands are executed one after the other).
The problem is after that after that, select() continue to tell me that there is something to read in the socket (FD_ISSET of the fd return 1), so my receive function is called for that fd and the getline() in it fail (without setting errno) and it loop like this forever.
I don't understand why the select still consider that there is something to read in the socket and why the getline is failing instead of blocking.
Any idea ?
edit: I'm not allowed to use non blocking socket or fork() for this project
The "main" loop:
while (g_run_server)
{
fd_max = g_socket_fd;
FD_ZERO(fds);
FD_SET(g_socket_fd, fds);
tmp = users;
while (tmp)
{
FD_SET(tmp->fd, fds);
fd_max = (tmp->fd > fd_max ? tmp->fd : fd_max);
tmp = tmp->next;
}
if (select(fd_max + 1, &fds, NULL, NULL, NULL) < 0)
break;
if (FD_ISSET(g_socket_fd, &fds))
accept_new_user(&hdl, &users);
handle_clients(&hdl, &fds);
}
The functions to handle and read client input :
static bool recv_and_execute(t_handle *hdl)
{
char *raw;
size_t len;
len = 0;
raw = NULL;
if (!hdl->sender->stream &&
(hdl->sender->stream = fdopen(dup(hdl->sender->fd), "r")) == NULL)
return (false);
if (getline(&raw, &len, hdl->sender->stream) != -1)
{
printf("Received \"%s\"\n", raw);
parse_cmd(hdl, raw);
exec_cmd(hdl);
}
else
printf("Getline failed %s\n", strerror(errno));
free(raw);
return (true);
}
int handle_clients(t_handle *hdl, fd_set *fds)
{
t_user *tmp;
tmp = *hdl->users;
while (tmp)
{
if (FD_ISSET(tmp->fd, fds))
{
printf("fd %d is ready to be read\n", tmp->fd);
hdl->sender = tmp;
recv_and_execute(hdl);
FD_CLR(tmp->fd, fds);
tmp = tmp->next;
if (hdl->sender->status == DEAD)
del_user(hdl->users, hdl->sender);
}
else
tmp = tmp->next;
}
return (0);
}
And this is the output when I connect one client and send "USER foo\r\nUSER no bo dy :wa\r\n" :
fd 4 is ready to be read
Received "NICK foo
"
[DEBUG] Executing "NICK" with params "foo" "(null)" "(null)" "(null)"
[INFO] Nickname successfully changed.
fd 4 is ready to be read
Received "USER no bo dy :wa
"
[DEBUG] Executing "USER" with params "no" "bo" "dy" ":wa"
[INFO] User registered.
fd 4 is ready to be read
Getline failed Success
fd 4 is ready to be read
Getline failed Success
fd 4 is ready to be read
Getline failed Success
fd 4 is ready to be read
Getline failed Success
fd 4 is ready to be read
Getline failed Success
continue like this....
Edit : I edited my receive function based on the comment of alk:
static bool recv_and_execute(t_handle *hdl)
{
char *raw;
size_t len;
ssize_t nread;
len = 0;
raw = NULL;
if (!hdl->sender->stream &&
(hdl->sender->stream = fdopen(dup(hdl->sender->fd), "r")) == NULL)
return (false);
errno = 0;
if ((nread = getline(&raw, &len, hdl->sender->stream)) > 0)
{
printf("Received \"%s\"\n", raw);
parse_cmd(hdl, raw);
exec_cmd(hdl);
}
else if (errno != 0)
printf("getline failed %s\n", strerror(errno));
else {
printf("EOF reached\n");
fclose(hdl->sender->stream);
hdl->sender->stream = NULL;
}
printf("nread = %zd\n", nread);
free(raw);
return (true);
}
So this time, when EOF is reach (getline return -1), I close the stream and set it to NULL to be reopened the next time select find data on the socket fd. But even when I close the stream, select still detect that there is data available and the inifinite loop continue :/
fd 4 is ready to be read
Received "NICK foo^M
"
nread = 10
fd 4 is ready to be read
Received "USER no bo dy :wa^M
"
nread = 19
fd 4 is ready to be read
EOF reached
nread = -1
fd 4 is ready to be read
EOF reached
nread = -1
fd 4 is ready to be read
EOF reached
nread = -1
and so on...
I'm pretty sure you're using the select wrong. I show you a simple code example on how to use it (I don't handle many errors) and you can edit it as you need.
/*
* If you read at man select bugs you can see
* that select could return that someone is
* ready but it isn't true
*/
int fd_c;
fd_set rdset;
fd_set set; /*That's the mask you'll use when new requests arrive*/
FD_ZERO(&set); /*Clears the mask*/
FD_SET(g_socket_fd,&set); /*Set the listening socket as ready*/
while(g_run_server){
/*
* YOU MUST INITIALIZATE IT EVERY LOOP
* read # man select
*/
rdset = set;
if(select((fd_num_max+1),(&rdset),NULL,NULL,NULL) < 0){
perror("Failed on select\n");
exit(EXIT_FAILURE);
}
/*You go through the ready clients in the rdset*/
for(fd=0;fd<=fd_num_max;fd++) {
if(FD_ISSET(fd,&rdset)) { /*If the bit is set*/
if(fd == fd_skt) { /*If it's a new client*/
/*You can handle the new client here or delegete it to someone*/
fd_c=accept(fd_skt,NULL,0); /*File descriptor of new client*/
FD_SET(fd_c,&set);
if(fd_c > fd_num_max) fd_num_max = fd_c;
}else { /*If it's a request from an existing client*/
/*You can handle the new request here or delegete it to someone*/
FD_SET(fd,&set);
}
}
}
}
You should also modify static bool recv_and_execute(t_handle *hdl) that way:
errno = 0;
if ((nread = getline(&raw, &len, hdl->sender->stream)) != -1){
printf("Received \"%s\"\n", raw);
parse_cmd(hdl, raw);
exec_cmd(hdl);
}else{
if( errno == 0){
printf("EOF reached\n");
fclose(hdl->sender->stream);
hdl->sender->stream = NULL;
}else{
printf("getline failed %s\n", strerror(errno));
exit(EXIT_FAILURE); /*You must handle it in some way, exiting or doing something*/
}
}