Repository: go
Issue: path/filepath: Clean strips trailing slash on some Windows device paths
Labels: OS-Windows,NeedsFix

### Go version

go version go1.22.4 windows/amd64

### Output of `go env` in your module/workspace:

```shell
set GO111MODULE=
set GOARCH=amd64
set GOBIN=
set GOCACHE=C:\Users\Hakkin\AppData\Local\go-build
set GOENV=C:\Users\Hakkin\AppData\Roaming\go\env
set GOEXE=.exe
set GOEXPERIMENT=
set GOFLAGS=
set GOHOSTARCH=amd64
set GOHOSTOS=windows
set GOINSECURE=
set GOMODCACHE=C:\Users\Hakkin\Desktop\Programs\Misc\Projects\Go\pkg\mod
set GONOPROXY=
set GONOSUMDB=
set GOOS=windows
set GOPATH=C:\Users\Hakkin\Desktop\Programs\Misc\Projects\Go
set GOPRIVATE=
set GOPROXY=https://proxy.golang.org,direct
set GOROOT=C:\Program Files\Go
set GOSUMDB=sum.golang.org
set GOTMPDIR=
set GOTOOLCHAIN=auto
set GOTOOLDIR=C:\Program Files\Go\pkg\tool\windows_amd64
set GOVCS=
set GOVERSION=go1.22.4
set GCCGO=gccgo
set GOAMD64=v1
set AR=ar
set CC=gcc
set CXX=g++
set CGO_ENABLED=1
set GOMOD=NUL
set GOWORK=
set CGO_CFLAGS=-O2 -g
set CGO_CPPFLAGS=
set CGO_CXXFLAGS=-O2 -g
set CGO_FFLAGS=-O2 -g
set CGO_LDFLAGS=-O2 -g
set PKG_CONFIG=pkg-config
set GOGCCFLAGS=-m64 -mthreads -Wl,--no-gc-sections -fmessage-length=0 -ffile-prefix-map=C:\Users\Hakkin\AppData\Local\Temp\go-build1491048773=/tmp/go-build -gno-record-gcc-switches
```


### What did you do?

Opening this issue in response to https://github.com/golang/go/issues/67834#issuecomment-2154302340

Trying to use `filepath.Clean` on Windows device paths that are more than 1 level deep results in the trailing backslash being removed from the path.
Example code:

```go
package main

import (
	"log"
	"os"
	"path/filepath"
)

func main() {
	// Example paths, theoretically all point to the same device/volume
	devicePaths := []string{
		`\\?\C:\`,
		`\\?\Volume{00000000-0000-0000-0000-000000000000}\`,
		`\\?\GLOBALROOT\Device\HarddiskVolume1\`,
	}

	for _, devicePath := range devicePaths {
		cleanPath := filepath.Clean(devicePath)
		hasBackslash := devicePath[len(cleanPath)-1] == os.PathSeparator
		log.Printf("has backslash: %v \tcleaned path: %s", hasBackslash, cleanPath)
	}
}
```

### What did you see happen?

```console
$ go run main.go 
has backslash: true         cleaned path: \\?\C:\
has backslash: true         cleaned path: \\?\Volume{00000000-0000-0000-0000-000000000000}\
has backslash: false        cleaned path: \\?\GLOBALROOT\Device\HarddiskVolume1
```

### What did you expect to see?

The path `\\?\GLOBALROOT\Device\HarddiskVolume1\` should not have the trailing backslash trimmed, since it is a root device path and is equivalent to the other two paths (in fact, internally, the first two paths are symbolic object links to the third path, so it is the true canonical device path). The first two forms in the example code are already handled by a special case in the Go code, but it only handles paths that are "top-level" directories.

Windows offers a variety of ways to access device paths like this, you can read more [here](https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file) and [here](https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-volume).

Paths in the form of `\\?\GLOBALROOT\Device\...` are returned by some Windows APIs, notably the Windows Shadow Copy API (device paths are returned in the form of `\\?\GLOBALROOT\Device\HarddiskVolumeShadowCopy...`).

Because the Windows Object Namespace supports symbolic linking, there are many (infinite?) paths to access the same device, for example, the `Volume{...}` path in the above example can also be accessed using the path `\\?\GLOBALROOT\GLOBAL??\Volume{00000000-0000-0000-0000-000000000000}\`. `GLOBALROOT` is a symlink to the root of the NT Object namespace, `GLOBAL??` is a directory for the Win32 namespace, and then `Volume{...}` is a symlink to `\Device\HarddiskVolume...`.

I think properly resolving all paths of this kind would be difficult, but at the very least, adding the canonical `\Device\...` paths to the special handling should probably be fairly straightforward.

Related issues https://github.com/golang/go/issues/64028 https://github.com/golang/go/issues/67834
and copying my comment from https://github.com/golang/go/issues/67834#issuecomment-2151928869 here:
> filepath.Clean does actually have special handling for these paths, but only in specific circumstances. This mostly seems to be handled in `volumeNameLen` here:
> 
> https://github.com/golang/go/blob/45967bb18e04fa6dc62c2786c87ce120443c64f6/src/internal/filepathlite/path_windows.go#L228-L243
> 
> then `Clean` uses this here:
> 
> https://github.com/golang/go/blob/45967bb18e04fa6dc62c2786c87ce120443c64f6/src/internal/filepathlite/path.go#L66-L75
> 
> So filepath.Clean does maintain the trailing slash for paths like `\\.\C:\`, but doesn't for paths like `\\?\GLOBALROOT\Device\HarddiskVolumeShadowCopy1\`, since it treats `GLOBALROOT` as the volume name instead of the full device path.