Skip to content

Commit 2a3813c

Browse files
authored
$prog.ape support (#977)
* ape loader: $prog.ape + login shell support If the ape loader is invoked with `$0 = $prog.ape`, then it searches for a `$prog` in the same directory as it and loads that. In particular, the loader searches the `PATH` for an executable named `$prog.ape`, then for an executable named `$prog` in the same directory. If the former but not the latter is found, the search terminates with an error. It also handles the special case of getting started as `-$SHELL`, which getty uses to indicate that the shell is a login shell. The path is not searched in this case, and the program location is read straight out of the `SHELL` variable. It is now possible to have `/usr/local/bin/zsh.ape` act as a login shell for a `/usr/local/bin/zsh` αpε, insofar as the program will get started with the 'correct' args. Unfortunately, many things break if `$0` is not the actual full path of the executable being run; for example, backspace does not update the display properly. To work around the brokenness introduced by not having `$0` be the full path of the binary, we cut the leading `-` out of `argv[0]` if present. This gets the loader's behavior with `$prog.ape` up to par, but doesn't tell login shells that they are login shells. So we introduce a hack to accomplish that: if ape is run as `-$prog.ape` and the shell is `$prog`, the binary that is loaded has a `-l` flag put into its first argument. As of this commit, αpε binaries can be used as login shells on OSX. * if islogin, execfn = shell Prior to this, execfn was not being properly set for login shells that did not receive `$_`, which was the case for iTerm2 on Mac. There were no observable consequences of this, but fixing it seems good anyway. * Fix auxv location calculation In the non-login-shell case, it was leaving a word of uninitialized memory at `envp[i] + 1`. This reuses the previous calculation based on `envp`.
1 parent 8dd3480 commit 2a3813c

File tree

1 file changed

+71
-19
lines changed

1 file changed

+71
-19
lines changed

ape/ape-m1.c

Lines changed: 71 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,7 @@ union ElfPhdrBuf {
194194

195195
struct PathSearcher {
196196
int literally;
197+
int indirect;
197198
unsigned long namelen;
198199
const char *name;
199200
const char *syspath;
@@ -220,16 +221,8 @@ static int StrCmp(const char *l, const char *r) {
220221
}
221222

222223
static const char *BaseName(const char *s) {
223-
int c;
224-
const char *b = "";
225-
if (s) {
226-
while ((c = *s++)) {
227-
if (c == '/') {
228-
b = s;
229-
}
230-
}
231-
}
232-
return b;
224+
const char *b = strrchr(s, '/');
225+
return b ? b + 1 : s;
233226
}
234227

235228
static char *GetEnv(char **p, const char *s) {
@@ -297,6 +290,14 @@ static long Print(int fd, const char *s, ...) {
297290
return write(fd, b, n);
298291
}
299292

293+
static int GetIndirectOffset(const char *arg0) {
294+
char *tail = strrchr(arg0, '.');
295+
if (tail && !StrCmp(tail + 1, "ape")) {
296+
return tail - arg0;
297+
}
298+
return 0;
299+
}
300+
300301
static void Perror(const char *thing, long rc, const char *reason) {
301302
char ibuf[21];
302303
ibuf[0] = 0;
@@ -316,7 +317,17 @@ static char AccessCommand(struct PathSearcher *ps, unsigned long pathlen) {
316317
if (pathlen && ps->path[pathlen - 1] != '/') ps->path[pathlen++] = '/';
317318
memmove(ps->path + pathlen, ps->name, ps->namelen);
318319
ps->path[pathlen + ps->namelen] = 0;
319-
return !access(ps->path, X_OK);
320+
if (!access(ps->path, X_OK)) {
321+
if (ps->indirect) {
322+
ps->namelen -= 4;
323+
ps->path[pathlen + ps->namelen] = 0;
324+
if (access(ps->path, X_OK) < 0) {
325+
Pexit(ps->path, -errno, "access(X_OK)");
326+
}
327+
}
328+
return 1;
329+
}
330+
return 0;
320331
}
321332

322333
static char SearchPath(struct PathSearcher *ps) {
@@ -870,11 +881,11 @@ static const char *TryElf(struct ApeLoader *M, union ElfEhdrBuf *ebuf,
870881

871882
int main(int argc, char **argv, char **envp) {
872883
unsigned i;
873-
int c, n, fd, rc;
874884
struct ApeLoader *M;
875885
long *sp, *sp2, *auxv;
876886
union ElfEhdrBuf *ebuf;
877-
char *p, *pe, *exe, *prog, *execfn;
887+
int c, islogin, n, fd, rc;
888+
char *p, *pe, *dash_l, *exe, *prog, *shell, *execfn;
878889

879890
/* allocate loader memory in program's arg block */
880891
n = sizeof(struct ApeLoader);
@@ -936,8 +947,21 @@ int main(int argc, char **argv, char **envp) {
936947
M->lib.dlclose = dlclose;
937948
M->lib.dlerror = dlerror;
938949

950+
/* there is a common convention of shells being told that they
951+
are login shells via the OS prepending a - to their argv[0].
952+
the APE system doesn't like it when argv[0] is not the full
953+
path of the binary. to rectify this, the loader puts a "-l"
954+
flag in argv[1] and ignores the dash. */
955+
if ((islogin = argc > 0 && *argv[0] == '-' && (shell = GetEnv(envp, "SHELL"))
956+
&& !StrCmp(argv[0] + 1, BaseName(shell)))) {
957+
execfn = shell;
958+
dash_l = __builtin_alloca(3);
959+
memmove(dash_l, "-l", 3);
960+
} else {
961+
execfn = argc > 0 ? argv[0] : 0;
962+
}
963+
939964
/* getenv("_") is close enough to at_execfn */
940-
execfn = argc > 0 ? argv[0] : 0;
941965
for (i = 0; envp[i]; ++i) {
942966
if (envp[i][0] == '_' && envp[i][1] == '=') {
943967
execfn = envp[i] + 2;
@@ -951,20 +975,47 @@ int main(int argc, char **argv, char **envp) {
951975
/* create new bottom of stack for spawned program
952976
system v abi aligns this on a 16-byte boundary
953977
grows down the alloc by poking the guard pages */
954-
n = (auxv - sp + AUXV_WORDS + 1) * sizeof(long);
978+
n = (auxv - sp + islogin + AUXV_WORDS + 1) * sizeof(long);
955979
sp2 = (long *)__builtin_alloca(n);
956980
if ((long)sp2 & 15) ++sp2;
957981
for (; n > 0; n -= pagesz) {
958982
((char *)sp2)[n - 1] = 0;
959983
}
960-
memmove(sp2, sp, (auxv - sp) * sizeof(long));
984+
if (islogin) {
985+
memmove(sp2, sp, 2 * sizeof(long));
986+
*((char **)sp2 + 2) = dash_l;
987+
memmove(sp2 + 3, sp + 2, (auxv - sp - 2) * sizeof(long));
988+
++argc;
989+
sp2[0] = argc;
990+
} else {
991+
memmove(sp2, sp, (auxv - sp) * sizeof(long));
992+
}
993+
961994
argv = (char **)(sp2 + 1);
962995
envp = (char **)(sp2 + 1 + argc + 1);
963-
auxv = sp2 + (auxv - sp);
996+
auxv = (long *)(envp + i + 1);
964997
sp = sp2;
965998

966999
/* interpret command line arguments */
967-
if ((M->ps.literally = argc >= 3 && !StrCmp(argv[1], "-"))) {
1000+
if ((M->ps.indirect = argc > 0 ? GetIndirectOffset(argv[0]) : 0)) {
1001+
M->ps.literally = 0;
1002+
/* if argv[0] is $prog.ape, then we strip off the .ape and run
1003+
$prog. This allows you to use symlinks to trick the OS when
1004+
a native executable is required. For example, let's say you
1005+
want to use the APE binary /opt/cosmos/bin/bash as a system
1006+
shell in /etc/shells, or perhaps in shebang lines like e.g.
1007+
`#!/opt/cosmos/bin/bash`. That won't work with APE normally,
1008+
but it will if you say:
1009+
ln -sf /usr/local/bin/ape /opt/cosmos/bin/bash.ape
1010+
and then use #!/opt/cosmos/bin/bash.ape instead. */
1011+
prog = (char *)sp[1];
1012+
argc = sp[0];
1013+
argv = (char **)(sp + 1);
1014+
if (islogin) {
1015+
++argv[0];
1016+
prog = shell;
1017+
}
1018+
} else if ((M->ps.literally = argc >= 3 && !StrCmp(argv[1], "-"))) {
9681019
/* if the first argument is a hyphen then we give the user the
9691020
power to change argv[0] or omit it entirely. most operating
9701021
systems don't permit the omission of argv[0] but we do, b/c
@@ -975,6 +1026,7 @@ int main(int argc, char **argv, char **envp) {
9751026
} else if (argc < 2) {
9761027
Emit("usage: ape PROG [ARGV1,ARGV2,...]\n"
9771028
" ape - PROG [ARGV0,ARGV1,...]\n"
1029+
" ($0 = PROG.ape) [ARGV1,ARGV2,...]\n"
9781030
"actually portable executable loader silicon 1.9\n"
9791031
"copyright 2023 justine alexandra roberts tunney\n"
9801032
"https://justine.lol/ape.html\n");
@@ -1006,7 +1058,7 @@ int main(int argc, char **argv, char **envp) {
10061058

10071059
/* resolve argv[0] to reflect path search */
10081060
if (argc > 0 && ((*prog != '/' && *exe == '/' && !StrCmp(prog, argv[0])) ||
1009-
!StrCmp(BaseName(prog), argv[0]))) {
1061+
M->ps.indirect || !StrCmp(BaseName(prog), argv[0]))) {
10101062
argv[0] = exe;
10111063
}
10121064

0 commit comments

Comments
 (0)