How to properly set buffer and bufsize with getpwuid_r()?

Viewed 809

Background Info

I am trying to get the string of a user's username, with the only info provided about that user being their uid number. I have the uid as a result of a preceding call to fstat (and the uid is stored in a struct stat).

I need to get the username in a thread-safe manner, and so I am trying to use getpwuid_r(). According to the getpwuid (3) man page:

int getpwuid_r(uid_t uid, struct passwd *pwd, char *buffer,
       size_t bufsize, struct passwd **result);

The getpwuid_r() function shall update the passwd structure pointed to by pwd and store a pointer to that structure at the location pointed to by result. The structure shall contain an entry from the user database with a matching uid. Storage referenced by the structure is allocated from the memory provided with the buffer parameter, which is bufsize bytes in size. A call to sysconf(_SC_GETPW_R_SIZE_MAX) returns either −1 without changing errno or an initial value suggested for the size of this buffer. A null pointer shall be returned at the location pointed to by result on error or if the requested entry is not found.

If successful, the getpwuid_r() function shall return zero; otherwise, an error number shall be returned to indicate the error.



Problem Statement

Upon reading the man page example below, I am confused as to why they need to iterate, while increasing the size of the buffer, until the buffer can hold its information.

I am under the presumption that the buffer holds the struct passwd pwd - considering this, why can't we just set buffer = (void *) malloc(getsize(struct passwd)) and bufsize = sizeof(struct passwd)?

long int initlen = sysconf(_SC_GETPW_R_SIZE_MAX);
size_t len;
if (initlen == −1)
    /* Default initial length. */
    len = 1024;
else
    len = (size_t) initlen;
struct passwd result;
struct passwd *resultp;
char *buffer = malloc(len);
if (buffer == NULL)
    ...handle error...
int e;
while ((e = getpwuid_r(42, &result, buffer, len, &resultp)) == ERANGE)
{
    size_t newlen = 2 * len;
    if (newlen < len)
        ...handle error...
    len = newlen;
    char *newbuffer = realloc(buffer, len);
    if (newbuffer == NULL)
        ...handle error...
    buffer = newbuffer;
}
if (e != 0)
    ...handle error...
free (buffer);

Is there something I'm not understanding about how this function sets the data within pwd? Perhaps I don't fully understand how the struct passwd we are setting is related to the buffer space.

1 Answers

The passwd struct is defined by the standard to contain at least these members:

char    *pw_name   // User's login name. 
uid_t    pw_uid    // Numerical user ID. 
gid_t    pw_gid    // Numerical group ID. 
char    *pw_dir    // Initial working directory. 
char    *pw_shell  // Program to use as shell. 

Note the three char * members; they point to storage that lies elsewhere, outside of the struct. Many implementations will have two more char * members: pw_passwd and pw_gecos.

The difference between getpwuid and getpwuid_r is that the former may use a static buffer to store the name, passwd, dir, gecos, and shell strings1 - as well as the passwd struct itself - while the latter, since it's reentrant, requires the user to supply one buffer to hold struct passwd and another buffer to hold the character strings.

In practice, the two functions share a lot of common code.

I am confused as to why they need to iterate, while increasing the size of the buffer, until the buffer can hold its information.

If the call to sysconf(_SC_GETPW_R_SIZE_MAX) fails, you just have to guess how big the buffer for the character strings should be, and keep increasing its size until it's big enough.

1 In V7, when all the info was in /etc/passwd, this static buffer was just a copy of the appropriate line of /etc/passwd with a NUL inserted at the end of each of the five string fields.

Related