pwd: Clean up and adopt POSIX semantics

According to POSIX, the default should be -L.  Based on code history,
whoever first wrote BSD pwd(1) could not figure out how to implement
-L and therefore made -P the default (and only) option. Support for -L
was later added, but the default was never changed.

Clean up the code, make -L the default, and rewrite getcwd_logical() to
reject paths that contain dot or dot-dot, as required by POSIX.

MFC after:	1 week
Reviewed by:	olce
Differential Revision:	https://reviews.freebsd.org/D55146
This commit is contained in:
Dag-Erling Smørgrav committed 2026-02-10 15:29:04 +01:00
1 parent 47fb51847f
commit 2df923c5d2
2 files changed
+54 -49

No files matched your search

+2 -2
View File
@@ -29,7 +29,7 @@
.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
.\" SUCH DAMAGE.
.\"
.Dd May 22, 2023
.Dd Februrary 6, 2026
.Dt PWD 1
.Os
.Sh NAME
@@ -60,7 +60,7 @@ Display the physical current working directory (all symbolic links resolved).
.El
.Pp
If no options are specified, the
.Fl P
.Fl L
option is assumed.
.Sh ENVIRONMENT
Environment variables used by
+52 -47
View File
@@ -3,6 +3,7 @@
*
* Copyright (c) 1991, 1993, 1994
* The Regents of the University of California. All rights reserved.
* Copyright (c) 2026 Dag-Erling Smørgrav
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
@@ -31,40 +32,74 @@
#include <sys/param.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <err.h>
#include <errno.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
static char *getcwd_logical(void);
void usage(void);
static char *
getcwd_logical(void)
{
struct stat log, phy;
char *pwd, *p, *q;
/* $PWD is set and absolute */
if ((pwd = getenv("PWD")) == NULL || *pwd != '/')
return (NULL);
/* $PWD does not contain /./ or /../ */
for (p = pwd; *p; p = q) {
for (q = ++p; *q && *q != '/'; q++)
/* nothing */;
if ((*p == '.' && q == ++p) ||
(*p == '.' && q == ++p))
return (NULL);
}
/* $PWD refers to the current directory */
if (stat(pwd, &log) != 0 || stat(".", &phy) != 0 ||
log.st_dev != phy.st_dev || log.st_ino != phy.st_ino)
return (NULL);
return (pwd);
}
static char *
getcwd_physical(void)
{
static char pwd[MAXPATHLEN];
return (getcwd(pwd, sizeof(pwd)));
}
static void
usage(void)
{
(void)fprintf(stderr, "usage: pwd [-L | -P]\n");
exit(1);
}
int
main(int argc, char *argv[])
{
int physical;
int ch;
char *p;
char *pwd;
int opt;
bool logical;
physical = 1;
while ((ch = getopt(argc, argv, "LP")) != -1)
switch (ch) {
logical = true;
while ((opt = getopt(argc, argv, "LP")) != -1) {
switch (opt) {
case 'L':
physical = 0;
logical = true;
break;
case 'P':
physical = 1;
logical = false;
break;
case '?':
default:
usage();
}
}
argc -= optind;
argv += optind;
if (argc != 0)
usage();
@@ -72,40 +107,10 @@ main(int argc, char *argv[])
* If we're trying to find the logical current directory and that
* fails, behave as if -P was specified.
*/
if ((!physical && (p = getcwd_logical()) != NULL) ||
(p = getcwd(NULL, 0)) != NULL)
printf("%s\n", p);
if ((logical && (pwd = getcwd_logical()) != NULL) ||
(pwd = getcwd_physical()) != NULL)
printf("%s\n", pwd);
else
err(1, ".");
exit(0);
}
void __dead2
usage(void)
{
(void)fprintf(stderr, "usage: pwd [-L | -P]\n");
exit(1);
}
static char *
getcwd_logical(void)
{
struct stat lg, phy;
char *pwd;
/*
* Check that $PWD is an absolute logical pathname referring to
* the current working directory.
*/
if ((pwd = getenv("PWD")) != NULL && *pwd == '/') {
if (stat(pwd, &lg) == -1 || stat(".", &phy) == -1)
return (NULL);
if (lg.st_dev == phy.st_dev && lg.st_ino == phy.st_ino)
return (pwd);
}
errno = ENOENT;
return (NULL);
}