`$0` can be full path in #IP1

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.

OK, so turns out that my program actually passes the test now.

It was failing earlier when I was providing the expanded path (seemed logical to me since I already have mechanism to do that as part of type implementation), but in the meantime I have changed it to keep it.

But even after that I was not able to observe the expected $0 form locally; apparently the test environment is different enough.

So maybe the point of this ticket remains; at least it can be hard to debug, even when I used POSIX-compliant shell such as dash, I always end up with $0 being the full path anyway…

1 Like

This Reddit thread seems relevant: Exactly how reliable is argv[0] at being the executable’s path, and if it’s not reliable, what can I use instead?.

BTW, also Python’s sys.argv is affected, so it’s not just bash and dash…

Hi @kelduben-cc, thanks for highlighting the issue! We’ll investigate this further and keep you updated on any changes.

For context, the test case you encountered was added in this stage to ensure consistency in a later stage. Specifically, when running [/full-path/]cat nonexistent on Debian, the error message changes based on $0:

$ cat nonexistent
cat: nonexistent: No such file or directory

$ /usr/bin/cat nonexistent
/usr/bin/cat: nonexistent: No such file or directory

The intention was to ensure $0 would be handled consistently in this stage to prevent potential issues in later stages.

2 Likes

Closing this thread due to inactivity. If you still need assistance, feel free to reopen or start a new discussion!

1 Like

This topic was automatically closed 5 days after the last reply. New replies are no longer allowed.