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:
-
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.
-
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.
Background
On POSIX platforms, the
PWDenvironment variable represents (emphasis mine) “an absolute pathname of the current working directory.”The specific value of
PWDis important if the current working directory can be reached through multiple absolute paths, as is the case for subdirectories of/tmpon macOS. It affects the behavior ofos.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). APWDin/tmparises 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
PWDappropriately when setting theDirhas led to a number of subtle bugs within the Go project, such as:go:generate)Moreover, setting
PWDcorrectly is not trivial:PWDis required to be absolute, but theDirfield may be relative, leading to bugs like #46832 when the two do not match.Proposal
I propose two ergonomic improvements to the
exec.CmdAPI:If the
Envfield of anexec.Cmdis nil and itsDirfield is non-empty on a platform that usesPWD, the subprocess'sPWDvariable should be implicitly set to an appropriate absolute path (computed fromos.Getwd()ifDiris relative) when the command is started.Dirfield is empty, thePWDvariable (or lack thereof) would continue to be inherited from the Go process, as it is today.Envfield is non-nil, itsPWDvariable (or lack thereof) would continue to be used, as it is today.A new method,
(*Cmd).Environ, analogous toos.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 theEnvfield (if non-nil), oros.Environ()augmented with thePWDand/orSYSTEMROOTvariables that would otherwise be used.Part (1) alone would set a more appropriate
PWDfor existing commands that setDirand do not explicitly modify the environment — that is, commands for which the author almost certainly hasn't considered thePWDinteraction 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
PWDare changed at all.Compatibility
This proposal would set an updated
PWDvariable 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
Dirfield or a non-nilEnvfield.It may change the observable behavior of commands that have a nil
Envbut are sensitive to the value ofPWD, including commands that enumerate all variables in the environment. For commands that are sensitive to the value of thePWDvariable, it should nearly always be a strict improvement: the semantics of thePWDvariable 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
PWDis 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 theEnvfield to a slice with the desiredPWDvariable, including no such variable at all.Limitations
This proposal would not fix the
PWDvariable for commands that explicitly setEnvwith an incorrect or missingPWDtoday (such as from an explicit call toos.Environ()). That is a deliberate choice to bias toward maintaining compatibility, but may limit the scope of improvements.Alternatives considered
PWDvariable more or less aggressively:a. We could omit the
PWDfrom the command's environment if the Go process itself has noPWDvariable, even if the platform normally usesPWD.b. We could set
PWD(on platforms that use it) wheneverDiris non-empty andEnvdoes not include an explicit entry for it, analogous to the conditions under which we setSYSTEMROOTon Windows today. That would fix commands that set explicit variables but accidentally omitPWD, but would remove the ability to deliberately leavePWDcompletely unset when using theDirfield. (At best, it would be set to the empty string.)c. We could set
PWD(on platforms that use it) wheneverDiris non-empty andEnvincludes aPWDthat is not an absolute path that resolves toDir. 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 noncompliantPWD.In my opinion, options (a) or (b) would be reasonable but (c) would be too invasive.
Environmethod, we could add aSetDirmethod that both sets theDirfield and appends an appropriate value to theEnvfield, populating theEnvfield usingos.Environ()if it was previously nil.I worry that appending to the existing
Envfield might be a bit too subtle: it wouldn't be intuitively obvious to me whetherSetDirappends toEnv(potentially overwriting entries in aliased slices), or toEnv[:len(Env):len(Env)](producing a possibly-unexpected new allocation). That could be addressed with documentation, but I think theEnvironmethod is easier to describe, especially by analogy toos.Environ.