-
Notifications
You must be signed in to change notification settings - Fork 18k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
os/exec: Command does a LookPath in the wrong place. #7228
Labels
Milestone
Comments
We're unlikely to change the behavior at this point, for compatibility reasons. Also, there's a work-around: don't use the exec.Command utility function. Just build the Cmd by hand if that's what you want. If anything, this might be a documentation issue. Documentation suggestions welcome. Labels changed: added release-go1.3maybe, repo-main. Status changed to WaitingForReply. |
Hi Brad, This bug lead to a nasty issue where we were convinced that something did work when it actually didn't. Also, the error message given was very confusing since initially it seemed like the stat system call was failing, except it was impossible to see why. Surely this could be fixed by: if LookPath returns an error, check to see if the cmd.Path is valid with respect to cmd.Dir? Then you couldn't end up in the extremely confusing situation where the success of the Command() call depends on the existence of a file which is not being executed. |
I don't think Cmd.Dir should affect the behavior of the implicit LookPath. The LookPath part is done by the current process, while the Dir is where the new process is being executed. Your proposed fix may change the behavior of existing programs, and I don't think it's the right thing to do anyway. If you know the directory you want to run in, use exec.Command(filepath.Join(dir, bin)) in the first place. Agree that there might be some documentation concerns here. |
Comment 4 by peter@scraperwiki.com: Hm. I'm not sure documentation would have prevented the multiple hours it took for us to understand why our program wasn't working, because the principle of least surprise is violated. Here's the story: We were in the unfortunate situation where something called "./foo" happened to be in the working directory of the process and executable, by happen-stance. Then we did the equivalent of Command(Dir: bar, Path: ./foo).Run(), and bar/foo (a completely different program with the same name) was correctly executed, as expected. At some time much later "./foo" was removed from the filesystem. At this point the program stopped functioning with the error "error: exec: "./foo": stat ./foo: no such file or directory". It was very hard to comprehend why the behaviour of the program had changed since the critical fact was that the behaviour of the program depended on something which had never been executed by our program in the past. The effect of fixing this that I would like to have is to remove that dependency, because it was very hard to find. It would have been the case if there was documentation in front of my face because I had *seen it work correctly* before and I had exec.Command's source code in front of me. I was thinking that I was seeing a filesystem problem. I'm happy that I can use exec.Command(filepath.Join()) and that maybe LookPath shouldn't look in cmd.Dir, but can this surprising behaviour really not be fixed? I understand and appreciate the importance of not changing the behaviour of programs in the wild, but can we demonstrate that it would be harmful to fix the issue as I've described it here? |
If you can think of a fix that doesn't change break any existing valid programs, perhaps. But since we currently do LookPath in the current directory, we can't remove that. So then if we changed things, we'd be in a situation where we first LookPath in the current directory and only if that fails, then LookPath in the cmd.Dir later at Run time. That's even more surprising: now there's two paths that are searched, and it depends on the context (current dir) of the program where it was run which one is used. So I really doubt we can change it. Maybe we can improve the error messages, though. |
So what's the proposed change? That we add a second LookPath from Cmd.Dir on Run, but only if the first one failed? To do that we'd need to add more unexported state to the Cmd struct to signify that the Command function was used to construct the Cmd and that the LookPath failed. I have three concerns with this change: 1. It makes the behavior of Command and Cmd's run methods do surprising things. I certainly wouldn't expect it to try LookPath in two separate locations. 2. It will change the behavior of existing programs. Even if not technically backward compatible, it adds complexity. 3. I just don't think it's the right behavior. The directory in which you exec a process has nothing to do with the location of the process binary. Also, you're specifying the command path before setting the Dir field, so it's not clear that you *should* be expecting it to look inside Dir for the binary. (You yourself said that if you hadn't seen it apparently working before, you wouldn't have thought it would be the case.) I think this is just one of those instances in debugging where your assumptions were wrong and you went down a blind alley. Happens to the best of us. This is all IMO, and my word is not necessarily final. I am happy to discuss it further. |
Comment 7 by david@scraperwiki.com: With respect to #3 "The directory in which you exec a process has nothing to do with the location of the process binary": if you think that is an assertion about the current behaviour then you are wrong. Look at the original example. The command is "./ls", but the process binary that gets executed is "/bin/ls". |
I would be incredibly surprised that LookPath("./ls") should return "/bin/ls" when run outside of "/bin". This program: package main import ( "fmt" "os/exec" ) func main() { fmt.Println(exec.LookPath("./ls")) fmt.Println(exec.LookPath("ls")) fmt.Println(exec.Command("./ls").Run()) fmt.Println(exec.Command("ls").Run()) } Returns this output on my system: $ go run lookpath.go exec: "./ls": stat ./ls: no such file or directory /bin/ls <nil> exec: "./ls": stat ./ls: no such file or directory <nil> which seems correct to me. And if I "cd /bin" I see what I would expect also: $ cd /bin $ go run /Users/adg/test/lookpath.go ./ls <nil> /bin/ls <nil> <nil> <nil> |
Comment 10 by david@scraperwiki.com: #8 I agree LookPath returns "./ls", but when .Dir is set before .Run() is called, then the actual process binary that gets run can be in another location. In the original example, why doesn't the executable script called "ls" in the current directory get executed (it is empty, so should have the same behaviour as true)? |
Ah, I see the problem now. That is really subtle. I had thought (wrongly) that forkexec was a singular syscall that takes a directory name as well as the program path. That's not the case. forkAndExecInChild is a function in the syscall package that forks, chdirs to the directory, and then execs the program. Knowing that, it's obvious why running "./ls" with a Dir field set would run the "ls" binary from the that directory, and never the one from the working directory. Ugh. |
On Unix systems, I suggest that if Command is called with a relative path (a path containing a '/' that does not start with a '/') then it should not call LookPath. That is in fact the documented behaviour. Calling LookPath in that case does not actually do anything except check whether the named file exists, which will be done by the exec call anyhow. Unfortunately we can't literally not call LookPath as that will do the wrong thing on Plan 9 and Windows in some cases. So instead I suggest that if Command is called with a relative path, and LookPath returns an exec.Error whose err value is an os.IsNotExist error, Command should ignore the error. Status changed to Accepted. |
Comment 13 by david@scraperwiki.com: a@ and i@ I Agree. Command should ignore errors from LookPath if there is a slash in the path (possibly this could be done simply by not calling LookPath if there is a slash in the path, since we "know" LookPath won't do anything useful in that case anyway). |
Is this related to https://golang.org/issue/3622 ? Alex |
This is related to issue #3622, but it's not the same, as this is really about the use of LookPath from Command. |
This assumption https://golang.org/issue/3622?c=2 is wrong, and the fact that LookPath can return both absolute and relative paths is documented now. Perhaps use of LookPath in Command can be revisited again. Perhaps we can fix the problem inside os/exec. Alex PS: I didn't investigate the problem properly. I'm just talking to myself. |
adg@ and I discussed this last night and also agreed we could change this, realizing the same thing, that LookPath returns a relative path. (I think originally it was supposed to return an absolute path but nothing depended on that, it wasn't tested, and it wasn't documented...) Owner changed to @bradfitz. |
This issue was closed by revision e6d8bfe. Status changed to Fixed. |
This issue was closed.
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
by david@scraperwiki.com:
The text was updated successfully, but these errors were encountered: