Remove Kotlin interactive-add emulation and run patch add via the native Git runtime with PTY-backed session IO.
652 lines
22 KiB
C
652 lines
22 KiB
C
#include <jni.h>
|
|
#include <android/log.h>
|
|
#include <dlfcn.h>
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <pthread.h>
|
|
#include <signal.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/ioctl.h>
|
|
#include <sys/wait.h>
|
|
#include <time.h>
|
|
#include <unistd.h>
|
|
|
|
#define LOG_TAG "GitHugNativeGit"
|
|
#define GIT_COMMAND_TIMEOUT_MS 30000
|
|
#define GIT_SESSION_MAX_READ_WAIT_MS 1000
|
|
|
|
typedef int (*git_main_fn)(int argc, const char **argv);
|
|
typedef int (*githug_git_main_fn)(int argc, const char **argv);
|
|
typedef void (*git_init_fn)(const char **argv);
|
|
|
|
struct output_buffer {
|
|
char *data;
|
|
size_t length;
|
|
size_t capacity;
|
|
};
|
|
|
|
static pthread_mutex_t git_mutex = PTHREAD_MUTEX_INITIALIZER;
|
|
static void *git_handle = NULL;
|
|
static git_main_fn git_main = NULL;
|
|
static githug_git_main_fn githug_git_main = NULL;
|
|
static git_init_fn git_init = NULL;
|
|
|
|
struct git_session {
|
|
int id;
|
|
pid_t child;
|
|
int pty_fd;
|
|
struct git_session *next;
|
|
};
|
|
|
|
static struct git_session *git_sessions = NULL;
|
|
static int next_git_session_id = 1;
|
|
|
|
static int append_output(struct output_buffer *buffer, const char *data, size_t length) {
|
|
if (length == 0) {
|
|
return 0;
|
|
}
|
|
size_t required = buffer->length + length + 1;
|
|
if (required > buffer->capacity) {
|
|
size_t next_capacity = buffer->capacity == 0 ? 4096 : buffer->capacity;
|
|
while (next_capacity < required) {
|
|
next_capacity *= 2;
|
|
}
|
|
char *next = realloc(buffer->data, next_capacity);
|
|
if (next == NULL) {
|
|
return -1;
|
|
}
|
|
buffer->data = next;
|
|
buffer->capacity = next_capacity;
|
|
}
|
|
memcpy(buffer->data + buffer->length, data, length);
|
|
buffer->length += length;
|
|
buffer->data[buffer->length] = '\0';
|
|
return 0;
|
|
}
|
|
|
|
static int drain_available_output(int fd, struct output_buffer *output, int *saw_eof) {
|
|
char chunk[4096];
|
|
for (;;) {
|
|
ssize_t count = read(fd, chunk, sizeof(chunk));
|
|
if (count > 0) {
|
|
if (append_output(output, chunk, (size_t)count) != 0) {
|
|
return -1;
|
|
}
|
|
continue;
|
|
}
|
|
if (count == 0) {
|
|
*saw_eof = 1;
|
|
return 0;
|
|
}
|
|
if (errno == EINTR) {
|
|
continue;
|
|
}
|
|
if (errno == EAGAIN || errno == EWOULDBLOCK) {
|
|
return 0;
|
|
}
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
static long long monotonic_millis(void) {
|
|
struct timespec now;
|
|
if (clock_gettime(CLOCK_MONOTONIC, &now) != 0) {
|
|
return 0;
|
|
}
|
|
return ((long long)now.tv_sec * 1000LL) + ((long long)now.tv_nsec / 1000000LL);
|
|
}
|
|
|
|
static jobjectArray make_result(JNIEnv *env, int exit_code, const char *output) {
|
|
jclass string_class = (*env)->FindClass(env, "java/lang/String");
|
|
jobjectArray result = (*env)->NewObjectArray(env, 2, string_class, NULL);
|
|
char exit_text[32];
|
|
snprintf(exit_text, sizeof(exit_text), "%d", exit_code);
|
|
jstring exit_string = (*env)->NewStringUTF(env, exit_text);
|
|
jstring output_string = (*env)->NewStringUTF(env, output != NULL ? output : "");
|
|
(*env)->SetObjectArrayElement(env, result, 0, exit_string);
|
|
(*env)->SetObjectArrayElement(env, result, 1, output_string);
|
|
(*env)->DeleteLocalRef(env, exit_string);
|
|
(*env)->DeleteLocalRef(env, output_string);
|
|
return result;
|
|
}
|
|
|
|
static jobjectArray make_session_result(JNIEnv *env, int session_id, int running, int exit_code, const char *output) {
|
|
jclass string_class = (*env)->FindClass(env, "java/lang/String");
|
|
jobjectArray result = (*env)->NewObjectArray(env, 4, string_class, NULL);
|
|
char session_text[32];
|
|
char running_text[8];
|
|
char exit_text[32];
|
|
snprintf(session_text, sizeof(session_text), "%d", session_id);
|
|
snprintf(running_text, sizeof(running_text), "%d", running ? 1 : 0);
|
|
if (exit_code >= 0) {
|
|
snprintf(exit_text, sizeof(exit_text), "%d", exit_code);
|
|
} else {
|
|
exit_text[0] = '\0';
|
|
}
|
|
jstring session_string = (*env)->NewStringUTF(env, session_text);
|
|
jstring running_string = (*env)->NewStringUTF(env, running_text);
|
|
jstring exit_string = (*env)->NewStringUTF(env, exit_text);
|
|
jstring output_string = (*env)->NewStringUTF(env, output != NULL ? output : "");
|
|
(*env)->SetObjectArrayElement(env, result, 0, session_string);
|
|
(*env)->SetObjectArrayElement(env, result, 1, running_string);
|
|
(*env)->SetObjectArrayElement(env, result, 2, exit_string);
|
|
(*env)->SetObjectArrayElement(env, result, 3, output_string);
|
|
(*env)->DeleteLocalRef(env, session_string);
|
|
(*env)->DeleteLocalRef(env, running_string);
|
|
(*env)->DeleteLocalRef(env, exit_string);
|
|
(*env)->DeleteLocalRef(env, output_string);
|
|
return result;
|
|
}
|
|
|
|
static jobjectArray make_error(JNIEnv *env, const char *message) {
|
|
return make_result(env, -1, message);
|
|
}
|
|
|
|
static int load_git(const char *library_path) {
|
|
if (git_init != NULL && git_main != NULL) {
|
|
return 0;
|
|
}
|
|
git_handle = dlopen(library_path, RTLD_NOW | RTLD_GLOBAL);
|
|
if (git_handle == NULL) {
|
|
__android_log_print(ANDROID_LOG_ERROR, LOG_TAG, "dlopen failed: %s", dlerror());
|
|
return -1;
|
|
}
|
|
git_init = (git_init_fn)dlsym(git_handle, "init_git");
|
|
if (git_init == NULL) {
|
|
__android_log_print(ANDROID_LOG_ERROR, LOG_TAG, "dlsym init_git failed: %s", dlerror());
|
|
return -1;
|
|
}
|
|
githug_git_main = (githug_git_main_fn)dlsym(git_handle, "githug_git_main");
|
|
git_main = (git_main_fn)dlsym(git_handle, "cmd_main");
|
|
if (git_main == NULL) {
|
|
__android_log_print(ANDROID_LOG_ERROR, LOG_TAG, "dlsym cmd_main failed: %s", dlerror());
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static char **copy_string_array(JNIEnv *env, jobjectArray source, int *count_out) {
|
|
int count = source == NULL ? 0 : (*env)->GetArrayLength(env, source);
|
|
char **values = calloc((size_t)count + 1, sizeof(char *));
|
|
if (values == NULL) {
|
|
return NULL;
|
|
}
|
|
for (int i = 0; i < count; i++) {
|
|
jstring item = (jstring)(*env)->GetObjectArrayElement(env, source, i);
|
|
const char *chars = (*env)->GetStringUTFChars(env, item, NULL);
|
|
values[i] = strdup(chars != NULL ? chars : "");
|
|
(*env)->ReleaseStringUTFChars(env, item, chars);
|
|
(*env)->DeleteLocalRef(env, item);
|
|
if (values[i] == NULL) {
|
|
for (int j = 0; j < i; j++) {
|
|
free(values[j]);
|
|
}
|
|
free(values);
|
|
return NULL;
|
|
}
|
|
}
|
|
*count_out = count;
|
|
return values;
|
|
}
|
|
|
|
static void free_string_array(char **values, int count) {
|
|
if (values == NULL) {
|
|
return;
|
|
}
|
|
for (int i = 0; i < count; i++) {
|
|
free(values[i]);
|
|
}
|
|
free(values);
|
|
}
|
|
|
|
static struct git_session *find_session(int session_id, struct git_session ***link_out) {
|
|
struct git_session **link = &git_sessions;
|
|
while (*link != NULL) {
|
|
if ((*link)->id == session_id) {
|
|
if (link_out != NULL) {
|
|
*link_out = link;
|
|
}
|
|
return *link;
|
|
}
|
|
link = &((*link)->next);
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static void remove_session(struct git_session **link) {
|
|
if (link == NULL) {
|
|
return;
|
|
}
|
|
struct git_session *session = *link;
|
|
if (session == NULL) {
|
|
return;
|
|
}
|
|
*link = session->next;
|
|
if (session->pty_fd >= 0) {
|
|
close(session->pty_fd);
|
|
}
|
|
free(session);
|
|
}
|
|
|
|
static int child_exit_code_from_status(int status) {
|
|
if (WIFEXITED(status)) {
|
|
return WEXITSTATUS(status);
|
|
}
|
|
if (WIFSIGNALED(status)) {
|
|
return 128 + WTERMSIG(status);
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
struct saved_env {
|
|
char *name;
|
|
char *previous;
|
|
int had_previous;
|
|
};
|
|
|
|
static void restore_environment(struct saved_env *saved, int count) {
|
|
for (int i = count - 1; i >= 0; i--) {
|
|
if (saved[i].had_previous) {
|
|
setenv(saved[i].name, saved[i].previous, 1);
|
|
} else {
|
|
unsetenv(saved[i].name);
|
|
}
|
|
free(saved[i].name);
|
|
free(saved[i].previous);
|
|
}
|
|
free(saved);
|
|
}
|
|
|
|
static struct saved_env *apply_environment(char **entries, int count, int *applied_out) {
|
|
struct saved_env *saved = calloc((size_t)count, sizeof(struct saved_env));
|
|
if (saved == NULL) {
|
|
return NULL;
|
|
}
|
|
int applied = 0;
|
|
for (int i = 0; i < count; i++) {
|
|
char *separator = strchr(entries[i], '=');
|
|
if (separator == NULL || separator == entries[i]) {
|
|
continue;
|
|
}
|
|
*separator = '\0';
|
|
const char *name = entries[i];
|
|
const char *value = separator + 1;
|
|
const char *previous = getenv(name);
|
|
saved[applied].name = strdup(name);
|
|
saved[applied].previous = previous == NULL ? NULL : strdup(previous);
|
|
saved[applied].had_previous = previous != NULL;
|
|
if (saved[applied].name == NULL || (previous != NULL && saved[applied].previous == NULL)) {
|
|
*separator = '=';
|
|
restore_environment(saved, applied + 1);
|
|
return NULL;
|
|
}
|
|
setenv(name, value, 1);
|
|
applied++;
|
|
*separator = '=';
|
|
}
|
|
*applied_out = applied;
|
|
return saved;
|
|
}
|
|
|
|
JNIEXPORT jobjectArray JNICALL
|
|
Java_solutions_tretter_githugandroid_NativeGitBridge_runGitMainNative(
|
|
JNIEnv *env,
|
|
jclass clazz,
|
|
jstring library_path,
|
|
jstring working_directory,
|
|
jobjectArray argv_array,
|
|
jobjectArray environment_array
|
|
) {
|
|
(void)clazz;
|
|
const char *library_path_chars = (*env)->GetStringUTFChars(env, library_path, NULL);
|
|
const char *working_directory_chars = (*env)->GetStringUTFChars(env, working_directory, NULL);
|
|
int argc = 0;
|
|
int envc = 0;
|
|
char **argv = copy_string_array(env, argv_array, &argc);
|
|
char **env_entries = copy_string_array(env, environment_array, &envc);
|
|
if (library_path_chars == NULL || working_directory_chars == NULL || argv == NULL || env_entries == NULL) {
|
|
return make_error(env, "Native Git execution failed: out of memory");
|
|
}
|
|
|
|
int pipe_fds[2];
|
|
if (pipe(pipe_fds) != 0) {
|
|
free_string_array(argv, argc);
|
|
free_string_array(env_entries, envc);
|
|
(*env)->ReleaseStringUTFChars(env, library_path, library_path_chars);
|
|
(*env)->ReleaseStringUTFChars(env, working_directory, working_directory_chars);
|
|
return make_error(env, "Native Git execution failed: could not create output pipe");
|
|
}
|
|
|
|
pthread_mutex_lock(&git_mutex);
|
|
if (load_git(library_path_chars) != 0) {
|
|
const char *error = dlerror();
|
|
close(pipe_fds[0]);
|
|
close(pipe_fds[1]);
|
|
pthread_mutex_unlock(&git_mutex);
|
|
free_string_array(argv, argc);
|
|
free_string_array(env_entries, envc);
|
|
(*env)->ReleaseStringUTFChars(env, library_path, library_path_chars);
|
|
(*env)->ReleaseStringUTFChars(env, working_directory, working_directory_chars);
|
|
return make_error(env, error != NULL ? error : "Native Git execution failed: init_git/cmd_main not found");
|
|
}
|
|
|
|
pid_t child = fork();
|
|
if (child < 0) {
|
|
close(pipe_fds[0]);
|
|
close(pipe_fds[1]);
|
|
pthread_mutex_unlock(&git_mutex);
|
|
free_string_array(argv, argc);
|
|
free_string_array(env_entries, envc);
|
|
(*env)->ReleaseStringUTFChars(env, library_path, library_path_chars);
|
|
(*env)->ReleaseStringUTFChars(env, working_directory, working_directory_chars);
|
|
return make_error(env, "Native Git execution failed: could not fork");
|
|
}
|
|
|
|
if (child == 0) {
|
|
setpgid(0, 0);
|
|
close(pipe_fds[0]);
|
|
dup2(pipe_fds[1], STDOUT_FILENO);
|
|
dup2(pipe_fds[1], STDERR_FILENO);
|
|
close(pipe_fds[1]);
|
|
|
|
int applied_env = 0;
|
|
struct saved_env *saved_env = apply_environment(env_entries, envc, &applied_env);
|
|
(void)saved_env;
|
|
chdir(working_directory_chars);
|
|
|
|
int child_exit_code;
|
|
if (githug_git_main != NULL) {
|
|
child_exit_code = githug_git_main(argc, (const char **)argv);
|
|
} else {
|
|
git_init((const char **)argv);
|
|
child_exit_code = git_main(argc, (const char **)argv);
|
|
}
|
|
fflush(stdout);
|
|
fflush(stderr);
|
|
_exit(child_exit_code);
|
|
}
|
|
setpgid(child, child);
|
|
|
|
close(pipe_fds[1]);
|
|
struct output_buffer output = {0};
|
|
int flags = fcntl(pipe_fds[0], F_GETFL, 0);
|
|
if (flags >= 0) {
|
|
fcntl(pipe_fds[0], F_SETFL, flags | O_NONBLOCK);
|
|
}
|
|
|
|
int child_status = 0;
|
|
int child_done = 0;
|
|
int saw_eof = 0;
|
|
int timed_out = 0;
|
|
long long started_at = monotonic_millis();
|
|
while (!child_done || !saw_eof) {
|
|
if (drain_available_output(pipe_fds[0], &output, &saw_eof) != 0) {
|
|
break;
|
|
}
|
|
if (!child_done) {
|
|
pid_t wait_result = waitpid(child, &child_status, WNOHANG);
|
|
if (wait_result == child) {
|
|
child_done = 1;
|
|
} else if (wait_result < 0 && errno != EINTR) {
|
|
child_done = 1;
|
|
}
|
|
}
|
|
if (!child_done && started_at > 0 && monotonic_millis() - started_at > GIT_COMMAND_TIMEOUT_MS) {
|
|
timed_out = 1;
|
|
kill(-child, SIGKILL);
|
|
kill(child, SIGKILL);
|
|
while (waitpid(child, &child_status, 0) < 0 && errno == EINTR) {
|
|
}
|
|
child_done = 1;
|
|
const char *timeout_message = "\nNative Git execution timed out.\n";
|
|
append_output(&output, timeout_message, strlen(timeout_message));
|
|
}
|
|
if (child_done) {
|
|
if (!saw_eof) {
|
|
drain_available_output(pipe_fds[0], &output, &saw_eof);
|
|
}
|
|
break;
|
|
}
|
|
usleep(10000);
|
|
}
|
|
close(pipe_fds[0]);
|
|
pthread_mutex_unlock(&git_mutex);
|
|
|
|
int exit_code = -1;
|
|
if (timed_out) {
|
|
exit_code = 124;
|
|
} else if (WIFEXITED(child_status)) {
|
|
exit_code = WEXITSTATUS(child_status);
|
|
} else if (WIFSIGNALED(child_status)) {
|
|
exit_code = 128 + WTERMSIG(child_status);
|
|
}
|
|
|
|
jobjectArray result = make_result(env, exit_code, output.data);
|
|
|
|
free(output.data);
|
|
free_string_array(argv, argc);
|
|
free_string_array(env_entries, envc);
|
|
(*env)->ReleaseStringUTFChars(env, library_path, library_path_chars);
|
|
(*env)->ReleaseStringUTFChars(env, working_directory, working_directory_chars);
|
|
return result;
|
|
}
|
|
|
|
JNIEXPORT jobjectArray JNICALL
|
|
Java_solutions_tretter_githugandroid_NativeGitBridge_startGitSessionNative(
|
|
JNIEnv *env,
|
|
jclass clazz,
|
|
jstring library_path,
|
|
jstring working_directory,
|
|
jobjectArray argv_array,
|
|
jobjectArray environment_array
|
|
) {
|
|
(void)clazz;
|
|
const char *library_path_chars = (*env)->GetStringUTFChars(env, library_path, NULL);
|
|
const char *working_directory_chars = (*env)->GetStringUTFChars(env, working_directory, NULL);
|
|
int argc = 0;
|
|
int envc = 0;
|
|
char **argv = copy_string_array(env, argv_array, &argc);
|
|
char **env_entries = copy_string_array(env, environment_array, &envc);
|
|
if (library_path_chars == NULL || working_directory_chars == NULL || argv == NULL || env_entries == NULL) {
|
|
return make_session_result(env, 0, 0, -1, "Native Git session failed: out of memory");
|
|
}
|
|
|
|
int master_fd = posix_openpt(O_RDWR | O_NOCTTY);
|
|
if (master_fd < 0 || grantpt(master_fd) != 0 || unlockpt(master_fd) != 0) {
|
|
if (master_fd >= 0) {
|
|
close(master_fd);
|
|
}
|
|
free_string_array(argv, argc);
|
|
free_string_array(env_entries, envc);
|
|
(*env)->ReleaseStringUTFChars(env, library_path, library_path_chars);
|
|
(*env)->ReleaseStringUTFChars(env, working_directory, working_directory_chars);
|
|
return make_session_result(env, 0, 0, -1, "Native Git session failed: could not create PTY");
|
|
}
|
|
char *slave_name = ptsname(master_fd);
|
|
if (slave_name == NULL) {
|
|
close(master_fd);
|
|
free_string_array(argv, argc);
|
|
free_string_array(env_entries, envc);
|
|
(*env)->ReleaseStringUTFChars(env, library_path, library_path_chars);
|
|
(*env)->ReleaseStringUTFChars(env, working_directory, working_directory_chars);
|
|
return make_session_result(env, 0, 0, -1, "Native Git session failed: could not resolve PTY slave");
|
|
}
|
|
|
|
pthread_mutex_lock(&git_mutex);
|
|
if (load_git(library_path_chars) != 0) {
|
|
close(master_fd);
|
|
pthread_mutex_unlock(&git_mutex);
|
|
free_string_array(argv, argc);
|
|
free_string_array(env_entries, envc);
|
|
(*env)->ReleaseStringUTFChars(env, library_path, library_path_chars);
|
|
(*env)->ReleaseStringUTFChars(env, working_directory, working_directory_chars);
|
|
return make_session_result(env, 0, 0, -1, "Native Git session failed: init_git/cmd_main not found");
|
|
}
|
|
|
|
pid_t child = fork();
|
|
if (child < 0) {
|
|
close(master_fd);
|
|
pthread_mutex_unlock(&git_mutex);
|
|
free_string_array(argv, argc);
|
|
free_string_array(env_entries, envc);
|
|
(*env)->ReleaseStringUTFChars(env, library_path, library_path_chars);
|
|
(*env)->ReleaseStringUTFChars(env, working_directory, working_directory_chars);
|
|
return make_session_result(env, 0, 0, -1, "Native Git session failed: could not fork");
|
|
}
|
|
|
|
if (child == 0) {
|
|
setsid();
|
|
int slave_fd = open(slave_name, O_RDWR);
|
|
if (slave_fd < 0) {
|
|
_exit(127);
|
|
}
|
|
ioctl(slave_fd, TIOCSCTTY, 0);
|
|
dup2(slave_fd, STDIN_FILENO);
|
|
dup2(slave_fd, STDOUT_FILENO);
|
|
dup2(slave_fd, STDERR_FILENO);
|
|
if (slave_fd > STDERR_FILENO) {
|
|
close(slave_fd);
|
|
}
|
|
close(master_fd);
|
|
|
|
int applied_env = 0;
|
|
struct saved_env *saved_env = apply_environment(env_entries, envc, &applied_env);
|
|
(void)saved_env;
|
|
chdir(working_directory_chars);
|
|
|
|
int child_exit_code;
|
|
if (githug_git_main != NULL) {
|
|
child_exit_code = githug_git_main(argc, (const char **)argv);
|
|
} else {
|
|
git_init((const char **)argv);
|
|
child_exit_code = git_main(argc, (const char **)argv);
|
|
}
|
|
fflush(stdout);
|
|
fflush(stderr);
|
|
_exit(child_exit_code);
|
|
}
|
|
|
|
setpgid(child, child);
|
|
int flags = fcntl(master_fd, F_GETFL, 0);
|
|
if (flags >= 0) {
|
|
fcntl(master_fd, F_SETFL, flags | O_NONBLOCK);
|
|
}
|
|
|
|
struct git_session *session = calloc(1, sizeof(struct git_session));
|
|
if (session == NULL) {
|
|
kill(-child, SIGKILL);
|
|
close(master_fd);
|
|
pthread_mutex_unlock(&git_mutex);
|
|
free_string_array(argv, argc);
|
|
free_string_array(env_entries, envc);
|
|
(*env)->ReleaseStringUTFChars(env, library_path, library_path_chars);
|
|
(*env)->ReleaseStringUTFChars(env, working_directory, working_directory_chars);
|
|
return make_session_result(env, 0, 0, -1, "Native Git session failed: out of memory");
|
|
}
|
|
session->id = next_git_session_id++;
|
|
session->child = child;
|
|
session->pty_fd = master_fd;
|
|
session->next = git_sessions;
|
|
git_sessions = session;
|
|
|
|
struct output_buffer output = {0};
|
|
int saw_eof = 0;
|
|
long long started_at = monotonic_millis();
|
|
while (started_at > 0 && monotonic_millis() - started_at < GIT_SESSION_MAX_READ_WAIT_MS) {
|
|
drain_available_output(session->pty_fd, &output, &saw_eof);
|
|
if (output.length > 0) {
|
|
break;
|
|
}
|
|
usleep(10000);
|
|
}
|
|
|
|
int session_id = session->id;
|
|
int status = 0;
|
|
int running = 1;
|
|
int exit_code = -1;
|
|
pid_t wait_result = waitpid(child, &status, WNOHANG);
|
|
if (wait_result == child) {
|
|
running = 0;
|
|
exit_code = child_exit_code_from_status(status);
|
|
struct git_session **link = NULL;
|
|
find_session(session_id, &link);
|
|
remove_session(link);
|
|
}
|
|
|
|
jobjectArray result = make_session_result(env, session_id, running, exit_code, output.data);
|
|
free(output.data);
|
|
pthread_mutex_unlock(&git_mutex);
|
|
free_string_array(argv, argc);
|
|
free_string_array(env_entries, envc);
|
|
(*env)->ReleaseStringUTFChars(env, library_path, library_path_chars);
|
|
(*env)->ReleaseStringUTFChars(env, working_directory, working_directory_chars);
|
|
return result;
|
|
}
|
|
|
|
JNIEXPORT jobjectArray JNICALL
|
|
Java_solutions_tretter_githugandroid_NativeGitBridge_writeGitSessionNative(
|
|
JNIEnv *env,
|
|
jclass clazz,
|
|
jint session_id,
|
|
jstring input
|
|
) {
|
|
(void)clazz;
|
|
const char *input_chars = (*env)->GetStringUTFChars(env, input, NULL);
|
|
pthread_mutex_lock(&git_mutex);
|
|
struct git_session **link = NULL;
|
|
struct git_session *session = find_session((int)session_id, &link);
|
|
if (session == NULL) {
|
|
pthread_mutex_unlock(&git_mutex);
|
|
if (input_chars != NULL) {
|
|
(*env)->ReleaseStringUTFChars(env, input, input_chars);
|
|
}
|
|
return make_session_result(env, (int)session_id, 0, -1, "Native Git session is not running.");
|
|
}
|
|
|
|
if (input_chars != NULL) {
|
|
size_t input_length = strlen(input_chars);
|
|
size_t written = 0;
|
|
while (written < input_length) {
|
|
ssize_t count = write(session->pty_fd, input_chars + written, input_length - written);
|
|
if (count > 0) {
|
|
written += (size_t)count;
|
|
} else if (errno != EINTR) {
|
|
break;
|
|
}
|
|
}
|
|
(*env)->ReleaseStringUTFChars(env, input, input_chars);
|
|
}
|
|
|
|
struct output_buffer output = {0};
|
|
int saw_eof = 0;
|
|
long long started_at = monotonic_millis();
|
|
while (started_at > 0 && monotonic_millis() - started_at < GIT_SESSION_MAX_READ_WAIT_MS) {
|
|
drain_available_output(session->pty_fd, &output, &saw_eof);
|
|
if (output.length > 0 || saw_eof) {
|
|
break;
|
|
}
|
|
usleep(10000);
|
|
}
|
|
|
|
int status = 0;
|
|
int running = 1;
|
|
int exit_code = -1;
|
|
pid_t wait_result = waitpid(session->child, &status, WNOHANG);
|
|
if (wait_result == session->child || saw_eof) {
|
|
if (wait_result != session->child) {
|
|
while (waitpid(session->child, &status, 0) < 0 && errno == EINTR) {
|
|
}
|
|
}
|
|
running = 0;
|
|
exit_code = child_exit_code_from_status(status);
|
|
remove_session(link);
|
|
}
|
|
|
|
jobjectArray result = make_session_result(env, (int)session_id, running, exit_code, output.data);
|
|
free(output.data);
|
|
pthread_mutex_unlock(&git_mutex);
|
|
return result;
|
|
}
|