Weird behavior of getpwnam

318 views Asked by At
#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.

3

There are 3 answers

3
dbush On BEST ANSWER

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.

0
James Risner On

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.

5
Jonathan Leffler On

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!