Path: blob/master/terminal-emulator/src/main/jni/termux.c
1635 views
#include <dirent.h>1#include <fcntl.h>2#include <jni.h>3#include <signal.h>4#include <stdio.h>5#include <stdlib.h>6#include <string.h>7#include <sys/ioctl.h>8#include <sys/wait.h>9#include <termios.h>10#include <unistd.h>1112#define TERMUX_UNUSED(x) x __attribute__((__unused__))13#ifdef __APPLE__14# define LACKS_PTSNAME_R15#endif1617static int throw_runtime_exception(JNIEnv* env, char const* message)18{19jclass exClass = (*env)->FindClass(env, "java/lang/RuntimeException");20(*env)->ThrowNew(env, exClass, message);21return -1;22}2324static int create_subprocess(JNIEnv* env,25char const* cmd,26char const* cwd,27char* const argv[],28char** envp,29int* pProcessId,30jint rows,31jint columns,32jint cell_width,33jint cell_height)34{35int ptm = open("/dev/ptmx", O_RDWR | O_CLOEXEC);36if (ptm < 0) return throw_runtime_exception(env, "Cannot open /dev/ptmx");3738#ifdef LACKS_PTSNAME_R39char* devname;40#else41char devname[64];42#endif43if (grantpt(ptm) || unlockpt(ptm) ||44#ifdef LACKS_PTSNAME_R45(devname = ptsname(ptm)) == NULL46#else47ptsname_r(ptm, devname, sizeof(devname))48#endif49) {50return throw_runtime_exception(env, "Cannot grantpt()/unlockpt()/ptsname_r() on /dev/ptmx");51}5253// Enable UTF-8 mode and disable flow control to prevent Ctrl+S from locking up the display.54struct termios tios;55tcgetattr(ptm, &tios);56tios.c_iflag |= IUTF8;57tios.c_iflag &= ~(IXON | IXOFF);58tcsetattr(ptm, TCSANOW, &tios);5960/** Set initial winsize. */61struct winsize sz = { .ws_row = (unsigned short) rows, .ws_col = (unsigned short) columns, .ws_xpixel = (unsigned short) (columns * cell_width), .ws_ypixel = (unsigned short) (rows * cell_height)};62ioctl(ptm, TIOCSWINSZ, &sz);6364pid_t pid = fork();65if (pid < 0) {66return throw_runtime_exception(env, "Fork failed");67} else if (pid > 0) {68*pProcessId = (int) pid;69return ptm;70} else {71// Clear signals which the Android java process may have blocked:72sigset_t signals_to_unblock;73sigfillset(&signals_to_unblock);74sigprocmask(SIG_UNBLOCK, &signals_to_unblock, 0);7576close(ptm);77setsid();7879int pts = open(devname, O_RDWR);80if (pts < 0) exit(-1);8182dup2(pts, 0);83dup2(pts, 1);84dup2(pts, 2);8586DIR* self_dir = opendir("/proc/self/fd");87if (self_dir != NULL) {88int self_dir_fd = dirfd(self_dir);89struct dirent* entry;90while ((entry = readdir(self_dir)) != NULL) {91int fd = atoi(entry->d_name);92if (fd > 2 && fd != self_dir_fd) close(fd);93}94closedir(self_dir);95}9697clearenv();98if (envp) for (; *envp; ++envp) putenv(*envp);99100if (chdir(cwd) != 0) {101char* error_message;102// No need to free asprintf()-allocated memory since doing execvp() or exit() below.103if (asprintf(&error_message, "chdir(\"%s\")", cwd) == -1) error_message = "chdir()";104perror(error_message);105fflush(stderr);106}107execvp(cmd, argv);108// Show terminal output about failing exec() call:109char* error_message;110if (asprintf(&error_message, "exec(\"%s\")", cmd) == -1) error_message = "exec()";111perror(error_message);112_exit(1);113}114}115116JNIEXPORT jint JNICALL Java_com_termux_terminal_JNI_createSubprocess(117JNIEnv* env,118jclass TERMUX_UNUSED(clazz),119jstring cmd,120jstring cwd,121jobjectArray args,122jobjectArray envVars,123jintArray processIdArray,124jint rows,125jint columns,126jint cell_width,127jint cell_height)128{129jsize size = args ? (*env)->GetArrayLength(env, args) : 0;130char** argv = NULL;131if (size > 0) {132argv = (char**) malloc((size + 1) * sizeof(char*));133if (!argv) return throw_runtime_exception(env, "Couldn't allocate argv array");134for (int i = 0; i < size; ++i) {135jstring arg_java_string = (jstring) (*env)->GetObjectArrayElement(env, args, i);136char const* arg_utf8 = (*env)->GetStringUTFChars(env, arg_java_string, NULL);137if (!arg_utf8) return throw_runtime_exception(env, "GetStringUTFChars() failed for argv");138argv[i] = strdup(arg_utf8);139(*env)->ReleaseStringUTFChars(env, arg_java_string, arg_utf8);140}141argv[size] = NULL;142}143144size = envVars ? (*env)->GetArrayLength(env, envVars) : 0;145char** envp = NULL;146if (size > 0) {147envp = (char**) malloc((size + 1) * sizeof(char *));148if (!envp) return throw_runtime_exception(env, "malloc() for envp array failed");149for (int i = 0; i < size; ++i) {150jstring env_java_string = (jstring) (*env)->GetObjectArrayElement(env, envVars, i);151char const* env_utf8 = (*env)->GetStringUTFChars(env, env_java_string, 0);152if (!env_utf8) return throw_runtime_exception(env, "GetStringUTFChars() failed for env");153envp[i] = strdup(env_utf8);154(*env)->ReleaseStringUTFChars(env, env_java_string, env_utf8);155}156envp[size] = NULL;157}158159int procId = 0;160char const* cmd_cwd = (*env)->GetStringUTFChars(env, cwd, NULL);161char const* cmd_utf8 = (*env)->GetStringUTFChars(env, cmd, NULL);162int ptm = create_subprocess(env, cmd_utf8, cmd_cwd, argv, envp, &procId, rows, columns, cell_width, cell_height);163(*env)->ReleaseStringUTFChars(env, cmd, cmd_utf8);164(*env)->ReleaseStringUTFChars(env, cmd, cmd_cwd);165166if (argv) {167for (char** tmp = argv; *tmp; ++tmp) free(*tmp);168free(argv);169}170if (envp) {171for (char** tmp = envp; *tmp; ++tmp) free(*tmp);172free(envp);173}174175int* pProcId = (int*) (*env)->GetPrimitiveArrayCritical(env, processIdArray, NULL);176if (!pProcId) return throw_runtime_exception(env, "JNI call GetPrimitiveArrayCritical(processIdArray, &isCopy) failed");177178*pProcId = procId;179(*env)->ReleasePrimitiveArrayCritical(env, processIdArray, pProcId, 0);180181return ptm;182}183184JNIEXPORT void JNICALL Java_com_termux_terminal_JNI_setPtyWindowSize(JNIEnv* TERMUX_UNUSED(env), jclass TERMUX_UNUSED(clazz), jint fd, jint rows, jint cols, jint cell_width, jint cell_height)185{186struct winsize sz = { .ws_row = (unsigned short) rows, .ws_col = (unsigned short) cols, .ws_xpixel = (unsigned short) (cols * cell_width), .ws_ypixel = (unsigned short) (rows * cell_height) };187ioctl(fd, TIOCSWINSZ, &sz);188}189190JNIEXPORT void JNICALL Java_com_termux_terminal_JNI_setPtyUTF8Mode(JNIEnv* TERMUX_UNUSED(env), jclass TERMUX_UNUSED(clazz), jint fd)191{192struct termios tios;193tcgetattr(fd, &tios);194if ((tios.c_iflag & IUTF8) == 0) {195tios.c_iflag |= IUTF8;196tcsetattr(fd, TCSANOW, &tios);197}198}199200JNIEXPORT jint JNICALL Java_com_termux_terminal_JNI_waitFor(JNIEnv* TERMUX_UNUSED(env), jclass TERMUX_UNUSED(clazz), jint pid)201{202int status;203waitpid(pid, &status, 0);204if (WIFEXITED(status)) {205return WEXITSTATUS(status);206} else if (WIFSIGNALED(status)) {207return -WTERMSIG(status);208} else {209// Should never happen - waitpid(2) says "One of the first three macros will evaluate to a non-zero (true) value".210return 0;211}212}213214JNIEXPORT void JNICALL Java_com_termux_terminal_JNI_close(JNIEnv* TERMUX_UNUSED(env), jclass TERMUX_UNUSED(clazz), jint fileDescriptor)215{216close(fileDescriptor);217}218219220