Date: 2026-02-06

Daily Source Reading: pwd

pwd

Today we are going to grab a small one, pwd. Not enough time today to go through a long file, so we pick an easy one to not break our streak so far.

OpenBSD

As usual, we pledge to limit any potential blast radius and then parse some options. pwd only has 2 to begin with, so this is quick.

if (lFlag)
    p = getcwd_logical();
else
    p = NULL;

By default, pwd uses physical pathnames. We check if -L is set and, if so, attempt to retrieve the current logical directory.

Logical directory here means the path without symlinks resolved. Whereas physical means that symlinks are resolved first.

rootnode /tmp/pwd $ ll
total 4
drwxr-xr-x  2 rootnode  wheel   512B Feb  6 13:33 a
lrwxr-xr-x  1 rootnode  wheel     1B Feb  6 13:33 b -> a
rootnode .../pwd/b $ /bin/pwd -L
/tmp/pwd/b
rootnode .../pwd/b $ /bin/pwd -P
/tmp/pwd/a

(Had to use /bin/pwd here so it doesn't use the built-in version from the shell)

If retrieving the logical path fails (returning NULL), or if physical was chosen (setting p to NULL) then we fall back to the getcwd system call to retrieve the physical path.

Now, time to look at getcwd_logical.

static char *
getcwd_logical(void)
{
    char *pwd, *p;
    struct stat s_pwd, s_dot;

    /* Check $PWD -- if it's right, it's fast. */
    pwd = getenv("PWD");
    if (pwd == NULL)
        return NULL;
    if (pwd[0] != '/')
        return NULL;

If $PWD is empty or doesn't start with a /, we can early exit as it doesn't form a logical path.

/* check for . or .. components, including trailing ones */
for (p = pwd; *p != '\0'; p++)
    if (p[0] == '/' && p[1] == '.') {
        if (p[2] == '.')
            p++;
        if (p[2] == '\0' || p[2] == '/')
            return NULL;
    }

Next is a quick loop to check the path for . and .. as this is not allowed according to POSIX.

    if (stat(pwd, &s_pwd) == -1 || stat(".", &s_dot) == -1)
        return NULL;
    if (s_pwd.st_dev != s_dot.st_dev || s_pwd.st_ino != s_dot.st_ino)
        return NULL;
    return pwd;
}

Lastly, we get the file stats on the pwd directory we just retrieved and the current directoy . we are in. Should either fail, we exit. If it works, we compare both if they have the same device or filystem ID and the same inode. If they don't match, we exit.

Finally we end up with a checked and verified logical path.

FreeBSD

Overall the structure is the same. It is however lacking the for loop that checks the path for any . or .. elements.

Conclusion

This time, both implementations are very similar, except for OpenBSD checking a few more things and restricting capabilities via pledge. Both versions however somehow use -P as the default if no arguments are supplied, but the POSIX entry defines "If neither -L nor -P is specified, the pwd utility shall behave as if -L had been specified.".

The implementation of the getcwd syscall was omitted on purpose. For now I will mostly sift through the userland programs first and start diving into kernel specifics later on in the series.

Next up might either take more than a day or I am going to split it into parts. Up next: ed.