¿Cómo funcionan los pseudo terminales * nix? ¿Cuál es el canal maestro / esclavo?
Pregunta
Quiero escribir un emulador de terminal X simple y tonto en C en un sistema Linux.
Al principio, pensé que tendría que abrir un shell y mostrar su salida. Revisé el código xterm y rxvt, y parece un poco más complicado.
Primero, tengo que abrir un pseudo-terminal con openpty. Así que miro la página de manual y veo que openpty llena 2 descriptores de archivos, el maestro y el esclavo. Tanto el código xterm como el rxvt son desordenados debido a la dependencia del sistema de esos archivos especiales.
Entiendo el término termios: es solo un montón de información sobre el código de escape del terminal. Lo que realmente no entiendo es: ¿qué se supone que debo hacer con el descriptor de archivo maestro / esclavo?
Un programa de ejemplo que abre un terminal, inicia sesión, ejecuta un " ls " en el caparazón sería increíble.
(el inglés no es mi lengua materna, disculpe mi eventual error)
Editar: Aquí está el código de muestra que se me ocurrió:
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <pty.h>
#include <utmp.h>
#include <ctype.h>
void
safe_print (char* s)
{
while(*s) {
if(*s == '\n')
putchar("\n");
else if(iscntrl(*s))
printf("\\e(%d)", *s);
else
putchar(*s);
s++;
}
}
int
main (int argc, char** argv)
{
char buf[BUFSIZ] = {0};
int master;
int ret = forkpty(&master, NULL, NULL, NULL);
if(ret == -1)
puts("no fork"), exit(0);
if(!ret) {
execl("/bin/sh", "sh", NULL);
exit(0);
}
sleep(1); /* let the shell run */
if(argc >= 2) {
write(master, argv[1], strlen(argv[1]));
write(master, "\n", 1);
} else {
write(master, "date\n", sizeof "date\n");
}
while(1) {
switch(ret = read(master, buf, BUFSIZ)) {
case -1:
puts("error!");
exit(1);
break;
case 0:
puts("nothing.."), sleep(1);
break;
default:
buf[ret] = '\0';
safe_print(buf);
}
}
close(master);
return 0;
}
Solución
Con respecto a la parte maestro / esclavo de su pregunta, de página de comando pty (4) (a la que se hace referencia desde la página de comando openpty (3) en mi sistema):
Un pseudo terminal es un par de dispositivos de caracteres, un dispositivo maestro y Un dispositivo esclavo. El dispositivo esclavo proporciona a un proceso una interfaz idéntico al descrito en tty (4). Sin embargo, mientras que todos los demás dispositivos que proporcionan la interfaz descrita en tty (4) tienen un dispositivo de hardware de algún tipo detrás de ellos, el esclavo el dispositivo tiene, en cambio, otro proceso manipulándolo a través del maestro La mitad del pseudo terminal. Es decir, cualquier cosa escrita en el dispositivo maestro se le da al dispositivo esclavo como entrada y cualquier cosa escrita en el esclavo el dispositivo se presenta como entrada en el dispositivo maestro.
Las páginas de manual son tus amigos.
Otros consejos
Acabo de probar los ejemplos que se encuentran en este tutorial , funcionan muy bien para mí y creo que son un punto de partida interesante para el problema.
EDITAR: El tutorial explica brevemente la función de pseudo terminales. La explicación se realiza paso a paso y le siguen ejemplos.
El siguiente ejemplo muestra cómo crear un nuevo pseudo-terminal, y bifurcar el proceso en dos partes, una escribiendo en el lado maestro del pseudo-terminal, la otra lectura del lado esclavo del pseudo-terminal.
#define _XOPEN_SOURCE 600
#include <stdlib.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>
#include <stdio.h>
#define __USE_BSD
#include <termios.h>
int main(void)
{
int fdm, fds, rc;
char input[150];
fdm = posix_openpt(O_RDWR);
if (fdm < 0)
{
fprintf(stderr, "Error %d on posix_openpt()\n", errno);
return 1;
}
rc = grantpt(fdm);
if (rc != 0)
{
fprintf(stderr, "Error %d on grantpt()\n", errno);
return 1;
}
rc = unlockpt(fdm);
if (rc != 0)
{
fprintf(stderr, "Error %d on unlockpt()\n", errno);
return 1;
}
// Open the slave PTY
fds = open(ptsname(fdm), O_RDWR);
printf("Virtual interface configured\n");
printf("The master side is named : %s\n", ptsname(fdm));
// Creation of a child process
if (fork())
{
// Father
// Close the slave side of the PTY
close(fds);
while (1)
{
// Operator's entry (standard input = terminal)
write(1, "Input : ", sizeof("Input : "));
rc = read(0, input, sizeof(input));
if (rc > 0)
{
// Send the input to the child process through the PTY
write(fdm, input, rc);
// Get the child's answer through the PTY
rc = read(fdm, input, sizeof(input) - 1);
if (rc > 0)
{
// Make the answer NUL terminated to display it as a string
input[rc] = '\0';
fprintf(stderr, "%s", input);
}
else
{
break;
}
}
else
{
break;
}
} // End while
}
else
{
struct termios slave_orig_term_settings; // Saved terminal settings
struct termios new_term_settings; // Current terminal settings
// Child
// Close the master side of the PTY
close(fdm);
// Save the default parameters of the slave side of the PTY
rc = tcgetattr(fds, &slave_orig_term_settings);
// Set raw mode on the slave side of the PTY
new_term_settings = slave_orig_term_settings;
cfmakeraw (&new_term_settings);
tcsetattr (fds, TCSANOW, &new_term_settings);
// The slave side of the PTY becomes the standard input and outputs of the child process
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)
while (1)
{
rc = read(fds, input, sizeof(input) - 1);
if (rc > 0)
{
// Replace the terminating \n by a NUL to display it as a string
input[rc - 1] = '\0';
printf("Child received : '%s'\n", input);
}
else
{
break;
}
} // End while
}
return 0;
} // main