~sircmpwn/ctools

14e8930669e1663d9382db066cbe40bae2994079 — Sebastian 3 years ago 53a217a
Fix pwd to be conformant

Prior to this commit, the pwd implementation was flawed:

  - The -L and -P flags were ignored entirely; the program would always
    print the physical working directory (returned from getcwd).
  - The program relied on non-standard behavior, in which getcwd would
    allocate a buffer if the buf argument was NULL. This behavior is
    unspecified by POSIX.
  - In the event that an error occured, the program would fail without
    printing anything to stderr describing which error occured.

This commit fixes all of the above issues. pwd now defaults to printing
the logical working directory (the value of the PWD environment
variable), and falls back to printing the physical working directory
(from getcwd). The -L and -P flags now work as intended, and errors are
handled properly.

The program is still not fully POSIX compliant. From pwd(1p):

    -L    If the PWD environment variable contains an absolute pathname
          of the current directory and **the pathname does not contain
          any components that are dot or dot-dot**, pwd shall write this
          pathname to standard output ... Otherwise, the -L option shall
          behave as the -P option.

As of this commit, no checks are done to verify that PWD doesn't have
any . or .. components. As far as I'm aware, this is the only area in
which the tool is non-conformant.

Signed-off-by: Sebastian <sebastian@sebsite.pw>
2 files changed, 59 insertions(+), 15 deletions(-)

M src/pwd.c
M test/pwd
M src/pwd.c => src/pwd.c +56 -12
@@ 1,3 1,4 @@
#include <errno.h>
#include <getopt.h>
#include <limits.h>
#include <stdio.h>


@@ 7,34 8,77 @@
static void
usage(void)
{
	fprintf(stderr, "usage: pwd [-L|-P]");
	fputs("usage: pwd [-L|-P]\n", stderr);
}

int
main(int argc, char *argv[])
physical(void)
{
	size_t sz = PATH_MAX;
	char *buf = malloc(sz);
	if (!buf) {
		fputs("FATAL: out of memory\n", stderr);
		return 1;
	}

	for (;;) {
		if (getcwd(buf, sz) != NULL) {
			break;
		}
		switch (errno) {
		case ERANGE:
			sz *= 2;
			buf = realloc(buf, sz);
			if (!buf) {
				fputs("FATAL: out of memory\n", stderr);
				return 1;
			}
			continue;
		default:
			perror("getcwd");
			return 1;
		}
	}

	puts(buf);
	return 0;
}

int
logical(void)
{
	char *pwd = NULL;
	char *pwd = getenv("PWD");
	if (!pwd) {
		return physical();
	}
	/* TODO: if any components of pwd are . or .., return physical() */
	puts(pwd);
	return 0;
}

int
main(int argc, char *argv[])
{
	enum { PHYSICAL, LOGICAL } kind = LOGICAL;
	char opt;
	/* last option specified takes precedence */
	while ((opt = getopt(argc, argv, "LP")) != -1) {
		switch (opt) {
		case 'P':
			kind = PHYSICAL;
			break;
		case 'L':
			kind = LOGICAL;
			break;
		default:
			usage();
			return 1;
		}
	}
	
	pwd = getcwd(pwd, 0);
	
	if (pwd == NULL) {
		return 1;
	}

	printf("%s\n", pwd);
	
	return 0;
	if (kind == PHYSICAL) {
		return physical();
	} else {
		return logical();
	}
}

M test/pwd => test/pwd +3 -3
@@ 12,13 12,13 @@ should_work() (
)

should_handle_symlink() (
    cd "$TMPDIR/dir-f"
    cd "$TMPDIR/dir-s"
    ct="$(pwd -L)"
    [ "$ct" = "$TMPDIR/dir-f" ]
    [ "$ct" = "$TMPDIR/dir-s" ]
)

should_resolve_symlink() (
    cd "$TMPDIR/dir-f"
    cd "$TMPDIR/dir-s"
    ct="$(pwd -P)"
    [ "$ct" = "$TMPDIR/dir-f" ]
)