termux-app

Форк
0
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_R
16
#endif
17

18
static int throw_runtime_exception(JNIEnv* env, char const* message)
19
{
20
    jclass exClass = (*env)->FindClass(env, "java/lang/RuntimeException");
21
    (*env)->ThrowNew(env, exClass, message);
22
    return -1;
23
}
24

25
static int create_subprocess(JNIEnv* env,
26
        char const* cmd,
27
        char const* cwd,
28
        char* const argv[],
29
        char** envp,
30
        int* pProcessId,
31
        jint rows,
32
        jint columns)
33
{
34
    int ptm = open("/dev/ptmx", O_RDWR | O_CLOEXEC);
35
    if (ptm < 0) return throw_runtime_exception(env, "Cannot open /dev/ptmx");
36

37
#ifdef LACKS_PTSNAME_R
38
    char* devname;
39
#else
40
    char devname[64];
41
#endif
42
    if (grantpt(ptm) || unlockpt(ptm) ||
43
#ifdef LACKS_PTSNAME_R
44
            (devname = ptsname(ptm)) == NULL
45
#else
46
            ptsname_r(ptm, devname, sizeof(devname))
47
#endif
48
       ) {
49
        return 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.
53
    struct termios tios;
54
    tcgetattr(ptm, &tios);
55
    tios.c_iflag |= IUTF8;
56
    tios.c_iflag &= ~(IXON | IXOFF);
57
    tcsetattr(ptm, TCSANOW, &tios);
58

59
    /** Set initial winsize. */
60
    struct winsize sz = { .ws_row = (unsigned short) rows, .ws_col = (unsigned short) columns };
61
    ioctl(ptm, TIOCSWINSZ, &sz);
62

63
    pid_t pid = fork();
64
    if (pid < 0) {
65
        return throw_runtime_exception(env, "Fork failed");
66
    } else if (pid > 0) {
67
        *pProcessId = (int) pid;
68
        return ptm;
69
    } else {
70
        // Clear signals which the Android java process may have blocked:
71
        sigset_t signals_to_unblock;
72
        sigfillset(&signals_to_unblock);
73
        sigprocmask(SIG_UNBLOCK, &signals_to_unblock, 0);
74

75
        close(ptm);
76
        setsid();
77

78
        int pts = open(devname, O_RDWR);
79
        if (pts < 0) exit(-1);
80

81
        dup2(pts, 0);
82
        dup2(pts, 1);
83
        dup2(pts, 2);
84

85
        DIR* self_dir = opendir("/proc/self/fd");
86
        if (self_dir != NULL) {
87
            int self_dir_fd = dirfd(self_dir);
88
            struct dirent* entry;
89
            while ((entry = readdir(self_dir)) != NULL) {
90
                int fd = atoi(entry->d_name);
91
                if (fd > 2 && fd != self_dir_fd) close(fd);
92
            }
93
            closedir(self_dir);
94
        }
95

96
        clearenv();
97
        if (envp) for (; *envp; ++envp) putenv(*envp);
98

99
        if (chdir(cwd) != 0) {
100
            char* error_message;
101
            // No need to free asprintf()-allocated memory since doing execvp() or exit() below.
102
            if (asprintf(&error_message, "chdir(\"%s\")", cwd) == -1) error_message = "chdir()";
103
            perror(error_message);
104
            fflush(stderr);
105
        }
106
        execvp(cmd, argv);
107
        // Show terminal output about failing exec() call:
108
        char* error_message;
109
        if (asprintf(&error_message, "exec(\"%s\")", cmd) == -1) error_message = "exec()";
110
        perror(error_message);
111
        _exit(1);
112
    }
113
}
114

115
JNIEXPORT jint JNICALL Java_com_termux_terminal_JNI_createSubprocess(
116
        JNIEnv* env,
117
        jclass TERMUX_UNUSED(clazz),
118
        jstring cmd,
119
        jstring cwd,
120
        jobjectArray args,
121
        jobjectArray envVars,
122
        jintArray processIdArray,
123
        jint rows,
124
        jint columns)
125
{
126
    jsize size = args ? (*env)->GetArrayLength(env, args) : 0;
127
    char** argv = NULL;
128
    if (size > 0) {
129
        argv = (char**) malloc((size + 1) * sizeof(char*));
130
        if (!argv) return throw_runtime_exception(env, "Couldn't allocate argv array");
131
        for (int i = 0; i < size; ++i) {
132
            jstring arg_java_string = (jstring) (*env)->GetObjectArrayElement(env, args, i);
133
            char const* arg_utf8 = (*env)->GetStringUTFChars(env, arg_java_string, NULL);
134
            if (!arg_utf8) return throw_runtime_exception(env, "GetStringUTFChars() failed for argv");
135
            argv[i] = strdup(arg_utf8);
136
            (*env)->ReleaseStringUTFChars(env, arg_java_string, arg_utf8);
137
        }
138
        argv[size] = NULL;
139
    }
140

141
    size = envVars ? (*env)->GetArrayLength(env, envVars) : 0;
142
    char** envp = NULL;
143
    if (size > 0) {
144
        envp = (char**) malloc((size + 1) * sizeof(char *));
145
        if (!envp) return throw_runtime_exception(env, "malloc() for envp array failed");
146
        for (int i = 0; i < size; ++i) {
147
            jstring env_java_string = (jstring) (*env)->GetObjectArrayElement(env, envVars, i);
148
            char const* env_utf8 = (*env)->GetStringUTFChars(env, env_java_string, 0);
149
            if (!env_utf8) return throw_runtime_exception(env, "GetStringUTFChars() failed for env");
150
            envp[i] = strdup(env_utf8);
151
            (*env)->ReleaseStringUTFChars(env, env_java_string, env_utf8);
152
        }
153
        envp[size] = NULL;
154
    }
155

156
    int procId = 0;
157
    char const* cmd_cwd = (*env)->GetStringUTFChars(env, cwd, NULL);
158
    char const* cmd_utf8 = (*env)->GetStringUTFChars(env, cmd, NULL);
159
    int 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

163
    if (argv) {
164
        for (char** tmp = argv; *tmp; ++tmp) free(*tmp);
165
        free(argv);
166
    }
167
    if (envp) {
168
        for (char** tmp = envp; *tmp; ++tmp) free(*tmp);
169
        free(envp);
170
    }
171

172
    int* pProcId = (int*) (*env)->GetPrimitiveArrayCritical(env, processIdArray, NULL);
173
    if (!pProcId) return throw_runtime_exception(env, "JNI call GetPrimitiveArrayCritical(processIdArray, &isCopy) failed");
174

175
    *pProcId = procId;
176
    (*env)->ReleasePrimitiveArrayCritical(env, processIdArray, pProcId, 0);
177

178
    return ptm;
179
}
180

181
JNIEXPORT void JNICALL Java_com_termux_terminal_JNI_setPtyWindowSize(JNIEnv* TERMUX_UNUSED(env), jclass TERMUX_UNUSED(clazz), jint fd, jint rows, jint cols)
182
{
183
    struct winsize sz = { .ws_row = (unsigned short) rows, .ws_col = (unsigned short) cols };
184
    ioctl(fd, TIOCSWINSZ, &sz);
185
}
186

187
JNIEXPORT void JNICALL Java_com_termux_terminal_JNI_setPtyUTF8Mode(JNIEnv* TERMUX_UNUSED(env), jclass TERMUX_UNUSED(clazz), jint fd)
188
{
189
    struct termios tios;
190
    tcgetattr(fd, &tios);
191
    if ((tios.c_iflag & IUTF8) == 0) {
192
        tios.c_iflag |= IUTF8;
193
        tcsetattr(fd, TCSANOW, &tios);
194
    }
195
}
196

197
JNIEXPORT jint JNICALL Java_com_termux_terminal_JNI_waitFor(JNIEnv* TERMUX_UNUSED(env), jclass TERMUX_UNUSED(clazz), jint pid)
198
{
199
    int status;
200
    waitpid(pid, &status, 0);
201
    if (WIFEXITED(status)) {
202
        return WEXITSTATUS(status);
203
    } else if (WIFSIGNALED(status)) {
204
        return -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".
207
        return 0;
208
    }
209
}
210

211
JNIEXPORT void JNICALL Java_com_termux_terminal_JNI_close(JNIEnv* TERMUX_UNUSED(env), jclass TERMUX_UNUSED(clazz), jint fileDescriptor)
212
{
213
    close(fileDescriptor);
214
}
215

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.