From 2015bba3824d9f75ad6d6cdd000604c759da6de5 Mon Sep 17 00:00:00 2001 From: tebako-ci Date: Sun, 27 Sep 2026 17:52:45 +0800 Subject: [PATCH] =?UTF-8?q?spawn=20plan=20apply:=20keep=20the=20plan's=20a?= =?UTF-8?q?rgv[0]=20=E2=80=94=20MRI=20does=20not=20re-prepend=20it?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit tfs_spawn_plan_apply skipped the plan's first token, believing ruby re-prepends command_abspath as the child's argv[0] at exec. It does not: the child argv comes from argv_buf verbatim, so the first real token ("--tebako-image") was consumed as argv[0] and the actual first mount triple fell out of the flag parse — the spawned runtime booted with only its env image and the payload mounts were lost (tebako#669's jing residual: Unable to access jarfile, argv-form spawn only). Proven by an argv-dump of the spawned store exe: argv[0] was "--tebako-image". MRI's execv takes the file from command_abspath and argv[0] from argv_buf's first token — rb_exec_fillarg's exact layout, which the plan already carries. Delete the skip; keep the full argv. Applies to every line's neutral patch (3.1-4.0) and the 3.2/3.3/3.4 msys carriers (the windows shell-form surface plans through the same function). Verified: tools/apply green for 3.1.7/3.2.11/3.3.12/3.4.10/4.0.6 on darwin and msys patch selections; applied process.c carries the full argv and the corrected comment. --- patches/3.1/process_c_tebako_spawn.patch | 14 ++++++-------- patches/3.2/process_c_tebako_spawn.patch | 14 ++++++-------- patches/3.2/process_c_tebako_spawn_msys.patch | 12 +++++------- patches/3.3/process_c_tebako_spawn.patch | 14 ++++++-------- patches/3.3/process_c_tebako_spawn_msys.patch | 12 +++++------- patches/3.4/process_c_tebako_spawn.patch | 14 ++++++-------- patches/3.4/process_c_tebako_spawn_msys.patch | 12 +++++------- patches/4.0/process_c_tebako_spawn.patch | 14 ++++++-------- 8 files changed, 45 insertions(+), 61 deletions(-) diff --git a/patches/3.1/process_c_tebako_spawn.patch b/patches/3.1/process_c_tebako_spawn.patch index ebc8a6d..b9ba07a 100644 --- a/patches/3.1/process_c_tebako_spawn.patch +++ b/patches/3.1/process_c_tebako_spawn.patch @@ -53,7 +53,7 @@ # exactly as before). A planned bare name (the app payload's # requires[].expose surface) retargets the exec pair to the store's # runtime exe, rebuilds argv_buf/argv_str from the plan (the plan's -# argv[0] is the exe; ruby re-prepends it at exec), and applies the +# argv[0] rides as the child's own argv[0] at exec), and applies the # plan's env ops (KEY=VALUE sets, bare KEY deletes) into # env_modification. A named error raises — the spawn NEVER falls through # to a host binary of the same name. The head runs BEFORE the darwin @@ -69,7 +69,7 @@ diff --git a/process.c b/process.c index 97fa336..f55697e 100644 --- a/process.c +++ b/process.c -@@ -2834,6 +2834,854 @@ rb_execarg_get(VALUE execarg_obj) +@@ -2834,6 +2834,852 @@ rb_execarg_get(VALUE execarg_obj) return eargp; } @@ -172,11 +172,10 @@ index 97fa336..f55697e 100644 +} + +/* A planned spawn (spec 30 §2): the plan's exe becomes the exec pair; -+ argv_buf takes the plan's tokens AFTER argv[0] (ruby re-prepends the -+ program at exec from command_abspath — and the plan's argv[0] IS that -+ exe); argv_str's pointer vector is rebuilt into the new buffer exactly -+ as rb_exec_fillarg built it (the vector points INTO the buffer's -+ storage, so a new buffer means a rebuilt vector). */ ++ argv_buf takes the plan's FULL argv, argv[0] included — the exec file ++ comes from command_abspath while the child's argv[0] is argv_buf's ++ first token (rb_exec_fillarg's exact layout); argv_str's pointers ++ point INTO the buffer, so a new buffer means a rebuilt vector. */ +static void +tfs_spawn_plan_apply(struct rb_execarg *eargp, const char *exe, + const char *argv, size_t argv_len, @@ -189,7 +188,6 @@ index 97fa336..f55697e 100644 + VALUE av; + eargp->invoke.cmd.command_name = rb_str_new_cstr(exe); + eargp->invoke.cmd.command_abspath = rb_str_new_cstr(exe); -+ if (p < ep) p += strlen(p) + 1; /* the plan's argv[0] is the exe */ + nbuf = hide_obj(rb_str_buf_new((ep - p) + 1)); + while (p < ep) { + rb_str_buf_cat(nbuf, p, strlen(p) + 1); diff --git a/patches/3.2/process_c_tebako_spawn.patch b/patches/3.2/process_c_tebako_spawn.patch index ebc8a6d..b9ba07a 100644 --- a/patches/3.2/process_c_tebako_spawn.patch +++ b/patches/3.2/process_c_tebako_spawn.patch @@ -53,7 +53,7 @@ # exactly as before). A planned bare name (the app payload's # requires[].expose surface) retargets the exec pair to the store's # runtime exe, rebuilds argv_buf/argv_str from the plan (the plan's -# argv[0] is the exe; ruby re-prepends it at exec), and applies the +# argv[0] rides as the child's own argv[0] at exec), and applies the # plan's env ops (KEY=VALUE sets, bare KEY deletes) into # env_modification. A named error raises — the spawn NEVER falls through # to a host binary of the same name. The head runs BEFORE the darwin @@ -69,7 +69,7 @@ diff --git a/process.c b/process.c index 97fa336..f55697e 100644 --- a/process.c +++ b/process.c -@@ -2834,6 +2834,854 @@ rb_execarg_get(VALUE execarg_obj) +@@ -2834,6 +2834,852 @@ rb_execarg_get(VALUE execarg_obj) return eargp; } @@ -172,11 +172,10 @@ index 97fa336..f55697e 100644 +} + +/* A planned spawn (spec 30 §2): the plan's exe becomes the exec pair; -+ argv_buf takes the plan's tokens AFTER argv[0] (ruby re-prepends the -+ program at exec from command_abspath — and the plan's argv[0] IS that -+ exe); argv_str's pointer vector is rebuilt into the new buffer exactly -+ as rb_exec_fillarg built it (the vector points INTO the buffer's -+ storage, so a new buffer means a rebuilt vector). */ ++ argv_buf takes the plan's FULL argv, argv[0] included — the exec file ++ comes from command_abspath while the child's argv[0] is argv_buf's ++ first token (rb_exec_fillarg's exact layout); argv_str's pointers ++ point INTO the buffer, so a new buffer means a rebuilt vector. */ +static void +tfs_spawn_plan_apply(struct rb_execarg *eargp, const char *exe, + const char *argv, size_t argv_len, @@ -189,7 +188,6 @@ index 97fa336..f55697e 100644 + VALUE av; + eargp->invoke.cmd.command_name = rb_str_new_cstr(exe); + eargp->invoke.cmd.command_abspath = rb_str_new_cstr(exe); -+ if (p < ep) p += strlen(p) + 1; /* the plan's argv[0] is the exe */ + nbuf = hide_obj(rb_str_buf_new((ep - p) + 1)); + while (p < ep) { + rb_str_buf_cat(nbuf, p, strlen(p) + 1); diff --git a/patches/3.2/process_c_tebako_spawn_msys.patch b/patches/3.2/process_c_tebako_spawn_msys.patch index 34e01ef..10ef635 100644 --- a/patches/3.2/process_c_tebako_spawn_msys.patch +++ b/patches/3.2/process_c_tebako_spawn_msys.patch @@ -22,7 +22,7 @@ diff --git a/process.c b/process.c index 97fa336..f55697e 100644 --- a/process.c +++ b/process.c -@@ -2834,6 +2834,854 @@ rb_execarg_get(VALUE execarg_obj) +@@ -2834,6 +2834,852 @@ rb_execarg_get(VALUE execarg_obj) return eargp; } @@ -125,11 +125,10 @@ index 97fa336..f55697e 100644 +} + +/* A planned spawn (spec 30 §2): the plan's exe becomes the exec pair; -+ argv_buf takes the plan's tokens AFTER argv[0] (ruby re-prepends the -+ program at exec from command_abspath — and the plan's argv[0] IS that -+ exe); argv_str's pointer vector is rebuilt into the new buffer exactly -+ as rb_exec_fillarg built it (the vector points INTO the buffer's -+ storage, so a new buffer means a rebuilt vector). */ ++ argv_buf takes the plan's FULL argv, argv[0] included — the exec file ++ comes from command_abspath while the child's argv[0] is argv_buf's ++ first token (rb_exec_fillarg's exact layout); argv_str's pointers ++ point INTO the buffer, so a new buffer means a rebuilt vector. */ +static void +tfs_spawn_plan_apply(struct rb_execarg *eargp, const char *exe, + const char *argv, size_t argv_len, @@ -142,7 +141,6 @@ index 97fa336..f55697e 100644 + VALUE av; + eargp->invoke.cmd.command_name = rb_str_new_cstr(exe); + eargp->invoke.cmd.command_abspath = rb_str_new_cstr(exe); -+ if (p < ep) p += strlen(p) + 1; /* the plan's argv[0] is the exe */ + nbuf = hide_obj(rb_str_buf_new((ep - p) + 1)); + while (p < ep) { + rb_str_buf_cat(nbuf, p, strlen(p) + 1); diff --git a/patches/3.3/process_c_tebako_spawn.patch b/patches/3.3/process_c_tebako_spawn.patch index ebc8a6d..b9ba07a 100644 --- a/patches/3.3/process_c_tebako_spawn.patch +++ b/patches/3.3/process_c_tebako_spawn.patch @@ -53,7 +53,7 @@ # exactly as before). A planned bare name (the app payload's # requires[].expose surface) retargets the exec pair to the store's # runtime exe, rebuilds argv_buf/argv_str from the plan (the plan's -# argv[0] is the exe; ruby re-prepends it at exec), and applies the +# argv[0] rides as the child's own argv[0] at exec), and applies the # plan's env ops (KEY=VALUE sets, bare KEY deletes) into # env_modification. A named error raises — the spawn NEVER falls through # to a host binary of the same name. The head runs BEFORE the darwin @@ -69,7 +69,7 @@ diff --git a/process.c b/process.c index 97fa336..f55697e 100644 --- a/process.c +++ b/process.c -@@ -2834,6 +2834,854 @@ rb_execarg_get(VALUE execarg_obj) +@@ -2834,6 +2834,852 @@ rb_execarg_get(VALUE execarg_obj) return eargp; } @@ -172,11 +172,10 @@ index 97fa336..f55697e 100644 +} + +/* A planned spawn (spec 30 §2): the plan's exe becomes the exec pair; -+ argv_buf takes the plan's tokens AFTER argv[0] (ruby re-prepends the -+ program at exec from command_abspath — and the plan's argv[0] IS that -+ exe); argv_str's pointer vector is rebuilt into the new buffer exactly -+ as rb_exec_fillarg built it (the vector points INTO the buffer's -+ storage, so a new buffer means a rebuilt vector). */ ++ argv_buf takes the plan's FULL argv, argv[0] included — the exec file ++ comes from command_abspath while the child's argv[0] is argv_buf's ++ first token (rb_exec_fillarg's exact layout); argv_str's pointers ++ point INTO the buffer, so a new buffer means a rebuilt vector. */ +static void +tfs_spawn_plan_apply(struct rb_execarg *eargp, const char *exe, + const char *argv, size_t argv_len, @@ -189,7 +188,6 @@ index 97fa336..f55697e 100644 + VALUE av; + eargp->invoke.cmd.command_name = rb_str_new_cstr(exe); + eargp->invoke.cmd.command_abspath = rb_str_new_cstr(exe); -+ if (p < ep) p += strlen(p) + 1; /* the plan's argv[0] is the exe */ + nbuf = hide_obj(rb_str_buf_new((ep - p) + 1)); + while (p < ep) { + rb_str_buf_cat(nbuf, p, strlen(p) + 1); diff --git a/patches/3.3/process_c_tebako_spawn_msys.patch b/patches/3.3/process_c_tebako_spawn_msys.patch index 34e01ef..10ef635 100644 --- a/patches/3.3/process_c_tebako_spawn_msys.patch +++ b/patches/3.3/process_c_tebako_spawn_msys.patch @@ -22,7 +22,7 @@ diff --git a/process.c b/process.c index 97fa336..f55697e 100644 --- a/process.c +++ b/process.c -@@ -2834,6 +2834,854 @@ rb_execarg_get(VALUE execarg_obj) +@@ -2834,6 +2834,852 @@ rb_execarg_get(VALUE execarg_obj) return eargp; } @@ -125,11 +125,10 @@ index 97fa336..f55697e 100644 +} + +/* A planned spawn (spec 30 §2): the plan's exe becomes the exec pair; -+ argv_buf takes the plan's tokens AFTER argv[0] (ruby re-prepends the -+ program at exec from command_abspath — and the plan's argv[0] IS that -+ exe); argv_str's pointer vector is rebuilt into the new buffer exactly -+ as rb_exec_fillarg built it (the vector points INTO the buffer's -+ storage, so a new buffer means a rebuilt vector). */ ++ argv_buf takes the plan's FULL argv, argv[0] included — the exec file ++ comes from command_abspath while the child's argv[0] is argv_buf's ++ first token (rb_exec_fillarg's exact layout); argv_str's pointers ++ point INTO the buffer, so a new buffer means a rebuilt vector. */ +static void +tfs_spawn_plan_apply(struct rb_execarg *eargp, const char *exe, + const char *argv, size_t argv_len, @@ -142,7 +141,6 @@ index 97fa336..f55697e 100644 + VALUE av; + eargp->invoke.cmd.command_name = rb_str_new_cstr(exe); + eargp->invoke.cmd.command_abspath = rb_str_new_cstr(exe); -+ if (p < ep) p += strlen(p) + 1; /* the plan's argv[0] is the exe */ + nbuf = hide_obj(rb_str_buf_new((ep - p) + 1)); + while (p < ep) { + rb_str_buf_cat(nbuf, p, strlen(p) + 1); diff --git a/patches/3.4/process_c_tebako_spawn.patch b/patches/3.4/process_c_tebako_spawn.patch index ebc8a6d..b9ba07a 100644 --- a/patches/3.4/process_c_tebako_spawn.patch +++ b/patches/3.4/process_c_tebako_spawn.patch @@ -53,7 +53,7 @@ # exactly as before). A planned bare name (the app payload's # requires[].expose surface) retargets the exec pair to the store's # runtime exe, rebuilds argv_buf/argv_str from the plan (the plan's -# argv[0] is the exe; ruby re-prepends it at exec), and applies the +# argv[0] rides as the child's own argv[0] at exec), and applies the # plan's env ops (KEY=VALUE sets, bare KEY deletes) into # env_modification. A named error raises — the spawn NEVER falls through # to a host binary of the same name. The head runs BEFORE the darwin @@ -69,7 +69,7 @@ diff --git a/process.c b/process.c index 97fa336..f55697e 100644 --- a/process.c +++ b/process.c -@@ -2834,6 +2834,854 @@ rb_execarg_get(VALUE execarg_obj) +@@ -2834,6 +2834,852 @@ rb_execarg_get(VALUE execarg_obj) return eargp; } @@ -172,11 +172,10 @@ index 97fa336..f55697e 100644 +} + +/* A planned spawn (spec 30 §2): the plan's exe becomes the exec pair; -+ argv_buf takes the plan's tokens AFTER argv[0] (ruby re-prepends the -+ program at exec from command_abspath — and the plan's argv[0] IS that -+ exe); argv_str's pointer vector is rebuilt into the new buffer exactly -+ as rb_exec_fillarg built it (the vector points INTO the buffer's -+ storage, so a new buffer means a rebuilt vector). */ ++ argv_buf takes the plan's FULL argv, argv[0] included — the exec file ++ comes from command_abspath while the child's argv[0] is argv_buf's ++ first token (rb_exec_fillarg's exact layout); argv_str's pointers ++ point INTO the buffer, so a new buffer means a rebuilt vector. */ +static void +tfs_spawn_plan_apply(struct rb_execarg *eargp, const char *exe, + const char *argv, size_t argv_len, @@ -189,7 +188,6 @@ index 97fa336..f55697e 100644 + VALUE av; + eargp->invoke.cmd.command_name = rb_str_new_cstr(exe); + eargp->invoke.cmd.command_abspath = rb_str_new_cstr(exe); -+ if (p < ep) p += strlen(p) + 1; /* the plan's argv[0] is the exe */ + nbuf = hide_obj(rb_str_buf_new((ep - p) + 1)); + while (p < ep) { + rb_str_buf_cat(nbuf, p, strlen(p) + 1); diff --git a/patches/3.4/process_c_tebako_spawn_msys.patch b/patches/3.4/process_c_tebako_spawn_msys.patch index 34e01ef..10ef635 100644 --- a/patches/3.4/process_c_tebako_spawn_msys.patch +++ b/patches/3.4/process_c_tebako_spawn_msys.patch @@ -22,7 +22,7 @@ diff --git a/process.c b/process.c index 97fa336..f55697e 100644 --- a/process.c +++ b/process.c -@@ -2834,6 +2834,854 @@ rb_execarg_get(VALUE execarg_obj) +@@ -2834,6 +2834,852 @@ rb_execarg_get(VALUE execarg_obj) return eargp; } @@ -125,11 +125,10 @@ index 97fa336..f55697e 100644 +} + +/* A planned spawn (spec 30 §2): the plan's exe becomes the exec pair; -+ argv_buf takes the plan's tokens AFTER argv[0] (ruby re-prepends the -+ program at exec from command_abspath — and the plan's argv[0] IS that -+ exe); argv_str's pointer vector is rebuilt into the new buffer exactly -+ as rb_exec_fillarg built it (the vector points INTO the buffer's -+ storage, so a new buffer means a rebuilt vector). */ ++ argv_buf takes the plan's FULL argv, argv[0] included — the exec file ++ comes from command_abspath while the child's argv[0] is argv_buf's ++ first token (rb_exec_fillarg's exact layout); argv_str's pointers ++ point INTO the buffer, so a new buffer means a rebuilt vector. */ +static void +tfs_spawn_plan_apply(struct rb_execarg *eargp, const char *exe, + const char *argv, size_t argv_len, @@ -142,7 +141,6 @@ index 97fa336..f55697e 100644 + VALUE av; + eargp->invoke.cmd.command_name = rb_str_new_cstr(exe); + eargp->invoke.cmd.command_abspath = rb_str_new_cstr(exe); -+ if (p < ep) p += strlen(p) + 1; /* the plan's argv[0] is the exe */ + nbuf = hide_obj(rb_str_buf_new((ep - p) + 1)); + while (p < ep) { + rb_str_buf_cat(nbuf, p, strlen(p) + 1); diff --git a/patches/4.0/process_c_tebako_spawn.patch b/patches/4.0/process_c_tebako_spawn.patch index 578571e..e1362a9 100644 --- a/patches/4.0/process_c_tebako_spawn.patch +++ b/patches/4.0/process_c_tebako_spawn.patch @@ -59,7 +59,7 @@ # exactly as before). A planned bare name (the app payload's # requires[].expose surface) retargets the exec pair to the store's # runtime exe, rebuilds argv_buf/argv_str from the plan (the plan's -# argv[0] is the exe; ruby re-prepends it at exec), and applies the +# argv[0] rides as the child's own argv[0] at exec), and applies the # plan's env ops (KEY=VALUE sets, bare KEY deletes) into # env_modification. A named error raises — the spawn NEVER falls through # to a host binary of the same name. The head runs BEFORE the darwin @@ -75,7 +75,7 @@ diff --git a/process.c b/process.c index 97fa336..f55697e 100644 --- a/process.c +++ b/process.c -@@ -2834,6 +2834,858 @@ rb_execarg_get(VALUE execarg_obj) +@@ -2834,6 +2834,856 @@ rb_execarg_get(VALUE execarg_obj) return eargp; } @@ -178,11 +178,10 @@ index 97fa336..f55697e 100644 +} + +/* A planned spawn (spec 30 §2): the plan's exe becomes the exec pair; -+ argv_buf takes the plan's tokens AFTER argv[0] (ruby re-prepends the -+ program at exec from command_abspath — and the plan's argv[0] IS that -+ exe); argv_str's pointer vector is rebuilt into the new buffer exactly -+ as rb_exec_fillarg built it (the vector points INTO the buffer's -+ storage, so a new buffer means a rebuilt vector). */ ++ argv_buf takes the plan's FULL argv, argv[0] included — the exec file ++ comes from command_abspath while the child's argv[0] is argv_buf's ++ first token (rb_exec_fillarg's exact layout); argv_str's pointers ++ point INTO the buffer, so a new buffer means a rebuilt vector. */ +static void +tfs_spawn_plan_apply(struct rb_execarg *eargp, const char *exe, + const char *argv, size_t argv_len, @@ -195,7 +194,6 @@ index 97fa336..f55697e 100644 + VALUE av; + eargp->invoke.cmd.command_name = rb_str_new_cstr(exe); + eargp->invoke.cmd.command_abspath = rb_str_new_cstr(exe); -+ if (p < ep) p += strlen(p) + 1; /* the plan's argv[0] is the exe */ + nbuf = hide_obj(rb_str_buf_new((ep - p) + 1)); + while (p < ep) { + rb_str_buf_cat(nbuf, p, strlen(p) + 1);