| // 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. |
| |
| //go:build darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris |
| // +build darwin dragonfly freebsd linux netbsd openbsd solaris |
| |
| package lsprpc |
| |
| import ( |
| "crypto/sha256" |
| "errors" |
| "fmt" |
| exec "golang.org/x/sys/execabs" |
| "log" |
| "os" |
| "os/user" |
| "path/filepath" |
| "strconv" |
| "syscall" |
| |
| "golang.org/x/xerrors" |
| ) |
| |
| func init() { |
| startRemote = startRemotePosix |
| autoNetworkAddress = autoNetworkAddressPosix |
| verifyRemoteOwnership = verifyRemoteOwnershipPosix |
| } |
| |
| func startRemotePosix(goplsPath string, args ...string) error { |
| cmd := exec.Command(goplsPath, args...) |
| cmd.SysProcAttr = &syscall.SysProcAttr{ |
| Setsid: true, |
| } |
| if err := cmd.Start(); err != nil { |
| return xerrors.Errorf("starting remote gopls: %w", err) |
| } |
| return nil |
| } |
| |
| // autoNetworkAddress resolves an id on the 'auto' pseduo-network to a |
| // real network and address. On unix, this uses unix domain sockets. |
| func autoNetworkAddressPosix(goplsPath, id string) (network string, address string) { |
| // Especially when doing local development or testing, it's important that |
| // the remote gopls instance we connect to is running the same binary as our |
| // forwarder. So we encode a short hash of the binary path into the daemon |
| // socket name. If possible, we also include the buildid in this hash, to |
| // account for long-running processes where the binary has been subsequently |
| // rebuilt. |
| h := sha256.New() |
| cmd := exec.Command("go", "tool", "buildid", goplsPath) |
| cmd.Stdout = h |
| var pathHash []byte |
| if err := cmd.Run(); err == nil { |
| pathHash = h.Sum(nil) |
| } else { |
| log.Printf("error getting current buildid: %v", err) |
| sum := sha256.Sum256([]byte(goplsPath)) |
| pathHash = sum[:] |
| } |
| shortHash := fmt.Sprintf("%x", pathHash)[:6] |
| user := os.Getenv("USER") |
| if user == "" { |
| user = "shared" |
| } |
| basename := filepath.Base(goplsPath) |
| idComponent := "" |
| if id != "" { |
| idComponent = "-" + id |
| } |
| runtimeDir := os.TempDir() |
| if xdg := os.Getenv("XDG_RUNTIME_DIR"); xdg != "" { |
| runtimeDir = xdg |
| } |
| return "unix", filepath.Join(runtimeDir, fmt.Sprintf("%s-%s-daemon.%s%s", basename, shortHash, user, idComponent)) |
| } |
| |
| func verifyRemoteOwnershipPosix(network, address string) (bool, error) { |
| if network != "unix" { |
| return true, nil |
| } |
| fi, err := os.Stat(address) |
| if err != nil { |
| if os.IsNotExist(err) { |
| return true, nil |
| } |
| return false, xerrors.Errorf("checking socket owner: %w", err) |
| } |
| stat, ok := fi.Sys().(*syscall.Stat_t) |
| if !ok { |
| return false, errors.New("fi.Sys() is not a Stat_t") |
| } |
| user, err := user.Current() |
| if err != nil { |
| return false, xerrors.Errorf("checking current user: %w", err) |
| } |
| uid, err := strconv.ParseUint(user.Uid, 10, 32) |
| if err != nil { |
| return false, xerrors.Errorf("parsing current UID: %w", err) |
| } |
| return stat.Uid == uint32(uid), nil |
| } |