2
#include <pwd.h>
#include <stdio.h>
#include <stdlib.h>

int main(void)
{
        printf("%s %s\n", getpwnam("steve")->pw_name, getpwnam("root")->pw_name);
        printf("%d %d\n", getpwnam("steve")->pw_uid, getpwnam("root")->pw_uid);

        return EXIT_SUCCESS;
}
$ gcc main.c && ./a.out
steve steve
1000 0

In line 8, we try to print the user names of steve and root, but it prints steve twice. In line 9, we try to print the UIDs of steve and root, and it successfully prints them.

I wanna ascertain the root cause of that bizarre behavior in line 8.

I know the pointer returned by getpwnam points to a statically allocated memory, and the memory pointed by fields like pw_name/pw_passwd/pw_gecos/pw_dir/pw_shell are also static, which means these values can be overwritten by subsequent calls. But still confused about this strange result.

This is exercise 8-1 of The Linux Programming Interface. Add this so that someone like me could find this through the search engine in the future:). And the question in the book is wrong, go here to see the revised version.

Steve Lau
  • 658
  • 7
  • 13

3 Answers3

4

The code calls getpwnam() in succession returning a pointer to the same address and passing the same pointer to printf() twice. The order the compiler decides to make the calls will determine whether it shows “steve” or “root”.

Allocate two buffer spaces and use one for each in the call to printf() by calling getpwnam_r() instead.

James Risner
  • 5,451
  • 11
  • 25
  • 47
3

The result from getpwnam() may be overwritten by another call to getpwnam() or getpwuid() or getpwent(). Your code is demonstrating that.

See the POSIX specifications of:

You have no control over the order of evaluation of the calls. If you saved the pointers returned, you'd probably get different results printed.

POSIX also says:

The application shall not modify the structure to which the return value points, nor any storage areas pointed to by pointers within the structure. The returned pointer, and pointers within the structure, might be invalidated or the structure or the storage areas might be overwritten by a subsequent call to getpwent(), getpwnam(), or getpwuid(). The returned pointer, and pointers within the structure, might also be invalidated if the calling thread is terminated.

You should treat the return values as if they were const-qualified, in other words.

Note that the code in the library functions need not overwrite previous data. For example, on macOS Big Sur 11.6.8, the following code, compiled with -DUSER1=\"daemon\" as one of the compiler options, yields the result:

daemon root
1 0
U1A = 0x7fecfa405fd0, U2A = 0x7fecfa405c60
U1B = 0x7fecfa405fd0, U2B = 0x7fecfa405c60

Modified code:

/* SO 7345-2740 */
#include <pwd.h>
#include <stdio.h>
#include <stdlib.h>

#ifndef USER1
#define USER1 "steve"
#endif
#ifndef USER2
#define USER2 "root"
#endif

int main(void)
{
        const struct passwd *user1a = getpwnam(USER1);
        const struct passwd *user2a = getpwnam(USER2);
        const char *user1_name = user1a->pw_name; 
        const char *user2_name = user2a->pw_name; 
        printf("%s %s\n", user1_name, user2_name);
        const struct passwd *user1b = getpwnam(USER1);
        const struct passwd *user2b = getpwnam(USER2);
        int user1_uid = user1b->pw_uid;
        int user2_uid = user2b->pw_uid;
        printf("%d %d\n", user1_uid, user2_uid);
        printf("U1A = %p, U2A = %p\n", (void *)user1a, (void *)user2a);
        printf("U1B = %p, U2B = %p\n", (void *)user1b, (void *)user2b);

        return EXIT_SUCCESS;
}

It is moderately likely that the library functions read the whole file into memory and then pass pointers to relevant sections of that memory. Certainly, in this example, the pointers to the entry for user daemon and user root are stable.

YMWV — Your Mileage Will Vary!

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
  • Thanks for your answer:) "You have no control over the order of evaluation of the calls", I think this is right now. But why does line 9 print the correct result, is this just a lucky result – Steve Lau Aug 23 '22 at 03:41
  • Lucky? Unlucky? There's a large chunk of 'You Get What You Get'. See the example code I've shown, and try running it on your machine (you don't need a `-DUSERn=\"name\"` option to use `steve` and `root`). Consider looking at the assembler generated for your code. Note that different libraries will produce different results. I consistently got `daemon`, `root`, `1`, `0` from the simpler versions of what I show above. You didn't — that's evidence of the way that theses functions can differ between implementations. – Jonathan Leffler Aug 23 '22 at 03:45
  • It seems the reason why line 9 prints the correct result is `pw_uid` is a numeric type, which is passed by value – Steve Lau Aug 23 '22 at 03:51
  • Yes, sorta. That is a factor — and the compiler processes the `pw_uid` values in such a way that it stores the integer result in the call frame for `printf()` before the next call to `getpwnam()`. If it had stored the pointer from call 1, then the pointer from call2, and only then dereferenced the two results, you would have a different output — which would depend on whether the calls to `getpwnam()` were made LR or RL. – Jonathan Leffler Aug 23 '22 at 03:55
  • Indeed, we get a duplicate inside the stack frame of `printf (or vsprintf)` – Steve Lau Aug 23 '22 at 03:57
3

The getpwnam function can return a pointer to static data, so each time it's called it returns the same pointer value. And because you're calling this function multiple times as an argument to printf, you'll only see the result of whichever one of those function calls happens last.

The key point here is that the evaluation order of the arguments to a function are unsequenced, which means there's no guarantee whether getpwnam("steve") happens first or getpwnam("root") happens first.

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
dbush
  • 205,898
  • 23
  • 218
  • 273
  • Why does line 9 print the UIDs successfully, a lucky result? Thanks! – Steve Lau Aug 23 '22 at 03:38
  • @SteveLau Probably because in this case each argument evaluates to a non-pointer value, and the value of one argument is evaluated before the value of the other. In the case of the names, both arguments evaluate to the same pointer value. – dbush Aug 23 '22 at 03:51
  • 1
    I got it, `pw_uid` is a numeric type, which is passed by value. Thus we get a duplicate inside the stack frame of `printf (or vsprintf)`. – Steve Lau Aug 23 '22 at 03:55