Fix Android native Git child exit handling so committed levels validate and Git calls avoid SIGSEGV status 139

This commit is contained in:
Joe Tretter
2026-06-25 14:56:35 -05:00
parent 60dd3c82f7
commit 2db8fdd328
6 changed files with 66 additions and 8 deletions

View File

@@ -450,7 +450,7 @@ build_android_git_for_abi() {
local output_dir="$ANDROID_JNI_DIR/$abi"
local output_binary="$output_dir/libgit.so"
local stamp="$output_dir/.source-fingerprint"
local fingerprint="$3:android:$abi:$cc:$ANDROID_API"
local fingerprint="$3:android:$abi:$cc:$ANDROID_API:githug-embedded-main"
require_path "$ANDROID_TOOLBIN/$cc"
@@ -470,6 +470,7 @@ build_android_git_for_abi() {
NO_PTHREADS=YesPlease \
NO_LIBGEN_H=YesPlease \
HAVE_DEV_TTY=YesPlease \
CFLAGS_APPEND=-DGITHUG_EMBEDDED_MAIN \
CC="$cc" \
AR=llvm-ar \
RANLIB=llvm-ranlib \

View File

@@ -155,13 +155,13 @@ Git build outputs are stamped with a Git source fingerprint. Re-running `--test`
Git manpage assets are also refreshed from the checked-out Git source whenever Git is compiled or an app artifact is built.
Git source patches can live under `patches/git/` and are applied by `AndroidProjectTooling.sh` after the Git source checkout is cloned or reused. These patches are part of the source fingerprint, so changing a patch forces the host and Android Git binaries to rebuild. The current runtime does not require a Git source patch: it resolves Git's existing exported `init_git` and `cmd_main` symbols through JNI and calls them with Git-style `argc`/`argv`.
Git source patches live under `patches/git/` and are applied by `AndroidProjectTooling.sh` after the Git source checkout is cloned or reused. These patches are part of the source fingerprint, so changing a patch forces Git binaries to rebuild. The Android Git build enables a small embedded-main patch that exports `githug_git_main(argc, argv)`. That wrapper still delegates command dispatch and parsing to Git, but routes Git's process-exit path through `_exit()` in the forked child so Android does not run libc/JVM inherited exit handlers after native Git completes.
## Runtime Architecture
The command engine has one app-facing runtime:
- **Native Git path**: the packaged Git binary for the device ABI is loaded by the JNI bridge and every `git ...` command invokes Git's existing `init_git(argv)` and `cmd_main(argc, argv)` path in a real per-level repository sandbox in app-private storage.
- **Native Git path**: the packaged Git binary for the device ABI is loaded by the JNI bridge and every `git ...` command invokes Git's patched embedded entry point, `githug_git_main(argc, argv)`, in a forked child process inside a real per-level repository sandbox in app-private storage.
The runtime exposes a `RepoState` surface to validators. In addition to files, commits, branches, tags, remotes, and config, the model tracks learning-relevant effects such as stashes, fetched remote refs, pushed branches/tags, submodules, and repository maintenance actions.

View File

@@ -20,8 +20,8 @@ android {
applicationId = "solutions.tretter.githugandroid"
minSdk = 26
targetSdk = 35
versionCode = 177
versionName = "0.1.176"
versionCode = 178
versionName = "0.1.177"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables.useSupportLibrary = true

View File

@@ -16,6 +16,7 @@
#define GIT_COMMAND_TIMEOUT_MS 30000
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 {
@@ -27,6 +28,7 @@ struct output_buffer {
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;
static int append_output(struct output_buffer *buffer, const char *data, size_t length) {
@@ -116,6 +118,7 @@ static int load_git(const char *library_path) {
__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());
@@ -274,8 +277,13 @@ Java_solutions_tretter_githugandroid_NativeGitBridge_runGitMainNative(
(void)saved_env;
chdir(working_directory_chars);
git_init((const char **)argv);
int child_exit_code = git_main(argc, (const char **)argv);
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);

View File

@@ -196,7 +196,7 @@ class GitRepositoryRuntime private constructor(
addAll(GitSandboxEngine.commandReferenceLines())
add("Native Git runtime:")
add(" binary path: nativeLibraryDir/libgit.so")
add(" invocation: init_git(argv), then cmd_main(argc, argv)")
add(" invocation: githug_git_main(argc, argv)")
add(" selected ABI: ${Build.SUPPORTED_ABIS.firstOrNull() ?: "unknown"}")
add(" helper commands: ls/dir, pwd, cat, sh <script>, ./<script>, touch, mkdir/md, cd.., rm/del, echo")
add(" visual editors: vi, vim, nano, emacs, ed, ex, edit, notepad")

View File

@@ -0,0 +1,49 @@
diff --git a/common-main.c b/common-main.c
index 0000000000..0000000000 100644
--- a/common-main.c
+++ b/common-main.c
@@ -1,6 +1,27 @@
#include "git-compat-util.h"
#include "common-init.h"
+#ifdef GITHUG_EMBEDDED_MAIN
+void githug_exit(int code)
+{
+ code &= 0xff;
+ fflush(stdout);
+ fflush(stderr);
+ _exit(code);
+}
+
+int githug_git_main(int argc, const char **argv)
+{
+ int result;
+
+ init_git(argv);
+ result = cmd_main(argc, argv);
+
+ /* Match Git's real main(), but return instead of calling libc exit(). */
+ return common_exit(__FILE__, __LINE__, result);
+}
+#endif
+
int main(int argc, const char **argv)
{
int result;
diff --git a/git-compat-util.h b/git-compat-util.h
index 0000000000..0000000000 100644
--- a/git-compat-util.h
+++ b/git-compat-util.h
@@ -1047,7 +1047,12 @@ int cmd_main(int, const char **);
* optionally emit a message before calling the real exit().
*/
int common_exit(const char *file, int line, int code);
+#ifdef GITHUG_EMBEDDED_MAIN
+NORETURN void githug_exit(int code);
+#define exit(code) githug_exit(common_exit(__FILE__, __LINE__, (code)))
+#else
#define exit(code) exit(common_exit(__FILE__, __LINE__, (code)))
+#endif
/*
* This include must come after system headers, since it introduces macros that