Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
termux
GitHub Repository: termux/termux-app
Path: blob/master/terminal-emulator/src/main/jni/termux.c
1635 views
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
jint cell_width,
34
jint cell_height)
35
{
36
int ptm = open("/dev/ptmx", O_RDWR | O_CLOEXEC);
37
if (ptm < 0) return throw_runtime_exception(env, "Cannot open /dev/ptmx");
38
39
#ifdef LACKS_PTSNAME_R
40
char* devname;
41
#else
42
char devname[64];
43
#endif
44
if (grantpt(ptm) || unlockpt(ptm) ||
45
#ifdef LACKS_PTSNAME_R
46
(devname = ptsname(ptm)) == NULL
47
#else
48
ptsname_r(ptm, devname, sizeof(devname))
49
#endif
50
) {
51
return throw_runtime_exception(env, "Cannot grantpt()/unlockpt()/ptsname_r() on /dev/ptmx");
52
}
53
54
// Enable UTF-8 mode and disable flow control to prevent Ctrl+S from locking up the display.
55
struct termios tios;
56
tcgetattr(ptm, &tios);
57
tios.c_iflag |= IUTF8;
58
tios.c_iflag &= ~(IXON | IXOFF);
59
tcsetattr(ptm, TCSANOW, &tios);
60
61
/** Set initial winsize. */
62
struct 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)};
63
ioctl(ptm, TIOCSWINSZ, &sz);
64
65
pid_t pid = fork();
66
if (pid < 0) {
67
return throw_runtime_exception(env, "Fork failed");
68
} else if (pid > 0) {
69
*pProcessId = (int) pid;
70
return ptm;
71
} else {
72
// Clear signals which the Android java process may have blocked:
73
sigset_t signals_to_unblock;
74
sigfillset(&signals_to_unblock);
75
sigprocmask(SIG_UNBLOCK, &signals_to_unblock, 0);
76
77
close(ptm);
78
setsid();
79
80
int pts = open(devname, O_RDWR);
81
if (pts < 0) exit(-1);
82
83
dup2(pts, 0);
84
dup2(pts, 1);
85
dup2(pts, 2);
86
87
DIR* self_dir = opendir("/proc/self/fd");
88
if (self_dir != NULL) {
89
int self_dir_fd = dirfd(self_dir);
90
struct dirent* entry;
91
while ((entry = readdir(self_dir)) != NULL) {
92
int fd = atoi(entry->d_name);
93
if (fd > 2 && fd != self_dir_fd) close(fd);
94
}
95
closedir(self_dir);
96
}
97
98
clearenv();
99
if (envp) for (; *envp; ++envp) putenv(*envp);
100
101
if (chdir(cwd) != 0) {
102
char* error_message;
103
// No need to free asprintf()-allocated memory since doing execvp() or exit() below.
104
if (asprintf(&error_message, "chdir(\"%s\")", cwd) == -1) error_message = "chdir()";
105
perror(error_message);
106
fflush(stderr);
107
}
108
execvp(cmd, argv);
109
// Show terminal output about failing exec() call:
110
char* error_message;
111
if (asprintf(&error_message, "exec(\"%s\")", cmd) == -1) error_message = "exec()";
112
perror(error_message);
113
_exit(1);
114
}
115
}
116
117
JNIEXPORT jint JNICALL Java_com_termux_terminal_JNI_createSubprocess(
118
JNIEnv* env,
119
jclass TERMUX_UNUSED(clazz),
120
jstring cmd,
121
jstring cwd,
122
jobjectArray args,
123
jobjectArray envVars,
124
jintArray processIdArray,
125
jint rows,
126
jint columns,
127
jint cell_width,
128
jint cell_height)
129
{
130
jsize size = args ? (*env)->GetArrayLength(env, args) : 0;
131
char** argv = NULL;
132
if (size > 0) {
133
argv = (char**) malloc((size + 1) * sizeof(char*));
134
if (!argv) return throw_runtime_exception(env, "Couldn't allocate argv array");
135
for (int i = 0; i < size; ++i) {
136
jstring arg_java_string = (jstring) (*env)->GetObjectArrayElement(env, args, i);
137
char const* arg_utf8 = (*env)->GetStringUTFChars(env, arg_java_string, NULL);
138
if (!arg_utf8) return throw_runtime_exception(env, "GetStringUTFChars() failed for argv");
139
argv[i] = strdup(arg_utf8);
140
(*env)->ReleaseStringUTFChars(env, arg_java_string, arg_utf8);
141
}
142
argv[size] = NULL;
143
}
144
145
size = envVars ? (*env)->GetArrayLength(env, envVars) : 0;
146
char** envp = NULL;
147
if (size > 0) {
148
envp = (char**) malloc((size + 1) * sizeof(char *));
149
if (!envp) return throw_runtime_exception(env, "malloc() for envp array failed");
150
for (int i = 0; i < size; ++i) {
151
jstring env_java_string = (jstring) (*env)->GetObjectArrayElement(env, envVars, i);
152
char const* env_utf8 = (*env)->GetStringUTFChars(env, env_java_string, 0);
153
if (!env_utf8) return throw_runtime_exception(env, "GetStringUTFChars() failed for env");
154
envp[i] = strdup(env_utf8);
155
(*env)->ReleaseStringUTFChars(env, env_java_string, env_utf8);
156
}
157
envp[size] = NULL;
158
}
159
160
int procId = 0;
161
char const* cmd_cwd = (*env)->GetStringUTFChars(env, cwd, NULL);
162
char const* cmd_utf8 = (*env)->GetStringUTFChars(env, cmd, NULL);
163
int ptm = create_subprocess(env, cmd_utf8, cmd_cwd, argv, envp, &procId, rows, columns, cell_width, cell_height);
164
(*env)->ReleaseStringUTFChars(env, cmd, cmd_utf8);
165
(*env)->ReleaseStringUTFChars(env, cmd, cmd_cwd);
166
167
if (argv) {
168
for (char** tmp = argv; *tmp; ++tmp) free(*tmp);
169
free(argv);
170
}
171
if (envp) {
172
for (char** tmp = envp; *tmp; ++tmp) free(*tmp);
173
free(envp);
174
}
175
176
int* pProcId = (int*) (*env)->GetPrimitiveArrayCritical(env, processIdArray, NULL);
177
if (!pProcId) return throw_runtime_exception(env, "JNI call GetPrimitiveArrayCritical(processIdArray, &isCopy) failed");
178
179
*pProcId = procId;
180
(*env)->ReleasePrimitiveArrayCritical(env, processIdArray, pProcId, 0);
181
182
return ptm;
183
}
184
185
JNIEXPORT 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)
186
{
187
struct 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) };
188
ioctl(fd, TIOCSWINSZ, &sz);
189
}
190
191
JNIEXPORT void JNICALL Java_com_termux_terminal_JNI_setPtyUTF8Mode(JNIEnv* TERMUX_UNUSED(env), jclass TERMUX_UNUSED(clazz), jint fd)
192
{
193
struct termios tios;
194
tcgetattr(fd, &tios);
195
if ((tios.c_iflag & IUTF8) == 0) {
196
tios.c_iflag |= IUTF8;
197
tcsetattr(fd, TCSANOW, &tios);
198
}
199
}
200
201
JNIEXPORT jint JNICALL Java_com_termux_terminal_JNI_waitFor(JNIEnv* TERMUX_UNUSED(env), jclass TERMUX_UNUSED(clazz), jint pid)
202
{
203
int status;
204
waitpid(pid, &status, 0);
205
if (WIFEXITED(status)) {
206
return WEXITSTATUS(status);
207
} else if (WIFSIGNALED(status)) {
208
return -WTERMSIG(status);
209
} else {
210
// Should never happen - waitpid(2) says "One of the first three macros will evaluate to a non-zero (true) value".
211
return 0;
212
}
213
}
214
215
JNIEXPORT void JNICALL Java_com_termux_terminal_JNI_close(JNIEnv* TERMUX_UNUSED(env), jclass TERMUX_UNUSED(clazz), jint fileDescriptor)
216
{
217
close(fileDescriptor);
218
}
219
220