In #IP1–"Run a program, is it really necessary to require the $0
to be the program name, instead of full path to the binary?
It appears that it’s not trivial (if even possible) to guarrantee that on Linux.
remote: [tester::#IP1] Running tests for Stage #IP1 (Run a program)
remote: [tester::#IP1] Running ./your_program.sh
remote: [your-program] $ custom_exe_7043 Maria
remote: [your-program] Program was passed 2 args (including program name).
remote: [your-program] Arg #0 (program name): /tmp/orange/grape/orange/custom_exe_7043
remote: [tester::#IP1] Output does not match expected value.
remote: [tester::#IP1] Expected: "Arg #0 (program name): custom_exe_7043"
remote: [tester::#IP1] Received: "Arg #0 (program name): /tmp/orange/grape/orange/custom_exe_7043"
remote: [your-program] Arg #1: Maria
remote: [your-program] Program Signature: 3723139110
remote: [your-program] $
remote: [tester::#IP1] Assertion failed.
remote: [tester::#IP1] Test failed (try setting 'debug: true' in your codecrafters.yml to see more details)
remote:
Here’s my research so far:
I’m doing the challenge in Zig on Linux (Debian 12), and it appears that execve
will still set $0 to the full path (its first argument)
Initially I thought it’s a zig problem but I’ve been able to check that passing slice such as “foo”, “bar”, “baz” to std.process.Child.init()
will result in Zig stdlib doing the PATH lookup and then calling execvpe()
with the original array, ie. the argv[0] is set to "foo"
, not /full/path/to/foo
.
Yet the foo script still ends up with $0
being the full path.
Indeed, strace confirms that Zig is not to blame:
4166104 execve("/usr/bin/ls", ["ls"], 0x7fdd00c53110 /* 80 vars */) = 0
So it looks like libc will replace it anyway, which kind of feels like bug in libc, but at least manpage does not strictly preculde this from happening, so it might be intentional.
From execve(2):
int execve(const char *pathname, char *const _Nullable argv[], char *const _Nullable envp[]);
…
argv is an array of pointers to strings passed to the new program as its command-line arguments. By convention, the first of these strings (i.e., argv[0]) should contain the filename associated with the file being executed. The argv array must be terminated by a NULL pointer. (Thus, in the new program, argv[argc] will be NULL.)
Anecdotal, but I’ve written a lot of Bash scripts under Linux, and I don’t recall the $0 to be ever set to something else than
- full path, if PATH was consulted)
- relative path, if the command was executed as relative path (ie. ./myscript or bin/myscript).
so it’s possible that this is a de-facto standard on Linux distros.
I’ve also tried to reproduce the behavior by using dash script placed into /usr/bin and invoked from bash or dash, and it seems to confirm my expectation:
root@nauron:~# cat /usr/bin/myargsr
#!/bin/dash
echo "0=$0"
echo "1=$1"
echo "2=$2"
root@nauron:~# myargsr foo bar
0=/usr/bin/myargsr
1=foo
2=bar
root@nauron:~# dash
# myargsr foo bar
0=/usr/bin/myargsr
1=foo
2=bar
#
By the way, I know there’s also a way to set “program name” using eg. exec -a foo myscript
but that does not affect $0
.