3

From the err/warn manpage:

The  err()  and  warn()  family of functions display a formatted error
message on the standard error output.  In all cases, the last component
of the program name, a colon character, and a space are output.  If the
fmt argument is not NULL, the printf(3)-like formatted error message is
output.

If I make this call: warn("message"); it will output something like this:

a.out: message: (output of strerror here)

How do the warn/err functions find the name of the program (in this case, a.out) without seemingly having any access to argv at all? Does it have anything to do with the fact that they are BSD extensions?

DevSolar
  • 67,862
  • 21
  • 134
  • 209
  • 1
    You have tagged this with C, but `err` and `warn` are not standard C functions. You should identify the specific C implementation you are using and, if possible, provide a link to the documentation for its `err` and `warn` functions (possibly [this](https://linux.die.net/man/3/warn)). When asking questions, always identify the specific things you are asking about. Even if you do not know which standard they are from, you can say they are on your system, and your system is Linux version xxx or Windows, and so on. – Eric Postpischil May 28 '22 at 21:02
  • @EricPostpischil The `err`/`warn` functions don't conform to any standard, they are BSD extensions, but work on any system with `libbsd` or a working `err.h` installed. Since this applies to nearly all Linux systems, I think it's acceptable to tag the question as C. (Maybe "BSD extensions" would be a better tag?) –  May 28 '22 at 21:08
  • I did not say you should not tag the question as C; I meant that, when you are asking about things that are not specified by the C standard, it is **insufficient** to tag the question with C. You must also specify where the functions are coming from, which can be done either via tags or via text in the post. Posting a question tagged C asking about `err` without saying where `err` comes from is like posting a question tagged Book asking about the character Rebecca without saying which book it is. – Eric Postpischil May 28 '22 at 21:13
  • 1
    @EricPostpischil At the end of my question, I did state that the functions are BSD extensions. Maybe I should've put that at the top or something. I wasn't sure if there was a BSD extensions tag, which is why I didn't use one. –  May 28 '22 at 21:16
  • The functions have somehow access to the command line arguments. If you want to find out, get the relevant source code which is certainly available somewhere. – Jabberwocky May 28 '22 at 21:20
  • The first hit googling naively this: _bsd warn function source code_ – Jabberwocky May 28 '22 at 21:21

3 Answers3

2

The err/warn functions prepend the basename of the program name. According to the answers to this SO post, there are a few ways to get the program name without access to argv.

One way is to call readlink on /proc/self/exe, then call basename on that. A simple program that demonstrates this:

#include <libgen.h>
#include <linux/limits.h>
#include <stdio.h>
#include <unistd.h>

char *
progname(void)
{
    char path[PATH_MAX];

    if (readlink("/proc/self/exe", path, PATH_MAX) == -1)
        return NULL;

    /* not sure if a basename-returned string should be
     * modified, maybe don't use this in production */
    return basename(path);
}

int
main(void)
{
    printf("%s: this is my fancy warn message!\n", progname());
    return 0;
}

You can also use the nonstandard variables __progname, which may not work depending on your compiler, and program_invocation_short_name, which is a GNU extension defined in errno.h.

  • yep, `__progname` works too, and is set by the glibc loader upon initializing your program, i.e. before `main` is even called. Good detective work on the /proc/self/exe, see my answer. – Marcus Müller May 28 '22 at 21:32
1

How do the warn/err functions find the name of the program (in this case, a.out) without seemingly having any access to argv at all? Does it have anything to do with the fact that they are BSD extensions?

Such things can easily be figured out using the strace utility, which records all system calls.

I wrote the highly complex program test.c:

#include <err.h>
int main() { warn("foo"); }

and gcc -o test -static test.c; strace ./test yields (the -static to avoid the noise from trying to load a lot of libraries):

execve("./test", ["./test"], 0x7fffcbb7fd60 /* 101 vars */) = 0
arch_prctl(0x3001 /* ARCH_??? */, 0x7ffd388d6540) = -1 EINVAL (Invalid argument)
brk(NULL)                               = 0x201e000
brk(0x201edc0)                          = 0x201edc0
arch_prctl(ARCH_SET_FS, 0x201e3c0)      = 0
set_tid_address(0x201e690)              = 55889
set_robust_list(0x201e6a0, 24)          = 0
uname({sysname="Linux", nodename="workhorse", ...}) = 0
prlimit64(0, RLIMIT_STACK, NULL, {rlim_cur=8192*1024, rlim_max=RLIM64_INFINITY}) = 0
readlink("/proc/self/exe", "/tmp/test", 4096) = 9
getrandom("\x43\xff\x90\x4b\xa8\x82\x38\xdd", 8, GRND_NONBLOCK) = 8
brk(0x203fdc0)                          = 0x203fdc0
brk(0x2040000)                          = 0x2040000
mprotect(0x4b6000, 16384, PROT_READ)    = 0
write(2, "test: ", 6)                   = 6
write(2, "foo", 3)                      = 3
write(2, ": Success\n", 10)             = 10
exit_group(0)                           = ?
+++ exited with 0 +++

And there you have it: you can just readlink /proc/self/exe to know what you're called.

Marcus Müller
  • 34,677
  • 4
  • 53
  • 94
  • 1
    That doesn't answer the question. – Jabberwocky May 28 '22 at 21:23
  • 2
    @Jabberwocky The question was: "How do the warn/err functions find the name of the program (in this case, a.out) without seemingly having any access to argv at all? ", and you might want to read my answer's last sentence. – Marcus Müller May 28 '22 at 21:25
  • I'm not familiar wit linux, and TBH I didn't get the meaning of the sentence either... – Jabberwocky May 28 '22 at 21:28
  • well, you didn't understand it but were sure it doesn't answer the question? Thank you for just asking what it means! – Marcus Müller May 28 '22 at 21:29
  • The name of the program is contained in what `readlink` returns on /proc/self/exe; hope that's better? If not, please don't hesitate to ask! – Marcus Müller May 28 '22 at 21:30
  • 1
    @Jabberwocky I would say it does answer the question –  May 28 '22 at 21:30
  • @Jabberwocky note that this answer addresses someone who seems to already have good basic grasp of Linux, so you not understanding it is kind of unfortunate, but maybe not too bad. – Marcus Müller May 28 '22 at 21:33
  • using `gcc -static` so you can `strace` without dynamic linking-related nonsense is a VERY good tip, thank you for the answer –  May 28 '22 at 21:37
  • @spitemim you're more than welcome! Took me years to notice :) – Marcus Müller May 28 '22 at 21:40
1

In pure standard C, there's no way to get the program name passed as argv[0] without getting it, directly or indirectly, from main. You can pass it as an argument to functions, or save it in a global variable.

But system functions also have the option of using system-specific methods. On open-source operating system, you can download the source code and see how it's done. For Unix-like systems, that's libc.

For example, on FreeBSD:

  • The warn and err functions call the internal system function _getprogname().
  • _getprogname() reads the global variable __progname.
  • __progname is set in handle_argv which is called from _start(). This code is not in libc, but in CSU, which is a separate library containing program startup code.
  • _start() is the program's entry point. It's defined in the architecture-specific crt1*.c. It's also the function that calls main, and it passes the same argv to both handle_argv() and main().
  • _start is the first C function called in the program. It's called from assembly code that reads the argv pointer from the stack.
  • The program arguments are copied into the program's address space by the kernel as part of the implementation of the execve system call.

Note that there are several concepts of “program name” and they aren't always equivalent. See Finding current executable's path without /proc/self/exe for a discussion of the topic.

Gilles 'SO- stop being evil'
  • 104,111
  • 38
  • 209
  • 254