#include #include #include #include #include #include #include #include #include #define TERMUX_UNUSED(x) x __attribute__((__unused__)) #ifdef __APPLE__ # define LACKS_PTSNAME_R #endif static int throw_runtime_exception(JNIEnv *env, char const *message) { jclass exClass = env->FindClass("java/lang/RuntimeException"); env->ThrowNew(exClass, message); return -1; } static int create_subprocess(JNIEnv *env, char const *cmd, char const *cwd, char *const argv[], char **envp, int *pProcessId, jint rows, jint columns) { int ptm = open("/dev/ptmx", O_RDWR | O_CLOEXEC); if (ptm < 0) return throw_runtime_exception(env, "Cannot open /dev/ptmx"); #ifdef LACKS_PTSNAME_R char* devname; #else char devname[64]; #endif if (grantpt(ptm) || unlockpt(ptm) || #ifdef LACKS_PTSNAME_R (devname = ptsname(ptm)) == NULL #else ptsname_r(ptm, devname, sizeof(devname)) #endif ) { return throw_runtime_exception(env, "Cannot grantpt()/unlockpt()/ptsname_r() on /dev/ptmx"); } // Enable UTF-8 mode and disable flow control to prevent Ctrl+S from locking up the display. struct termios tios; tcgetattr(ptm, &tios); tios.c_iflag |= IUTF8; tios.c_iflag &= ~(IXON | IXOFF); tcsetattr(ptm, TCSANOW, &tios); /** Set initial winsize. */ struct winsize sz = {.ws_row = static_cast(rows), .ws_col = static_cast(columns)}; ioctl(ptm, TIOCSWINSZ, &sz); pid_t pid = fork(); if (pid < 0) { return throw_runtime_exception(env, "Fork failed"); } else if (pid > 0) { *pProcessId = (int) pid; return ptm; } else { // Clear signals which the Android java process may have blocked: sigset_t signals_to_unblock; sigfillset(&signals_to_unblock); sigprocmask(SIG_UNBLOCK, &signals_to_unblock, 0); close(ptm); setsid(); int pts = open(devname, O_RDWR); if (pts < 0) exit(-1); dup2(pts, 0); dup2(pts, 1); dup2(pts, 2); DIR *self_dir = opendir("/proc/self/fd"); if (self_dir != NULL) { int self_dir_fd = dirfd(self_dir); struct dirent *entry; while ((entry = readdir(self_dir)) != NULL) { int fd = atoi(entry->d_name); if (fd > 2 && fd != self_dir_fd) close(fd); } closedir(self_dir); } clearenv(); if (envp) for (; *envp; ++envp) putenv(*envp); if (chdir(cwd) != 0) { char *error_message; // No need to free asprintf()-allocated memory since doing execvp() or exit() below. if (asprintf(&error_message, "chdir(\"%s\")", cwd) == -1) error_message = const_cast("chdir()"); perror(error_message); fflush(stderr); } execvp(cmd, argv); // Show terminal output about failing exec() call: char *error_message; if (asprintf(&error_message, "exec(\"%s\")", cmd) == -1) const_cast("exec()");; perror(error_message); _exit(1); } } extern "C" JNIEXPORT jint JNICALL Java_io_neoterm_backend_JNI_createSubprocess( JNIEnv *env, jclass TERMUX_UNUSED(clazz), jstring cmd, jstring cwd, jobjectArray args, jobjectArray envVars, jintArray processIdArray, jint rows, jint columns) { jsize size = args ? env->GetArrayLength(args) : 0; char **argv = NULL; if (size > 0) { argv = (char **) malloc((size + 1) * sizeof(char *)); if (!argv) return throw_runtime_exception(env, "Couldn't allocate argv array"); for (int i = 0; i < size; ++i) { jstring arg_java_string = (jstring) env->GetObjectArrayElement(args, i); char const *arg_utf8 = env->GetStringUTFChars(arg_java_string, NULL); if (!arg_utf8) return throw_runtime_exception(env, "GetStringUTFChars() failed for argv"); argv[i] = strdup(arg_utf8); env->ReleaseStringUTFChars(arg_java_string, arg_utf8); } argv[size] = NULL; } size = envVars ? env->GetArrayLength(envVars) : 0; char **envp = NULL; if (size > 0) { envp = (char **) malloc((size + 1) * sizeof(char *)); if (!envp) return throw_runtime_exception(env, "malloc() for envp array failed"); for (int i = 0; i < size; ++i) { jstring env_java_string = (jstring) env->GetObjectArrayElement(envVars, i); char const *env_utf8 = env->GetStringUTFChars(env_java_string, 0); if (!env_utf8) return throw_runtime_exception(env, "GetStringUTFChars() failed for env"); envp[i] = strdup(env_utf8); env->ReleaseStringUTFChars(env_java_string, env_utf8); } envp[size] = NULL; } int procId = 0; char const *cmd_cwd = env->GetStringUTFChars(cwd, NULL); char const *cmd_utf8 = env->GetStringUTFChars(cmd, NULL); int ptm = create_subprocess(env, cmd_utf8, cmd_cwd, argv, envp, &procId, rows, columns); env->ReleaseStringUTFChars(cmd, cmd_utf8); env->ReleaseStringUTFChars(cmd, cmd_cwd); if (argv) { for (char **tmp = argv; *tmp; ++tmp) free(*tmp); free(argv); } if (envp) { for (char **tmp = envp; *tmp; ++tmp) free(*tmp); free(envp); } int *pProcId = (int *) env->GetPrimitiveArrayCritical(processIdArray, NULL); if (!pProcId) return throw_runtime_exception(env, "JNI call GetPrimitiveArrayCritical(processIdArray, &isCopy) failed"); *pProcId = procId; env->ReleasePrimitiveArrayCritical(processIdArray, pProcId, 0); return ptm; } extern "C" JNIEXPORT void JNICALL Java_io_neoterm_backend_JNI_setPtyWindowSize(JNIEnv *TERMUX_UNUSED(env), jclass TERMUX_UNUSED(clazz), jint fd, jint rows, jint cols) { struct winsize sz = {.ws_row = static_cast(rows), .ws_col = static_cast(cols)}; ioctl(fd, TIOCSWINSZ, &sz); } extern "C" JNIEXPORT void JNICALL Java_io_neoterm_backend_JNI_setPtyUTF8Mode(JNIEnv *TERMUX_UNUSED(env), jclass TERMUX_UNUSED(clazz), jint fd) { struct termios tios; tcgetattr(fd, &tios); if ((tios.c_iflag & IUTF8) == 0) { tios.c_iflag |= IUTF8; tcsetattr(fd, TCSANOW, &tios); } } extern "C" JNIEXPORT int JNICALL Java_io_neoterm_backend_JNI_waitFor(JNIEnv *TERMUX_UNUSED(env), jclass TERMUX_UNUSED(clazz), jint pid) { int status; waitpid(pid, &status, 0); if (WIFEXITED(status)) { return WEXITSTATUS(status); } else if (WIFSIGNALED(status)) { return -WTERMSIG(status); } else { // Should never happen - waitpid(2) says "One of the first three macros will evaluate to a non-zero (true) value". return 0; } } extern "C" JNIEXPORT void JNICALL Java_io_neoterm_backend_JNI_close(JNIEnv *TERMUX_UNUSED(env), jclass TERMUX_UNUSED(clazz), jint fileDescriptor) { close(fileDescriptor); }