cmd/tailscaled: handle tailscaled symlink on macOS
When Tailscale is installed via Homebrew, `/usr/local/bin/tailscaled` is a symlink to the actual binary. Now when `tailscaled install-system-daemon` runs, it will not attempt to overwrite that symlink if it already points to the tailscaled binary. However, if executed binary and the link target differ, the path will he overwritten - this can happen when a user decides to replace Homebrew-installed tailscaled with a one compiled from source code. Fixes #5353 Signed-off-by: Anton Tolchanov <anton@tailscale.com>
This commit is contained in:
parent
51d488673a
commit
c070d39287
|
@ -11,6 +11,7 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/fs"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
|
@ -83,6 +84,13 @@ func uninstallSystemDaemonDarwin(args []string) (ret error) {
|
|||
ret = err
|
||||
}
|
||||
}
|
||||
|
||||
// Do not delete targetBin if it's a symlink, which happens if it was installed via
|
||||
// Homebrew.
|
||||
if isSymlink(targetBin) {
|
||||
return ret
|
||||
}
|
||||
|
||||
if err := os.Remove(targetBin); err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
err = nil
|
||||
|
@ -107,40 +115,24 @@ func installSystemDaemonDarwin(args []string) (err error) {
|
|||
// Best effort:
|
||||
uninstallSystemDaemonDarwin(nil)
|
||||
|
||||
// Copy ourselves to /usr/local/bin/tailscaled.
|
||||
if err := os.MkdirAll(filepath.Dir(targetBin), 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
exe, err := os.Executable()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to find our own executable path: %w", err)
|
||||
}
|
||||
tmpBin := targetBin + ".tmp"
|
||||
f, err := os.Create(tmpBin)
|
||||
|
||||
same, err := sameFile(exe, targetBin)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
self, err := os.Open(exe)
|
||||
if err != nil {
|
||||
f.Close()
|
||||
return err
|
||||
}
|
||||
_, err = io.Copy(f, self)
|
||||
self.Close()
|
||||
if err != nil {
|
||||
f.Close()
|
||||
return err
|
||||
}
|
||||
if err := f.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := os.Chmod(tmpBin, 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := os.Rename(tmpBin, targetBin); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Do not overwrite targetBin with the binary file if it it's already
|
||||
// pointing to it. This is primarily to handle Homebrew that writes
|
||||
// /usr/local/bin/tailscaled is a symlink to the actual binary.
|
||||
if !same {
|
||||
if err := copyBinary(exe, targetBin); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := os.WriteFile(sysPlist, []byte(darwinLaunchdPlist), 0700); err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -155,3 +147,55 @@ func installSystemDaemonDarwin(args []string) (err error) {
|
|||
|
||||
return nil
|
||||
}
|
||||
|
||||
// copyBinary copies binary file `src` into `dst`.
|
||||
func copyBinary(src, dst string) error {
|
||||
if err := os.MkdirAll(filepath.Dir(dst), 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
tmpBin := dst + ".tmp"
|
||||
f, err := os.Create(tmpBin)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
srcf, err := os.Open(src)
|
||||
if err != nil {
|
||||
f.Close()
|
||||
return err
|
||||
}
|
||||
_, err = io.Copy(f, srcf)
|
||||
srcf.Close()
|
||||
if err != nil {
|
||||
f.Close()
|
||||
return err
|
||||
}
|
||||
if err := f.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := os.Chmod(tmpBin, 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := os.Rename(tmpBin, dst); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func isSymlink(path string) bool {
|
||||
fi, err := os.Lstat(path)
|
||||
return err == nil && (fi.Mode()&os.ModeSymlink == os.ModeSymlink)
|
||||
}
|
||||
|
||||
// sameFile returns true if both file paths exist and resolve to the same file.
|
||||
func sameFile(path1, path2 string) (bool, error) {
|
||||
dst1, err := filepath.EvalSymlinks(path1)
|
||||
if err != nil && !errors.Is(err, fs.ErrNotExist) {
|
||||
return false, fmt.Errorf("EvalSymlinks(%s): %w", path1, err)
|
||||
}
|
||||
dst2, err := filepath.EvalSymlinks(path2)
|
||||
if err != nil && !errors.Is(err, fs.ErrNotExist) {
|
||||
return false, fmt.Errorf("EvalSymlinks(%s): %w", path2, err)
|
||||
}
|
||||
return dst1 == dst2, nil
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue