Skip to content

path/filepath: filepath.Join() removes dir if it is just a dot #70711

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

Closed
TLINDEN opened this issue Dec 6, 2024 · 5 comments
Closed

path/filepath: filepath.Join() removes dir if it is just a dot #70711

TLINDEN opened this issue Dec 6, 2024 · 5 comments

Comments

@TLINDEN
Copy link

TLINDEN commented Dec 6, 2024

Go version

go version go1.22.1 linux/amd64

Output of go env in your module/workspace:

GO111MODULE=''
GOARCH='amd64'
GOBIN=''
GOCACHE='/home/foo/.cache/go-build'
GOENV='/home/foo/.config/go/env'
GOEXE=''
GOEXPERIMENT=''
GOFLAGS=''
GOHOSTARCH='amd64'
GOHOSTOS='linux'
GOINSECURE=''
GOMODCACHE='/home/foo/go/pkg/mod'
GONOPROXY=''
GONOSUMDB=''
GOOS='linux'
GOPATH='/home/foo/go'
GOPRIVATE=''
GOPROXY='https://proxy.golang.org,direct'
GOROOT='/usr/local/go1.22.1.linux-amd64'
GOSUMDB='sum.golang.org'
GOTMPDIR=''
GOTOOLCHAIN='auto'
GOTOOLDIR='/usr/local/go1.22.1.linux-amd64/pkg/tool/linux_amd64'
GOVCS=''
GOVERSION='go1.22.1'
GCCGO='gccgo'
GOAMD64='v1'
AR='ar'
CC='gcc'
CXX='g++'
CGO_ENABLED='1'
GOMOD='/home/foo/bar/git/nachweis/go.mod'
GOWORK=''
CGO_CFLAGS='-O2 -g'
CGO_CPPFLAGS=''
CGO_CXXFLAGS='-O2 -g'
CGO_FFLAGS='-O2 -g'
CGO_LDFLAGS='-O2 -g'
PKG_CONFIG='pkg-config'
GOGCCFLAGS='-fPIC -m64 -pthread -Wl,--no-gc-sections -fmessage-length=0 -ffile-prefix-map=/tmp/go-build3844692246=/tmp/go-build -gno-record-gcc-switches'

What did you do?

I am working on an inhouse commandline client to generate PDF reports. The client supports various input options, one of them is an auto discover feature. Users call it like -a <dir>. The client then digs through the given directory for various kinds of evidence to included into the report. One possibility are shell scripts which will be executed. I execute those by piping the script name into a bash call, like:

[..]
cmd := exec.Command("bash")
cmd.Stdin = strings.NewReader(collector.Script) // string, e.g. "dir/script.sh" or "ifconfig -a"
[..]

The reason is, that it is also possible to specify a shell command directly using another option (-e "kubectl get pods"), which runs the same code.

The script name will be concatenated using filepath.Join(dir, file).

So, this all works fine and dandy, but if the user executes it inside the current directory and specifies -a ., then the . will be removed by filepath.Join().

In almost all thinkable cases this might be the expected behavior. However, since I am piping the script name into bash's STDIN, bash then considers it to be a command and because there's no path component it searches it in $PATH, which of course fails, e.g.:

bash: line 1: Secrets-Installed.sh: command not found

So, my current workaround looks like this:

path := filepath.Join(dir, file)
if dir == "." {
  // FIXME: filepath.Join() doesn't do that
  path = "./" + file
}

This works, but isn't portable anymore.

I'd like to have a portable solution without me writing code to verify the runtime operating system, if possible.

Thanks in advance,
Tom

What did you see happen?

While I understand the current behavior, it is not documented and is an decision I am unable to influence. Yes, changing the behavior will break existing code, so this would or course not be an option. But maybe you can consider to add some kind of flag so that one is able to force join a single dot directory, or something like that.

What did you expect to see?

See description above.

@Jorropo Jorropo changed the title import/path: filepath.Join() removes dir if it is just a dot path/filepath: filepath.Join() removes dir if it is just a dot Dec 6, 2024
@Jorropo
Copy link
Member

Jorropo commented Dec 6, 2024

Imo filepath.Join is correct here, it creates a valid path for your OS.
The fact you need a leading ./ is a behavior of your shell.

A safe solution would need to be more complex than leading ./ because there are plenty of paths which are valid according to the OS but would break a bash expression.
To me it looks like you need a dedicated bash filepath escape function and I don't know if that has place in the std.

I'm also curious why your current workaround is not portable, afait it produce valid paths on everything but windows, however I would be surprised if you are invoking bash on windows.

@TLINDEN
Copy link
Author

TLINDEN commented Dec 6, 2024

I'm also curious why your current workaround is not portable, afait it produce valid paths on everything but windows, however I would be surprised if you are invoking bash on windows.

Hmm, now that you wrote it, I have to admit that this is indeed true :)

However, I also tried another workaround (a better one to my taste): I am checking if the given parameter is an existing file and in that case I call it directly:

if collector.Script != "" {
	// run directly
	cmd = exec.Command(collector.Script)
} else {
	// pipe code into bash
	cmd = exec.Command("bash")
	cmd.Stdin = strings.NewReader(collector.Code)
}

So now with scripts, there's no bash involved anymore. But the error persists of course:

exec: \"heutiges-datum.sh\": executable file not found in $PATH

So I think I'm going to modify $PATH by adding . if the given script has no path component. But it's not nice as well...

@TLINDEN
Copy link
Author

TLINDEN commented Dec 6, 2024

No, I am using filepath.Abs() if the script file exists. That way I don't have to cope with the dot in $PATH.

So, from my point of view, this could be closed.

And thanks a lot for the amazingly fast response!

@Jorropo
Copy link
Member

Jorropo commented Dec 6, 2024

So, from my point of view, this could be closed.

✨ if someone else would care about this it could be reopened.

@Jorropo Jorropo closed this as not planned Won't fix, can't repro, duplicate, stale Dec 6, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants