termux-app
214 строк · 7.0 Кб
1#include <dirent.h>2#include <fcntl.h>3#include <jni.h>4#include <signal.h>5#include <stdio.h>6#include <stdlib.h>7#include <string.h>8#include <sys/ioctl.h>9#include <sys/wait.h>10#include <termios.h>11#include <unistd.h>12
13#define TERMUX_UNUSED(x) x __attribute__((__unused__))14#ifdef __APPLE__15# define LACKS_PTSNAME_R16#endif17
18static int throw_runtime_exception(JNIEnv* env, char const* message)19{
20jclass exClass = (*env)->FindClass(env, "java/lang/RuntimeException");21(*env)->ThrowNew(env, exClass, message);22return -1;23}
24
25static int create_subprocess(JNIEnv* env,26char const* cmd,27char const* cwd,28char* const argv[],29char** envp,30int* pProcessId,31jint rows,32jint columns)33{
34int ptm = open("/dev/ptmx", O_RDWR | O_CLOEXEC);35if (ptm < 0) return throw_runtime_exception(env, "Cannot open /dev/ptmx");36
37#ifdef LACKS_PTSNAME_R38char* devname;39#else40char devname[64];41#endif42if (grantpt(ptm) || unlockpt(ptm) ||43#ifdef LACKS_PTSNAME_R44(devname = ptsname(ptm)) == NULL45#else46ptsname_r(ptm, devname, sizeof(devname))47#endif48) {49return throw_runtime_exception(env, "Cannot grantpt()/unlockpt()/ptsname_r() on /dev/ptmx");50}51
52// Enable UTF-8 mode and disable flow control to prevent Ctrl+S from locking up the display.53struct termios tios;54tcgetattr(ptm, &tios);55tios.c_iflag |= IUTF8;56tios.c_iflag &= ~(IXON | IXOFF);57tcsetattr(ptm, TCSANOW, &tios);58
59/** Set initial winsize. */60struct winsize sz = { .ws_row = (unsigned short) rows, .ws_col = (unsigned short) columns };61ioctl(ptm, TIOCSWINSZ, &sz);62
63pid_t pid = fork();64if (pid < 0) {65return throw_runtime_exception(env, "Fork failed");66} else if (pid > 0) {67*pProcessId = (int) pid;68return ptm;69} else {70// Clear signals which the Android java process may have blocked:71sigset_t signals_to_unblock;72sigfillset(&signals_to_unblock);73sigprocmask(SIG_UNBLOCK, &signals_to_unblock, 0);74
75close(ptm);76setsid();77
78int pts = open(devname, O_RDWR);79if (pts < 0) exit(-1);80
81dup2(pts, 0);82dup2(pts, 1);83dup2(pts, 2);84
85DIR* self_dir = opendir("/proc/self/fd");86if (self_dir != NULL) {87int self_dir_fd = dirfd(self_dir);88struct dirent* entry;89while ((entry = readdir(self_dir)) != NULL) {90int fd = atoi(entry->d_name);91if (fd > 2 && fd != self_dir_fd) close(fd);92}93closedir(self_dir);94}95
96clearenv();97if (envp) for (; *envp; ++envp) putenv(*envp);98
99if (chdir(cwd) != 0) {100char* error_message;101// No need to free asprintf()-allocated memory since doing execvp() or exit() below.102if (asprintf(&error_message, "chdir(\"%s\")", cwd) == -1) error_message = "chdir()";103perror(error_message);104fflush(stderr);105}106execvp(cmd, argv);107// Show terminal output about failing exec() call:108char* error_message;109if (asprintf(&error_message, "exec(\"%s\")", cmd) == -1) error_message = "exec()";110perror(error_message);111_exit(1);112}113}
114
115JNIEXPORT jint JNICALL Java_com_termux_terminal_JNI_createSubprocess(116JNIEnv* env,117jclass TERMUX_UNUSED(clazz),118jstring cmd,119jstring cwd,120jobjectArray args,121jobjectArray envVars,122jintArray processIdArray,123jint rows,124jint columns)125{
126jsize size = args ? (*env)->GetArrayLength(env, args) : 0;127char** argv = NULL;128if (size > 0) {129argv = (char**) malloc((size + 1) * sizeof(char*));130if (!argv) return throw_runtime_exception(env, "Couldn't allocate argv array");131for (int i = 0; i < size; ++i) {132jstring arg_java_string = (jstring) (*env)->GetObjectArrayElement(env, args, i);133char const* arg_utf8 = (*env)->GetStringUTFChars(env, arg_java_string, NULL);134if (!arg_utf8) return throw_runtime_exception(env, "GetStringUTFChars() failed for argv");135argv[i] = strdup(arg_utf8);136(*env)->ReleaseStringUTFChars(env, arg_java_string, arg_utf8);137}138argv[size] = NULL;139}140
141size = envVars ? (*env)->GetArrayLength(env, envVars) : 0;142char** envp = NULL;143if (size > 0) {144envp = (char**) malloc((size + 1) * sizeof(char *));145if (!envp) return throw_runtime_exception(env, "malloc() for envp array failed");146for (int i = 0; i < size; ++i) {147jstring env_java_string = (jstring) (*env)->GetObjectArrayElement(env, envVars, i);148char const* env_utf8 = (*env)->GetStringUTFChars(env, env_java_string, 0);149if (!env_utf8) return throw_runtime_exception(env, "GetStringUTFChars() failed for env");150envp[i] = strdup(env_utf8);151(*env)->ReleaseStringUTFChars(env, env_java_string, env_utf8);152}153envp[size] = NULL;154}155
156int procId = 0;157char const* cmd_cwd = (*env)->GetStringUTFChars(env, cwd, NULL);158char const* cmd_utf8 = (*env)->GetStringUTFChars(env, cmd, NULL);159int ptm = create_subprocess(env, cmd_utf8, cmd_cwd, argv, envp, &procId, rows, columns);160(*env)->ReleaseStringUTFChars(env, cmd, cmd_utf8);161(*env)->ReleaseStringUTFChars(env, cmd, cmd_cwd);162
163if (argv) {164for (char** tmp = argv; *tmp; ++tmp) free(*tmp);165free(argv);166}167if (envp) {168for (char** tmp = envp; *tmp; ++tmp) free(*tmp);169free(envp);170}171
172int* pProcId = (int*) (*env)->GetPrimitiveArrayCritical(env, processIdArray, NULL);173if (!pProcId) return throw_runtime_exception(env, "JNI call GetPrimitiveArrayCritical(processIdArray, &isCopy) failed");174
175*pProcId = procId;176(*env)->ReleasePrimitiveArrayCritical(env, processIdArray, pProcId, 0);177
178return ptm;179}
180
181JNIEXPORT void JNICALL Java_com_termux_terminal_JNI_setPtyWindowSize(JNIEnv* TERMUX_UNUSED(env), jclass TERMUX_UNUSED(clazz), jint fd, jint rows, jint cols)182{
183struct winsize sz = { .ws_row = (unsigned short) rows, .ws_col = (unsigned short) cols };184ioctl(fd, TIOCSWINSZ, &sz);185}
186
187JNIEXPORT void JNICALL Java_com_termux_terminal_JNI_setPtyUTF8Mode(JNIEnv* TERMUX_UNUSED(env), jclass TERMUX_UNUSED(clazz), jint fd)188{
189struct termios tios;190tcgetattr(fd, &tios);191if ((tios.c_iflag & IUTF8) == 0) {192tios.c_iflag |= IUTF8;193tcsetattr(fd, TCSANOW, &tios);194}195}
196
197JNIEXPORT jint JNICALL Java_com_termux_terminal_JNI_waitFor(JNIEnv* TERMUX_UNUSED(env), jclass TERMUX_UNUSED(clazz), jint pid)198{
199int status;200waitpid(pid, &status, 0);201if (WIFEXITED(status)) {202return WEXITSTATUS(status);203} else if (WIFSIGNALED(status)) {204return -WTERMSIG(status);205} else {206// Should never happen - waitpid(2) says "One of the first three macros will evaluate to a non-zero (true) value".207return 0;208}209}
210
211JNIEXPORT void JNICALL Java_com_termux_terminal_JNI_close(JNIEnv* TERMUX_UNUSED(env), jclass TERMUX_UNUSED(clazz), jint fileDescriptor)212{
213close(fileDescriptor);214}
215