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.