1
0
mirror of https://github.com/golang/go synced 2024-10-01 12:28:37 -06:00
go/internal/lsp/cache/os_darwin.go
Heschi Kreinick 82bb89366a internal/lsp/cache: validate workspace path case
On case-insensitive file systems, the editor may send us a path that
works but doesn't match the actual file's case. Then when we run go
list, we'll get mismatching paths and everything will break.

We can't reliably fix this problem: tracking what case the editor
expects is too difficult to be worth it. Instead, check the workspace
path and bail if it's mismatched.

Possibly we should also check files on DidOpen or such, but we can start
with this.

Updates golang/go#36904.

Change-Id: I7635c8136bf9400db4143a0f2fde25c39b5abc44
Reviewed-on: https://go-review.googlesource.com/c/tools/+/225239
Reviewed-by: Ian Cottrell <iancottrell@google.com>
2020-03-27 19:55:53 +00:00

59 lines
1.7 KiB
Go

// Copyright 2020 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package cache
import (
"bytes"
"fmt"
"os"
"path/filepath"
"strings"
"syscall"
"unsafe"
)
func init() {
checkPathCase = darwinCheckPathCase
}
func darwinCheckPathCase(path string) error {
// Darwin provides fcntl(F_GETPATH) to get a path for an arbitrary FD.
// Conveniently for our purposes, it gives the canonical case back. But
// there's no guarantee that it will follow the same route through the
// filesystem that the original path did.
path, err := filepath.Abs(path)
if err != nil {
return err
}
fd, err := syscall.Open(path, os.O_RDONLY, 0)
if err != nil {
return err
}
defer syscall.Close(fd)
buf := make([]byte, 4096) // No MAXPATHLEN in syscall, I think it's 1024, this is bigger.
// Wheeee! syscall doesn't expose a way to call Fcntl except FcntlFlock.
// As of writing, it just passes the pointer through, so we can just lie.
if err := syscall.FcntlFlock(uintptr(fd), syscall.F_GETPATH, (*syscall.Flock_t)(unsafe.Pointer(&buf[0]))); err != nil {
return err
}
buf = buf[:bytes.IndexByte(buf, 0)]
isRoot := func(p string) bool {
return p[len(p)-1] == filepath.Separator
}
// Darwin seems to like having multiple names for the same folder. Match as much of the suffix as we can.
for got, want := path, string(buf); !isRoot(got) && !isRoot(want); got, want = filepath.Dir(got), filepath.Dir(want) {
g, w := filepath.Base(got), filepath.Base(want)
if !strings.EqualFold(g, w) {
break
}
if g != w {
return fmt.Errorf("case mismatch in path %q: component %q should be %q", path, g, w)
}
}
return nil
}