319 lines
6.5 KiB
C
319 lines
6.5 KiB
C
/*
|
|
* Copyright 2013, Tan Chee Eng (@tan-ce)
|
|
*/
|
|
|
|
/*
|
|
* pts.c
|
|
*
|
|
* Manages the pseudo-terminal driver on Linux/Android and provides some
|
|
* helper functions to handle raw input mode and terminal window resizing
|
|
*/
|
|
|
|
#define _GNU_SOURCE
|
|
#include <stdlib.h>
|
|
#include <unistd.h>
|
|
#include <fcntl.h>
|
|
#include <signal.h>
|
|
#include <termios.h>
|
|
#include <errno.h>
|
|
#include <pthread.h>
|
|
|
|
#include "magisk.h"
|
|
#include "pts.h"
|
|
|
|
/**
|
|
* Helper functions
|
|
*/
|
|
// Ensures all the data is written out
|
|
static int write_blocking(int fd, char *buf, ssize_t bufsz) {
|
|
ssize_t ret, written;
|
|
|
|
written = 0;
|
|
do {
|
|
ret = write(fd, buf + written, bufsz - written);
|
|
if (ret == -1) return -1;
|
|
written += ret;
|
|
} while (written < bufsz);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Pump data from input FD to output FD. If close_output is
|
|
* true, then close the output FD when we're done.
|
|
*/
|
|
static void pump_ex(int input, int output, int close_output) {
|
|
char buf[4096];
|
|
int len;
|
|
while ((len = read(input, buf, 4096)) > 0) {
|
|
if (write_blocking(output, buf, len) == -1) break;
|
|
}
|
|
close(input);
|
|
if (close_output) close(output);
|
|
}
|
|
|
|
/**
|
|
* Pump data from input FD to output FD. Will close the
|
|
* output FD when done.
|
|
*/
|
|
static void pump(int input, int output) {
|
|
pump_ex(input, output, 1);
|
|
}
|
|
|
|
static void* pump_thread(void* data) {
|
|
int* files = (int*)data;
|
|
int input = files[0];
|
|
int output = files[1];
|
|
pump(input, output);
|
|
free(data);
|
|
return NULL;
|
|
}
|
|
|
|
static void pump_async(int input, int output) {
|
|
pthread_t writer;
|
|
int* files = (int*)malloc(sizeof(int) * 2);
|
|
if (files == NULL) {
|
|
exit(-1);
|
|
}
|
|
files[0] = input;
|
|
files[1] = output;
|
|
pthread_create(&writer, NULL, pump_thread, files);
|
|
}
|
|
|
|
|
|
/**
|
|
* pts_open
|
|
*
|
|
* Opens a pts device and returns the name of the slave tty device.
|
|
*
|
|
* Arguments
|
|
* slave_name the name of the slave device
|
|
* slave_name_size the size of the buffer passed via slave_name
|
|
*
|
|
* Return Values
|
|
* on failure either -2 or -1 (errno set) is returned.
|
|
* on success, the file descriptor of the master device is returned.
|
|
*/
|
|
int pts_open(char *slave_name, size_t slave_name_size) {
|
|
int fdm;
|
|
char sn_tmp[256];
|
|
|
|
// Open master ptmx device
|
|
fdm = open("/dev/ptmx", O_RDWR);
|
|
if (fdm == -1)
|
|
goto error;
|
|
|
|
// Get the slave name
|
|
if (ptsname_r(fdm, slave_name, slave_name_size-1))
|
|
goto error;
|
|
|
|
slave_name[slave_name_size - 1] = '\0';
|
|
|
|
// Grant, then unlock
|
|
if (grantpt(fdm) == -1)
|
|
goto error;
|
|
|
|
if (unlockpt(fdm) == -1)
|
|
goto error;
|
|
|
|
return fdm;
|
|
error:
|
|
close(fdm);
|
|
PLOGE("pts_open");
|
|
return -1;
|
|
}
|
|
|
|
// Stores the previous termios of stdin
|
|
static struct termios old_stdin;
|
|
static int stdin_is_raw = 0;
|
|
|
|
/**
|
|
* set_stdin_raw
|
|
*
|
|
* Changes stdin to raw unbuffered mode, disables echo,
|
|
* auto carriage return, etc.
|
|
*
|
|
* Return Value
|
|
* on failure -1, and errno is set
|
|
* on success 0
|
|
*/
|
|
int set_stdin_raw(void) {
|
|
struct termios new_termios;
|
|
|
|
// Save the current stdin termios
|
|
if (tcgetattr(STDIN_FILENO, &old_stdin) < 0) {
|
|
return -1;
|
|
}
|
|
|
|
// Start from the current settings
|
|
new_termios = old_stdin;
|
|
|
|
// Make the terminal like an SSH or telnet client
|
|
new_termios.c_iflag |= IGNPAR;
|
|
new_termios.c_iflag &= ~(ISTRIP | INLCR | IGNCR | ICRNL | IXON | IXANY | IXOFF);
|
|
new_termios.c_lflag &= ~(ISIG | ICANON | ECHO | ECHOE | ECHOK | ECHONL);
|
|
new_termios.c_oflag &= ~OPOST;
|
|
new_termios.c_cc[VMIN] = 1;
|
|
new_termios.c_cc[VTIME] = 0;
|
|
|
|
if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &new_termios) < 0) {
|
|
return -1;
|
|
}
|
|
|
|
stdin_is_raw = 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* restore_stdin
|
|
*
|
|
* Restore termios on stdin to the state it was before
|
|
* set_stdin_raw() was called. If set_stdin_raw() was
|
|
* never called, does nothing and doesn't return an error.
|
|
*
|
|
* This function is async-safe.
|
|
*
|
|
* Return Value
|
|
* on failure, -1 and errno is set
|
|
* on success, 0
|
|
*/
|
|
int restore_stdin(void) {
|
|
if (!stdin_is_raw) return 0;
|
|
|
|
if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &old_stdin) < 0) {
|
|
return -1;
|
|
}
|
|
|
|
stdin_is_raw = 0;
|
|
|
|
return 0;
|
|
}
|
|
|
|
// Flag indicating whether the sigwinch watcher should terminate.
|
|
static volatile int closing_time = 0;
|
|
|
|
/**
|
|
* Thread process. Wait for a SIGWINCH to be received, then update
|
|
* the terminal size.
|
|
*/
|
|
static void *watch_sigwinch(void *data) {
|
|
sigset_t winch;
|
|
int sig;
|
|
int master = ((int *)data)[0];
|
|
int slave = ((int *)data)[1];
|
|
|
|
sigemptyset(&winch);
|
|
sigaddset(&winch, SIGWINCH);
|
|
|
|
do {
|
|
if (closing_time) break;
|
|
|
|
// Get the new terminal size
|
|
struct winsize w;
|
|
if (ioctl(master, TIOCGWINSZ, &w) == -1) {
|
|
continue;
|
|
}
|
|
|
|
// Set the new terminal size
|
|
ioctl(slave, TIOCSWINSZ, &w);
|
|
|
|
} while (sigwait(&winch, &sig) == 0);
|
|
|
|
free(data);
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* watch_sigwinch_async
|
|
*
|
|
* After calling this function, if the application receives
|
|
* SIGWINCH, the terminal window size will be read from
|
|
* "input" and set on "output".
|
|
*
|
|
* NOTE: This function blocks SIGWINCH and spawns a thread.
|
|
* NOTE 2: This function must be called before any of the
|
|
* pump functions.
|
|
*
|
|
* Arguments
|
|
* master A file descriptor of the TTY window size to follow
|
|
* slave A file descriptor of the TTY window size which is
|
|
* to be set on SIGWINCH
|
|
*
|
|
* Return Value
|
|
* on failure, -1 and errno will be set. In this case, no
|
|
* thread has been spawned and SIGWINCH will not be
|
|
* blocked.
|
|
* on success, 0
|
|
*/
|
|
int watch_sigwinch_async(int master, int slave) {
|
|
pthread_t watcher;
|
|
int *files = (int *) malloc(sizeof(int) * 2);
|
|
if (files == NULL) {
|
|
return -1;
|
|
}
|
|
|
|
// Block SIGWINCH so sigwait can later receive it
|
|
sigset_t winch;
|
|
sigemptyset(&winch);
|
|
sigaddset(&winch, SIGWINCH);
|
|
if (sigprocmask(SIG_BLOCK, &winch, NULL) == -1) {
|
|
free(files);
|
|
return -1;
|
|
}
|
|
|
|
// Initialize some variables, then start the thread
|
|
closing_time = 0;
|
|
files[0] = master;
|
|
files[1] = slave;
|
|
int ret = pthread_create(&watcher, NULL, &watch_sigwinch, files);
|
|
if (ret != 0) {
|
|
free(files);
|
|
errno = ret;
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* watch_sigwinch_cleanup
|
|
*
|
|
* Cause the SIGWINCH watcher thread to terminate
|
|
*/
|
|
void watch_sigwinch_cleanup(void) {
|
|
closing_time = 1;
|
|
raise(SIGWINCH);
|
|
}
|
|
|
|
/**
|
|
* pump_stdin_async
|
|
*
|
|
* Forward data from STDIN to the given FD
|
|
* in a seperate thread
|
|
*/
|
|
void pump_stdin_async(int outfd) {
|
|
// Put stdin into raw mode
|
|
set_stdin_raw();
|
|
|
|
// Pump data from stdin to the PTY
|
|
pump_async(STDIN_FILENO, outfd);
|
|
}
|
|
|
|
/**
|
|
* pump_stdout_blocking
|
|
*
|
|
* Forward data from the FD to STDOUT.
|
|
* Returns when the remote end of the FD closes.
|
|
*
|
|
* Before returning, restores stdin settings.
|
|
*/
|
|
void pump_stdout_blocking(int infd) {
|
|
// Pump data from stdout to PTY
|
|
pump_ex(infd, STDOUT_FILENO, 0 /* Don't close output when done */);
|
|
|
|
// Cleanup
|
|
restore_stdin();
|
|
watch_sigwinch_cleanup();
|
|
}
|