Skip to content

os/exec: improved API and default behavior for the PWD env variable #50599

@bcmills

Description

@bcmills

Background

On POSIX platforms, the PWD environment variable represents (emphasis mine) “an absolute pathname of the current working directory.”

The specific value of PWD is important if the current working directory can be reached through multiple absolute paths, as is the case for subdirectories of /tmp on macOS. It affects the behavior of os.Getwd (see #8400), which can in turn affect both the error and non-error output of programs, including behaviors like paths in debug metadata (which can invalidate build caches if skewed) and parent-directory lookup (important for, among other things, identifying the current Go workspace and module). A PWD in /tmp arises naturally in any Go test that executes a subprocesses within a directory obtained by calling (*testing.T).TempDir, which is (or ought to be!) common for tests of command-line binaries.

Failing to set PWD appropriately when setting the Dir has led to a number of subtle bugs within the Go project, such as:

Moreover, setting PWD correctly is not trivial: PWD is required to be absolute, but the Dir field may be relative, leading to bugs like #46832 when the two do not match.

Proposal

I propose two ergonomic improvements to the exec.Cmd API:

  1. If the Env field of an exec.Cmd is nil and its Dir field is non-empty on a platform that uses PWD, the subprocess's PWD variable should be implicitly set to an appropriate absolute path (computed from os.Getwd() if Dir is relative) when the command is started.

    • If the Dir field is empty, the PWD variable (or lack thereof) would continue to be inherited from the Go process, as it is today.
    • If the Env field is non-nil, its PWD variable (or lack thereof) would continue to be used, as it is today.
  2. A new method, (*Cmd).Environ, analogous to os.Environ(), should return a copy of the environment that would be used by the command in its current configuration: either a copy of the current value of the Env field (if non-nil), or os.Environ() augmented with the PWD and/or SYSTEMROOT variables that would otherwise be used.

Part (1) alone would set a more appropriate PWD for existing commands that set Dir and do not explicitly modify the environment — that is, commands for which the author almost certainly hasn't considered the PWD interaction at all.

Part (2) would make it easier to set up a command that runs in a particular directory with a small number of overridden environment variables — the common case when environment variables other than PWD are changed at all.

Compatibility

This proposal would set an updated PWD variable for some commands that today receive a stale one (or none at all) inherited from the parent Go process.

It would have no effect on commands that have an empty Dir field or a non-nil Env field.

It may change the observable behavior of commands that have a nil Env but are sensitive to the value of PWD, including commands that enumerate all variables in the environment. For commands that are sensitive to the value of the PWD variable, it should nearly always be a strict improvement: the semantics of the PWD variable are explicitly described in the POSIX standard, and this change would bring the behavior in line with that standard on POSIX-like platforms.

The only case I can conceive of in which a user explicitly wants to inherit a missing or mismatched PWD is when testing the behavior of a command-line binary in a non-POSIX-compliant environment. However, for that rare case it would be straightforward for the user to explicitly set the Env field to a slice with the desired PWD variable, including no such variable at all.

Limitations

This proposal would not fix the PWD variable for commands that explicitly set Env with an incorrect or missing PWD today (such as from an explicit call to os.Environ()). That is a deliberate choice to bias toward maintaining compatibility, but may limit the scope of improvements.

Alternatives considered

  • We could set the PWD variable more or less aggressively:
    a. We could omit the PWD from the command's environment if the Go process itself has no PWD variable, even if the platform normally uses PWD.
    b. We could set PWD (on platforms that use it) whenever Dir is non-empty and Env does not include an explicit entry for it, analogous to the conditions under which we set SYSTEMROOT on Windows today. That would fix commands that set explicit variables but accidentally omit PWD, but would remove the ability to deliberately leave PWD completely unset when using the Dir field. (At best, it would be set to the empty string.)
    c. We could set PWD (on platforms that use it) whenever Dir is non-empty and Env includes a PWD that is not an absolute path that resolves to Dir. That would enforce compliance with the POSIX standard for the variable, but may be expensive to detect, and would remove the ability to deliberately use a noncompliant PWD.

In my opinion, options (a) or (b) would be reasonable but (c) would be too invasive.

  • Instead of the proposed Environ method, we could add a SetDir method that both sets the Dir field and appends an appropriate value to the Env field, populating the Env field using os.Environ() if it was previously nil.

I worry that appending to the existing Env field might be a bit too subtle: it wouldn't be intuitively obvious to me whether SetDir appends to Env (potentially overwriting entries in aliased slices), or to Env[:len(Env):len(Env)] (producing a possibly-unexpected new allocation). That could be addressed with documentation, but I think the Environ method is easier to describe, especially by analogy to os.Environ.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions